branch: accept multiple upstream branches for tracking

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>

Josh Steadmon committed Dec 20, 2021 at 19:30 UTC a3f40ec4b0dc56a54e6a6866d0a44a04d4a6edf0
2 files changed +106 -43
branch.c
+103 -40
@@ -49,25 +49,46 @@ static int should_setup_rebase(const char *origin)
49 return 0;
50 }
51
52 -static const char tracking_advice[] =
53 -N_("\n"
54 -"After fixing the error cause you may try to fix up\n"
55 -"the remote tracking information by invoking\n"
56 -"\"git branch --set-upstream-to=%s%s%s\".");
57 -
58 -int install_branch_config(int flag, const char *local, const char *origin, const char *remote)
52 +/**
53 + * Install upstream tracking configuration for a branch; specifically, add
54 + * `branch.<name>.remote` and `branch.<name>.merge` entries.
55 + *
56 + * `flag` contains integer flags for options; currently only
57 + * BRANCH_CONFIG_VERBOSE is checked.
58 + *
59 + * `local` is the name of the branch whose configuration we're installing.
60 + *
61 + * `origin` is the name of the remote owning the upstream branches. NULL means
62 + * the upstream branches are local to this repo.
63 + *
64 + * `remotes` is a list of refs that are upstream of local
65 + */
66 +static int install_branch_config_multiple_remotes(int flag, const char *local,
67 + const char *origin, struct string_list *remotes)
68 {
69 const char *shortname = NULL;
70 struct strbuf key = STRBUF_INIT;
71 + struct string_list_item *item;
72 int rebasing = should_setup_rebase(origin);
73
64 - if (skip_prefix(remote, "refs/heads/", &shortname)
65 - && !strcmp(local, shortname)
66 - && !origin) {
67 - warning(_("Not setting branch %s as its own upstream."),
68 - local);
69 - return 0;
70 - }
74 + if (!remotes->nr)
75 + BUG("must provide at least one remote for branch config");
76 + if (rebasing && remotes->nr > 1)
77 + die(_("cannot inherit upstream tracking configuration of "
78 + "multiple refs when rebasing is requested"));
79 +
80 + /*
81 + * If the new branch is trying to track itself, something has gone
82 + * wrong. Warn the user and don't proceed any further.
83 + */
84 + if (!origin)
85 + for_each_string_list_item(item, remotes)
86 + if (skip_prefix(item->string, "refs/heads/", &shortname)
87 + && !strcmp(local, shortname)) {
88 + warning(_("not setting branch '%s' as its own upstream."),
89 + local);
90 + return 0;
91 + }
92
93 strbuf_addf(&key, "branch.%s.remote", local);
94 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
96
97 strbuf_reset(&key);
98 strbuf_addf(&key, "branch.%s.merge", local);
78 - if (git_config_set_gently(key.buf, remote) < 0)
99 + /*
100 + * We want to overwrite any existing config with all the branches in
101 + * "remotes". Override any existing config, then write our branches. If
102 + * more than one is provided, use CONFIG_REGEX_NONE to preserve what
103 + * we've written so far.
104 + */
105 + if (git_config_set_gently(key.buf, NULL) < 0)
106 goto out_err;
107 + for_each_string_list_item(item, remotes)
108 + if (git_config_set_multivar_gently(key.buf, item->string, CONFIG_REGEX_NONE, 0) < 0)
109 + goto out_err;
110
111 if (rebasing) {
112 strbuf_reset(&key);
@@ -87,29 +117,40 @@ int install_branch_config(int flag, const char *local, const char *origin, const
117 strbuf_release(&key);
118
119 if (flag & BRANCH_CONFIG_VERBOSE) {
90 - if (shortname) {
91 - if (origin)
92 - printf_ln(rebasing ?
93 - _("Branch '%s' set up to track remote branch '%s' from '%s' by rebasing.") :
94 - _("Branch '%s' set up to track remote branch '%s' from '%s'."),
95 - local, shortname, origin);
96 - else
97 - printf_ln(rebasing ?
98 - _("Branch '%s' set up to track local branch '%s' by rebasing.") :
99 - _("Branch '%s' set up to track local branch '%s'."),
100 - local, shortname);
120 + struct strbuf tmp_ref_name = STRBUF_INIT;
121 + struct string_list friendly_ref_names = STRING_LIST_INIT_DUP;
122 +
123 + for_each_string_list_item(item, remotes) {
124 + shortname = item->string;
125 + skip_prefix(shortname, "refs/heads/", &shortname);
126 + if (origin) {
127 + strbuf_addf(&tmp_ref_name, "%s/%s",
128 + origin, shortname);
129 + string_list_append_nodup(
130 + &friendly_ref_names,
131 + strbuf_detach(&tmp_ref_name, NULL));
132 + } else {
133 + string_list_append(
134 + &friendly_ref_names, shortname);
135 + }
136 + }
137 +
138 + if (remotes->nr == 1) {
139 + /*
140 + * Rebasing is only allowed in the case of a single
141 + * upstream branch.
142 + */
143 + printf_ln(rebasing ?
144 + _("branch '%s' set up to track '%s' by rebasing.") :
145 + _("branch '%s' set up to track '%s'."),
146 + local, friendly_ref_names.items[0].string);
147 } else {
102 - if (origin)
103 - printf_ln(rebasing ?
104 - _("Branch '%s' set up to track remote ref '%s' by rebasing.") :
105 - _("Branch '%s' set up to track remote ref '%s'."),
106 - local, remote);
107 - else
108 - printf_ln(rebasing ?
109 - _("Branch '%s' set up to track local ref '%s' by rebasing.") :
110 - _("Branch '%s' set up to track local ref '%s'."),
111 - local, remote);
148 + printf_ln(_("branch '%s' set up to track:"), local);
149 + for_each_string_list_item(item, &friendly_ref_names)
150 + printf_ln(" %s", item->string);
151 }
152 +
153 + string_list_clear(&friendly_ref_names, 0);
154 }
155
156 return 0;
@@ -118,14 +159,36 @@ out_err:
159 strbuf_release(&key);
160 error(_("Unable to write upstream branch configuration"));
161
121 - advise(_(tracking_advice),
122 - origin ? origin : "",
123 - origin ? "/" : "",
124 - shortname ? shortname : remote);
162 + advise(_("\nAfter fixing the error cause you may try to fix up\n"
163 + "the remote tracking information by invoking:"));
164 + if (remotes->nr == 1)
165 + advise(" git branch --set-upstream-to=%s%s%s",
166 + origin ? origin : "",
167 + origin ? "/" : "",
168 + remotes->items[0].string);
169 + else {
170 + advise(" git config --add branch.\"%s\".remote %s",
171 + local, origin ? origin : ".");
172 + for_each_string_list_item(item, remotes)
173 + advise(" git config --add branch.\"%s\".merge %s",
174 + local, item->string);
175 + }
176
177 return -1;
178 }
179
180 +int install_branch_config(int flag, const char *local, const char *origin,
181 + const char *remote)
182 +{
183 + int ret;
184 + struct string_list remotes = STRING_LIST_INIT_DUP;
185 +
186 + string_list_append(&remotes, remote);
187 + ret = install_branch_config_multiple_remotes(flag, local, origin, &remotes);
188 + string_list_clear(&remotes, 0);
189 + return ret;
190 +}
191 +
192 /*
193 * This is called when new_ref is branched off of orig_ref, and tries
194 * to infer the settings for branch.<new_ref>.{remote,merge} from the
t/t3200-branch.sh
+3 -3
@@ -950,15 +950,15 @@ test_expect_success 'disabled option --set-upstream fails' '
950 test_must_fail git branch --set-upstream origin/main
951 '
952
953 -test_expect_success '--set-upstream-to notices an error to set branch as own upstream' '
953 +test_expect_success '--set-upstream-to notices an error to set branch as own upstream' "
954 git branch --set-upstream-to refs/heads/my13 my13 2>actual &&
955 cat >expect <<-\EOF &&
956 - warning: Not setting branch my13 as its own upstream.
956 + warning: not setting branch 'my13' as its own upstream.
957 EOF
958 test_expect_code 1 git config branch.my13.remote &&
959 test_expect_code 1 git config branch.my13.merge &&
960 test_cmp expect actual
961 -'
961 +"
962
963 # Keep this test last, as it changes the current branch
964 cat >expect <<EOF