Skip to main content

git_pincer/commands/
run.rs

1//! merge / rebase / pull: launch a git operation and take over the conflict resolution that follows.
2
3use std::path::Path;
4
5use anyhow::{Result, bail};
6use clap::Args;
7
8use super::resolve::resolve_loop;
9use crate::git::Git;
10
11/// merge 子命令参数。
12#[derive(Debug, Args)]
13pub struct MergeArgs {
14    /// 要合并进当前分支的目标引用(分支 / tag / commit)
15    pub target: String,
16}
17
18/// rebase 子命令参数。
19#[derive(Debug, Args)]
20pub struct RebaseArgs {
21    /// 变基的目标引用
22    pub target: String,
23}
24
25/// pull 子命令参数(全部透传给 git pull)。
26#[derive(Debug, Args)]
27pub struct PullArgs {
28    /// 透传给 git pull 的参数,如 `origin main` / `--rebase`
29    #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
30    pub args: Vec<String>,
31}
32
33/// cherry-pick 子命令参数(全部透传,可带多个提交与选项)。
34#[derive(Debug, Args)]
35pub struct CherryPickArgs {
36    /// 透传给 git cherry-pick 的参数,如 `abc123` / `-x A B`
37    #[arg(trailing_var_arg = true, allow_hyphen_values = true, required = true)]
38    pub args: Vec<String>,
39}
40
41/// revert 子命令参数(全部透传,可带多个提交与选项)。
42#[derive(Debug, Args)]
43pub struct RevertArgs {
44    /// 透传给 git revert 的参数,如 `abc123` / `HEAD~2..HEAD`
45    #[arg(trailing_var_arg = true, allow_hyphen_values = true, required = true)]
46    pub args: Vec<String>,
47}
48
49/// 执行 `git merge` 并接管冲突解决。
50pub fn merge(args: MergeArgs, verbose: bool, dir: &Path, light: bool) -> Result<()> {
51    launch(&["merge", "--no-edit", &args.target], verbose, dir, light)
52}
53
54/// 执行 `git rebase` 并接管冲突解决。
55pub fn rebase(args: RebaseArgs, verbose: bool, dir: &Path, light: bool) -> Result<()> {
56    launch(&["rebase", &args.target], verbose, dir, light)
57}
58
59/// 执行 `git pull`(参数透传)并接管冲突解决。
60pub fn pull(args: PullArgs, verbose: bool, dir: &Path, light: bool) -> Result<()> {
61    let mut cmd = vec!["pull"];
62    cmd.extend(args.args.iter().map(String::as_str));
63    launch(&cmd, verbose, dir, light)
64}
65
66/// 执行 `git cherry-pick`(参数透传)并接管冲突解决(多提交会多轮循环)。
67pub fn cherry_pick(args: CherryPickArgs, verbose: bool, dir: &Path, light: bool) -> Result<()> {
68    let mut cmd = vec!["cherry-pick"];
69    cmd.extend(args.args.iter().map(String::as_str));
70    launch(&cmd, verbose, dir, light)
71}
72
73/// 执行 `git revert`(参数透传)并接管冲突解决。
74pub fn revert(args: RevertArgs, verbose: bool, dir: &Path, light: bool) -> Result<()> {
75    let mut cmd = vec!["revert", "--no-edit"];
76    cmd.extend(args.args.iter().map(String::as_str));
77    launch(&cmd, verbose, dir, light)
78}
79
80/// 共享编排:透传执行 git 操作;干净则结束,产生冲突则进入解决循环。
81fn launch(initial: &[&str], verbose: bool, dir: &Path, light: bool) -> Result<()> {
82    let git = Git::discover(dir, verbose)?;
83    println!("[git-pincer] $ git {}", initial.join(" "));
84    let status = git.run_inherit(initial)?;
85    if status.success() {
86        // git 已输出自身的合并结果,不再重复旁白
87        return Ok(());
88    }
89    if git.conflicted_files()?.is_empty() {
90        bail!(
91            "git {} execution failed, please check the Git output messages",
92            initial.join(" ")
93        );
94    }
95    resolve_loop(&git, light)
96}
97
98/// 菜单模式的一次 git 执行结果。
99#[derive(Debug)]
100pub enum LaunchOutcome {
101    /// 顺利完成,无冲突(附捕获的输出,供回放)
102    Success(std::process::Output),
103    /// 产生冲突,等待接管解决(附捕获的输出,供回放)
104    Conflicts(std::process::Output),
105    /// 非冲突失败(附整理后的失败原因,供弹框展示)
106    Failed(String),
107}
108
109/// 菜单模式编排:捕获输出执行 git 操作并判定结果。
110///
111/// 不打印、不进入任何 TUI,因此可以在菜单会话仍打开时调用
112/// (失败弹框无需退出再重进 TUI,避免闪屏);
113/// 输出的回放时机由调用方决定(离开 TUI 之后)。
114pub fn launch_captured(git: &Git, initial: &[&str]) -> Result<LaunchOutcome> {
115    let out = git.run(initial)?;
116    if out.status.success() {
117        return Ok(LaunchOutcome::Success(out));
118    }
119    if git.conflicted_files()?.is_empty() {
120        let stderr = String::from_utf8_lossy(&out.stderr).trim().to_owned();
121        let stdout = String::from_utf8_lossy(&out.stdout).trim().to_owned();
122        let reason = if stderr.is_empty() { stdout } else { stderr };
123        return Ok(LaunchOutcome::Failed(reason));
124    }
125    Ok(LaunchOutcome::Conflicts(out))
126}