diff mbox series

[12/14] replay: introduce guess_new_base()

Message ID 20230407072415.1360068-13-christian.couder@gmail.com (mailing list archive)
State New, archived
Headers show
Series Introduce new `git replay` command | expand

Commit Message

Christian Couder April 7, 2023, 7:24 a.m. UTC
From: Elijah Newren <newren@gmail.com>

In many cases the `--onto` option is not necessary as we can guess the
branch we would like to replay onto.

So let's introduce guess_new_base() for that purpose and make `--onto`
optional.

Co-authored-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
 Documentation/git-replay.txt |  8 ++++-
 builtin/replay.c             | 61 +++++++++++++++++++++++++++++++-----
 2 files changed, 61 insertions(+), 8 deletions(-)
diff mbox series

Patch

diff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt
index 7a83f70343..ce2cafc42e 100644
--- a/Documentation/git-replay.txt
+++ b/Documentation/git-replay.txt
@@ -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
 -------
 
diff --git a/builtin/replay.c b/builtin/replay.c
index 63513ea6f1..af948af73c 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -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;