mirror of
https://github.com/git/git.git
synced 2026-06-19 15:39:47 +02:00
Merge branch 'mh/fetch-follow-remote-head-config' into jch
The `fetch.followRemoteHEAD` configuration variable has been added to provide a default for the per-remote `remote.<name>.followRemoteHEAD` setting. * mh/fetch-follow-remote-head-config: fetch: fixup a misaligned comment fetch: add configuration variable fetch.followRemoteHEAD fetch: refactor do_fetch handling of followRemoteHEAD fetch: rename function report_set_head t5510: cleanup remote in followRemoteHEAD dangling ref test doc: explain fetchRemoteHEADWarn advice fetch: fixup set_head advice for warn-if-not-branch
This commit is contained in:
@@ -48,6 +48,10 @@ all advice messages.
|
||||
to create a local branch after the fact.
|
||||
diverging::
|
||||
Shown when a fast-forward is not possible.
|
||||
fetchRemoteHEADWarn::
|
||||
Shown when linkgit:git-fetch[1] reveals that a remote `HEAD`
|
||||
differs from what is set locally and the user has opted into
|
||||
receiving a warning in this situation.
|
||||
fetchShowForcedUpdates::
|
||||
Shown when linkgit:git-fetch[1] takes a long time
|
||||
to calculate forced updates after ref updates, or to warn
|
||||
|
||||
@@ -126,3 +126,22 @@ the new bundle URI.
|
||||
The creation token values are chosen by the provider serving the specific
|
||||
bundle URI. If you modify the URI at `fetch.bundleURI`, then be sure to
|
||||
remove the value for the `fetch.bundleCreationToken` value before fetching.
|
||||
|
||||
`fetch.followRemoteHEAD`::
|
||||
When fetching using a default refspec, this setting determines how to handle
|
||||
differences between a fetched remote's `HEAD` and the local
|
||||
`remotes/<name>/HEAD` symbolic-ref. Its value is one of
|
||||
+
|
||||
--
|
||||
`create`;;
|
||||
Create `remotes/<name>/HEAD` if a ref exists on the remote, but not locally.
|
||||
An existing symbolic-ref will not be touched. This is the default value.
|
||||
`warn`;;
|
||||
Display a warning if the remote advertises a different `HEAD` than what is
|
||||
set locally. Behaves like "create" if the local symbolic-ref doesn't exist.
|
||||
`always`;;
|
||||
Silently update `remotes/<name>/HEAD` whenever the remote advertises a new
|
||||
value.
|
||||
`never`;;
|
||||
Never create or modify the `remotes/<name>/HEAD` symbolic-ref.
|
||||
--
|
||||
|
||||
@@ -166,15 +166,12 @@ Blank values signal to ignore all previous values, allowing a reset of
|
||||
the list from broader config scenarios.
|
||||
|
||||
remote.<name>.followRemoteHEAD::
|
||||
How linkgit:git-fetch[1] should handle updates to `remotes/<name>/HEAD`
|
||||
when fetching using the configured refspecs of a remote.
|
||||
The default value is "create", which will create `remotes/<name>/HEAD`
|
||||
if it exists on the remote, but not locally; this will not touch an
|
||||
already existing local reference. Setting it to "warn" will print
|
||||
a message if the remote has a different value than the local one;
|
||||
in case there is no local reference, it behaves like "create".
|
||||
A variant on "warn" is "warn-if-not-$branch", which behaves like
|
||||
"warn", but if `HEAD` on the remote is `$branch` it will be silent.
|
||||
Setting it to "always" will silently update `remotes/<name>/HEAD` to
|
||||
the value on the remote. Finally, setting it to "never" will never
|
||||
change or create the local reference.
|
||||
When fetching this remote using its default refspec, this setting determines
|
||||
how to handle differences between the remote's `HEAD` and the local
|
||||
`remotes/<name>/HEAD` symbolic-ref. Overrides the value of
|
||||
`fetch.followRemoteHEAD`. See `fetch.followRemoteHEAD` for a description of
|
||||
accepted values.
|
||||
+
|
||||
In addition to the values supported by `fetch.followRemoteHEAD`, this setting
|
||||
may also take on the value "warn-if-not-`$branch`", which behaves like "warn",
|
||||
but ignores the warning if the remote's `HEAD` is `remotes/<name>/$branch`.
|
||||
|
||||
+37
-12
@@ -103,6 +103,7 @@ static struct string_list negotiation_include = STRING_LIST_INIT_NODUP;
|
||||
|
||||
struct fetch_config {
|
||||
enum display_format display_format;
|
||||
enum follow_remote_head_settings follow_remote_head;
|
||||
int all;
|
||||
int prune;
|
||||
int prune_tags;
|
||||
@@ -173,6 +174,19 @@ static int git_fetch_config(const char *k, const char *v,
|
||||
"fetch.output", v);
|
||||
}
|
||||
|
||||
if (!strcmp(k, "fetch.followremotehead")) {
|
||||
if (!v)
|
||||
return config_error_nonbool(k);
|
||||
else if (!strcmp(v, "never"))
|
||||
fetch_config->follow_remote_head = FOLLOW_REMOTE_NEVER;
|
||||
else if (!strcmp(v, "create"))
|
||||
fetch_config->follow_remote_head = FOLLOW_REMOTE_CREATE;
|
||||
else if (!strcmp(v, "warn"))
|
||||
fetch_config->follow_remote_head = FOLLOW_REMOTE_WARN;
|
||||
else if (!strcmp(v, "always"))
|
||||
fetch_config->follow_remote_head = FOLLOW_REMOTE_ALWAYS;
|
||||
}
|
||||
|
||||
return git_default_config(k, v, ctx, cb);
|
||||
}
|
||||
|
||||
@@ -1697,17 +1711,19 @@ static const char *strip_refshead(const char *name){
|
||||
static void set_head_advice_msg(const char *remote, const char *head_name)
|
||||
{
|
||||
const char message_advice_set_head[] =
|
||||
N_("Run 'git remote set-head %s %s' to follow the change, or set\n"
|
||||
"'remote.%s.followRemoteHEAD' configuration option to a different value\n"
|
||||
"if you do not want to see this message. Specifically running\n"
|
||||
"'git config set remote.%s.followRemoteHEAD warn-if-not-branch-%s'\n"
|
||||
"will disable the warning until the remote changes HEAD to something else.");
|
||||
N_("Run 'git remote set-head %s %s' to follow the change, or modify\n"
|
||||
"either of the 'remote.%s.followRemoteHEAD' or 'fetch.followRemoteHEAD'\n"
|
||||
"configuration variables to handle the situation differently.\n\n"
|
||||
|
||||
"Using this specific setting\n\n"
|
||||
" git config set remote.%s.followRemoteHEAD warn-if-not-%s\n\n"
|
||||
"will suppress the warning until the remote changes HEAD to something else.");
|
||||
|
||||
advise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head),
|
||||
remote, head_name, remote, remote, head_name);
|
||||
}
|
||||
|
||||
static void report_set_head(const char *remote, const char *head_name,
|
||||
static void warn_set_head(const char *remote, const char *head_name,
|
||||
struct strbuf *buf_prev, int updateres) {
|
||||
struct strbuf buf_prefix = STRBUF_INIT;
|
||||
const char *prev_head = NULL;
|
||||
@@ -1729,12 +1745,12 @@ static void report_set_head(const char *remote, const char *head_name,
|
||||
strbuf_release(&buf_prefix);
|
||||
}
|
||||
|
||||
static int set_head(const struct ref *remote_refs, struct remote *remote)
|
||||
static int set_head(const struct ref *remote_refs, struct remote *remote,
|
||||
int follow_remote_head)
|
||||
{
|
||||
int result = 0, create_only, baremirror, was_detached;
|
||||
struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
|
||||
b_local_head = STRBUF_INIT;
|
||||
int follow_remote_head = remote->follow_remote_head;
|
||||
const char *no_warn_branch = remote->no_warn_branch;
|
||||
char *head_name = NULL;
|
||||
struct ref *ref, *matches;
|
||||
@@ -1773,7 +1789,7 @@ static int set_head(const struct ref *remote_refs, struct remote *remote)
|
||||
strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote->name);
|
||||
strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote->name, head_name);
|
||||
}
|
||||
/* make sure it's valid */
|
||||
/* make sure it's valid */
|
||||
if (!baremirror && !refs_ref_exists(refs, b_remote_head.buf)) {
|
||||
result = 1;
|
||||
goto cleanup;
|
||||
@@ -1787,7 +1803,7 @@ static int set_head(const struct ref *remote_refs, struct remote *remote)
|
||||
if (verbosity >= 0 &&
|
||||
follow_remote_head == FOLLOW_REMOTE_WARN &&
|
||||
(!no_warn_branch || strcmp(no_warn_branch, head_name)))
|
||||
report_set_head(remote->name, head_name, &b_local_head, was_detached);
|
||||
warn_set_head(remote->name, head_name, &b_local_head, was_detached);
|
||||
|
||||
cleanup:
|
||||
free(head_name);
|
||||
@@ -1901,6 +1917,7 @@ static int do_fetch(struct transport *transport,
|
||||
struct ref_update_display_info_array display_array = { 0 };
|
||||
struct strmap rejected_refs = STRMAP_INIT;
|
||||
int summary_width = 0;
|
||||
int follow_remote_head;
|
||||
|
||||
if (tags == TAGS_DEFAULT) {
|
||||
if (transport->remote->fetch_tags == 2)
|
||||
@@ -1916,6 +1933,13 @@ static int do_fetch(struct transport *transport,
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (transport->remote->follow_remote_head)
|
||||
follow_remote_head = transport->remote->follow_remote_head;
|
||||
else if (config->follow_remote_head)
|
||||
follow_remote_head = config->follow_remote_head;
|
||||
else
|
||||
follow_remote_head = BUILTIN_FOLLOW_REMOTE_HEAD_DFLT;
|
||||
|
||||
if (rs->nr) {
|
||||
refspec_ref_prefixes(rs, &transport_ls_refs_options.ref_prefixes);
|
||||
} else {
|
||||
@@ -1924,7 +1948,7 @@ static int do_fetch(struct transport *transport,
|
||||
if (transport->remote->fetch.nr) {
|
||||
refspec_ref_prefixes(&transport->remote->fetch,
|
||||
&transport_ls_refs_options.ref_prefixes);
|
||||
if (transport->remote->follow_remote_head != FOLLOW_REMOTE_NEVER)
|
||||
if (follow_remote_head != FOLLOW_REMOTE_NEVER)
|
||||
do_set_head = 1;
|
||||
}
|
||||
if (branch && branch_has_merge_config(branch) &&
|
||||
@@ -2131,7 +2155,7 @@ static int do_fetch(struct transport *transport,
|
||||
* Way too many cases where this can go wrong so let's just
|
||||
* ignore errors and fail silently for now.
|
||||
*/
|
||||
set_head(remote_refs, transport->remote);
|
||||
set_head(remote_refs, transport->remote, follow_remote_head);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
@@ -2471,6 +2495,7 @@ int cmd_fetch(int argc,
|
||||
{
|
||||
struct fetch_config config = {
|
||||
.display_format = DISPLAY_FORMAT_FULL,
|
||||
.follow_remote_head = FOLLOW_REMOTE_UNCONFIGURED,
|
||||
.prune = -1,
|
||||
.prune_tags = -1,
|
||||
.show_forced_updates = 1,
|
||||
|
||||
@@ -62,12 +62,14 @@ struct remote_state {
|
||||
void remote_state_clear(struct remote_state *remote_state);
|
||||
struct remote_state *remote_state_new(void);
|
||||
|
||||
enum follow_remote_head_settings {
|
||||
FOLLOW_REMOTE_NEVER = -1,
|
||||
FOLLOW_REMOTE_CREATE = 0,
|
||||
FOLLOW_REMOTE_WARN = 1,
|
||||
FOLLOW_REMOTE_ALWAYS = 2,
|
||||
};
|
||||
#define BUILTIN_FOLLOW_REMOTE_HEAD_DFLT FOLLOW_REMOTE_CREATE
|
||||
enum follow_remote_head_settings {
|
||||
FOLLOW_REMOTE_UNCONFIGURED = 0,
|
||||
FOLLOW_REMOTE_NEVER,
|
||||
FOLLOW_REMOTE_CREATE,
|
||||
FOLLOW_REMOTE_WARN,
|
||||
FOLLOW_REMOTE_ALWAYS,
|
||||
};
|
||||
|
||||
struct remote {
|
||||
struct hashmap_entry ent;
|
||||
|
||||
@@ -140,6 +140,16 @@ test_expect_success "fetch test remote HEAD change" '
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success "fetch test default followRemoteHEAD never" '
|
||||
git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
|
||||
test_config -C two fetch.followRemoteHEAD "never" &&
|
||||
GIT_TRACE_PACKET=$PWD/trace.out git -C two fetch &&
|
||||
# Confirm that we do not even ask for HEAD when we are
|
||||
# not going to act on it.
|
||||
test_grep ! "ref-prefix HEAD" trace.out &&
|
||||
test_must_fail git -C two rev-parse --verify refs/remotes/origin/HEAD
|
||||
'
|
||||
|
||||
test_expect_success "fetch test followRemoteHEAD never" '
|
||||
git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
|
||||
test_config -C two remote.origin.followRemoteHEAD "never" &&
|
||||
@@ -150,6 +160,21 @@ test_expect_success "fetch test followRemoteHEAD never" '
|
||||
test_must_fail git -C two rev-parse --verify refs/remotes/origin/HEAD
|
||||
'
|
||||
|
||||
test_expect_success "fetch test default followRemoteHEAD warn no change" '
|
||||
git -C two rev-parse --verify refs/remotes/origin/other &&
|
||||
git -C two remote set-head origin other &&
|
||||
git -C two rev-parse --verify refs/remotes/origin/HEAD &&
|
||||
git -C two rev-parse --verify refs/remotes/origin/main &&
|
||||
test_config -C two fetch.followRemoteHEAD "warn" &&
|
||||
git -C two fetch >output &&
|
||||
echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
|
||||
"but we have ${SQ}other${SQ} locally." >expect &&
|
||||
test_cmp expect output &&
|
||||
head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
|
||||
branch=$(git -C two rev-parse refs/remotes/origin/other) &&
|
||||
test "z$head" = "z$branch"
|
||||
'
|
||||
|
||||
test_expect_success "fetch test followRemoteHEAD warn no change" '
|
||||
git -C two rev-parse --verify refs/remotes/origin/other &&
|
||||
git -C two remote set-head origin other &&
|
||||
@@ -165,6 +190,17 @@ test_expect_success "fetch test followRemoteHEAD warn no change" '
|
||||
test "z$head" = "z$branch"
|
||||
'
|
||||
|
||||
test_expect_success "fetch test default followRemoteHEAD warn create" '
|
||||
git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
|
||||
test_config -C two fetch.followRemoteHEAD "warn" &&
|
||||
git -C two rev-parse --verify refs/remotes/origin/main &&
|
||||
output=$(git -C two fetch) &&
|
||||
test "z" = "z$output" &&
|
||||
head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
|
||||
branch=$(git -C two rev-parse refs/remotes/origin/main) &&
|
||||
test "z$head" = "z$branch"
|
||||
'
|
||||
|
||||
test_expect_success "fetch test followRemoteHEAD warn create" '
|
||||
git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
|
||||
test_config -C two remote.origin.followRemoteHEAD "warn" &&
|
||||
@@ -176,6 +212,18 @@ test_expect_success "fetch test followRemoteHEAD warn create" '
|
||||
test "z$head" = "z$branch"
|
||||
'
|
||||
|
||||
test_expect_success "fetch test default followRemoteHEAD warn detached" '
|
||||
git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
|
||||
git -C two update-ref refs/remotes/origin/HEAD HEAD &&
|
||||
HEAD=$(git -C two log --pretty="%H") &&
|
||||
test_config -C two fetch.followRemoteHEAD "warn" &&
|
||||
git -C two fetch >output &&
|
||||
echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
|
||||
"but we have a detached HEAD pointing to" \
|
||||
"${SQ}${HEAD}${SQ} locally." >expect &&
|
||||
test_cmp expect output
|
||||
'
|
||||
|
||||
test_expect_success "fetch test followRemoteHEAD warn detached" '
|
||||
git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
|
||||
git -C two update-ref refs/remotes/origin/HEAD HEAD &&
|
||||
@@ -188,6 +236,19 @@ test_expect_success "fetch test followRemoteHEAD warn detached" '
|
||||
test_cmp expect output
|
||||
'
|
||||
|
||||
test_expect_success "fetch test default followRemoteHEAD warn quiet" '
|
||||
git -C two rev-parse --verify refs/remotes/origin/other &&
|
||||
git -C two remote set-head origin other &&
|
||||
git -C two rev-parse --verify refs/remotes/origin/HEAD &&
|
||||
git -C two rev-parse --verify refs/remotes/origin/main &&
|
||||
test_config -C two fetch.followRemoteHEAD "warn" &&
|
||||
output=$(git -C two fetch --quiet) &&
|
||||
test "z" = "z$output" &&
|
||||
head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
|
||||
branch=$(git -C two rev-parse refs/remotes/origin/other) &&
|
||||
test "z$head" = "z$branch"
|
||||
'
|
||||
|
||||
test_expect_success "fetch test followRemoteHEAD warn quiet" '
|
||||
git -C two rev-parse --verify refs/remotes/origin/other &&
|
||||
git -C two remote set-head origin other &&
|
||||
@@ -229,6 +290,18 @@ test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is di
|
||||
test "z$head" = "z$branch"
|
||||
'
|
||||
|
||||
test_expect_success "fetch test default followRemoteHEAD always" '
|
||||
git -C two rev-parse --verify refs/remotes/origin/other &&
|
||||
git -C two remote set-head origin other &&
|
||||
git -C two rev-parse --verify refs/remotes/origin/HEAD &&
|
||||
git -C two rev-parse --verify refs/remotes/origin/main &&
|
||||
test_config -C two fetch.followRemoteHEAD "always" &&
|
||||
git -C two fetch &&
|
||||
head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
|
||||
branch=$(git -C two rev-parse refs/remotes/origin/main) &&
|
||||
test "z$head" = "z$branch"
|
||||
'
|
||||
|
||||
test_expect_success "fetch test followRemoteHEAD always" '
|
||||
git -C two rev-parse --verify refs/remotes/origin/other &&
|
||||
git -C two remote set-head origin other &&
|
||||
@@ -241,6 +314,28 @@ test_expect_success "fetch test followRemoteHEAD always" '
|
||||
test "z$head" = "z$branch"
|
||||
'
|
||||
|
||||
test_expect_success 'per-remote followRemoteHEAD takes priority over fetch default' '
|
||||
git -C two rev-parse --verify refs/remotes/origin/other &&
|
||||
git -C two remote set-head origin other &&
|
||||
git -C two rev-parse --verify refs/remotes/origin/HEAD &&
|
||||
git -C two rev-parse --verify refs/remotes/origin/main &&
|
||||
test_config -C two fetch.followRemoteHEAD "never" &&
|
||||
test_config -C two remote.origin.followRemoteHEAD "always" &&
|
||||
git -C two fetch &&
|
||||
head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
|
||||
branch=$(git -C two rev-parse refs/remotes/origin/main) &&
|
||||
test "z$head" = "z$branch"
|
||||
'
|
||||
|
||||
test_expect_success 'default followRemoteHEAD does not kick in with refspecs' '
|
||||
git -C two remote set-head origin other &&
|
||||
test_config -C two fetch.followRemoteHEAD always &&
|
||||
git -C two fetch origin refs/heads/main:refs/remotes/origin/main &&
|
||||
echo refs/remotes/origin/other >expect &&
|
||||
git -C two symbolic-ref refs/remotes/origin/HEAD >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'followRemoteHEAD does not kick in with refspecs' '
|
||||
git -C two remote set-head origin other &&
|
||||
test_config -C two remote.origin.followRemoteHEAD always &&
|
||||
@@ -250,7 +345,18 @@ test_expect_success 'followRemoteHEAD does not kick in with refspecs' '
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'default followRemoteHEAD create does not overwrite dangling symref' '
|
||||
test_when_finished "git -C two remote remove custom-head" &&
|
||||
git -C two remote add -m does-not-exist custom-head ../one &&
|
||||
test_config -C two fetch.followRemoteHEAD create &&
|
||||
git -C two fetch custom-head &&
|
||||
echo refs/remotes/custom-head/does-not-exist >expect &&
|
||||
git -C two symbolic-ref refs/remotes/custom-head/HEAD >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'followRemoteHEAD create does not overwrite dangling symref' '
|
||||
test_when_finished "git -C two remote remove custom-head" &&
|
||||
git -C two remote add -m does-not-exist custom-head ../one &&
|
||||
test_config -C two remote.custom-head.followRemoteHEAD create &&
|
||||
git -C two fetch custom-head &&
|
||||
|
||||
Reference in New Issue
Block a user