@@ -9,7 +9,7 @@ git-replay - Replay commits on a different base, without touching working tree
SYNOPSIS
--------
[verse]
-'git replay' --onto <newbase> <revision-range>...
+'git replay' [--onto <newbase>] <revision-range>...
DESCRIPTION
-----------
@@ -20,6 +20,12 @@ references. However, the output of this command is meant to be used
as input to `git update-ref --stdin`, which would update the relevant
branches.
+When the `--onto <newbase>` option is not passed, the commits will be
+replayed onto a base guessed from the `<revision-range>`. For example
+if the `<revision-range>` is `origin/main..mybranch` then `mybranch`
+was probably based on an old version of `origin/main`, so we will
+replay it on the newest version of that branch.
+
OPTIONS
-------
@@ -75,6 +75,54 @@ static struct commit *create_commit(struct tree *tree,
return (struct commit *)obj;
}
+static struct commit *guess_new_base(struct rev_cmdline_info *info)
+{
+ struct commit *new_base = NULL;
+ int i, bottom_commits = 0;
+
+ /*
+ * When the user specifies e.g.
+ * git replay origin/main..mybranch
+ * git replay ^origin/next mybranch1 mybranch2
+ * we want to be able to determine where to replay the commits. In
+ * these examples, the branches are probably based on an old version
+ * of either origin/main or origin/next, so we want to replay on the
+ * newest version of that branch. In contrast we would want to error
+ * out if they ran
+ * git replay ^origin/master ^origin/next mybranch
+ * git replay mybranch~2..mybranch
+ * the first of those because there's no unique base to choose, and
+ * the second because they'd likely just be replaying commits on top
+ * of the same commit and not making any difference.
+ */
+ for (i = 0; i < info->nr; i++) {
+ struct rev_cmdline_entry *e = info->rev + i;
+ struct object_id oid;
+ char *fullname = NULL;
+
+ if (!(e->flags & BOTTOM))
+ continue;
+
+ /*
+ * We need a unique base commit to know where to replay; error
+ * out if not unique.
+ *
+ * Also, we usually don't want to replay commits on the same
+ * base they started on, so only accept this as the base if
+ * it uniquely names some ref.
+ */
+ if (bottom_commits++ ||
+ dwim_ref(e->name, strlen(e->name), &oid, &fullname, 0) != 1)
+ die(_("cannot determine where to replay commits; please specify --onto"));
+
+ free(fullname);
+ new_base = lookup_commit_reference_gently(the_repository,
+ &e->item->oid, 1);
+ }
+
+ return new_base;
+}
+
static struct commit *pick_regular_commit(struct commit *pickme,
struct commit *last_commit,
struct merge_options *merge_opt,
@@ -117,7 +165,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
int ret = 0;
const char * const replay_usage[] = {
- N_("git replay --onto <newbase> <revision-range>..."),
+ N_("git replay [--onto <newbase>] <revision-range>..."),
NULL
};
struct option replay_options[] = {
@@ -130,12 +178,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, replay_options, replay_usage,
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
- if (!onto_name) {
- error(_("option --onto is mandatory"));
- usage_with_options(replay_usage, replay_options);
- }
-
- onto = peel_committish(onto_name);
repo_init_revisions(the_repository, &revs, prefix);
@@ -151,6 +193,11 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
revs.topo_order = 1;
revs.simplify_history = 0;
+ if (onto_name)
+ onto = peel_committish(onto_name);
+ else
+ onto = guess_new_base(&revs.cmdline);
+
if (prepare_revision_walk(&revs) < 0) {
ret = error(_("error preparing revisions"));
goto cleanup;