Files
git/builtin/replay.c
Toon Claes 23d83f8ddb replay: allow to specify a ref with option --ref
When option '--onto' is passed to git-replay(1), the command will update
refs from the <revision-range> passed to the command. When using option
'--advance' or '--revert', the argument of that option is a ref that
will be updated.

To enable users to specify which ref to update, add option '--ref'. When
using option '--ref', the refs described above are left untouched and
instead the argument of this option is updated instead.

Because this introduces code paths in replay.c that jump to `out` before
init_basic_merge_options() is called on `merge_opt`, zero-initialize the
struct.

Signed-off-by: Toon Claes <toon@iotcl.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-04-01 21:34:25 -07:00

260 lines
7.5 KiB
C

/*
* "git replay" builtin command
*/
#include "git-compat-util.h"
#include "builtin.h"
#include "config.h"
#include "hex.h"
#include "object-name.h"
#include "parse-options.h"
#include "refs.h"
#include "replay.h"
#include "revision.h"
enum ref_action_mode {
REF_ACTION_UPDATE,
REF_ACTION_PRINT,
};
static enum ref_action_mode parse_ref_action_mode(const char *ref_action, const char *source)
{
if (!ref_action || !strcmp(ref_action, "update"))
return REF_ACTION_UPDATE;
if (!strcmp(ref_action, "print"))
return REF_ACTION_PRINT;
die(_("invalid %s value: '%s'"), source, ref_action);
}
static enum ref_action_mode get_ref_action_mode(struct repository *repo, const char *ref_action)
{
const char *config_value = NULL;
/* Command line option takes precedence */
if (ref_action)
return parse_ref_action_mode(ref_action, "--ref-action");
/* Check config value */
if (!repo_config_get_string_tmp(repo, "replay.refAction", &config_value))
return parse_ref_action_mode(config_value, "replay.refAction");
/* Default to update mode */
return REF_ACTION_UPDATE;
}
static int handle_ref_update(enum ref_action_mode mode,
struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const struct object_id *old_oid,
const char *reflog_msg,
struct strbuf *err)
{
switch (mode) {
case REF_ACTION_PRINT:
printf("update %s %s %s\n",
refname,
oid_to_hex(new_oid),
oid_to_hex(old_oid));
return 0;
case REF_ACTION_UPDATE:
return ref_transaction_update(transaction, refname, new_oid, old_oid,
NULL, NULL, 0, reflog_msg, err);
default:
BUG("unknown ref_action_mode %d", mode);
}
}
int cmd_replay(int argc,
const char **argv,
const char *prefix,
struct repository *repo)
{
struct replay_revisions_options opts = { 0 };
struct replay_result result = { 0 };
const char *ref_action = NULL;
enum ref_action_mode ref_mode;
struct rev_info revs;
struct ref_transaction *transaction = NULL;
struct strbuf transaction_err = STRBUF_INIT;
struct strbuf reflog_msg = STRBUF_INIT;
int desired_reverse;
int ret = 0;
const char *const replay_usage[] = {
N_("(EXPERIMENTAL!) git replay "
"([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)\n"
"[--ref=<ref>] [--ref-action=<mode>] <revision-range>"),
NULL
};
struct option replay_options[] = {
OPT_BOOL(0, "contained", &opts.contained,
N_("update all branches that point at commits in <revision-range>")),
OPT_STRING_F(0, "onto", &opts.onto,
N_("revision"),
N_("replay onto given commit"),
PARSE_OPT_NONEG),
OPT_STRING_F(0, "advance", &opts.advance,
N_("branch"),
N_("make replay advance given branch"),
PARSE_OPT_NONEG),
OPT_STRING_F(0, "revert", &opts.revert,
N_("branch"),
N_("revert commits onto given branch"),
PARSE_OPT_NONEG),
OPT_STRING_F(0, "ref", &opts.ref,
N_("branch"),
N_("reference to update with result"),
PARSE_OPT_NONEG),
OPT_STRING_F(0, "ref-action", &ref_action,
N_("mode"),
N_("control ref update behavior (update|print)"),
PARSE_OPT_NONEG),
OPT_END()
};
argc = parse_options(argc, argv, prefix, replay_options, replay_usage,
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
/* Exactly one mode must be specified */
if (!opts.onto && !opts.advance && !opts.revert) {
error(_("exactly one of --onto, --advance, or --revert is required"));
usage_with_options(replay_usage, replay_options);
}
die_for_incompatible_opt3(!!opts.onto, "--onto",
!!opts.advance, "--advance",
!!opts.revert, "--revert");
die_for_incompatible_opt2(!!opts.advance, "--advance",
opts.contained, "--contained");
die_for_incompatible_opt2(!!opts.revert, "--revert",
opts.contained, "--contained");
die_for_incompatible_opt2(!!opts.ref, "--ref",
!!opts.contained, "--contained");
/* Parse ref action mode from command line or config */
ref_mode = get_ref_action_mode(repo, ref_action);
/*
* Cherry-pick/rebase need oldest-first ordering so that each
* replayed commit can build on its already-replayed parent.
* Revert needs newest-first ordering (like git revert) to
* reduce conflicts by peeling off changes from the top.
*/
desired_reverse = !opts.revert;
repo_init_revisions(repo, &revs, prefix);
/*
* Set desired values for rev walking options here. If they
* are changed by some user specified option in setup_revisions()
* below, we will detect that below and then warn.
*
* TODO: In the future we might want to either die(), or allow
* some options changing these values if we think they could
* be useful.
*/
revs.reverse = desired_reverse;
revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
revs.topo_order = 1;
revs.simplify_history = 0;
argc = setup_revisions(argc, argv, &revs, NULL);
if (argc > 1) {
ret = error(_("unrecognized argument: %s"), argv[1]);
goto cleanup;
}
/*
* Detect and warn if we override some user specified rev
* walking options.
*/
if (revs.reverse != desired_reverse) {
warning(_("some rev walking options will be overridden as "
"'%s' bit in 'struct rev_info' will be forced"),
"reverse");
revs.reverse = desired_reverse;
}
if (revs.sort_order != REV_SORT_IN_GRAPH_ORDER) {
warning(_("some rev walking options will be overridden as "
"'%s' bit in 'struct rev_info' will be forced"),
"sort_order");
revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
}
if (revs.topo_order != 1) {
warning(_("some rev walking options will be overridden as "
"'%s' bit in 'struct rev_info' will be forced"),
"topo_order");
revs.topo_order = 1;
}
if (revs.simplify_history != 0) {
warning(_("some rev walking options will be overridden as "
"'%s' bit in 'struct rev_info' will be forced"),
"simplify_history");
revs.simplify_history = 0;
}
ret = replay_revisions(&revs, &opts, &result);
if (ret)
goto cleanup;
/* Build reflog message */
if (opts.revert) {
strbuf_addf(&reflog_msg, "replay --revert %s", opts.revert);
} else if (opts.advance) {
strbuf_addf(&reflog_msg, "replay --advance %s", opts.advance);
} else {
struct object_id oid;
if (repo_get_oid_committish(repo, opts.onto, &oid))
BUG("--onto commit should have been resolved beforehand already");
strbuf_addf(&reflog_msg, "replay --onto %s", oid_to_hex(&oid));
}
/* Initialize ref transaction if using update mode */
if (ref_mode == REF_ACTION_UPDATE) {
transaction = ref_store_transaction_begin(get_main_ref_store(repo),
0, &transaction_err);
if (!transaction) {
ret = error(_("failed to begin ref transaction: %s"),
transaction_err.buf);
goto cleanup;
}
}
for (size_t i = 0; i < result.updates_nr; i++) {
ret = handle_ref_update(ref_mode, transaction, result.updates[i].refname,
&result.updates[i].new_oid, &result.updates[i].old_oid,
reflog_msg.buf, &transaction_err);
if (ret) {
ret = error(_("failed to update ref '%s': %s"),
result.updates[i].refname, transaction_err.buf);
goto cleanup;
}
}
/* Commit the ref transaction if we have one */
if (transaction) {
if (ref_transaction_commit(transaction, &transaction_err)) {
ret = error(_("failed to commit ref transaction: %s"),
transaction_err.buf);
goto cleanup;
}
}
ret = 0;
cleanup:
if (transaction)
ref_transaction_free(transaction);
replay_result_release(&result);
strbuf_release(&transaction_err);
strbuf_release(&reflog_msg);
release_revisions(&revs);
/* Return */
if (ret < 0)
exit(128);
return ret;
}