14
14
#include "parse-options.h"
15
15
#include "refs.h"
16
16
#include "revision.h"
17
+ #include "strmap.h"
17
18
#include <oidset.h>
18
19
#include <tree.h>
19
20
@@ -82,6 +83,146 @@ static struct commit *create_commit(struct tree *tree,
82
83
return (struct commit * )obj ;
83
84
}
84
85
86
+ struct ref_info {
87
+ struct commit * onto ;
88
+ struct strset positive_refs ;
89
+ struct strset negative_refs ;
90
+ int positive_refexprs ;
91
+ int negative_refexprs ;
92
+ };
93
+
94
+ static void get_ref_information (struct rev_cmdline_info * cmd_info ,
95
+ struct ref_info * ref_info )
96
+ {
97
+ int i ;
98
+
99
+ ref_info -> onto = NULL ;
100
+ strset_init (& ref_info -> positive_refs );
101
+ strset_init (& ref_info -> negative_refs );
102
+ ref_info -> positive_refexprs = 0 ;
103
+ ref_info -> negative_refexprs = 0 ;
104
+
105
+ /*
106
+ * When the user specifies e.g.
107
+ * git replay origin/main..mybranch
108
+ * git replay ^origin/next mybranch1 mybranch2
109
+ * we want to be able to determine where to replay the commits. In
110
+ * these examples, the branches are probably based on an old version
111
+ * of either origin/main or origin/next, so we want to replay on the
112
+ * newest version of that branch. In contrast we would want to error
113
+ * out if they ran
114
+ * git replay ^origin/master ^origin/next mybranch
115
+ * git replay mybranch~2..mybranch
116
+ * the first of those because there's no unique base to choose, and
117
+ * the second because they'd likely just be replaying commits on top
118
+ * of the same commit and not making any difference.
119
+ */
120
+ for (i = 0 ; i < cmd_info -> nr ; i ++ ) {
121
+ struct rev_cmdline_entry * e = cmd_info -> rev + i ;
122
+ struct object_id oid ;
123
+ const char * refexpr = e -> name ;
124
+ char * fullname = NULL ;
125
+ int can_uniquely_dwim = 1 ;
126
+
127
+ if (* refexpr == '^' )
128
+ refexpr ++ ;
129
+ if (repo_dwim_ref (the_repository , refexpr , strlen (refexpr ), & oid , & fullname , 0 ) != 1 )
130
+ can_uniquely_dwim = 0 ;
131
+
132
+ if (e -> flags & BOTTOM ) {
133
+ if (can_uniquely_dwim )
134
+ strset_add (& ref_info -> negative_refs , fullname );
135
+ if (!ref_info -> negative_refexprs )
136
+ ref_info -> onto = lookup_commit_reference_gently (the_repository ,
137
+ & e -> item -> oid , 1 );
138
+ ref_info -> negative_refexprs ++ ;
139
+ } else {
140
+ if (can_uniquely_dwim )
141
+ strset_add (& ref_info -> positive_refs , fullname );
142
+ ref_info -> positive_refexprs ++ ;
143
+ }
144
+
145
+ free (fullname );
146
+ }
147
+ }
148
+
149
+ static void determine_replay_mode (struct rev_cmdline_info * cmd_info ,
150
+ const char * onto_name ,
151
+ const char * * advance_name ,
152
+ struct commit * * onto ,
153
+ struct strset * * update_refs )
154
+ {
155
+ struct ref_info rinfo ;
156
+
157
+ get_ref_information (cmd_info , & rinfo );
158
+ if (!rinfo .positive_refexprs )
159
+ die (_ ("need some commits to replay" ));
160
+ if (onto_name && * advance_name )
161
+ die (_ ("--onto and --advance are incompatible" ));
162
+ else if (onto_name ) {
163
+ * onto = peel_committish (onto_name );
164
+ if (rinfo .positive_refexprs <
165
+ strset_get_size (& rinfo .positive_refs ))
166
+ die (_ ("all positive revisions given must be references" ));
167
+ } else if (* advance_name ) {
168
+ struct object_id oid ;
169
+ char * fullname = NULL ;
170
+
171
+ * onto = peel_committish (* advance_name );
172
+ if (repo_dwim_ref (the_repository , * advance_name , strlen (* advance_name ),
173
+ & oid , & fullname , 0 ) == 1 ) {
174
+ * advance_name = fullname ;
175
+ } else {
176
+ die (_ ("argument to --advance must be a reference" ));
177
+ }
178
+ if (rinfo .positive_refexprs > 1 )
179
+ die (_ ("cannot advance target with multiple sources because ordering would be ill-defined" ));
180
+ } else {
181
+ int positive_refs_complete = (
182
+ rinfo .positive_refexprs ==
183
+ strset_get_size (& rinfo .positive_refs ));
184
+ int negative_refs_complete = (
185
+ rinfo .negative_refexprs ==
186
+ strset_get_size (& rinfo .negative_refs ));
187
+ /*
188
+ * We need either positive_refs_complete or
189
+ * negative_refs_complete, but not both.
190
+ */
191
+ if (rinfo .negative_refexprs > 0 &&
192
+ positive_refs_complete == negative_refs_complete )
193
+ die (_ ("cannot implicitly determine whether this is an --advance or --onto operation" ));
194
+ if (negative_refs_complete ) {
195
+ struct hashmap_iter iter ;
196
+ struct strmap_entry * entry ;
197
+
198
+ if (rinfo .negative_refexprs == 0 )
199
+ die (_ ("all positive revisions given must be references" ));
200
+ else if (rinfo .negative_refexprs > 1 )
201
+ die (_ ("cannot implicitly determine whether this is an --advance or --onto operation" ));
202
+ else if (rinfo .positive_refexprs > 1 )
203
+ die (_ ("cannot advance target with multiple source branches because ordering would be ill-defined" ));
204
+
205
+ /* Only one entry, but we have to loop to get it */
206
+ strset_for_each_entry (& rinfo .negative_refs ,
207
+ & iter , entry ) {
208
+ * advance_name = entry -> key ;
209
+ }
210
+ } else { /* positive_refs_complete */
211
+ if (rinfo .negative_refexprs > 1 )
212
+ die (_ ("cannot implicitly determine correct base for --onto" ));
213
+ if (rinfo .negative_refexprs == 1 )
214
+ * onto = rinfo .onto ;
215
+ }
216
+ }
217
+ if (!* advance_name ) {
218
+ * update_refs = xcalloc (1 , sizeof (* * update_refs ));
219
+ * * update_refs = rinfo .positive_refs ;
220
+ memset (& rinfo .positive_refs , 0 , sizeof (* * update_refs ));
221
+ }
222
+ strset_clear (& rinfo .negative_refs );
223
+ strset_clear (& rinfo .positive_refs );
224
+ }
225
+
85
226
static struct commit * pick_regular_commit (struct commit * pickme ,
86
227
struct commit * last_commit ,
87
228
struct merge_options * merge_opt ,
@@ -114,20 +255,26 @@ static struct commit *pick_regular_commit(struct commit *pickme,
114
255
115
256
int cmd_replay (int argc , const char * * argv , const char * prefix )
116
257
{
117
- struct commit * onto ;
258
+ const char * advance_name = NULL ;
259
+ struct commit * onto = NULL ;
118
260
const char * onto_name = NULL ;
119
- struct commit * last_commit = NULL ;
261
+
120
262
struct rev_info revs ;
263
+ struct commit * last_commit = NULL ;
121
264
struct commit * commit ;
122
265
struct merge_options merge_opt ;
123
266
struct merge_result result ;
267
+ struct strset * update_refs = NULL ;
124
268
int ret = 0 ;
125
269
126
270
const char * const replay_usage [] = {
127
- N_ ("(EXPERIMENTAL!) git replay --onto <newbase> <revision-range>..." ),
271
+ N_ ("(EXPERIMENTAL!) git replay ( --onto <newbase> | --advance <branch>) <revision-range>..." ),
128
272
NULL
129
273
};
130
274
struct option replay_options [] = {
275
+ OPT_STRING (0 , "advance" , & advance_name ,
276
+ N_ ("branch" ),
277
+ N_ ("make replay advance given branch" )),
131
278
OPT_STRING (0 , "onto" , & onto_name ,
132
279
N_ ("revision" ),
133
280
N_ ("replay onto given commit" )),
@@ -137,13 +284,11 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
137
284
argc = parse_options (argc , argv , prefix , replay_options , replay_usage ,
138
285
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT );
139
286
140
- if (!onto_name ) {
141
- error (_ ("option --onto is mandatory" ));
287
+ if (!onto_name && ! advance_name ) {
288
+ error (_ ("option --onto or --advance is mandatory" ));
142
289
usage_with_options (replay_usage , replay_options );
143
290
}
144
291
145
- onto = peel_committish (onto_name );
146
-
147
292
repo_init_revisions (the_repository , & revs , prefix );
148
293
149
294
/*
@@ -195,6 +340,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
195
340
revs .simplify_history = 0 ;
196
341
}
197
342
343
+ determine_replay_mode (& revs .cmdline , onto_name , & advance_name ,
344
+ & onto , & update_refs );
345
+
346
+ if (!onto ) /* FIXME: Should handle replaying down to root commit */
347
+ die ("Replaying down to root commit is not supported yet!" );
348
+
198
349
if (prepare_revision_walk (& revs ) < 0 ) {
199
350
ret = error (_ ("error preparing revisions" ));
200
351
goto cleanup ;
@@ -203,6 +354,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
203
354
init_merge_options (& merge_opt , the_repository );
204
355
memset (& result , 0 , sizeof (result ));
205
356
merge_opt .show_rename_progress = 0 ;
357
+
206
358
result .tree = repo_get_commit_tree (the_repository , onto );
207
359
last_commit = onto ;
208
360
while ((commit = get_revision (& revs ))) {
@@ -217,12 +369,15 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
217
369
if (!last_commit )
218
370
break ;
219
371
372
+ /* Update any necessary branches */
373
+ if (advance_name )
374
+ continue ;
220
375
decoration = get_name_decoration (& commit -> object );
221
376
if (!decoration )
222
377
continue ;
223
-
224
378
while (decoration ) {
225
- if (decoration -> type == DECORATION_REF_LOCAL ) {
379
+ if (decoration -> type == DECORATION_REF_LOCAL &&
380
+ strset_contains (update_refs , decoration -> name )) {
226
381
printf ("update %s %s %s\n" ,
227
382
decoration -> name ,
228
383
oid_to_hex (& last_commit -> object .oid ),
@@ -232,10 +387,22 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
232
387
}
233
388
}
234
389
390
+ /* In --advance mode, advance the target ref */
391
+ if (result .clean == 1 && advance_name ) {
392
+ printf ("update %s %s %s\n" ,
393
+ advance_name ,
394
+ oid_to_hex (& last_commit -> object .oid ),
395
+ oid_to_hex (& onto -> object .oid ));
396
+ }
397
+
235
398
merge_finalize (& merge_opt , & result );
236
399
ret = result .clean ;
237
400
238
401
cleanup :
402
+ if (update_refs ) {
403
+ strset_clear (update_refs );
404
+ free (update_refs );
405
+ }
239
406
release_revisions (& revs );
240
407
241
408
/* Return */
0 commit comments