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