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>] <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_END()
115 };
116
117 argc = parse_options(argc, argv, prefix, replay_options, replay_usage,
118 PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
119
120 /* Exactly one mode must be specified */
121 if (!opts.onto && !opts.advance && !opts.revert) {
122 error(_("exactly one of --onto, --advance, or --revert is required"));
123 usage_with_options(replay_usage, replay_options);
124 }
125
126 die_for_incompatible_opt3(!!opts.onto, "--onto",
127 !!opts.advance, "--advance",
128 !!opts.revert, "--revert");
129 die_for_incompatible_opt2(!!opts.advance, "--advance",
130 opts.contained, "--contained");
131 die_for_incompatible_opt2(!!opts.revert, "--revert",
132 opts.contained, "--contained");
133 die_for_incompatible_opt2(!!opts.ref, "--ref",
134 !!opts.contained, "--contained");
135
136 /* Parse ref action mode from command line or config */
137 ref_mode = get_ref_action_mode(repo, ref_action);
138
139 /*
140 * Cherry-pick/rebase need oldest-first ordering so that each
141 * replayed commit can build on its already-replayed parent.
142 * Revert needs newest-first ordering (like git revert) to
143 * reduce conflicts by peeling off changes from the top.
144 */
145 desired_reverse = !opts.revert;
146
147 repo_init_revisions(repo, &revs, prefix);
148
149 /*
150 * Set desired values for rev walking options here. If they
151 * are changed by some user specified option in setup_revisions()
152 * below, we will detect that below and then warn.
153 *
154 * TODO: In the future we might want to either die(), or allow
155 * some options changing these values if we think they could
156 * be useful.
157 */
158 revs.reverse = desired_reverse;
159 revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
160 revs.topo_order = 1;
161 revs.simplify_history = 0;
162
163 argc = setup_revisions(argc, argv, &revs, NULL);
164 if (argc > 1) {
165 ret = error(_("unrecognized argument: %s"), argv[1]);
166 goto cleanup;
167 }
168
169 /*
170 * Detect and warn if we override some user specified rev
171 * walking options.
172 */
173 if (revs.reverse != desired_reverse) {
174 warning(_("some rev walking options will be overridden as "
175 "'%s' bit in 'struct rev_info' will be forced"),
176 "reverse");
177 revs.reverse = desired_reverse;
178 }
179 if (revs.sort_order != REV_SORT_IN_GRAPH_ORDER) {
180 warning(_("some rev walking options will be overridden as "
181 "'%s' bit in 'struct rev_info' will be forced"),
182 "sort_order");
183 revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
184 }
185 if (revs.topo_order != 1) {
186 warning(_("some rev walking options will be overridden as "
187 "'%s' bit in 'struct rev_info' will be forced"),
188 "topo_order");
189 revs.topo_order = 1;
190 }
191 if (revs.simplify_history != 0) {
192 warning(_("some rev walking options will be overridden as "
193 "'%s' bit in 'struct rev_info' will be forced"),
194 "simplify_history");
195 revs.simplify_history = 0;
196 }
197
198 ret = replay_revisions(&revs, &opts, &result);
199 if (ret)
200 goto cleanup;
201
202 /* Build reflog message */
203 if (opts.revert) {
204 strbuf_addf(&reflog_msg, "replay --revert %s", opts.revert);
205 } else if (opts.advance) {
206 strbuf_addf(&reflog_msg, "replay --advance %s", opts.advance);
207 } else {
208 struct object_id oid;
209 if (repo_get_oid_committish(repo, opts.onto, &oid))
210 BUG("--onto commit should have been resolved beforehand already");
211 strbuf_addf(&reflog_msg, "replay --onto %s", oid_to_hex(&oid));
212 }
213
214 /* Initialize ref transaction if using update mode */
215 if (ref_mode == REF_ACTION_UPDATE) {
216 transaction = ref_store_transaction_begin(get_main_ref_store(repo),
217 0, &transaction_err);
218 if (!transaction) {
219 ret = error(_("failed to begin ref transaction: %s"),
220 transaction_err.buf);
221 goto cleanup;
222 }
223 }
224
225 for (size_t i = 0; i < result.updates_nr; i++) {
226 ret = handle_ref_update(ref_mode, transaction, result.updates[i].refname,
227 &result.updates[i].new_oid, &result.updates[i].old_oid,
228 reflog_msg.buf, &transaction_err);
229 if (ret) {
230 ret = error(_("failed to update ref '%s': %s"),
231 result.updates[i].refname, transaction_err.buf);
232 goto cleanup;
233 }
234 }
235
236 /* Commit the ref transaction if we have one */
237 if (transaction) {
238 if (ref_transaction_commit(transaction, &transaction_err)) {
239 ret = error(_("failed to commit ref transaction: %s"),
240 transaction_err.buf);
241 goto cleanup;
242 }
243 }
244
245 ret = 0;
246
247 cleanup:
248 if (transaction)
249 ref_transaction_free(transaction);
250 replay_result_release(&result);
251 strbuf_release(&transaction_err);
252 strbuf_release(&reflog_msg);
253 release_revisions(&revs);
254
255 /* Return */
256 if (ret < 0)
257 exit(128);
258 return ret;
259 }