2 * "git replay" builtin command
5 #include "git-compat-util.h"
8 #include "environment.h"
11 #include "merge-ort.h"
12 #include "object-name.h"
13 #include "parse-options.h"
20 static const char *short_commit_name(struct commit
*commit
)
22 return repo_find_unique_abbrev(the_repository
, &commit
->object
.oid
,
26 static struct commit
*peel_committish(const char *name
)
31 if (repo_get_oid(the_repository
, name
, &oid
))
33 obj
= parse_object(the_repository
, &oid
);
34 return (struct commit
*)repo_peel_to_type(the_repository
, name
, 0, obj
,
38 static char *get_author(const char *message
)
43 a
= find_commit_header(message
, "author", &len
);
45 return xmemdupz(a
, len
);
50 static struct commit
*create_commit(struct tree
*tree
,
51 struct commit
*based_on
,
52 struct commit
*parent
)
56 struct commit_list
*parents
= NULL
;
58 char *sign_commit
= NULL
; /* FIXME: cli users might want to sign again */
59 struct commit_extra_header
*extra
;
60 struct strbuf msg
= STRBUF_INIT
;
61 const char *out_enc
= get_commit_output_encoding();
62 const char *message
= repo_logmsg_reencode(the_repository
, based_on
,
64 const char *orig_message
= NULL
;
65 const char *exclude_gpgsig
[] = { "gpgsig", NULL
};
67 commit_list_insert(parent
, &parents
);
68 extra
= read_commit_extra_headers(based_on
, exclude_gpgsig
);
69 find_commit_subject(message
, &orig_message
);
70 strbuf_addstr(&msg
, orig_message
);
71 author
= get_author(message
);
73 if (commit_tree_extended(msg
.buf
, msg
.len
, &tree
->object
.oid
, parents
,
74 &ret
, author
, NULL
, sign_commit
, extra
)) {
75 error(_("failed to write commit object"));
81 obj
= parse_object(the_repository
, &ret
);
82 return (struct commit
*)obj
;
87 struct strset positive_refs
;
88 struct strset negative_refs
;
89 int positive_refexprs
;
90 int negative_refexprs
;
93 static void get_ref_information(struct rev_cmdline_info
*cmd_info
,
94 struct ref_info
*ref_info
)
98 ref_info
->onto
= NULL
;
99 strset_init(&ref_info
->positive_refs
);
100 strset_init(&ref_info
->negative_refs
);
101 ref_info
->positive_refexprs
= 0;
102 ref_info
->negative_refexprs
= 0;
105 * When the user specifies e.g.
106 * git replay origin/main..mybranch
107 * git replay ^origin/next mybranch1 mybranch2
108 * we want to be able to determine where to replay the commits. In
109 * these examples, the branches are probably based on an old version
110 * of either origin/main or origin/next, so we want to replay on the
111 * newest version of that branch. In contrast we would want to error
113 * git replay ^origin/master ^origin/next mybranch
114 * git replay mybranch~2..mybranch
115 * the first of those because there's no unique base to choose, and
116 * the second because they'd likely just be replaying commits on top
117 * of the same commit and not making any difference.
119 for (i
= 0; i
< cmd_info
->nr
; i
++) {
120 struct rev_cmdline_entry
*e
= cmd_info
->rev
+ i
;
121 struct object_id oid
;
122 const char *refexpr
= e
->name
;
123 char *fullname
= NULL
;
124 int can_uniquely_dwim
= 1;
128 if (repo_dwim_ref(the_repository
, refexpr
, strlen(refexpr
), &oid
, &fullname
, 0) != 1)
129 can_uniquely_dwim
= 0;
131 if (e
->flags
& BOTTOM
) {
132 if (can_uniquely_dwim
)
133 strset_add(&ref_info
->negative_refs
, fullname
);
134 if (!ref_info
->negative_refexprs
)
135 ref_info
->onto
= lookup_commit_reference_gently(the_repository
,
137 ref_info
->negative_refexprs
++;
139 if (can_uniquely_dwim
)
140 strset_add(&ref_info
->positive_refs
, fullname
);
141 ref_info
->positive_refexprs
++;
148 static void determine_replay_mode(struct rev_cmdline_info
*cmd_info
,
149 const char *onto_name
,
150 const char **advance_name
,
151 struct commit
**onto
,
152 struct strset
**update_refs
)
154 struct ref_info rinfo
;
156 get_ref_information(cmd_info
, &rinfo
);
157 if (!rinfo
.positive_refexprs
)
158 die(_("need some commits to replay"));
159 if (onto_name
&& *advance_name
)
160 die(_("--onto and --advance are incompatible"));
161 else if (onto_name
) {
162 *onto
= peel_committish(onto_name
);
163 if (rinfo
.positive_refexprs
<
164 strset_get_size(&rinfo
.positive_refs
))
165 die(_("all positive revisions given must be references"));
166 } else if (*advance_name
) {
167 struct object_id oid
;
168 char *fullname
= NULL
;
170 *onto
= peel_committish(*advance_name
);
171 if (repo_dwim_ref(the_repository
, *advance_name
, strlen(*advance_name
),
172 &oid
, &fullname
, 0) == 1) {
173 *advance_name
= fullname
;
175 die(_("argument to --advance must be a reference"));
177 if (rinfo
.positive_refexprs
> 1)
178 die(_("cannot advance target with multiple sources because ordering would be ill-defined"));
180 int positive_refs_complete
= (
181 rinfo
.positive_refexprs
==
182 strset_get_size(&rinfo
.positive_refs
));
183 int negative_refs_complete
= (
184 rinfo
.negative_refexprs
==
185 strset_get_size(&rinfo
.negative_refs
));
187 * We need either positive_refs_complete or
188 * negative_refs_complete, but not both.
190 if (rinfo
.negative_refexprs
> 0 &&
191 positive_refs_complete
== negative_refs_complete
)
192 die(_("cannot implicitly determine whether this is an --advance or --onto operation"));
193 if (negative_refs_complete
) {
194 struct hashmap_iter iter
;
195 struct strmap_entry
*entry
;
197 if (rinfo
.negative_refexprs
== 0)
198 die(_("all positive revisions given must be references"));
199 else if (rinfo
.negative_refexprs
> 1)
200 die(_("cannot implicitly determine whether this is an --advance or --onto operation"));
201 else if (rinfo
.positive_refexprs
> 1)
202 die(_("cannot advance target with multiple source branches because ordering would be ill-defined"));
204 /* Only one entry, but we have to loop to get it */
205 strset_for_each_entry(&rinfo
.negative_refs
,
207 *advance_name
= entry
->key
;
209 } else { /* positive_refs_complete */
210 if (rinfo
.negative_refexprs
> 1)
211 die(_("cannot implicitly determine correct base for --onto"));
212 if (rinfo
.negative_refexprs
== 1)
216 if (!*advance_name
) {
217 *update_refs
= xcalloc(1, sizeof(**update_refs
));
218 **update_refs
= rinfo
.positive_refs
;
219 memset(&rinfo
.positive_refs
, 0, sizeof(**update_refs
));
221 strset_clear(&rinfo
.negative_refs
);
222 strset_clear(&rinfo
.positive_refs
);
225 static struct commit
*mapped_commit(kh_oid_map_t
*replayed_commits
,
226 struct commit
*commit
,
227 struct commit
*fallback
)
229 khint_t pos
= kh_get_oid_map(replayed_commits
, commit
->object
.oid
);
230 if (pos
== kh_end(replayed_commits
))
232 return kh_value(replayed_commits
, pos
);
235 static struct commit
*pick_regular_commit(struct commit
*pickme
,
236 kh_oid_map_t
*replayed_commits
,
238 struct merge_options
*merge_opt
,
239 struct merge_result
*result
)
241 struct commit
*base
, *replayed_base
;
242 struct tree
*pickme_tree
, *base_tree
;
244 base
= pickme
->parents
->item
;
245 replayed_base
= mapped_commit(replayed_commits
, base
, onto
);
247 result
->tree
= repo_get_commit_tree(the_repository
, replayed_base
);
248 pickme_tree
= repo_get_commit_tree(the_repository
, pickme
);
249 base_tree
= repo_get_commit_tree(the_repository
, base
);
251 merge_opt
->branch1
= short_commit_name(replayed_base
);
252 merge_opt
->branch2
= short_commit_name(pickme
);
253 merge_opt
->ancestor
= xstrfmt("parent of %s", merge_opt
->branch2
);
255 merge_incore_nonrecursive(merge_opt
,
261 free((char*)merge_opt
->ancestor
);
262 merge_opt
->ancestor
= NULL
;
265 return create_commit(result
->tree
, pickme
, replayed_base
);
268 int cmd_replay(int argc
, const char **argv
, const char *prefix
)
270 const char *advance_name
= NULL
;
271 struct commit
*onto
= NULL
;
272 const char *onto_name
= NULL
;
275 struct rev_info revs
;
276 struct commit
*last_commit
= NULL
;
277 struct commit
*commit
;
278 struct merge_options merge_opt
;
279 struct merge_result result
;
280 struct strset
*update_refs
= NULL
;
281 kh_oid_map_t
*replayed_commits
;
284 const char * const replay_usage
[] = {
285 N_("(EXPERIMENTAL!) git replay "
286 "([--contained] --onto <newbase> | --advance <branch>) "
287 "<revision-range>..."),
290 struct option replay_options
[] = {
291 OPT_STRING(0, "advance", &advance_name
,
293 N_("make replay advance given branch")),
294 OPT_STRING(0, "onto", &onto_name
,
296 N_("replay onto given commit")),
297 OPT_BOOL(0, "contained", &contained
,
298 N_("advance all branches contained in revision-range")),
302 argc
= parse_options(argc
, argv
, prefix
, replay_options
, replay_usage
,
303 PARSE_OPT_KEEP_ARGV0
| PARSE_OPT_KEEP_UNKNOWN_OPT
);
305 if (!onto_name
&& !advance_name
) {
306 error(_("option --onto or --advance is mandatory"));
307 usage_with_options(replay_usage
, replay_options
);
310 if (advance_name
&& contained
)
311 die(_("options '%s' and '%s' cannot be used together"),
312 "--advance", "--contained");
314 repo_init_revisions(the_repository
, &revs
, prefix
);
317 * Set desired values for rev walking options here. If they
318 * are changed by some user specified option in setup_revisions()
319 * below, we will detect that below and then warn.
321 * TODO: In the future we might want to either die(), or allow
322 * some options changing these values if we think they could
326 revs
.sort_order
= REV_SORT_IN_GRAPH_ORDER
;
328 revs
.simplify_history
= 0;
330 argc
= setup_revisions(argc
, argv
, &revs
, NULL
);
332 ret
= error(_("unrecognized argument: %s"), argv
[1]);
337 * Detect and warn if we override some user specified rev
340 if (revs
.reverse
!= 1) {
341 warning(_("some rev walking options will be overridden as "
342 "'%s' bit in 'struct rev_info' will be forced"),
346 if (revs
.sort_order
!= REV_SORT_IN_GRAPH_ORDER
) {
347 warning(_("some rev walking options will be overridden as "
348 "'%s' bit in 'struct rev_info' will be forced"),
350 revs
.sort_order
= REV_SORT_IN_GRAPH_ORDER
;
352 if (revs
.topo_order
!= 1) {
353 warning(_("some rev walking options will be overridden as "
354 "'%s' bit in 'struct rev_info' will be forced"),
358 if (revs
.simplify_history
!= 0) {
359 warning(_("some rev walking options will be overridden as "
360 "'%s' bit in 'struct rev_info' will be forced"),
362 revs
.simplify_history
= 0;
365 determine_replay_mode(&revs
.cmdline
, onto_name
, &advance_name
,
366 &onto
, &update_refs
);
368 if (!onto
) /* FIXME: Should handle replaying down to root commit */
369 die("Replaying down to root commit is not supported yet!");
371 if (prepare_revision_walk(&revs
) < 0) {
372 ret
= error(_("error preparing revisions"));
376 init_merge_options(&merge_opt
, the_repository
);
377 memset(&result
, 0, sizeof(result
));
378 merge_opt
.show_rename_progress
= 0;
380 replayed_commits
= kh_init_oid_map();
381 while ((commit
= get_revision(&revs
))) {
382 const struct name_decoration
*decoration
;
386 if (!commit
->parents
)
387 die(_("replaying down to root commit is not supported yet!"));
388 if (commit
->parents
->next
)
389 die(_("replaying merge commits is not supported yet!"));
391 last_commit
= pick_regular_commit(commit
, replayed_commits
, onto
,
392 &merge_opt
, &result
);
396 /* Record commit -> last_commit mapping */
397 pos
= kh_put_oid_map(replayed_commits
, commit
->object
.oid
, &hr
);
399 BUG("Duplicate rewritten commit: %s\n",
400 oid_to_hex(&commit
->object
.oid
));
401 kh_value(replayed_commits
, pos
) = last_commit
;
403 /* Update any necessary branches */
406 decoration
= get_name_decoration(&commit
->object
);
410 if (decoration
->type
== DECORATION_REF_LOCAL
&&
411 (contained
|| strset_contains(update_refs
,
412 decoration
->name
))) {
413 printf("update %s %s %s\n",
415 oid_to_hex(&last_commit
->object
.oid
),
416 oid_to_hex(&commit
->object
.oid
));
418 decoration
= decoration
->next
;
422 /* In --advance mode, advance the target ref */
423 if (result
.clean
== 1 && advance_name
) {
424 printf("update %s %s %s\n",
426 oid_to_hex(&last_commit
->object
.oid
),
427 oid_to_hex(&onto
->object
.oid
));
430 merge_finalize(&merge_opt
, &result
);
431 kh_destroy_oid_map(replayed_commits
);
433 strset_clear(update_refs
);
439 release_revisions(&revs
);