builtin/replay: move core logic into "libgit.a"

Move the core logic used to replay commits into "libgit.a" so that it can be easily reused by other commands. It will be used in a subsequent commit where we're about to introduce a new git-history(1) command. Note that with this change we have no sign-comparison warnings anymore, and neither do we depend on `the_repository`. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Jan 13, 2026 at 10:54 UTC 6aeda3cf5b6d5c38e9b51f1f39133e23d0981d55
5 files changed +418 -365
Makefile
+1
@@ -1285,6 +1285,7 @@ LIB_OBJS += repack-geometry.o
1285 LIB_OBJS += repack-midx.o
1286 LIB_OBJS += repack-promisor.o
1287 LIB_OBJS += replace-object.o
1288 +LIB_OBJS += replay.o
1289 LIB_OBJS += repo-settings.o
1290 LIB_OBJS += repository.o
1291 LIB_OBJS += rerere.o
builtin/replay.c
+1 -365
@@ -2,386 +2,22 @@
2 * "git replay" builtin command
3 */
4
5 -#define USE_THE_REPOSITORY_VARIABLE
6 -#define DISABLE_SIGN_COMPARE_WARNINGS
7 -
5 #include "git-compat-util.h"
6
7 #include "builtin.h"
8 #include "config.h"
12 -#include "environment.h"
9 #include "hex.h"
14 -#include "lockfile.h"
15 -#include "merge-ort.h"
10 #include "object-name.h"
11 #include "parse-options.h"
12 #include "refs.h"
13 +#include "replay.h"
14 #include "revision.h"
20 -#include "strmap.h"
21 -#include <oidset.h>
22 -#include <tree.h>
15
16 enum ref_action_mode {
17 REF_ACTION_UPDATE,
18 REF_ACTION_PRINT,
19 };
20
29 -static const char *short_commit_name(struct repository *repo,
30 - struct commit *commit)
31 -{
32 - return repo_find_unique_abbrev(repo, &commit->object.oid,
33 - DEFAULT_ABBREV);
34 -}
35 -
36 -static struct commit *peel_committish(struct repository *repo,
37 - const char *name,
38 - const char *mode)
39 -{
40 - struct object *obj;
41 - struct object_id oid;
42 -
43 - if (repo_get_oid(repo, name, &oid))
44 - die(_("'%s' is not a valid commit-ish for %s"), name, mode);
45 - obj = parse_object_or_die(repo, &oid, name);
46 - return (struct commit *)repo_peel_to_type(repo, name, 0, obj,
47 - OBJ_COMMIT);
48 -}
49 -
50 -static char *get_author(const char *message)
51 -{
52 - size_t len;
53 - const char *a;
54 -
55 - a = find_commit_header(message, "author", &len);
56 - if (a)
57 - return xmemdupz(a, len);
58 -
59 - return NULL;
60 -}
61 -
62 -static struct commit *create_commit(struct repository *repo,
63 - struct tree *tree,
64 - struct commit *based_on,
65 - struct commit *parent)
66 -{
67 - struct object_id ret;
68 - struct object *obj = NULL;
69 - struct commit_list *parents = NULL;
70 - char *author;
71 - char *sign_commit = NULL; /* FIXME: cli users might want to sign again */
72 - struct commit_extra_header *extra = NULL;
73 - struct strbuf msg = STRBUF_INIT;
74 - const char *out_enc = get_commit_output_encoding();
75 - const char *message = repo_logmsg_reencode(repo, based_on,
76 - NULL, out_enc);
77 - const char *orig_message = NULL;
78 - const char *exclude_gpgsig[] = { "gpgsig", "gpgsig-sha256", NULL };
79 -
80 - commit_list_insert(parent, &parents);
81 - extra = read_commit_extra_headers(based_on, exclude_gpgsig);
82 - find_commit_subject(message, &orig_message);
83 - strbuf_addstr(&msg, orig_message);
84 - author = get_author(message);
85 - reset_ident_date();
86 - if (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents,
87 - &ret, author, NULL, sign_commit, extra)) {
88 - error(_("failed to write commit object"));
89 - goto out;
90 - }
91 -
92 - obj = parse_object(repo, &ret);
93 -
94 -out:
95 - repo_unuse_commit_buffer(the_repository, based_on, message);
96 - free_commit_extra_headers(extra);
97 - free_commit_list(parents);
98 - strbuf_release(&msg);
99 - free(author);
100 - return (struct commit *)obj;
101 -}
102 -
103 -struct ref_info {
104 - struct commit *onto;
105 - struct strset positive_refs;
106 - struct strset negative_refs;
107 - int positive_refexprs;
108 - int negative_refexprs;
109 -};
110 -
111 -static void get_ref_information(struct repository *repo,
112 - struct rev_cmdline_info *cmd_info,
113 - struct ref_info *ref_info)
114 -{
115 - int i;
116 -
117 - ref_info->onto = NULL;
118 - strset_init(&ref_info->positive_refs);
119 - strset_init(&ref_info->negative_refs);
120 - ref_info->positive_refexprs = 0;
121 - ref_info->negative_refexprs = 0;
122 -
123 - /*
124 - * When the user specifies e.g.
125 - * git replay origin/main..mybranch
126 - * git replay ^origin/next mybranch1 mybranch2
127 - * we want to be able to determine where to replay the commits. In
128 - * these examples, the branches are probably based on an old version
129 - * of either origin/main or origin/next, so we want to replay on the
130 - * newest version of that branch. In contrast we would want to error
131 - * out if they ran
132 - * git replay ^origin/master ^origin/next mybranch
133 - * git replay mybranch~2..mybranch
134 - * the first of those because there's no unique base to choose, and
135 - * the second because they'd likely just be replaying commits on top
136 - * of the same commit and not making any difference.
137 - */
138 - for (i = 0; i < cmd_info->nr; i++) {
139 - struct rev_cmdline_entry *e = cmd_info->rev + i;
140 - struct object_id oid;
141 - const char *refexpr = e->name;
142 - char *fullname = NULL;
143 - int can_uniquely_dwim = 1;
144 -
145 - if (*refexpr == '^')
146 - refexpr++;
147 - if (repo_dwim_ref(repo, refexpr, strlen(refexpr), &oid, &fullname, 0) != 1)
148 - can_uniquely_dwim = 0;
149 -
150 - if (e->flags & BOTTOM) {
151 - if (can_uniquely_dwim)
152 - strset_add(&ref_info->negative_refs, fullname);
153 - if (!ref_info->negative_refexprs)
154 - ref_info->onto = lookup_commit_reference_gently(repo,
155 - &e->item->oid, 1);
156 - ref_info->negative_refexprs++;
157 - } else {
158 - if (can_uniquely_dwim)
159 - strset_add(&ref_info->positive_refs, fullname);
160 - ref_info->positive_refexprs++;
161 - }
162 -
163 - free(fullname);
164 - }
165 -}
166 -
167 -static void set_up_replay_mode(struct repository *repo,
168 - struct rev_cmdline_info *cmd_info,
169 - const char *onto_name,
170 - char **advance_name,
171 - struct commit **onto,
172 - struct strset **update_refs)
173 -{
174 - struct ref_info rinfo;
175 -
176 - get_ref_information(repo, cmd_info, &rinfo);
177 - if (!rinfo.positive_refexprs)
178 - die(_("need some commits to replay"));
179 -
180 - if (!onto_name == !*advance_name)
181 - BUG("one and only one of onto_name and *advance_name must be given");
182 -
183 - if (onto_name) {
184 - *onto = peel_committish(repo, onto_name, "--onto");
185 - if (rinfo.positive_refexprs <
186 - strset_get_size(&rinfo.positive_refs))
187 - die(_("all positive revisions given must be references"));
188 - *update_refs = xcalloc(1, sizeof(**update_refs));
189 - **update_refs = rinfo.positive_refs;
190 - memset(&rinfo.positive_refs, 0, sizeof(**update_refs));
191 - } else {
192 - struct object_id oid;
193 - char *fullname = NULL;
194 -
195 - if (!*advance_name)
196 - BUG("expected either onto_name or *advance_name in this function");
197 -
198 - if (repo_dwim_ref(repo, *advance_name, strlen(*advance_name),
199 - &oid, &fullname, 0) == 1) {
200 - free(*advance_name);
201 - *advance_name = fullname;
202 - } else {
203 - die(_("argument to --advance must be a reference"));
204 - }
205 - *onto = peel_committish(repo, *advance_name, "--advance");
206 - if (rinfo.positive_refexprs > 1)
207 - die(_("cannot advance target with multiple sources because ordering would be ill-defined"));
208 - }
209 - strset_clear(&rinfo.negative_refs);
210 - strset_clear(&rinfo.positive_refs);
211 -}
212 -
213 -static struct commit *mapped_commit(kh_oid_map_t *replayed_commits,
214 - struct commit *commit,
215 - struct commit *fallback)
216 -{
217 - khint_t pos = kh_get_oid_map(replayed_commits, commit->object.oid);
218 - if (pos == kh_end(replayed_commits))
219 - return fallback;
220 - return kh_value(replayed_commits, pos);
221 -}
222 -
223 -static struct commit *pick_regular_commit(struct repository *repo,
224 - struct commit *pickme,
225 - kh_oid_map_t *replayed_commits,
226 - struct commit *onto,
227 - struct merge_options *merge_opt,
228 - struct merge_result *result)
229 -{
230 - struct commit *base, *replayed_base;
231 - struct tree *pickme_tree, *base_tree;
232 -
233 - base = pickme->parents->item;
234 - replayed_base = mapped_commit(replayed_commits, base, onto);
235 -
236 - result->tree = repo_get_commit_tree(repo, replayed_base);
237 - pickme_tree = repo_get_commit_tree(repo, pickme);
238 - base_tree = repo_get_commit_tree(repo, base);
239 -
240 - merge_opt->branch1 = short_commit_name(repo, replayed_base);
241 - merge_opt->branch2 = short_commit_name(repo, pickme);
242 - merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2);
243 -
244 - merge_incore_nonrecursive(merge_opt,
245 - base_tree,
246 - result->tree,
247 - pickme_tree,
248 - result);
249 -
250 - free((char*)merge_opt->ancestor);
251 - merge_opt->ancestor = NULL;
252 - if (!result->clean)
253 - return NULL;
254 - return create_commit(repo, result->tree, pickme, replayed_base);
255 -}
256 -
257 -struct replay_revisions_options {
258 - const char *advance;
259 - const char *onto;
260 - int contained;
261 -};
262 -
263 -struct replay_result {
264 - struct replay_ref_update {
265 - char *refname;
266 - struct object_id old_oid;
267 - struct object_id new_oid;
268 - } *updates;
269 - size_t updates_nr, updates_alloc;
270 -};
271 -
272 -static void replay_result_release(struct replay_result *result)
273 -{
274 - for (size_t i = 0; i < result->updates_nr; i++)
275 - free(result->updates[i].refname);
276 - free(result->updates);
277 -}
278 -
279 -static void replay_result_queue_update(struct replay_result *result,
280 - const char *refname,
281 - const struct object_id *old_oid,
282 - const struct object_id *new_oid)
283 -{
284 - ALLOC_GROW(result->updates, result->updates_nr + 1, result->updates_alloc);
285 - result->updates[result->updates_nr].refname = xstrdup(refname);
286 - result->updates[result->updates_nr].old_oid = *old_oid;
287 - result->updates[result->updates_nr].new_oid = *new_oid;
288 - result->updates_nr++;
289 -}
290 -
291 -static int replay_revisions(struct rev_info *revs,
292 - struct replay_revisions_options *opts,
293 - struct replay_result *out)
294 -{
295 - kh_oid_map_t *replayed_commits = NULL;
296 - struct strset *update_refs = NULL;
297 - struct commit *last_commit = NULL;
298 - struct commit *commit;
299 - struct commit *onto = NULL;
300 - struct merge_options merge_opt;
301 - struct merge_result result;
302 - char *advance;
303 - int ret;
304 -
305 - advance = xstrdup_or_null(opts->advance);
306 - set_up_replay_mode(revs->repo, &revs->cmdline, opts->onto, &advance,
307 - &onto, &update_refs);
308 -
309 - /* FIXME: Should allow replaying commits with the first as a root commit */
310 -
311 - if (prepare_revision_walk(revs) < 0) {
312 - ret = error(_("error preparing revisions"));
313 - goto out;
314 - }
315 -
316 - init_basic_merge_options(&merge_opt, revs->repo);
317 - memset(&result, 0, sizeof(result));
318 - merge_opt.show_rename_progress = 0;
319 - last_commit = onto;
320 - replayed_commits = kh_init_oid_map();
321 - while ((commit = get_revision(revs))) {
322 - const struct name_decoration *decoration;
323 - khint_t pos;
324 - int hr;
325 -
326 - if (!commit->parents)
327 - die(_("replaying down from root commit is not supported yet!"));
328 - if (commit->parents->next)
329 - die(_("replaying merge commits is not supported yet!"));
330 -
331 - last_commit = pick_regular_commit(revs->repo, commit, replayed_commits,
332 - onto, &merge_opt, &result);
333 - if (!last_commit)
334 - break;
335 -
336 - /* Record commit -> last_commit mapping */
337 - pos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);
338 - if (hr == 0)
339 - BUG("Duplicate rewritten commit: %s\n",
340 - oid_to_hex(&commit->object.oid));
341 - kh_value(replayed_commits, pos) = last_commit;
342 -
343 - /* Update any necessary branches */
344 - if (advance)
345 - continue;
346 - decoration = get_name_decoration(&commit->object);
347 - if (!decoration)
348 - continue;
349 - while (decoration) {
350 - if (decoration->type == DECORATION_REF_LOCAL &&
351 - (opts->contained || strset_contains(update_refs,
352 - decoration->name))) {
353 - replay_result_queue_update(out, decoration->name,
354 - &commit->object.oid,
355 - &last_commit->object.oid);
356 - }
357 - decoration = decoration->next;
358 - }
359 - }
360 -
361 - if (!result.clean) {
362 - ret = 1;
363 - goto out;
364 - }
365 -
366 - /* In --advance mode, advance the target ref */
367 - if (advance)
368 - replay_result_queue_update(out, advance,
369 - &onto->object.oid,
370 - &last_commit->object.oid);
371 -
372 - ret = 0;
373 -
374 -out:
375 - if (update_refs) {
376 - strset_clear(update_refs);
377 - free(update_refs);
378 - }
379 - kh_destroy_oid_map(replayed_commits);
380 - merge_finalize(&merge_opt, &result);
381 - free(advance);
382 - return ret;
383 -}
384 -
21 static enum ref_action_mode parse_ref_action_mode(const char *ref_action, const char *source)
22 {
23 if (!ref_action || !strcmp(ref_action, "update"))
meson.build
+1
@@ -471,6 +471,7 @@ libgit_sources = [
471 'repack-midx.c',
472 'repack-promisor.c',
473 'replace-object.c',
474 + 'replay.c',
475 'repo-settings.c',
476 'repository.c',
477 'rerere.c',
replay.c new
+354
@@ -0,0 +1,354 @@
1 +#define USE_THE_REPOSITORY_VARIABLE
2 +#define DISABLE_SIGN_COMPARE_WARNINGS
3 +
4 +#include "git-compat-util.h"
5 +#include "environment.h"
6 +#include "hex.h"
7 +#include "merge-ort.h"
8 +#include "object-name.h"
9 +#include "refs.h"
10 +#include "replay.h"
11 +#include "revision.h"
12 +#include "strmap.h"
13 +#include "tree.h"
14 +
15 +static const char *short_commit_name(struct repository *repo,
16 + struct commit *commit)
17 +{
18 + return repo_find_unique_abbrev(repo, &commit->object.oid,
19 + DEFAULT_ABBREV);
20 +}
21 +
22 +static struct commit *peel_committish(struct repository *repo,
23 + const char *name,
24 + const char *mode)
25 +{
26 + struct object *obj;
27 + struct object_id oid;
28 +
29 + if (repo_get_oid(repo, name, &oid))
30 + die(_("'%s' is not a valid commit-ish for %s"), name, mode);
31 + obj = parse_object_or_die(repo, &oid, name);
32 + return (struct commit *)repo_peel_to_type(repo, name, 0, obj,
33 + OBJ_COMMIT);
34 +}
35 +
36 +static char *get_author(const char *message)
37 +{
38 + size_t len;
39 + const char *a;
40 +
41 + a = find_commit_header(message, "author", &len);
42 + if (a)
43 + return xmemdupz(a, len);
44 +
45 + return NULL;
46 +}
47 +
48 +static struct commit *create_commit(struct repository *repo,
49 + struct tree *tree,
50 + struct commit *based_on,
51 + struct commit *parent)
52 +{
53 + struct object_id ret;
54 + struct object *obj = NULL;
55 + struct commit_list *parents = NULL;
56 + char *author;
57 + char *sign_commit = NULL; /* FIXME: cli users might want to sign again */
58 + struct commit_extra_header *extra = NULL;
59 + struct strbuf msg = STRBUF_INIT;
60 + const char *out_enc = get_commit_output_encoding();
61 + const char *message = repo_logmsg_reencode(repo, based_on,
62 + NULL, out_enc);
63 + const char *orig_message = NULL;
64 + const char *exclude_gpgsig[] = { "gpgsig", "gpgsig-sha256", NULL };
65 +
66 + commit_list_insert(parent, &parents);
67 + extra = read_commit_extra_headers(based_on, exclude_gpgsig);
68 + find_commit_subject(message, &orig_message);
69 + strbuf_addstr(&msg, orig_message);
70 + author = get_author(message);
71 + reset_ident_date();
72 + if (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents,
73 + &ret, author, NULL, sign_commit, extra)) {
74 + error(_("failed to write commit object"));
75 + goto out;
76 + }
77 +
78 + obj = parse_object(repo, &ret);
79 +
80 +out:
81 + repo_unuse_commit_buffer(the_repository, based_on, message);
82 + free_commit_extra_headers(extra);
83 + free_commit_list(parents);
84 + strbuf_release(&msg);
85 + free(author);
86 + return (struct commit *)obj;
87 +}
88 +
89 +struct ref_info {
90 + struct commit *onto;
91 + struct strset positive_refs;
92 + struct strset negative_refs;
93 + int positive_refexprs;
94 + int negative_refexprs;
95 +};
96 +
97 +static void get_ref_information(struct repository *repo,
98 + struct rev_cmdline_info *cmd_info,
99 + struct ref_info *ref_info)
100 +{
101 + int i;
102 +
103 + ref_info->onto = NULL;
104 + strset_init(&ref_info->positive_refs);
105 + strset_init(&ref_info->negative_refs);
106 + ref_info->positive_refexprs = 0;
107 + ref_info->negative_refexprs = 0;
108 +
109 + /*
110 + * When the user specifies e.g.
111 + * git replay origin/main..mybranch
112 + * git replay ^origin/next mybranch1 mybranch2
113 + * we want to be able to determine where to replay the commits. In
114 + * these examples, the branches are probably based on an old version
115 + * of either origin/main or origin/next, so we want to replay on the
116 + * newest version of that branch. In contrast we would want to error
117 + * out if they ran
118 + * git replay ^origin/master ^origin/next mybranch
119 + * git replay mybranch~2..mybranch
120 + * the first of those because there's no unique base to choose, and
121 + * the second because they'd likely just be replaying commits on top
122 + * of the same commit and not making any difference.
123 + */
124 + for (i = 0; i < cmd_info->nr; i++) {
125 + struct rev_cmdline_entry *e = cmd_info->rev + i;
126 + struct object_id oid;
127 + const char *refexpr = e->name;
128 + char *fullname = NULL;
129 + int can_uniquely_dwim = 1;
130 +
131 + if (*refexpr == '^')
132 + refexpr++;
133 + if (repo_dwim_ref(repo, refexpr, strlen(refexpr), &oid, &fullname, 0) != 1)
134 + can_uniquely_dwim = 0;
135 +
136 + if (e->flags & BOTTOM) {
137 + if (can_uniquely_dwim)
138 + strset_add(&ref_info->negative_refs, fullname);
139 + if (!ref_info->negative_refexprs)
140 + ref_info->onto = lookup_commit_reference_gently(repo,
141 + &e->item->oid, 1);
142 + ref_info->negative_refexprs++;
143 + } else {
144 + if (can_uniquely_dwim)
145 + strset_add(&ref_info->positive_refs, fullname);
146 + ref_info->positive_refexprs++;
147 + }
148 +
149 + free(fullname);
150 + }
151 +}
152 +
153 +static void set_up_replay_mode(struct repository *repo,
154 + struct rev_cmdline_info *cmd_info,
155 + const char *onto_name,
156 + char **advance_name,
157 + struct commit **onto,
158 + struct strset **update_refs)
159 +{
160 + struct ref_info rinfo;
161 +
162 + get_ref_information(repo, cmd_info, &rinfo);
163 + if (!rinfo.positive_refexprs)
164 + die(_("need some commits to replay"));
165 +
166 + if (!onto_name == !*advance_name)
167 + BUG("one and only one of onto_name and *advance_name must be given");
168 +
169 + if (onto_name) {
170 + *onto = peel_committish(repo, onto_name, "--onto");
171 + if (rinfo.positive_refexprs <
172 + strset_get_size(&rinfo.positive_refs))
173 + die(_("all positive revisions given must be references"));
174 + *update_refs = xcalloc(1, sizeof(**update_refs));
175 + **update_refs = rinfo.positive_refs;
176 + memset(&rinfo.positive_refs, 0, sizeof(**update_refs));
177 + } else {
178 + struct object_id oid;
179 + char *fullname = NULL;
180 +
181 + if (!*advance_name)
182 + BUG("expected either onto_name or *advance_name in this function");
183 +
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 + *onto = peel_committish(repo, *advance_name, "--advance");
192 + if (rinfo.positive_refexprs > 1)
193 + die(_("cannot advance target with multiple sources because ordering would be ill-defined"));
194 + }
195 + strset_clear(&rinfo.negative_refs);
196 + strset_clear(&rinfo.positive_refs);
197 +}
198 +
199 +static struct commit *mapped_commit(kh_oid_map_t *replayed_commits,
200 + struct commit *commit,
201 + struct commit *fallback)
202 +{
203 + khint_t pos = kh_get_oid_map(replayed_commits, commit->object.oid);
204 + if (pos == kh_end(replayed_commits))
205 + return fallback;
206 + return kh_value(replayed_commits, pos);
207 +}
208 +
209 +static struct commit *pick_regular_commit(struct repository *repo,
210 + struct commit *pickme,
211 + kh_oid_map_t *replayed_commits,
212 + struct commit *onto,
213 + struct merge_options *merge_opt,
214 + struct merge_result *result)
215 +{
216 + struct commit *base, *replayed_base;
217 + struct tree *pickme_tree, *base_tree;
218 +
219 + base = pickme->parents->item;
220 + replayed_base = mapped_commit(replayed_commits, base, onto);
221 +
222 + result->tree = repo_get_commit_tree(repo, replayed_base);
223 + pickme_tree = repo_get_commit_tree(repo, pickme);
224 + base_tree = repo_get_commit_tree(repo, base);
225 +
226 + merge_opt->branch1 = short_commit_name(repo, replayed_base);
227 + merge_opt->branch2 = short_commit_name(repo, pickme);
228 + merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2);
229 +
230 + merge_incore_nonrecursive(merge_opt,
231 + base_tree,
232 + result->tree,
233 + pickme_tree,
234 + result);
235 +
236 + free((char*)merge_opt->ancestor);
237 + merge_opt->ancestor = NULL;
238 + if (!result->clean)
239 + return NULL;
240 + return create_commit(repo, result->tree, pickme, replayed_base);
241 +}
242 +
243 +void replay_result_release(struct replay_result *result)
244 +{
245 + for (size_t i = 0; i < result->updates_nr; i++)
246 + free(result->updates[i].refname);
247 + free(result->updates);
248 +}
249 +
250 +static void replay_result_queue_update(struct replay_result *result,
251 + const char *refname,
252 + const struct object_id *old_oid,
253 + const struct object_id *new_oid)
254 +{
255 + ALLOC_GROW(result->updates, result->updates_nr + 1, result->updates_alloc);
256 + result->updates[result->updates_nr].refname = xstrdup(refname);
257 + result->updates[result->updates_nr].old_oid = *old_oid;
258 + result->updates[result->updates_nr].new_oid = *new_oid;
259 + result->updates_nr++;
260 +}
261 +
262 +int replay_revisions(struct rev_info *revs,
263 + struct replay_revisions_options *opts,
264 + struct replay_result *out)
265 +{
266 + kh_oid_map_t *replayed_commits = NULL;
267 + struct strset *update_refs = NULL;
268 + struct commit *last_commit = NULL;
269 + struct commit *commit;
270 + struct commit *onto = NULL;
271 + struct merge_options merge_opt;
272 + struct merge_result result;
273 + char *advance;
274 + int ret;
275 +
276 + advance = xstrdup_or_null(opts->advance);
277 + set_up_replay_mode(revs->repo, &revs->cmdline, opts->onto, &advance,
278 + &onto, &update_refs);
279 +
280 + /* FIXME: Should allow replaying commits with the first as a root commit */
281 +
282 + if (prepare_revision_walk(revs) < 0) {
283 + ret = error(_("error preparing revisions"));
284 + goto out;
285 + }
286 +
287 + init_basic_merge_options(&merge_opt, revs->repo);
288 + memset(&result, 0, sizeof(result));
289 + merge_opt.show_rename_progress = 0;
290 + last_commit = onto;
291 + replayed_commits = kh_init_oid_map();
292 + while ((commit = get_revision(revs))) {
293 + const struct name_decoration *decoration;
294 + khint_t pos;
295 + int hr;
296 +
297 + if (!commit->parents)
298 + die(_("replaying down from root commit is not supported yet!"));
299 + if (commit->parents->next)
300 + die(_("replaying merge commits is not supported yet!"));
301 +
302 + last_commit = pick_regular_commit(revs->repo, commit, replayed_commits,
303 + onto, &merge_opt, &result);
304 + if (!last_commit)
305 + break;
306 +
307 + /* Record commit -> last_commit mapping */
308 + pos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);
309 + if (hr == 0)
310 + BUG("Duplicate rewritten commit: %s\n",
311 + oid_to_hex(&commit->object.oid));
312 + kh_value(replayed_commits, pos) = last_commit;
313 +
314 + /* Update any necessary branches */
315 + if (advance)
316 + continue;
317 + decoration = get_name_decoration(&commit->object);
318 + if (!decoration)
319 + continue;
320 + while (decoration) {
321 + if (decoration->type == DECORATION_REF_LOCAL &&
322 + (opts->contained || strset_contains(update_refs,
323 + decoration->name))) {
324 + replay_result_queue_update(out, decoration->name,
325 + &commit->object.oid,
326 + &last_commit->object.oid);
327 + }
328 + decoration = decoration->next;
329 + }
330 + }
331 +
332 + if (!result.clean) {
333 + ret = 1;
334 + goto out;
335 + }
336 +
337 + /* In --advance mode, advance the target ref */
338 + if (advance)
339 + replay_result_queue_update(out, advance,
340 + &onto->object.oid,
341 + &last_commit->object.oid);
342 +
343 + ret = 0;
344 +
345 +out:
346 + if (update_refs) {
347 + strset_clear(update_refs);
348 + free(update_refs);
349 + }
350 + kh_destroy_oid_map(replayed_commits);
351 + merge_finalize(&merge_opt, &result);
352 + free(advance);
353 + return ret;
354 +}
replay.h new
+61
@@ -0,0 +1,61 @@
1 +#ifndef REPLAY_H
2 +#define REPLAY_H
3 +
4 +#include "hash.h"
5 +
6 +struct repository;
7 +struct rev_info;
8 +
9 +/*
10 + * A set of options that can be passed to `replay_revisions()`.
11 + */
12 +struct replay_revisions_options {
13 + /*
14 + * Starting point at which to create the new commits; must be a branch
15 + * name. The branch will be updated to point to the rewritten commits.
16 + * This option is mutually exclusive with `onto`.
17 + */
18 + const char *advance;
19 +
20 + /*
21 + * Starting point at which to create the new commits; must be a
22 + * committish. References pointing at decendants of `onto` will be
23 + * updated to point to the new commits.
24 + */
25 + const char *onto;
26 +
27 + /*
28 + * Update branches that point at commits in the given revision range.
29 + * Requires `onto` to be set.
30 + */
31 + int contained;
32 +};
33 +
34 +/* This struct is used as an out-parameter by `replay_revisions()`. */
35 +struct replay_result {
36 + /*
37 + * The set of reference updates that are caused by replaying the
38 + * commits.
39 + */
40 + struct replay_ref_update {
41 + char *refname;
42 + struct object_id old_oid;
43 + struct object_id new_oid;
44 + } *updates;
45 + size_t updates_nr, updates_alloc;
46 +};
47 +
48 +void replay_result_release(struct replay_result *result);
49 +
50 +/*
51 + * Replay a set of commits onto a new location. Leaves both the working tree,
52 + * index and references untouched. Reference updates caused by the replay will
53 + * be recorded in the `updates` out pointer.
54 + *
55 + * Returns 0 on success, 1 on conflict and a negative error code otherwise.
56 + */
57 +int replay_revisions(struct rev_info *revs,
58 + struct replay_revisions_options *opts,
59 + struct replay_result *out);
60 +
61 +#endif