Raw
1 /*
2 * "git replay" builtin command
3 */
4
5 #include "git-compat-util.h"
6
7 #include "builtin.h"
8 #include "config.h"
9 #include "hex.h"
10 #include "object-name.h"
11 #include "parse-options.h"
12 #include "refs.h"
13 #include "replay.h"
14 #include "revision.h"
15
16 enum ref_action_mode {
17 REF_ACTION_UPDATE,
18 REF_ACTION_PRINT,
19 };
20
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"))
24 return REF_ACTION_UPDATE;
25 if (!strcmp(ref_action, "print"))
26 return REF_ACTION_PRINT;
27 die(_("invalid %s value: '%s'"), source, ref_action);
28 }
29
30 static enum ref_action_mode get_ref_action_mode(struct repository *repo, const char *ref_action)
31 {
32 const char *config_value = NULL;
33
34 /* Command line option takes precedence */
35 if (ref_action)
36 return parse_ref_action_mode(ref_action, "--ref-action");
37
38 /* Check config value */
39 if (!repo_config_get_string_tmp(repo, "replay.refAction", &config_value))
40 return parse_ref_action_mode(config_value, "replay.refAction");
41
42 /* Default to update mode */
43 return REF_ACTION_UPDATE;
44 }
45
46 static int handle_ref_update(enum ref_action_mode mode,
47 struct ref_transaction *transaction,
48 const char *refname,
49 const struct object_id *new_oid,
50 const struct object_id *old_oid,
51 const char *reflog_msg,
52 struct strbuf *err)
53 {
54 switch (mode) {
55 case REF_ACTION_PRINT:
56 printf("update %s %s %s\n",
57 refname,
58 oid_to_hex(new_oid),
59 oid_to_hex(old_oid));
60 return 0;
61 case REF_ACTION_UPDATE:
62 return ref_transaction_update(transaction, refname, new_oid, old_oid,
63 NULL, NULL, 0, reflog_msg, err);
64 default:
65 BUG("unknown ref_action_mode %d", mode);
66 }
67 }
68
69 int cmd_replay(int argc,
70 const char **argv,
71 const char *prefix,
72 struct repository *repo)
73 {
74 struct replay_revisions_options opts = { 0 };
75 struct replay_result result = { 0 };
76 const char *ref_action = NULL;
77 enum ref_action_mode ref_mode;
78 struct rev_info revs;
79 struct ref_transaction *transaction = NULL;
80 struct strbuf transaction_err = STRBUF_INIT;
81 struct strbuf reflog_msg = STRBUF_INIT;
82 int desired_reverse;
83 int ret = 0;
84
85 const char *const replay_usage[] = {
86 N_("(EXPERIMENTAL!) git replay "
87 "([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)\n"
88 "[--ref=<ref>] [--ref-action=<mode>] [--linearize] <revision-range>"),
89 NULL
90 };
91 struct option replay_options[] = {
92 OPT_BOOL(0, "contained", &opts.contained,
93 N_("update all branches that point at commits in <revision-range>")),
94 OPT_STRING_F(0, "onto", &opts.onto,
95 N_("revision"),
96 N_("replay onto given commit"),
97 PARSE_OPT_NONEG),
98 OPT_STRING_F(0, "advance", &opts.advance,
99 N_("branch"),
100 N_("make replay advance given branch"),
101 PARSE_OPT_NONEG),
102 OPT_STRING_F(0, "revert", &opts.revert,
103 N_("branch"),
104 N_("revert commits onto given branch"),
105 PARSE_OPT_NONEG),
106 OPT_STRING_F(0, "ref", &opts.ref,
107 N_("branch"),
108 N_("reference to update with result"),
109 PARSE_OPT_NONEG),
110 OPT_STRING_F(0, "ref-action", &ref_action,
111 N_("mode"),
112 N_("control ref update behavior (update|print)"),
113 PARSE_OPT_NONEG),
114 OPT_BOOL(0, "linearize", &opts.linearize,
115 N_("drop merge commits, replaying only non-merge commits")),
116 OPT_END()
117 };
118
119 argc = parse_options(argc, argv, prefix, replay_options, replay_usage,
120 PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
121
122 /* Exactly one mode must be specified */
123 if (!opts.onto && !opts.advance && !opts.revert) {
124 error(_("exactly one of --onto, --advance, or --revert is required"));
125 usage_with_options(replay_usage, replay_options);
126 }
127
128 die_for_incompatible_opt3(!!opts.onto, "--onto",
129 !!opts.advance, "--advance",
130 !!opts.revert, "--revert");
131 die_for_incompatible_opt2(!!opts.advance, "--advance",
132 opts.contained, "--contained");
133 die_for_incompatible_opt2(!!opts.revert, "--revert",
134 opts.contained, "--contained");
135 die_for_incompatible_opt2(!!opts.ref, "--ref",
136 !!opts.contained, "--contained");
137 die_for_incompatible_opt2(opts.linearize, "--linearize",
138 !!opts.contained, "--contained");
139
140 /* Parse ref action mode from command line or config */
141 ref_mode = get_ref_action_mode(repo, ref_action);
142
143 /*
144 * Cherry-pick/rebase need oldest-first ordering so that each
145 * replayed commit can build on its already-replayed parent.
146 * Revert needs newest-first ordering (like git revert) to
147 * reduce conflicts by peeling off changes from the top.
148 */
149 desired_reverse = !opts.revert;
150
151 repo_init_revisions(repo, &revs, prefix);
152
153 /*
154 * Set desired values for rev walking options here. If they
155 * are changed by some user specified option in setup_revisions()
156 * below, we will detect that below and then warn.
157 *
158 * TODO: In the future we might want to either die(), or allow
159 * some options changing these values if we think they could
160 * be useful.
161 */
162 revs.reverse = desired_reverse;
163 revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
164 revs.topo_order = 1;
165 revs.simplify_history = 0;
166
167 argc = setup_revisions(argc, argv, &revs, NULL);
168 if (argc > 1) {
169 ret = error(_("unrecognized argument: %s"), argv[1]);
170 goto cleanup;
171 }
172
173 /*
174 * Detect and warn if we override some user specified rev
175 * walking options.
176 */
177 if (revs.reverse != desired_reverse) {
178 warning(_("some rev walking options will be overridden as "
179 "'%s' bit in 'struct rev_info' will be forced"),
180 "reverse");
181 revs.reverse = desired_reverse;
182 }
183 if (revs.sort_order != REV_SORT_IN_GRAPH_ORDER) {
184 warning(_("some rev walking options will be overridden as "
185 "'%s' bit in 'struct rev_info' will be forced"),
186 "sort_order");
187 revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
188 }
189 if (revs.topo_order != 1) {
190 warning(_("some rev walking options will be overridden as "
191 "'%s' bit in 'struct rev_info' will be forced"),
192 "topo_order");
193 revs.topo_order = 1;
194 }
195 if (revs.simplify_history != 0) {
196 warning(_("some rev walking options will be overridden as "
197 "'%s' bit in 'struct rev_info' will be forced"),
198 "simplify_history");
199 revs.simplify_history = 0;
200 }
201
202 ret = replay_revisions(&revs, &opts, &result);
203 if (ret)
204 goto cleanup;
205
206 /* Build reflog message */
207 if (opts.revert) {
208 strbuf_addf(&reflog_msg, "replay --revert %s", opts.revert);
209 } else if (opts.advance) {
210 strbuf_addf(&reflog_msg, "replay --advance %s", opts.advance);
211 } else {
212 struct object_id oid;
213 if (repo_get_oid_committish(repo, opts.onto, &oid))
214 BUG("--onto commit should have been resolved beforehand already");
215 strbuf_addf(&reflog_msg, "replay --onto %s", oid_to_hex(&oid));
216 }
217
218 /* Initialize ref transaction if using update mode */
219 if (ref_mode == REF_ACTION_UPDATE) {
220 transaction = ref_store_transaction_begin(get_main_ref_store(repo),
221 0, &transaction_err);
222 if (!transaction) {
223 ret = error(_("failed to begin ref transaction: %s"),
224 transaction_err.buf);
225 goto cleanup;
226 }
227 }
228
229 for (size_t i = 0; i < result.updates_nr; i++) {
230 ret = handle_ref_update(ref_mode, transaction, result.updates[i].refname,
231 &result.updates[i].new_oid, &result.updates[i].old_oid,
232 reflog_msg.buf, &transaction_err);
233 if (ret) {
234 ret = error(_("failed to update ref '%s': %s"),
235 result.updates[i].refname, transaction_err.buf);
236 goto cleanup;
237 }
238 }
239
240 /* Commit the ref transaction if we have one */
241 if (transaction) {
242 if (ref_transaction_commit(transaction, &transaction_err)) {
243 ret = error(_("failed to commit ref transaction: %s"),
244 transaction_err.buf);
245 goto cleanup;
246 }
247 }
248
249 ret = 0;
250
251 cleanup:
252 if (transaction)
253 ref_transaction_free(transaction);
254 replay_result_release(&result);
255 strbuf_release(&transaction_err);
256 strbuf_release(&reflog_msg);
257 release_revisions(&revs);
258
259 /* Return */
260 if (ret < 0)
261 exit(128);
262 return ret;
263 }