88#include "refs.h"
99#include "replay.h"
1010#include "revision.h"
11+ #include "sequencer.h"
1112#include "strmap.h"
1213#include "tree.h"
1314
15+ enum replay_mode {
16+ REPLAY_MODE_PICK ,
17+ REPLAY_MODE_REVERT ,
18+ };
19+
1420static const char * short_commit_name (struct repository * repo ,
1521 struct commit * commit )
1622{
@@ -44,15 +50,35 @@ static char *get_author(const char *message)
4450 return NULL ;
4551}
4652
53+ static void generate_revert_message (struct strbuf * msg ,
54+ struct commit * commit ,
55+ struct repository * repo )
56+ {
57+ const char * out_enc = get_commit_output_encoding ();
58+ const char * message = repo_logmsg_reencode (repo , commit , NULL , out_enc );
59+ const char * subject_start ;
60+ int subject_len ;
61+ char * subject ;
62+
63+ subject_len = find_commit_subject (message , & subject_start );
64+ subject = xmemdupz (subject_start , subject_len );
65+
66+ sequencer_format_revert_header (msg , subject , & commit -> object .oid );
67+
68+ free (subject );
69+ repo_unuse_commit_buffer (repo , commit , message );
70+ }
71+
4772static struct commit * create_commit (struct repository * repo ,
4873 struct tree * tree ,
4974 struct commit * based_on ,
50- struct commit * parent )
75+ struct commit * parent ,
76+ enum replay_mode mode )
5177{
5278 struct object_id ret ;
5379 struct object * obj = NULL ;
5480 struct commit_list * parents = NULL ;
55- char * author ;
81+ char * author = NULL ;
5682 char * sign_commit = NULL ; /* FIXME: cli users might want to sign again */
5783 struct commit_extra_header * extra = NULL ;
5884 struct strbuf msg = STRBUF_INIT ;
@@ -64,9 +90,16 @@ static struct commit *create_commit(struct repository *repo,
6490
6591 commit_list_insert (parent , & parents );
6692 extra = read_commit_extra_headers (based_on , exclude_gpgsig );
67- find_commit_subject (message , & orig_message );
68- strbuf_addstr (& msg , orig_message );
69- author = get_author (message );
93+ if (mode == REPLAY_MODE_REVERT ) {
94+ generate_revert_message (& msg , based_on , repo );
95+ /* For revert, use current user as author (NULL = use default) */
96+ } else if (mode == REPLAY_MODE_PICK ) {
97+ find_commit_subject (message , & orig_message );
98+ strbuf_addstr (& msg , orig_message );
99+ author = get_author (message );
100+ } else {
101+ BUG ("unexpected replay mode %d" , mode );
102+ }
70103 reset_ident_date ();
71104 if (commit_tree_extended (msg .buf , msg .len , & tree -> object .oid , parents ,
72105 & ret , author , NULL , sign_commit , extra )) {
@@ -147,11 +180,34 @@ static void get_ref_information(struct repository *repo,
147180 }
148181}
149182
183+ static void set_up_branch_mode (struct repository * repo ,
184+ char * * branch_name ,
185+ const char * option_name ,
186+ struct ref_info * rinfo ,
187+ struct commit * * onto )
188+ {
189+ struct object_id oid ;
190+ char * fullname = NULL ;
191+
192+ if (repo_dwim_ref (repo , * branch_name , strlen (* branch_name ),
193+ & oid , & fullname , 0 ) == 1 ) {
194+ free (* branch_name );
195+ * branch_name = fullname ;
196+ } else {
197+ die (_ ("argument to %s must be a reference" ), option_name );
198+ }
199+ * onto = peel_committish (repo , * branch_name , option_name );
200+ if (rinfo -> positive_refexprs > 1 )
201+ die (_ ("cannot %s target with multiple sources because ordering would be ill-defined" ),
202+ option_name + 2 ); /* skip "--" prefix */
203+ }
204+
150205static void set_up_replay_mode (struct repository * repo ,
151206 struct rev_cmdline_info * cmd_info ,
152207 const char * onto_name ,
153208 bool * detached_head ,
154209 char * * advance_name ,
210+ char * * revert_name ,
155211 struct commit * * onto ,
156212 struct strset * * update_refs )
157213{
@@ -166,9 +222,6 @@ static void set_up_replay_mode(struct repository *repo,
166222 if (!rinfo .positive_refexprs )
167223 die (_ ("need some commits to replay" ));
168224
169- if (!onto_name == !* advance_name )
170- BUG ("one and only one of onto_name and *advance_name must be given" );
171-
172225 if (onto_name ) {
173226 * onto = peel_committish (repo , onto_name , "--onto" );
174227 if (rinfo .positive_refexprs <
@@ -177,23 +230,12 @@ static void set_up_replay_mode(struct repository *repo,
177230 * update_refs = xcalloc (1 , sizeof (* * update_refs ));
178231 * * update_refs = rinfo .positive_refs ;
179232 memset (& rinfo .positive_refs , 0 , sizeof (* * update_refs ));
233+ } else if (* advance_name ) {
234+ set_up_branch_mode (repo , advance_name , "--advance" , & rinfo , onto );
235+ } else if (* revert_name ) {
236+ set_up_branch_mode (repo , revert_name , "--revert" , & rinfo , onto );
180237 } else {
181- struct object_id oid ;
182- char * fullname = NULL ;
183-
184- if (!* advance_name )
185- BUG ("expected either onto_name or *advance_name in this function" );
186-
187- if (repo_dwim_ref (repo , * advance_name , strlen (* advance_name ),
188- & oid , & fullname , 0 ) == 1 ) {
189- free (* advance_name );
190- * advance_name = fullname ;
191- } else {
192- die (_ ("argument to --advance must be a reference" ));
193- }
194- * onto = peel_committish (repo , * advance_name , "--advance" );
195- if (rinfo .positive_refexprs > 1 )
196- die (_ ("cannot advance target with multiple sources because ordering would be ill-defined" ));
238+ BUG ("expected one of onto_name, *advance_name, or *revert_name" );
197239 }
198240 strset_clear (& rinfo .negative_refs );
199241 strset_clear (& rinfo .positive_refs );
@@ -214,7 +256,8 @@ static struct commit *pick_regular_commit(struct repository *repo,
214256 kh_oid_map_t * replayed_commits ,
215257 struct commit * onto ,
216258 struct merge_options * merge_opt ,
217- struct merge_result * result )
259+ struct merge_result * result ,
260+ enum replay_mode mode )
218261{
219262 struct commit * base , * replayed_base ;
220263 struct tree * pickme_tree , * base_tree , * replayed_base_tree ;
@@ -226,25 +269,46 @@ static struct commit *pick_regular_commit(struct repository *repo,
226269 pickme_tree = repo_get_commit_tree (repo , pickme );
227270 base_tree = repo_get_commit_tree (repo , base );
228271
229- merge_opt -> branch1 = short_commit_name (repo , replayed_base );
230- merge_opt -> branch2 = short_commit_name (repo , pickme );
231- merge_opt -> ancestor = xstrfmt ("parent of %s" , merge_opt -> branch2 );
232-
233- merge_incore_nonrecursive (merge_opt ,
234- base_tree ,
235- replayed_base_tree ,
236- pickme_tree ,
237- result );
238-
239- free ((char * )merge_opt -> ancestor );
272+ if (mode == REPLAY_MODE_PICK ) {
273+ /* Cherry-pick: normal order */
274+ merge_opt -> branch1 = short_commit_name (repo , replayed_base );
275+ merge_opt -> branch2 = short_commit_name (repo , pickme );
276+ merge_opt -> ancestor = xstrfmt ("parent of %s" , merge_opt -> branch2 );
277+
278+ merge_incore_nonrecursive (merge_opt ,
279+ base_tree ,
280+ replayed_base_tree ,
281+ pickme_tree ,
282+ result );
283+
284+ free ((char * )merge_opt -> ancestor );
285+ } else if (mode == REPLAY_MODE_REVERT ) {
286+ /* Revert: swap base and pickme to reverse the diff */
287+ const char * pickme_name = short_commit_name (repo , pickme );
288+ merge_opt -> branch1 = short_commit_name (repo , replayed_base );
289+ merge_opt -> branch2 = xstrfmt ("parent of %s" , pickme_name );
290+ merge_opt -> ancestor = pickme_name ;
291+
292+ merge_incore_nonrecursive (merge_opt ,
293+ pickme_tree ,
294+ replayed_base_tree ,
295+ base_tree ,
296+ result );
297+
298+ free ((char * )merge_opt -> branch2 );
299+ } else {
300+ BUG ("unexpected replay mode %d" , mode );
301+ }
240302 merge_opt -> ancestor = NULL ;
303+ merge_opt -> branch2 = NULL ;
241304 if (!result -> clean )
242305 return NULL ;
243- /* Drop commits that become empty */
244- if (oideq (& replayed_base_tree -> object .oid , & result -> tree -> object .oid ) &&
306+ /* Drop commits that become empty (only for picks) */
307+ if (mode == REPLAY_MODE_PICK &&
308+ oideq (& replayed_base_tree -> object .oid , & result -> tree -> object .oid ) &&
245309 !oideq (& pickme_tree -> object .oid , & base_tree -> object .oid ))
246310 return replayed_base ;
247- return create_commit (repo , result -> tree , pickme , replayed_base );
311+ return create_commit (repo , result -> tree , pickme , replayed_base , mode );
248312}
249313
250314void replay_result_release (struct replay_result * result )
@@ -281,11 +345,16 @@ int replay_revisions(struct rev_info *revs,
281345 };
282346 bool detached_head ;
283347 char * advance ;
348+ char * revert ;
349+ enum replay_mode mode = REPLAY_MODE_PICK ;
284350 int ret ;
285351
286352 advance = xstrdup_or_null (opts -> advance );
353+ revert = xstrdup_or_null (opts -> revert );
354+ if (revert )
355+ mode = REPLAY_MODE_REVERT ;
287356 set_up_replay_mode (revs -> repo , & revs -> cmdline , opts -> onto ,
288- & detached_head , & advance , & onto , & update_refs );
357+ & detached_head , & advance , & revert , & onto , & update_refs );
289358
290359 /* FIXME: Should allow replaying commits with the first as a root commit */
291360
@@ -309,7 +378,7 @@ int replay_revisions(struct rev_info *revs,
309378 die (_ ("replaying merge commits is not supported yet!" ));
310379
311380 last_commit = pick_regular_commit (revs -> repo , commit , replayed_commits ,
312- onto , & merge_opt , & result );
381+ onto , & merge_opt , & result , mode );
313382 if (!last_commit )
314383 break ;
315384
@@ -321,7 +390,7 @@ int replay_revisions(struct rev_info *revs,
321390 kh_value (replayed_commits , pos ) = last_commit ;
322391
323392 /* Update any necessary branches */
324- if (advance )
393+ if (advance || revert )
325394 continue ;
326395
327396 for (decoration = get_name_decoration (& commit -> object );
@@ -355,11 +424,13 @@ int replay_revisions(struct rev_info *revs,
355424 goto out ;
356425 }
357426
358- /* In --advance mode, advance the target ref */
359- if (advance )
360- replay_result_queue_update (out , advance ,
427+ /* In --advance or --revert mode, update the target ref */
428+ if (advance || revert ) {
429+ const char * ref = advance ? advance : revert ;
430+ replay_result_queue_update (out , ref ,
361431 & onto -> object .oid ,
362432 & last_commit -> object .oid );
433+ }
363434
364435 ret = 0 ;
365436
@@ -371,5 +442,6 @@ int replay_revisions(struct rev_info *revs,
371442 kh_destroy_oid_map (replayed_commits );
372443 merge_finalize (& merge_opt , & result );
373444 free (advance );
445+ free (revert );
374446 return ret ;
375447}
0 commit comments