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