2 * "git replay" builtin command
5 #define USE_THE_INDEX_VARIABLE
6 #include "git-compat-util.h"
9 #include "environment.h"
12 #include "merge-ort.h"
13 #include "object-name.h"
14 #include "parse-options.h"
21 static const char *short_commit_name(struct commit
*commit
)
23 return repo_find_unique_abbrev(the_repository
, &commit
->object
.oid
,
27 static struct commit
*peel_committish(const char *name
)
32 if (repo_get_oid(the_repository
, name
, &oid
))
34 obj
= parse_object(the_repository
, &oid
);
35 return (struct commit
*)repo_peel_to_type(the_repository
, name
, 0, obj
,
39 static char *get_author(const char *message
)
44 a
= find_commit_header(message
, "author", &len
);
46 return xmemdupz(a
, len
);
51 static struct commit
*create_commit(struct tree
*tree
,
52 struct commit
*based_on
,
53 struct commit
*parent
)
57 struct commit_list
*parents
= NULL
;
59 char *sign_commit
= NULL
; /* FIXME: cli users might want to sign again */
60 struct commit_extra_header
*extra
;
61 struct strbuf msg
= STRBUF_INIT
;
62 const char *out_enc
= get_commit_output_encoding();
63 const char *message
= repo_logmsg_reencode(the_repository
, based_on
,
65 const char *orig_message
= NULL
;
66 const char *exclude_gpgsig
[] = { "gpgsig", NULL
};
68 commit_list_insert(parent
, &parents
);
69 extra
= read_commit_extra_headers(based_on
, exclude_gpgsig
);
70 find_commit_subject(message
, &orig_message
);
71 strbuf_addstr(&msg
, orig_message
);
72 author
= get_author(message
);
74 if (commit_tree_extended(msg
.buf
, msg
.len
, &tree
->object
.oid
, parents
,
75 &ret
, author
, NULL
, sign_commit
, extra
)) {
76 error(_("failed to write commit object"));
82 obj
= parse_object(the_repository
, &ret
);
83 return (struct commit
*)obj
;
88 struct strset positive_refs
;
89 struct strset negative_refs
;
90 int positive_refexprs
;
91 int negative_refexprs
;
94 static void get_ref_information(struct rev_cmdline_info
*cmd_info
,
95 struct ref_info
*ref_info
)
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;
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
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.
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;
129 if (repo_dwim_ref(the_repository
, refexpr
, strlen(refexpr
), &oid
, &fullname
, 0) != 1)
130 can_uniquely_dwim
= 0;
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
,
138 ref_info
->negative_refexprs
++;
140 if (can_uniquely_dwim
)
141 strset_add(&ref_info
->positive_refs
, fullname
);
142 ref_info
->positive_refexprs
++;
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
)
155 struct ref_info rinfo
;
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
;
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
;
176 die(_("argument to --advance must be a reference"));
178 if (rinfo
.positive_refexprs
> 1)
179 die(_("cannot advance target with multiple sources because ordering would be ill-defined"));
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
));
188 * We need either positive_refs_complete or
189 * negative_refs_complete, but not both.
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
;
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"));
205 /* Only one entry, but we have to loop to get it */
206 strset_for_each_entry(&rinfo
.negative_refs
,
208 *advance_name
= entry
->key
;
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)
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
));
222 strset_clear(&rinfo
.negative_refs
);
223 strset_clear(&rinfo
.positive_refs
);
226 static struct commit
*mapped_commit(kh_oid_map_t
*replayed_commits
,
227 struct commit
*commit
,
228 struct commit
*fallback
)
230 khint_t pos
= kh_get_oid_map(replayed_commits
, commit
->object
.oid
);
231 if (pos
== kh_end(replayed_commits
))
233 return kh_value(replayed_commits
, pos
);
236 static struct commit
*pick_regular_commit(struct commit
*pickme
,
237 kh_oid_map_t
*replayed_commits
,
239 struct merge_options
*merge_opt
,
240 struct merge_result
*result
)
242 struct commit
*base
, *replayed_base
;
243 struct tree
*pickme_tree
, *base_tree
;
245 base
= pickme
->parents
->item
;
246 replayed_base
= mapped_commit(replayed_commits
, base
, onto
);
248 result
->tree
= repo_get_commit_tree(the_repository
, replayed_base
);
249 pickme_tree
= repo_get_commit_tree(the_repository
, pickme
);
250 base_tree
= repo_get_commit_tree(the_repository
, base
);
252 merge_opt
->branch1
= short_commit_name(replayed_base
);
253 merge_opt
->branch2
= short_commit_name(pickme
);
254 merge_opt
->ancestor
= xstrfmt("parent of %s", merge_opt
->branch2
);
256 merge_incore_nonrecursive(merge_opt
,
262 free((char*)merge_opt
->ancestor
);
263 merge_opt
->ancestor
= NULL
;
266 return create_commit(result
->tree
, pickme
, replayed_base
);
269 int cmd_replay(int argc
, const char **argv
, const char *prefix
)
271 const char *advance_name
= NULL
;
272 struct commit
*onto
= NULL
;
273 const char *onto_name
= NULL
;
276 struct rev_info revs
;
277 struct commit
*last_commit
= NULL
;
278 struct commit
*commit
;
279 struct merge_options merge_opt
;
280 struct merge_result result
;
281 struct strset
*update_refs
= NULL
;
282 kh_oid_map_t
*replayed_commits
;
285 const char * const replay_usage
[] = {
286 N_("(EXPERIMENTAL!) git replay "
287 "([--contained] --onto <newbase> | --advance <branch>) "
288 "<revision-range>..."),
291 struct option replay_options
[] = {
292 OPT_STRING(0, "advance", &advance_name
,
294 N_("make replay advance given branch")),
295 OPT_STRING(0, "onto", &onto_name
,
297 N_("replay onto given commit")),
298 OPT_BOOL(0, "contained", &contained
,
299 N_("advance all branches contained in revision-range")),
303 argc
= parse_options(argc
, argv
, prefix
, replay_options
, replay_usage
,
304 PARSE_OPT_KEEP_ARGV0
| PARSE_OPT_KEEP_UNKNOWN_OPT
);
306 if (!onto_name
&& !advance_name
) {
307 error(_("option --onto or --advance is mandatory"));
308 usage_with_options(replay_usage
, replay_options
);
311 if (advance_name
&& contained
)
312 die(_("options '%s' and '%s' cannot be used together"),
313 "--advance", "--contained");
315 repo_init_revisions(the_repository
, &revs
, prefix
);
318 * Set desired values for rev walking options here. If they
319 * are changed by some user specified option in setup_revisions()
320 * below, we will detect that below and then warn.
322 * TODO: In the future we might want to either die(), or allow
323 * some options changing these values if we think they could
327 revs
.sort_order
= REV_SORT_IN_GRAPH_ORDER
;
329 revs
.simplify_history
= 0;
331 argc
= setup_revisions(argc
, argv
, &revs
, NULL
);
333 ret
= error(_("unrecognized argument: %s"), argv
[1]);
338 * Detect and warn if we override some user specified rev
341 if (revs
.reverse
!= 1) {
342 warning(_("some rev walking options will be overridden as "
343 "'%s' bit in 'struct rev_info' will be forced"),
347 if (revs
.sort_order
!= REV_SORT_IN_GRAPH_ORDER
) {
348 warning(_("some rev walking options will be overridden as "
349 "'%s' bit in 'struct rev_info' will be forced"),
351 revs
.sort_order
= REV_SORT_IN_GRAPH_ORDER
;
353 if (revs
.topo_order
!= 1) {
354 warning(_("some rev walking options will be overridden as "
355 "'%s' bit in 'struct rev_info' will be forced"),
359 if (revs
.simplify_history
!= 0) {
360 warning(_("some rev walking options will be overridden as "
361 "'%s' bit in 'struct rev_info' will be forced"),
363 revs
.simplify_history
= 0;
366 determine_replay_mode(&revs
.cmdline
, onto_name
, &advance_name
,
367 &onto
, &update_refs
);
369 if (!onto
) /* FIXME: Should handle replaying down to root commit */
370 die("Replaying down to root commit is not supported yet!");
372 if (prepare_revision_walk(&revs
) < 0) {
373 ret
= error(_("error preparing revisions"));
377 init_merge_options(&merge_opt
, the_repository
);
378 memset(&result
, 0, sizeof(result
));
379 merge_opt
.show_rename_progress
= 0;
381 replayed_commits
= kh_init_oid_map();
382 while ((commit
= get_revision(&revs
))) {
383 const struct name_decoration
*decoration
;
387 if (!commit
->parents
)
388 die(_("replaying down to root commit is not supported yet!"));
389 if (commit
->parents
->next
)
390 die(_("replaying merge commits is not supported yet!"));
392 last_commit
= pick_regular_commit(commit
, replayed_commits
, onto
,
393 &merge_opt
, &result
);
397 /* Record commit -> last_commit mapping */
398 pos
= kh_put_oid_map(replayed_commits
, commit
->object
.oid
, &hr
);
400 BUG("Duplicate rewritten commit: %s\n",
401 oid_to_hex(&commit
->object
.oid
));
402 kh_value(replayed_commits
, pos
) = last_commit
;
404 /* Update any necessary branches */
407 decoration
= get_name_decoration(&commit
->object
);
411 if (decoration
->type
== DECORATION_REF_LOCAL
&&
412 (contained
|| strset_contains(update_refs
,
413 decoration
->name
))) {
414 printf("update %s %s %s\n",
416 oid_to_hex(&last_commit
->object
.oid
),
417 oid_to_hex(&commit
->object
.oid
));
419 decoration
= decoration
->next
;
423 /* In --advance mode, advance the target ref */
424 if (result
.clean
== 1 && advance_name
) {
425 printf("update %s %s %s\n",
427 oid_to_hex(&last_commit
->object
.oid
),
428 oid_to_hex(&onto
->object
.oid
));
431 merge_finalize(&merge_opt
, &result
);
432 kh_destroy_oid_map(replayed_commits
);
434 strset_clear(update_refs
);
440 release_revisions(&revs
);