Merge branch 'jk/submodule-remote-lookup-cleanup'

Updating submodules from the upstream did not work well when
submodule's HEAD is detached, which has been improved.

* jk/submodule-remote-lookup-cleanup:
  submodule: look up remotes by URL first
  submodule: move get_default_remote_submodule()
  submodule--helper: improve logic for fallback remote name
  remote: remove the_repository from some functions
  dir: move starts_with_dot(_dot)_slash to dir.h
  remote: fix tear down of struct remote
  remote: remove branch->merge_name and fix branch_release()
This commit is contained in:
Junio C Hamano 2025-07-07 14:12:55 -07:00
commit 7310e539ad
8 changed files with 230 additions and 135 deletions

View File

@ -230,7 +230,7 @@ static int inherit_tracking(struct tracking *tracking, const char *orig_ref)
return -1; return -1;
} }
if (branch->merge_nr < 1 || !branch->merge_name || !branch->merge_name[0]) { if (branch->merge_nr < 1 || !branch->merge || !branch->merge[0] || !branch->merge[0]->src) {
warning(_("asked to inherit tracking from '%s', but no merge configuration is set"), warning(_("asked to inherit tracking from '%s', but no merge configuration is set"),
bare_ref); bare_ref);
return -1; return -1;
@ -238,7 +238,7 @@ static int inherit_tracking(struct tracking *tracking, const char *orig_ref)
tracking->remote = branch->remote_name; tracking->remote = branch->remote_name;
for (i = 0; i < branch->merge_nr; i++) for (i = 0; i < branch->merge_nr; i++)
string_list_append(tracking->srcs, branch->merge_name[i]); string_list_append(tracking->srcs, branch->merge[i]->src);
return 0; return 0;
} }

View File

@ -490,7 +490,7 @@ static void NORETURN die_no_merge_candidates(const char *repo, const char **refs
} else } else
fprintf_ln(stderr, _("Your configuration specifies to merge with the ref '%s'\n" fprintf_ln(stderr, _("Your configuration specifies to merge with the ref '%s'\n"
"from the remote, but no such ref was fetched."), "from the remote, but no such ref was fetched."),
*curr_branch->merge_name); curr_branch->merge[0]->src);
exit(1); exit(1);
} }

View File

@ -41,61 +41,9 @@
typedef void (*each_submodule_fn)(const struct cache_entry *list_item, typedef void (*each_submodule_fn)(const struct cache_entry *list_item,
void *cb_data); void *cb_data);
static int repo_get_default_remote(struct repository *repo, char **default_remote)
{
char *dest = NULL;
struct strbuf sb = STRBUF_INIT;
struct ref_store *store = get_main_ref_store(repo);
const char *refname = refs_resolve_ref_unsafe(store, "HEAD", 0, NULL,
NULL);
if (!refname)
return die_message(_("No such ref: %s"), "HEAD");
/* detached HEAD */
if (!strcmp(refname, "HEAD")) {
*default_remote = xstrdup("origin");
return 0;
}
if (!skip_prefix(refname, "refs/heads/", &refname))
return die_message(_("Expecting a full ref name, got %s"),
refname);
strbuf_addf(&sb, "branch.%s.remote", refname);
if (repo_config_get_string(repo, sb.buf, &dest))
*default_remote = xstrdup("origin");
else
*default_remote = dest;
strbuf_release(&sb);
return 0;
}
static int get_default_remote_submodule(const char *module_path, char **default_remote)
{
struct repository subrepo;
int ret;
if (repo_submodule_init(&subrepo, the_repository, module_path,
null_oid(the_hash_algo)) < 0)
return die_message(_("could not get a repository handle for submodule '%s'"),
module_path);
ret = repo_get_default_remote(&subrepo, default_remote);
repo_clear(&subrepo);
return ret;
}
static char *get_default_remote(void) static char *get_default_remote(void)
{ {
char *default_remote; return xstrdup(repo_default_remote(the_repository));
int code = repo_get_default_remote(the_repository, &default_remote);
if (code)
exit(code);
return default_remote;
} }
static char *resolve_relative_url(const char *rel_url, const char *up_path, int quiet) static char *resolve_relative_url(const char *rel_url, const char *up_path, int quiet)
@ -122,6 +70,46 @@ static char *resolve_relative_url(const char *rel_url, const char *up_path, int
return resolved_url; return resolved_url;
} }
static int get_default_remote_submodule(const char *module_path, char **default_remote)
{
const struct submodule *sub;
struct repository subrepo;
const char *remote_name = NULL;
char *url = NULL;
sub = submodule_from_path(the_repository, null_oid(the_hash_algo), module_path);
if (sub && sub->url) {
url = xstrdup(sub->url);
/* Possibly a url relative to parent */
if (starts_with_dot_dot_slash(url) ||
starts_with_dot_slash(url)) {
char *oldurl = url;
url = resolve_relative_url(oldurl, NULL, 1);
free(oldurl);
}
}
if (repo_submodule_init(&subrepo, the_repository, module_path,
null_oid(the_hash_algo)) < 0)
return die_message(_("could not get a repository handle for submodule '%s'"),
module_path);
/* Look up by URL first */
if (url)
remote_name = repo_remote_from_url(&subrepo, url);
if (!remote_name)
remote_name = repo_default_remote(&subrepo);
*default_remote = xstrdup(remote_name);
repo_clear(&subrepo);
free(url);
return 0;
}
/* the result should be freed by the caller. */ /* the result should be freed by the caller. */
static char *get_submodule_displaypath(const char *path, const char *prefix, static char *get_submodule_displaypath(const char *path, const char *prefix,
const char *super_prefix) const char *super_prefix)
@ -438,18 +426,6 @@ cleanup:
return ret; return ret;
} }
static int starts_with_dot_slash(const char *const path)
{
return path_match_flags(path, PATH_MATCH_STARTS_WITH_DOT_SLASH |
PATH_MATCH_XPLATFORM);
}
static int starts_with_dot_dot_slash(const char *const path)
{
return path_match_flags(path, PATH_MATCH_STARTS_WITH_DOT_DOT_SLASH |
PATH_MATCH_XPLATFORM);
}
struct init_cb { struct init_cb {
const char *prefix; const char *prefix;
const char *super_prefix; const char *super_prefix;

23
dir.h
View File

@ -676,4 +676,27 @@ static inline int starts_with_dot_dot_slash_native(const char *const path)
return path_match_flags(path, what | PATH_MATCH_NATIVE); return path_match_flags(path, what | PATH_MATCH_NATIVE);
} }
/**
* starts_with_dot_slash: convenience wrapper for
* patch_match_flags() with PATH_MATCH_STARTS_WITH_DOT_SLASH and
* PATH_MATCH_XPLATFORM.
*/
static inline int starts_with_dot_slash(const char *const path)
{
const enum path_match_flags what = PATH_MATCH_STARTS_WITH_DOT_SLASH;
return path_match_flags(path, what | PATH_MATCH_XPLATFORM);
}
/**
* starts_with_dot_dot_slash: convenience wrapper for
* patch_match_flags() with PATH_MATCH_STARTS_WITH_DOT_DOT_SLASH and
* PATH_MATCH_XPLATFORM.
*/
static inline int starts_with_dot_dot_slash(const char *const path)
{
const enum path_match_flags what = PATH_MATCH_STARTS_WITH_DOT_DOT_SLASH;
return path_match_flags(path, what | PATH_MATCH_XPLATFORM);
}
#endif #endif

149
remote.c
View File

@ -165,6 +165,9 @@ static void remote_clear(struct remote *remote)
strvec_clear(&remote->url); strvec_clear(&remote->url);
strvec_clear(&remote->pushurl); strvec_clear(&remote->pushurl);
refspec_clear(&remote->push);
refspec_clear(&remote->fetch);
free((char *)remote->receivepack); free((char *)remote->receivepack);
free((char *)remote->uploadpack); free((char *)remote->uploadpack);
FREE_AND_NULL(remote->http_proxy); FREE_AND_NULL(remote->http_proxy);
@ -174,9 +177,15 @@ static void remote_clear(struct remote *remote)
static void add_merge(struct branch *branch, const char *name) static void add_merge(struct branch *branch, const char *name)
{ {
ALLOC_GROW(branch->merge_name, branch->merge_nr + 1, struct refspec_item *merge;
ALLOC_GROW(branch->merge, branch->merge_nr + 1,
branch->merge_alloc); branch->merge_alloc);
branch->merge_name[branch->merge_nr++] = name;
merge = xcalloc(1, sizeof(*merge));
merge->src = xstrdup(name);
branch->merge[branch->merge_nr++] = merge;
} }
struct branches_hash_key { struct branches_hash_key {
@ -247,15 +256,23 @@ static struct branch *make_branch(struct remote_state *remote_state,
return ret; return ret;
} }
static void merge_clear(struct branch *branch)
{
for (int i = 0; i < branch->merge_nr; i++) {
refspec_item_clear(branch->merge[i]);
free(branch->merge[i]);
}
FREE_AND_NULL(branch->merge);
branch->merge_nr = 0;
}
static void branch_release(struct branch *branch) static void branch_release(struct branch *branch)
{ {
free((char *)branch->name); free((char *)branch->name);
free((char *)branch->refname); free((char *)branch->refname);
free(branch->remote_name); free(branch->remote_name);
free(branch->pushremote_name); free(branch->pushremote_name);
for (int i = 0; i < branch->merge_nr; i++) merge_clear(branch);
refspec_item_clear(branch->merge[i]);
free(branch->merge);
} }
static struct rewrite *make_rewrite(struct rewrites *r, static struct rewrite *make_rewrite(struct rewrites *r,
@ -317,11 +334,10 @@ static void warn_about_deprecated_remote_type(const char *type,
type, remote->name, remote->name, remote->name); type, remote->name, remote->name, remote->name);
} }
static void read_remotes_file(struct remote_state *remote_state, static void read_remotes_file(struct repository *repo, struct remote *remote)
struct remote *remote)
{ {
struct strbuf buf = STRBUF_INIT; struct strbuf buf = STRBUF_INIT;
FILE *f = fopen_or_warn(repo_git_path_append(the_repository, &buf, FILE *f = fopen_or_warn(repo_git_path_append(repo, &buf,
"remotes/%s", remote->name), "r"); "remotes/%s", remote->name), "r");
if (!f) if (!f)
@ -337,7 +353,7 @@ static void read_remotes_file(struct remote_state *remote_state,
strbuf_rtrim(&buf); strbuf_rtrim(&buf);
if (skip_prefix(buf.buf, "URL:", &v)) if (skip_prefix(buf.buf, "URL:", &v))
add_url_alias(remote_state, remote, add_url_alias(repo->remote_state, remote,
skip_spaces(v)); skip_spaces(v));
else if (skip_prefix(buf.buf, "Push:", &v)) else if (skip_prefix(buf.buf, "Push:", &v))
refspec_append(&remote->push, skip_spaces(v)); refspec_append(&remote->push, skip_spaces(v));
@ -350,12 +366,11 @@ out:
strbuf_release(&buf); strbuf_release(&buf);
} }
static void read_branches_file(struct remote_state *remote_state, static void read_branches_file(struct repository *repo, struct remote *remote)
struct remote *remote)
{ {
char *frag, *to_free = NULL; char *frag, *to_free = NULL;
struct strbuf buf = STRBUF_INIT; struct strbuf buf = STRBUF_INIT;
FILE *f = fopen_or_warn(repo_git_path_append(the_repository, &buf, FILE *f = fopen_or_warn(repo_git_path_append(repo, &buf,
"branches/%s", remote->name), "r"); "branches/%s", remote->name), "r");
if (!f) if (!f)
@ -382,9 +397,9 @@ static void read_branches_file(struct remote_state *remote_state,
if (frag) if (frag)
*(frag++) = '\0'; *(frag++) = '\0';
else else
frag = to_free = repo_default_branch_name(the_repository, 0); frag = to_free = repo_default_branch_name(repo, 0);
add_url_alias(remote_state, remote, buf.buf); add_url_alias(repo->remote_state, remote, buf.buf);
refspec_appendf(&remote->fetch, "refs/heads/%s:refs/heads/%s", refspec_appendf(&remote->fetch, "refs/heads/%s:refs/heads/%s",
frag, remote->name); frag, remote->name);
@ -429,7 +444,7 @@ static int handle_config(const char *key, const char *value,
} else if (!strcmp(subkey, "merge")) { } else if (!strcmp(subkey, "merge")) {
if (!value) if (!value)
return config_error_nonbool(key); return config_error_nonbool(key);
add_merge(branch, xstrdup(value)); add_merge(branch, value);
} }
return 0; return 0;
} }
@ -681,7 +696,7 @@ const char *pushremote_for_branch(struct branch *branch, int *explicit)
branch, explicit); branch, explicit);
} }
static struct remote *remotes_remote_get(struct remote_state *remote_state, static struct remote *remotes_remote_get(struct repository *repo,
const char *name); const char *name);
char *remote_ref_for_branch(struct branch *branch, int for_push) char *remote_ref_for_branch(struct branch *branch, int for_push)
@ -692,7 +707,7 @@ char *remote_ref_for_branch(struct branch *branch, int for_push)
if (branch) { if (branch) {
if (!for_push) { if (!for_push) {
if (branch->merge_nr) { if (branch->merge_nr) {
return xstrdup(branch->merge_name[0]); return xstrdup(branch->merge[0]->src);
} }
} else { } else {
char *dst; char *dst;
@ -700,7 +715,7 @@ char *remote_ref_for_branch(struct branch *branch, int for_push)
the_repository->remote_state, branch, the_repository->remote_state, branch,
NULL); NULL);
struct remote *remote = remotes_remote_get( struct remote *remote = remotes_remote_get(
the_repository->remote_state, remote_name); the_repository, remote_name);
if (remote && remote->push.nr && if (remote && remote->push.nr &&
(dst = apply_refspecs(&remote->push, (dst = apply_refspecs(&remote->push,
@ -757,10 +772,11 @@ loop_cleanup:
} }
static struct remote * static struct remote *
remotes_remote_get_1(struct remote_state *remote_state, const char *name, remotes_remote_get_1(struct repository *repo, const char *name,
const char *(*get_default)(struct remote_state *, const char *(*get_default)(struct remote_state *,
struct branch *, int *)) struct branch *, int *))
{ {
struct remote_state *remote_state = repo->remote_state;
struct remote *ret; struct remote *ret;
int name_given = 0; int name_given = 0;
@ -774,9 +790,9 @@ remotes_remote_get_1(struct remote_state *remote_state, const char *name,
#ifndef WITH_BREAKING_CHANGES #ifndef WITH_BREAKING_CHANGES
if (valid_remote_nick(name) && have_git_dir()) { if (valid_remote_nick(name) && have_git_dir()) {
if (!valid_remote(ret)) if (!valid_remote(ret))
read_remotes_file(remote_state, ret); read_remotes_file(repo, ret);
if (!valid_remote(ret)) if (!valid_remote(ret))
read_branches_file(remote_state, ret); read_branches_file(repo, ret);
} }
#endif /* WITH_BREAKING_CHANGES */ #endif /* WITH_BREAKING_CHANGES */
if (name_given && !valid_remote(ret)) if (name_given && !valid_remote(ret))
@ -790,35 +806,33 @@ remotes_remote_get_1(struct remote_state *remote_state, const char *name,
} }
static inline struct remote * static inline struct remote *
remotes_remote_get(struct remote_state *remote_state, const char *name) remotes_remote_get(struct repository *repo, const char *name)
{ {
return remotes_remote_get_1(remote_state, name, return remotes_remote_get_1(repo, name, remotes_remote_for_branch);
remotes_remote_for_branch);
} }
struct remote *remote_get(const char *name) struct remote *remote_get(const char *name)
{ {
read_config(the_repository, 0); read_config(the_repository, 0);
return remotes_remote_get(the_repository->remote_state, name); return remotes_remote_get(the_repository, name);
} }
struct remote *remote_get_early(const char *name) struct remote *remote_get_early(const char *name)
{ {
read_config(the_repository, 1); read_config(the_repository, 1);
return remotes_remote_get(the_repository->remote_state, name); return remotes_remote_get(the_repository, name);
} }
static inline struct remote * static inline struct remote *
remotes_pushremote_get(struct remote_state *remote_state, const char *name) remotes_pushremote_get(struct repository *repo, const char *name)
{ {
return remotes_remote_get_1(remote_state, name, return remotes_remote_get_1(repo, name, remotes_pushremote_for_branch);
remotes_pushremote_for_branch);
} }
struct remote *pushremote_get(const char *name) struct remote *pushremote_get(const char *name)
{ {
read_config(the_repository, 0); read_config(the_repository, 0);
return remotes_pushremote_get(the_repository->remote_state, name); return remotes_pushremote_get(the_repository, name);
} }
int remote_is_configured(struct remote *remote, int in_repo) int remote_is_configured(struct remote *remote, int in_repo)
@ -1722,7 +1736,7 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
} }
} }
static void set_merge(struct remote_state *remote_state, struct branch *ret) static void set_merge(struct repository *repo, struct branch *ret)
{ {
struct remote *remote; struct remote *remote;
char *ref; char *ref;
@ -1731,52 +1745,80 @@ static void set_merge(struct remote_state *remote_state, struct branch *ret)
if (!ret) if (!ret)
return; /* no branch */ return; /* no branch */
if (ret->merge) if (ret->set_merge)
return; /* already run */ return; /* already run */
if (!ret->remote_name || !ret->merge_nr) { if (!ret->remote_name || !ret->merge_nr) {
/* /*
* no merge config; let's make sure we don't confuse callers * no merge config; let's make sure we don't confuse callers
* with a non-zero merge_nr but a NULL merge * with a non-zero merge_nr but a NULL merge
*/ */
ret->merge_nr = 0; merge_clear(ret);
return; return;
} }
ret->set_merge = 1;
remote = remotes_remote_get(remote_state, ret->remote_name); remote = remotes_remote_get(repo, ret->remote_name);
CALLOC_ARRAY(ret->merge, ret->merge_nr);
for (i = 0; i < ret->merge_nr; i++) { for (i = 0; i < ret->merge_nr; i++) {
ret->merge[i] = xcalloc(1, sizeof(**ret->merge));
ret->merge[i]->src = xstrdup(ret->merge_name[i]);
if (!remote_find_tracking(remote, ret->merge[i]) || if (!remote_find_tracking(remote, ret->merge[i]) ||
strcmp(ret->remote_name, ".")) strcmp(ret->remote_name, "."))
continue; continue;
if (repo_dwim_ref(the_repository, ret->merge_name[i], if (repo_dwim_ref(repo, ret->merge[i]->src,
strlen(ret->merge_name[i]), &oid, &ref, strlen(ret->merge[i]->src), &oid, &ref,
0) == 1) 0) == 1)
ret->merge[i]->dst = ref; ret->merge[i]->dst = ref;
else else
ret->merge[i]->dst = xstrdup(ret->merge_name[i]); ret->merge[i]->dst = xstrdup(ret->merge[i]->src);
} }
} }
static struct branch *repo_branch_get(struct repository *repo, const char *name)
{
struct branch *ret;
read_config(repo, 0);
if (!name || !*name || !strcmp(name, "HEAD"))
ret = repo->remote_state->current_branch;
else
ret = make_branch(repo->remote_state, name,
strlen(name));
set_merge(repo, ret);
return ret;
}
struct branch *branch_get(const char *name) struct branch *branch_get(const char *name)
{ {
struct branch *ret; return repo_branch_get(the_repository, name);
}
read_config(the_repository, 0); const char *repo_default_remote(struct repository *repo)
if (!name || !*name || !strcmp(name, "HEAD")) {
ret = the_repository->remote_state->current_branch; struct branch *branch;
else
ret = make_branch(the_repository->remote_state, name, read_config(repo, 0);
strlen(name)); branch = repo_branch_get(repo, "HEAD");
set_merge(the_repository->remote_state, ret);
return ret; return remotes_remote_for_branch(repo->remote_state, branch, NULL);
}
const char *repo_remote_from_url(struct repository *repo, const char *url)
{
read_config(repo, 0);
for (int i = 0; i < repo->remote_state->remotes_nr; i++) {
struct remote *remote = repo->remote_state->remotes[i];
if (!remote)
continue;
if (remote_has_url(remote, url))
return remote->name;
}
return NULL;
} }
int branch_has_merge_config(struct branch *branch) int branch_has_merge_config(struct branch *branch)
{ {
return branch && !!branch->merge; return branch && branch->set_merge;
} }
int branch_merge_matches(struct branch *branch, int branch_merge_matches(struct branch *branch,
@ -1841,13 +1883,14 @@ static const char *tracking_for_push_dest(struct remote *remote,
return ret; return ret;
} }
static const char *branch_get_push_1(struct remote_state *remote_state, static const char *branch_get_push_1(struct repository *repo,
struct branch *branch, struct strbuf *err) struct branch *branch, struct strbuf *err)
{ {
struct remote_state *remote_state = repo->remote_state;
struct remote *remote; struct remote *remote;
remote = remotes_remote_get( remote = remotes_remote_get(
remote_state, repo,
remotes_pushremote_for_branch(remote_state, branch, NULL)); remotes_pushremote_for_branch(remote_state, branch, NULL));
if (!remote) if (!remote)
return error_buf(err, return error_buf(err,
@ -1914,7 +1957,7 @@ const char *branch_get_push(struct branch *branch, struct strbuf *err)
if (!branch->push_tracking_ref) if (!branch->push_tracking_ref)
branch->push_tracking_ref = branch_get_push_1( branch->push_tracking_ref = branch_get_push_1(
the_repository->remote_state, branch, err); the_repository, branch, err);
return branch->push_tracking_ref; return branch->push_tracking_ref;
} }

View File

@ -9,6 +9,7 @@
struct option; struct option;
struct transport_ls_refs_options; struct transport_ls_refs_options;
struct repository;
/** /**
* The API gives access to the configuration related to remotes. It handles * The API gives access to the configuration related to remotes. It handles
@ -315,8 +316,8 @@ struct branch {
char *pushremote_name; char *pushremote_name;
/* An array of the "merge" lines in the configuration. */ /* True if set_merge() has been called to finalize the merge array */
const char **merge_name; int set_merge;
/** /**
* An array of the struct refspecs used for the merge lines. That is, * An array of the struct refspecs used for the merge lines. That is,
@ -338,6 +339,9 @@ const char *remote_for_branch(struct branch *branch, int *explicit);
const char *pushremote_for_branch(struct branch *branch, int *explicit); const char *pushremote_for_branch(struct branch *branch, int *explicit);
char *remote_ref_for_branch(struct branch *branch, int for_push); char *remote_ref_for_branch(struct branch *branch, int for_push);
const char *repo_default_remote(struct repository *repo);
const char *repo_remote_from_url(struct repository *repo, const char *url);
/* returns true if the given branch has merge configuration given. */ /* returns true if the given branch has merge configuration given. */
int branch_has_merge_config(struct branch *branch); int branch_has_merge_config(struct branch *branch);

View File

@ -235,18 +235,6 @@ in_component:
return 0; return 0;
} }
static int starts_with_dot_slash(const char *const path)
{
return path_match_flags(path, PATH_MATCH_STARTS_WITH_DOT_SLASH |
PATH_MATCH_XPLATFORM);
}
static int starts_with_dot_dot_slash(const char *const path)
{
return path_match_flags(path, PATH_MATCH_STARTS_WITH_DOT_DOT_SLASH |
PATH_MATCH_XPLATFORM);
}
static int submodule_url_is_relative(const char *url) static int submodule_url_is_relative(const char *url)
{ {
return starts_with_dot_slash(url) || starts_with_dot_dot_slash(url); return starts_with_dot_slash(url) || starts_with_dot_dot_slash(url);

View File

@ -1137,6 +1137,67 @@ test_expect_success 'setup clean recursive superproject' '
git clone --recurse-submodules top top-clean git clone --recurse-submodules top top-clean
' '
test_expect_success 'submodule update with multiple remotes' '
test_when_finished "rm -fr top-cloned" &&
cp -r top-clean top-cloned &&
# Create a commit in each repo, starting with bottom
test_commit -C bottom multiple_remote_commit &&
# Create middle commit
git -C middle/bottom fetch &&
git -C middle/bottom checkout -f FETCH_HEAD &&
git -C middle add bottom &&
git -C middle commit -m "multiple_remote_commit" &&
# Create top commit
git -C top/middle fetch &&
git -C top/middle checkout -f FETCH_HEAD &&
git -C top add middle &&
git -C top commit -m "multiple_remote_commit" &&
# rename the submodule remote
git -C top-cloned/middle remote rename origin upstream &&
# Add another remote
git -C top-cloned/middle remote add other bogus &&
# Make the update of "middle" a no-op, otherwise we error out
# because of its unmerged state
test_config -C top-cloned submodule.middle.update !true &&
git -C top-cloned submodule update --recursive 2>actual.err &&
cat >expect.err <<-\EOF &&
EOF
test_cmp expect.err actual.err
'
test_expect_success 'submodule update with renamed remote' '
test_when_finished "rm -fr top-cloned" &&
cp -r top-clean top-cloned &&
# Create a commit in each repo, starting with bottom
test_commit -C bottom rename_commit &&
# Create middle commit
git -C middle/bottom fetch &&
git -C middle/bottom checkout -f FETCH_HEAD &&
git -C middle add bottom &&
git -C middle commit -m "rename_commit" &&
# Create top commit
git -C top/middle fetch &&
git -C top/middle checkout -f FETCH_HEAD &&
git -C top add middle &&
git -C top commit -m "rename_commit" &&
# rename the submodule remote
git -C top-cloned/middle remote rename origin upstream &&
# Make the update of "middle" a no-op, otherwise we error out
# because of its unmerged state
test_config -C top-cloned submodule.middle.update !true &&
git -C top-cloned submodule update --recursive 2>actual.err &&
cat >expect.err <<-\EOF &&
EOF
test_cmp expect.err actual.err
'
test_expect_success 'submodule update should skip unmerged submodules' ' test_expect_success 'submodule update should skip unmerged submodules' '
test_when_finished "rm -fr top-cloned" && test_when_finished "rm -fr top-cloned" &&
cp -r top-clean top-cloned && cp -r top-clean top-cloned &&