@@ -6,7 +6,7 @@ use anyhow::{Context as _, Result, bail};
66use bstr:: BString ;
77use but_core:: {
88 RepositoryExt as _,
9- commit:: { HEADERS_CONFLICTED_FIELD , Headers , TreeKind } ,
9+ commit:: { HEADERS_CONFLICTED_FIELD , Headers , SignCommit , TreeKind } ,
1010} ;
1111use gix:: { objs:: tree:: EntryKind , prelude:: ObjectIdExt as _} ;
1212
@@ -35,7 +35,18 @@ pub enum CherryPickOutcome {
3535 } ,
3636}
3737
38- /// Cherry pick, but supports supports cherry-picking merge commits.
38+ /// Controls when a commit is cherry-picked during a rebase.
39+ #[ derive( Debug , Clone , Copy , PartialEq ) ]
40+ pub enum PickMode {
41+ /// Cherry-picks the commit only if it's necessary because of changes to the commit or its
42+ /// parents.
43+ IfChanged ,
44+ /// Forces a cherry-pick on a commit. This is for example useful in combination with
45+ /// [`SignCommit`] to sign/unsign commits that are otherwise unchanged.
46+ Force ,
47+ }
48+
49+ /// Cherry pick, but supports cherry-picking merge commits.
3950///
4051/// When cherry-picking a commit onto two or more commits, we first find the
4152/// merge of the two "onto" commits, and then cherry-pick onto that.
@@ -52,15 +63,19 @@ pub enum CherryPickOutcome {
5263///
5364/// Except in the case where X is conflicted. In that case we then make use of
5465/// X's "base" sub-tree as the base.
66+ ///
67+ /// `pick_mode` - controls how to determine if a commit should be cherry-picked.
68+ /// `sign_commit` - controls how the resulting commit is signed.
5569pub fn cherry_pick (
5670 repo : & gix:: Repository ,
5771 target : gix:: ObjectId ,
5872 ontos : & [ gix:: ObjectId ] ,
59- sign_if_configured : bool ,
73+ pick_mode : PickMode ,
74+ sign_commit : SignCommit ,
6075) -> Result < CherryPickOutcome > {
6176 let target = but_core:: Commit :: from_id ( target. attach ( repo) ) ?;
6277
63- if ontos == target. parents . as_slice ( ) {
78+ if ontos == target. parents . as_slice ( ) && pick_mode != PickMode :: Force {
6479 // We don't need to rebase
6580 return Ok ( CherryPickOutcome :: Identity ( target. id . detach ( ) ) ) ;
6681 }
@@ -72,9 +87,22 @@ pub fn cherry_pick(
7287 let onto_t = tree_from_merging_commits ( repo, ontos, TreeKind :: AutoResolution ) ?;
7388
7489 match ( & base_t, & onto_t) {
90+ ( MergeOutcome :: NoCommit , MergeOutcome :: NoCommit ) if pick_mode == PickMode :: Force => {
91+ // We should only end up here when trying to force-pick a parentless commit. At that
92+ // point, it's safe to simply recreate that commit outright.
93+ //
94+ // Currently, the only known use case for this is to forcibly sign/unsign root commits.
95+ let commit = crate :: commit:: create (
96+ repo,
97+ target. inner ,
98+ DateMode :: CommitterUpdateAuthorKeep ,
99+ sign_commit,
100+ ) ?;
101+ Ok ( CherryPickOutcome :: Commit ( commit) )
102+ }
75103 ( MergeOutcome :: NoCommit , MergeOutcome :: NoCommit ) => {
76104 // We shouldn't actually ever hit this because it should be handled
77- // by the ontos & parents comparison.
105+ // by the ontos & parents comparison or the PickMode::Force case .
78106 Ok ( CherryPickOutcome :: Identity ( target. id . detach ( ) ) )
79107 }
80108 // TODO(cto): We can handle the specific case where (the base is
@@ -130,15 +158,14 @@ pub fn cherry_pick(
130158 base_t,
131159 onto_t,
132160 target_t. detach ( ) ,
133- sign_if_configured ,
161+ sign_commit ,
134162 ) ?;
135163 Ok ( CherryPickOutcome :: ConflictedCommit (
136164 conflicted_commit. detach ( ) ,
137165 ) )
138166 } else {
139167 Ok ( CherryPickOutcome :: Commit (
140- commit_from_unconflicted_tree ( ontos, target, tree_id, sign_if_configured) ?
141- . detach ( ) ,
168+ commit_from_unconflicted_tree ( ontos, target, tree_id, sign_commit) ?. detach ( ) ,
142169 ) )
143170 }
144171 }
@@ -273,7 +300,7 @@ fn commit_from_unconflicted_tree<'repo>(
273300 parents : & [ gix:: ObjectId ] ,
274301 to_rebase : but_core:: Commit < ' repo > ,
275302 resolved_tree_id : gix:: Id < ' repo > ,
276- sign_if_configured : bool ,
303+ sign_commit : SignCommit ,
277304) -> anyhow:: Result < gix:: Id < ' repo > > {
278305 let repo = to_rebase. id . repo ;
279306
@@ -303,7 +330,7 @@ fn commit_from_unconflicted_tree<'repo>(
303330 repo,
304331 new_commit,
305332 DateMode :: CommitterUpdateAuthorKeep ,
306- sign_if_configured ,
333+ sign_commit ,
307334 ) ?
308335 . attach ( repo) )
309336}
@@ -318,7 +345,7 @@ fn commit_from_conflicted_tree<'repo>(
318345 base_tree_id : gix:: ObjectId ,
319346 ours_tree_id : gix:: ObjectId ,
320347 theirs_tree_id : gix:: ObjectId ,
321- sign_if_configured : bool ,
348+ sign_commit : SignCommit ,
322349) -> anyhow:: Result < gix:: Id < ' repo > > {
323350 let repo = resolved_tree_id. repo ;
324351 // in case someone checks this out with vanilla Git, we should warn why it looks like this
@@ -370,7 +397,7 @@ fn commit_from_conflicted_tree<'repo>(
370397 repo,
371398 to_rebase. inner ,
372399 DateMode :: CommitterUpdateAuthorKeep ,
373- sign_if_configured ,
400+ sign_commit ,
374401 ) ?
375402 . attach ( repo) )
376403}
0 commit comments