Git fork

submodule: look up remotes by URL first

The get_default_remote_submodule() function performs a lookup to find
the appropriate remote to use within a submodule. The function first
checks to see if it can find the remote for the current branch. If this
fails, it then checks to see if there is exactly one remote. It will use
this, before finally falling back to "origin" as the default.

If a user happens to rename their default remote from origin, either
manually or by setting something like clone.defaultRemoteName, this
fallback will not work.

In such cases, the submodule logic will try to use a non-existent
remote. This usually manifests as a failure to trigger the submodule
update.

The parent project already knows and stores the submodule URL in either
.gitmodules or its .git/config.

Add a new repo_remote_from_url() helper which will iterate over all the
remotes in a repository and return the first remote which has a matching
URL.

Refactor get_default_remote_submodule to find the submodule and get its
URL. If a valid URL exists, first try to obtain a remote using the new
repo_remote_from_url(). Fall back to the repo_default_remote()
otherwise.

The fallback logic is kept in case for some reason the user has manually
changed the URL within the submodule. Additionally, we still try to use
a remote rather than directly passing the URL in the
fetch_in_submodule() logic. This ensures that an update will properly
update the remote refs within the submodule as expected, rather than
just fetching into FETCH_HEAD.

Signed-off-by: Jacob Keller <jacob.keller@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>

authored by

Jacob Keller and committed by
Junio C Hamano
ca62f524 fedfb073

+73 -1
+25 -1
builtin/submodule--helper.c
··· 72 73 static int get_default_remote_submodule(const char *module_path, char **default_remote) 74 { 75 struct repository subrepo; 76 77 if (repo_submodule_init(&subrepo, the_repository, module_path, 78 null_oid(the_hash_algo)) < 0) 79 return die_message(_("could not get a repository handle for submodule '%s'"), 80 module_path); 81 82 - *default_remote = xstrdup(repo_default_remote(&subrepo)); 83 84 repo_clear(&subrepo); 85 86 return 0; 87 }
··· 72 73 static int get_default_remote_submodule(const char *module_path, char **default_remote) 74 { 75 + const struct submodule *sub; 76 struct repository subrepo; 77 + const char *remote_name = NULL; 78 + char *url = NULL; 79 + 80 + sub = submodule_from_path(the_repository, null_oid(the_hash_algo), module_path); 81 + if (sub && sub->url) { 82 + url = xstrdup(sub->url); 83 + 84 + /* Possibly a url relative to parent */ 85 + if (starts_with_dot_dot_slash(url) || 86 + starts_with_dot_slash(url)) { 87 + char *oldurl = url; 88 + 89 + url = resolve_relative_url(oldurl, NULL, 1); 90 + free(oldurl); 91 + } 92 + } 93 94 if (repo_submodule_init(&subrepo, the_repository, module_path, 95 null_oid(the_hash_algo)) < 0) 96 return die_message(_("could not get a repository handle for submodule '%s'"), 97 module_path); 98 99 + /* Look up by URL first */ 100 + if (url) 101 + remote_name = repo_remote_from_url(&subrepo, url); 102 + if (!remote_name) 103 + remote_name = repo_default_remote(&subrepo); 104 + 105 + *default_remote = xstrdup(remote_name); 106 107 repo_clear(&subrepo); 108 + free(url); 109 110 return 0; 111 }
+15
remote.c
··· 1801 return remotes_remote_for_branch(repo->remote_state, branch, NULL); 1802 } 1803 1804 int branch_has_merge_config(struct branch *branch) 1805 { 1806 return branch && branch->set_merge;
··· 1801 return remotes_remote_for_branch(repo->remote_state, branch, NULL); 1802 } 1803 1804 + const char *repo_remote_from_url(struct repository *repo, const char *url) 1805 + { 1806 + read_config(repo, 0); 1807 + 1808 + for (int i = 0; i < repo->remote_state->remotes_nr; i++) { 1809 + struct remote *remote = repo->remote_state->remotes[i]; 1810 + if (!remote) 1811 + continue; 1812 + 1813 + if (remote_has_url(remote, url)) 1814 + return remote->name; 1815 + } 1816 + return NULL; 1817 + } 1818 + 1819 int branch_has_merge_config(struct branch *branch) 1820 { 1821 return branch && branch->set_merge;
+1
remote.h
··· 340 char *remote_ref_for_branch(struct branch *branch, int for_push); 341 342 const char *repo_default_remote(struct repository *repo); 343 344 /* returns true if the given branch has merge configuration given. */ 345 int branch_has_merge_config(struct branch *branch);
··· 340 char *remote_ref_for_branch(struct branch *branch, int for_push); 341 342 const char *repo_default_remote(struct repository *repo); 343 + const char *repo_remote_from_url(struct repository *repo, const char *url); 344 345 /* returns true if the given branch has merge configuration given. */ 346 int branch_has_merge_config(struct branch *branch);
+32
t/t7406-submodule-update.sh
··· 1134 git clone --recurse-submodules top top-clean 1135 ' 1136 1137 test_expect_success 'submodule update with renamed remote' ' 1138 test_when_finished "rm -fr top-cloned" && 1139 cp -r top-clean top-cloned &&
··· 1134 git clone --recurse-submodules top top-clean 1135 ' 1136 1137 + test_expect_success 'submodule update with multiple remotes' ' 1138 + test_when_finished "rm -fr top-cloned" && 1139 + cp -r top-clean top-cloned && 1140 + 1141 + # Create a commit in each repo, starting with bottom 1142 + test_commit -C bottom multiple_remote_commit && 1143 + # Create middle commit 1144 + git -C middle/bottom fetch && 1145 + git -C middle/bottom checkout -f FETCH_HEAD && 1146 + git -C middle add bottom && 1147 + git -C middle commit -m "multiple_remote_commit" && 1148 + # Create top commit 1149 + git -C top/middle fetch && 1150 + git -C top/middle checkout -f FETCH_HEAD && 1151 + git -C top add middle && 1152 + git -C top commit -m "multiple_remote_commit" && 1153 + 1154 + # rename the submodule remote 1155 + git -C top-cloned/middle remote rename origin upstream && 1156 + 1157 + # Add another remote 1158 + git -C top-cloned/middle remote add other bogus && 1159 + 1160 + # Make the update of "middle" a no-op, otherwise we error out 1161 + # because of its unmerged state 1162 + test_config -C top-cloned submodule.middle.update !true && 1163 + git -C top-cloned submodule update --recursive 2>actual.err && 1164 + cat >expect.err <<-\EOF && 1165 + EOF 1166 + test_cmp expect.err actual.err 1167 + ' 1168 + 1169 test_expect_success 'submodule update with renamed remote' ' 1170 test_when_finished "rm -fr top-cloned" && 1171 cp -r top-clean top-cloned &&