Skip to main content

git_spawn/command/
cherry_pick.rs

1//! `git cherry-pick` — apply the changes introduced by some existing commits.
2
3use crate::command::{CommandExecutor, CommandOutput, GitCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6
7/// Builder for `git cherry-pick`.
8#[derive(Debug, Clone, Default)]
9pub struct CherryPickCommand {
10    /// Shared executor.
11    pub executor: CommandExecutor,
12    /// Commits (or ranges) to pick.
13    pub commits: Vec<String>,
14    /// `--no-commit` / `-n`.
15    pub no_commit: bool,
16    /// `--edit`.
17    pub edit: bool,
18    /// `--signoff` / `-s`.
19    pub signoff: bool,
20    /// `-x` append "cherry picked from commit …".
21    pub reference: bool,
22    /// `--mainline N` (for merges).
23    pub mainline: Option<u32>,
24    /// `--strategy`.
25    pub strategy: Option<String>,
26    /// `--abort`.
27    pub abort: bool,
28    /// `--continue`.
29    pub cont: bool,
30    /// `--skip`.
31    pub skip: bool,
32    /// `--quit`.
33    pub quit: bool,
34    /// `--allow-empty`.
35    pub allow_empty: bool,
36    /// `--keep-redundant-commits`.
37    pub keep_redundant: bool,
38}
39
40impl CherryPickCommand {
41    /// New command.
42    #[must_use]
43    pub fn new() -> Self {
44        Self::default()
45    }
46
47    /// Add a commit / range.
48    pub fn commit(&mut self, c: impl Into<String>) -> &mut Self {
49        self.commits.push(c.into());
50        self
51    }
52
53    /// Do not commit automatically.
54    pub fn no_commit(&mut self) -> &mut Self {
55        self.no_commit = true;
56        self
57    }
58
59    /// Open the editor for the commit message.
60    pub fn edit(&mut self) -> &mut Self {
61        self.edit = true;
62        self
63    }
64
65    /// Add `Signed-off-by`.
66    pub fn signoff(&mut self) -> &mut Self {
67        self.signoff = true;
68        self
69    }
70
71    /// Append `(cherry picked from commit …)` to the message.
72    pub fn reference(&mut self) -> &mut Self {
73        self.reference = true;
74        self
75    }
76
77    /// For merge commits, specify the mainline parent.
78    pub fn mainline(&mut self, n: u32) -> &mut Self {
79        self.mainline = Some(n);
80        self
81    }
82
83    /// Merge strategy.
84    pub fn strategy(&mut self, s: impl Into<String>) -> &mut Self {
85        self.strategy = Some(s.into());
86        self
87    }
88
89    /// Abort an in-progress cherry-pick.
90    pub fn abort(&mut self) -> &mut Self {
91        self.abort = true;
92        self
93    }
94
95    /// Continue after resolving conflicts.
96    pub fn cont(&mut self) -> &mut Self {
97        self.cont = true;
98        self
99    }
100
101    /// Skip the current commit.
102    pub fn skip(&mut self) -> &mut Self {
103        self.skip = true;
104        self
105    }
106
107    /// Forget the in-progress cherry-pick state.
108    pub fn quit(&mut self) -> &mut Self {
109        self.quit = true;
110        self
111    }
112
113    /// Allow empty commits.
114    pub fn allow_empty(&mut self) -> &mut Self {
115        self.allow_empty = true;
116        self
117    }
118
119    /// Keep commits that become empty due to the pick.
120    pub fn keep_redundant(&mut self) -> &mut Self {
121        self.keep_redundant = true;
122        self
123    }
124}
125
126#[async_trait]
127impl GitCommand for CherryPickCommand {
128    type Output = CommandOutput;
129    fn get_executor(&self) -> &CommandExecutor {
130        &self.executor
131    }
132    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
133        &mut self.executor
134    }
135    fn build_command_args(&self) -> Vec<String> {
136        let mut args = vec!["cherry-pick".to_string()];
137        if self.abort {
138            args.push("--abort".into());
139            return args;
140        }
141        if self.cont {
142            args.push("--continue".into());
143            return args;
144        }
145        if self.skip {
146            args.push("--skip".into());
147            return args;
148        }
149        if self.quit {
150            args.push("--quit".into());
151            return args;
152        }
153        if self.no_commit {
154            args.push("--no-commit".into());
155        }
156        if self.edit {
157            args.push("--edit".into());
158        }
159        if self.signoff {
160            args.push("--signoff".into());
161        }
162        if self.reference {
163            args.push("-x".into());
164        }
165        if self.allow_empty {
166            args.push("--allow-empty".into());
167        }
168        if self.keep_redundant {
169            args.push("--keep-redundant-commits".into());
170        }
171        if let Some(m) = self.mainline {
172            args.push("--mainline".into());
173            args.push(m.to_string());
174        }
175        if let Some(s) = &self.strategy {
176            args.push(format!("--strategy={s}"));
177        }
178        args.extend(self.commits.iter().cloned());
179        args
180    }
181    async fn execute(&self) -> Result<CommandOutput> {
182        self.execute_raw().await
183    }
184}