66 */
77
88use std:: iter:: zip;
9+ use std:: collections:: HashSet ;
910
1011use crate :: {
1112 error:: { add_error, Error , Result , ResultExt } ,
@@ -41,12 +42,34 @@ pub struct DiffOptions {
4142 #[ clap( long, short = 'm' ) ]
4243 message : Option < String > ,
4344
45+ /// Which commits in the branch should be created/updated. This can be a
46+ /// revspec such as HEAD~4..HEAD~1 or just one commit like HEAD~7.
47+ #[ clap( long, short = 'r' ) ]
48+ refs : Option < String > ,
49+
4450 /// Submit this commit as if it was cherry-picked on master. Do not base it
4551 /// on any intermediate changes between the master branch and this commit.
4652 #[ clap( long) ]
4753 cherry_pick : bool ,
4854}
4955
56+ fn get_oids ( refs : & str , repo : & git2:: Repository ) -> Result < HashSet < Oid > > {
57+ // refs might be a single (eg 012345abc or HEAD) or a range (HEAD~4..HEAD~2)
58+ let revspec = repo. revparse ( & refs) ?;
59+
60+ let from = revspec. from ( ) . ok_or ( Error :: new ( "Unexpectedly no from id in range" ) ) ?. id ( ) ;
61+ if revspec. mode ( ) . contains ( git2:: RevparseMode :: SINGLE ) {
62+ // simple case, just return the id
63+ return Ok ( HashSet :: from ( [ from] ) ) ;
64+ }
65+ let to = revspec. to ( ) . ok_or ( Error :: new ( "Unexpectedly no to id in range" ) ) ?. id ( ) ;
66+
67+ let mut walk = repo. revwalk ( ) ?;
68+ walk. push ( to) ?;
69+ walk. hide ( from) ?;
70+ walk. map ( |r| Ok ( r?) ) . collect ( )
71+ }
72+
5073pub async fn diff (
5174 opts : DiffOptions ,
5275 git : & crate :: git:: Git ,
@@ -70,11 +93,25 @@ pub async fn diff(
7093 return result;
7194 } ;
7295
73- if !opts. all {
74- // Remove all prepared commits from the vector but the last. So, if
75- // `--all` is not given, we only operate on the HEAD commit.
76- prepared_commits. drain ( 0 ..prepared_commits. len ( ) - 1 ) ;
77- }
96+ // If refs is set, we want to track which commits to run `diff`
97+ // against. The simple approach would be to adjust the prepared_commits
98+ // Vec (as with opts.all above). This does not work however, as we need
99+ // to know the entire list (or more specifically the list after the first update)
100+ // for the rewrite_commit_messages step.
101+ // This is not a problem for opts.all as it only ever has a single commit to update,
102+ // and so nothing after it.
103+ let revs_to_pr = match ( & opts. refs , opts. all ) {
104+ ( Some ( refs) , false ) => get_oids ( refs, & git. repo ( ) ) . map ( |i| Some ( i) ) ,
105+ ( Some ( _) , true ) => Err ( Error :: new ( "Do not use --refs with --all" ) ) ,
106+ ( None , all) => {
107+ // Remove all prepared commits from the vector but the last. So, if
108+ // `--all` is not given, we only operate on the HEAD commit.
109+ if !all {
110+ prepared_commits. drain ( 0 ..prepared_commits. len ( ) - 1 ) ;
111+ }
112+ Ok ( None )
113+ }
114+ } ?;
78115
79116 #[ allow( clippy:: needless_collect) ]
80117 let pull_request_tasks: Vec < _ > = prepared_commits
@@ -94,6 +131,12 @@ pub async fn diff(
94131 break ;
95132 }
96133
134+ if let Some ( revs) = & revs_to_pr {
135+ if !revs. contains ( & prepared_commit. oid ) {
136+ continue ;
137+ }
138+ }
139+
97140 let pull_request = if let Some ( task) = pull_request_task {
98141 Some ( task. await ??)
99142 } else {
0 commit comments