remote: announce removal of "branches/" and "remotes/"

Back when Git was in its infancy, remotes were configured via separate files in "branches/" (back in 2005). This mechanism was replaced later that year with the "remotes/" directory. Both mechanisms have eventually been replaced by config-based remotes, and it is very unlikely that anybody still uses these directories to configure their remotes. Both of these directories have been marked as deprecated, one in 2005 and the other one in 2011. Follow through with the deprecation and finally announce the removal of these features in Git 3.0. Signed-off-by: Patrick Steinhardt <ps@pks.im> [jc: with a small tweak to the help message] Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Jan 22, 2025 at 12:31 UTC 8ccc75c2452b5814d2445d60d54266293ca48674
9 files changed +99 -43
Documentation/BreakingChanges.txt
+25
@@ -154,6 +154,31 @@ Cf. <xmqq1rjuz6n3.fsf_-_@gitster.c.googlers.com>,
154 <CAKvOHKAFXQwt4D8yUCCkf_TQL79mYaJ=KAKhtpDNTvHJFuX1NA@mail.gmail.com>,
155 <20230323204047.GA9290@coredump.intra.peff.net>,
156
157 +* Support for storing shorthands for remote URLs in "$GIT_COMMON_DIR/branches/"
158 + and "$GIT_COMMON_DIR/remotes/" has been long superseded by storing remotes in
159 + the repository configuration.
160 ++
161 +The mechanism has originally been introduced in f170e4b39d ([PATCH] fetch/pull:
162 +short-hand notation for remote repositories., 2005-07-16) and was superseded by
163 +6687f8fea2 ([PATCH] Use .git/remote/origin, not .git/branches/origin.,
164 +2005-08-20), where we switched from ".git/branches/" to ".git/remotes/". That
165 +commit already mentions an upcoming deprecation of the ".git/branches/"
166 +directory, and starting with a1d4aa7424 (Add repository-layout document.,
167 +2005-09-01) we have also marked this layout as deprecated. Eventually we also
168 +started to migrate away from ".git/remotes/" in favor of config-based remotes,
169 +and we have marked the directory as legacy in 3d3d282146 (Documentation:
170 +Grammar correction, wording fixes and cleanup, 2011-08-23)
171 ++
172 +As our documentation mentions, these directories are not to be found in modern
173 +repositories at all and most users aren't even aware of these mechanisms. They
174 +have been deprecated for almost 20 years and 14 years respectively, and we are
175 +not aware of any active users that have complained about this deprecation.
176 +Furthermore, the ".git/branches/" directory is nowadays misleadingly named and
177 +may cause confusion as "branches" are almost exclusively used in the context of
178 +references.
179 ++
180 +These features will be removed.
181 +
182 == Superseded features that will not be deprecated
183
184 Some features have gained newer replacements that aim to improve the design in
Documentation/gitrepository-layout.txt
+5 -2
@@ -153,7 +153,7 @@ config.worktree::
153 linkgit:git-worktree[1]).
154
155 branches::
156 - A slightly deprecated way to store shorthands to be used
156 + A deprecated way to store shorthands to be used
157 to specify a URL to 'git fetch', 'git pull' and 'git push'.
158 A file can be stored as `branches/<name>` and then
159 'name' can be given to these commands in place of
@@ -162,7 +162,8 @@ branches::
162 and not likely to be found in modern repositories. This
163 directory is ignored if $GIT_COMMON_DIR is set and
164 "$GIT_COMMON_DIR/branches" will be used instead.
165 -
165 ++
166 +Git will stop reading remotes from this directory in Git 3.0.
167
168 hooks::
169 Hooks are customization scripts used by various Git
@@ -238,6 +239,8 @@ remotes::
239 and not likely to be found in modern repositories. This
240 directory is ignored if $GIT_COMMON_DIR is set and
241 "$GIT_COMMON_DIR/remotes" will be used instead.
242 ++
243 +Git will stop reading remotes from this directory in Git 3.0.
244
245 logs::
246 Records of changes made to refs are stored in this directory.
builtin/remote.c
+2
@@ -640,10 +640,12 @@ static int migrate_file(struct remote *remote)
640 strbuf_addf(&buf, "remote.%s.fetch", remote->name);
641 for (i = 0; i < remote->fetch.nr; i++)
642 git_config_set_multivar(buf.buf, remote->fetch.items[i].raw, "^$", 0);
643 +#ifndef WITH_BREAKING_CHANGES
644 if (remote->origin == REMOTE_REMOTES)
645 unlink_or_warn(git_path("remotes/%s", remote->name));
646 else if (remote->origin == REMOTE_BRANCHES)
647 unlink_or_warn(git_path("branches/%s", remote->name));
648 +#endif /* WITH_BREAKING_CHANGES */
649 strbuf_release(&buf);
650
651 return 0;
remote.c
+26
@@ -293,6 +293,7 @@ static void add_instead_of(struct rewrite *rewrite, const char *instead_of)
293 rewrite->instead_of_nr++;
294 }
295
296 +#ifndef WITH_BREAKING_CHANGES
297 static const char *skip_spaces(const char *s)
298 {
299 while (isspace(*s))
@@ -300,6 +301,21 @@ static const char *skip_spaces(const char *s)
301 return s;
302 }
303
304 +static void warn_about_deprecated_remote_type(const char *type,
305 + const struct remote *remote)
306 +{
307 + warning(_("reading remote from \"%s/%s\", which is nominated for removal.\n"
308 + "\n"
309 + "If you still use the \"remotes/\" directory it is recommended to\n"
310 + "migrate to config-based remotes:\n"
311 + "\n"
312 + "\tgit remote rename %s %s\n"
313 + "\n"
314 + "If you cannot, please let us know why you still need to use it by\n"
315 + "sending an e-mail to <git@vger.kernel.org>."),
316 + type, remote->name, remote->name, remote->name);
317 +}
318 +
319 static void read_remotes_file(struct remote_state *remote_state,
320 struct remote *remote)
321 {
@@ -308,6 +324,9 @@ static void read_remotes_file(struct remote_state *remote_state,
324
325 if (!f)
326 return;
327 +
328 + warn_about_deprecated_remote_type("remotes", remote);
329 +
330 remote->configured_in_repo = 1;
331 remote->origin = REMOTE_REMOTES;
332 while (strbuf_getline(&buf, f) != EOF) {
@@ -337,6 +356,8 @@ static void read_branches_file(struct remote_state *remote_state,
356 if (!f)
357 return;
358
359 + warn_about_deprecated_remote_type("branches", remote);
360 +
361 strbuf_getline_lf(&buf, f);
362 fclose(f);
363 strbuf_trim(&buf);
@@ -374,6 +395,7 @@ static void read_branches_file(struct remote_state *remote_state,
395 strbuf_release(&buf);
396 free(to_free);
397 }
398 +#endif /* WITH_BREAKING_CHANGES */
399
400 static int handle_config(const char *key, const char *value,
401 const struct config_context *ctx, void *cb)
@@ -572,6 +594,7 @@ static void read_config(struct repository *repo, int early)
594 alias_all_urls(repo->remote_state);
595 }
596
597 +#ifndef WITH_BREAKING_CHANGES
598 static int valid_remote_nick(const char *name)
599 {
600 if (!name[0] || is_dot_or_dotdot(name))
@@ -583,6 +606,7 @@ static int valid_remote_nick(const char *name)
606 return 0;
607 return 1;
608 }
609 +#endif /* WITH_BREAKING_CHANGES */
610
611 static const char *remotes_remote_for_branch(struct remote_state *remote_state,
612 struct branch *branch,
@@ -725,12 +749,14 @@ remotes_remote_get_1(struct remote_state *remote_state, const char *name,
749 &name_given);
750
751 ret = make_remote(remote_state, name, 0);
752 +#ifndef WITH_BREAKING_CHANGES
753 if (valid_remote_nick(name) && have_git_dir()) {
754 if (!valid_remote(ret))
755 read_remotes_file(remote_state, ret);
756 if (!valid_remote(ret))
757 read_branches_file(remote_state, ret);
758 }
759 +#endif /* WITH_BREAKING_CHANGES */
760 if (name_given && !valid_remote(ret))
761 add_url_alias(remote_state, ret, name);
762 if (!valid_remote(ret))
remote.h
+2
@@ -21,8 +21,10 @@ struct transport_ls_refs_options;
21 enum {
22 REMOTE_UNCONFIGURED = 0,
23 REMOTE_CONFIG,
24 +#ifndef WITH_BREAKING_CHANGES
25 REMOTE_REMOTES,
26 REMOTE_BRANCHES
27 +#endif /* WITH_BREAKING_CHANGES */
28 };
29
30 struct rewrite {
t/t5505-remote.sh
+3 -3
@@ -1007,7 +1007,7 @@ Pull: refs/heads/main:refs/heads/origin
1007 Pull: refs/heads/next:refs/heads/origin2
1008 EOF
1009
1010 -test_expect_success 'migrate a remote from named file in $GIT_DIR/remotes' '
1010 +test_expect_success WITHOUT_BREAKING_CHANGES 'migrate a remote from named file in $GIT_DIR/remotes' '
1011 git clone one five &&
1012 origin_url=$(pwd)/one &&
1013 (
@@ -1033,7 +1033,7 @@ test_expect_success 'migrate a remote from named file in $GIT_DIR/remotes' '
1033 )
1034 '
1035
1036 -test_expect_success 'migrate a remote from named file in $GIT_DIR/branches' '
1036 +test_expect_success WITHOUT_BREAKING_CHANGES 'migrate a remote from named file in $GIT_DIR/branches' '
1037 git clone --template= one six &&
1038 origin_url=$(pwd)/one &&
1039 (
@@ -1049,7 +1049,7 @@ test_expect_success 'migrate a remote from named file in $GIT_DIR/branches' '
1049 )
1050 '
1051
1052 -test_expect_success 'migrate a remote from named file in $GIT_DIR/branches (2)' '
1052 +test_expect_success WITHOUT_BREAKING_CHANGES 'migrate a remote from named file in $GIT_DIR/branches (2)' '
1053 git clone --template= one seven &&
1054 (
1055 cd seven &&
t/t5510-fetch.sh
+5 -8
@@ -34,14 +34,11 @@ test_expect_success "clone and setup child repos" '
34 git clone . three &&
35 (
36 cd three &&
37 - git config branch.main.remote two &&
38 - git config branch.main.merge refs/heads/one &&
39 - mkdir -p .git/remotes &&
40 - cat >.git/remotes/two <<-\EOF
41 - URL: ../two/.git/
42 - Pull: refs/heads/main:refs/heads/two
43 - Pull: refs/heads/one:refs/heads/one
44 - EOF
37 + git config set remote.two.url ../two/.git/ &&
38 + git config set remote.two.fetch refs/heads/main:refs/heads/two &&
39 + git config set --append remote.two.fetch refs/heads/one:refs/heads/one &&
40 + git config set branch.main.remote two &&
41 + git config set branch.main.merge refs/heads/one
42 ) &&
43 git clone . bundle &&
44 git clone . seven
t/t5515-fetch-merge-logic.sh
+25 -22
@@ -104,28 +104,31 @@ test_expect_success setup '
104 git config remote.config-glob.fetch refs/heads/*:refs/remotes/rem/* &&
105 remotes="$remotes config-glob" &&
106
107 - mkdir -p .git/remotes &&
108 - cat >.git/remotes/remote-explicit <<-\EOF &&
109 - URL: ../.git/
110 - Pull: refs/heads/main:remotes/rem/main
111 - Pull: refs/heads/one:remotes/rem/one
112 - Pull: two:remotes/rem/two
113 - Pull: refs/heads/three:remotes/rem/three
114 - EOF
115 - remotes="$remotes remote-explicit" &&
116 -
117 - cat >.git/remotes/remote-glob <<-\EOF &&
118 - URL: ../.git/
119 - Pull: refs/heads/*:refs/remotes/rem/*
120 - EOF
121 - remotes="$remotes remote-glob" &&
122 -
123 - mkdir -p .git/branches &&
124 - echo "../.git" > .git/branches/branches-default &&
125 - remotes="$remotes branches-default" &&
126 -
127 - echo "../.git#one" > .git/branches/branches-one &&
128 - remotes="$remotes branches-one" &&
107 + if test_have_prereq WITHOUT_BREAKING_CHANGES
108 + then
109 + mkdir -p .git/remotes &&
110 + cat >.git/remotes/remote-explicit <<-\EOF &&
111 + URL: ../.git/
112 + Pull: refs/heads/main:remotes/rem/main
113 + Pull: refs/heads/one:remotes/rem/one
114 + Pull: two:remotes/rem/two
115 + Pull: refs/heads/three:remotes/rem/three
116 + EOF
117 + remotes="$remotes remote-explicit" &&
118 +
119 + cat >.git/remotes/remote-glob <<-\EOF &&
120 + URL: ../.git/
121 + Pull: refs/heads/*:refs/remotes/rem/*
122 + EOF
123 + remotes="$remotes remote-glob" &&
124 +
125 + mkdir -p .git/branches &&
126 + echo "../.git" > .git/branches/branches-default &&
127 + remotes="$remotes branches-default" &&
128 +
129 + echo "../.git#one" > .git/branches/branches-one &&
130 + remotes="$remotes branches-one"
131 + fi &&
132
133 for remote in $remotes ; do
134 git config branch.br-$remote.remote $remote &&
t/t5516-fetch-push.sh
+6 -8
@@ -975,7 +975,7 @@ test_expect_success 'allow push to HEAD of non-bare repository (config)' '
975 ! grep "warning: updating the current branch" stderr
976 '
977
978 -test_expect_success 'fetch with branches' '
978 +test_expect_success WITHOUT_BREAKING_CHANGES 'fetch with branches' '
979 mk_empty testrepo &&
980 git branch second $the_first_commit &&
981 git checkout second &&
@@ -991,7 +991,7 @@ test_expect_success 'fetch with branches' '
991 git checkout main
992 '
993
994 -test_expect_success 'fetch with branches containing #' '
994 +test_expect_success WITHOUT_BREAKING_CHANGES 'fetch with branches containing #' '
995 mk_empty testrepo &&
996 mkdir testrepo/.git/branches &&
997 echo "..#second" > testrepo/.git/branches/branch2 &&
@@ -1005,7 +1005,7 @@ test_expect_success 'fetch with branches containing #' '
1005 git checkout main
1006 '
1007
1008 -test_expect_success 'push with branches' '
1008 +test_expect_success WITHOUT_BREAKING_CHANGES 'push with branches' '
1009 mk_empty testrepo &&
1010 git checkout second &&
1011
@@ -1022,7 +1022,7 @@ test_expect_success 'push with branches' '
1022 )
1023 '
1024
1025 -test_expect_success 'push with branches containing #' '
1025 +test_expect_success WITHOUT_BREAKING_CHANGES 'push with branches containing #' '
1026 mk_empty testrepo &&
1027
1028 test_when_finished "rm -rf .git/branches" &&
@@ -1211,18 +1211,16 @@ test_expect_success 'push --porcelain --dry-run rejected' '
1211 '
1212
1213 test_expect_success 'push --prune' '
1214 - mk_test testrepo heads/main heads/second heads/foo heads/bar &&
1214 + mk_test testrepo heads/main heads/foo heads/bar &&
1215 git push --prune testrepo : &&
1216 check_push_result testrepo $the_commit heads/main &&
1217 - check_push_result testrepo $the_first_commit heads/second &&
1217 ! check_push_result testrepo $the_first_commit heads/foo heads/bar
1218 '
1219
1220 test_expect_success 'push --prune refspec' '
1222 - mk_test testrepo tmp/main tmp/second tmp/foo tmp/bar &&
1221 + mk_test testrepo tmp/main tmp/foo tmp/bar &&
1222 git push --prune testrepo "refs/heads/*:refs/tmp/*" &&
1223 check_push_result testrepo $the_commit tmp/main &&
1225 - check_push_result testrepo $the_first_commit tmp/second &&
1224 ! check_push_result testrepo $the_first_commit tmp/foo tmp/bar
1225 '
1226