]> git.ipfire.org Git - thirdparty/git.git/commitdiff
branch: accept multiple upstream branches for tracking
authorJosh Steadmon <steadmon@google.com>
Tue, 21 Dec 2021 03:30:22 +0000 (19:30 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 21 Dec 2021 06:40:21 +0000 (22:40 -0800)
Add a new static variant of install_branch_config() that accepts
multiple remote branch names for tracking. This will be used in an
upcoming commit that enables inheriting the tracking configuration from
a parent branch.

Currently, all callers of install_branch_config() pass only a single
remote. Make install_branch_config() a small wrapper around
install_branch_config_multiple_remotes() so that existing callers do not
need to be changed.

Signed-off-by: Josh Steadmon <steadmon@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
branch.c
t/t3200-branch.sh

index 07a46430b3846f0fa4595f9ea0f98c2523e8d5f1..299c8f07f7503d29f5214590c5cb0787411d8b16 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -49,25 +49,46 @@ static int should_setup_rebase(const char *origin)
        return 0;
 }
 
-static const char tracking_advice[] =
-N_("\n"
-"After fixing the error cause you may try to fix up\n"
-"the remote tracking information by invoking\n"
-"\"git branch --set-upstream-to=%s%s%s\".");
-
-int install_branch_config(int flag, const char *local, const char *origin, const char *remote)
+/**
+ * Install upstream tracking configuration for a branch; specifically, add
+ * `branch.<name>.remote` and `branch.<name>.merge` entries.
+ *
+ * `flag` contains integer flags for options; currently only
+ * BRANCH_CONFIG_VERBOSE is checked.
+ *
+ * `local` is the name of the branch whose configuration we're installing.
+ *
+ * `origin` is the name of the remote owning the upstream branches. NULL means
+ * the upstream branches are local to this repo.
+ *
+ * `remotes` is a list of refs that are upstream of local
+ */
+static int install_branch_config_multiple_remotes(int flag, const char *local,
+               const char *origin, struct string_list *remotes)
 {
        const char *shortname = NULL;
        struct strbuf key = STRBUF_INIT;
+       struct string_list_item *item;
        int rebasing = should_setup_rebase(origin);
 
-       if (skip_prefix(remote, "refs/heads/", &shortname)
-           && !strcmp(local, shortname)
-           && !origin) {
-               warning(_("Not setting branch %s as its own upstream."),
-                       local);
-               return 0;
-       }
+       if (!remotes->nr)
+               BUG("must provide at least one remote for branch config");
+       if (rebasing && remotes->nr > 1)
+               die(_("cannot inherit upstream tracking configuration of "
+                     "multiple refs when rebasing is requested"));
+
+       /*
+        * If the new branch is trying to track itself, something has gone
+        * wrong. Warn the user and don't proceed any further.
+        */
+       if (!origin)
+               for_each_string_list_item(item, remotes)
+                       if (skip_prefix(item->string, "refs/heads/", &shortname)
+                           && !strcmp(local, shortname)) {
+                               warning(_("not setting branch '%s' as its own upstream."),
+                                       local);
+                               return 0;
+                       }
 
        strbuf_addf(&key, "branch.%s.remote", local);
        if (git_config_set_gently(key.buf, origin ? origin : ".") < 0)
@@ -75,8 +96,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 
        strbuf_reset(&key);
        strbuf_addf(&key, "branch.%s.merge", local);
-       if (git_config_set_gently(key.buf, remote) < 0)
+       /*
+        * We want to overwrite any existing config with all the branches in
+        * "remotes". Override any existing config, then write our branches. If
+        * more than one is provided, use CONFIG_REGEX_NONE to preserve what
+        * we've written so far.
+        */
+       if (git_config_set_gently(key.buf, NULL) < 0)
                goto out_err;
+       for_each_string_list_item(item, remotes)
+               if (git_config_set_multivar_gently(key.buf, item->string, CONFIG_REGEX_NONE, 0) < 0)
+                       goto out_err;
 
        if (rebasing) {
                strbuf_reset(&key);
@@ -87,29 +117,40 @@ int install_branch_config(int flag, const char *local, const char *origin, const
        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);
-                       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);
+               struct strbuf tmp_ref_name = STRBUF_INIT;
+               struct string_list friendly_ref_names = STRING_LIST_INIT_DUP;
+
+               for_each_string_list_item(item, remotes) {
+                       shortname = item->string;
+                       skip_prefix(shortname, "refs/heads/", &shortname);
+                       if (origin) {
+                               strbuf_addf(&tmp_ref_name, "%s/%s",
+                                           origin, shortname);
+                               string_list_append_nodup(
+                                       &friendly_ref_names,
+                                       strbuf_detach(&tmp_ref_name, NULL));
+                       } else {
+                               string_list_append(
+                                       &friendly_ref_names, shortname);
+                       }
+               }
+
+               if (remotes->nr == 1) {
+                       /*
+                        * Rebasing is only allowed in the case of a single
+                        * upstream branch.
+                        */
+                       printf_ln(rebasing ?
+                               _("branch '%s' set up to track '%s' by rebasing.") :
+                               _("branch '%s' set up to track '%s'."),
+                               local, friendly_ref_names.items[0].string);
                } 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);
-                       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);
+                       for_each_string_list_item(item, &friendly_ref_names)
+                               printf_ln("  %s", item->string);
                }
+
+               string_list_clear(&friendly_ref_names, 0);
        }
 
        return 0;
@@ -118,14 +159,36 @@ out_err:
        strbuf_release(&key);
        error(_("Unable to write upstream branch configuration"));
 
-       advise(_(tracking_advice),
-              origin ? origin : "",
-              origin ? "/" : "",
-              shortname ? shortname : remote);
+       advise(_("\nAfter fixing the error cause you may try to fix up\n"
+               "the remote tracking information by invoking:"));
+       if (remotes->nr == 1)
+               advise("  git branch --set-upstream-to=%s%s%s",
+                       origin ? origin : "",
+                       origin ? "/" : "",
+                       remotes->items[0].string);
+       else {
+               advise("  git config --add branch.\"%s\".remote %s",
+                       local, origin ? origin : ".");
+               for_each_string_list_item(item, remotes)
+                       advise("  git config --add branch.\"%s\".merge %s",
+                               local, item->string);
+       }
 
        return -1;
 }
 
+int install_branch_config(int flag, const char *local, const char *origin,
+               const char *remote)
+{
+       int ret;
+       struct string_list remotes = STRING_LIST_INIT_DUP;
+
+       string_list_append(&remotes, remote);
+       ret = install_branch_config_multiple_remotes(flag, local, origin, &remotes);
+       string_list_clear(&remotes, 0);
+       return ret;
+}
+
 /*
  * This is called when new_ref is branched off of orig_ref, and tries
  * to infer the settings for branch.<new_ref>.{remote,merge} from the
index e575ffb4ffb4c2d9c69da9b83706a7eecd01a6ef..bcc1acaa786e7da44599cfde9dc2cb67210ea4c3 100755 (executable)
@@ -950,15 +950,15 @@ test_expect_success 'disabled option --set-upstream fails' '
        test_must_fail git branch --set-upstream origin/main
 '
 
-test_expect_success '--set-upstream-to notices an error to set branch as own upstream' '
+test_expect_success '--set-upstream-to notices an error to set branch as own upstream' "
        git branch --set-upstream-to refs/heads/my13 my13 2>actual &&
        cat >expect <<-\EOF &&
-       warning: Not setting branch my13 as its own upstream.
+       warning: not setting branch 'my13' as its own upstream.
        EOF
        test_expect_code 1 git config branch.my13.remote &&
        test_expect_code 1 git config branch.my13.merge &&
        test_cmp expect actual
-'
+"
 
 # Keep this test last, as it changes the current branch
 cat >expect <<EOF