{"thread":{"id":"55013","subject":"[PATCH] branch, config: teach branch.autosetuprebase about 'merges' mode","startedAt":"2021-01-20T00:21:34Z","lastAt":"2021-01-20T00:21:34Z","messageCount":1,"participants":["Alex Reed via GitGitGadget"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"414768","messageId":"pull.948.git.git.1611102010142.gitgitgadget@gmail.com","threadId":"55013","inReplyTo":null,"subject":"[PATCH] branch, config: teach branch.autosetuprebase about 'merges' mode","fromName":"Alex Reed via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-20T00:20:09Z","receivedAt":"2021-01-20T00:21:34Z","isPatch":true,"sender":{"key":"name:Alex Reed","avatar":null},"body":"From: Alex Reed <acreed4@gmail.com>\n\nbranch.autoSetupRebase now honors the 'merges' flag, allowing tracking branches\nto be auto-populated with 'branch.<name>.rebase = merges'.  This allows complex\nworkflows to more easily retain non-trivial merges while rebasing branches on\npull operations.  Seeding new branches with 'branch.<name>.rebase = true' is not\nalways sufficient (read: not project default) and requiring developers to\nmanually reconfigure every new branch is cumbersome and error-prone.\n\nSigned-off-by: Alex Reed <acreed4@gmail.com>\n---\n    branch, config: teach branch.autosetuprebase about 'merges' mode\n    \n    branch.autoSetupRebase now honors the 'merges' flag, allowing tracking\n    branches to be auto-populated with 'branch..rebase = merges'. This\n    allows complex workflows to more easily retain non-trivial merges while\n    rebasing branches on pull operations. Seeding new branches with\n    'branch..rebase = true' is not always sufficient (read: not project\n    default) and requiring developers to manually reconfigure every new\n    branch is cumbersome and error-prone.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-948%2Facr4%2Fautosetuprebase-merges-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-948/acr4/autosetuprebase-merges-v1\nPull-Request: https://github.com/git/git/pull/948\n\n Documentation/config/branch.txt |  2 +\n branch.c                        | 70 ++++++++++++++++++++-------------\n cache.h                         |  3 +-\n config.c                        |  2 +\n t/t3200-branch.sh               | 45 +++++++++++++++++++++\n t/t5601-clone.sh                | 14 ++++++-\n 6 files changed, 107 insertions(+), 29 deletions(-)\n\ndiff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt\nindex cc5f3249fc5..98410bf003f 100644\n--- a/Documentation/config/branch.txt\n+++ b/Documentation/config/branch.txt\n@@ -21,6 +21,8 @@ branch.autoSetupRebase::\n \tremote-tracking branches.\n \tWhen `always`, rebase will be set to true for all tracking\n \tbranches.\n+\tWhen `merges`, rebase will be set to `merges` for all tracking\n+\tbranches.\n \tSee \"branch.autoSetupMerge\" for details on how to set up a\n \tbranch to track another branch.\n \tThis option defaults to never.\ndiff --git a/branch.c b/branch.c\nindex 9c9dae1eae3..b197fe0bbb3 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -34,17 +34,25 @@ static int find_tracked_branch(struct remote *remote, void *priv)\n \treturn 0;\n }\n \n-static int should_setup_rebase(const char *origin)\n+typedef enum {\n+\tREBASE_FALSE,\n+\tREBASE_TRUE,\n+\tREBASE_MERGES\n+} rebase_type;\n+\n+static rebase_type should_setup_rebase(const char *origin)\n {\n \tswitch (autorebase) {\n \tcase AUTOREBASE_NEVER:\n-\t\treturn 0;\n+\t\treturn REBASE_FALSE;\n \tcase AUTOREBASE_LOCAL:\n-\t\treturn origin == NULL;\n+\t\treturn origin == NULL ? REBASE_TRUE : REBASE_FALSE;\n \tcase AUTOREBASE_REMOTE:\n-\t\treturn origin != NULL;\n+\t\treturn origin != NULL ? REBASE_TRUE : REBASE_FALSE;\n \tcase AUTOREBASE_ALWAYS:\n-\t\treturn 1;\n+\t\treturn REBASE_TRUE;\n+\tcase AUTOREBASE_MERGES:\n+\t\treturn REBASE_MERGES;\n \t}\n \treturn 0;\n }\n@@ -59,7 +67,8 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n {\n \tconst char *shortname = NULL;\n \tstruct strbuf key = STRBUF_INIT;\n-\tint rebasing = should_setup_rebase(origin);\n+\trebase_type rebasing = should_setup_rebase(origin);\n+\tstruct strbuf method = STRBUF_INIT;\n \n \tif (skip_prefix(remote, \"refs/heads/\", &shortname)\n \t    && !strcmp(local, shortname)\n@@ -78,44 +87,51 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n \tif (git_config_set_gently(key.buf, remote) < 0)\n \t\tgoto out_err;\n \n-\tif (rebasing) {\n-\t\tstrbuf_reset(&key);\n-\t\tstrbuf_addf(&key, \"branch.%s.rebase\", local);\n-\t\tif (git_config_set_gently(key.buf, \"true\") < 0)\n-\t\t\tgoto out_err;\n+\tstrbuf_reset(&key);\n+\tstrbuf_addf(&key, \"branch.%s.rebase\", local);\n+\tswitch(rebasing) {\n+\t\tcase REBASE_TRUE:\n+\t\t\tstrbuf_addstr(&method, \" by rebasing\");\n+\t\t\tif(git_config_set_gently(key.buf, \"true\") < 0)\n+\t\t\t\tgoto out_err;\n+\t\t\tbreak;\n+\t\tcase REBASE_MERGES:\n+\t\t\tstrbuf_addstr(&method, \" by rebasing while preserving merges\");\n+\t\t\tif (git_config_set_gently(key.buf, \"merges\") < 0)\n+\t\t\t\tgoto out_err;\n+\t\t\tbreak;\n+    default:;\n \t}\n \tstrbuf_release(&key);\n \n \tif (flag & BRANCH_CONFIG_VERBOSE) {\n \t\tif (shortname) {\n \t\t\tif (origin)\n-\t\t\t\tprintf_ln(rebasing ?\n-\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s' by rebasing.\") :\n-\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s'.\"),\n-\t\t\t\t\t  local, shortname, origin);\n+\t\t\t\tprintf_ln(\n+\t\t\t\t\t_(\"Branch '%s' set up to track remote branch '%s' from '%s'%s.\"),\n+\t\t\t\t\tlocal, shortname, origin, method.buf);\n \t\t\telse\n-\t\t\t\tprintf_ln(rebasing ?\n-\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s' by rebasing.\") :\n-\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s'.\"),\n-\t\t\t\t\t  local, shortname);\n+\t\t\t\tprintf_ln(\n+\t\t\t\t\t_(\"Branch '%s' set up to track local branch '%s'%s.\"),\n+\t\t\t\t\tlocal, shortname, method.buf);\n \t\t} else {\n \t\t\tif (origin)\n-\t\t\t\tprintf_ln(rebasing ?\n-\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s' by rebasing.\") :\n-\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s'.\"),\n-\t\t\t\t\t  local, remote);\n+\t\t\t\tprintf_ln(\n+\t\t\t\t\t_(\"Branch '%s' set up to track remote ref '%s'%s.\"),\n+\t\t\t\t\tlocal, remote, method.buf);\n \t\t\telse\n-\t\t\t\tprintf_ln(rebasing ?\n-\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s' by rebasing.\") :\n-\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s'.\"),\n-\t\t\t\t\t  local, remote);\n+\t\t\t\tprintf_ln(\n+\t\t\t\t\t_(\"Branch '%s' set up to track local ref '%s'%s.\"),\n+\t\t\t\t\tlocal, remote, method.buf);\n \t\t}\n \t}\n \n+\tstrbuf_release(&method);\n \treturn 0;\n \n out_err:\n \tstrbuf_release(&key);\n+\tstrbuf_release(&method);\n \terror(_(\"Unable to write upstream branch configuration\"));\n \n \tadvise(_(tracking_advice),\ndiff --git a/cache.h b/cache.h\nindex eefa93b08f8..5a378bcd534 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -995,7 +995,8 @@ enum rebase_setup_type {\n \tAUTOREBASE_NEVER = 0,\n \tAUTOREBASE_LOCAL,\n \tAUTOREBASE_REMOTE,\n-\tAUTOREBASE_ALWAYS\n+\tAUTOREBASE_ALWAYS,\n+\tAUTOREBASE_MERGES\n };\n \n enum push_default_type {\ndiff --git a/config.c b/config.c\nindex 4c0cf3a1c15..28d813f2595 100644\n--- a/config.c\n+++ b/config.c\n@@ -1443,6 +1443,8 @@ static int git_default_branch_config(const char *var, const char *value)\n \t\t\tautorebase = AUTOREBASE_REMOTE;\n \t\telse if (!strcmp(value, \"always\"))\n \t\t\tautorebase = AUTOREBASE_ALWAYS;\n+\t\telse if (!strcmp(value, \"merges\"))\n+\t\t\tautorebase = AUTOREBASE_MERGES;\n \t\telse\n \t\t\treturn error(_(\"malformed value for %s\"), var);\n \t\treturn 0;\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex 0af3b85d172..056192f72c5 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -1222,6 +1222,51 @@ test_expect_success 'autosetuprebase always on an untracked remote branch' '\n \ttest \"z$(git config branch.myr20.rebase)\" = z\n '\n \n+test_expect_success 'autosetuprebase merges on a tracked local branch' '\n+\tgit config branch.autosetuprebase merges &&\n+\tgit config remote.local.url . &&\n+\tgit config remote.local.fetch refs/heads/*:refs/remotes/local/* &&\n+\t(git show-ref -q refs/remotes/local/o || git fetch local) &&\n+\tgit branch mybase21 &&\n+\tgit branch --track myr21 mybase3 &&\n+\ttest \"$(git config branch.myr21.remote)\" = . &&\n+\ttest \"$(git config branch.myr21.merge)\" = refs/heads/mybase3 &&\n+\ttest \"$(git config branch.myr21.rebase)\" = merges\n+'\n+\n+test_expect_success 'autosetuprebase merges on a tracked remote branch' '\n+\tgit config branch.autosetuprebase merges &&\n+\tgit config remote.local.url . &&\n+\tgit config remote.local.fetch refs/heads/*:refs/remotes/local/* &&\n+\t(git show-ref -q refs/remotes/local/main || git fetch local) &&\n+\tgit branch --track myr22 local/main &&\n+\ttest \"$(git config branch.myr22.remote)\" = local &&\n+\ttest \"$(git config branch.myr22.merge)\" = refs/heads/main &&\n+\ttest \"$(git config branch.myr22.rebase)\" = merges\n+'\n+\n+test_expect_success 'autosetuprebase merges on an untracked local branch' '\n+\tgit config branch.autosetuprebase merges &&\n+\tgit config remote.local.url . &&\n+\tgit config remote.local.fetch refs/heads/*:refs/remotes/local/* &&\n+\t(git show-ref -q refs/remotes/local/main || git fetch local) &&\n+\tgit branch --no-track myr23 mybase2 &&\n+\ttest \"z$(git config branch.myr23.remote)\" = z &&\n+\ttest \"z$(git config branch.myr23.merge)\" = z &&\n+\ttest \"z$(git config branch.myr23.rebase)\" = z\n+'\n+\n+test_expect_success 'autosetuprebase merges on an untracked remote branch' '\n+\tgit config branch.autosetuprebase merges &&\n+\tgit config remote.local.url . &&\n+\tgit config remote.local.fetch refs/heads/*:refs/remotes/local/* &&\n+\t(git show-ref -q refs/remotes/local/main || git fetch local) &&\n+\tgit branch --no-track myr24 local/main &&\n+\ttest \"z$(git config branch.myr24.remote)\" = z &&\n+\ttest \"z$(git config branch.myr24.merge)\" = z &&\n+\ttest \"z$(git config branch.myr24.rebase)\" = z\n+'\n+\n test_expect_success 'autosetuprebase always on detached HEAD' '\n \tgit config branch.autosetupmerge always &&\n \ttest_when_finished git checkout main &&\ndiff --git a/t/t5601-clone.sh b/t/t5601-clone.sh\nindex 7df3c5373ae..10983191439 100755\n--- a/t/t5601-clone.sh\n+++ b/t/t5601-clone.sh\n@@ -210,7 +210,7 @@ test_expect_success 'clone a void' '\n \ttest_cmp target-6/.git/config target-7/.git/config\n '\n \n-test_expect_success 'clone respects global branch.autosetuprebase' '\n+test_expect_success 'clone respects global branch.autosetuprebase remote' '\n \t(\n \t\ttest_config=\"$HOME/.gitconfig\" &&\n \t\tgit config -f \"$test_config\" branch.autosetuprebase remote &&\n@@ -222,6 +222,18 @@ test_expect_success 'clone respects global branch.autosetuprebase' '\n \t)\n '\n \n+test_expect_success 'clone respects global branch.autosetuprebase merges' '\n+\t(\n+\t\ttest_config=\"$HOME/.gitconfig\" &&\n+\t\tgit config -f \"$test_config\" branch.autosetuprebase merges &&\n+\t\trm -fr dst &&\n+\t\tgit clone src dst &&\n+\t\tcd dst &&\n+\t\tactual=\"z$(git config branch.master.rebase)\" &&\n+\t\ttest zmerges = $actual\n+\t)\n+'\n+\n test_expect_success 'respect url-encoding of file://' '\n \tgit init x+y &&\n \tgit clone \"file://$PWD/x+y\" xy-url-1 &&\n\nbase-commit: 66e871b6647ffea61a77a0f82c7ef3415f1ee79c\n-- \ngitgitgadget\n"}]}