8
#include "refs.h"
9
#include "replay.h"
10
#include "revision.h"
11
+#include "sequencer.h"
12
#include "strmap.h"
13
#include "tree.h"
14
15
+enum replay_mode {
16
+ REPLAY_MODE_PICK,
17
+ REPLAY_MODE_REVERT,
18
+};
19
+
20
static const char *short_commit_name(struct repository *repo,
21
struct commit *commit)
22
{
50
return NULL;
51
}
52
53
+static void generate_revert_message(struct strbuf *msg,
54
+ struct commit *commit,
55
+ struct repository *repo)
56
+{
57
+ const char *out_enc = get_commit_output_encoding();
58
+ const char *message = repo_logmsg_reencode(repo, commit, NULL, out_enc);
59
+ const char *subject_start;
60
+ int subject_len;
61
+ char *subject;
62
+
63
+ subject_len = find_commit_subject(message, &subject_start);
64
+ subject = xmemdupz(subject_start, subject_len);
65
+
66
+ sequencer_format_revert_message(repo, subject, commit,
67
+ commit->parents ? commit->parents->item : NULL,
68
+ false, msg);
69
+
70
+ free(subject);
71
+ repo_unuse_commit_buffer(repo, commit, message);
72
+}
73
+
74
static struct commit *create_commit(struct repository *repo,
75
struct tree *tree,
76
struct commit *based_on,
50
- struct commit *parent)
77
+ struct commit *parent,
78
+ enum replay_mode mode)
79
{
80
struct object_id ret;
81
struct object *obj = NULL;
82
struct commit_list *parents = NULL;
55
- char *author;
83
+ char *author = NULL;
84
char *sign_commit = NULL; /* FIXME: cli users might want to sign again */
85
struct commit_extra_header *extra = NULL;
86
struct strbuf msg = STRBUF_INIT;
92
93
commit_list_insert(parent, &parents);
94
extra = read_commit_extra_headers(based_on, exclude_gpgsig);
67
- find_commit_subject(message, &orig_message);
68
- strbuf_addstr(&msg, orig_message);
69
- author = get_author(message);
95
+ if (mode == REPLAY_MODE_REVERT) {
96
+ generate_revert_message(&msg, based_on, repo);
97
+ /* For revert, use current user as author (NULL = use default) */
98
+ } else if (mode == REPLAY_MODE_PICK) {
99
+ find_commit_subject(message, &orig_message);
100
+ strbuf_addstr(&msg, orig_message);
101
+ author = get_author(message);
102
+ } else {
103
+ BUG("unexpected replay mode %d", mode);
104
+ }
105
reset_ident_date();
106
if (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents,
107
&ret, author, NULL, sign_commit, extra)) {
182
}
183
}
184
185
+static void set_up_branch_mode(struct repository *repo,
186
+ char **branch_name,
187
+ const char *option_name,
188
+ struct ref_info *rinfo,
189
+ struct commit **onto)
190
+{
191
+ struct object_id oid;
192
+ char *fullname = NULL;
193
+
194
+ if (repo_dwim_ref(repo, *branch_name, strlen(*branch_name),
195
+ &oid, &fullname, 0) == 1) {
196
+ free(*branch_name);
197
+ *branch_name = fullname;
198
+ } else {
199
+ die(_("argument to %s must be a reference"), option_name);
200
+ }
201
+ *onto = peel_committish(repo, *branch_name, option_name);
202
+ if (rinfo->positive_refexprs > 1)
203
+ die(_("'%s' cannot be used with multiple revision ranges "
204
+ "because the ordering would be ill-defined"),
205
+ option_name);
206
+}
207
+
208
static void set_up_replay_mode(struct repository *repo,
209
struct rev_cmdline_info *cmd_info,
210
const char *onto_name,
211
bool *detached_head,
212
char **advance_name,
213
+ char **revert_name,
214
struct commit **onto,
215
struct strset **update_refs)
216
{
225
if (!rinfo.positive_refexprs)
226
die(_("need some commits to replay"));
227
169
- if (!onto_name == !*advance_name)
170
- BUG("one and only one of onto_name and *advance_name must be given");
171
-
228
if (onto_name) {
229
*onto = peel_committish(repo, onto_name, "--onto");
230
if (rinfo.positive_refexprs <
233
*update_refs = xcalloc(1, sizeof(**update_refs));
234
**update_refs = rinfo.positive_refs;
235
memset(&rinfo.positive_refs, 0, sizeof(**update_refs));
236
+ } else if (*advance_name) {
237
+ set_up_branch_mode(repo, advance_name, "--advance", &rinfo, onto);
238
+ } else if (*revert_name) {
239
+ set_up_branch_mode(repo, revert_name, "--revert", &rinfo, onto);
240
} else {
181
- struct object_id oid;
182
- char *fullname = NULL;
183
-
184
- if (!*advance_name)
185
- BUG("expected either onto_name or *advance_name in this function");
186
-
187
- if (repo_dwim_ref(repo, *advance_name, strlen(*advance_name),
188
- &oid, &fullname, 0) == 1) {
189
- free(*advance_name);
190
- *advance_name = fullname;
191
- } else {
192
- die(_("argument to --advance must be a reference"));
193
- }
194
- *onto = peel_committish(repo, *advance_name, "--advance");
195
- if (rinfo.positive_refexprs > 1)
196
- die(_("cannot advance target with multiple sources because ordering would be ill-defined"));
241
+ BUG("expected one of onto_name, *advance_name, or *revert_name");
242
}
243
strset_clear(&rinfo.negative_refs);
244
strset_clear(&rinfo.positive_refs);
259
kh_oid_map_t *replayed_commits,
260
struct commit *onto,
261
struct merge_options *merge_opt,
217
- struct merge_result *result)
262
+ struct merge_result *result,
263
+ enum replay_mode mode)
264
{
265
struct commit *base, *replayed_base;
266
struct tree *pickme_tree, *base_tree, *replayed_base_tree;
272
pickme_tree = repo_get_commit_tree(repo, pickme);
273
base_tree = repo_get_commit_tree(repo, base);
274
229
- merge_opt->branch1 = short_commit_name(repo, replayed_base);
230
- merge_opt->branch2 = short_commit_name(repo, pickme);
231
- merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2);
232
-
233
- merge_incore_nonrecursive(merge_opt,
234
- base_tree,
235
- replayed_base_tree,
236
- pickme_tree,
237
- result);
238
-
239
- free((char*)merge_opt->ancestor);
275
+ if (mode == REPLAY_MODE_PICK) {
276
+ /* Cherry-pick: normal order */
277
+ merge_opt->branch1 = short_commit_name(repo, replayed_base);
278
+ merge_opt->branch2 = short_commit_name(repo, pickme);
279
+ merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2);
280
+
281
+ merge_incore_nonrecursive(merge_opt,
282
+ base_tree,
283
+ replayed_base_tree,
284
+ pickme_tree,
285
+ result);
286
+
287
+ free((char *)merge_opt->ancestor);
288
+ } else if (mode == REPLAY_MODE_REVERT) {
289
+ /* Revert: swap base and pickme to reverse the diff */
290
+ const char *pickme_name = short_commit_name(repo, pickme);
291
+ merge_opt->branch1 = short_commit_name(repo, replayed_base);
292
+ merge_opt->branch2 = xstrfmt("parent of %s", pickme_name);
293
+ merge_opt->ancestor = pickme_name;
294
+
295
+ merge_incore_nonrecursive(merge_opt,
296
+ pickme_tree,
297
+ replayed_base_tree,
298
+ base_tree,
299
+ result);
300
+
301
+ free((char *)merge_opt->branch2);
302
+ } else {
303
+ BUG("unexpected replay mode %d", mode);
304
+ }
305
merge_opt->ancestor = NULL;
306
+ merge_opt->branch2 = NULL;
307
if (!result->clean)
308
return NULL;
309
/* Drop commits that become empty */
310
if (oideq(&replayed_base_tree->object.oid, &result->tree->object.oid) &&
311
!oideq(&pickme_tree->object.oid, &base_tree->object.oid))
312
return replayed_base;
247
- return create_commit(repo, result->tree, pickme, replayed_base);
313
+ return create_commit(repo, result->tree, pickme, replayed_base, mode);
314
}
315
316
void replay_result_release(struct replay_result *result)
347
};
348
bool detached_head;
349
char *advance;
350
+ char *revert;
351
+ enum replay_mode mode = REPLAY_MODE_PICK;
352
int ret;
353
354
advance = xstrdup_or_null(opts->advance);
355
+ revert = xstrdup_or_null(opts->revert);
356
+ if (revert)
357
+ mode = REPLAY_MODE_REVERT;
358
set_up_replay_mode(revs->repo, &revs->cmdline, opts->onto,
288
- &detached_head, &advance, &onto, &update_refs);
359
+ &detached_head, &advance, &revert, &onto, &update_refs);
360
361
/* FIXME: Should allow replaying commits with the first as a root commit */
362
380
die(_("replaying merge commits is not supported yet!"));
381
382
last_commit = pick_regular_commit(revs->repo, commit, replayed_commits,
312
- onto, &merge_opt, &result);
383
+ mode == REPLAY_MODE_REVERT ? last_commit : onto,
384
+ &merge_opt, &result, mode);
385
if (!last_commit)
386
break;
387
393
kh_value(replayed_commits, pos) = last_commit;
394
395
/* Update any necessary branches */
324
- if (advance)
396
+ if (advance || revert)
397
continue;
398
399
for (decoration = get_name_decoration(&commit->object);
427
goto out;
428
}
429
358
- /* In --advance mode, advance the target ref */
359
- if (advance)
360
- replay_result_queue_update(out, advance,
430
+ /* In --advance or --revert mode, update the target ref */
431
+ if (advance || revert) {
432
+ const char *ref = advance ? advance : revert;
433
+ replay_result_queue_update(out, ref,
434
&onto->object.oid,
435
&last_commit->object.oid);
436
+ }
437
438
ret = 0;
439
445
kh_destroy_oid_map(replayed_commits);
446
merge_finalize(&merge_opt, &result);
447
free(advance);
448
+ free(revert);
449
return ret;
450
}