Git fork
1/*
2 * "git replay" builtin command
3 */
4
5#define USE_THE_REPOSITORY_VARIABLE
6#define DISABLE_SIGN_COMPARE_WARNINGS
7
8#include "git-compat-util.h"
9
10#include "builtin.h"
11#include "environment.h"
12#include "hex.h"
13#include "lockfile.h"
14#include "merge-ort.h"
15#include "object-name.h"
16#include "parse-options.h"
17#include "refs.h"
18#include "revision.h"
19#include "strmap.h"
20#include <oidset.h>
21#include <tree.h>
22
23static const char *short_commit_name(struct repository *repo,
24 struct commit *commit)
25{
26 return repo_find_unique_abbrev(repo, &commit->object.oid,
27 DEFAULT_ABBREV);
28}
29
30static struct commit *peel_committish(struct repository *repo, const char *name)
31{
32 struct object *obj;
33 struct object_id oid;
34
35 if (repo_get_oid(repo, name, &oid))
36 return NULL;
37 obj = parse_object(repo, &oid);
38 return (struct commit *)repo_peel_to_type(repo, name, 0, obj,
39 OBJ_COMMIT);
40}
41
42static char *get_author(const char *message)
43{
44 size_t len;
45 const char *a;
46
47 a = find_commit_header(message, "author", &len);
48 if (a)
49 return xmemdupz(a, len);
50
51 return NULL;
52}
53
54static struct commit *create_commit(struct repository *repo,
55 struct tree *tree,
56 struct commit *based_on,
57 struct commit *parent)
58{
59 struct object_id ret;
60 struct object *obj = NULL;
61 struct commit_list *parents = NULL;
62 char *author;
63 char *sign_commit = NULL; /* FIXME: cli users might want to sign again */
64 struct commit_extra_header *extra = NULL;
65 struct strbuf msg = STRBUF_INIT;
66 const char *out_enc = get_commit_output_encoding();
67 const char *message = repo_logmsg_reencode(repo, based_on,
68 NULL, out_enc);
69 const char *orig_message = NULL;
70 const char *exclude_gpgsig[] = { "gpgsig", NULL };
71
72 commit_list_insert(parent, &parents);
73 extra = read_commit_extra_headers(based_on, exclude_gpgsig);
74 find_commit_subject(message, &orig_message);
75 strbuf_addstr(&msg, orig_message);
76 author = get_author(message);
77 reset_ident_date();
78 if (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents,
79 &ret, author, NULL, sign_commit, extra)) {
80 error(_("failed to write commit object"));
81 goto out;
82 }
83
84 obj = parse_object(repo, &ret);
85
86out:
87 repo_unuse_commit_buffer(the_repository, based_on, message);
88 free_commit_extra_headers(extra);
89 free_commit_list(parents);
90 strbuf_release(&msg);
91 free(author);
92 return (struct commit *)obj;
93}
94
95struct ref_info {
96 struct commit *onto;
97 struct strset positive_refs;
98 struct strset negative_refs;
99 int positive_refexprs;
100 int negative_refexprs;
101};
102
103static void get_ref_information(struct repository *repo,
104 struct rev_cmdline_info *cmd_info,
105 struct ref_info *ref_info)
106{
107 int i;
108
109 ref_info->onto = NULL;
110 strset_init(&ref_info->positive_refs);
111 strset_init(&ref_info->negative_refs);
112 ref_info->positive_refexprs = 0;
113 ref_info->negative_refexprs = 0;
114
115 /*
116 * When the user specifies e.g.
117 * git replay origin/main..mybranch
118 * git replay ^origin/next mybranch1 mybranch2
119 * we want to be able to determine where to replay the commits. In
120 * these examples, the branches are probably based on an old version
121 * of either origin/main or origin/next, so we want to replay on the
122 * newest version of that branch. In contrast we would want to error
123 * out if they ran
124 * git replay ^origin/master ^origin/next mybranch
125 * git replay mybranch~2..mybranch
126 * the first of those because there's no unique base to choose, and
127 * the second because they'd likely just be replaying commits on top
128 * of the same commit and not making any difference.
129 */
130 for (i = 0; i < cmd_info->nr; i++) {
131 struct rev_cmdline_entry *e = cmd_info->rev + i;
132 struct object_id oid;
133 const char *refexpr = e->name;
134 char *fullname = NULL;
135 int can_uniquely_dwim = 1;
136
137 if (*refexpr == '^')
138 refexpr++;
139 if (repo_dwim_ref(repo, refexpr, strlen(refexpr), &oid, &fullname, 0) != 1)
140 can_uniquely_dwim = 0;
141
142 if (e->flags & BOTTOM) {
143 if (can_uniquely_dwim)
144 strset_add(&ref_info->negative_refs, fullname);
145 if (!ref_info->negative_refexprs)
146 ref_info->onto = lookup_commit_reference_gently(repo,
147 &e->item->oid, 1);
148 ref_info->negative_refexprs++;
149 } else {
150 if (can_uniquely_dwim)
151 strset_add(&ref_info->positive_refs, fullname);
152 ref_info->positive_refexprs++;
153 }
154
155 free(fullname);
156 }
157}
158
159static void determine_replay_mode(struct repository *repo,
160 struct rev_cmdline_info *cmd_info,
161 const char *onto_name,
162 char **advance_name,
163 struct commit **onto,
164 struct strset **update_refs)
165{
166 struct ref_info rinfo;
167
168 get_ref_information(repo, cmd_info, &rinfo);
169 if (!rinfo.positive_refexprs)
170 die(_("need some commits to replay"));
171
172 die_for_incompatible_opt2(!!onto_name, "--onto",
173 !!*advance_name, "--advance");
174 if (onto_name) {
175 *onto = peel_committish(repo, onto_name);
176 if (rinfo.positive_refexprs <
177 strset_get_size(&rinfo.positive_refs))
178 die(_("all positive revisions given must be references"));
179 } else if (*advance_name) {
180 struct object_id oid;
181 char *fullname = NULL;
182
183 *onto = peel_committish(repo, *advance_name);
184 if (repo_dwim_ref(repo, *advance_name, strlen(*advance_name),
185 &oid, &fullname, 0) == 1) {
186 free(*advance_name);
187 *advance_name = fullname;
188 } else {
189 die(_("argument to --advance must be a reference"));
190 }
191 if (rinfo.positive_refexprs > 1)
192 die(_("cannot advance target with multiple sources because ordering would be ill-defined"));
193 } else {
194 int positive_refs_complete = (
195 rinfo.positive_refexprs ==
196 strset_get_size(&rinfo.positive_refs));
197 int negative_refs_complete = (
198 rinfo.negative_refexprs ==
199 strset_get_size(&rinfo.negative_refs));
200 /*
201 * We need either positive_refs_complete or
202 * negative_refs_complete, but not both.
203 */
204 if (rinfo.negative_refexprs > 0 &&
205 positive_refs_complete == negative_refs_complete)
206 die(_("cannot implicitly determine whether this is an --advance or --onto operation"));
207 if (negative_refs_complete) {
208 struct hashmap_iter iter;
209 struct strmap_entry *entry;
210 const char *last_key = NULL;
211
212 if (rinfo.negative_refexprs == 0)
213 die(_("all positive revisions given must be references"));
214 else if (rinfo.negative_refexprs > 1)
215 die(_("cannot implicitly determine whether this is an --advance or --onto operation"));
216 else if (rinfo.positive_refexprs > 1)
217 die(_("cannot advance target with multiple source branches because ordering would be ill-defined"));
218
219 /* Only one entry, but we have to loop to get it */
220 strset_for_each_entry(&rinfo.negative_refs,
221 &iter, entry) {
222 last_key = entry->key;
223 }
224
225 free(*advance_name);
226 *advance_name = xstrdup_or_null(last_key);
227 } else { /* positive_refs_complete */
228 if (rinfo.negative_refexprs > 1)
229 die(_("cannot implicitly determine correct base for --onto"));
230 if (rinfo.negative_refexprs == 1)
231 *onto = rinfo.onto;
232 }
233 }
234 if (!*advance_name) {
235 *update_refs = xcalloc(1, sizeof(**update_refs));
236 **update_refs = rinfo.positive_refs;
237 memset(&rinfo.positive_refs, 0, sizeof(**update_refs));
238 }
239 strset_clear(&rinfo.negative_refs);
240 strset_clear(&rinfo.positive_refs);
241}
242
243static struct commit *mapped_commit(kh_oid_map_t *replayed_commits,
244 struct commit *commit,
245 struct commit *fallback)
246{
247 khint_t pos = kh_get_oid_map(replayed_commits, commit->object.oid);
248 if (pos == kh_end(replayed_commits))
249 return fallback;
250 return kh_value(replayed_commits, pos);
251}
252
253static struct commit *pick_regular_commit(struct repository *repo,
254 struct commit *pickme,
255 kh_oid_map_t *replayed_commits,
256 struct commit *onto,
257 struct merge_options *merge_opt,
258 struct merge_result *result)
259{
260 struct commit *base, *replayed_base;
261 struct tree *pickme_tree, *base_tree;
262
263 base = pickme->parents->item;
264 replayed_base = mapped_commit(replayed_commits, base, onto);
265
266 result->tree = repo_get_commit_tree(repo, replayed_base);
267 pickme_tree = repo_get_commit_tree(repo, pickme);
268 base_tree = repo_get_commit_tree(repo, base);
269
270 merge_opt->branch1 = short_commit_name(repo, replayed_base);
271 merge_opt->branch2 = short_commit_name(repo, pickme);
272 merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2);
273
274 merge_incore_nonrecursive(merge_opt,
275 base_tree,
276 result->tree,
277 pickme_tree,
278 result);
279
280 free((char*)merge_opt->ancestor);
281 merge_opt->ancestor = NULL;
282 if (!result->clean)
283 return NULL;
284 return create_commit(repo, result->tree, pickme, replayed_base);
285}
286
287int cmd_replay(int argc,
288 const char **argv,
289 const char *prefix,
290 struct repository *repo)
291{
292 const char *advance_name_opt = NULL;
293 char *advance_name = NULL;
294 struct commit *onto = NULL;
295 const char *onto_name = NULL;
296 int contained = 0;
297
298 struct rev_info revs;
299 struct commit *last_commit = NULL;
300 struct commit *commit;
301 struct merge_options merge_opt;
302 struct merge_result result;
303 struct strset *update_refs = NULL;
304 kh_oid_map_t *replayed_commits;
305 int ret = 0;
306
307 const char * const replay_usage[] = {
308 N_("(EXPERIMENTAL!) git replay "
309 "([--contained] --onto <newbase> | --advance <branch>) "
310 "<revision-range>..."),
311 NULL
312 };
313 struct option replay_options[] = {
314 OPT_STRING(0, "advance", &advance_name_opt,
315 N_("branch"),
316 N_("make replay advance given branch")),
317 OPT_STRING(0, "onto", &onto_name,
318 N_("revision"),
319 N_("replay onto given commit")),
320 OPT_BOOL(0, "contained", &contained,
321 N_("advance all branches contained in revision-range")),
322 OPT_END()
323 };
324
325 argc = parse_options(argc, argv, prefix, replay_options, replay_usage,
326 PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
327
328 if (!onto_name && !advance_name_opt) {
329 error(_("option --onto or --advance is mandatory"));
330 usage_with_options(replay_usage, replay_options);
331 }
332
333 if (advance_name_opt && contained)
334 die(_("options '%s' and '%s' cannot be used together"),
335 "--advance", "--contained");
336 advance_name = xstrdup_or_null(advance_name_opt);
337
338 repo_init_revisions(repo, &revs, prefix);
339
340 /*
341 * Set desired values for rev walking options here. If they
342 * are changed by some user specified option in setup_revisions()
343 * below, we will detect that below and then warn.
344 *
345 * TODO: In the future we might want to either die(), or allow
346 * some options changing these values if we think they could
347 * be useful.
348 */
349 revs.reverse = 1;
350 revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
351 revs.topo_order = 1;
352 revs.simplify_history = 0;
353
354 argc = setup_revisions(argc, argv, &revs, NULL);
355 if (argc > 1) {
356 ret = error(_("unrecognized argument: %s"), argv[1]);
357 goto cleanup;
358 }
359
360 /*
361 * Detect and warn if we override some user specified rev
362 * walking options.
363 */
364 if (revs.reverse != 1) {
365 warning(_("some rev walking options will be overridden as "
366 "'%s' bit in 'struct rev_info' will be forced"),
367 "reverse");
368 revs.reverse = 1;
369 }
370 if (revs.sort_order != REV_SORT_IN_GRAPH_ORDER) {
371 warning(_("some rev walking options will be overridden as "
372 "'%s' bit in 'struct rev_info' will be forced"),
373 "sort_order");
374 revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
375 }
376 if (revs.topo_order != 1) {
377 warning(_("some rev walking options will be overridden as "
378 "'%s' bit in 'struct rev_info' will be forced"),
379 "topo_order");
380 revs.topo_order = 1;
381 }
382 if (revs.simplify_history != 0) {
383 warning(_("some rev walking options will be overridden as "
384 "'%s' bit in 'struct rev_info' will be forced"),
385 "simplify_history");
386 revs.simplify_history = 0;
387 }
388
389 determine_replay_mode(repo, &revs.cmdline, onto_name, &advance_name,
390 &onto, &update_refs);
391
392 if (!onto) /* FIXME: Should handle replaying down to root commit */
393 die("Replaying down to root commit is not supported yet!");
394
395 if (prepare_revision_walk(&revs) < 0) {
396 ret = error(_("error preparing revisions"));
397 goto cleanup;
398 }
399
400 init_basic_merge_options(&merge_opt, repo);
401 memset(&result, 0, sizeof(result));
402 merge_opt.show_rename_progress = 0;
403 last_commit = onto;
404 replayed_commits = kh_init_oid_map();
405 while ((commit = get_revision(&revs))) {
406 const struct name_decoration *decoration;
407 khint_t pos;
408 int hr;
409
410 if (!commit->parents)
411 die(_("replaying down to root commit is not supported yet!"));
412 if (commit->parents->next)
413 die(_("replaying merge commits is not supported yet!"));
414
415 last_commit = pick_regular_commit(repo, commit, replayed_commits,
416 onto, &merge_opt, &result);
417 if (!last_commit)
418 break;
419
420 /* Record commit -> last_commit mapping */
421 pos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);
422 if (hr == 0)
423 BUG("Duplicate rewritten commit: %s\n",
424 oid_to_hex(&commit->object.oid));
425 kh_value(replayed_commits, pos) = last_commit;
426
427 /* Update any necessary branches */
428 if (advance_name)
429 continue;
430 decoration = get_name_decoration(&commit->object);
431 if (!decoration)
432 continue;
433 while (decoration) {
434 if (decoration->type == DECORATION_REF_LOCAL &&
435 (contained || strset_contains(update_refs,
436 decoration->name))) {
437 printf("update %s %s %s\n",
438 decoration->name,
439 oid_to_hex(&last_commit->object.oid),
440 oid_to_hex(&commit->object.oid));
441 }
442 decoration = decoration->next;
443 }
444 }
445
446 /* In --advance mode, advance the target ref */
447 if (result.clean == 1 && advance_name) {
448 printf("update %s %s %s\n",
449 advance_name,
450 oid_to_hex(&last_commit->object.oid),
451 oid_to_hex(&onto->object.oid));
452 }
453
454 merge_finalize(&merge_opt, &result);
455 kh_destroy_oid_map(replayed_commits);
456 if (update_refs) {
457 strset_clear(update_refs);
458 free(update_refs);
459 }
460 ret = result.clean;
461
462cleanup:
463 release_revisions(&revs);
464 free(advance_name);
465
466 /* Return */
467 if (ret < 0)
468 exit(128);
469 return ret ? 0 : 1;
470}