Subject: [PATCH] branch, config: teach branch.autosetuprebase about 'merges' mode

From: "Alex Reed via GitGitGadget" <[email-masked]>
Date: Wed, 20 Jan 2021 00:20:09 +0000

From: Alex Reed <[email-masked]>

branch.autoSetupRebase now honors the 'merges' flag, allowing tracking branches
to be auto-populated with 'branch.<name>.rebase = merges'.  This allows complex
workflows to more easily retain non-trivial merges while rebasing branches on
pull operations.  Seeding new branches with 'branch.<name>.rebase = true' is not
always sufficient (read: not project default) and requiring developers to
manually reconfigure every new branch is cumbersome and error-prone.

Signed-off-by: Alex Reed <[email-masked]>
---
    branch, config: teach branch.autosetuprebase about 'merges' mode
    
    branch.autoSetupRebase now honors the 'merges' flag, allowing tracking
    branches to be auto-populated with 'branch..rebase = merges'. This
    allows complex workflows to more easily retain non-trivial merges while
    rebasing branches on pull operations. Seeding new branches with
    'branch..rebase = true' is not always sufficient (read: not project
    default) and requiring developers to manually reconfigure every new
    branch is cumbersome and error-prone.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-948%2Facr4%2Fautosetuprebase-merges-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-948/acr4/autosetuprebase-merges-v1
Pull-Request: https://github.com/git/git/pull/948

 Documentation/config/branch.txt |  2 +
 branch.c                        | 70 ++++++++++++++++++++-------------
 cache.h                         |  3 +-
 config.c                        |  2 +
 t/t3200-branch.sh               | 45 +++++++++++++++++++++
 t/t5601-clone.sh                | 14 ++++++-
 6 files changed, 107 insertions(+), 29 deletions(-)

diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt
index cc5f3249fc5..98410bf003f 100644
--- a/Documentation/config/branch.txt
+++ b/Documentation/config/branch.txt
@@ -21,6 +21,8 @@ branch.autoSetupRebase::
 	remote-tracking branches.
 	When `always`, rebase will be set to true for all tracking
 	branches.
+	When `merges`, rebase will be set to `merges` for all tracking
+	branches.
 	See "branch.autoSetupMerge" for details on how to set up a
 	branch to track another branch.
 	This option defaults to never.
diff --git a/branch.c b/branch.c
index 9c9dae1eae3..b197fe0bbb3 100644
--- a/branch.c
+++ b/branch.c
@@ -34,17 +34,25 @@ static int find_tracked_branch(struct remote *remote, void *priv)
 	return 0;
 }
 
-static int should_setup_rebase(const char *origin)
+typedef enum {
+	REBASE_FALSE,
+	REBASE_TRUE,
+	REBASE_MERGES
+} rebase_type;
+
+static rebase_type should_setup_rebase(const char *origin)
 {
 	switch (autorebase) {
 	case AUTOREBASE_NEVER:
-		return 0;
+		return REBASE_FALSE;
 	case AUTOREBASE_LOCAL:
-		return origin == NULL;
+		return origin == NULL ? REBASE_TRUE : REBASE_FALSE;
 	case AUTOREBASE_REMOTE:
-		return origin != NULL;
+		return origin != NULL ? REBASE_TRUE : REBASE_FALSE;
 	case AUTOREBASE_ALWAYS:
-		return 1;
+		return REBASE_TRUE;
+	case AUTOREBASE_MERGES:
+		return REBASE_MERGES;
 	}
 	return 0;
 }
@@ -59,7 +67,8 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 {
 	const char *shortname = NULL;
 	struct strbuf key = STRBUF_INIT;
-	int rebasing = should_setup_rebase(origin);
+	rebase_type rebasing = should_setup_rebase(origin);
+	struct strbuf method = STRBUF_INIT;
 
 	if (skip_prefix(remote, "refs/heads/", &shortname)
 	    && !strcmp(local, shortname)
@@ -78,44 +87,51 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 	if (git_config_set_gently(key.buf, remote) < 0)
 		goto out_err;
 
-	if (rebasing) {
-		strbuf_reset(&key);
-		strbuf_addf(&key, "branch.%s.rebase", local);
-		if (git_config_set_gently(key.buf, "true") < 0)
-			goto out_err;
+	strbuf_reset(&key);
+	strbuf_addf(&key, "branch.%s.rebase", local);
+	switch(rebasing) {
+		case REBASE_TRUE:
+			strbuf_addstr(&method, " by rebasing");
+			if(git_config_set_gently(key.buf, "true") < 0)
+				goto out_err;
+			break;
+		case REBASE_MERGES:
+			strbuf_addstr(&method, " by rebasing while preserving merges");
+			if (git_config_set_gently(key.buf, "merges") < 0)
+				goto out_err;
+			break;
+    default:;
 	}
 	strbuf_release(&key);
 
 	if (flag & BRANCH_CONFIG_VERBOSE) {
 		if (shortname) {
 			if (origin)
-				printf_ln(rebasing ?
-					  _("Branch '%s' set up to track remote branch '%s' from '%s' by rebasing.") :
-					  _("Branch '%s' set up to track remote branch '%s' from '%s'."),
-					  local, shortname, origin);
+				printf_ln(
+					_("Branch '%s' set up to track remote branch '%s' from '%s'%s."),
+					local, shortname, origin, method.buf);
 			else
-				printf_ln(rebasing ?
-					  _("Branch '%s' set up to track local branch '%s' by rebasing.") :
-					  _("Branch '%s' set up to track local branch '%s'."),
-					  local, shortname);
+				printf_ln(
+					_("Branch '%s' set up to track local branch '%s'%s."),
+					local, shortname, method.buf);
 		} else {
 			if (origin)
-				printf_ln(rebasing ?
-					  _("Branch '%s' set up to track remote ref '%s' by rebasing.") :
-					  _("Branch '%s' set up to track remote ref '%s'."),
-					  local, remote);
+				printf_ln(
+					_("Branch '%s' set up to track remote ref '%s'%s."),
+					local, remote, method.buf);
 			else
-				printf_ln(rebasing ?
-					  _("Branch '%s' set up to track local ref '%s' by rebasing.") :
-					  _("Branch '%s' set up to track local ref '%s'."),
-					  local, remote);
+				printf_ln(
+					_("Branch '%s' set up to track local ref '%s'%s."),
+					local, remote, method.buf);
 		}
 	}
 
+	strbuf_release(&method);
 	return 0;
 
 out_err:
 	strbuf_release(&key);
+	strbuf_release(&method);
 	error(_("Unable to write upstream branch configuration"));
 
 	advise(_(tracking_advice),
diff --git a/cache.h b/cache.h
index eefa93b08f8..5a378bcd534 100644
--- a/cache.h
+++ b/cache.h
@@ -995,7 +995,8 @@ enum rebase_setup_type {
 	AUTOREBASE_NEVER = 0,
 	AUTOREBASE_LOCAL,
 	AUTOREBASE_REMOTE,
-	AUTOREBASE_ALWAYS
+	AUTOREBASE_ALWAYS,
+	AUTOREBASE_MERGES
 };
 
 enum push_default_type {
diff --git a/config.c b/config.c
index 4c0cf3a1c15..28d813f2595 100644
--- a/config.c
+++ b/config.c
@@ -1443,6 +1443,8 @@ static int git_default_branch_config(const char *var, const char *value)
 			autorebase = AUTOREBASE_REMOTE;
 		else if (!strcmp(value, "always"))
 			autorebase = AUTOREBASE_ALWAYS;
+		else if (!strcmp(value, "merges"))
+			autorebase = AUTOREBASE_MERGES;
 		else
 			return error(_("malformed value for %s"), var);
 		return 0;
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 0af3b85d172..056192f72c5 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -1222,6 +1222,51 @@ test_expect_success 'autosetuprebase always on an untracked remote branch' '
 	test "z$(git config branch.myr20.rebase)" = z
 '
 
+test_expect_success 'autosetuprebase merges on a tracked local branch' '
+	git config branch.autosetuprebase merges &&
+	git config remote.local.url . &&
+	git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+	(git show-ref -q refs/remotes/local/o || git fetch local) &&
+	git branch mybase21 &&
+	git branch --track myr21 mybase3 &&
+	test "$(git config branch.myr21.remote)" = . &&
+	test "$(git config branch.myr21.merge)" = refs/heads/mybase3 &&
+	test "$(git config branch.myr21.rebase)" = merges
+'
+
+test_expect_success 'autosetuprebase merges on a tracked remote branch' '
+	git config branch.autosetuprebase merges &&
+	git config remote.local.url . &&
+	git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+	(git show-ref -q refs/remotes/local/main || git fetch local) &&
+	git branch --track myr22 local/main &&
+	test "$(git config branch.myr22.remote)" = local &&
+	test "$(git config branch.myr22.merge)" = refs/heads/main &&
+	test "$(git config branch.myr22.rebase)" = merges
+'
+
+test_expect_success 'autosetuprebase merges on an untracked local branch' '
+	git config branch.autosetuprebase merges &&
+	git config remote.local.url . &&
+	git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+	(git show-ref -q refs/remotes/local/main || git fetch local) &&
+	git branch --no-track myr23 mybase2 &&
+	test "z$(git config branch.myr23.remote)" = z &&
+	test "z$(git config branch.myr23.merge)" = z &&
+	test "z$(git config branch.myr23.rebase)" = z
+'
+
+test_expect_success 'autosetuprebase merges on an untracked remote branch' '
+	git config branch.autosetuprebase merges &&
+	git config remote.local.url . &&
+	git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+	(git show-ref -q refs/remotes/local/main || git fetch local) &&
+	git branch --no-track myr24 local/main &&
+	test "z$(git config branch.myr24.remote)" = z &&
+	test "z$(git config branch.myr24.merge)" = z &&
+	test "z$(git config branch.myr24.rebase)" = z
+'
+
 test_expect_success 'autosetuprebase always on detached HEAD' '
 	git config branch.autosetupmerge always &&
 	test_when_finished git checkout main &&
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 7df3c5373ae..10983191439 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -210,7 +210,7 @@ test_expect_success 'clone a void' '
 	test_cmp target-6/.git/config target-7/.git/config
 '
 
-test_expect_success 'clone respects global branch.autosetuprebase' '
+test_expect_success 'clone respects global branch.autosetuprebase remote' '
 	(
 		test_config="$HOME/.gitconfig" &&
 		git config -f "$test_config" branch.autosetuprebase remote &&
@@ -222,6 +222,18 @@ test_expect_success 'clone respects global branch.autosetuprebase' '
 	)
 '
 
+test_expect_success 'clone respects global branch.autosetuprebase merges' '
+	(
+		test_config="$HOME/.gitconfig" &&
+		git config -f "$test_config" branch.autosetuprebase merges &&
+		rm -fr dst &&
+		git clone src dst &&
+		cd dst &&
+		actual="z$(git config branch.master.rebase)" &&
+		test zmerges = $actual
+	)
+'
+
 test_expect_success 'respect url-encoding of file://' '
 	git init x+y &&
 	git clone "file://$PWD/x+y" xy-url-1 &&

base-commit: 66e871b6647ffea61a77a0f82c7ef3415f1ee79c
-- 
gitgitgadget