* [PATCH v7 2/6] remote set-head: add new variable for readability
2024-10-12 23:03 ` [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
@ 2024-10-12 23:03 ` Bence Ferdinandy
2024-10-12 23:03 ` [PATCH v7 3/6] remote set-head: better output for --auto Bence Ferdinandy
` (5 subsequent siblings)
6 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-12 23:03 UTC (permalink / raw)
To: git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano, Bence Ferdinandy
Instead of calling get_main_ref_store(the_repository) multiple times,
call it once and store in a new refs variable. Although this change
probably offers some performance benefits, the main purpose is to
shorten the line lengths of function calls using this variable for
better readability.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v5: new patch (split from the next patch as a preparatory step)
v6: no change
v7: - change commit prefix to be more in line with project standards
builtin/remote.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index d8ff440027..353ffd2c43 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1404,6 +1404,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
int i, opt_a = 0, opt_d = 0, result = 0;
struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
char *head_name = NULL;
+ struct ref_store *refs = get_main_ref_store(the_repository);
struct option options[] = {
OPT_BOOL('a', "auto", &opt_a,
@@ -1434,7 +1435,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
head_name = xstrdup(states.heads.items[0].string);
free_remote_ref_states(&states);
} else if (opt_d && !opt_a && argc == 1) {
- if (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF))
+ if (refs_delete_ref(refs, NULL, buf.buf, NULL, REF_NO_DEREF))
result |= error(_("Could not delete %s"), buf.buf);
} else
usage_with_options(builtin_remote_sethead_usage, options);
@@ -1442,9 +1443,9 @@ static int set_head(int argc, const char **argv, const char *prefix)
if (head_name) {
strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name);
/* make sure it's valid */
- if (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))
+ if (!refs_ref_exists(refs, buf2.buf))
result |= error(_("Not a valid ref: %s"), buf2.buf);
- else if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote set-head", NULL))
+ else if (refs_update_symref(refs, buf.buf, buf2.buf, "remote set-head", NULL))
result |= error(_("Could not setup %s"), buf.buf);
else if (opt_a)
printf("%s/HEAD set to %s\n", argv[0], head_name);
--
2.47.0.7.g072c39eddb.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v7 3/6] remote set-head: better output for --auto
2024-10-12 23:03 ` [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
2024-10-12 23:03 ` [PATCH v7 2/6] remote set-head: add new variable for readability Bence Ferdinandy
@ 2024-10-12 23:03 ` Bence Ferdinandy
2024-10-12 23:03 ` [PATCH v7 4/6] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
` (4 subsequent siblings)
6 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-12 23:03 UTC (permalink / raw)
To: git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano, Bence Ferdinandy
Currently, set-head --auto will print a message saying "remote/HEAD set
to branch", which implies something was changed.
Change the output of --auto, so the output actually reflects what was
done: a) set a previously unset HEAD, b) change HEAD because remote
changed or c) no updates. As a fourth output, if HEAD is changed from
a previous value that was not a remote branch, explicitly call attention
to this fact.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v1-v2:
was RFC in https://lore.kernel.org/git/20240910203835.2288291-1-bence@ferdinandy.com/
v3:
This patch was originally sent along when I thought set-head was
going to be invoked by fetch, but the discussion on the RFC
concluded that it should be not. This opened the possibility to make
it more explicit.
Note: although I feel both things the patch does are really just
cosmetic, an argument could be made for breaking it into two, one
for the no-op part and one for the --auto print update.
Was sent in:
https://lore.kernel.org/git/20240915221055.904107-1-bence@ferdinandy.com/
v4:
- changes are now handled atomically via the ref update transaction
- outputs have changed along the lines of Junio's suggestion
- minor refactor to set_head for improved legibility
v5: - the minor refactor has been split out into its own patch
v6: - fixed uninitialized prev_head
- fixed case of unusual previous target
- fixed a test that would have been actually broken at this patch
(the output was only correct with the later update to fetch)
- added 4 tests for the 4 output cases
v7: - change commit prefix to be more in line with project standards
- fixed tests to also work with the reftable backend
- renamed report function, fixed style issue with checking buf len
- fixed not releasing an strbuf
builtin/remote.c | 35 ++++++++++++++++++++++++++++----
t/t5505-remote.sh | 51 ++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 81 insertions(+), 5 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 353ffd2c43..2b6948439f 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1399,10 +1399,35 @@ static int show(int argc, const char **argv, const char *prefix)
return result;
}
+static void report_set_head_auto(const char *remote, const char *head_name,
+ struct strbuf *buf_prev) {
+ struct strbuf buf_prefix = STRBUF_INIT;
+ const char *prev_head = NULL;
+
+ strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
+ skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);
+
+ if (prev_head && !strcmp(prev_head, head_name))
+ printf("'%s/HEAD' is unchanged and points to '%s'\n",
+ remote, head_name);
+ else if (prev_head)
+ printf("'%s/HEAD' has changed from '%s' and now points to '%s'\n",
+ remote, prev_head, head_name);
+ else if (!buf_prev->len)
+ printf("'%s/HEAD' is now created and points to '%s'\n",
+ remote, head_name);
+ else
+ printf("'%s/HEAD' used to point to '%s' "
+ "(which is not a remote branch), but now points to '%s'\n",
+ remote, buf_prev->buf, head_name);
+ strbuf_release(&buf_prefix);
+}
+
static int set_head(int argc, const char **argv, const char *prefix)
{
int i, opt_a = 0, opt_d = 0, result = 0;
- struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
+ struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT,
+ buf_prev = STRBUF_INIT;
char *head_name = NULL;
struct ref_store *refs = get_main_ref_store(the_repository);
@@ -1445,15 +1470,17 @@ static int set_head(int argc, const char **argv, const char *prefix)
/* make sure it's valid */
if (!refs_ref_exists(refs, buf2.buf))
result |= error(_("Not a valid ref: %s"), buf2.buf);
- else if (refs_update_symref(refs, buf.buf, buf2.buf, "remote set-head", NULL))
+ else if (refs_update_symref(refs, buf.buf, buf2.buf, "remote set-head", &buf_prev))
result |= error(_("Could not setup %s"), buf.buf);
- else if (opt_a)
- printf("%s/HEAD set to %s\n", argv[0], head_name);
+ else if (opt_a) {
+ report_set_head_auto(argv[0], head_name, &buf_prev);
+ }
free(head_name);
}
strbuf_release(&buf);
strbuf_release(&buf2);
+ strbuf_release(&buf_prev);
return result;
}
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 532035933f..d99cd1d0aa 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -429,12 +429,51 @@ test_expect_success 'set-head --auto' '
)
'
+test_expect_success 'set-head --auto detects creation' '
+ (
+ cd test &&
+ git symbolic-ref -d refs/remotes/origin/HEAD &&
+ git remote set-head --auto origin >output &&
+ echo "'\''origin/HEAD'\'' is now created and points to '\''main'\''" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects no change' '
+ (
+ cd test &&
+ git remote set-head --auto origin >output &&
+ echo "'\''origin/HEAD'\'' is unchanged and points to '\''main'\''" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects change' '
+ (
+ cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/ahead &&
+ git remote set-head --auto origin >output &&
+ echo "'\''origin/HEAD'\'' has changed from '\''ahead'\'' and now points to '\''main'\''" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects strange ref' '
+ (
+ cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD refs/heads/main &&
+ git remote set-head --auto origin >output &&
+ echo "'\''origin/HEAD'\'' used to point to '\''refs/heads/main'\'' (which is not a remote branch), but now points to '\''main'\''" >expect &&
+ test_cmp expect output
+ )
+'
+
test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
(
cd test &&
git fetch two "refs/heads/*:refs/remotes/two/*" &&
git remote set-head --auto two >output 2>&1 &&
- echo "two/HEAD set to main" >expect &&
+ echo "'\''two/HEAD'\'' is now created and points to '\''main'\''" >expect &&
test_cmp expect output
)
'
@@ -453,6 +492,16 @@ test_expect_success 'set-head explicit' '
)
'
+test_expect_success 'set-head --auto reports change' '
+ (
+ cd test &&
+ git remote set-head origin side2 &&
+ git remote set-head --auto origin >output 2>&1 &&
+ echo "'\''origin/HEAD'\'' has changed from '\''side2'\'' and now points to '\''main'\''" >expect &&
+ test_cmp expect output
+ )
+'
+
cat >test/expect <<EOF
Pruning origin
URL: $(pwd)/one
--
2.47.0.7.g072c39eddb.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v7 4/6] refs: add TRANSACTION_CREATE_EXISTS error
2024-10-12 23:03 ` [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
2024-10-12 23:03 ` [PATCH v7 2/6] remote set-head: add new variable for readability Bence Ferdinandy
2024-10-12 23:03 ` [PATCH v7 3/6] remote set-head: better output for --auto Bence Ferdinandy
@ 2024-10-12 23:03 ` Bence Ferdinandy
2024-10-13 14:03 ` Phillip Wood
2024-10-12 23:03 ` [PATCH v7 5/6] refs: add create_only option to refs_update_symref Bence Ferdinandy
` (3 subsequent siblings)
6 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-12 23:03 UTC (permalink / raw)
To: git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano, Bence Ferdinandy
Currently there is only one special error for transaction, for when
there is a naming conflict, all other errors are dumped under a generic
error. Add a new special error case for when the caller requests the
reference to be updated only when it does not yet exist and the
reference actually does exist.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: no change
v6: no change
v7: - change commit prefix to be more in line with project standards
- changed error checking to Karthik's suggestion
refs.h | 4 +++-
refs/files-backend.c | 24 ++++++++++++++++--------
refs/reftable-backend.c | 6 ++++--
3 files changed, 23 insertions(+), 11 deletions(-)
diff --git a/refs.h b/refs.h
index b09a3a4384..c83b1ec76e 100644
--- a/refs.h
+++ b/refs.h
@@ -759,8 +759,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,
/* Naming conflict (for example, the ref names A and A/B conflict). */
#define TRANSACTION_NAME_CONFLICT -1
+/* When only creation was requested, but the ref already exists. */
+#define TRANSACTION_CREATE_EXISTS -2
/* All other errors. */
-#define TRANSACTION_GENERIC_ERROR -2
+#define TRANSACTION_GENERIC_ERROR -3
/*
* Perform the preparatory stages of committing `transaction`. Acquire
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 0824c0b8a9..e743ec44b5 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2502,14 +2502,18 @@ static int split_symref_update(struct ref_update *update,
static int check_old_oid(struct ref_update *update, struct object_id *oid,
struct strbuf *err)
{
+ int ret = TRANSACTION_GENERIC_ERROR;
+
if (!(update->flags & REF_HAVE_OLD) ||
oideq(oid, &update->old_oid))
return 0;
- if (is_null_oid(&update->old_oid))
+ if (is_null_oid(&update->old_oid)) {
strbuf_addf(err, "cannot lock ref '%s': "
"reference already exists",
ref_update_original_update_refname(update));
+ ret = TRANSACTION_CREATE_EXISTS;
+ }
else if (is_null_oid(oid))
strbuf_addf(err, "cannot lock ref '%s': "
"reference is missing but expected %s",
@@ -2522,7 +2526,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,
oid_to_hex(oid),
oid_to_hex(&update->old_oid));
- return -1;
+ return ret;
}
/*
@@ -2602,9 +2606,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
- } else if (check_old_oid(update, &lock->old_oid, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto out;
+ } else {
+ ret = check_old_oid(update, &lock->old_oid, err);
+ if (ret) {
+ goto out;
+ }
}
} else {
/*
@@ -2635,9 +2641,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,
update->old_target);
ret = TRANSACTION_GENERIC_ERROR;
goto out;
- } else if (check_old_oid(update, &lock->old_oid, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto out;
+ } else {
+ ret = check_old_oid(update, &lock->old_oid, err);
+ if (ret) {
+ goto out;
+ }
}
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 3c96fbf66f..ebf8e57fbc 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1206,10 +1206,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
goto done;
}
} else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) {
- if (is_null_oid(&u->old_oid))
+ ret = TRANSACTION_NAME_CONFLICT;
+ if (is_null_oid(&u->old_oid)) {
strbuf_addf(err, _("cannot lock ref '%s': "
"reference already exists"),
ref_update_original_update_refname(u));
+ ret = TRANSACTION_CREATE_EXISTS;
+ }
else if (is_null_oid(¤t_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
"reference is missing but expected %s"),
@@ -1221,7 +1224,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
ref_update_original_update_refname(u),
oid_to_hex(¤t_oid),
oid_to_hex(&u->old_oid));
- ret = -1;
goto done;
}
--
2.47.0.7.g072c39eddb.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v7 4/6] refs: add TRANSACTION_CREATE_EXISTS error
2024-10-12 23:03 ` [PATCH v7 4/6] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
@ 2024-10-13 14:03 ` Phillip Wood
2024-10-13 20:52 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Phillip Wood @ 2024-10-13 14:03 UTC (permalink / raw)
To: Bence Ferdinandy, git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano
Hi Bence
On 13/10/2024 00:03, Bence Ferdinandy wrote:
> Currently there is only one special error for transaction, for when
> there is a naming conflict, all other errors are dumped under a generic
> error. Add a new special error case for when the caller requests the
> reference to be updated only when it does not yet exist and the
> reference actually does exist.
This looks like useful improvement. Are the changes to
reftable-backend.c correct - it looks like where it previously returned
TRANSACTION_GENERIC_ERR it now returns TRANSACTION_NAME_CONFLICT which I
think is used to indicate a file/directory conflict (e.g. trying to
create refs/heads/topic/one when refs/heads/topic exists)
Best Wishes
Phillip
> Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
> ---
>
> Notes:
> v4: new patch
> v5: no change
> v6: no change
>
> v7: - change commit prefix to be more in line with project standards
> - changed error checking to Karthik's suggestion
>
> refs.h | 4 +++-
> refs/files-backend.c | 24 ++++++++++++++++--------
> refs/reftable-backend.c | 6 ++++--
> 3 files changed, 23 insertions(+), 11 deletions(-)
>
> diff --git a/refs.h b/refs.h
> index b09a3a4384..c83b1ec76e 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -759,8 +759,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,
>
> /* Naming conflict (for example, the ref names A and A/B conflict). */
> #define TRANSACTION_NAME_CONFLICT -1
> +/* When only creation was requested, but the ref already exists. */
> +#define TRANSACTION_CREATE_EXISTS -2
> /* All other errors. */
> -#define TRANSACTION_GENERIC_ERROR -2
> +#define TRANSACTION_GENERIC_ERROR -3
>
> /*
> * Perform the preparatory stages of committing `transaction`. Acquire
> diff --git a/refs/files-backend.c b/refs/files-backend.c
> index 0824c0b8a9..e743ec44b5 100644
> --- a/refs/files-backend.c
> +++ b/refs/files-backend.c
> @@ -2502,14 +2502,18 @@ static int split_symref_update(struct ref_update *update,
> static int check_old_oid(struct ref_update *update, struct object_id *oid,
> struct strbuf *err)
> {
> + int ret = TRANSACTION_GENERIC_ERROR;
> +
> if (!(update->flags & REF_HAVE_OLD) ||
> oideq(oid, &update->old_oid))
> return 0;
>
> - if (is_null_oid(&update->old_oid))
> + if (is_null_oid(&update->old_oid)) {
> strbuf_addf(err, "cannot lock ref '%s': "
> "reference already exists",
> ref_update_original_update_refname(update));
> + ret = TRANSACTION_CREATE_EXISTS;
> + }
> else if (is_null_oid(oid))
> strbuf_addf(err, "cannot lock ref '%s': "
> "reference is missing but expected %s",
> @@ -2522,7 +2526,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,
> oid_to_hex(oid),
> oid_to_hex(&update->old_oid));
>
> - return -1;
> + return ret;
> }
>
> /*
> @@ -2602,9 +2606,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,
> ret = TRANSACTION_GENERIC_ERROR;
> goto out;
> }
> - } else if (check_old_oid(update, &lock->old_oid, err)) {
> - ret = TRANSACTION_GENERIC_ERROR;
> - goto out;
> + } else {
> + ret = check_old_oid(update, &lock->old_oid, err);
> + if (ret) {
> + goto out;
> + }
> }
> } else {
> /*
> @@ -2635,9 +2641,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,
> update->old_target);
> ret = TRANSACTION_GENERIC_ERROR;
> goto out;
> - } else if (check_old_oid(update, &lock->old_oid, err)) {
> - ret = TRANSACTION_GENERIC_ERROR;
> - goto out;
> + } else {
> + ret = check_old_oid(update, &lock->old_oid, err);
> + if (ret) {
> + goto out;
> + }
> }
>
> /*
> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
> index 3c96fbf66f..ebf8e57fbc 100644
> --- a/refs/reftable-backend.c
> +++ b/refs/reftable-backend.c
> @@ -1206,10 +1206,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
> goto done;
> }
> } else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) {
> - if (is_null_oid(&u->old_oid))
> + ret = TRANSACTION_NAME_CONFLICT;
> + if (is_null_oid(&u->old_oid)) {
> strbuf_addf(err, _("cannot lock ref '%s': "
> "reference already exists"),
> ref_update_original_update_refname(u));
> + ret = TRANSACTION_CREATE_EXISTS;
> + }
> else if (is_null_oid(¤t_oid))
> strbuf_addf(err, _("cannot lock ref '%s': "
> "reference is missing but expected %s"),
> @@ -1221,7 +1224,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
> ref_update_original_update_refname(u),
> oid_to_hex(¤t_oid),
> oid_to_hex(&u->old_oid));
> - ret = -1;
> goto done;
> }
>
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v7 4/6] refs: add TRANSACTION_CREATE_EXISTS error
2024-10-13 14:03 ` Phillip Wood
@ 2024-10-13 20:52 ` Bence Ferdinandy
2024-10-14 8:48 ` Phillip Wood
0 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-13 20:52 UTC (permalink / raw)
To: phillip.wood, git
Cc: Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano
On Sun Oct 13, 2024 at 16:03, Phillip Wood <phillip.wood123@gmail.com> wrote:
> Hi Bence
>
> On 13/10/2024 00:03, Bence Ferdinandy wrote:
>> Currently there is only one special error for transaction, for when
>> there is a naming conflict, all other errors are dumped under a generic
>> error. Add a new special error case for when the caller requests the
>> reference to be updated only when it does not yet exist and the
>> reference actually does exist.
>
> This looks like useful improvement. Are the changes to
> reftable-backend.c correct - it looks like where it previously returned
> TRANSACTION_GENERIC_ERR it now returns TRANSACTION_NAME_CONFLICT which I
> think is used to indicate a file/directory conflict (e.g. trying to
> create refs/heads/topic/one when refs/heads/topic exists)
This passes:
GIT_TEST_DEFAULT_REF_FORMAT=reftable prove --timer --jobs 8 ./t[0-9]*.sh
so I'm hoping it's correct :)
[snip]
I guess you are referring to this part below:
>> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
>> index 3c96fbf66f..ebf8e57fbc 100644
>> --- a/refs/reftable-backend.c
>> +++ b/refs/reftable-backend.c
>> @@ -1206,10 +1206,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
>> goto done;
>> }
>> } else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) {
>> - if (is_null_oid(&u->old_oid))
>> + ret = TRANSACTION_NAME_CONFLICT;
>> + if (is_null_oid(&u->old_oid)) {
>> strbuf_addf(err, _("cannot lock ref '%s': "
>> "reference already exists"),
>> ref_update_original_update_refname(u));
>> + ret = TRANSACTION_CREATE_EXISTS;
>> + }
>> else if (is_null_oid(¤t_oid))
>> strbuf_addf(err, _("cannot lock ref '%s': "
>> "reference is missing but expected %s"),
>> @@ -1221,7 +1224,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
>> ref_update_original_update_refname(u),
>> oid_to_hex(¤t_oid),
>> oid_to_hex(&u->old_oid));
>> - ret = -1;
>> goto done;
>> }
>>
This originally returned -1, and it still returns that if it doesn't return -2,
I just used the named variable instead of the integer itself. It might still be
that this should be -3 GENERIC_ERROR, but if that is the case I think fixing
that should be a different patch? I didn't check if changing that -1 to
something else breaks anything or not.
Best,
Bence
--
bence.ferdinandy.com
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v7 4/6] refs: add TRANSACTION_CREATE_EXISTS error
2024-10-13 20:52 ` Bence Ferdinandy
@ 2024-10-14 8:48 ` Phillip Wood
0 siblings, 0 replies; 258+ messages in thread
From: Phillip Wood @ 2024-10-14 8:48 UTC (permalink / raw)
To: Bence Ferdinandy, phillip.wood, git
Cc: Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano
Hi Bence
On 13/10/2024 21:52, Bence Ferdinandy wrote:
> On Sun Oct 13, 2024 at 16:03, Phillip Wood <phillip.wood123@gmail.com> wrote:
>>> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
>>> index 3c96fbf66f..ebf8e57fbc 100644
>>> --- a/refs/reftable-backend.c
>>> +++ b/refs/reftable-backend.c
>>> @@ -1206,10 +1206,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
>>> goto done;
>>> }
>>> } else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) {
>>> - if (is_null_oid(&u->old_oid))
>>> + ret = TRANSACTION_NAME_CONFLICT;
>>> + if (is_null_oid(&u->old_oid)) {
>>> strbuf_addf(err, _("cannot lock ref '%s': "
>>> "reference already exists"),
>>> ref_update_original_update_refname(u));
>>> + ret = TRANSACTION_CREATE_EXISTS;
>>> + }
>>> else if (is_null_oid(¤t_oid))
>>> strbuf_addf(err, _("cannot lock ref '%s': "
>>> "reference is missing but expected %s"),
>>> @@ -1221,7 +1224,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
>>> ref_update_original_update_refname(u),
>>> oid_to_hex(¤t_oid),
>>> oid_to_hex(&u->old_oid));
>>> - ret = -1;
>>> goto done;
>>> }
>>>
>
> This originally returned -1, and it still returns that if it doesn't return -2,
> I just used the named variable instead of the integer itself. It might still be
> that this should be -3 GENERIC_ERROR, but if that is the case I think fixing
> that should be a different patch? I didn't check if changing that -1 to
> something else breaks anything or not.
Oh sorry I was confused and thought TRANSACTION_GENERIC_ERROR was -1.
Best Wishes
Phillip
> Best,
> Bence
>
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v7 5/6] refs: add create_only option to refs_update_symref
2024-10-12 23:03 ` [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
` (2 preceding siblings ...)
2024-10-12 23:03 ` [PATCH v7 4/6] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
@ 2024-10-12 23:03 ` Bence Ferdinandy
2024-10-12 23:03 ` [PATCH v7 6/6] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
` (2 subsequent siblings)
6 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-12 23:03 UTC (permalink / raw)
To: git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano, Bence Ferdinandy
Allow the caller to specify that it only wants to update the symref if
it does not already exist. Silently ignore the error from the
transaction API if the symref already exists.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: no change
v6: - switched from bool to int for create_only
- refactored logic in refs_update_symref with goto as layed out by
Junio
v7: - change commit prefix to be more in line with project standards
- refactored code to accommodate changes in the first patch, but
otherwise no change
builtin/branch.c | 2 +-
builtin/checkout.c | 5 +++--
builtin/clone.c | 8 +++++---
builtin/notes.c | 3 ++-
builtin/remote.c | 9 ++++++---
builtin/symbolic-ref.c | 2 +-
builtin/worktree.c | 2 +-
refs.c | 30 ++++++++++++++++++++++--------
refs.h | 2 +-
reset.c | 2 +-
sequencer.c | 3 ++-
setup.c | 3 ++-
t/helper/test-ref-store.c | 2 +-
13 files changed, 48 insertions(+), 25 deletions(-)
diff --git a/builtin/branch.c b/builtin/branch.c
index 6c87690b58..2fcbcbfd59 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -559,7 +559,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,
continue;
refs = get_worktree_ref_store(worktrees[i]);
- if (refs_update_symref(refs, "HEAD", newref, logmsg, NULL))
+ if (refs_update_symref(refs, "HEAD", newref, logmsg, NULL, 0))
ret = error(_("HEAD of working tree %s is not updated"),
worktrees[i]->path);
}
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 356ee9bcde..9abb71dc07 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1015,7 +1015,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
describe_detached_head(_("HEAD is now at"), new_branch_info->commit);
}
} else if (new_branch_info->path) { /* Switch branches. */
- if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", new_branch_info->path, msg.buf, NULL) < 0)
+ if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", new_branch_info->path,
+ msg.buf, NULL, 0) < 0)
die(_("unable to update HEAD"));
if (!opts->quiet) {
if (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) {
@@ -1479,7 +1480,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)
die(_("You are on a branch yet to be born"));
strbuf_addf(&branch_ref, "refs/heads/%s", opts->new_branch);
status = refs_update_symref(get_main_ref_store(the_repository),
- "HEAD", branch_ref.buf, "checkout -b", NULL);
+ "HEAD", branch_ref.buf, "checkout -b", NULL, 0);
strbuf_release(&branch_ref);
if (!opts->quiet)
fprintf(stderr, _("Switched to a new branch '%s'\n"),
diff --git a/builtin/clone.c b/builtin/clone.c
index ead2af20ea..87e8f1421a 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -661,7 +661,7 @@ static void update_remote_refs(const struct ref *refs,
strbuf_addstr(&head_ref, "HEAD");
if (refs_update_symref(get_main_ref_store(the_repository), head_ref.buf,
remote_head_points_at->peer_ref->name,
- msg, NULL) < 0)
+ msg, NULL, 0) < 0)
die(_("unable to update %s"), head_ref.buf);
strbuf_release(&head_ref);
}
@@ -673,7 +673,8 @@ static void update_head(const struct ref *our, const struct ref *remote,
const char *head;
if (our && skip_prefix(our->name, "refs/heads/", &head)) {
/* Local default branch link */
- if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", our->name, NULL, NULL) < 0)
+ if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", our->name,
+ NULL, NULL, 0) < 0)
die(_("unable to update HEAD"));
if (!option_bare) {
refs_update_ref(get_main_ref_store(the_repository),
@@ -702,7 +703,8 @@ static void update_head(const struct ref *our, const struct ref *remote,
* Unborn head from remote; same as "our" case above except
* that we have no ref to update.
*/
- if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", unborn, NULL, NULL) < 0)
+ if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", unborn,
+ NULL, NULL, 0) < 0)
die(_("unable to update HEAD"));
if (!option_bare)
install_branch_config(0, head, remote_name, unborn);
diff --git a/builtin/notes.c b/builtin/notes.c
index ba646f06ff..7b3138e3c3 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -980,7 +980,8 @@ static int merge(int argc, const char **argv, const char *prefix)
die(_("a notes merge into %s is already in-progress at %s"),
notes_ref, wt->path);
free_worktrees(worktrees);
- if (refs_update_symref(get_main_ref_store(the_repository), "NOTES_MERGE_REF", notes_ref, NULL, NULL))
+ if (refs_update_symref(get_main_ref_store(the_repository), "NOTES_MERGE_REF", notes_ref,
+ NULL, NULL, 0))
die(_("failed to store link to current notes ref (%s)"),
notes_ref);
fprintf(stderr, _("Automatic notes merge failed. Fix conflicts in %s "
diff --git a/builtin/remote.c b/builtin/remote.c
index 2b6948439f..d60ec6e907 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -244,7 +244,8 @@ static int add(int argc, const char **argv, const char *prefix)
strbuf_reset(&buf2);
strbuf_addf(&buf2, "refs/remotes/%s/%s", name, master);
- if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote add", NULL))
+ if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf,
+ "remote add", NULL, 0))
result = error(_("Could not setup master '%s'"), master);
}
@@ -864,7 +865,8 @@ static int mv(int argc, const char **argv, const char *prefix)
strbuf_reset(&buf3);
strbuf_addf(&buf3, "remote: renamed %s to %s",
item->string, buf.buf);
- if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf, NULL))
+ if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf,
+ buf3.buf, NULL, 0))
die(_("creating '%s' failed"), buf.buf);
display_progress(progress, ++refs_renamed_nr);
}
@@ -1470,7 +1472,8 @@ static int set_head(int argc, const char **argv, const char *prefix)
/* make sure it's valid */
if (!refs_ref_exists(refs, buf2.buf))
result |= error(_("Not a valid ref: %s"), buf2.buf);
- else if (refs_update_symref(refs, buf.buf, buf2.buf, "remote set-head", &buf_prev))
+ else if (refs_update_symref(refs, buf.buf, buf2.buf,
+ "remote set-head", &buf_prev, 0))
result |= error(_("Could not setup %s"), buf.buf);
else if (opt_a) {
report_set_head_auto(argv[0], head_name, &buf_prev);
diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c
index 7728fbc3c1..cfbb53b30c 100644
--- a/builtin/symbolic-ref.c
+++ b/builtin/symbolic-ref.c
@@ -88,7 +88,7 @@ int cmd_symbolic_ref(int argc,
if (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0)
die("Refusing to set '%s' to invalid ref '%s'", argv[0], argv[1]);
ret = !!refs_update_symref(get_main_ref_store(the_repository),
- argv[0], argv[1], msg, NULL);
+ argv[0], argv[1], msg, NULL, 0);
break;
default:
usage_with_options(git_symbolic_ref_usage, options);
diff --git a/builtin/worktree.c b/builtin/worktree.c
index a7ab4193c1..2e95367235 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,
ret = refs_update_ref(wt_refs, NULL, "HEAD", &commit->object.oid,
NULL, 0, UPDATE_REFS_MSG_ON_ERR);
else
- ret = refs_update_symref(wt_refs, "HEAD", symref.buf, NULL, NULL);
+ ret = refs_update_symref(wt_refs, "HEAD", symref.buf, NULL, NULL, 0);
if (ret)
goto done;
diff --git a/refs.c b/refs.c
index b964ac44d0..29c020e78e 100644
--- a/refs.c
+++ b/refs.c
@@ -2115,26 +2115,40 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
int refs_update_symref(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg,
- struct strbuf *referent)
+ struct strbuf *referent, int create_only)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
- int ret = 0;
+ int ret = 0, prepret = 0;
transaction = ref_store_transaction_begin(refs, &err);
- if (!transaction ||
- ref_transaction_update(transaction, ref, NULL, NULL,
- target, NULL, REF_NO_DEREF,
- logmsg, &err) ||
- ref_transaction_prepare(transaction, &err)) {
+ if (!transaction) {
+ error_return:
ret = error("%s", err.buf);
goto cleanup;
}
+ if (create_only) {
+ if (ref_transaction_create(transaction, ref, NULL, target,
+ REF_NO_DEREF, logmsg, &err))
+ goto error_return;
+ prepret = ref_transaction_prepare(transaction, &err);
+ if (prepret && prepret != TRANSACTION_CREATE_EXISTS)
+ goto error_return;
+ } else {
+ if (ref_transaction_update(transaction, ref, NULL, NULL,
+ target, NULL, REF_NO_DEREF,
+ logmsg, &err) ||
+ ref_transaction_prepare(transaction, &err))
+ goto error_return;
+ }
+
if (referent)
refs_read_symbolic_ref(refs, ref, referent);
+ if (prepret == TRANSACTION_CREATE_EXISTS)
+ goto cleanup;
if (ref_transaction_commit(transaction, &err))
- ret = error("%s", err.buf);
+ goto error_return;
cleanup:
strbuf_release(&err);
diff --git a/refs.h b/refs.h
index c83b1ec76e..41ae384f11 100644
--- a/refs.h
+++ b/refs.h
@@ -572,7 +572,7 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
int refs_update_symref(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg,
- struct strbuf *referent);
+ struct strbuf *referent, int create_only);
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
diff --git a/reset.c b/reset.c
index cc36a9ed56..919fb84f5f 100644
--- a/reset.c
+++ b/reset.c
@@ -76,7 +76,7 @@ static int update_refs(const struct reset_head_opts *opts,
if (!ret)
ret = refs_update_symref(get_main_ref_store(the_repository),
"HEAD", switch_to_branch,
- reflog_head, NULL);
+ reflog_head, NULL, 0);
}
if (!ret && run_hook)
run_hooks_l(the_repository, "post-checkout",
diff --git a/sequencer.c b/sequencer.c
index 23b162924c..103d0ddfb2 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -5107,7 +5107,8 @@ static int pick_commits(struct repository *r,
}
msg = reflog_message(opts, "finish", "returning to %s",
head_ref.buf);
- if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", head_ref.buf, msg, NULL)) {
+ if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", head_ref.buf,
+ msg, NULL, 0)) {
res = error(_("could not update HEAD to %s"),
head_ref.buf);
goto cleanup_head_ref;
diff --git a/setup.c b/setup.c
index d95f051465..9bbc655ee4 100644
--- a/setup.c
+++ b/setup.c
@@ -2275,7 +2275,8 @@ void create_reference_database(enum ref_storage_format ref_storage_format,
die(_("invalid initial branch name: '%s'"),
initial_branch);
- if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", ref, NULL, NULL) < 0)
+ if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", ref,
+ NULL, NULL, 0) < 0)
exit(1);
free(ref);
}
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index a911302bea..f72b029e51 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -120,7 +120,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)
const char *target = notnull(*argv++, "target");
const char *logmsg = *argv++;
- return refs_update_symref(refs, refname, target, logmsg, NULL);
+ return refs_update_symref(refs, refname, target, logmsg, NULL, 0);
}
static struct flag_definition transaction_flags[] = {
--
2.47.0.7.g072c39eddb.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v7 6/6] fetch: set remote/HEAD if it does not exist
2024-10-12 23:03 ` [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
` (3 preceding siblings ...)
2024-10-12 23:03 ` [PATCH v7 5/6] refs: add create_only option to refs_update_symref Bence Ferdinandy
@ 2024-10-12 23:03 ` Bence Ferdinandy
2024-10-13 13:52 ` [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref Phillip Wood
2024-10-14 22:53 ` [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7 Bence Ferdinandy
6 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-12 23:03 UTC (permalink / raw)
To: git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano, Bence Ferdinandy
If the user has remote/HEAD set already and it looks like it has changed
on the server, then print a message, otherwise set it if we can.
Silently pass if the user already has the same remote/HEAD set as
reported by the server or if we encounter any errors along the way.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v3: - does not rely on remote set-head anymore so it only authenticates
once
- uses the new REF_CREATE_ONLY to atomically check if the ref exists
and only write it if it doesn't
- in all other cases the maximum it does is print a warning
v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,
but updated transaction api to request a silent create only
- it now uses the atomic before_target to determine reporting
- refactored for legibility
v5: - instead of printing a not too useful message, it now fails
silently, this in line with the objective to only set up
remote/HEAD automatically if the right thing is trivial, for
everything else there is remote set-head
- fixed all failing tests
- added two new tests, one for checking if remote/HEAD is set to the
correct one, and one to test that we do not override remote/HEAD
if it has changed on the server from what we have locally
v6: - fixed style issues and unintended extra empty line
- updated function call with bool to int from previous patch's
change
- removed calls to error(...) inherited from builtin/remote.c so we
actually fail silently
- set the test for remote set-head --auto to the correct value here,
which was previously erronously set in the remote set-head patch
v7: - no change
builtin/fetch.c | 82 +++++++++++
t/t4207-log-decoration-colors.sh | 3 +-
t/t5505-remote.sh | 13 +-
t/t5510-fetch.sh | 229 ++++++++++++++++---------------
t/t5512-ls-remote.sh | 2 +
t/t5514-fetch-multiple.sh | 17 ++-
t/t5516-fetch-push.sh | 3 +-
t/t5527-fetch-odd-refs.sh | 3 +-
t/t7900-maintenance.sh | 3 +-
t/t9210-scalar.sh | 5 +-
t/t9211-scalar-clone.sh | 6 +-
t/t9902-completion.sh | 65 +++++++++
12 files changed, 308 insertions(+), 123 deletions(-)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 80a64d0d26..4c68f21be1 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1578,6 +1578,80 @@ static int backfill_tags(struct display_state *display_state,
return retcode;
}
+static void report_set_head(const char *remote, const char *head_name,
+ struct strbuf *buf_prev) {
+ struct strbuf buf_prefix = STRBUF_INIT;
+ const char *prev_head = NULL;
+
+ strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
+ skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);
+
+ if (prev_head && strcmp(prev_head, head_name)) {
+ printf("'HEAD' at '%s' has changed from '%s' to '%s'\n",
+ remote, prev_head, head_name);
+ printf("Run 'git remote set-head %s %s' to follow the change.\n",
+ remote, head_name);
+ }
+}
+
+static const char *strip_refshead(const char *name){
+ skip_prefix(name, "refs/heads/", &name);
+ return name;
+}
+
+static int set_head(const struct ref *remote_refs)
+{
+ int result = 0;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
+ b_local_head = STRBUF_INIT;
+ const char *remote = gtransport->remote->name;
+ char *head_name = NULL;
+ struct ref *ref, *matches;
+ struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
+ struct refspec_item refspec = {
+ .force = 0,
+ .pattern = 1,
+ .src = (char *) "refs/heads/*",
+ .dst = (char *) "refs/heads/*",
+ };
+ struct string_list heads = STRING_LIST_INIT_DUP;
+ struct ref_store *refs = get_main_ref_store(the_repository);
+
+ get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
+ matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
+ fetch_map, 1);
+ for (ref = matches; ref; ref = ref->next) {
+ string_list_append(&heads, strip_refshead(ref->name));
+ }
+
+
+ if (!heads.nr)
+ result = 1;
+ else if (heads.nr > 1)
+ result = 1;
+ else
+ head_name = xstrdup(heads.items[0].string);
+ if (head_name) {
+ strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name);
+ /* make sure it's valid */
+ if (!refs_ref_exists(refs, b_remote_head.buf))
+ result = 1;
+ else if (refs_update_symref(refs, b_head.buf, b_remote_head.buf,
+ "remote set-head", &b_local_head, 1))
+ result = 1;
+ else
+ report_set_head(remote, head_name, &b_local_head);
+
+ free(head_name);
+ }
+
+ strbuf_release(&b_head);
+ strbuf_release(&b_local_head);
+ strbuf_release(&b_remote_head);
+ return result;
+}
+
static int do_fetch(struct transport *transport,
struct refspec *rs,
const struct fetch_config *config)
@@ -1647,6 +1721,8 @@ static int do_fetch(struct transport *transport,
"refs/tags/");
}
+ strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
+
if (must_list_refs) {
trace2_region_enter("fetch", "remote_refs", the_repository);
remote_refs = transport_get_remote_refs(transport,
@@ -1791,6 +1867,12 @@ static int do_fetch(struct transport *transport,
"you need to specify exactly one branch with the --set-upstream option"));
}
}
+ if (set_head(remote_refs))
+ ;
+ /*
+ * Way too many cases where this can go wrong
+ * so let's just fail silently for now.
+ */
cleanup:
if (retcode) {
diff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh
index 73ea9e5155..d55d22cb2f 100755
--- a/t/t4207-log-decoration-colors.sh
+++ b/t/t4207-log-decoration-colors.sh
@@ -59,7 +59,8 @@ ${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \
${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \
-${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1
+${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit}, \
+${c_reset}${c_remoteBranch}other/HEAD${c_reset}${c_commit})${c_reset} A1
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
${c_stash}refs/stash${c_reset}${c_commit})${c_reset} On main: Changes to A.t
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index d99cd1d0aa..cb45f22232 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -71,7 +71,7 @@ test_expect_success 'add another remote' '
cd test &&
git remote add -f second ../two &&
tokens_match "origin second" "$(git remote)" &&
- check_tracking_branch second main side another &&
+ check_tracking_branch second main side another HEAD &&
git for-each-ref "--format=%(refname)" refs/remotes |
sed -e "/^refs\/remotes\/origin\//d" \
-e "/^refs\/remotes\/second\//d" >actual &&
@@ -473,7 +473,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
cd test &&
git fetch two "refs/heads/*:refs/remotes/two/*" &&
git remote set-head --auto two >output 2>&1 &&
- echo "'\''two/HEAD'\'' is now created and points to '\''main'\''" >expect &&
+ echo "'\''two/HEAD'\'' is unchanged and points to '\''main'\''" >expect &&
test_cmp expect output
)
'
@@ -763,6 +763,7 @@ test_expect_success 'reject --no-no-tags' '
cat >one/expect <<\EOF
apis/main
apis/side
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -780,6 +781,7 @@ test_expect_success 'update' '
'
cat >one/expect <<\EOF
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -792,7 +794,7 @@ EOF
test_expect_success 'update with arguments' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -835,7 +837,7 @@ EOF
test_expect_success 'update default' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -847,6 +849,7 @@ test_expect_success 'update default' '
'
cat >one/expect <<\EOF
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -869,7 +872,7 @@ test_expect_success 'update default (overridden, with funny whitespace)' '
test_expect_success 'update (with remotes.default defined)' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 0890b9f61c..dfc8d748ba 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -75,6 +75,30 @@ test_expect_success "fetch test for-merge" '
cut -f -2 .git/FETCH_HEAD >actual &&
test_cmp expected actual'
+test_expect_success "fetch test remote HEAD" '
+ cd "$D" &&
+ cd two &&
+ git fetch &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"'
+
+test_expect_success "fetch test remote HEAD change" '
+ cd "$D" &&
+ cd two &&
+ git switch -c other &&
+ git push -u origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git fetch &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"'
+
test_expect_success 'fetch --prune on its own works as expected' '
cd "$D" &&
git clone . prune &&
@@ -478,7 +502,6 @@ test_expect_success 'unbundle 1' '
test_must_fail git fetch "$D/bundle1" main:main
'
-
test_expect_success 'bundle 1 has only 3 files ' '
cd "$D" &&
test_bundle_object_count bundle1 3
@@ -819,7 +842,7 @@ test_expect_success 'fetch from multiple configured URLs in single remote' '
# configured prune tests
-set_config_tristate () {
+set_config_tristate() {
# var=$1 val=$2
case "$2" in
unset)
@@ -833,12 +856,12 @@ set_config_tristate () {
esac
}
-test_configured_prune () {
+test_configured_prune() {
test_configured_prune_type "$@" "name"
test_configured_prune_type "$@" "link"
}
-test_configured_prune_type () {
+test_configured_prune_type() {
fetch_prune=$1
remote_origin_prune=$2
fetch_prune_tags=$3
@@ -848,8 +871,7 @@ test_configured_prune_type () {
cmdline=$7
mode=$8
- if test -z "$cmdline_setup"
- then
+ if test -z "$cmdline_setup"; then
test_expect_success 'setup cmdline_setup variable for subsequent test' '
remote_url="file://$(git -C one config remote.origin.url)" &&
remote_fetch="$(git -C one config remote.origin.fetch)" &&
@@ -857,12 +879,10 @@ test_configured_prune_type () {
'
fi
- if test "$mode" = 'link'
- then
+ if test "$mode" = 'link'; then
new_cmdline=""
- if test "$cmdline" = ""
- then
+ if test "$cmdline" = ""; then
new_cmdline=$cmdline_setup
else
new_cmdline=$(perl -e '
@@ -873,10 +893,8 @@ test_configured_prune_type () {
fi
if test "$fetch_prune_tags" = 'true' ||
- test "$remote_origin_prune_tags" = 'true'
- then
- if ! printf '%s' "$cmdline\n" | grep -q refs/remotes/origin/
- then
+ test "$remote_origin_prune_tags" = 'true'; then
+ if ! printf '%s' "$cmdline\n" | grep -q refs/remotes/origin/; then
new_cmdline="$new_cmdline refs/tags/*:refs/tags/*"
fi
fi
@@ -946,100 +964,100 @@ test_configured_prune_type () {
# $7 git-fetch $cmdline:
#
# $1 $2 $3 $4 $5 $6 $7
-test_configured_prune unset unset unset unset kept kept ""
-test_configured_prune unset unset unset unset kept kept "--no-prune"
-test_configured_prune unset unset unset unset pruned kept "--prune"
-test_configured_prune unset unset unset unset kept pruned \
+test_configured_prune unset unset unset unset kept kept ""
+test_configured_prune unset unset unset unset kept kept "--no-prune"
+test_configured_prune unset unset unset unset pruned kept "--prune"
+test_configured_prune unset unset unset unset kept pruned \
"--prune origin refs/tags/*:refs/tags/*"
test_configured_prune unset unset unset unset pruned pruned \
"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
-test_configured_prune false unset unset unset kept kept ""
-test_configured_prune false unset unset unset kept kept "--no-prune"
-test_configured_prune false unset unset unset pruned kept "--prune"
+test_configured_prune false unset unset unset kept kept ""
+test_configured_prune false unset unset unset kept kept "--no-prune"
+test_configured_prune false unset unset unset pruned kept "--prune"
-test_configured_prune true unset unset unset pruned kept ""
-test_configured_prune true unset unset unset pruned kept "--prune"
-test_configured_prune true unset unset unset kept kept "--no-prune"
+test_configured_prune true unset unset unset pruned kept ""
+test_configured_prune true unset unset unset pruned kept "--prune"
+test_configured_prune true unset unset unset kept kept "--no-prune"
-test_configured_prune unset false unset unset kept kept ""
-test_configured_prune unset false unset unset kept kept "--no-prune"
-test_configured_prune unset false unset unset pruned kept "--prune"
+test_configured_prune unset false unset unset kept kept ""
+test_configured_prune unset false unset unset kept kept "--no-prune"
+test_configured_prune unset false unset unset pruned kept "--prune"
-test_configured_prune false false unset unset kept kept ""
-test_configured_prune false false unset unset kept kept "--no-prune"
-test_configured_prune false false unset unset pruned kept "--prune"
-test_configured_prune false false unset unset kept pruned \
+test_configured_prune false false unset unset kept kept ""
+test_configured_prune false false unset unset kept kept "--no-prune"
+test_configured_prune false false unset unset pruned kept "--prune"
+test_configured_prune false false unset unset kept pruned \
"--prune origin refs/tags/*:refs/tags/*"
test_configured_prune false false unset unset pruned pruned \
"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
-test_configured_prune true false unset unset kept kept ""
-test_configured_prune true false unset unset pruned kept "--prune"
-test_configured_prune true false unset unset kept kept "--no-prune"
+test_configured_prune true false unset unset kept kept ""
+test_configured_prune true false unset unset pruned kept "--prune"
+test_configured_prune true false unset unset kept kept "--no-prune"
-test_configured_prune unset true unset unset pruned kept ""
-test_configured_prune unset true unset unset kept kept "--no-prune"
-test_configured_prune unset true unset unset pruned kept "--prune"
+test_configured_prune unset true unset unset pruned kept ""
+test_configured_prune unset true unset unset kept kept "--no-prune"
+test_configured_prune unset true unset unset pruned kept "--prune"
-test_configured_prune false true unset unset pruned kept ""
-test_configured_prune false true unset unset kept kept "--no-prune"
-test_configured_prune false true unset unset pruned kept "--prune"
+test_configured_prune false true unset unset pruned kept ""
+test_configured_prune false true unset unset kept kept "--no-prune"
+test_configured_prune false true unset unset pruned kept "--prune"
-test_configured_prune true true unset unset pruned kept ""
-test_configured_prune true true unset unset pruned kept "--prune"
-test_configured_prune true true unset unset kept kept "--no-prune"
-test_configured_prune true true unset unset kept pruned \
+test_configured_prune true true unset unset pruned kept ""
+test_configured_prune true true unset unset pruned kept "--prune"
+test_configured_prune true true unset unset kept kept "--no-prune"
+test_configured_prune true true unset unset kept pruned \
"--prune origin refs/tags/*:refs/tags/*"
-test_configured_prune true true unset unset pruned pruned \
+test_configured_prune true true unset unset pruned pruned \
"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
# --prune-tags on its own does nothing, needs --prune as well, same
# for fetch.pruneTags without fetch.prune
-test_configured_prune unset unset unset unset kept kept "--prune-tags"
-test_configured_prune unset unset true unset kept kept ""
-test_configured_prune unset unset unset true kept kept ""
+test_configured_prune unset unset unset unset kept kept "--prune-tags"
+test_configured_prune unset unset true unset kept kept ""
+test_configured_prune unset unset unset true kept kept ""
# These will prune the tags
test_configured_prune unset unset unset unset pruned pruned "--prune --prune-tags"
-test_configured_prune true unset true unset pruned pruned ""
-test_configured_prune unset true unset true pruned pruned ""
+test_configured_prune true unset true unset pruned pruned ""
+test_configured_prune unset true unset true pruned pruned ""
# remote.<name>.pruneTags overrides fetch.pruneTags, just like
# remote.<name>.prune overrides fetch.prune if set.
-test_configured_prune true unset true unset pruned pruned ""
-test_configured_prune false true false true pruned pruned ""
-test_configured_prune true false true false kept kept ""
+test_configured_prune true unset true unset pruned pruned ""
+test_configured_prune false true false true pruned pruned ""
+test_configured_prune true false true false kept kept ""
# When --prune-tags is supplied it's ignored if an explicit refspec is
# given, same for the configuration options.
test_configured_prune unset unset unset unset pruned kept \
"--prune --prune-tags origin +refs/heads/*:refs/remotes/origin/*"
-test_configured_prune unset unset true unset pruned kept \
+test_configured_prune unset unset true unset pruned kept \
"--prune origin +refs/heads/*:refs/remotes/origin/*"
-test_configured_prune unset unset unset true pruned kept \
+test_configured_prune unset unset unset true pruned kept \
"--prune origin +refs/heads/*:refs/remotes/origin/*"
# Pruning that also takes place if a file:// url replaces a named
# remote. However, because there's no implicit
# +refs/heads/*:refs/remotes/origin/* refspec and supplying it on the
# command-line negates --prune-tags, the branches will not be pruned.
-test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "name"
-test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "link"
+test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "name"
+test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "link"
test_configured_prune_type unset unset unset unset pruned pruned "origin --prune --prune-tags" "name"
-test_configured_prune_type unset unset unset unset kept pruned "origin --prune --prune-tags" "link"
+test_configured_prune_type unset unset unset unset kept pruned "origin --prune --prune-tags" "link"
test_configured_prune_type unset unset unset unset pruned pruned "--prune --prune-tags origin" "name"
-test_configured_prune_type unset unset unset unset kept pruned "--prune --prune-tags origin" "link"
-test_configured_prune_type unset unset true unset pruned pruned "--prune origin" "name"
-test_configured_prune_type unset unset true unset kept pruned "--prune origin" "link"
-test_configured_prune_type unset unset unset true pruned pruned "--prune origin" "name"
-test_configured_prune_type unset unset unset true kept pruned "--prune origin" "link"
-test_configured_prune_type true unset true unset pruned pruned "origin" "name"
-test_configured_prune_type true unset true unset kept pruned "origin" "link"
-test_configured_prune_type unset true true unset pruned pruned "origin" "name"
-test_configured_prune_type unset true true unset kept pruned "origin" "link"
-test_configured_prune_type unset true unset true pruned pruned "origin" "name"
-test_configured_prune_type unset true unset true kept pruned "origin" "link"
+test_configured_prune_type unset unset unset unset kept pruned "--prune --prune-tags origin" "link"
+test_configured_prune_type unset unset true unset pruned pruned "--prune origin" "name"
+test_configured_prune_type unset unset true unset kept pruned "--prune origin" "link"
+test_configured_prune_type unset unset unset true pruned pruned "--prune origin" "name"
+test_configured_prune_type unset unset unset true kept pruned "--prune origin" "link"
+test_configured_prune_type true unset true unset pruned pruned "origin" "name"
+test_configured_prune_type true unset true unset kept pruned "origin" "link"
+test_configured_prune_type unset true true unset pruned pruned "origin" "name"
+test_configured_prune_type unset true true unset kept pruned "origin" "link"
+test_configured_prune_type unset true unset true pruned pruned "origin" "name"
+test_configured_prune_type unset true unset true kept pruned "origin" "link"
# When all remote.origin.fetch settings are deleted a --prune
# --prune-tags still implicitly supplies refs/tags/*:refs/tags/* so
@@ -1137,8 +1155,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '
)
'
-for section in fetch transfer
-do
+for section in fetch transfer; do
test_expect_success "$section.hideRefs affects connectivity check" '
GIT_TRACE="$PWD"/trace git -c $section.hideRefs=refs -c \
$section.hideRefs="!refs/tags/" fetch &&
@@ -1157,21 +1174,23 @@ test_expect_success 'prepare source branch' '
test 3 -le $(wc -l <actual)
'
-validate_store_type () {
+validate_store_type() {
git -C dest count-objects -v >actual &&
- case "$store_type" in
- packed)
- grep "^count: 0$" actual ;;
- loose)
- grep "^packs: 0$" actual ;;
- esac || {
+ case "$store_type" in
+ packed)
+ grep "^count: 0$" actual
+ ;;
+ loose)
+ grep "^packs: 0$" actual
+ ;;
+ esac || {
echo "store_type is $store_type"
cat actual
false
}
}
-test_unpack_limit () {
+test_unpack_limit() {
store_type=$1
case "$store_type" in
@@ -1192,43 +1211,39 @@ test_unpack_limit () {
test_unpack_limit packed
test_unpack_limit loose
-setup_negotiation_tip () {
+setup_negotiation_tip() {
SERVER="$1"
URL="$2"
USE_PROTOCOL_V2="$3"
rm -rf "$SERVER" client trace &&
- git init -b main "$SERVER" &&
- test_commit -C "$SERVER" alpha_1 &&
- test_commit -C "$SERVER" alpha_2 &&
- git -C "$SERVER" checkout --orphan beta &&
- test_commit -C "$SERVER" beta_1 &&
- test_commit -C "$SERVER" beta_2 &&
-
- git clone "$URL" client &&
-
- if test "$USE_PROTOCOL_V2" -eq 1
- then
- git -C "$SERVER" config protocol.version 2 &&
- git -C client config protocol.version 2
- fi &&
-
- test_commit -C "$SERVER" beta_s &&
- git -C "$SERVER" checkout main &&
- test_commit -C "$SERVER" alpha_s &&
- git -C "$SERVER" tag -d alpha_1 alpha_2 beta_1 beta_2
+ git init -b main "$SERVER" &&
+ test_commit -C "$SERVER" alpha_1 &&
+ test_commit -C "$SERVER" alpha_2 &&
+ git -C "$SERVER" checkout --orphan beta &&
+ test_commit -C "$SERVER" beta_1 &&
+ test_commit -C "$SERVER" beta_2 &&
+ git clone "$URL" client &&
+ if test "$USE_PROTOCOL_V2" -eq 1; then
+ git -C "$SERVER" config protocol.version 2 &&
+ git -C client config protocol.version 2
+ fi &&
+ test_commit -C "$SERVER" beta_s &&
+ git -C "$SERVER" checkout main &&
+ test_commit -C "$SERVER" alpha_s &&
+ git -C "$SERVER" tag -d alpha_1 alpha_2 beta_1 beta_2
}
-check_negotiation_tip () {
+check_negotiation_tip() {
# Ensure that {alpha,beta}_1 are sent as "have", but not {alpha_beta}_2
ALPHA_1=$(git -C client rev-parse alpha_1) &&
- grep "fetch> have $ALPHA_1" trace &&
- BETA_1=$(git -C client rev-parse beta_1) &&
- grep "fetch> have $BETA_1" trace &&
- ALPHA_2=$(git -C client rev-parse alpha_2) &&
- ! grep "fetch> have $ALPHA_2" trace &&
- BETA_2=$(git -C client rev-parse beta_2) &&
- ! grep "fetch> have $BETA_2" trace
+ grep "fetch> have $ALPHA_1" trace &&
+ BETA_1=$(git -C client rev-parse beta_1) &&
+ grep "fetch> have $BETA_1" trace &&
+ ALPHA_2=$(git -C client rev-parse alpha_2) &&
+ ! grep "fetch> have $ALPHA_2" trace &&
+ BETA_2=$(git -C client rev-parse beta_2) &&
+ ! grep "fetch> have $BETA_2" trace
}
test_expect_success '--negotiation-tip limits "have" lines sent' '
diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh
index 64b3491e4e..1b3865e154 100755
--- a/t/t5512-ls-remote.sh
+++ b/t/t5512-ls-remote.sh
@@ -293,6 +293,8 @@ test_expect_success 'ls-remote with filtered symref (refname)' '
cat >expect <<-EOF &&
ref: refs/heads/main HEAD
$rev HEAD
+ ref: refs/remotes/origin/main refs/remotes/origin/HEAD
+ $rev refs/remotes/origin/HEAD
EOF
git ls-remote --symref . HEAD >actual &&
test_cmp expect actual
diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh
index 579872c258..e3482b27b2 100755
--- a/t/t5514-fetch-multiple.sh
+++ b/t/t5514-fetch-multiple.sh
@@ -45,14 +45,17 @@ test_expect_success setup '
'
cat > test/expect << EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -97,6 +100,7 @@ cat > expect << EOF
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
@@ -112,8 +116,10 @@ test_expect_success 'git fetch --multiple (but only one remote)' '
'
cat > expect << EOF
+ one/HEAD -> one/main
one/main
one/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -141,7 +147,7 @@ test_expect_success 'git fetch --multiple (bad remote names)' '
test_expect_success 'git fetch --all (skipFetchAll)' '
(cd test4 &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -153,11 +159,14 @@ test_expect_success 'git fetch --all (skipFetchAll)' '
'
cat > expect << EOF
+ one/HEAD -> one/main
one/main
one/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -165,7 +174,7 @@ EOF
test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '
(cd test4 &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -221,14 +230,17 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' '
create_fetch_all_expect () {
cat >expect <<-\EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -265,6 +277,7 @@ test_expect_success 'git fetch (fetch all remotes with fetch.all = true)' '
create_fetch_one_expect () {
cat >expect <<-\EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 331778bd42..5a051aa0c7 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -1395,7 +1395,8 @@ test_expect_success 'fetch follows tags by default' '
git tag -m "annotated" tag &&
git for-each-ref >tmp1 &&
sed -n "p; s|refs/heads/main$|refs/remotes/origin/main|p" tmp1 |
- sort -k 3 >../expect
+ sed -n "p; s|refs/heads/main$|refs/remotes/origin/HEAD|p" |
+ sort -k 4 >../expect
) &&
test_when_finished "rm -rf dst" &&
git init dst &&
diff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh
index 98ece27c6a..d3996af6ee 100755
--- a/t/t5527-fetch-odd-refs.sh
+++ b/t/t5527-fetch-odd-refs.sh
@@ -52,7 +52,8 @@ test_expect_success LONG_REF 'fetch handles extremely long refname' '
long
main
EOF
- git for-each-ref --format="%(subject)" refs/remotes/long >actual &&
+ git for-each-ref --format="%(subject)" refs/remotes/long \
+ --exclude=refs/remotes/long/HEAD >actual &&
test_cmp expect actual
'
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index a66d0e089d..88331d1ceb 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -329,7 +329,8 @@ test_expect_success 'incremental-repack task' '
# Delete refs that have not been repacked in these packs.
git for-each-ref --format="delete %(refname)" \
- refs/prefetch refs/tags refs/remotes >refs &&
+ refs/prefetch refs/tags refs/remotes \
+ --exclude=refs/remotes/*/HEAD >refs &&
git update-ref --stdin <refs &&
# Replace the object directory with this pack layout.
diff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh
index 027235d61a..a81662713e 100755
--- a/t/t9210-scalar.sh
+++ b/t/t9210-scalar.sh
@@ -150,7 +150,8 @@ test_expect_success 'scalar clone' '
"$(pwd)" &&
git for-each-ref --format="%(refname)" refs/remotes/origin/ >actual &&
- echo "refs/remotes/origin/parallel" >expect &&
+ echo "refs/remotes/origin/HEAD" >>expect &&
+ echo "refs/remotes/origin/parallel" >>expect &&
test_cmp expect actual &&
test_path_is_missing 1/2 &&
@@ -219,7 +220,7 @@ test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '
done
'
- test_expect_success 'scalar reconfigure --all with detached HEADs' '
+test_expect_success 'scalar reconfigure --all with detached HEADs' '
repos="two three four" &&
for num in $repos
do
diff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh
index 7869f45ee6..01f71910f5 100755
--- a/t/t9211-scalar-clone.sh
+++ b/t/t9211-scalar-clone.sh
@@ -31,7 +31,7 @@ test_expect_success 'set up repository to clone' '
)
'
-cleanup_clone () {
+cleanup_clone() {
rm -rf "$1"
}
@@ -127,7 +127,7 @@ test_expect_success '--single-branch clones HEAD only' '
(
cd $enlistment/src &&
git for-each-ref refs/remotes/origin >out &&
- test_line_count = 1 out &&
+ test_line_count = 2 out &&
grep "refs/remotes/origin/base" out
) &&
@@ -141,7 +141,7 @@ test_expect_success '--no-single-branch clones all branches' '
(
cd $enlistment/src &&
git for-each-ref refs/remotes/origin >out &&
- test_line_count = 2 out &&
+ test_line_count = 3 out &&
grep "refs/remotes/origin/base" out &&
grep "refs/remotes/origin/parallel" out
) &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index cc6aa9f0cd..b663c4609e 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -658,6 +658,7 @@ test_expect_success '__git_refs - simple' '
HEAD
main
matching-branch
+ other/HEAD
other/branch-in-other
other/main-in-other
matching-tag
@@ -673,6 +674,7 @@ test_expect_success '__git_refs - full refs' '
cat >expected <<-EOF &&
refs/heads/main
refs/heads/matching-branch
+ refs/remotes/other/HEAD
refs/remotes/other/branch-in-other
refs/remotes/other/main-in-other
refs/tags/matching-tag
@@ -729,6 +731,7 @@ test_expect_success '__git_refs - remote on local file system - full refs' '
test_expect_success '__git_refs - configured remote' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -756,6 +759,7 @@ test_expect_success '__git_refs - configured remote - full refs' '
test_expect_success '__git_refs - configured remote - repo given on the command line' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -787,6 +791,7 @@ test_expect_success '__git_refs - configured remote - full refs - repo given on
test_expect_success '__git_refs - configured remote - remote name matches a directory' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -875,12 +880,14 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer
HEAD
main
matching-branch
+ other/HEAD
other/ambiguous
other/branch-in-other
other/main-in-other
remote/ambiguous
remote/branch-in-remote
matching-tag
+ HEAD
branch-in-other
branch-in-remote
main-in-other
@@ -904,6 +911,7 @@ test_expect_success '__git_refs - after --opt=' '
HEAD
main
matching-branch
+ other/HEAD
other/branch-in-other
other/main-in-other
matching-tag
@@ -919,6 +927,7 @@ test_expect_success '__git_refs - after --opt= - full refs' '
cat >expected <<-EOF &&
refs/heads/main
refs/heads/matching-branch
+ refs/remotes/other/HEAD
refs/remotes/other/branch-in-other
refs/remotes/other/main-in-other
refs/tags/matching-tag
@@ -935,6 +944,7 @@ test_expect_success '__git refs - excluding refs' '
^HEAD
^main
^matching-branch
+ ^other/HEAD
^other/branch-in-other
^other/main-in-other
^matching-tag
@@ -950,6 +960,7 @@ test_expect_success '__git refs - excluding full refs' '
cat >expected <<-EOF &&
^refs/heads/main
^refs/heads/matching-branch
+ ^refs/remotes/other/HEAD
^refs/remotes/other/branch-in-other
^refs/remotes/other/main-in-other
^refs/tags/matching-tag
@@ -975,6 +986,7 @@ test_expect_success '__git_refs - do not filter refs unless told so' '
main
matching-branch
matching/branch
+ other/HEAD
other/branch-in-other
other/main-in-other
other/matching/branch-in-other
@@ -1095,6 +1107,7 @@ test_expect_success '__git_complete_refs - simple' '
HEAD Z
main Z
matching-branch Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
matching-tag Z
@@ -1123,6 +1136,7 @@ test_expect_success '__git_complete_refs - matching' '
test_expect_success '__git_complete_refs - remote' '
sed -e "s/Z$//" >expected <<-EOF &&
HEAD Z
+ HEAD Z
branch-in-other Z
main-in-other Z
EOF
@@ -1139,9 +1153,11 @@ test_expect_success '__git_complete_refs - track' '
HEAD Z
main Z
matching-branch Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
matching-tag Z
+ HEAD Z
branch-in-other Z
main-in-other Z
EOF
@@ -1184,6 +1200,7 @@ test_expect_success '__git_complete_refs - suffix' '
HEAD.
main.
matching-branch.
+ other/HEAD.
other/branch-in-other.
other/main-in-other.
matching-tag.
@@ -1199,6 +1216,7 @@ test_expect_success '__git_complete_refs - suffix' '
test_expect_success '__git_complete_fetch_refspecs - simple' '
sed -e "s/Z$//" >expected <<-EOF &&
HEAD:HEAD Z
+ HEAD:HEAD Z
branch-in-other:branch-in-other Z
main-in-other:main-in-other Z
EOF
@@ -1225,6 +1243,7 @@ test_expect_success '__git_complete_fetch_refspecs - matching' '
test_expect_success '__git_complete_fetch_refspecs - prefix' '
sed -e "s/Z$//" >expected <<-EOF &&
+HEAD:HEAD Z
+ +HEAD:HEAD Z
+branch-in-other:branch-in-other Z
+main-in-other:main-in-other Z
EOF
@@ -1289,6 +1308,7 @@ test_expect_success '__git_complete_worktree_paths with -C' '
test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '
test_completion "git switch " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1435,11 +1455,13 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena
test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '
test_completion "git checkout " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1461,6 +1483,7 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl
test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '
GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git switch --guess " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1470,6 +1493,7 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G
test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '
test_completion "git switch --no-guess --guess " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1490,6 +1514,7 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1498,11 +1523,13 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete
test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '
GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git checkout --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1514,6 +1541,7 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1522,11 +1550,13 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '
test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '
test_completion "git checkout --no-guess --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1538,6 +1568,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1550,6 +1581,7 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1559,11 +1591,13 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a
test_config checkout.guess true &&
test_completion "git checkout " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1573,11 +1607,13 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.
test_config checkout.guess false &&
test_completion "git checkout --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1590,6 +1626,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1601,6 +1638,7 @@ test_expect_success 'git switch - with --detach, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1612,6 +1650,7 @@ test_expect_success 'git checkout - with --detach, complete only references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1783,6 +1822,7 @@ test_expect_success 'git switch - with -d, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1794,6 +1834,7 @@ test_expect_success 'git checkout - with -d, complete only references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1801,10 +1842,12 @@ test_expect_success 'git checkout - with -d, complete only references' '
test_expect_success 'git switch - with --track, complete only remote branches' '
test_completion "git switch --track " <<-\EOF &&
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
test_completion "git switch -t " <<-\EOF
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1812,10 +1855,12 @@ test_expect_success 'git switch - with --track, complete only remote branches' '
test_expect_success 'git checkout - with --track, complete only remote branches' '
test_completion "git checkout --track " <<-\EOF &&
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
test_completion "git checkout -t " <<-\EOF
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1834,6 +1879,7 @@ test_expect_success 'git checkout - with --no-track, complete only local referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1845,6 +1891,7 @@ test_expect_success 'git switch - with -c, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1856,6 +1903,7 @@ test_expect_success 'git switch - with -C, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1867,6 +1915,7 @@ test_expect_success 'git switch - with -c and --track, complete all references'
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1878,6 +1927,7 @@ test_expect_success 'git switch - with -C and --track, complete all references'
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1889,6 +1939,7 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1900,6 +1951,7 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1911,6 +1963,7 @@ test_expect_success 'git checkout - with -b, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1922,6 +1975,7 @@ test_expect_success 'git checkout - with -B, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1933,6 +1987,7 @@ test_expect_success 'git checkout - with -b and --track, complete all references
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1944,6 +1999,7 @@ test_expect_success 'git checkout - with -B and --track, complete all references
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1955,6 +2011,7 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1966,6 +2023,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1973,6 +2031,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen
test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '
test_completion "git switch -c " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1982,6 +2041,7 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem
test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '
test_completion "git switch -C " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2019,6 +2079,7 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche
test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '
test_completion "git checkout -b " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2028,6 +2089,7 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r
test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '
test_completion "git checkout -B " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2065,6 +2127,7 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc
test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '
test_completion "git switch --orphan " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2080,6 +2143,7 @@ test_expect_success 'git switch - --orphan with branch already provided complete
test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '
test_completion "git checkout --orphan " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2093,6 +2157,7 @@ test_expect_success 'git checkout - --orphan with branch already provided comple
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
--
2.47.0.7.g072c39eddb.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref
2024-10-12 23:03 ` [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
` (4 preceding siblings ...)
2024-10-12 23:03 ` [PATCH v7 6/6] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
@ 2024-10-13 13:52 ` Phillip Wood
2024-10-13 21:24 ` Bence Ferdinandy
2024-10-14 22:53 ` [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7 Bence Ferdinandy
6 siblings, 1 reply; 258+ messages in thread
From: Phillip Wood @ 2024-10-13 13:52 UTC (permalink / raw)
To: Bence Ferdinandy, git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano
Hi Bence
On 13/10/2024 00:03, Bence Ferdinandy wrote:
> When updating a symref it's currently not possible to know for sure what
> was the previous value that was overwritten.
It is if you use a ref transaction rather than call refs_update_symref()
and query the ref after calling ref_transaction_prepare() and before
calling ref_transaction_commit() which is what the code below does.
> Record the value after the
> ref has been locked if the caller of refs_update_symref requests it via
> a new variable in the function call.
To me this patch and patch 5 feel quite disruptive to all the existing
callers which don't need this specialized functionality. I think it
would be less disruptive over all if you used a ref transaction rather
than calling refs_update_symref() in the final patch. That would enable
us to keep the simpler interface for refs_update_symref().
I'm also not sure about the proposed interface I would have thought it
would be simpler to take a "char**" rather than an "struct strbuf*" if
we do decide that it is useful for callers of refs_update_symref() to
query the old value.
Best Wishes
Phillip
> Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
> ---
>
> Notes:
> v4: new patch
>
> v5: - added before_target to reftables backend
> - added an extra safety check for transaction's existence in refs.c
>
> v6: - no change
>
> v7: - remove the whole before_target concept from the backends and
> handle checking it in refs.c instead (thanks Karthik)
> - rename the before_target to referent which is how the same concept
> is called in the backends
> - change commit prefix to be more in line with project standards
>
> builtin/branch.c | 2 +-
> builtin/checkout.c | 4 ++--
> builtin/clone.c | 6 +++---
> builtin/notes.c | 2 +-
> builtin/remote.c | 6 +++---
> builtin/symbolic-ref.c | 2 +-
> builtin/worktree.c | 2 +-
> refs.c | 16 +++++++++++-----
> refs.h | 3 ++-
> reset.c | 2 +-
> sequencer.c | 2 +-
> setup.c | 2 +-
> t/helper/test-ref-store.c | 2 +-
> 13 files changed, 29 insertions(+), 22 deletions(-)
>
> diff --git a/builtin/branch.c b/builtin/branch.c
> index fd1611ebf5..6c87690b58 100644
> --- a/builtin/branch.c
> +++ b/builtin/branch.c
> @@ -559,7 +559,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,
> continue;
>
> refs = get_worktree_ref_store(worktrees[i]);
> - if (refs_update_symref(refs, "HEAD", newref, logmsg))
> + if (refs_update_symref(refs, "HEAD", newref, logmsg, NULL))
> ret = error(_("HEAD of working tree %s is not updated"),
> worktrees[i]->path);
> }
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index 9c30000d3a..356ee9bcde 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -1015,7 +1015,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
> describe_detached_head(_("HEAD is now at"), new_branch_info->commit);
> }
> } else if (new_branch_info->path) { /* Switch branches. */
> - if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", new_branch_info->path, msg.buf) < 0)
> + if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", new_branch_info->path, msg.buf, NULL) < 0)
> die(_("unable to update HEAD"));
> if (!opts->quiet) {
> if (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) {
> @@ -1479,7 +1479,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)
> die(_("You are on a branch yet to be born"));
> strbuf_addf(&branch_ref, "refs/heads/%s", opts->new_branch);
> status = refs_update_symref(get_main_ref_store(the_repository),
> - "HEAD", branch_ref.buf, "checkout -b");
> + "HEAD", branch_ref.buf, "checkout -b", NULL);
> strbuf_release(&branch_ref);
> if (!opts->quiet)
> fprintf(stderr, _("Switched to a new branch '%s'\n"),
> diff --git a/builtin/clone.c b/builtin/clone.c
> index e77339c847..ead2af20ea 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -661,7 +661,7 @@ static void update_remote_refs(const struct ref *refs,
> strbuf_addstr(&head_ref, "HEAD");
> if (refs_update_symref(get_main_ref_store(the_repository), head_ref.buf,
> remote_head_points_at->peer_ref->name,
> - msg) < 0)
> + msg, NULL) < 0)
> die(_("unable to update %s"), head_ref.buf);
> strbuf_release(&head_ref);
> }
> @@ -673,7 +673,7 @@ static void update_head(const struct ref *our, const struct ref *remote,
> const char *head;
> if (our && skip_prefix(our->name, "refs/heads/", &head)) {
> /* Local default branch link */
> - if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", our->name, NULL) < 0)
> + if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", our->name, NULL, NULL) < 0)
> die(_("unable to update HEAD"));
> if (!option_bare) {
> refs_update_ref(get_main_ref_store(the_repository),
> @@ -702,7 +702,7 @@ static void update_head(const struct ref *our, const struct ref *remote,
> * Unborn head from remote; same as "our" case above except
> * that we have no ref to update.
> */
> - if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", unborn, NULL) < 0)
> + if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", unborn, NULL, NULL) < 0)
> die(_("unable to update HEAD"));
> if (!option_bare)
> install_branch_config(0, head, remote_name, unborn);
> diff --git a/builtin/notes.c b/builtin/notes.c
> index 8c26e45526..ba646f06ff 100644
> --- a/builtin/notes.c
> +++ b/builtin/notes.c
> @@ -980,7 +980,7 @@ static int merge(int argc, const char **argv, const char *prefix)
> die(_("a notes merge into %s is already in-progress at %s"),
> notes_ref, wt->path);
> free_worktrees(worktrees);
> - if (refs_update_symref(get_main_ref_store(the_repository), "NOTES_MERGE_REF", notes_ref, NULL))
> + if (refs_update_symref(get_main_ref_store(the_repository), "NOTES_MERGE_REF", notes_ref, NULL, NULL))
> die(_("failed to store link to current notes ref (%s)"),
> notes_ref);
> fprintf(stderr, _("Automatic notes merge failed. Fix conflicts in %s "
> diff --git a/builtin/remote.c b/builtin/remote.c
> index 76670ddd8b..d8ff440027 100644
> --- a/builtin/remote.c
> +++ b/builtin/remote.c
> @@ -244,7 +244,7 @@ static int add(int argc, const char **argv, const char *prefix)
> strbuf_reset(&buf2);
> strbuf_addf(&buf2, "refs/remotes/%s/%s", name, master);
>
> - if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote add"))
> + if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote add", NULL))
> result = error(_("Could not setup master '%s'"), master);
> }
>
> @@ -864,7 +864,7 @@ static int mv(int argc, const char **argv, const char *prefix)
> strbuf_reset(&buf3);
> strbuf_addf(&buf3, "remote: renamed %s to %s",
> item->string, buf.buf);
> - if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf))
> + if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf, NULL))
> die(_("creating '%s' failed"), buf.buf);
> display_progress(progress, ++refs_renamed_nr);
> }
> @@ -1444,7 +1444,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
> /* make sure it's valid */
> if (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))
> result |= error(_("Not a valid ref: %s"), buf2.buf);
> - else if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote set-head"))
> + else if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote set-head", NULL))
> result |= error(_("Could not setup %s"), buf.buf);
> else if (opt_a)
> printf("%s/HEAD set to %s\n", argv[0], head_name);
> diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c
> index 299d23d76a..7728fbc3c1 100644
> --- a/builtin/symbolic-ref.c
> +++ b/builtin/symbolic-ref.c
> @@ -88,7 +88,7 @@ int cmd_symbolic_ref(int argc,
> if (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0)
> die("Refusing to set '%s' to invalid ref '%s'", argv[0], argv[1]);
> ret = !!refs_update_symref(get_main_ref_store(the_repository),
> - argv[0], argv[1], msg);
> + argv[0], argv[1], msg, NULL);
> break;
> default:
> usage_with_options(git_symbolic_ref_usage, options);
> diff --git a/builtin/worktree.c b/builtin/worktree.c
> index fc31d072a6..a7ab4193c1 100644
> --- a/builtin/worktree.c
> +++ b/builtin/worktree.c
> @@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,
> ret = refs_update_ref(wt_refs, NULL, "HEAD", &commit->object.oid,
> NULL, 0, UPDATE_REFS_MSG_ON_ERR);
> else
> - ret = refs_update_symref(wt_refs, "HEAD", symref.buf, NULL);
> + ret = refs_update_symref(wt_refs, "HEAD", symref.buf, NULL, NULL);
> if (ret)
> goto done;
>
> diff --git a/refs.c b/refs.c
> index 5f729ed412..b964ac44d0 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -2114,7 +2114,8 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
> }
>
> int refs_update_symref(struct ref_store *refs, const char *ref,
> - const char *target, const char *logmsg)
> + const char *target, const char *logmsg,
> + struct strbuf *referent)
> {
> struct ref_transaction *transaction;
> struct strbuf err = STRBUF_INIT;
> @@ -2122,17 +2123,23 @@ int refs_update_symref(struct ref_store *refs, const char *ref,
>
> transaction = ref_store_transaction_begin(refs, &err);
> if (!transaction ||
> - ref_transaction_update(transaction, ref, NULL, NULL,
> + ref_transaction_update(transaction, ref, NULL, NULL,
> target, NULL, REF_NO_DEREF,
> logmsg, &err) ||
> - ref_transaction_commit(transaction, &err)) {
> + ref_transaction_prepare(transaction, &err)) {
> ret = error("%s", err.buf);
> + goto cleanup;
> }
> + if (referent)
> + refs_read_symbolic_ref(refs, ref, referent);
> +
> + if (ref_transaction_commit(transaction, &err))
> + ret = error("%s", err.buf);
>
> +cleanup:
> strbuf_release(&err);
> if (transaction)
> ref_transaction_free(transaction);
> -
> return ret;
> }
>
> @@ -2948,4 +2955,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)
> return (update->flags & REF_HAVE_OLD) &&
> (!is_null_oid(&update->old_oid) || update->old_target);
> }
> -
> diff --git a/refs.h b/refs.h
> index 108dfc93b3..b09a3a4384 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -571,7 +571,8 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
> const char *newref, const char *logmsg);
>
> int refs_update_symref(struct ref_store *refs, const char *refname,
> - const char *target, const char *logmsg);
> + const char *target, const char *logmsg,
> + struct strbuf *referent);
>
> enum action_on_err {
> UPDATE_REFS_MSG_ON_ERR,
> diff --git a/reset.c b/reset.c
> index b22b1be792..cc36a9ed56 100644
> --- a/reset.c
> +++ b/reset.c
> @@ -76,7 +76,7 @@ static int update_refs(const struct reset_head_opts *opts,
> if (!ret)
> ret = refs_update_symref(get_main_ref_store(the_repository),
> "HEAD", switch_to_branch,
> - reflog_head);
> + reflog_head, NULL);
> }
> if (!ret && run_hook)
> run_hooks_l(the_repository, "post-checkout",
> diff --git a/sequencer.c b/sequencer.c
> index 8d01cd50ac..23b162924c 100644
> --- a/sequencer.c
> +++ b/sequencer.c
> @@ -5107,7 +5107,7 @@ static int pick_commits(struct repository *r,
> }
> msg = reflog_message(opts, "finish", "returning to %s",
> head_ref.buf);
> - if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", head_ref.buf, msg)) {
> + if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", head_ref.buf, msg, NULL)) {
> res = error(_("could not update HEAD to %s"),
> head_ref.buf);
> goto cleanup_head_ref;
> diff --git a/setup.c b/setup.c
> index 94e79b2e48..d95f051465 100644
> --- a/setup.c
> +++ b/setup.c
> @@ -2275,7 +2275,7 @@ void create_reference_database(enum ref_storage_format ref_storage_format,
> die(_("invalid initial branch name: '%s'"),
> initial_branch);
>
> - if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", ref, NULL) < 0)
> + if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", ref, NULL, NULL) < 0)
> exit(1);
> free(ref);
> }
> diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
> index 65346dee55..a911302bea 100644
> --- a/t/helper/test-ref-store.c
> +++ b/t/helper/test-ref-store.c
> @@ -120,7 +120,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)
> const char *target = notnull(*argv++, "target");
> const char *logmsg = *argv++;
>
> - return refs_update_symref(refs, refname, target, logmsg);
> + return refs_update_symref(refs, refname, target, logmsg, NULL);
> }
>
> static struct flag_definition transaction_flags[] = {
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref
2024-10-13 13:52 ` [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref Phillip Wood
@ 2024-10-13 21:24 ` Bence Ferdinandy
2024-10-15 14:05 ` Phillip Wood
0 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-13 21:24 UTC (permalink / raw)
To: phillip.wood, git
Cc: Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano
On Sun Oct 13, 2024 at 15:52, Phillip Wood <phillip.wood123@gmail.com> wrote:
> Hi Bence
>
> On 13/10/2024 00:03, Bence Ferdinandy wrote:
>> When updating a symref it's currently not possible to know for sure what
>> was the previous value that was overwritten.
>
> It is if you use a ref transaction rather than call refs_update_symref()
> and query the ref after calling ref_transaction_prepare() and before
> calling ref_transaction_commit() which is what the code below does.
Yeah, it would be more clear if that sentence would say "when using
update_symref".
>
>> Record the value after the
>> ref has been locked if the caller of refs_update_symref requests it via
>> a new variable in the function call.
>
> To me this patch and patch 5 feel quite disruptive to all the existing
> callers which don't need this specialized functionality. I think it
> would be less disruptive over all if you used a ref transaction rather
> than calling refs_update_symref() in the final patch. That would enable
> us to keep the simpler interface for refs_update_symref().
The extra parameter introduced here is actually used in two places by the end
of the series, in remote set-head and fetch (of course you could make a similar
argument for the functionality added in 5/6 which is only used in fetch by the
final patch). To avoid code duplication I think even if we did not touch
refs_update_symref() it would make sense to create
a "refs_update_symref_extended()" and make refs_update_symref() a wrapper
around that with a few less parameters. That would be similar to how
refs_update_symref() and refs_update_ref() predetermine a couple of parameters
to say transaction_update().
Currently there are 15 calls to refs_update_symref() in total, of these
5 do not use the complete functionality of the function (they pass NULL as
logmsg), so the current implementation would not be completely unprecedented.
(This tally did make me catch an error on my side: the logmsg in fetch's
set_head should be "fetch" not "remote set-head", I'll fix that in a v8).
Imho, even if I manage to come up with a better name than
"refs_update_symref_extended()" wouldn't it be more confusing to have two ways
to update symrefs via a function call, rather than have one, where _usually_
you pass two NULL-s at the end?
>
> I'm also not sure about the proposed interface I would have thought it
> would be simpler to take a "char**" rather than an "struct strbuf*" if
> we do decide that it is useful for callers of refs_update_symref() to
> query the old value.
refs_read_symbolic_ref requires a strbuf, so one would need to be created
anyway and I also sort of got the feeling that the project likes to handle refs
in strbufs (which may be wrong). Are there any downsides I'm not seeing?
Thanks,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref
2024-10-13 21:24 ` Bence Ferdinandy
@ 2024-10-15 14:05 ` Phillip Wood
2024-10-15 17:25 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Phillip Wood @ 2024-10-15 14:05 UTC (permalink / raw)
To: Bence Ferdinandy, phillip.wood, git
Cc: Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano
Hi Bence
On 13/10/2024 22:24, Bence Ferdinandy wrote:
>
> On Sun Oct 13, 2024 at 15:52, Phillip Wood <phillip.wood123@gmail.com> wrote:
>> On 13/10/2024 00:03, Bence Ferdinandy wrote:
>>> Record the value after the
>>> ref has been locked if the caller of refs_update_symref requests it via
>>> a new variable in the function call.
>>
>> To me this patch and patch 5 feel quite disruptive to all the existing
>> callers which don't need this specialized functionality. I think it
>> would be less disruptive over all if you used a ref transaction rather
>> than calling refs_update_symref() in the final patch. That would enable
>> us to keep the simpler interface for refs_update_symref().
>
> The extra parameter introduced here is actually used in two places by the end
> of the series, in remote set-head and fetch (of course you could make a similar
> argument for the functionality added in 5/6 which is only used in fetch by the
> final patch). To avoid code duplication I think even if we did not touch
> refs_update_symref() it would make sense to create
> a "refs_update_symref_extended()" and make refs_update_symref() a wrapper
> around that with a few less parameters. That would be similar to how
> refs_update_symref() and refs_update_ref() predetermine a couple of parameters
> to say transaction_update().
>
> Currently there are 15 calls to refs_update_symref() in total, of these
> 5 do not use the complete functionality of the function (they pass NULL as
> logmsg), so the current implementation would not be completely unprecedented.
As those figures show it's pretty unusual not to pass a reflog message
when updating a ref, on the other hand it is very unusual to want the
old value so I don't think the two are comparable. At a high level the
two callers that want to be able to check the old value are both doing
essentially the same thing so can we create a specialized function that
encapsulates the functionality needed by --set-head and uses a ref
transaction?
> (This tally did make me catch an error on my side: the logmsg in fetch's
> set_head should be "fetch" not "remote set-head", I'll fix that in a v8).
>
> Imho, even if I manage to come up with a better name than
> "refs_update_symref_extended()" wouldn't it be more confusing to have two ways
> to update symrefs via a function call, rather than have one, where _usually_
> you pass two NULL-s at the end?
>
>>
>> I'm also not sure about the proposed interface I would have thought it
>> would be simpler to take a "char**" rather than an "struct strbuf*" if
>> we do decide that it is useful for callers of refs_update_symref() to
>> query the old value.
>
> refs_read_symbolic_ref requires a strbuf, so one would need to be created
> anyway and I also sort of got the feeling that the project likes to handle refs
> in strbufs (which may be wrong). Are there any downsides I'm not seeing?
It's true that refs_read_symbolic_ref takes and strbuf. I'd argue that's
a mistake for a function that is just returning a string in an "out"
parameter as I think it is more continent for the caller not to have to
initialize an strbuf just to retrieve the target of a symbolic ref. I
alse think that it is inconsistent with functions like
refs_resolve_refdup() that return a string.
Best Wishes
Phillip
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref
2024-10-15 14:05 ` Phillip Wood
@ 2024-10-15 17:25 ` Bence Ferdinandy
2024-10-19 22:53 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-15 17:25 UTC (permalink / raw)
To: phillip.wood, git
Cc: Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano
On Tue Oct 15, 2024 at 16:05, Phillip Wood <phillip.wood123@gmail.com> wrote:
> Hi Bence
>
> On 13/10/2024 22:24, Bence Ferdinandy wrote:
>>
>> On Sun Oct 13, 2024 at 15:52, Phillip Wood <phillip.wood123@gmail.com> wrote:
>>> On 13/10/2024 00:03, Bence Ferdinandy wrote:
>>>> Record the value after the
>>>> ref has been locked if the caller of refs_update_symref requests it via
>>>> a new variable in the function call.
>>>
>>> To me this patch and patch 5 feel quite disruptive to all the existing
>>> callers which don't need this specialized functionality. I think it
>>> would be less disruptive over all if you used a ref transaction rather
>>> than calling refs_update_symref() in the final patch. That would enable
>>> us to keep the simpler interface for refs_update_symref().
>>
>> The extra parameter introduced here is actually used in two places by the end
>> of the series, in remote set-head and fetch (of course you could make a similar
>> argument for the functionality added in 5/6 which is only used in fetch by the
>> final patch). To avoid code duplication I think even if we did not touch
>> refs_update_symref() it would make sense to create
>> a "refs_update_symref_extended()" and make refs_update_symref() a wrapper
>> around that with a few less parameters. That would be similar to how
>> refs_update_symref() and refs_update_ref() predetermine a couple of parameters
>> to say transaction_update().
>>
>> Currently there are 15 calls to refs_update_symref() in total, of these
>> 5 do not use the complete functionality of the function (they pass NULL as
>> logmsg), so the current implementation would not be completely unprecedented.
>
> As those figures show it's pretty unusual not to pass a reflog message
> when updating a ref, on the other hand it is very unusual to want the
> old value so I don't think the two are comparable. At a high level the
> two callers that want to be able to check the old value are both doing
> essentially the same thing so can we create a specialized function that
> encapsulates the functionality needed by --set-head and uses a ref
> transaction?
Ok, so let's not change the signature of refs_update_symref(). On the other
hand the best approach here is still not quite clear to me. As it is now, the
series added two extra parameters to refs_update_symref(), the referent and the
create_only option. Obviously any new function would also take all the
parameters refs_update_symref takes. Should I rename the current (v8)
refs_update_symref to something else (name suggestion would be welcome,
refs_update_symref_extended?, refs_update_symref_check_current? checking the
currently existing symref is the common theme for the two new parameters) and
leave refs_update_symref as it is? Or is this really about just not having to
change all the already existing callers? Because in that case I'd just wrap the
new function, something like
int refs_update_symref(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg)
return refs_update_symref_extended(*refs, *ref, *target, *logmsg, NULL, 0);
Let me know what you think, I'll hold off on a v9 until I see more clearly here
on what would be preferred.
>
>> (This tally did make me catch an error on my side: the logmsg in fetch's
>> set_head should be "fetch" not "remote set-head", I'll fix that in a v8).
>>
>> Imho, even if I manage to come up with a better name than
>> "refs_update_symref_extended()" wouldn't it be more confusing to have two ways
>> to update symrefs via a function call, rather than have one, where _usually_
>> you pass two NULL-s at the end?
>>
>>>
>>> I'm also not sure about the proposed interface I would have thought it
>>> would be simpler to take a "char**" rather than an "struct strbuf*" if
>>> we do decide that it is useful for callers of refs_update_symref() to
>>> query the old value.
>>
>> refs_read_symbolic_ref requires a strbuf, so one would need to be created
>> anyway and I also sort of got the feeling that the project likes to handle refs
>> in strbufs (which may be wrong). Are there any downsides I'm not seeing?
>
> It's true that refs_read_symbolic_ref takes and strbuf. I'd argue that's
> a mistake for a function that is just returning a string in an "out"
> parameter as I think it is more continent for the caller not to have to
> initialize an strbuf just to retrieve the target of a symbolic ref. I
> alse think that it is inconsistent with functions like
> refs_resolve_refdup() that return a string.
Ok, I'll change this to use **char. On the other hand, if
refs_read_symbolic_ref is inconsistent would it make sense to change it to also
use a **char instead of strbuf? There's only four calls to it including the one
I just added. Although I might rather do that _after_ this series is resolved :)
Thanks (to everyone) for your time!
Best,
Bence
--
bence.ferdinandy.com
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref
2024-10-15 17:25 ` Bence Ferdinandy
@ 2024-10-19 22:53 ` Bence Ferdinandy
0 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-19 22:53 UTC (permalink / raw)
To: phillip.wood, git
Cc: Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano
On Tue Oct 15, 2024 at 19:25, Bence Ferdinandy <bence@ferdinandy.com> wrote:
>
> On Tue Oct 15, 2024 at 16:05, Phillip Wood <phillip.wood123@gmail.com> wrote:
[snip]
>>>>
>>>> I'm also not sure about the proposed interface I would have thought it
>>>> would be simpler to take a "char**" rather than an "struct strbuf*" if
>>>> we do decide that it is useful for callers of refs_update_symref() to
>>>> query the old value.
>>>
>>> refs_read_symbolic_ref requires a strbuf, so one would need to be created
>>> anyway and I also sort of got the feeling that the project likes to handle refs
>>> in strbufs (which may be wrong). Are there any downsides I'm not seeing?
>>
>> It's true that refs_read_symbolic_ref takes and strbuf. I'd argue that's
>> a mistake for a function that is just returning a string in an "out"
>> parameter as I think it is more continent for the caller not to have to
>> initialize an strbuf just to retrieve the target of a symbolic ref. I
>> alse think that it is inconsistent with functions like
>> refs_resolve_refdup() that return a string.
>
> Ok, I'll change this to use **char. On the other hand, if
> refs_read_symbolic_ref is inconsistent would it make sense to change it to also
> use a **char instead of strbuf? There's only four calls to it including the one
> I just added. Although I might rather do that _after_ this series is resolved :)
I started doing this change, but ran into two things which I would yet again,
bring up in defence of strbuf. So not only refs_read_symbolic_ref makes use of strbuf, but
a) I also use it in refs_update_symref[_extended] to check if the caller
actually wants that referent or not (passing a NULL or a strbuf).
b) the consumer, report_set_head_auto check for !buf->len
Both of these sound way more convenient with strbuf than with *char. Ofc I'm
not exactly a C guru so ... Anyhow, v9 does not have this change.
Best,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7
2024-10-12 23:03 ` [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
` (5 preceding siblings ...)
2024-10-13 13:52 ` [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref Phillip Wood
@ 2024-10-14 22:53 ` Bence Ferdinandy
2024-10-14 22:53 ` [PATCH v8 1/6] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
` (6 more replies)
6 siblings, 7 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-14 22:53 UTC (permalink / raw)
To: git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano, Bence Ferdinandy, karthik.188
v8 is minor non-functional fix from v7
❯ git range-diff v7~6..v7 v8~6..v8
1: e4a2d01bca = 1: e4a2d01bca refs: atomically record overwritten ref in update_symref
2: e6c40ef04c = 2: e6c40ef04c remote set-head: add new variable for readability
3: aefa49aa0d = 3: aefa49aa0d remote set-head: better output for --auto
4: ad8907c6a3 = 4: ad8907c6a3 refs: add TRANSACTION_CREATE_EXISTS error
5: ff616d8b77 = 5: ff616d8b77 refs: add create_only option to refs_update_symref
6: 35b93f454c ! 6: 7b844b1dbb fetch: set remote/HEAD if it does not exist
@@ Notes
v7: - no change
+ v8: - changed logmsg in call to refs_update_symref from "remote
+ set-head" to "fetch"
+
## builtin/fetch.c ##
@@ builtin/fetch.c: static int backfill_tags(struct display_state *display_state,
return retcode;
@@ builtin/fetch.c: static int backfill_tags(struct display_state *display_state,
+ if (!refs_ref_exists(refs, b_remote_head.buf))
+ result = 1;
+ else if (refs_update_symref(refs, b_head.buf, b_remote_head.buf,
-+ "remote set-head", &b_local_head, 1))
++ "fetch", &b_local_head, 1))
+ result = 1;
+ else
+ report_set_head(remote, head_name, &b_local_head);
Bence Ferdinandy (6):
refs: atomically record overwritten ref in update_symref
remote set-head: add new variable for readability
remote set-head: better output for --auto
refs: add TRANSACTION_CREATE_EXISTS error
refs: add create_only option to refs_update_symref
fetch: set remote/HEAD if it does not exist
builtin/branch.c | 2 +-
builtin/checkout.c | 5 +-
builtin/clone.c | 8 +-
builtin/fetch.c | 82 +++++++++++
builtin/notes.c | 3 +-
builtin/remote.c | 47 +++++--
builtin/symbolic-ref.c | 2 +-
builtin/worktree.c | 2 +-
refs.c | 38 +++--
refs.h | 7 +-
refs/files-backend.c | 24 ++--
refs/reftable-backend.c | 6 +-
reset.c | 2 +-
sequencer.c | 3 +-
setup.c | 3 +-
t/helper/test-ref-store.c | 2 +-
t/t4207-log-decoration-colors.sh | 3 +-
t/t5505-remote.sh | 62 ++++++++-
t/t5510-fetch.sh | 229 ++++++++++++++++---------------
t/t5512-ls-remote.sh | 2 +
t/t5514-fetch-multiple.sh | 17 ++-
t/t5516-fetch-push.sh | 3 +-
t/t5527-fetch-odd-refs.sh | 3 +-
t/t7900-maintenance.sh | 3 +-
t/t9210-scalar.sh | 5 +-
t/t9211-scalar-clone.sh | 6 +-
t/t9902-completion.sh | 65 +++++++++
27 files changed, 469 insertions(+), 165 deletions(-)
--
2.47.0.7.g072c39eddb.dirty
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v8 1/6] refs: atomically record overwritten ref in update_symref
2024-10-14 22:53 ` [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7 Bence Ferdinandy
@ 2024-10-14 22:53 ` Bence Ferdinandy
2024-10-15 7:41 ` karthik nayak
2024-10-14 22:53 ` [PATCH v8 2/6] remote set-head: add new variable for readability Bence Ferdinandy
` (5 subsequent siblings)
6 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-14 22:53 UTC (permalink / raw)
To: git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano, Bence Ferdinandy, karthik.188
When updating a symref it's currently not possible to know for sure what
was the previous value that was overwritten. Record the value after the
ref has been locked if the caller of refs_update_symref requests it via
a new variable in the function call.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: - added before_target to reftables backend
- added an extra safety check for transaction's existence in refs.c
v6: - no change
v7: - remove the whole before_target concept from the backends and
handle checking it in refs.c instead (thanks Karthik)
- rename the before_target to referent which is how the same concept
is called in the backends
- change commit prefix to be more in line with project standards
v8: no change
builtin/branch.c | 2 +-
builtin/checkout.c | 4 ++--
builtin/clone.c | 6 +++---
builtin/notes.c | 2 +-
builtin/remote.c | 6 +++---
builtin/symbolic-ref.c | 2 +-
builtin/worktree.c | 2 +-
refs.c | 16 +++++++++++-----
refs.h | 3 ++-
reset.c | 2 +-
sequencer.c | 2 +-
setup.c | 2 +-
t/helper/test-ref-store.c | 2 +-
13 files changed, 29 insertions(+), 22 deletions(-)
diff --git a/builtin/branch.c b/builtin/branch.c
index fd1611ebf5..6c87690b58 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -559,7 +559,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,
continue;
refs = get_worktree_ref_store(worktrees[i]);
- if (refs_update_symref(refs, "HEAD", newref, logmsg))
+ if (refs_update_symref(refs, "HEAD", newref, logmsg, NULL))
ret = error(_("HEAD of working tree %s is not updated"),
worktrees[i]->path);
}
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 9c30000d3a..356ee9bcde 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1015,7 +1015,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
describe_detached_head(_("HEAD is now at"), new_branch_info->commit);
}
} else if (new_branch_info->path) { /* Switch branches. */
- if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", new_branch_info->path, msg.buf) < 0)
+ if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", new_branch_info->path, msg.buf, NULL) < 0)
die(_("unable to update HEAD"));
if (!opts->quiet) {
if (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) {
@@ -1479,7 +1479,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)
die(_("You are on a branch yet to be born"));
strbuf_addf(&branch_ref, "refs/heads/%s", opts->new_branch);
status = refs_update_symref(get_main_ref_store(the_repository),
- "HEAD", branch_ref.buf, "checkout -b");
+ "HEAD", branch_ref.buf, "checkout -b", NULL);
strbuf_release(&branch_ref);
if (!opts->quiet)
fprintf(stderr, _("Switched to a new branch '%s'\n"),
diff --git a/builtin/clone.c b/builtin/clone.c
index e77339c847..ead2af20ea 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -661,7 +661,7 @@ static void update_remote_refs(const struct ref *refs,
strbuf_addstr(&head_ref, "HEAD");
if (refs_update_symref(get_main_ref_store(the_repository), head_ref.buf,
remote_head_points_at->peer_ref->name,
- msg) < 0)
+ msg, NULL) < 0)
die(_("unable to update %s"), head_ref.buf);
strbuf_release(&head_ref);
}
@@ -673,7 +673,7 @@ static void update_head(const struct ref *our, const struct ref *remote,
const char *head;
if (our && skip_prefix(our->name, "refs/heads/", &head)) {
/* Local default branch link */
- if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", our->name, NULL) < 0)
+ if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", our->name, NULL, NULL) < 0)
die(_("unable to update HEAD"));
if (!option_bare) {
refs_update_ref(get_main_ref_store(the_repository),
@@ -702,7 +702,7 @@ static void update_head(const struct ref *our, const struct ref *remote,
* Unborn head from remote; same as "our" case above except
* that we have no ref to update.
*/
- if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", unborn, NULL) < 0)
+ if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", unborn, NULL, NULL) < 0)
die(_("unable to update HEAD"));
if (!option_bare)
install_branch_config(0, head, remote_name, unborn);
diff --git a/builtin/notes.c b/builtin/notes.c
index 8c26e45526..ba646f06ff 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -980,7 +980,7 @@ static int merge(int argc, const char **argv, const char *prefix)
die(_("a notes merge into %s is already in-progress at %s"),
notes_ref, wt->path);
free_worktrees(worktrees);
- if (refs_update_symref(get_main_ref_store(the_repository), "NOTES_MERGE_REF", notes_ref, NULL))
+ if (refs_update_symref(get_main_ref_store(the_repository), "NOTES_MERGE_REF", notes_ref, NULL, NULL))
die(_("failed to store link to current notes ref (%s)"),
notes_ref);
fprintf(stderr, _("Automatic notes merge failed. Fix conflicts in %s "
diff --git a/builtin/remote.c b/builtin/remote.c
index 76670ddd8b..d8ff440027 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -244,7 +244,7 @@ static int add(int argc, const char **argv, const char *prefix)
strbuf_reset(&buf2);
strbuf_addf(&buf2, "refs/remotes/%s/%s", name, master);
- if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote add"))
+ if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote add", NULL))
result = error(_("Could not setup master '%s'"), master);
}
@@ -864,7 +864,7 @@ static int mv(int argc, const char **argv, const char *prefix)
strbuf_reset(&buf3);
strbuf_addf(&buf3, "remote: renamed %s to %s",
item->string, buf.buf);
- if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf))
+ if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf, NULL))
die(_("creating '%s' failed"), buf.buf);
display_progress(progress, ++refs_renamed_nr);
}
@@ -1444,7 +1444,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
/* make sure it's valid */
if (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))
result |= error(_("Not a valid ref: %s"), buf2.buf);
- else if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote set-head"))
+ else if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote set-head", NULL))
result |= error(_("Could not setup %s"), buf.buf);
else if (opt_a)
printf("%s/HEAD set to %s\n", argv[0], head_name);
diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c
index 299d23d76a..7728fbc3c1 100644
--- a/builtin/symbolic-ref.c
+++ b/builtin/symbolic-ref.c
@@ -88,7 +88,7 @@ int cmd_symbolic_ref(int argc,
if (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0)
die("Refusing to set '%s' to invalid ref '%s'", argv[0], argv[1]);
ret = !!refs_update_symref(get_main_ref_store(the_repository),
- argv[0], argv[1], msg);
+ argv[0], argv[1], msg, NULL);
break;
default:
usage_with_options(git_symbolic_ref_usage, options);
diff --git a/builtin/worktree.c b/builtin/worktree.c
index fc31d072a6..a7ab4193c1 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,
ret = refs_update_ref(wt_refs, NULL, "HEAD", &commit->object.oid,
NULL, 0, UPDATE_REFS_MSG_ON_ERR);
else
- ret = refs_update_symref(wt_refs, "HEAD", symref.buf, NULL);
+ ret = refs_update_symref(wt_refs, "HEAD", symref.buf, NULL, NULL);
if (ret)
goto done;
diff --git a/refs.c b/refs.c
index 5f729ed412..b964ac44d0 100644
--- a/refs.c
+++ b/refs.c
@@ -2114,7 +2114,8 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
}
int refs_update_symref(struct ref_store *refs, const char *ref,
- const char *target, const char *logmsg)
+ const char *target, const char *logmsg,
+ struct strbuf *referent)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
@@ -2122,17 +2123,23 @@ int refs_update_symref(struct ref_store *refs, const char *ref,
transaction = ref_store_transaction_begin(refs, &err);
if (!transaction ||
- ref_transaction_update(transaction, ref, NULL, NULL,
+ ref_transaction_update(transaction, ref, NULL, NULL,
target, NULL, REF_NO_DEREF,
logmsg, &err) ||
- ref_transaction_commit(transaction, &err)) {
+ ref_transaction_prepare(transaction, &err)) {
ret = error("%s", err.buf);
+ goto cleanup;
}
+ if (referent)
+ refs_read_symbolic_ref(refs, ref, referent);
+
+ if (ref_transaction_commit(transaction, &err))
+ ret = error("%s", err.buf);
+cleanup:
strbuf_release(&err);
if (transaction)
ref_transaction_free(transaction);
-
return ret;
}
@@ -2948,4 +2955,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)
return (update->flags & REF_HAVE_OLD) &&
(!is_null_oid(&update->old_oid) || update->old_target);
}
-
diff --git a/refs.h b/refs.h
index 108dfc93b3..b09a3a4384 100644
--- a/refs.h
+++ b/refs.h
@@ -571,7 +571,8 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
const char *newref, const char *logmsg);
int refs_update_symref(struct ref_store *refs, const char *refname,
- const char *target, const char *logmsg);
+ const char *target, const char *logmsg,
+ struct strbuf *referent);
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
diff --git a/reset.c b/reset.c
index b22b1be792..cc36a9ed56 100644
--- a/reset.c
+++ b/reset.c
@@ -76,7 +76,7 @@ static int update_refs(const struct reset_head_opts *opts,
if (!ret)
ret = refs_update_symref(get_main_ref_store(the_repository),
"HEAD", switch_to_branch,
- reflog_head);
+ reflog_head, NULL);
}
if (!ret && run_hook)
run_hooks_l(the_repository, "post-checkout",
diff --git a/sequencer.c b/sequencer.c
index 8d01cd50ac..23b162924c 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -5107,7 +5107,7 @@ static int pick_commits(struct repository *r,
}
msg = reflog_message(opts, "finish", "returning to %s",
head_ref.buf);
- if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", head_ref.buf, msg)) {
+ if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", head_ref.buf, msg, NULL)) {
res = error(_("could not update HEAD to %s"),
head_ref.buf);
goto cleanup_head_ref;
diff --git a/setup.c b/setup.c
index 94e79b2e48..d95f051465 100644
--- a/setup.c
+++ b/setup.c
@@ -2275,7 +2275,7 @@ void create_reference_database(enum ref_storage_format ref_storage_format,
die(_("invalid initial branch name: '%s'"),
initial_branch);
- if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", ref, NULL) < 0)
+ if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", ref, NULL, NULL) < 0)
exit(1);
free(ref);
}
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index 65346dee55..a911302bea 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -120,7 +120,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)
const char *target = notnull(*argv++, "target");
const char *logmsg = *argv++;
- return refs_update_symref(refs, refname, target, logmsg);
+ return refs_update_symref(refs, refname, target, logmsg, NULL);
}
static struct flag_definition transaction_flags[] = {
--
2.47.0.7.g072c39eddb.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v8 1/6] refs: atomically record overwritten ref in update_symref
2024-10-14 22:53 ` [PATCH v8 1/6] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
@ 2024-10-15 7:41 ` karthik nayak
2024-10-15 16:24 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: karthik nayak @ 2024-10-15 7:41 UTC (permalink / raw)
To: Bence Ferdinandy, git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano
[-- Attachment #1: Type: text/plain, Size: 1452 bytes --]
Bence Ferdinandy <bence@ferdinandy.com> writes:
[snip]
> diff --git a/refs.c b/refs.c
> index 5f729ed412..b964ac44d0 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -2114,7 +2114,8 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
> }
>
> int refs_update_symref(struct ref_store *refs, const char *ref,
> - const char *target, const char *logmsg)
> + const char *target, const char *logmsg,
> + struct strbuf *referent)
> {
> struct ref_transaction *transaction;
> struct strbuf err = STRBUF_INIT;
> @@ -2122,17 +2123,23 @@ int refs_update_symref(struct ref_store *refs, const char *ref,
>
> transaction = ref_store_transaction_begin(refs, &err);
> if (!transaction ||
> - ref_transaction_update(transaction, ref, NULL, NULL,
> + ref_transaction_update(transaction, ref, NULL, NULL,
> target, NULL, REF_NO_DEREF,
> logmsg, &err) ||
> - ref_transaction_commit(transaction, &err)) {
> + ref_transaction_prepare(transaction, &err)) {
> ret = error("%s", err.buf);
> + goto cleanup;
> }
> + if (referent)
> + refs_read_symbolic_ref(refs, ref, referent);
Shouldn't we also check the return value here?
> +
> + if (ref_transaction_commit(transaction, &err))
> + ret = error("%s", err.buf);
>
> +cleanup:
> strbuf_release(&err);
> if (transaction)
> ref_transaction_free(transaction);
> -
>
Why remove this whiteline?
> return ret;
> }
>
[snip]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v8 1/6] refs: atomically record overwritten ref in update_symref
2024-10-15 7:41 ` karthik nayak
@ 2024-10-15 16:24 ` Bence Ferdinandy
0 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-15 16:24 UTC (permalink / raw)
To: karthik nayak, git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano
On Tue Oct 15, 2024 at 09:41, karthik nayak <karthik.188@gmail.com> wrote:
> Bence Ferdinandy <bence@ferdinandy.com> writes:
>
> [snip]
>
>> diff --git a/refs.c b/refs.c
>> index 5f729ed412..b964ac44d0 100644
>> --- a/refs.c
>> +++ b/refs.c
>> @@ -2114,7 +2114,8 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
>> }
>>
>> int refs_update_symref(struct ref_store *refs, const char *ref,
>> - const char *target, const char *logmsg)
>> + const char *target, const char *logmsg,
>> + struct strbuf *referent)
>> {
>> struct ref_transaction *transaction;
>> struct strbuf err = STRBUF_INIT;
>> @@ -2122,17 +2123,23 @@ int refs_update_symref(struct ref_store *refs, const char *ref,
>>
>> transaction = ref_store_transaction_begin(refs, &err);
>> if (!transaction ||
>> - ref_transaction_update(transaction, ref, NULL, NULL,
>> + ref_transaction_update(transaction, ref, NULL, NULL,
>> target, NULL, REF_NO_DEREF,
>> logmsg, &err) ||
>> - ref_transaction_commit(transaction, &err)) {
>> + ref_transaction_prepare(transaction, &err)) {
>> ret = error("%s", err.buf);
>> + goto cleanup;
>> }
>> + if (referent)
>> + refs_read_symbolic_ref(refs, ref, referent);
>
> Shouldn't we also check the return value here?
My reasoning was that if this fails referent will just look like as if it did
not exist. Since this is an addition to set-head and fetch failing to set the
HEAD in a case which would have previously worked I did not think it prudent to
now fail on this for any reason.
>
>> +
>> + if (ref_transaction_commit(transaction, &err))
>> + ret = error("%s", err.buf);
>>
>> +cleanup:
>> strbuf_release(&err);
>> if (transaction)
>> ref_transaction_free(transaction);
>> -
>>
>
> Why remove this whiteline?
Looks like a mistake made during rebase.
--
bence.ferdinandy.com
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v8 2/6] remote set-head: add new variable for readability
2024-10-14 22:53 ` [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7 Bence Ferdinandy
2024-10-14 22:53 ` [PATCH v8 1/6] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
@ 2024-10-14 22:53 ` Bence Ferdinandy
2024-10-14 22:53 ` [PATCH v8 3/6] remote set-head: better output for --auto Bence Ferdinandy
` (4 subsequent siblings)
6 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-14 22:53 UTC (permalink / raw)
To: git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano, Bence Ferdinandy, karthik.188
Instead of calling get_main_ref_store(the_repository) multiple times,
call it once and store in a new refs variable. Although this change
probably offers some performance benefits, the main purpose is to
shorten the line lengths of function calls using this variable for
better readability.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v5: new patch (split from the next patch as a preparatory step)
v6: no change
v7: - change commit prefix to be more in line with project standards
v8: no change
builtin/remote.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index d8ff440027..353ffd2c43 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1404,6 +1404,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
int i, opt_a = 0, opt_d = 0, result = 0;
struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
char *head_name = NULL;
+ struct ref_store *refs = get_main_ref_store(the_repository);
struct option options[] = {
OPT_BOOL('a', "auto", &opt_a,
@@ -1434,7 +1435,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
head_name = xstrdup(states.heads.items[0].string);
free_remote_ref_states(&states);
} else if (opt_d && !opt_a && argc == 1) {
- if (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF))
+ if (refs_delete_ref(refs, NULL, buf.buf, NULL, REF_NO_DEREF))
result |= error(_("Could not delete %s"), buf.buf);
} else
usage_with_options(builtin_remote_sethead_usage, options);
@@ -1442,9 +1443,9 @@ static int set_head(int argc, const char **argv, const char *prefix)
if (head_name) {
strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name);
/* make sure it's valid */
- if (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))
+ if (!refs_ref_exists(refs, buf2.buf))
result |= error(_("Not a valid ref: %s"), buf2.buf);
- else if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote set-head", NULL))
+ else if (refs_update_symref(refs, buf.buf, buf2.buf, "remote set-head", NULL))
result |= error(_("Could not setup %s"), buf.buf);
else if (opt_a)
printf("%s/HEAD set to %s\n", argv[0], head_name);
--
2.47.0.7.g072c39eddb.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v8 3/6] remote set-head: better output for --auto
2024-10-14 22:53 ` [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7 Bence Ferdinandy
2024-10-14 22:53 ` [PATCH v8 1/6] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
2024-10-14 22:53 ` [PATCH v8 2/6] remote set-head: add new variable for readability Bence Ferdinandy
@ 2024-10-14 22:53 ` Bence Ferdinandy
2024-10-15 7:47 ` karthik nayak
2024-10-14 22:53 ` [PATCH v8 4/6] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
` (3 subsequent siblings)
6 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-14 22:53 UTC (permalink / raw)
To: git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano, Bence Ferdinandy, karthik.188
Currently, set-head --auto will print a message saying "remote/HEAD set
to branch", which implies something was changed.
Change the output of --auto, so the output actually reflects what was
done: a) set a previously unset HEAD, b) change HEAD because remote
changed or c) no updates. As a fourth output, if HEAD is changed from
a previous value that was not a remote branch, explicitly call attention
to this fact.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v1-v2:
was RFC in https://lore.kernel.org/git/20240910203835.2288291-1-bence@ferdinandy.com/
v3:
This patch was originally sent along when I thought set-head was
going to be invoked by fetch, but the discussion on the RFC
concluded that it should be not. This opened the possibility to make
it more explicit.
Note: although I feel both things the patch does are really just
cosmetic, an argument could be made for breaking it into two, one
for the no-op part and one for the --auto print update.
Was sent in:
https://lore.kernel.org/git/20240915221055.904107-1-bence@ferdinandy.com/
v4:
- changes are now handled atomically via the ref update transaction
- outputs have changed along the lines of Junio's suggestion
- minor refactor to set_head for improved legibility
v5: - the minor refactor has been split out into its own patch
v6: - fixed uninitialized prev_head
- fixed case of unusual previous target
- fixed a test that would have been actually broken at this patch
(the output was only correct with the later update to fetch)
- added 4 tests for the 4 output cases
v7: - change commit prefix to be more in line with project standards
- fixed tests to also work with the reftable backend
- renamed report function, fixed style issue with checking buf len
- fixed not releasing an strbuf
v8: no change
builtin/remote.c | 35 ++++++++++++++++++++++++++++----
t/t5505-remote.sh | 51 ++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 81 insertions(+), 5 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 353ffd2c43..2b6948439f 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1399,10 +1399,35 @@ static int show(int argc, const char **argv, const char *prefix)
return result;
}
+static void report_set_head_auto(const char *remote, const char *head_name,
+ struct strbuf *buf_prev) {
+ struct strbuf buf_prefix = STRBUF_INIT;
+ const char *prev_head = NULL;
+
+ strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
+ skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);
+
+ if (prev_head && !strcmp(prev_head, head_name))
+ printf("'%s/HEAD' is unchanged and points to '%s'\n",
+ remote, head_name);
+ else if (prev_head)
+ printf("'%s/HEAD' has changed from '%s' and now points to '%s'\n",
+ remote, prev_head, head_name);
+ else if (!buf_prev->len)
+ printf("'%s/HEAD' is now created and points to '%s'\n",
+ remote, head_name);
+ else
+ printf("'%s/HEAD' used to point to '%s' "
+ "(which is not a remote branch), but now points to '%s'\n",
+ remote, buf_prev->buf, head_name);
+ strbuf_release(&buf_prefix);
+}
+
static int set_head(int argc, const char **argv, const char *prefix)
{
int i, opt_a = 0, opt_d = 0, result = 0;
- struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
+ struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT,
+ buf_prev = STRBUF_INIT;
char *head_name = NULL;
struct ref_store *refs = get_main_ref_store(the_repository);
@@ -1445,15 +1470,17 @@ static int set_head(int argc, const char **argv, const char *prefix)
/* make sure it's valid */
if (!refs_ref_exists(refs, buf2.buf))
result |= error(_("Not a valid ref: %s"), buf2.buf);
- else if (refs_update_symref(refs, buf.buf, buf2.buf, "remote set-head", NULL))
+ else if (refs_update_symref(refs, buf.buf, buf2.buf, "remote set-head", &buf_prev))
result |= error(_("Could not setup %s"), buf.buf);
- else if (opt_a)
- printf("%s/HEAD set to %s\n", argv[0], head_name);
+ else if (opt_a) {
+ report_set_head_auto(argv[0], head_name, &buf_prev);
+ }
free(head_name);
}
strbuf_release(&buf);
strbuf_release(&buf2);
+ strbuf_release(&buf_prev);
return result;
}
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 532035933f..d99cd1d0aa 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -429,12 +429,51 @@ test_expect_success 'set-head --auto' '
)
'
+test_expect_success 'set-head --auto detects creation' '
+ (
+ cd test &&
+ git symbolic-ref -d refs/remotes/origin/HEAD &&
+ git remote set-head --auto origin >output &&
+ echo "'\''origin/HEAD'\'' is now created and points to '\''main'\''" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects no change' '
+ (
+ cd test &&
+ git remote set-head --auto origin >output &&
+ echo "'\''origin/HEAD'\'' is unchanged and points to '\''main'\''" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects change' '
+ (
+ cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/ahead &&
+ git remote set-head --auto origin >output &&
+ echo "'\''origin/HEAD'\'' has changed from '\''ahead'\'' and now points to '\''main'\''" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects strange ref' '
+ (
+ cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD refs/heads/main &&
+ git remote set-head --auto origin >output &&
+ echo "'\''origin/HEAD'\'' used to point to '\''refs/heads/main'\'' (which is not a remote branch), but now points to '\''main'\''" >expect &&
+ test_cmp expect output
+ )
+'
+
test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
(
cd test &&
git fetch two "refs/heads/*:refs/remotes/two/*" &&
git remote set-head --auto two >output 2>&1 &&
- echo "two/HEAD set to main" >expect &&
+ echo "'\''two/HEAD'\'' is now created and points to '\''main'\''" >expect &&
test_cmp expect output
)
'
@@ -453,6 +492,16 @@ test_expect_success 'set-head explicit' '
)
'
+test_expect_success 'set-head --auto reports change' '
+ (
+ cd test &&
+ git remote set-head origin side2 &&
+ git remote set-head --auto origin >output 2>&1 &&
+ echo "'\''origin/HEAD'\'' has changed from '\''side2'\'' and now points to '\''main'\''" >expect &&
+ test_cmp expect output
+ )
+'
+
cat >test/expect <<EOF
Pruning origin
URL: $(pwd)/one
--
2.47.0.7.g072c39eddb.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v8 3/6] remote set-head: better output for --auto
2024-10-14 22:53 ` [PATCH v8 3/6] remote set-head: better output for --auto Bence Ferdinandy
@ 2024-10-15 7:47 ` karthik nayak
2024-10-15 16:31 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: karthik nayak @ 2024-10-15 7:47 UTC (permalink / raw)
To: Bence Ferdinandy, git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano
[-- Attachment #1: Type: text/plain, Size: 1358 bytes --]
Bence Ferdinandy <bence@ferdinandy.com> writes:
[snip]
> +static void report_set_head_auto(const char *remote, const char *head_name,
> + struct strbuf *buf_prev) {
> + struct strbuf buf_prefix = STRBUF_INIT;
> + const char *prev_head = NULL;
> +
> + strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
> + skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);
> +
> + if (prev_head && !strcmp(prev_head, head_name))
> + printf("'%s/HEAD' is unchanged and points to '%s'\n",
> + remote, head_name);
> + else if (prev_head)
> + printf("'%s/HEAD' has changed from '%s' and now points to '%s'\n",
> + remote, prev_head, head_name);
> + else if (!buf_prev->len)
> + printf("'%s/HEAD' is now created and points to '%s'\n",
> + remote, head_name);
> + else
> + printf("'%s/HEAD' used to point to '%s' "
> + "(which is not a remote branch), but now points to '%s'\n",
> + remote, buf_prev->buf, head_name);
> + strbuf_release(&buf_prefix);
> +}
> +
Shouldn't the messages above be marked for translation too? using `_()`?
> static int set_head(int argc, const char **argv, const char *prefix)
> {
> int i, opt_a = 0, opt_d = 0, result = 0;
> - struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
> + struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT,
> + buf_prev = STRBUF_INIT;
Nit: these names could definitely be better.
[snip]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v8 3/6] remote set-head: better output for --auto
2024-10-15 7:47 ` karthik nayak
@ 2024-10-15 16:31 ` Bence Ferdinandy
0 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-15 16:31 UTC (permalink / raw)
To: karthik nayak, git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano
On Tue Oct 15, 2024 at 09:47, karthik nayak <karthik.188@gmail.com> wrote:
> Bence Ferdinandy <bence@ferdinandy.com> writes:
>
> [snip]
>
>> +static void report_set_head_auto(const char *remote, const char *head_name,
>> + struct strbuf *buf_prev) {
>> + struct strbuf buf_prefix = STRBUF_INIT;
>> + const char *prev_head = NULL;
>> +
>> + strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
>> + skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);
>> +
>> + if (prev_head && !strcmp(prev_head, head_name))
>> + printf("'%s/HEAD' is unchanged and points to '%s'\n",
>> + remote, head_name);
>> + else if (prev_head)
>> + printf("'%s/HEAD' has changed from '%s' and now points to '%s'\n",
>> + remote, prev_head, head_name);
>> + else if (!buf_prev->len)
>> + printf("'%s/HEAD' is now created and points to '%s'\n",
>> + remote, head_name);
>> + else
>> + printf("'%s/HEAD' used to point to '%s' "
>> + "(which is not a remote branch), but now points to '%s'\n",
>> + remote, buf_prev->buf, head_name);
>> + strbuf_release(&buf_prefix);
>> +}
>> +
>
> Shouldn't the messages above be marked for translation too? using `_()`?
I guess it is unlikely to fall under the "should be machine readable" category.
I'll fix that up as well.
>
>> static int set_head(int argc, const char **argv, const char *prefix)
>> {
>> int i, opt_a = 0, opt_d = 0, result = 0;
>> - struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
>> + struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT,
>> + buf_prev = STRBUF_INIT;
>
> Nit: these names could definitely be better.
Indeed ... The second patch is a refactor of remote set-head, I could include
a variable rename refactor there as well, and use the same names I used in the
last patch when adding the similar calls to fetch's set_head.
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v8 4/6] refs: add TRANSACTION_CREATE_EXISTS error
2024-10-14 22:53 ` [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7 Bence Ferdinandy
` (2 preceding siblings ...)
2024-10-14 22:53 ` [PATCH v8 3/6] remote set-head: better output for --auto Bence Ferdinandy
@ 2024-10-14 22:53 ` Bence Ferdinandy
2024-10-14 22:53 ` [PATCH v8 5/6] refs: add create_only option to refs_update_symref Bence Ferdinandy
` (2 subsequent siblings)
6 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-14 22:53 UTC (permalink / raw)
To: git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano, Bence Ferdinandy, karthik.188
Currently there is only one special error for transaction, for when
there is a naming conflict, all other errors are dumped under a generic
error. Add a new special error case for when the caller requests the
reference to be updated only when it does not yet exist and the
reference actually does exist.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: no change
v6: no change
v7: - change commit prefix to be more in line with project standards
- changed error checking to Karthik's suggestion
v8: no change
refs.h | 4 +++-
refs/files-backend.c | 24 ++++++++++++++++--------
refs/reftable-backend.c | 6 ++++--
3 files changed, 23 insertions(+), 11 deletions(-)
diff --git a/refs.h b/refs.h
index b09a3a4384..c83b1ec76e 100644
--- a/refs.h
+++ b/refs.h
@@ -759,8 +759,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,
/* Naming conflict (for example, the ref names A and A/B conflict). */
#define TRANSACTION_NAME_CONFLICT -1
+/* When only creation was requested, but the ref already exists. */
+#define TRANSACTION_CREATE_EXISTS -2
/* All other errors. */
-#define TRANSACTION_GENERIC_ERROR -2
+#define TRANSACTION_GENERIC_ERROR -3
/*
* Perform the preparatory stages of committing `transaction`. Acquire
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 0824c0b8a9..e743ec44b5 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2502,14 +2502,18 @@ static int split_symref_update(struct ref_update *update,
static int check_old_oid(struct ref_update *update, struct object_id *oid,
struct strbuf *err)
{
+ int ret = TRANSACTION_GENERIC_ERROR;
+
if (!(update->flags & REF_HAVE_OLD) ||
oideq(oid, &update->old_oid))
return 0;
- if (is_null_oid(&update->old_oid))
+ if (is_null_oid(&update->old_oid)) {
strbuf_addf(err, "cannot lock ref '%s': "
"reference already exists",
ref_update_original_update_refname(update));
+ ret = TRANSACTION_CREATE_EXISTS;
+ }
else if (is_null_oid(oid))
strbuf_addf(err, "cannot lock ref '%s': "
"reference is missing but expected %s",
@@ -2522,7 +2526,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,
oid_to_hex(oid),
oid_to_hex(&update->old_oid));
- return -1;
+ return ret;
}
/*
@@ -2602,9 +2606,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
- } else if (check_old_oid(update, &lock->old_oid, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto out;
+ } else {
+ ret = check_old_oid(update, &lock->old_oid, err);
+ if (ret) {
+ goto out;
+ }
}
} else {
/*
@@ -2635,9 +2641,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,
update->old_target);
ret = TRANSACTION_GENERIC_ERROR;
goto out;
- } else if (check_old_oid(update, &lock->old_oid, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto out;
+ } else {
+ ret = check_old_oid(update, &lock->old_oid, err);
+ if (ret) {
+ goto out;
+ }
}
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 3c96fbf66f..ebf8e57fbc 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1206,10 +1206,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
goto done;
}
} else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) {
- if (is_null_oid(&u->old_oid))
+ ret = TRANSACTION_NAME_CONFLICT;
+ if (is_null_oid(&u->old_oid)) {
strbuf_addf(err, _("cannot lock ref '%s': "
"reference already exists"),
ref_update_original_update_refname(u));
+ ret = TRANSACTION_CREATE_EXISTS;
+ }
else if (is_null_oid(¤t_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
"reference is missing but expected %s"),
@@ -1221,7 +1224,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
ref_update_original_update_refname(u),
oid_to_hex(¤t_oid),
oid_to_hex(&u->old_oid));
- ret = -1;
goto done;
}
--
2.47.0.7.g072c39eddb.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v8 5/6] refs: add create_only option to refs_update_symref
2024-10-14 22:53 ` [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7 Bence Ferdinandy
` (3 preceding siblings ...)
2024-10-14 22:53 ` [PATCH v8 4/6] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
@ 2024-10-14 22:53 ` Bence Ferdinandy
2024-10-14 22:53 ` [PATCH v8 6/6] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
2024-10-16 0:26 ` [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7 Taylor Blau
6 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-14 22:53 UTC (permalink / raw)
To: git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano, Bence Ferdinandy, karthik.188
Allow the caller to specify that it only wants to update the symref if
it does not already exist. Silently ignore the error from the
transaction API if the symref already exists.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: no change
v6: - switched from bool to int for create_only
- refactored logic in refs_update_symref with goto as layed out by
Junio
v7: - change commit prefix to be more in line with project standards
- refactored code to accommodate changes in the first patch, but
otherwise no change
v8: no change
builtin/branch.c | 2 +-
builtin/checkout.c | 5 +++--
builtin/clone.c | 8 +++++---
builtin/notes.c | 3 ++-
builtin/remote.c | 9 ++++++---
builtin/symbolic-ref.c | 2 +-
builtin/worktree.c | 2 +-
refs.c | 30 ++++++++++++++++++++++--------
refs.h | 2 +-
reset.c | 2 +-
sequencer.c | 3 ++-
setup.c | 3 ++-
t/helper/test-ref-store.c | 2 +-
13 files changed, 48 insertions(+), 25 deletions(-)
diff --git a/builtin/branch.c b/builtin/branch.c
index 6c87690b58..2fcbcbfd59 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -559,7 +559,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,
continue;
refs = get_worktree_ref_store(worktrees[i]);
- if (refs_update_symref(refs, "HEAD", newref, logmsg, NULL))
+ if (refs_update_symref(refs, "HEAD", newref, logmsg, NULL, 0))
ret = error(_("HEAD of working tree %s is not updated"),
worktrees[i]->path);
}
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 356ee9bcde..9abb71dc07 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1015,7 +1015,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
describe_detached_head(_("HEAD is now at"), new_branch_info->commit);
}
} else if (new_branch_info->path) { /* Switch branches. */
- if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", new_branch_info->path, msg.buf, NULL) < 0)
+ if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", new_branch_info->path,
+ msg.buf, NULL, 0) < 0)
die(_("unable to update HEAD"));
if (!opts->quiet) {
if (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) {
@@ -1479,7 +1480,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)
die(_("You are on a branch yet to be born"));
strbuf_addf(&branch_ref, "refs/heads/%s", opts->new_branch);
status = refs_update_symref(get_main_ref_store(the_repository),
- "HEAD", branch_ref.buf, "checkout -b", NULL);
+ "HEAD", branch_ref.buf, "checkout -b", NULL, 0);
strbuf_release(&branch_ref);
if (!opts->quiet)
fprintf(stderr, _("Switched to a new branch '%s'\n"),
diff --git a/builtin/clone.c b/builtin/clone.c
index ead2af20ea..87e8f1421a 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -661,7 +661,7 @@ static void update_remote_refs(const struct ref *refs,
strbuf_addstr(&head_ref, "HEAD");
if (refs_update_symref(get_main_ref_store(the_repository), head_ref.buf,
remote_head_points_at->peer_ref->name,
- msg, NULL) < 0)
+ msg, NULL, 0) < 0)
die(_("unable to update %s"), head_ref.buf);
strbuf_release(&head_ref);
}
@@ -673,7 +673,8 @@ static void update_head(const struct ref *our, const struct ref *remote,
const char *head;
if (our && skip_prefix(our->name, "refs/heads/", &head)) {
/* Local default branch link */
- if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", our->name, NULL, NULL) < 0)
+ if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", our->name,
+ NULL, NULL, 0) < 0)
die(_("unable to update HEAD"));
if (!option_bare) {
refs_update_ref(get_main_ref_store(the_repository),
@@ -702,7 +703,8 @@ static void update_head(const struct ref *our, const struct ref *remote,
* Unborn head from remote; same as "our" case above except
* that we have no ref to update.
*/
- if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", unborn, NULL, NULL) < 0)
+ if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", unborn,
+ NULL, NULL, 0) < 0)
die(_("unable to update HEAD"));
if (!option_bare)
install_branch_config(0, head, remote_name, unborn);
diff --git a/builtin/notes.c b/builtin/notes.c
index ba646f06ff..7b3138e3c3 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -980,7 +980,8 @@ static int merge(int argc, const char **argv, const char *prefix)
die(_("a notes merge into %s is already in-progress at %s"),
notes_ref, wt->path);
free_worktrees(worktrees);
- if (refs_update_symref(get_main_ref_store(the_repository), "NOTES_MERGE_REF", notes_ref, NULL, NULL))
+ if (refs_update_symref(get_main_ref_store(the_repository), "NOTES_MERGE_REF", notes_ref,
+ NULL, NULL, 0))
die(_("failed to store link to current notes ref (%s)"),
notes_ref);
fprintf(stderr, _("Automatic notes merge failed. Fix conflicts in %s "
diff --git a/builtin/remote.c b/builtin/remote.c
index 2b6948439f..d60ec6e907 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -244,7 +244,8 @@ static int add(int argc, const char **argv, const char *prefix)
strbuf_reset(&buf2);
strbuf_addf(&buf2, "refs/remotes/%s/%s", name, master);
- if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote add", NULL))
+ if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf,
+ "remote add", NULL, 0))
result = error(_("Could not setup master '%s'"), master);
}
@@ -864,7 +865,8 @@ static int mv(int argc, const char **argv, const char *prefix)
strbuf_reset(&buf3);
strbuf_addf(&buf3, "remote: renamed %s to %s",
item->string, buf.buf);
- if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf, NULL))
+ if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf,
+ buf3.buf, NULL, 0))
die(_("creating '%s' failed"), buf.buf);
display_progress(progress, ++refs_renamed_nr);
}
@@ -1470,7 +1472,8 @@ static int set_head(int argc, const char **argv, const char *prefix)
/* make sure it's valid */
if (!refs_ref_exists(refs, buf2.buf))
result |= error(_("Not a valid ref: %s"), buf2.buf);
- else if (refs_update_symref(refs, buf.buf, buf2.buf, "remote set-head", &buf_prev))
+ else if (refs_update_symref(refs, buf.buf, buf2.buf,
+ "remote set-head", &buf_prev, 0))
result |= error(_("Could not setup %s"), buf.buf);
else if (opt_a) {
report_set_head_auto(argv[0], head_name, &buf_prev);
diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c
index 7728fbc3c1..cfbb53b30c 100644
--- a/builtin/symbolic-ref.c
+++ b/builtin/symbolic-ref.c
@@ -88,7 +88,7 @@ int cmd_symbolic_ref(int argc,
if (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0)
die("Refusing to set '%s' to invalid ref '%s'", argv[0], argv[1]);
ret = !!refs_update_symref(get_main_ref_store(the_repository),
- argv[0], argv[1], msg, NULL);
+ argv[0], argv[1], msg, NULL, 0);
break;
default:
usage_with_options(git_symbolic_ref_usage, options);
diff --git a/builtin/worktree.c b/builtin/worktree.c
index a7ab4193c1..2e95367235 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,
ret = refs_update_ref(wt_refs, NULL, "HEAD", &commit->object.oid,
NULL, 0, UPDATE_REFS_MSG_ON_ERR);
else
- ret = refs_update_symref(wt_refs, "HEAD", symref.buf, NULL, NULL);
+ ret = refs_update_symref(wt_refs, "HEAD", symref.buf, NULL, NULL, 0);
if (ret)
goto done;
diff --git a/refs.c b/refs.c
index b964ac44d0..29c020e78e 100644
--- a/refs.c
+++ b/refs.c
@@ -2115,26 +2115,40 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
int refs_update_symref(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg,
- struct strbuf *referent)
+ struct strbuf *referent, int create_only)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
- int ret = 0;
+ int ret = 0, prepret = 0;
transaction = ref_store_transaction_begin(refs, &err);
- if (!transaction ||
- ref_transaction_update(transaction, ref, NULL, NULL,
- target, NULL, REF_NO_DEREF,
- logmsg, &err) ||
- ref_transaction_prepare(transaction, &err)) {
+ if (!transaction) {
+ error_return:
ret = error("%s", err.buf);
goto cleanup;
}
+ if (create_only) {
+ if (ref_transaction_create(transaction, ref, NULL, target,
+ REF_NO_DEREF, logmsg, &err))
+ goto error_return;
+ prepret = ref_transaction_prepare(transaction, &err);
+ if (prepret && prepret != TRANSACTION_CREATE_EXISTS)
+ goto error_return;
+ } else {
+ if (ref_transaction_update(transaction, ref, NULL, NULL,
+ target, NULL, REF_NO_DEREF,
+ logmsg, &err) ||
+ ref_transaction_prepare(transaction, &err))
+ goto error_return;
+ }
+
if (referent)
refs_read_symbolic_ref(refs, ref, referent);
+ if (prepret == TRANSACTION_CREATE_EXISTS)
+ goto cleanup;
if (ref_transaction_commit(transaction, &err))
- ret = error("%s", err.buf);
+ goto error_return;
cleanup:
strbuf_release(&err);
diff --git a/refs.h b/refs.h
index c83b1ec76e..41ae384f11 100644
--- a/refs.h
+++ b/refs.h
@@ -572,7 +572,7 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
int refs_update_symref(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg,
- struct strbuf *referent);
+ struct strbuf *referent, int create_only);
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
diff --git a/reset.c b/reset.c
index cc36a9ed56..919fb84f5f 100644
--- a/reset.c
+++ b/reset.c
@@ -76,7 +76,7 @@ static int update_refs(const struct reset_head_opts *opts,
if (!ret)
ret = refs_update_symref(get_main_ref_store(the_repository),
"HEAD", switch_to_branch,
- reflog_head, NULL);
+ reflog_head, NULL, 0);
}
if (!ret && run_hook)
run_hooks_l(the_repository, "post-checkout",
diff --git a/sequencer.c b/sequencer.c
index 23b162924c..103d0ddfb2 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -5107,7 +5107,8 @@ static int pick_commits(struct repository *r,
}
msg = reflog_message(opts, "finish", "returning to %s",
head_ref.buf);
- if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", head_ref.buf, msg, NULL)) {
+ if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", head_ref.buf,
+ msg, NULL, 0)) {
res = error(_("could not update HEAD to %s"),
head_ref.buf);
goto cleanup_head_ref;
diff --git a/setup.c b/setup.c
index d95f051465..9bbc655ee4 100644
--- a/setup.c
+++ b/setup.c
@@ -2275,7 +2275,8 @@ void create_reference_database(enum ref_storage_format ref_storage_format,
die(_("invalid initial branch name: '%s'"),
initial_branch);
- if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", ref, NULL, NULL) < 0)
+ if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", ref,
+ NULL, NULL, 0) < 0)
exit(1);
free(ref);
}
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index a911302bea..f72b029e51 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -120,7 +120,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)
const char *target = notnull(*argv++, "target");
const char *logmsg = *argv++;
- return refs_update_symref(refs, refname, target, logmsg, NULL);
+ return refs_update_symref(refs, refname, target, logmsg, NULL, 0);
}
static struct flag_definition transaction_flags[] = {
--
2.47.0.7.g072c39eddb.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v8 6/6] fetch: set remote/HEAD if it does not exist
2024-10-14 22:53 ` [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7 Bence Ferdinandy
` (4 preceding siblings ...)
2024-10-14 22:53 ` [PATCH v8 5/6] refs: add create_only option to refs_update_symref Bence Ferdinandy
@ 2024-10-14 22:53 ` Bence Ferdinandy
2024-10-16 0:26 ` [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7 Taylor Blau
6 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-14 22:53 UTC (permalink / raw)
To: git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano, Bence Ferdinandy, karthik.188
If the user has remote/HEAD set already and it looks like it has changed
on the server, then print a message, otherwise set it if we can.
Silently pass if the user already has the same remote/HEAD set as
reported by the server or if we encounter any errors along the way.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v3: - does not rely on remote set-head anymore so it only authenticates
once
- uses the new REF_CREATE_ONLY to atomically check if the ref exists
and only write it if it doesn't
- in all other cases the maximum it does is print a warning
v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,
but updated transaction api to request a silent create only
- it now uses the atomic before_target to determine reporting
- refactored for legibility
v5: - instead of printing a not too useful message, it now fails
silently, this in line with the objective to only set up
remote/HEAD automatically if the right thing is trivial, for
everything else there is remote set-head
- fixed all failing tests
- added two new tests, one for checking if remote/HEAD is set to the
correct one, and one to test that we do not override remote/HEAD
if it has changed on the server from what we have locally
v6: - fixed style issues and unintended extra empty line
- updated function call with bool to int from previous patch's
change
- removed calls to error(...) inherited from builtin/remote.c so we
actually fail silently
- set the test for remote set-head --auto to the correct value here,
which was previously erronously set in the remote set-head patch
v7: - no change
v8: - changed logmsg in call to refs_update_symref from "remote
set-head" to "fetch"
builtin/fetch.c | 82 +++++++++++
t/t4207-log-decoration-colors.sh | 3 +-
t/t5505-remote.sh | 13 +-
t/t5510-fetch.sh | 229 ++++++++++++++++---------------
t/t5512-ls-remote.sh | 2 +
t/t5514-fetch-multiple.sh | 17 ++-
t/t5516-fetch-push.sh | 3 +-
t/t5527-fetch-odd-refs.sh | 3 +-
t/t7900-maintenance.sh | 3 +-
t/t9210-scalar.sh | 5 +-
t/t9211-scalar-clone.sh | 6 +-
t/t9902-completion.sh | 65 +++++++++
12 files changed, 308 insertions(+), 123 deletions(-)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 80a64d0d26..88ebca8294 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1578,6 +1578,80 @@ static int backfill_tags(struct display_state *display_state,
return retcode;
}
+static void report_set_head(const char *remote, const char *head_name,
+ struct strbuf *buf_prev) {
+ struct strbuf buf_prefix = STRBUF_INIT;
+ const char *prev_head = NULL;
+
+ strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
+ skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);
+
+ if (prev_head && strcmp(prev_head, head_name)) {
+ printf("'HEAD' at '%s' has changed from '%s' to '%s'\n",
+ remote, prev_head, head_name);
+ printf("Run 'git remote set-head %s %s' to follow the change.\n",
+ remote, head_name);
+ }
+}
+
+static const char *strip_refshead(const char *name){
+ skip_prefix(name, "refs/heads/", &name);
+ return name;
+}
+
+static int set_head(const struct ref *remote_refs)
+{
+ int result = 0;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
+ b_local_head = STRBUF_INIT;
+ const char *remote = gtransport->remote->name;
+ char *head_name = NULL;
+ struct ref *ref, *matches;
+ struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
+ struct refspec_item refspec = {
+ .force = 0,
+ .pattern = 1,
+ .src = (char *) "refs/heads/*",
+ .dst = (char *) "refs/heads/*",
+ };
+ struct string_list heads = STRING_LIST_INIT_DUP;
+ struct ref_store *refs = get_main_ref_store(the_repository);
+
+ get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
+ matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
+ fetch_map, 1);
+ for (ref = matches; ref; ref = ref->next) {
+ string_list_append(&heads, strip_refshead(ref->name));
+ }
+
+
+ if (!heads.nr)
+ result = 1;
+ else if (heads.nr > 1)
+ result = 1;
+ else
+ head_name = xstrdup(heads.items[0].string);
+ if (head_name) {
+ strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name);
+ /* make sure it's valid */
+ if (!refs_ref_exists(refs, b_remote_head.buf))
+ result = 1;
+ else if (refs_update_symref(refs, b_head.buf, b_remote_head.buf,
+ "fetch", &b_local_head, 1))
+ result = 1;
+ else
+ report_set_head(remote, head_name, &b_local_head);
+
+ free(head_name);
+ }
+
+ strbuf_release(&b_head);
+ strbuf_release(&b_local_head);
+ strbuf_release(&b_remote_head);
+ return result;
+}
+
static int do_fetch(struct transport *transport,
struct refspec *rs,
const struct fetch_config *config)
@@ -1647,6 +1721,8 @@ static int do_fetch(struct transport *transport,
"refs/tags/");
}
+ strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
+
if (must_list_refs) {
trace2_region_enter("fetch", "remote_refs", the_repository);
remote_refs = transport_get_remote_refs(transport,
@@ -1791,6 +1867,12 @@ static int do_fetch(struct transport *transport,
"you need to specify exactly one branch with the --set-upstream option"));
}
}
+ if (set_head(remote_refs))
+ ;
+ /*
+ * Way too many cases where this can go wrong
+ * so let's just fail silently for now.
+ */
cleanup:
if (retcode) {
diff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh
index 73ea9e5155..d55d22cb2f 100755
--- a/t/t4207-log-decoration-colors.sh
+++ b/t/t4207-log-decoration-colors.sh
@@ -59,7 +59,8 @@ ${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \
${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \
-${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1
+${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit}, \
+${c_reset}${c_remoteBranch}other/HEAD${c_reset}${c_commit})${c_reset} A1
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
${c_stash}refs/stash${c_reset}${c_commit})${c_reset} On main: Changes to A.t
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index d99cd1d0aa..cb45f22232 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -71,7 +71,7 @@ test_expect_success 'add another remote' '
cd test &&
git remote add -f second ../two &&
tokens_match "origin second" "$(git remote)" &&
- check_tracking_branch second main side another &&
+ check_tracking_branch second main side another HEAD &&
git for-each-ref "--format=%(refname)" refs/remotes |
sed -e "/^refs\/remotes\/origin\//d" \
-e "/^refs\/remotes\/second\//d" >actual &&
@@ -473,7 +473,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
cd test &&
git fetch two "refs/heads/*:refs/remotes/two/*" &&
git remote set-head --auto two >output 2>&1 &&
- echo "'\''two/HEAD'\'' is now created and points to '\''main'\''" >expect &&
+ echo "'\''two/HEAD'\'' is unchanged and points to '\''main'\''" >expect &&
test_cmp expect output
)
'
@@ -763,6 +763,7 @@ test_expect_success 'reject --no-no-tags' '
cat >one/expect <<\EOF
apis/main
apis/side
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -780,6 +781,7 @@ test_expect_success 'update' '
'
cat >one/expect <<\EOF
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -792,7 +794,7 @@ EOF
test_expect_success 'update with arguments' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -835,7 +837,7 @@ EOF
test_expect_success 'update default' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -847,6 +849,7 @@ test_expect_success 'update default' '
'
cat >one/expect <<\EOF
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -869,7 +872,7 @@ test_expect_success 'update default (overridden, with funny whitespace)' '
test_expect_success 'update (with remotes.default defined)' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 0890b9f61c..dfc8d748ba 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -75,6 +75,30 @@ test_expect_success "fetch test for-merge" '
cut -f -2 .git/FETCH_HEAD >actual &&
test_cmp expected actual'
+test_expect_success "fetch test remote HEAD" '
+ cd "$D" &&
+ cd two &&
+ git fetch &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"'
+
+test_expect_success "fetch test remote HEAD change" '
+ cd "$D" &&
+ cd two &&
+ git switch -c other &&
+ git push -u origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git fetch &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"'
+
test_expect_success 'fetch --prune on its own works as expected' '
cd "$D" &&
git clone . prune &&
@@ -478,7 +502,6 @@ test_expect_success 'unbundle 1' '
test_must_fail git fetch "$D/bundle1" main:main
'
-
test_expect_success 'bundle 1 has only 3 files ' '
cd "$D" &&
test_bundle_object_count bundle1 3
@@ -819,7 +842,7 @@ test_expect_success 'fetch from multiple configured URLs in single remote' '
# configured prune tests
-set_config_tristate () {
+set_config_tristate() {
# var=$1 val=$2
case "$2" in
unset)
@@ -833,12 +856,12 @@ set_config_tristate () {
esac
}
-test_configured_prune () {
+test_configured_prune() {
test_configured_prune_type "$@" "name"
test_configured_prune_type "$@" "link"
}
-test_configured_prune_type () {
+test_configured_prune_type() {
fetch_prune=$1
remote_origin_prune=$2
fetch_prune_tags=$3
@@ -848,8 +871,7 @@ test_configured_prune_type () {
cmdline=$7
mode=$8
- if test -z "$cmdline_setup"
- then
+ if test -z "$cmdline_setup"; then
test_expect_success 'setup cmdline_setup variable for subsequent test' '
remote_url="file://$(git -C one config remote.origin.url)" &&
remote_fetch="$(git -C one config remote.origin.fetch)" &&
@@ -857,12 +879,10 @@ test_configured_prune_type () {
'
fi
- if test "$mode" = 'link'
- then
+ if test "$mode" = 'link'; then
new_cmdline=""
- if test "$cmdline" = ""
- then
+ if test "$cmdline" = ""; then
new_cmdline=$cmdline_setup
else
new_cmdline=$(perl -e '
@@ -873,10 +893,8 @@ test_configured_prune_type () {
fi
if test "$fetch_prune_tags" = 'true' ||
- test "$remote_origin_prune_tags" = 'true'
- then
- if ! printf '%s' "$cmdline\n" | grep -q refs/remotes/origin/
- then
+ test "$remote_origin_prune_tags" = 'true'; then
+ if ! printf '%s' "$cmdline\n" | grep -q refs/remotes/origin/; then
new_cmdline="$new_cmdline refs/tags/*:refs/tags/*"
fi
fi
@@ -946,100 +964,100 @@ test_configured_prune_type () {
# $7 git-fetch $cmdline:
#
# $1 $2 $3 $4 $5 $6 $7
-test_configured_prune unset unset unset unset kept kept ""
-test_configured_prune unset unset unset unset kept kept "--no-prune"
-test_configured_prune unset unset unset unset pruned kept "--prune"
-test_configured_prune unset unset unset unset kept pruned \
+test_configured_prune unset unset unset unset kept kept ""
+test_configured_prune unset unset unset unset kept kept "--no-prune"
+test_configured_prune unset unset unset unset pruned kept "--prune"
+test_configured_prune unset unset unset unset kept pruned \
"--prune origin refs/tags/*:refs/tags/*"
test_configured_prune unset unset unset unset pruned pruned \
"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
-test_configured_prune false unset unset unset kept kept ""
-test_configured_prune false unset unset unset kept kept "--no-prune"
-test_configured_prune false unset unset unset pruned kept "--prune"
+test_configured_prune false unset unset unset kept kept ""
+test_configured_prune false unset unset unset kept kept "--no-prune"
+test_configured_prune false unset unset unset pruned kept "--prune"
-test_configured_prune true unset unset unset pruned kept ""
-test_configured_prune true unset unset unset pruned kept "--prune"
-test_configured_prune true unset unset unset kept kept "--no-prune"
+test_configured_prune true unset unset unset pruned kept ""
+test_configured_prune true unset unset unset pruned kept "--prune"
+test_configured_prune true unset unset unset kept kept "--no-prune"
-test_configured_prune unset false unset unset kept kept ""
-test_configured_prune unset false unset unset kept kept "--no-prune"
-test_configured_prune unset false unset unset pruned kept "--prune"
+test_configured_prune unset false unset unset kept kept ""
+test_configured_prune unset false unset unset kept kept "--no-prune"
+test_configured_prune unset false unset unset pruned kept "--prune"
-test_configured_prune false false unset unset kept kept ""
-test_configured_prune false false unset unset kept kept "--no-prune"
-test_configured_prune false false unset unset pruned kept "--prune"
-test_configured_prune false false unset unset kept pruned \
+test_configured_prune false false unset unset kept kept ""
+test_configured_prune false false unset unset kept kept "--no-prune"
+test_configured_prune false false unset unset pruned kept "--prune"
+test_configured_prune false false unset unset kept pruned \
"--prune origin refs/tags/*:refs/tags/*"
test_configured_prune false false unset unset pruned pruned \
"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
-test_configured_prune true false unset unset kept kept ""
-test_configured_prune true false unset unset pruned kept "--prune"
-test_configured_prune true false unset unset kept kept "--no-prune"
+test_configured_prune true false unset unset kept kept ""
+test_configured_prune true false unset unset pruned kept "--prune"
+test_configured_prune true false unset unset kept kept "--no-prune"
-test_configured_prune unset true unset unset pruned kept ""
-test_configured_prune unset true unset unset kept kept "--no-prune"
-test_configured_prune unset true unset unset pruned kept "--prune"
+test_configured_prune unset true unset unset pruned kept ""
+test_configured_prune unset true unset unset kept kept "--no-prune"
+test_configured_prune unset true unset unset pruned kept "--prune"
-test_configured_prune false true unset unset pruned kept ""
-test_configured_prune false true unset unset kept kept "--no-prune"
-test_configured_prune false true unset unset pruned kept "--prune"
+test_configured_prune false true unset unset pruned kept ""
+test_configured_prune false true unset unset kept kept "--no-prune"
+test_configured_prune false true unset unset pruned kept "--prune"
-test_configured_prune true true unset unset pruned kept ""
-test_configured_prune true true unset unset pruned kept "--prune"
-test_configured_prune true true unset unset kept kept "--no-prune"
-test_configured_prune true true unset unset kept pruned \
+test_configured_prune true true unset unset pruned kept ""
+test_configured_prune true true unset unset pruned kept "--prune"
+test_configured_prune true true unset unset kept kept "--no-prune"
+test_configured_prune true true unset unset kept pruned \
"--prune origin refs/tags/*:refs/tags/*"
-test_configured_prune true true unset unset pruned pruned \
+test_configured_prune true true unset unset pruned pruned \
"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
# --prune-tags on its own does nothing, needs --prune as well, same
# for fetch.pruneTags without fetch.prune
-test_configured_prune unset unset unset unset kept kept "--prune-tags"
-test_configured_prune unset unset true unset kept kept ""
-test_configured_prune unset unset unset true kept kept ""
+test_configured_prune unset unset unset unset kept kept "--prune-tags"
+test_configured_prune unset unset true unset kept kept ""
+test_configured_prune unset unset unset true kept kept ""
# These will prune the tags
test_configured_prune unset unset unset unset pruned pruned "--prune --prune-tags"
-test_configured_prune true unset true unset pruned pruned ""
-test_configured_prune unset true unset true pruned pruned ""
+test_configured_prune true unset true unset pruned pruned ""
+test_configured_prune unset true unset true pruned pruned ""
# remote.<name>.pruneTags overrides fetch.pruneTags, just like
# remote.<name>.prune overrides fetch.prune if set.
-test_configured_prune true unset true unset pruned pruned ""
-test_configured_prune false true false true pruned pruned ""
-test_configured_prune true false true false kept kept ""
+test_configured_prune true unset true unset pruned pruned ""
+test_configured_prune false true false true pruned pruned ""
+test_configured_prune true false true false kept kept ""
# When --prune-tags is supplied it's ignored if an explicit refspec is
# given, same for the configuration options.
test_configured_prune unset unset unset unset pruned kept \
"--prune --prune-tags origin +refs/heads/*:refs/remotes/origin/*"
-test_configured_prune unset unset true unset pruned kept \
+test_configured_prune unset unset true unset pruned kept \
"--prune origin +refs/heads/*:refs/remotes/origin/*"
-test_configured_prune unset unset unset true pruned kept \
+test_configured_prune unset unset unset true pruned kept \
"--prune origin +refs/heads/*:refs/remotes/origin/*"
# Pruning that also takes place if a file:// url replaces a named
# remote. However, because there's no implicit
# +refs/heads/*:refs/remotes/origin/* refspec and supplying it on the
# command-line negates --prune-tags, the branches will not be pruned.
-test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "name"
-test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "link"
+test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "name"
+test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "link"
test_configured_prune_type unset unset unset unset pruned pruned "origin --prune --prune-tags" "name"
-test_configured_prune_type unset unset unset unset kept pruned "origin --prune --prune-tags" "link"
+test_configured_prune_type unset unset unset unset kept pruned "origin --prune --prune-tags" "link"
test_configured_prune_type unset unset unset unset pruned pruned "--prune --prune-tags origin" "name"
-test_configured_prune_type unset unset unset unset kept pruned "--prune --prune-tags origin" "link"
-test_configured_prune_type unset unset true unset pruned pruned "--prune origin" "name"
-test_configured_prune_type unset unset true unset kept pruned "--prune origin" "link"
-test_configured_prune_type unset unset unset true pruned pruned "--prune origin" "name"
-test_configured_prune_type unset unset unset true kept pruned "--prune origin" "link"
-test_configured_prune_type true unset true unset pruned pruned "origin" "name"
-test_configured_prune_type true unset true unset kept pruned "origin" "link"
-test_configured_prune_type unset true true unset pruned pruned "origin" "name"
-test_configured_prune_type unset true true unset kept pruned "origin" "link"
-test_configured_prune_type unset true unset true pruned pruned "origin" "name"
-test_configured_prune_type unset true unset true kept pruned "origin" "link"
+test_configured_prune_type unset unset unset unset kept pruned "--prune --prune-tags origin" "link"
+test_configured_prune_type unset unset true unset pruned pruned "--prune origin" "name"
+test_configured_prune_type unset unset true unset kept pruned "--prune origin" "link"
+test_configured_prune_type unset unset unset true pruned pruned "--prune origin" "name"
+test_configured_prune_type unset unset unset true kept pruned "--prune origin" "link"
+test_configured_prune_type true unset true unset pruned pruned "origin" "name"
+test_configured_prune_type true unset true unset kept pruned "origin" "link"
+test_configured_prune_type unset true true unset pruned pruned "origin" "name"
+test_configured_prune_type unset true true unset kept pruned "origin" "link"
+test_configured_prune_type unset true unset true pruned pruned "origin" "name"
+test_configured_prune_type unset true unset true kept pruned "origin" "link"
# When all remote.origin.fetch settings are deleted a --prune
# --prune-tags still implicitly supplies refs/tags/*:refs/tags/* so
@@ -1137,8 +1155,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '
)
'
-for section in fetch transfer
-do
+for section in fetch transfer; do
test_expect_success "$section.hideRefs affects connectivity check" '
GIT_TRACE="$PWD"/trace git -c $section.hideRefs=refs -c \
$section.hideRefs="!refs/tags/" fetch &&
@@ -1157,21 +1174,23 @@ test_expect_success 'prepare source branch' '
test 3 -le $(wc -l <actual)
'
-validate_store_type () {
+validate_store_type() {
git -C dest count-objects -v >actual &&
- case "$store_type" in
- packed)
- grep "^count: 0$" actual ;;
- loose)
- grep "^packs: 0$" actual ;;
- esac || {
+ case "$store_type" in
+ packed)
+ grep "^count: 0$" actual
+ ;;
+ loose)
+ grep "^packs: 0$" actual
+ ;;
+ esac || {
echo "store_type is $store_type"
cat actual
false
}
}
-test_unpack_limit () {
+test_unpack_limit() {
store_type=$1
case "$store_type" in
@@ -1192,43 +1211,39 @@ test_unpack_limit () {
test_unpack_limit packed
test_unpack_limit loose
-setup_negotiation_tip () {
+setup_negotiation_tip() {
SERVER="$1"
URL="$2"
USE_PROTOCOL_V2="$3"
rm -rf "$SERVER" client trace &&
- git init -b main "$SERVER" &&
- test_commit -C "$SERVER" alpha_1 &&
- test_commit -C "$SERVER" alpha_2 &&
- git -C "$SERVER" checkout --orphan beta &&
- test_commit -C "$SERVER" beta_1 &&
- test_commit -C "$SERVER" beta_2 &&
-
- git clone "$URL" client &&
-
- if test "$USE_PROTOCOL_V2" -eq 1
- then
- git -C "$SERVER" config protocol.version 2 &&
- git -C client config protocol.version 2
- fi &&
-
- test_commit -C "$SERVER" beta_s &&
- git -C "$SERVER" checkout main &&
- test_commit -C "$SERVER" alpha_s &&
- git -C "$SERVER" tag -d alpha_1 alpha_2 beta_1 beta_2
+ git init -b main "$SERVER" &&
+ test_commit -C "$SERVER" alpha_1 &&
+ test_commit -C "$SERVER" alpha_2 &&
+ git -C "$SERVER" checkout --orphan beta &&
+ test_commit -C "$SERVER" beta_1 &&
+ test_commit -C "$SERVER" beta_2 &&
+ git clone "$URL" client &&
+ if test "$USE_PROTOCOL_V2" -eq 1; then
+ git -C "$SERVER" config protocol.version 2 &&
+ git -C client config protocol.version 2
+ fi &&
+ test_commit -C "$SERVER" beta_s &&
+ git -C "$SERVER" checkout main &&
+ test_commit -C "$SERVER" alpha_s &&
+ git -C "$SERVER" tag -d alpha_1 alpha_2 beta_1 beta_2
}
-check_negotiation_tip () {
+check_negotiation_tip() {
# Ensure that {alpha,beta}_1 are sent as "have", but not {alpha_beta}_2
ALPHA_1=$(git -C client rev-parse alpha_1) &&
- grep "fetch> have $ALPHA_1" trace &&
- BETA_1=$(git -C client rev-parse beta_1) &&
- grep "fetch> have $BETA_1" trace &&
- ALPHA_2=$(git -C client rev-parse alpha_2) &&
- ! grep "fetch> have $ALPHA_2" trace &&
- BETA_2=$(git -C client rev-parse beta_2) &&
- ! grep "fetch> have $BETA_2" trace
+ grep "fetch> have $ALPHA_1" trace &&
+ BETA_1=$(git -C client rev-parse beta_1) &&
+ grep "fetch> have $BETA_1" trace &&
+ ALPHA_2=$(git -C client rev-parse alpha_2) &&
+ ! grep "fetch> have $ALPHA_2" trace &&
+ BETA_2=$(git -C client rev-parse beta_2) &&
+ ! grep "fetch> have $BETA_2" trace
}
test_expect_success '--negotiation-tip limits "have" lines sent' '
diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh
index 64b3491e4e..1b3865e154 100755
--- a/t/t5512-ls-remote.sh
+++ b/t/t5512-ls-remote.sh
@@ -293,6 +293,8 @@ test_expect_success 'ls-remote with filtered symref (refname)' '
cat >expect <<-EOF &&
ref: refs/heads/main HEAD
$rev HEAD
+ ref: refs/remotes/origin/main refs/remotes/origin/HEAD
+ $rev refs/remotes/origin/HEAD
EOF
git ls-remote --symref . HEAD >actual &&
test_cmp expect actual
diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh
index 579872c258..e3482b27b2 100755
--- a/t/t5514-fetch-multiple.sh
+++ b/t/t5514-fetch-multiple.sh
@@ -45,14 +45,17 @@ test_expect_success setup '
'
cat > test/expect << EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -97,6 +100,7 @@ cat > expect << EOF
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
@@ -112,8 +116,10 @@ test_expect_success 'git fetch --multiple (but only one remote)' '
'
cat > expect << EOF
+ one/HEAD -> one/main
one/main
one/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -141,7 +147,7 @@ test_expect_success 'git fetch --multiple (bad remote names)' '
test_expect_success 'git fetch --all (skipFetchAll)' '
(cd test4 &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -153,11 +159,14 @@ test_expect_success 'git fetch --all (skipFetchAll)' '
'
cat > expect << EOF
+ one/HEAD -> one/main
one/main
one/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -165,7 +174,7 @@ EOF
test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '
(cd test4 &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -221,14 +230,17 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' '
create_fetch_all_expect () {
cat >expect <<-\EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -265,6 +277,7 @@ test_expect_success 'git fetch (fetch all remotes with fetch.all = true)' '
create_fetch_one_expect () {
cat >expect <<-\EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 331778bd42..5a051aa0c7 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -1395,7 +1395,8 @@ test_expect_success 'fetch follows tags by default' '
git tag -m "annotated" tag &&
git for-each-ref >tmp1 &&
sed -n "p; s|refs/heads/main$|refs/remotes/origin/main|p" tmp1 |
- sort -k 3 >../expect
+ sed -n "p; s|refs/heads/main$|refs/remotes/origin/HEAD|p" |
+ sort -k 4 >../expect
) &&
test_when_finished "rm -rf dst" &&
git init dst &&
diff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh
index 98ece27c6a..d3996af6ee 100755
--- a/t/t5527-fetch-odd-refs.sh
+++ b/t/t5527-fetch-odd-refs.sh
@@ -52,7 +52,8 @@ test_expect_success LONG_REF 'fetch handles extremely long refname' '
long
main
EOF
- git for-each-ref --format="%(subject)" refs/remotes/long >actual &&
+ git for-each-ref --format="%(subject)" refs/remotes/long \
+ --exclude=refs/remotes/long/HEAD >actual &&
test_cmp expect actual
'
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index a66d0e089d..88331d1ceb 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -329,7 +329,8 @@ test_expect_success 'incremental-repack task' '
# Delete refs that have not been repacked in these packs.
git for-each-ref --format="delete %(refname)" \
- refs/prefetch refs/tags refs/remotes >refs &&
+ refs/prefetch refs/tags refs/remotes \
+ --exclude=refs/remotes/*/HEAD >refs &&
git update-ref --stdin <refs &&
# Replace the object directory with this pack layout.
diff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh
index 027235d61a..a81662713e 100755
--- a/t/t9210-scalar.sh
+++ b/t/t9210-scalar.sh
@@ -150,7 +150,8 @@ test_expect_success 'scalar clone' '
"$(pwd)" &&
git for-each-ref --format="%(refname)" refs/remotes/origin/ >actual &&
- echo "refs/remotes/origin/parallel" >expect &&
+ echo "refs/remotes/origin/HEAD" >>expect &&
+ echo "refs/remotes/origin/parallel" >>expect &&
test_cmp expect actual &&
test_path_is_missing 1/2 &&
@@ -219,7 +220,7 @@ test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '
done
'
- test_expect_success 'scalar reconfigure --all with detached HEADs' '
+test_expect_success 'scalar reconfigure --all with detached HEADs' '
repos="two three four" &&
for num in $repos
do
diff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh
index 7869f45ee6..01f71910f5 100755
--- a/t/t9211-scalar-clone.sh
+++ b/t/t9211-scalar-clone.sh
@@ -31,7 +31,7 @@ test_expect_success 'set up repository to clone' '
)
'
-cleanup_clone () {
+cleanup_clone() {
rm -rf "$1"
}
@@ -127,7 +127,7 @@ test_expect_success '--single-branch clones HEAD only' '
(
cd $enlistment/src &&
git for-each-ref refs/remotes/origin >out &&
- test_line_count = 1 out &&
+ test_line_count = 2 out &&
grep "refs/remotes/origin/base" out
) &&
@@ -141,7 +141,7 @@ test_expect_success '--no-single-branch clones all branches' '
(
cd $enlistment/src &&
git for-each-ref refs/remotes/origin >out &&
- test_line_count = 2 out &&
+ test_line_count = 3 out &&
grep "refs/remotes/origin/base" out &&
grep "refs/remotes/origin/parallel" out
) &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index cc6aa9f0cd..b663c4609e 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -658,6 +658,7 @@ test_expect_success '__git_refs - simple' '
HEAD
main
matching-branch
+ other/HEAD
other/branch-in-other
other/main-in-other
matching-tag
@@ -673,6 +674,7 @@ test_expect_success '__git_refs - full refs' '
cat >expected <<-EOF &&
refs/heads/main
refs/heads/matching-branch
+ refs/remotes/other/HEAD
refs/remotes/other/branch-in-other
refs/remotes/other/main-in-other
refs/tags/matching-tag
@@ -729,6 +731,7 @@ test_expect_success '__git_refs - remote on local file system - full refs' '
test_expect_success '__git_refs - configured remote' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -756,6 +759,7 @@ test_expect_success '__git_refs - configured remote - full refs' '
test_expect_success '__git_refs - configured remote - repo given on the command line' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -787,6 +791,7 @@ test_expect_success '__git_refs - configured remote - full refs - repo given on
test_expect_success '__git_refs - configured remote - remote name matches a directory' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -875,12 +880,14 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer
HEAD
main
matching-branch
+ other/HEAD
other/ambiguous
other/branch-in-other
other/main-in-other
remote/ambiguous
remote/branch-in-remote
matching-tag
+ HEAD
branch-in-other
branch-in-remote
main-in-other
@@ -904,6 +911,7 @@ test_expect_success '__git_refs - after --opt=' '
HEAD
main
matching-branch
+ other/HEAD
other/branch-in-other
other/main-in-other
matching-tag
@@ -919,6 +927,7 @@ test_expect_success '__git_refs - after --opt= - full refs' '
cat >expected <<-EOF &&
refs/heads/main
refs/heads/matching-branch
+ refs/remotes/other/HEAD
refs/remotes/other/branch-in-other
refs/remotes/other/main-in-other
refs/tags/matching-tag
@@ -935,6 +944,7 @@ test_expect_success '__git refs - excluding refs' '
^HEAD
^main
^matching-branch
+ ^other/HEAD
^other/branch-in-other
^other/main-in-other
^matching-tag
@@ -950,6 +960,7 @@ test_expect_success '__git refs - excluding full refs' '
cat >expected <<-EOF &&
^refs/heads/main
^refs/heads/matching-branch
+ ^refs/remotes/other/HEAD
^refs/remotes/other/branch-in-other
^refs/remotes/other/main-in-other
^refs/tags/matching-tag
@@ -975,6 +986,7 @@ test_expect_success '__git_refs - do not filter refs unless told so' '
main
matching-branch
matching/branch
+ other/HEAD
other/branch-in-other
other/main-in-other
other/matching/branch-in-other
@@ -1095,6 +1107,7 @@ test_expect_success '__git_complete_refs - simple' '
HEAD Z
main Z
matching-branch Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
matching-tag Z
@@ -1123,6 +1136,7 @@ test_expect_success '__git_complete_refs - matching' '
test_expect_success '__git_complete_refs - remote' '
sed -e "s/Z$//" >expected <<-EOF &&
HEAD Z
+ HEAD Z
branch-in-other Z
main-in-other Z
EOF
@@ -1139,9 +1153,11 @@ test_expect_success '__git_complete_refs - track' '
HEAD Z
main Z
matching-branch Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
matching-tag Z
+ HEAD Z
branch-in-other Z
main-in-other Z
EOF
@@ -1184,6 +1200,7 @@ test_expect_success '__git_complete_refs - suffix' '
HEAD.
main.
matching-branch.
+ other/HEAD.
other/branch-in-other.
other/main-in-other.
matching-tag.
@@ -1199,6 +1216,7 @@ test_expect_success '__git_complete_refs - suffix' '
test_expect_success '__git_complete_fetch_refspecs - simple' '
sed -e "s/Z$//" >expected <<-EOF &&
HEAD:HEAD Z
+ HEAD:HEAD Z
branch-in-other:branch-in-other Z
main-in-other:main-in-other Z
EOF
@@ -1225,6 +1243,7 @@ test_expect_success '__git_complete_fetch_refspecs - matching' '
test_expect_success '__git_complete_fetch_refspecs - prefix' '
sed -e "s/Z$//" >expected <<-EOF &&
+HEAD:HEAD Z
+ +HEAD:HEAD Z
+branch-in-other:branch-in-other Z
+main-in-other:main-in-other Z
EOF
@@ -1289,6 +1308,7 @@ test_expect_success '__git_complete_worktree_paths with -C' '
test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '
test_completion "git switch " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1435,11 +1455,13 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena
test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '
test_completion "git checkout " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1461,6 +1483,7 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl
test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '
GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git switch --guess " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1470,6 +1493,7 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G
test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '
test_completion "git switch --no-guess --guess " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1490,6 +1514,7 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1498,11 +1523,13 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete
test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '
GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git checkout --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1514,6 +1541,7 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1522,11 +1550,13 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '
test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '
test_completion "git checkout --no-guess --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1538,6 +1568,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1550,6 +1581,7 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1559,11 +1591,13 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a
test_config checkout.guess true &&
test_completion "git checkout " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1573,11 +1607,13 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.
test_config checkout.guess false &&
test_completion "git checkout --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1590,6 +1626,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1601,6 +1638,7 @@ test_expect_success 'git switch - with --detach, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1612,6 +1650,7 @@ test_expect_success 'git checkout - with --detach, complete only references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1783,6 +1822,7 @@ test_expect_success 'git switch - with -d, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1794,6 +1834,7 @@ test_expect_success 'git checkout - with -d, complete only references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1801,10 +1842,12 @@ test_expect_success 'git checkout - with -d, complete only references' '
test_expect_success 'git switch - with --track, complete only remote branches' '
test_completion "git switch --track " <<-\EOF &&
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
test_completion "git switch -t " <<-\EOF
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1812,10 +1855,12 @@ test_expect_success 'git switch - with --track, complete only remote branches' '
test_expect_success 'git checkout - with --track, complete only remote branches' '
test_completion "git checkout --track " <<-\EOF &&
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
test_completion "git checkout -t " <<-\EOF
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1834,6 +1879,7 @@ test_expect_success 'git checkout - with --no-track, complete only local referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1845,6 +1891,7 @@ test_expect_success 'git switch - with -c, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1856,6 +1903,7 @@ test_expect_success 'git switch - with -C, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1867,6 +1915,7 @@ test_expect_success 'git switch - with -c and --track, complete all references'
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1878,6 +1927,7 @@ test_expect_success 'git switch - with -C and --track, complete all references'
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1889,6 +1939,7 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1900,6 +1951,7 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1911,6 +1963,7 @@ test_expect_success 'git checkout - with -b, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1922,6 +1975,7 @@ test_expect_success 'git checkout - with -B, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1933,6 +1987,7 @@ test_expect_success 'git checkout - with -b and --track, complete all references
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1944,6 +1999,7 @@ test_expect_success 'git checkout - with -B and --track, complete all references
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1955,6 +2011,7 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1966,6 +2023,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1973,6 +2031,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen
test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '
test_completion "git switch -c " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1982,6 +2041,7 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem
test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '
test_completion "git switch -C " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2019,6 +2079,7 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche
test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '
test_completion "git checkout -b " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2028,6 +2089,7 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r
test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '
test_completion "git checkout -B " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2065,6 +2127,7 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc
test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '
test_completion "git switch --orphan " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2080,6 +2143,7 @@ test_expect_success 'git switch - --orphan with branch already provided complete
test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '
test_completion "git checkout --orphan " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2093,6 +2157,7 @@ test_expect_success 'git checkout - --orphan with branch already provided comple
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
--
2.47.0.7.g072c39eddb.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7
2024-10-14 22:53 ` [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7 Bence Ferdinandy
` (5 preceding siblings ...)
2024-10-14 22:53 ` [PATCH v8 6/6] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
@ 2024-10-16 0:26 ` Taylor Blau
2024-10-16 8:18 ` Bence Ferdinandy
2024-10-19 22:53 ` [PATCH v9 0/7] set-head/fetch remote/HEAD updates Bence Ferdinandy
6 siblings, 2 replies; 258+ messages in thread
From: Taylor Blau @ 2024-10-16 0:26 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188
On Tue, Oct 15, 2024 at 12:53:09AM +0200, Bence Ferdinandy wrote:
> Bence Ferdinandy (6):
> refs: atomically record overwritten ref in update_symref
> remote set-head: add new variable for readability
> remote set-head: better output for --auto
> refs: add TRANSACTION_CREATE_EXISTS error
> refs: add create_only option to refs_update_symref
> fetch: set remote/HEAD if it does not exist
I integrated this new round into my copy of 'seen' today and noticed
some test breakage in t5505 here:
https://github.com/ttaylorr/git/actions/runs/11356267070
I'm going to temporarily eject this topic out of 'seen' until we can get
an analysis of what's going on here.
Thanks,
Taylor
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7
2024-10-16 0:26 ` [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7 Taylor Blau
@ 2024-10-16 8:18 ` Bence Ferdinandy
2024-10-16 21:05 ` Taylor Blau
2024-10-19 22:53 ` [PATCH v9 0/7] set-head/fetch remote/HEAD updates Bence Ferdinandy
1 sibling, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-16 8:18 UTC (permalink / raw)
To: Taylor Blau
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188
On Wed Oct 16, 2024 at 02:26, Taylor Blau <me@ttaylorr.com> wrote:
> On Tue, Oct 15, 2024 at 12:53:09AM +0200, Bence Ferdinandy wrote:
>> Bence Ferdinandy (6):
>> refs: atomically record overwritten ref in update_symref
>> remote set-head: add new variable for readability
>> remote set-head: better output for --auto
>> refs: add TRANSACTION_CREATE_EXISTS error
>> refs: add create_only option to refs_update_symref
>> fetch: set remote/HEAD if it does not exist
>
> I integrated this new round into my copy of 'seen' today and noticed
> some test breakage in t5505 here:
>
> https://github.com/ttaylorr/git/actions/runs/11356267070
>
> I'm going to temporarily eject this topic out of 'seen' until we can get
> an analysis of what's going on here.
Thanks for the heads up! I see that indeed the failing output is different than
what I have locally (e.g. no "apis/HEAD -> apis/main" in any of the test files
I have). On the other hand I can't reproduce it so I will need some help with
this I think.
I tested with both a rebase on master and a rebase on your seen branch. I'm
using these to commands in t/ to run tests:
prove --timer --jobs 15 ./t[0-9]*.sh
GIT_TEST_DEFAULT_REF_FORMAT=reftable prove --timer --jobs 15 ./t[0-9]*.sh
Everything passes. Should I be running this differently? I'm assuming the test
do not use any global or local git config I may have?
I'm testing on Ubuntu 22.04 natively and in WSL, but the nature of the error
doesn't seem like it could or should be related to the OS.
Thanks,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7
2024-10-16 8:18 ` Bence Ferdinandy
@ 2024-10-16 21:05 ` Taylor Blau
2024-10-17 15:23 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Taylor Blau @ 2024-10-16 21:05 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188
On Wed, Oct 16, 2024 at 10:18:44AM +0200, Bence Ferdinandy wrote:
>
> On Wed Oct 16, 2024 at 02:26, Taylor Blau <me@ttaylorr.com> wrote:
> > On Tue, Oct 15, 2024 at 12:53:09AM +0200, Bence Ferdinandy wrote:
> >> Bence Ferdinandy (6):
> >> refs: atomically record overwritten ref in update_symref
> >> remote set-head: add new variable for readability
> >> remote set-head: better output for --auto
> >> refs: add TRANSACTION_CREATE_EXISTS error
> >> refs: add create_only option to refs_update_symref
> >> fetch: set remote/HEAD if it does not exist
> >
> > I integrated this new round into my copy of 'seen' today and noticed
> > some test breakage in t5505 here:
> >
> > https://github.com/ttaylorr/git/actions/runs/11356267070
> >
> > I'm going to temporarily eject this topic out of 'seen' until we can get
> > an analysis of what's going on here.
>
> Thanks for the heads up! I see that indeed the failing output is different than
> what I have locally (e.g. no "apis/HEAD -> apis/main" in any of the test files
> I have). On the other hand I can't reproduce it so I will need some help with
> this I think.
I similarly could not reproduce it (I ran 'make test' on this topic
before integrating it into 'seen', and it passed, otherwise I wouldn't
have picked it up).
I am not sure what the differences are. The 'ci' directory has some more
bits on how the various suites are run, and the '.github/workflows'
directory has some more bits still.
Thanks,
Taylor
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7
2024-10-16 21:05 ` Taylor Blau
@ 2024-10-17 15:23 ` Bence Ferdinandy
0 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-17 15:23 UTC (permalink / raw)
To: Taylor Blau
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188
On Wed Oct 16, 2024 at 23:05, Taylor Blau <me@ttaylorr.com> wrote:
> On Wed, Oct 16, 2024 at 10:18:44AM +0200, Bence Ferdinandy wrote:
>> Thanks for the heads up! I see that indeed the failing output is different than
>> what I have locally (e.g. no "apis/HEAD -> apis/main" in any of the test files
>> I have). On the other hand I can't reproduce it so I will need some help with
>> this I think.
>
> I similarly could not reproduce it (I ran 'make test' on this topic
> before integrating it into 'seen', and it passed, otherwise I wouldn't
> have picked it up).
>
> I am not sure what the differences are. The 'ci' directory has some more
> bits on how the various suites are run, and the '.github/workflows'
> directory has some more bits still.
I managed to reproduce it in docker once, but I'm not sure how and I wasn't
able to do it again ... On the other hand (using some printf) I managed to
figure out that `fetch --multiple --all` is running both in the CI and locally
and the difference is actually what we see in the output (so no error per se).
E.g. in t5505.55 update, if you run it locally refs/remotes/apis/HEAD does not
exist, while in the CI it does.
I'm not sure why this difference exists yet, so I'm hoping someone might have
an idea. I'll keep poking around in the meanwhile.
Thanks,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v9 0/7] set-head/fetch remote/HEAD updates
2024-10-16 0:26 ` [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7 Taylor Blau
2024-10-16 8:18 ` Bence Ferdinandy
@ 2024-10-19 22:53 ` Bence Ferdinandy
2024-10-19 22:53 ` [PATCH v9 1/7] t/t5505-remote: set default branch to main Bence Ferdinandy
` (7 more replies)
1 sibling, 8 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-19 22:53 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy
Hi,
the two most notable changes are
- the new patch 1/7 which address
https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/, but see
https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/
- the refs_update_symref -> refs_update_symref_extended change in 2/7,
reflecting on Phillip's comments (see
https://lore.kernel.org/git/a7cb48e5-d8ba-44c1-9dbe-d1e8f8a63e3c@gmail.com/)
Hopefully with 1/7 the series is ready to move back to seen :)
Best,
Bence
Bence Ferdinandy (7):
t/t5505-remote: set default branch to main
refs: atomically record overwritten ref in update_symref
remote set-head: refactor for readability
remote set-head: better output for --auto
refs: add TRANSACTION_CREATE_EXISTS error
refs: add create_only option to refs_update_symref_extended
fetch: set remote/HEAD if it does not exist
builtin/fetch.c | 82 +++++++++++
builtin/remote.c | 52 +++++--
refs.c | 41 +++++-
refs.h | 8 +-
refs/files-backend.c | 24 ++--
refs/reftable-backend.c | 6 +-
t/t4207-log-decoration-colors.sh | 3 +-
t/t5505-remote.sh | 73 +++++++++-
t/t5510-fetch.sh | 229 ++++++++++++++++---------------
t/t5512-ls-remote.sh | 2 +
t/t5514-fetch-multiple.sh | 17 ++-
t/t5516-fetch-push.sh | 3 +-
t/t5527-fetch-odd-refs.sh | 3 +-
t/t7900-maintenance.sh | 3 +-
t/t9210-scalar.sh | 5 +-
t/t9211-scalar-clone.sh | 6 +-
t/t9902-completion.sh | 65 +++++++++
17 files changed, 468 insertions(+), 154 deletions(-)
--
2.47.0.92.g83fdbe24c3.dirty
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v9 1/7] t/t5505-remote: set default branch to main
2024-10-19 22:53 ` [PATCH v9 0/7] set-head/fetch remote/HEAD updates Bence Ferdinandy
@ 2024-10-19 22:53 ` Bence Ferdinandy
2024-10-19 22:53 ` [PATCH v9 2/7] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
` (6 subsequent siblings)
7 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-19 22:53 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy
Consider the bare repository called "mirror" in the test. Running `git
remote add --mirror -f origin ../one` will not change HEAD, consequently
if init.defaultBranch is not the same as what HEAD in the remote
("one"), HEAD in "mirror" will be pointing to a non-existent reference.
Hence if "mirror" is used as a remote by yet another repository,
ls-remote will not show HEAD. On the other hand, if init.defaultBranch
happens to match HEAD in "one", then ls-remote will show HEAD.
Since the CI globally exports GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main,
there's a drift between how the test repositories are set up in the CI
and during local testing. This issue does not manifest currently, as the
test does not do any remote HEAD manipulation where this would come up,
but should such things be added, a locally passing test would break the
CI vice-versa.
Set GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main in the test to be
consistent with the CI.
---
Notes:
v9: - new patch
- a bandaid for the CI issue noticed by Taylor (cf:
https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/), but
see
https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/ for the root cause in detail
t/t5505-remote.sh | 3 +++
1 file changed, 3 insertions(+)
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 532035933f..9b50276646 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -2,6 +2,9 @@
test_description='git remote porcelain-ish'
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
--
2.47.0.92.g83fdbe24c3.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v9 2/7] refs: atomically record overwritten ref in update_symref
2024-10-19 22:53 ` [PATCH v9 0/7] set-head/fetch remote/HEAD updates Bence Ferdinandy
2024-10-19 22:53 ` [PATCH v9 1/7] t/t5505-remote: set default branch to main Bence Ferdinandy
@ 2024-10-19 22:53 ` Bence Ferdinandy
2024-10-19 22:53 ` [PATCH v9 3/7] remote set-head: refactor for readability Bence Ferdinandy
` (5 subsequent siblings)
7 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-19 22:53 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy
When updating a symref with update_symref it's currently not possible to
know for sure what was the previous value that was overwritten. Extend
refs_update_symref under a new function name, to record the value after
the ref has been locked if the caller of refs_update_symref_extended
requests it via a new variable in the function call. Keep the original
refs_update_symref function with the same signature, but now as
a wrapper around refs_update_symref_extended.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: - added before_target to reftables backend
- added an extra safety check for transaction's existence in refs.c
v6: - no change
v7: - remove the whole before_target concept from the backends and
handle checking it in refs.c instead (thanks Karthik)
- rename the before_target to referent which is how the same concept
is called in the backends
- change commit prefix to be more in line with project standards
v8: no change
v9: - instead of adding parameters to refs_update_symref, rename what
was in v8 as refs_update_symref_extended and make refs_update_symref
a wrapper for that. This significantly reduces the number of files
that need to be touched, and avoids adding a lot of dummy NULL-s
in unrelated places.
refs.c | 19 ++++++++++++++++---
refs.h | 4 ++++
2 files changed, 20 insertions(+), 3 deletions(-)
diff --git a/refs.c b/refs.c
index 5f729ed412..24a4172cd2 100644
--- a/refs.c
+++ b/refs.c
@@ -2115,6 +2115,13 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
int refs_update_symref(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg)
+{
+ return refs_update_symref_extended(refs, ref, target, logmsg, NULL);
+}
+
+int refs_update_symref_extended(struct ref_store *refs, const char *ref,
+ const char *target, const char *logmsg,
+ struct strbuf *referent)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
@@ -2122,13 +2129,20 @@ int refs_update_symref(struct ref_store *refs, const char *ref,
transaction = ref_store_transaction_begin(refs, &err);
if (!transaction ||
- ref_transaction_update(transaction, ref, NULL, NULL,
+ ref_transaction_update(transaction, ref, NULL, NULL,
target, NULL, REF_NO_DEREF,
logmsg, &err) ||
- ref_transaction_commit(transaction, &err)) {
+ ref_transaction_prepare(transaction, &err)) {
ret = error("%s", err.buf);
+ goto cleanup;
}
+ if (referent)
+ refs_read_symbolic_ref(refs, ref, referent);
+ if (ref_transaction_commit(transaction, &err))
+ ret = error("%s", err.buf);
+
+cleanup:
strbuf_release(&err);
if (transaction)
ref_transaction_free(transaction);
@@ -2948,4 +2962,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)
return (update->flags & REF_HAVE_OLD) &&
(!is_null_oid(&update->old_oid) || update->old_target);
}
-
diff --git a/refs.h b/refs.h
index 108dfc93b3..259191a485 100644
--- a/refs.h
+++ b/refs.h
@@ -573,6 +573,10 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
int refs_update_symref(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg);
+int refs_update_symref_extended(struct ref_store *refs, const char *refname,
+ const char *target, const char *logmsg,
+ struct strbuf *referent);
+
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
UPDATE_REFS_DIE_ON_ERR,
--
2.47.0.92.g83fdbe24c3.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v9 3/7] remote set-head: refactor for readability
2024-10-19 22:53 ` [PATCH v9 0/7] set-head/fetch remote/HEAD updates Bence Ferdinandy
2024-10-19 22:53 ` [PATCH v9 1/7] t/t5505-remote: set default branch to main Bence Ferdinandy
2024-10-19 22:53 ` [PATCH v9 2/7] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
@ 2024-10-19 22:53 ` Bence Ferdinandy
2024-10-19 22:53 ` [PATCH v9 4/7] remote set-head: better output for --auto Bence Ferdinandy
` (4 subsequent siblings)
7 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-19 22:53 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy
Rename buf and buf2 to something more explanatory.
Instead of calling get_main_ref_store(the_repository) multiple times,
call it once and store in a new refs variable. Although this change
probably offers some performance benefits, the main purpose is to
shorten the line lengths of function calls using this variable.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v5: new patch (split from the next patch as a preparatory step)
v6: no change
v7: - change commit prefix to be more in line with project standards
v8: no change
v9: - further improve readability by renaming buf, and buf2 consistently
with how patch 6 was already done
builtin/remote.c | 23 ++++++++++++-----------
1 file changed, 12 insertions(+), 11 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 76670ddd8b..1d68c5b2ba 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1402,8 +1402,9 @@ static int show(int argc, const char **argv, const char *prefix)
static int set_head(int argc, const char **argv, const char *prefix)
{
int i, opt_a = 0, opt_d = 0, result = 0;
- struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
char *head_name = NULL;
+ struct ref_store *refs = get_main_ref_store(the_repository);
struct option options[] = {
OPT_BOOL('a', "auto", &opt_a,
@@ -1415,7 +1416,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options,
builtin_remote_sethead_usage, 0);
if (argc)
- strbuf_addf(&buf, "refs/remotes/%s/HEAD", argv[0]);
+ strbuf_addf(&b_head, "refs/remotes/%s/HEAD", argv[0]);
if (!opt_a && !opt_d && argc == 2) {
head_name = xstrdup(argv[1]);
@@ -1434,25 +1435,25 @@ static int set_head(int argc, const char **argv, const char *prefix)
head_name = xstrdup(states.heads.items[0].string);
free_remote_ref_states(&states);
} else if (opt_d && !opt_a && argc == 1) {
- if (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF))
- result |= error(_("Could not delete %s"), buf.buf);
+ if (refs_delete_ref(refs, NULL, b_head.buf, NULL, REF_NO_DEREF))
+ result |= error(_("Could not delete %s"), b_head.buf);
} else
usage_with_options(builtin_remote_sethead_usage, options);
if (head_name) {
- strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name);
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", argv[0], head_name);
/* make sure it's valid */
- if (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))
- result |= error(_("Not a valid ref: %s"), buf2.buf);
- else if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote set-head"))
- result |= error(_("Could not setup %s"), buf.buf);
+ if (!refs_ref_exists(refs, b_remote_head.buf))
+ result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
+ else if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, "remote set-head"))
+ result |= error(_("Could not setup %s"), b_head.buf);
else if (opt_a)
printf("%s/HEAD set to %s\n", argv[0], head_name);
free(head_name);
}
- strbuf_release(&buf);
- strbuf_release(&buf2);
+ strbuf_release(&b_head);
+ strbuf_release(&b_remote_head);
return result;
}
--
2.47.0.92.g83fdbe24c3.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v9 4/7] remote set-head: better output for --auto
2024-10-19 22:53 ` [PATCH v9 0/7] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (2 preceding siblings ...)
2024-10-19 22:53 ` [PATCH v9 3/7] remote set-head: refactor for readability Bence Ferdinandy
@ 2024-10-19 22:53 ` Bence Ferdinandy
2024-10-19 22:53 ` [PATCH v9 5/7] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
` (3 subsequent siblings)
7 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-19 22:53 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy
Currently, set-head --auto will print a message saying "remote/HEAD set
to branch", which implies something was changed.
Change the output of --auto, so the output actually reflects what was
done: a) set a previously unset HEAD, b) change HEAD because remote
changed or c) no updates. As a fourth output, if HEAD is changed from
a previous value that was not a remote branch, explicitly call attention
to this fact.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v1-v2:
was RFC in https://lore.kernel.org/git/20240910203835.2288291-1-bence@ferdinandy.com/
v3:
This patch was originally sent along when I thought set-head was
going to be invoked by fetch, but the discussion on the RFC
concluded that it should be not. This opened the possibility to make
it more explicit.
Note: although I feel both things the patch does are really just
cosmetic, an argument could be made for breaking it into two, one
for the no-op part and one for the --auto print update.
Was sent in:
https://lore.kernel.org/git/20240915221055.904107-1-bence@ferdinandy.com/
v4:
- changes are now handled atomically via the ref update transaction
- outputs have changed along the lines of Junio's suggestion
- minor refactor to set_head for improved legibility
v5: - the minor refactor has been split out into its own patch
v6: - fixed uninitialized prev_head
- fixed case of unusual previous target
- fixed a test that would have been actually broken at this patch
(the output was only correct with the later update to fetch)
- added 4 tests for the 4 output cases
v7: - change commit prefix to be more in line with project standards
- fixed tests to also work with the reftable backend
- renamed report function, fixed style issue with checking buf len
- fixed not releasing an strbuf
v8: no change
v9: - mark output strings in report_set_head_auto as translatable
- rename buf_prev to b_local_head for consistency
- use ${SQ} in tests instead of '\''
builtin/remote.c | 33 +++++++++++++++++++++++++++---
t/t5505-remote.sh | 51 ++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 80 insertions(+), 4 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 1d68c5b2ba..108f1271d3 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1399,10 +1399,35 @@ static int show(int argc, const char **argv, const char *prefix)
return result;
}
+static void report_set_head_auto(const char *remote, const char *head_name,
+ struct strbuf *b_local_head) {
+ struct strbuf buf_prefix = STRBUF_INIT;
+ const char *prev_head = NULL;
+
+ strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
+ skip_prefix(b_local_head->buf, buf_prefix.buf, &prev_head);
+
+ if (prev_head && !strcmp(prev_head, head_name))
+ printf(_("'%s/HEAD' is unchanged and points to '%s'\n"),
+ remote, head_name);
+ else if (prev_head)
+ printf(_("'%s/HEAD' has changed from '%s' and now points to '%s'\n"),
+ remote, prev_head, head_name);
+ else if (!b_local_head->len)
+ printf(_("'%s/HEAD' is now created and points to '%s'\n"),
+ remote, head_name);
+ else
+ printf(_("'%s/HEAD' used to point to '%s' "
+ "(which is not a remote branch), but now points to '%s'\n"),
+ remote, b_local_head->buf, head_name);
+ strbuf_release(&buf_prefix);
+}
+
static int set_head(int argc, const char **argv, const char *prefix)
{
int i, opt_a = 0, opt_d = 0, result = 0;
- struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
+ b_local_head = STRBUF_INIT;
char *head_name = NULL;
struct ref_store *refs = get_main_ref_store(the_repository);
@@ -1445,15 +1470,17 @@ static int set_head(int argc, const char **argv, const char *prefix)
/* make sure it's valid */
if (!refs_ref_exists(refs, b_remote_head.buf))
result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
- else if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, "remote set-head"))
+ else if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
+ "remote set-head", &b_local_head))
result |= error(_("Could not setup %s"), b_head.buf);
else if (opt_a)
- printf("%s/HEAD set to %s\n", argv[0], head_name);
+ report_set_head_auto(argv[0], head_name, &b_local_head);
free(head_name);
}
strbuf_release(&b_head);
strbuf_release(&b_remote_head);
+ strbuf_release(&b_local_head);
return result;
}
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 9b50276646..0ea86d51a4 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -432,12 +432,51 @@ test_expect_success 'set-head --auto' '
)
'
+test_expect_success 'set-head --auto detects creation' '
+ (
+ cd test &&
+ git symbolic-ref -d refs/remotes/origin/HEAD &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects no change' '
+ (
+ cd test &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects change' '
+ (
+ cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/ahead &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} has changed from ${SQ}ahead${SQ} and now points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects strange ref' '
+ (
+ cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD refs/heads/main &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} used to point to ${SQ}refs/heads/main${SQ} (which is not a remote branch), but now points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
(
cd test &&
git fetch two "refs/heads/*:refs/remotes/two/*" &&
git remote set-head --auto two >output 2>&1 &&
- echo "two/HEAD set to main" >expect &&
+ echo "${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
test_cmp expect output
)
'
@@ -456,6 +495,16 @@ test_expect_success 'set-head explicit' '
)
'
+test_expect_success 'set-head --auto reports change' '
+ (
+ cd test &&
+ git remote set-head origin side2 &&
+ git remote set-head --auto origin >output 2>&1 &&
+ echo "${SQ}origin/HEAD${SQ} has changed from ${SQ}side2${SQ} and now points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
cat >test/expect <<EOF
Pruning origin
URL: $(pwd)/one
--
2.47.0.92.g83fdbe24c3.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v9 5/7] refs: add TRANSACTION_CREATE_EXISTS error
2024-10-19 22:53 ` [PATCH v9 0/7] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (3 preceding siblings ...)
2024-10-19 22:53 ` [PATCH v9 4/7] remote set-head: better output for --auto Bence Ferdinandy
@ 2024-10-19 22:53 ` Bence Ferdinandy
2024-10-19 22:53 ` [PATCH v9 6/7] refs: add create_only option to refs_update_symref_extended Bence Ferdinandy
` (2 subsequent siblings)
7 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-19 22:53 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy
Currently there is only one special error for transaction, for when
there is a naming conflict, all other errors are dumped under a generic
error. Add a new special error case for when the caller requests the
reference to be updated only when it does not yet exist and the
reference actually does exist.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: no change
v6: no change
v7: - change commit prefix to be more in line with project standards
- changed error checking to Karthik's suggestion
v8: no change
v9: - no change
refs.h | 4 +++-
refs/files-backend.c | 24 ++++++++++++++++--------
refs/reftable-backend.c | 6 ++++--
3 files changed, 23 insertions(+), 11 deletions(-)
diff --git a/refs.h b/refs.h
index 259191a485..a5bc25442b 100644
--- a/refs.h
+++ b/refs.h
@@ -762,8 +762,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,
/* Naming conflict (for example, the ref names A and A/B conflict). */
#define TRANSACTION_NAME_CONFLICT -1
+/* When only creation was requested, but the ref already exists. */
+#define TRANSACTION_CREATE_EXISTS -2
/* All other errors. */
-#define TRANSACTION_GENERIC_ERROR -2
+#define TRANSACTION_GENERIC_ERROR -3
/*
* Perform the preparatory stages of committing `transaction`. Acquire
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 0824c0b8a9..e743ec44b5 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2502,14 +2502,18 @@ static int split_symref_update(struct ref_update *update,
static int check_old_oid(struct ref_update *update, struct object_id *oid,
struct strbuf *err)
{
+ int ret = TRANSACTION_GENERIC_ERROR;
+
if (!(update->flags & REF_HAVE_OLD) ||
oideq(oid, &update->old_oid))
return 0;
- if (is_null_oid(&update->old_oid))
+ if (is_null_oid(&update->old_oid)) {
strbuf_addf(err, "cannot lock ref '%s': "
"reference already exists",
ref_update_original_update_refname(update));
+ ret = TRANSACTION_CREATE_EXISTS;
+ }
else if (is_null_oid(oid))
strbuf_addf(err, "cannot lock ref '%s': "
"reference is missing but expected %s",
@@ -2522,7 +2526,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,
oid_to_hex(oid),
oid_to_hex(&update->old_oid));
- return -1;
+ return ret;
}
/*
@@ -2602,9 +2606,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
- } else if (check_old_oid(update, &lock->old_oid, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto out;
+ } else {
+ ret = check_old_oid(update, &lock->old_oid, err);
+ if (ret) {
+ goto out;
+ }
}
} else {
/*
@@ -2635,9 +2641,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,
update->old_target);
ret = TRANSACTION_GENERIC_ERROR;
goto out;
- } else if (check_old_oid(update, &lock->old_oid, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto out;
+ } else {
+ ret = check_old_oid(update, &lock->old_oid, err);
+ if (ret) {
+ goto out;
+ }
}
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 3c6107c7ce..b3b5ce77dd 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1206,10 +1206,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
goto done;
}
} else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) {
- if (is_null_oid(&u->old_oid))
+ ret = TRANSACTION_NAME_CONFLICT;
+ if (is_null_oid(&u->old_oid)) {
strbuf_addf(err, _("cannot lock ref '%s': "
"reference already exists"),
ref_update_original_update_refname(u));
+ ret = TRANSACTION_CREATE_EXISTS;
+ }
else if (is_null_oid(¤t_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
"reference is missing but expected %s"),
@@ -1221,7 +1224,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
ref_update_original_update_refname(u),
oid_to_hex(¤t_oid),
oid_to_hex(&u->old_oid));
- ret = -1;
goto done;
}
--
2.47.0.92.g83fdbe24c3.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v9 6/7] refs: add create_only option to refs_update_symref_extended
2024-10-19 22:53 ` [PATCH v9 0/7] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (4 preceding siblings ...)
2024-10-19 22:53 ` [PATCH v9 5/7] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
@ 2024-10-19 22:53 ` Bence Ferdinandy
2024-10-19 22:53 ` [PATCH v9 7/7] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
2024-10-21 21:13 ` [PATCH v9 0/7] set-head/fetch remote/HEAD updates Bence Ferdinandy
7 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-19 22:53 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy
Allow the caller to specify that it only wants to update the symref if
it does not already exist. Silently ignore the error from the
transaction API if the symref already exists.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: no change
v6: - switched from bool to int for create_only
- refactored logic in refs_update_symref with goto as layed out by
Junio
v7: - change commit prefix to be more in line with project standards
- refactored code to accommodate changes in the first patch, but
otherwise no change
v8: no change
v9: - no change (except for following through the
refs_update_symref_extended refactoring)
builtin/remote.c | 2 +-
refs.c | 32 +++++++++++++++++++++++---------
refs.h | 2 +-
3 files changed, 25 insertions(+), 11 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 108f1271d3..b1eba75a2b 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1471,7 +1471,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
if (!refs_ref_exists(refs, b_remote_head.buf))
result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
else if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
- "remote set-head", &b_local_head))
+ "remote set-head", &b_local_head, 0))
result |= error(_("Could not setup %s"), b_head.buf);
else if (opt_a)
report_set_head_auto(argv[0], head_name, &b_local_head);
diff --git a/refs.c b/refs.c
index 24a4172cd2..093ee11ab0 100644
--- a/refs.c
+++ b/refs.c
@@ -2116,31 +2116,45 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
int refs_update_symref(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg)
{
- return refs_update_symref_extended(refs, ref, target, logmsg, NULL);
+ return refs_update_symref_extended(refs, ref, target, logmsg, NULL, 0);
}
int refs_update_symref_extended(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg,
- struct strbuf *referent)
+ struct strbuf *referent, int create_only)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
- int ret = 0;
+ int ret = 0, prepret = 0;
transaction = ref_store_transaction_begin(refs, &err);
- if (!transaction ||
- ref_transaction_update(transaction, ref, NULL, NULL,
- target, NULL, REF_NO_DEREF,
- logmsg, &err) ||
- ref_transaction_prepare(transaction, &err)) {
+ if (!transaction) {
+ error_return:
ret = error("%s", err.buf);
goto cleanup;
}
+ if (create_only) {
+ if (ref_transaction_create(transaction, ref, NULL, target,
+ REF_NO_DEREF, logmsg, &err))
+ goto error_return;
+ prepret = ref_transaction_prepare(transaction, &err);
+ if (prepret && prepret != TRANSACTION_CREATE_EXISTS)
+ goto error_return;
+ } else {
+ if (ref_transaction_update(transaction, ref, NULL, NULL,
+ target, NULL, REF_NO_DEREF,
+ logmsg, &err) ||
+ ref_transaction_prepare(transaction, &err))
+ goto error_return;
+ }
+
if (referent)
refs_read_symbolic_ref(refs, ref, referent);
+ if (prepret == TRANSACTION_CREATE_EXISTS)
+ goto cleanup;
if (ref_transaction_commit(transaction, &err))
- ret = error("%s", err.buf);
+ goto error_return;
cleanup:
strbuf_release(&err);
diff --git a/refs.h b/refs.h
index a5bc25442b..458582ebcf 100644
--- a/refs.h
+++ b/refs.h
@@ -575,7 +575,7 @@ int refs_update_symref(struct ref_store *refs, const char *refname,
int refs_update_symref_extended(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg,
- struct strbuf *referent);
+ struct strbuf *referent, int create_only);
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
--
2.47.0.92.g83fdbe24c3.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v9 7/7] fetch: set remote/HEAD if it does not exist
2024-10-19 22:53 ` [PATCH v9 0/7] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (5 preceding siblings ...)
2024-10-19 22:53 ` [PATCH v9 6/7] refs: add create_only option to refs_update_symref_extended Bence Ferdinandy
@ 2024-10-19 22:53 ` Bence Ferdinandy
2024-10-21 21:13 ` [PATCH v9 0/7] set-head/fetch remote/HEAD updates Bence Ferdinandy
7 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-19 22:53 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy
If the user has remote/HEAD set already and it looks like it has changed
on the server, then print a message, otherwise set it if we can.
Silently pass if the user already has the same remote/HEAD set as
reported by the server or if we encounter any errors along the way.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v3: - does not rely on remote set-head anymore so it only authenticates
once
- uses the new REF_CREATE_ONLY to atomically check if the ref exists
and only write it if it doesn't
- in all other cases the maximum it does is print a warning
v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,
but updated transaction api to request a silent create only
- it now uses the atomic before_target to determine reporting
- refactored for legibility
v5: - instead of printing a not too useful message, it now fails
silently, this in line with the objective to only set up
remote/HEAD automatically if the right thing is trivial, for
everything else there is remote set-head
- fixed all failing tests
- added two new tests, one for checking if remote/HEAD is set to the
correct one, and one to test that we do not override remote/HEAD
if it has changed on the server from what we have locally
v6: - fixed style issues and unintended extra empty line
- updated function call with bool to int from previous patch's
change
- removed calls to error(...) inherited from builtin/remote.c so we
actually fail silently
- set the test for remote set-head --auto to the correct value here,
which was previously erronously set in the remote set-head patch
v7: - no change
v8: - changed logmsg in call to refs_update_symref from "remote
set-head" to "fetch"
v9: - follow through with refs_update_symref_extended
- fix test errors uncovered by the new patch
builtin/fetch.c | 82 +++++++++++
t/t4207-log-decoration-colors.sh | 3 +-
t/t5505-remote.sh | 21 ++-
t/t5510-fetch.sh | 229 ++++++++++++++++---------------
t/t5512-ls-remote.sh | 2 +
t/t5514-fetch-multiple.sh | 17 ++-
t/t5516-fetch-push.sh | 3 +-
t/t5527-fetch-odd-refs.sh | 3 +-
t/t7900-maintenance.sh | 3 +-
t/t9210-scalar.sh | 5 +-
t/t9211-scalar-clone.sh | 6 +-
t/t9902-completion.sh | 65 +++++++++
12 files changed, 315 insertions(+), 124 deletions(-)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index d9027e4dc9..31edc67447 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1578,6 +1578,80 @@ static int backfill_tags(struct display_state *display_state,
return retcode;
}
+static void report_set_head(const char *remote, const char *head_name,
+ struct strbuf *buf_prev) {
+ struct strbuf buf_prefix = STRBUF_INIT;
+ const char *prev_head = NULL;
+
+ strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
+ skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);
+
+ if (prev_head && strcmp(prev_head, head_name)) {
+ printf("'HEAD' at '%s' has changed from '%s' to '%s'\n",
+ remote, prev_head, head_name);
+ printf("Run 'git remote set-head %s %s' to follow the change.\n",
+ remote, head_name);
+ }
+}
+
+static const char *strip_refshead(const char *name){
+ skip_prefix(name, "refs/heads/", &name);
+ return name;
+}
+
+static int set_head(const struct ref *remote_refs)
+{
+ int result = 0;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
+ b_local_head = STRBUF_INIT;
+ const char *remote = gtransport->remote->name;
+ char *head_name = NULL;
+ struct ref *ref, *matches;
+ struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
+ struct refspec_item refspec = {
+ .force = 0,
+ .pattern = 1,
+ .src = (char *) "refs/heads/*",
+ .dst = (char *) "refs/heads/*",
+ };
+ struct string_list heads = STRING_LIST_INIT_DUP;
+ struct ref_store *refs = get_main_ref_store(the_repository);
+
+ get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
+ matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
+ fetch_map, 1);
+ for (ref = matches; ref; ref = ref->next) {
+ string_list_append(&heads, strip_refshead(ref->name));
+ }
+
+
+ if (!heads.nr)
+ result = 1;
+ else if (heads.nr > 1)
+ result = 1;
+ else
+ head_name = xstrdup(heads.items[0].string);
+ if (head_name) {
+ strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name);
+ /* make sure it's valid */
+ if (!refs_ref_exists(refs, b_remote_head.buf))
+ result = 1;
+ else if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
+ "fetch", &b_local_head, 1))
+ result = 1;
+ else
+ report_set_head(remote, head_name, &b_local_head);
+
+ free(head_name);
+ }
+
+ strbuf_release(&b_head);
+ strbuf_release(&b_local_head);
+ strbuf_release(&b_remote_head);
+ return result;
+}
+
static int do_fetch(struct transport *transport,
struct refspec *rs,
const struct fetch_config *config)
@@ -1647,6 +1721,8 @@ static int do_fetch(struct transport *transport,
"refs/tags/");
}
+ strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
+
if (must_list_refs) {
trace2_region_enter("fetch", "remote_refs", the_repository);
remote_refs = transport_get_remote_refs(transport,
@@ -1791,6 +1867,12 @@ static int do_fetch(struct transport *transport,
"you need to specify exactly one branch with the --set-upstream option"));
}
}
+ if (set_head(remote_refs))
+ ;
+ /*
+ * Way too many cases where this can go wrong
+ * so let's just fail silently for now.
+ */
cleanup:
if (retcode) {
diff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh
index 73ea9e5155..d55d22cb2f 100755
--- a/t/t4207-log-decoration-colors.sh
+++ b/t/t4207-log-decoration-colors.sh
@@ -59,7 +59,8 @@ ${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \
${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \
-${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1
+${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit}, \
+${c_reset}${c_remoteBranch}other/HEAD${c_reset}${c_commit})${c_reset} A1
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
${c_stash}refs/stash${c_reset}${c_commit})${c_reset} On main: Changes to A.t
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 0ea86d51a4..4990d00209 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -74,7 +74,7 @@ test_expect_success 'add another remote' '
cd test &&
git remote add -f second ../two &&
tokens_match "origin second" "$(git remote)" &&
- check_tracking_branch second main side another &&
+ check_tracking_branch second main side another HEAD &&
git for-each-ref "--format=%(refname)" refs/remotes |
sed -e "/^refs\/remotes\/origin\//d" \
-e "/^refs\/remotes\/second\//d" >actual &&
@@ -476,7 +476,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
cd test &&
git fetch two "refs/heads/*:refs/remotes/two/*" &&
git remote set-head --auto two >output 2>&1 &&
- echo "${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
+ echo "${SQ}two/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}" >expect &&
test_cmp expect output
)
'
@@ -764,8 +764,10 @@ test_expect_success 'reject --no-no-tags' '
'
cat >one/expect <<\EOF
+ apis/HEAD -> apis/main
apis/main
apis/side
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -783,11 +785,14 @@ test_expect_success 'update' '
'
cat >one/expect <<\EOF
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
+ manduca/HEAD -> manduca/main
manduca/main
manduca/side
+ megaloprepus/HEAD -> megaloprepus/main
megaloprepus/main
megaloprepus/side
EOF
@@ -795,7 +800,7 @@ EOF
test_expect_success 'update with arguments' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -827,10 +832,13 @@ test_expect_success 'update --prune' '
'
cat >one/expect <<-\EOF
+ apis/HEAD -> apis/main
apis/main
apis/side
+ manduca/HEAD -> manduca/main
manduca/main
manduca/side
+ megaloprepus/HEAD -> megaloprepus/main
megaloprepus/main
megaloprepus/side
EOF
@@ -838,7 +846,7 @@ EOF
test_expect_success 'update default' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -850,6 +858,7 @@ test_expect_success 'update default' '
'
cat >one/expect <<\EOF
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -858,7 +867,7 @@ EOF
test_expect_success 'update default (overridden, with funny whitespace)' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -872,7 +881,7 @@ test_expect_success 'update default (overridden, with funny whitespace)' '
test_expect_success 'update (with remotes.default defined)' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 0890b9f61c..dfc8d748ba 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -75,6 +75,30 @@ test_expect_success "fetch test for-merge" '
cut -f -2 .git/FETCH_HEAD >actual &&
test_cmp expected actual'
+test_expect_success "fetch test remote HEAD" '
+ cd "$D" &&
+ cd two &&
+ git fetch &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"'
+
+test_expect_success "fetch test remote HEAD change" '
+ cd "$D" &&
+ cd two &&
+ git switch -c other &&
+ git push -u origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git fetch &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"'
+
test_expect_success 'fetch --prune on its own works as expected' '
cd "$D" &&
git clone . prune &&
@@ -478,7 +502,6 @@ test_expect_success 'unbundle 1' '
test_must_fail git fetch "$D/bundle1" main:main
'
-
test_expect_success 'bundle 1 has only 3 files ' '
cd "$D" &&
test_bundle_object_count bundle1 3
@@ -819,7 +842,7 @@ test_expect_success 'fetch from multiple configured URLs in single remote' '
# configured prune tests
-set_config_tristate () {
+set_config_tristate() {
# var=$1 val=$2
case "$2" in
unset)
@@ -833,12 +856,12 @@ set_config_tristate () {
esac
}
-test_configured_prune () {
+test_configured_prune() {
test_configured_prune_type "$@" "name"
test_configured_prune_type "$@" "link"
}
-test_configured_prune_type () {
+test_configured_prune_type() {
fetch_prune=$1
remote_origin_prune=$2
fetch_prune_tags=$3
@@ -848,8 +871,7 @@ test_configured_prune_type () {
cmdline=$7
mode=$8
- if test -z "$cmdline_setup"
- then
+ if test -z "$cmdline_setup"; then
test_expect_success 'setup cmdline_setup variable for subsequent test' '
remote_url="file://$(git -C one config remote.origin.url)" &&
remote_fetch="$(git -C one config remote.origin.fetch)" &&
@@ -857,12 +879,10 @@ test_configured_prune_type () {
'
fi
- if test "$mode" = 'link'
- then
+ if test "$mode" = 'link'; then
new_cmdline=""
- if test "$cmdline" = ""
- then
+ if test "$cmdline" = ""; then
new_cmdline=$cmdline_setup
else
new_cmdline=$(perl -e '
@@ -873,10 +893,8 @@ test_configured_prune_type () {
fi
if test "$fetch_prune_tags" = 'true' ||
- test "$remote_origin_prune_tags" = 'true'
- then
- if ! printf '%s' "$cmdline\n" | grep -q refs/remotes/origin/
- then
+ test "$remote_origin_prune_tags" = 'true'; then
+ if ! printf '%s' "$cmdline\n" | grep -q refs/remotes/origin/; then
new_cmdline="$new_cmdline refs/tags/*:refs/tags/*"
fi
fi
@@ -946,100 +964,100 @@ test_configured_prune_type () {
# $7 git-fetch $cmdline:
#
# $1 $2 $3 $4 $5 $6 $7
-test_configured_prune unset unset unset unset kept kept ""
-test_configured_prune unset unset unset unset kept kept "--no-prune"
-test_configured_prune unset unset unset unset pruned kept "--prune"
-test_configured_prune unset unset unset unset kept pruned \
+test_configured_prune unset unset unset unset kept kept ""
+test_configured_prune unset unset unset unset kept kept "--no-prune"
+test_configured_prune unset unset unset unset pruned kept "--prune"
+test_configured_prune unset unset unset unset kept pruned \
"--prune origin refs/tags/*:refs/tags/*"
test_configured_prune unset unset unset unset pruned pruned \
"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
-test_configured_prune false unset unset unset kept kept ""
-test_configured_prune false unset unset unset kept kept "--no-prune"
-test_configured_prune false unset unset unset pruned kept "--prune"
+test_configured_prune false unset unset unset kept kept ""
+test_configured_prune false unset unset unset kept kept "--no-prune"
+test_configured_prune false unset unset unset pruned kept "--prune"
-test_configured_prune true unset unset unset pruned kept ""
-test_configured_prune true unset unset unset pruned kept "--prune"
-test_configured_prune true unset unset unset kept kept "--no-prune"
+test_configured_prune true unset unset unset pruned kept ""
+test_configured_prune true unset unset unset pruned kept "--prune"
+test_configured_prune true unset unset unset kept kept "--no-prune"
-test_configured_prune unset false unset unset kept kept ""
-test_configured_prune unset false unset unset kept kept "--no-prune"
-test_configured_prune unset false unset unset pruned kept "--prune"
+test_configured_prune unset false unset unset kept kept ""
+test_configured_prune unset false unset unset kept kept "--no-prune"
+test_configured_prune unset false unset unset pruned kept "--prune"
-test_configured_prune false false unset unset kept kept ""
-test_configured_prune false false unset unset kept kept "--no-prune"
-test_configured_prune false false unset unset pruned kept "--prune"
-test_configured_prune false false unset unset kept pruned \
+test_configured_prune false false unset unset kept kept ""
+test_configured_prune false false unset unset kept kept "--no-prune"
+test_configured_prune false false unset unset pruned kept "--prune"
+test_configured_prune false false unset unset kept pruned \
"--prune origin refs/tags/*:refs/tags/*"
test_configured_prune false false unset unset pruned pruned \
"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
-test_configured_prune true false unset unset kept kept ""
-test_configured_prune true false unset unset pruned kept "--prune"
-test_configured_prune true false unset unset kept kept "--no-prune"
+test_configured_prune true false unset unset kept kept ""
+test_configured_prune true false unset unset pruned kept "--prune"
+test_configured_prune true false unset unset kept kept "--no-prune"
-test_configured_prune unset true unset unset pruned kept ""
-test_configured_prune unset true unset unset kept kept "--no-prune"
-test_configured_prune unset true unset unset pruned kept "--prune"
+test_configured_prune unset true unset unset pruned kept ""
+test_configured_prune unset true unset unset kept kept "--no-prune"
+test_configured_prune unset true unset unset pruned kept "--prune"
-test_configured_prune false true unset unset pruned kept ""
-test_configured_prune false true unset unset kept kept "--no-prune"
-test_configured_prune false true unset unset pruned kept "--prune"
+test_configured_prune false true unset unset pruned kept ""
+test_configured_prune false true unset unset kept kept "--no-prune"
+test_configured_prune false true unset unset pruned kept "--prune"
-test_configured_prune true true unset unset pruned kept ""
-test_configured_prune true true unset unset pruned kept "--prune"
-test_configured_prune true true unset unset kept kept "--no-prune"
-test_configured_prune true true unset unset kept pruned \
+test_configured_prune true true unset unset pruned kept ""
+test_configured_prune true true unset unset pruned kept "--prune"
+test_configured_prune true true unset unset kept kept "--no-prune"
+test_configured_prune true true unset unset kept pruned \
"--prune origin refs/tags/*:refs/tags/*"
-test_configured_prune true true unset unset pruned pruned \
+test_configured_prune true true unset unset pruned pruned \
"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
# --prune-tags on its own does nothing, needs --prune as well, same
# for fetch.pruneTags without fetch.prune
-test_configured_prune unset unset unset unset kept kept "--prune-tags"
-test_configured_prune unset unset true unset kept kept ""
-test_configured_prune unset unset unset true kept kept ""
+test_configured_prune unset unset unset unset kept kept "--prune-tags"
+test_configured_prune unset unset true unset kept kept ""
+test_configured_prune unset unset unset true kept kept ""
# These will prune the tags
test_configured_prune unset unset unset unset pruned pruned "--prune --prune-tags"
-test_configured_prune true unset true unset pruned pruned ""
-test_configured_prune unset true unset true pruned pruned ""
+test_configured_prune true unset true unset pruned pruned ""
+test_configured_prune unset true unset true pruned pruned ""
# remote.<name>.pruneTags overrides fetch.pruneTags, just like
# remote.<name>.prune overrides fetch.prune if set.
-test_configured_prune true unset true unset pruned pruned ""
-test_configured_prune false true false true pruned pruned ""
-test_configured_prune true false true false kept kept ""
+test_configured_prune true unset true unset pruned pruned ""
+test_configured_prune false true false true pruned pruned ""
+test_configured_prune true false true false kept kept ""
# When --prune-tags is supplied it's ignored if an explicit refspec is
# given, same for the configuration options.
test_configured_prune unset unset unset unset pruned kept \
"--prune --prune-tags origin +refs/heads/*:refs/remotes/origin/*"
-test_configured_prune unset unset true unset pruned kept \
+test_configured_prune unset unset true unset pruned kept \
"--prune origin +refs/heads/*:refs/remotes/origin/*"
-test_configured_prune unset unset unset true pruned kept \
+test_configured_prune unset unset unset true pruned kept \
"--prune origin +refs/heads/*:refs/remotes/origin/*"
# Pruning that also takes place if a file:// url replaces a named
# remote. However, because there's no implicit
# +refs/heads/*:refs/remotes/origin/* refspec and supplying it on the
# command-line negates --prune-tags, the branches will not be pruned.
-test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "name"
-test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "link"
+test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "name"
+test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "link"
test_configured_prune_type unset unset unset unset pruned pruned "origin --prune --prune-tags" "name"
-test_configured_prune_type unset unset unset unset kept pruned "origin --prune --prune-tags" "link"
+test_configured_prune_type unset unset unset unset kept pruned "origin --prune --prune-tags" "link"
test_configured_prune_type unset unset unset unset pruned pruned "--prune --prune-tags origin" "name"
-test_configured_prune_type unset unset unset unset kept pruned "--prune --prune-tags origin" "link"
-test_configured_prune_type unset unset true unset pruned pruned "--prune origin" "name"
-test_configured_prune_type unset unset true unset kept pruned "--prune origin" "link"
-test_configured_prune_type unset unset unset true pruned pruned "--prune origin" "name"
-test_configured_prune_type unset unset unset true kept pruned "--prune origin" "link"
-test_configured_prune_type true unset true unset pruned pruned "origin" "name"
-test_configured_prune_type true unset true unset kept pruned "origin" "link"
-test_configured_prune_type unset true true unset pruned pruned "origin" "name"
-test_configured_prune_type unset true true unset kept pruned "origin" "link"
-test_configured_prune_type unset true unset true pruned pruned "origin" "name"
-test_configured_prune_type unset true unset true kept pruned "origin" "link"
+test_configured_prune_type unset unset unset unset kept pruned "--prune --prune-tags origin" "link"
+test_configured_prune_type unset unset true unset pruned pruned "--prune origin" "name"
+test_configured_prune_type unset unset true unset kept pruned "--prune origin" "link"
+test_configured_prune_type unset unset unset true pruned pruned "--prune origin" "name"
+test_configured_prune_type unset unset unset true kept pruned "--prune origin" "link"
+test_configured_prune_type true unset true unset pruned pruned "origin" "name"
+test_configured_prune_type true unset true unset kept pruned "origin" "link"
+test_configured_prune_type unset true true unset pruned pruned "origin" "name"
+test_configured_prune_type unset true true unset kept pruned "origin" "link"
+test_configured_prune_type unset true unset true pruned pruned "origin" "name"
+test_configured_prune_type unset true unset true kept pruned "origin" "link"
# When all remote.origin.fetch settings are deleted a --prune
# --prune-tags still implicitly supplies refs/tags/*:refs/tags/* so
@@ -1137,8 +1155,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '
)
'
-for section in fetch transfer
-do
+for section in fetch transfer; do
test_expect_success "$section.hideRefs affects connectivity check" '
GIT_TRACE="$PWD"/trace git -c $section.hideRefs=refs -c \
$section.hideRefs="!refs/tags/" fetch &&
@@ -1157,21 +1174,23 @@ test_expect_success 'prepare source branch' '
test 3 -le $(wc -l <actual)
'
-validate_store_type () {
+validate_store_type() {
git -C dest count-objects -v >actual &&
- case "$store_type" in
- packed)
- grep "^count: 0$" actual ;;
- loose)
- grep "^packs: 0$" actual ;;
- esac || {
+ case "$store_type" in
+ packed)
+ grep "^count: 0$" actual
+ ;;
+ loose)
+ grep "^packs: 0$" actual
+ ;;
+ esac || {
echo "store_type is $store_type"
cat actual
false
}
}
-test_unpack_limit () {
+test_unpack_limit() {
store_type=$1
case "$store_type" in
@@ -1192,43 +1211,39 @@ test_unpack_limit () {
test_unpack_limit packed
test_unpack_limit loose
-setup_negotiation_tip () {
+setup_negotiation_tip() {
SERVER="$1"
URL="$2"
USE_PROTOCOL_V2="$3"
rm -rf "$SERVER" client trace &&
- git init -b main "$SERVER" &&
- test_commit -C "$SERVER" alpha_1 &&
- test_commit -C "$SERVER" alpha_2 &&
- git -C "$SERVER" checkout --orphan beta &&
- test_commit -C "$SERVER" beta_1 &&
- test_commit -C "$SERVER" beta_2 &&
-
- git clone "$URL" client &&
-
- if test "$USE_PROTOCOL_V2" -eq 1
- then
- git -C "$SERVER" config protocol.version 2 &&
- git -C client config protocol.version 2
- fi &&
-
- test_commit -C "$SERVER" beta_s &&
- git -C "$SERVER" checkout main &&
- test_commit -C "$SERVER" alpha_s &&
- git -C "$SERVER" tag -d alpha_1 alpha_2 beta_1 beta_2
+ git init -b main "$SERVER" &&
+ test_commit -C "$SERVER" alpha_1 &&
+ test_commit -C "$SERVER" alpha_2 &&
+ git -C "$SERVER" checkout --orphan beta &&
+ test_commit -C "$SERVER" beta_1 &&
+ test_commit -C "$SERVER" beta_2 &&
+ git clone "$URL" client &&
+ if test "$USE_PROTOCOL_V2" -eq 1; then
+ git -C "$SERVER" config protocol.version 2 &&
+ git -C client config protocol.version 2
+ fi &&
+ test_commit -C "$SERVER" beta_s &&
+ git -C "$SERVER" checkout main &&
+ test_commit -C "$SERVER" alpha_s &&
+ git -C "$SERVER" tag -d alpha_1 alpha_2 beta_1 beta_2
}
-check_negotiation_tip () {
+check_negotiation_tip() {
# Ensure that {alpha,beta}_1 are sent as "have", but not {alpha_beta}_2
ALPHA_1=$(git -C client rev-parse alpha_1) &&
- grep "fetch> have $ALPHA_1" trace &&
- BETA_1=$(git -C client rev-parse beta_1) &&
- grep "fetch> have $BETA_1" trace &&
- ALPHA_2=$(git -C client rev-parse alpha_2) &&
- ! grep "fetch> have $ALPHA_2" trace &&
- BETA_2=$(git -C client rev-parse beta_2) &&
- ! grep "fetch> have $BETA_2" trace
+ grep "fetch> have $ALPHA_1" trace &&
+ BETA_1=$(git -C client rev-parse beta_1) &&
+ grep "fetch> have $BETA_1" trace &&
+ ALPHA_2=$(git -C client rev-parse alpha_2) &&
+ ! grep "fetch> have $ALPHA_2" trace &&
+ BETA_2=$(git -C client rev-parse beta_2) &&
+ ! grep "fetch> have $BETA_2" trace
}
test_expect_success '--negotiation-tip limits "have" lines sent' '
diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh
index 64b3491e4e..1b3865e154 100755
--- a/t/t5512-ls-remote.sh
+++ b/t/t5512-ls-remote.sh
@@ -293,6 +293,8 @@ test_expect_success 'ls-remote with filtered symref (refname)' '
cat >expect <<-EOF &&
ref: refs/heads/main HEAD
$rev HEAD
+ ref: refs/remotes/origin/main refs/remotes/origin/HEAD
+ $rev refs/remotes/origin/HEAD
EOF
git ls-remote --symref . HEAD >actual &&
test_cmp expect actual
diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh
index 579872c258..e3482b27b2 100755
--- a/t/t5514-fetch-multiple.sh
+++ b/t/t5514-fetch-multiple.sh
@@ -45,14 +45,17 @@ test_expect_success setup '
'
cat > test/expect << EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -97,6 +100,7 @@ cat > expect << EOF
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
@@ -112,8 +116,10 @@ test_expect_success 'git fetch --multiple (but only one remote)' '
'
cat > expect << EOF
+ one/HEAD -> one/main
one/main
one/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -141,7 +147,7 @@ test_expect_success 'git fetch --multiple (bad remote names)' '
test_expect_success 'git fetch --all (skipFetchAll)' '
(cd test4 &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -153,11 +159,14 @@ test_expect_success 'git fetch --all (skipFetchAll)' '
'
cat > expect << EOF
+ one/HEAD -> one/main
one/main
one/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -165,7 +174,7 @@ EOF
test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '
(cd test4 &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -221,14 +230,17 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' '
create_fetch_all_expect () {
cat >expect <<-\EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -265,6 +277,7 @@ test_expect_success 'git fetch (fetch all remotes with fetch.all = true)' '
create_fetch_one_expect () {
cat >expect <<-\EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 331778bd42..5a051aa0c7 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -1395,7 +1395,8 @@ test_expect_success 'fetch follows tags by default' '
git tag -m "annotated" tag &&
git for-each-ref >tmp1 &&
sed -n "p; s|refs/heads/main$|refs/remotes/origin/main|p" tmp1 |
- sort -k 3 >../expect
+ sed -n "p; s|refs/heads/main$|refs/remotes/origin/HEAD|p" |
+ sort -k 4 >../expect
) &&
test_when_finished "rm -rf dst" &&
git init dst &&
diff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh
index 98ece27c6a..d3996af6ee 100755
--- a/t/t5527-fetch-odd-refs.sh
+++ b/t/t5527-fetch-odd-refs.sh
@@ -52,7 +52,8 @@ test_expect_success LONG_REF 'fetch handles extremely long refname' '
long
main
EOF
- git for-each-ref --format="%(subject)" refs/remotes/long >actual &&
+ git for-each-ref --format="%(subject)" refs/remotes/long \
+ --exclude=refs/remotes/long/HEAD >actual &&
test_cmp expect actual
'
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index a66d0e089d..88331d1ceb 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -329,7 +329,8 @@ test_expect_success 'incremental-repack task' '
# Delete refs that have not been repacked in these packs.
git for-each-ref --format="delete %(refname)" \
- refs/prefetch refs/tags refs/remotes >refs &&
+ refs/prefetch refs/tags refs/remotes \
+ --exclude=refs/remotes/*/HEAD >refs &&
git update-ref --stdin <refs &&
# Replace the object directory with this pack layout.
diff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh
index a30b2c9f70..2237844550 100755
--- a/t/t9210-scalar.sh
+++ b/t/t9210-scalar.sh
@@ -151,7 +151,8 @@ test_expect_success 'scalar clone' '
"$(pwd)" &&
git for-each-ref --format="%(refname)" refs/remotes/origin/ >actual &&
- echo "refs/remotes/origin/parallel" >expect &&
+ echo "refs/remotes/origin/HEAD" >>expect &&
+ echo "refs/remotes/origin/parallel" >>expect &&
test_cmp expect actual &&
test_path_is_missing 1/2 &&
@@ -220,7 +221,7 @@ test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '
done
'
- test_expect_success 'scalar reconfigure --all with detached HEADs' '
+test_expect_success 'scalar reconfigure --all with detached HEADs' '
repos="two three four" &&
for num in $repos
do
diff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh
index c16ea67c1d..d9cb6b9a3e 100755
--- a/t/t9211-scalar-clone.sh
+++ b/t/t9211-scalar-clone.sh
@@ -32,7 +32,7 @@ test_expect_success 'set up repository to clone' '
)
'
-cleanup_clone () {
+cleanup_clone() {
rm -rf "$1"
}
@@ -128,7 +128,7 @@ test_expect_success '--single-branch clones HEAD only' '
(
cd $enlistment/src &&
git for-each-ref refs/remotes/origin >out &&
- test_line_count = 1 out &&
+ test_line_count = 2 out &&
grep "refs/remotes/origin/base" out
) &&
@@ -142,7 +142,7 @@ test_expect_success '--no-single-branch clones all branches' '
(
cd $enlistment/src &&
git for-each-ref refs/remotes/origin >out &&
- test_line_count = 2 out &&
+ test_line_count = 3 out &&
grep "refs/remotes/origin/base" out &&
grep "refs/remotes/origin/parallel" out
) &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index cc6aa9f0cd..b663c4609e 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -658,6 +658,7 @@ test_expect_success '__git_refs - simple' '
HEAD
main
matching-branch
+ other/HEAD
other/branch-in-other
other/main-in-other
matching-tag
@@ -673,6 +674,7 @@ test_expect_success '__git_refs - full refs' '
cat >expected <<-EOF &&
refs/heads/main
refs/heads/matching-branch
+ refs/remotes/other/HEAD
refs/remotes/other/branch-in-other
refs/remotes/other/main-in-other
refs/tags/matching-tag
@@ -729,6 +731,7 @@ test_expect_success '__git_refs - remote on local file system - full refs' '
test_expect_success '__git_refs - configured remote' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -756,6 +759,7 @@ test_expect_success '__git_refs - configured remote - full refs' '
test_expect_success '__git_refs - configured remote - repo given on the command line' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -787,6 +791,7 @@ test_expect_success '__git_refs - configured remote - full refs - repo given on
test_expect_success '__git_refs - configured remote - remote name matches a directory' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -875,12 +880,14 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer
HEAD
main
matching-branch
+ other/HEAD
other/ambiguous
other/branch-in-other
other/main-in-other
remote/ambiguous
remote/branch-in-remote
matching-tag
+ HEAD
branch-in-other
branch-in-remote
main-in-other
@@ -904,6 +911,7 @@ test_expect_success '__git_refs - after --opt=' '
HEAD
main
matching-branch
+ other/HEAD
other/branch-in-other
other/main-in-other
matching-tag
@@ -919,6 +927,7 @@ test_expect_success '__git_refs - after --opt= - full refs' '
cat >expected <<-EOF &&
refs/heads/main
refs/heads/matching-branch
+ refs/remotes/other/HEAD
refs/remotes/other/branch-in-other
refs/remotes/other/main-in-other
refs/tags/matching-tag
@@ -935,6 +944,7 @@ test_expect_success '__git refs - excluding refs' '
^HEAD
^main
^matching-branch
+ ^other/HEAD
^other/branch-in-other
^other/main-in-other
^matching-tag
@@ -950,6 +960,7 @@ test_expect_success '__git refs - excluding full refs' '
cat >expected <<-EOF &&
^refs/heads/main
^refs/heads/matching-branch
+ ^refs/remotes/other/HEAD
^refs/remotes/other/branch-in-other
^refs/remotes/other/main-in-other
^refs/tags/matching-tag
@@ -975,6 +986,7 @@ test_expect_success '__git_refs - do not filter refs unless told so' '
main
matching-branch
matching/branch
+ other/HEAD
other/branch-in-other
other/main-in-other
other/matching/branch-in-other
@@ -1095,6 +1107,7 @@ test_expect_success '__git_complete_refs - simple' '
HEAD Z
main Z
matching-branch Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
matching-tag Z
@@ -1123,6 +1136,7 @@ test_expect_success '__git_complete_refs - matching' '
test_expect_success '__git_complete_refs - remote' '
sed -e "s/Z$//" >expected <<-EOF &&
HEAD Z
+ HEAD Z
branch-in-other Z
main-in-other Z
EOF
@@ -1139,9 +1153,11 @@ test_expect_success '__git_complete_refs - track' '
HEAD Z
main Z
matching-branch Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
matching-tag Z
+ HEAD Z
branch-in-other Z
main-in-other Z
EOF
@@ -1184,6 +1200,7 @@ test_expect_success '__git_complete_refs - suffix' '
HEAD.
main.
matching-branch.
+ other/HEAD.
other/branch-in-other.
other/main-in-other.
matching-tag.
@@ -1199,6 +1216,7 @@ test_expect_success '__git_complete_refs - suffix' '
test_expect_success '__git_complete_fetch_refspecs - simple' '
sed -e "s/Z$//" >expected <<-EOF &&
HEAD:HEAD Z
+ HEAD:HEAD Z
branch-in-other:branch-in-other Z
main-in-other:main-in-other Z
EOF
@@ -1225,6 +1243,7 @@ test_expect_success '__git_complete_fetch_refspecs - matching' '
test_expect_success '__git_complete_fetch_refspecs - prefix' '
sed -e "s/Z$//" >expected <<-EOF &&
+HEAD:HEAD Z
+ +HEAD:HEAD Z
+branch-in-other:branch-in-other Z
+main-in-other:main-in-other Z
EOF
@@ -1289,6 +1308,7 @@ test_expect_success '__git_complete_worktree_paths with -C' '
test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '
test_completion "git switch " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1435,11 +1455,13 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena
test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '
test_completion "git checkout " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1461,6 +1483,7 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl
test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '
GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git switch --guess " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1470,6 +1493,7 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G
test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '
test_completion "git switch --no-guess --guess " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1490,6 +1514,7 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1498,11 +1523,13 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete
test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '
GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git checkout --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1514,6 +1541,7 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1522,11 +1550,13 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '
test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '
test_completion "git checkout --no-guess --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1538,6 +1568,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1550,6 +1581,7 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1559,11 +1591,13 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a
test_config checkout.guess true &&
test_completion "git checkout " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1573,11 +1607,13 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.
test_config checkout.guess false &&
test_completion "git checkout --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1590,6 +1626,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1601,6 +1638,7 @@ test_expect_success 'git switch - with --detach, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1612,6 +1650,7 @@ test_expect_success 'git checkout - with --detach, complete only references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1783,6 +1822,7 @@ test_expect_success 'git switch - with -d, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1794,6 +1834,7 @@ test_expect_success 'git checkout - with -d, complete only references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1801,10 +1842,12 @@ test_expect_success 'git checkout - with -d, complete only references' '
test_expect_success 'git switch - with --track, complete only remote branches' '
test_completion "git switch --track " <<-\EOF &&
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
test_completion "git switch -t " <<-\EOF
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1812,10 +1855,12 @@ test_expect_success 'git switch - with --track, complete only remote branches' '
test_expect_success 'git checkout - with --track, complete only remote branches' '
test_completion "git checkout --track " <<-\EOF &&
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
test_completion "git checkout -t " <<-\EOF
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1834,6 +1879,7 @@ test_expect_success 'git checkout - with --no-track, complete only local referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1845,6 +1891,7 @@ test_expect_success 'git switch - with -c, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1856,6 +1903,7 @@ test_expect_success 'git switch - with -C, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1867,6 +1915,7 @@ test_expect_success 'git switch - with -c and --track, complete all references'
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1878,6 +1927,7 @@ test_expect_success 'git switch - with -C and --track, complete all references'
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1889,6 +1939,7 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1900,6 +1951,7 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1911,6 +1963,7 @@ test_expect_success 'git checkout - with -b, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1922,6 +1975,7 @@ test_expect_success 'git checkout - with -B, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1933,6 +1987,7 @@ test_expect_success 'git checkout - with -b and --track, complete all references
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1944,6 +1999,7 @@ test_expect_success 'git checkout - with -B and --track, complete all references
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1955,6 +2011,7 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1966,6 +2023,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1973,6 +2031,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen
test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '
test_completion "git switch -c " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1982,6 +2041,7 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem
test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '
test_completion "git switch -C " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2019,6 +2079,7 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche
test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '
test_completion "git checkout -b " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2028,6 +2089,7 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r
test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '
test_completion "git checkout -B " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2065,6 +2127,7 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc
test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '
test_completion "git switch --orphan " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2080,6 +2143,7 @@ test_expect_success 'git switch - --orphan with branch already provided complete
test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '
test_completion "git checkout --orphan " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2093,6 +2157,7 @@ test_expect_success 'git checkout - --orphan with branch already provided comple
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
--
2.47.0.92.g83fdbe24c3.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v9 0/7] set-head/fetch remote/HEAD updates
2024-10-19 22:53 ` [PATCH v9 0/7] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (6 preceding siblings ...)
2024-10-19 22:53 ` [PATCH v9 7/7] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
@ 2024-10-21 21:13 ` Bence Ferdinandy
2024-10-22 19:45 ` [PATCH v11 0/8] " Bence Ferdinandy
7 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-21 21:13 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau
Hi,
just a heads-up that due to me not being careful, v10 is at a new thread:
https://lore.kernel.org/git/4836c7c9-58d7-4e2a-ab78-be4e7296dc1f@ferdinandy.com/T/,
with a wrong noreply address in Cc.
Sorry for the noise.
Best,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v11 0/8] set-head/fetch remote/HEAD updates
2024-10-21 21:13 ` [PATCH v9 0/7] set-head/fetch remote/HEAD updates Bence Ferdinandy
@ 2024-10-22 19:45 ` Bence Ferdinandy
2024-10-22 19:45 ` [PATCH v11 1/8] t/t5505-remote: set default branch to main Bence Ferdinandy
` (8 more replies)
0 siblings, 9 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-22 19:45 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy
v11: fixed leaks found by running tests after `make SANITIZE=leak`
green pipeline:
https://github.com/ferdinandyb/git/actions/runs/11466803523
v10: added a new patch (8/8) on top of v9 to handle HEAD in mirrored bare
repositories, rest of the patches are unchanged
v9:
the two most notable changes are
- the new patch 1/7 which address
https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/, but see
https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/
- the refs_update_symref -> refs_update_symref_extended change in 2/7,
reflecting on Phillip's comments (see
https://lore.kernel.org/git/a7cb48e5-d8ba-44c1-9dbe-d1e8f8a63e3c@gmail.com/)
Bence Ferdinandy (8):
t/t5505-remote: set default branch to main
refs: atomically record overwritten ref in update_symref
remote set-head: refactor for readability
remote set-head: better output for --auto
refs: add TRANSACTION_CREATE_EXISTS error
refs: add create_only option to refs_update_symref_extended
fetch: set remote/HEAD if it does not exist
fetch set_head: handle mirrored bare repositories
builtin/fetch.c | 94 +++++++++++++
builtin/remote.c | 52 +++++--
refs.c | 41 +++++-
refs.h | 8 +-
refs/files-backend.c | 24 ++--
refs/reftable-backend.c | 6 +-
t/t4207-log-decoration-colors.sh | 3 +-
t/t5505-remote.sh | 83 ++++++++++-
t/t5510-fetch.sh | 229 ++++++++++++++++---------------
t/t5512-ls-remote.sh | 2 +
t/t5514-fetch-multiple.sh | 17 ++-
t/t5516-fetch-push.sh | 3 +-
t/t5527-fetch-odd-refs.sh | 3 +-
t/t7900-maintenance.sh | 3 +-
t/t9210-scalar.sh | 5 +-
t/t9211-scalar-clone.sh | 6 +-
t/t9902-completion.sh | 65 +++++++++
17 files changed, 490 insertions(+), 154 deletions(-)
--
2.47.0.94.gb64850d498
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v11 1/8] t/t5505-remote: set default branch to main
2024-10-22 19:45 ` [PATCH v11 0/8] " Bence Ferdinandy
@ 2024-10-22 19:45 ` Bence Ferdinandy
2024-10-22 19:53 ` Kristoffer Haugsbakk
2024-10-22 19:45 ` [PATCH v11 2/8] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
` (7 subsequent siblings)
8 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-22 19:45 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy
Consider the bare repository called "mirror" in the test. Running `git
remote add --mirror -f origin ../one` will not change HEAD, consequently
if init.defaultBranch is not the same as what HEAD in the remote
("one"), HEAD in "mirror" will be pointing to a non-existent reference.
Hence if "mirror" is used as a remote by yet another repository,
ls-remote will not show HEAD. On the other hand, if init.defaultBranch
happens to match HEAD in "one", then ls-remote will show HEAD.
Since the CI globally exports GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main,
there's a drift between how the test repositories are set up in the CI
and during local testing. This issue does not manifest currently, as the
test does not do any remote HEAD manipulation where this would come up,
but should such things be added, a locally passing test would break the
CI vice-versa.
Set GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main in the test to be
consistent with the CI.
---
Notes:
v9: - new patch
- a bandaid for the CI issue noticed by Taylor (cf:
https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/), but
see
https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/ for the root cause in detail
v10: no change
v11: no change
t/t5505-remote.sh | 3 +++
1 file changed, 3 insertions(+)
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 532035933f..9b50276646 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -2,6 +2,9 @@
test_description='git remote porcelain-ish'
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
--
2.47.0.94.gb64850d498
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v11 1/8] t/t5505-remote: set default branch to main
2024-10-22 19:45 ` [PATCH v11 1/8] t/t5505-remote: set default branch to main Bence Ferdinandy
@ 2024-10-22 19:53 ` Kristoffer Haugsbakk
2024-10-22 20:14 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Kristoffer Haugsbakk @ 2024-10-22 19:53 UTC (permalink / raw)
To: Bence Ferdinandy, git
Cc: Phillip Wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, Karthik Nayak, Taylor Blau
On Tue, Oct 22, 2024, at 21:45, Bence Ferdinandy wrote:
> Consider the bare repository called "mirror" in the test. Running `git
> remote add --mirror -f origin ../one` will not change HEAD, consequently
> if init.defaultBranch is not the same as what HEAD in the remote
> ("one"), HEAD in "mirror" will be pointing to a non-existent reference.
> Hence if "mirror" is used as a remote by yet another repository,
> ls-remote will not show HEAD. On the other hand, if init.defaultBranch
> happens to match HEAD in "one", then ls-remote will show HEAD.
>
> Since the CI globally exports GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main,
> there's a drift between how the test repositories are set up in the CI
> and during local testing. This issue does not manifest currently, as the
> test does not do any remote HEAD manipulation where this would come up,
> but should such things be added, a locally passing test would break the
> CI vice-versa.
>
> Set GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main in the test to be
> consistent with the CI.
> ---
>
> Notes:
> v9: - new patch
> - a bandaid for the CI issue noticed by Taylor (cf:
> https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/),
> but
> see
>
> https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/
> for the root cause in detail
Why no signoff?
--
Kristoffer Haugsbakk
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v11 1/8] t/t5505-remote: set default branch to main
2024-10-22 19:53 ` Kristoffer Haugsbakk
@ 2024-10-22 20:14 ` Bence Ferdinandy
2024-10-23 15:09 ` Taylor Blau
0 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-22 20:14 UTC (permalink / raw)
To: Kristoffer Haugsbakk, git
Cc: Phillip Wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, Karthik Nayak, Taylor Blau
On Tue Oct 22, 2024 at 21:53, Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com> wrote:
> On Tue, Oct 22, 2024, at 21:45, Bence Ferdinandy wrote:
>> Consider the bare repository called "mirror" in the test. Running `git
>> remote add --mirror -f origin ../one` will not change HEAD, consequently
>> if init.defaultBranch is not the same as what HEAD in the remote
>> ("one"), HEAD in "mirror" will be pointing to a non-existent reference.
>> Hence if "mirror" is used as a remote by yet another repository,
>> ls-remote will not show HEAD. On the other hand, if init.defaultBranch
>> happens to match HEAD in "one", then ls-remote will show HEAD.
>>
>> Since the CI globally exports GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main,
>> there's a drift between how the test repositories are set up in the CI
>> and during local testing. This issue does not manifest currently, as the
>> test does not do any remote HEAD manipulation where this would come up,
>> but should such things be added, a locally passing test would break the
>> CI vice-versa.
>>
>> Set GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main in the test to be
>> consistent with the CI.
>> ---
>>
>> Notes:
>> v9: - new patch
>> - a bandaid for the CI issue noticed by Taylor (cf:
>> https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/),
>> but
>> see
>>
>> https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/
>> for the root cause in detail
>
> Why no signoff?
Because I always forget :( I've been thinking on making it by default, but
I didn't want to add it to all the work related commits, where I'd be the only
one doing it, but I just found the format.signoff setting, so I should be
covered for future patches.
I'll wait some time before sending a fix for this :)
Thanks,
Bence
--
bence.ferdinandy.com
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v11 1/8] t/t5505-remote: set default branch to main
2024-10-22 20:14 ` Bence Ferdinandy
@ 2024-10-23 15:09 ` Taylor Blau
2024-10-23 15:34 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Taylor Blau @ 2024-10-23 15:09 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: Kristoffer Haugsbakk, git, Phillip Wood, René Scharfe,
Johannes Schindelin, Junio C Hamano, Karthik Nayak
On Tue, Oct 22, 2024 at 10:14:12PM +0200, Bence Ferdinandy wrote:
>
> On Tue Oct 22, 2024 at 21:53, Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com> wrote:
> > On Tue, Oct 22, 2024, at 21:45, Bence Ferdinandy wrote:
> >> Consider the bare repository called "mirror" in the test. Running `git
> >> remote add --mirror -f origin ../one` will not change HEAD, consequently
> >> if init.defaultBranch is not the same as what HEAD in the remote
> >> ("one"), HEAD in "mirror" will be pointing to a non-existent reference.
> >> Hence if "mirror" is used as a remote by yet another repository,
> >> ls-remote will not show HEAD. On the other hand, if init.defaultBranch
> >> happens to match HEAD in "one", then ls-remote will show HEAD.
> >>
> >> Since the CI globally exports GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main,
> >> there's a drift between how the test repositories are set up in the CI
> >> and during local testing. This issue does not manifest currently, as the
> >> test does not do any remote HEAD manipulation where this would come up,
> >> but should such things be added, a locally passing test would break the
> >> CI vice-versa.
> >>
> >> Set GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main in the test to be
> >> consistent with the CI.
> >> ---
> >>
> >> Notes:
> >> v9: - new patch
> >> - a bandaid for the CI issue noticed by Taylor (cf:
> >> https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/),
> >> but
> >> see
> >>
> >> https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/
> >> for the root cause in detail
> >
> > Why no signoff?
>
> Because I always forget :( I've been thinking on making it by default, but
> I didn't want to add it to all the work related commits, where I'd be the only
> one doing it, but I just found the format.signoff setting, so I should be
> covered for future patches.
>
> I'll wait some time before sending a fix for this :)
Yeah, I was going to suggest format.signOff as well. If you don't want
to add your S-o-b to work commits but do want to add it to patches you
send to the Git list, you could just stop there (assuming that you don't
use format-patch for work-related patches).
Alternatively, you could set it in your copy of git.git's own
configuration in $GIT_DIR/config, so that it doesn't apply to work you
do outside of that repository.
Either way, I'm going to avoid queueing this round since it is missing
the required Signed-off-by line.
Thanks,
Taylor
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v11 1/8] t/t5505-remote: set default branch to main
2024-10-23 15:09 ` Taylor Blau
@ 2024-10-23 15:34 ` Bence Ferdinandy
2024-10-23 18:45 ` Taylor Blau
0 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-23 15:34 UTC (permalink / raw)
To: Taylor Blau
Cc: Kristoffer Haugsbakk, git, Phillip Wood, René Scharfe,
Johannes Schindelin, Junio C Hamano, Karthik Nayak
On Wed Oct 23, 2024 at 17:09, Taylor Blau <me@ttaylorr.com> wrote:
> On Tue, Oct 22, 2024 at 10:14:12PM +0200, Bence Ferdinandy wrote:
>>
>> On Tue Oct 22, 2024 at 21:53, Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com> wrote:
>> > On Tue, Oct 22, 2024, at 21:45, Bence Ferdinandy wrote:
>> >> Consider the bare repository called "mirror" in the test. Running `git
>> >> remote add --mirror -f origin ../one` will not change HEAD, consequently
>> >> if init.defaultBranch is not the same as what HEAD in the remote
>> >> ("one"), HEAD in "mirror" will be pointing to a non-existent reference.
>> >> Hence if "mirror" is used as a remote by yet another repository,
>> >> ls-remote will not show HEAD. On the other hand, if init.defaultBranch
>> >> happens to match HEAD in "one", then ls-remote will show HEAD.
>> >>
>> >> Since the CI globally exports GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main,
>> >> there's a drift between how the test repositories are set up in the CI
>> >> and during local testing. This issue does not manifest currently, as the
>> >> test does not do any remote HEAD manipulation where this would come up,
>> >> but should such things be added, a locally passing test would break the
>> >> CI vice-versa.
>> >>
>> >> Set GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main in the test to be
>> >> consistent with the CI.
>> >> ---
>> >>
>> >> Notes:
>> >> v9: - new patch
>> >> - a bandaid for the CI issue noticed by Taylor (cf:
>> >> https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/),
>> >> but
>> >> see
>> >>
>> >> https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/
>> >> for the root cause in detail
>> >
>> > Why no signoff?
>>
>> Because I always forget :( I've been thinking on making it by default, but
>> I didn't want to add it to all the work related commits, where I'd be the only
>> one doing it, but I just found the format.signoff setting, so I should be
>> covered for future patches.
>>
>> I'll wait some time before sending a fix for this :)
>
> Yeah, I was going to suggest format.signOff as well. If you don't want
> to add your S-o-b to work commits but do want to add it to patches you
> send to the Git list, you could just stop there (assuming that you don't
> use format-patch for work-related patches).
The projects I contribute to seem to have a bidirectional mapping of requiring
S-o-b and format patch so I should be good with this :)
>
> Alternatively, you could set it in your copy of git.git's own
> configuration in $GIT_DIR/config, so that it doesn't apply to work you
> do outside of that repository.
>
> Either way, I'm going to avoid queueing this round since it is missing
> the required Signed-off-by line.
In that case I'll send a v12 with the S-o-b so the series can get back into
circulation.
Best,
Bence
>
> Thanks,
> Taylor
--
bence.ferdinandy.com
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v11 1/8] t/t5505-remote: set default branch to main
2024-10-23 15:34 ` Bence Ferdinandy
@ 2024-10-23 18:45 ` Taylor Blau
0 siblings, 0 replies; 258+ messages in thread
From: Taylor Blau @ 2024-10-23 18:45 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: Kristoffer Haugsbakk, git, Phillip Wood, René Scharfe,
Johannes Schindelin, Junio C Hamano, Karthik Nayak
On Wed, Oct 23, 2024 at 05:34:23PM +0200, Bence Ferdinandy wrote:
> > Alternatively, you could set it in your copy of git.git's own
> > configuration in $GIT_DIR/config, so that it doesn't apply to work you
> > do outside of that repository.
> >
> > Either way, I'm going to avoid queueing this round since it is missing
> > the required Signed-off-by line.
>
> In that case I'll send a v12 with the S-o-b so the series can get back into
> circulation.
Thanks, will queue.
Thanks,
Taylor
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v11 2/8] refs: atomically record overwritten ref in update_symref
2024-10-22 19:45 ` [PATCH v11 0/8] " Bence Ferdinandy
2024-10-22 19:45 ` [PATCH v11 1/8] t/t5505-remote: set default branch to main Bence Ferdinandy
@ 2024-10-22 19:45 ` Bence Ferdinandy
2024-10-22 19:45 ` [PATCH v11 3/8] remote set-head: refactor for readability Bence Ferdinandy
` (6 subsequent siblings)
8 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-22 19:45 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy
When updating a symref with update_symref it's currently not possible to
know for sure what was the previous value that was overwritten. Extend
refs_update_symref under a new function name, to record the value after
the ref has been locked if the caller of refs_update_symref_extended
requests it via a new variable in the function call. Keep the original
refs_update_symref function with the same signature, but now as
a wrapper around refs_update_symref_extended.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: - added before_target to reftables backend
- added an extra safety check for transaction's existence in refs.c
v6: - no change
v7: - remove the whole before_target concept from the backends and
handle checking it in refs.c instead (thanks Karthik)
- rename the before_target to referent which is how the same concept
is called in the backends
- change commit prefix to be more in line with project standards
v8: no change
v9: - instead of adding parameters to refs_update_symref, rename what
was in v8 as refs_update_symref_extended and make refs_update_symref
a wrapper for that. This significantly reduces the number of files
that need to be touched, and avoids adding a lot of dummy NULL-s
in unrelated places.
v10: no change
v11: no change
refs.c | 19 ++++++++++++++++---
refs.h | 4 ++++
2 files changed, 20 insertions(+), 3 deletions(-)
diff --git a/refs.c b/refs.c
index 5f729ed412..24a4172cd2 100644
--- a/refs.c
+++ b/refs.c
@@ -2115,6 +2115,13 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
int refs_update_symref(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg)
+{
+ return refs_update_symref_extended(refs, ref, target, logmsg, NULL);
+}
+
+int refs_update_symref_extended(struct ref_store *refs, const char *ref,
+ const char *target, const char *logmsg,
+ struct strbuf *referent)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
@@ -2122,13 +2129,20 @@ int refs_update_symref(struct ref_store *refs, const char *ref,
transaction = ref_store_transaction_begin(refs, &err);
if (!transaction ||
- ref_transaction_update(transaction, ref, NULL, NULL,
+ ref_transaction_update(transaction, ref, NULL, NULL,
target, NULL, REF_NO_DEREF,
logmsg, &err) ||
- ref_transaction_commit(transaction, &err)) {
+ ref_transaction_prepare(transaction, &err)) {
ret = error("%s", err.buf);
+ goto cleanup;
}
+ if (referent)
+ refs_read_symbolic_ref(refs, ref, referent);
+ if (ref_transaction_commit(transaction, &err))
+ ret = error("%s", err.buf);
+
+cleanup:
strbuf_release(&err);
if (transaction)
ref_transaction_free(transaction);
@@ -2948,4 +2962,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)
return (update->flags & REF_HAVE_OLD) &&
(!is_null_oid(&update->old_oid) || update->old_target);
}
-
diff --git a/refs.h b/refs.h
index 108dfc93b3..259191a485 100644
--- a/refs.h
+++ b/refs.h
@@ -573,6 +573,10 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
int refs_update_symref(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg);
+int refs_update_symref_extended(struct ref_store *refs, const char *refname,
+ const char *target, const char *logmsg,
+ struct strbuf *referent);
+
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
UPDATE_REFS_DIE_ON_ERR,
--
2.47.0.94.gb64850d498
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v11 3/8] remote set-head: refactor for readability
2024-10-22 19:45 ` [PATCH v11 0/8] " Bence Ferdinandy
2024-10-22 19:45 ` [PATCH v11 1/8] t/t5505-remote: set default branch to main Bence Ferdinandy
2024-10-22 19:45 ` [PATCH v11 2/8] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
@ 2024-10-22 19:45 ` Bence Ferdinandy
2024-10-22 19:45 ` [PATCH v11 4/8] remote set-head: better output for --auto Bence Ferdinandy
` (5 subsequent siblings)
8 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-22 19:45 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy
Rename buf and buf2 to something more explanatory.
Instead of calling get_main_ref_store(the_repository) multiple times,
call it once and store in a new refs variable. Although this change
probably offers some performance benefits, the main purpose is to
shorten the line lengths of function calls using this variable.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v5: new patch (split from the next patch as a preparatory step)
v6: no change
v7: - change commit prefix to be more in line with project standards
v8: no change
v9: - further improve readability by renaming buf, and buf2 consistently
with how patch 6 was already done
v10: no change
v11: no change
builtin/remote.c | 23 ++++++++++++-----------
1 file changed, 12 insertions(+), 11 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 76670ddd8b..1d68c5b2ba 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1402,8 +1402,9 @@ static int show(int argc, const char **argv, const char *prefix)
static int set_head(int argc, const char **argv, const char *prefix)
{
int i, opt_a = 0, opt_d = 0, result = 0;
- struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
char *head_name = NULL;
+ struct ref_store *refs = get_main_ref_store(the_repository);
struct option options[] = {
OPT_BOOL('a', "auto", &opt_a,
@@ -1415,7 +1416,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options,
builtin_remote_sethead_usage, 0);
if (argc)
- strbuf_addf(&buf, "refs/remotes/%s/HEAD", argv[0]);
+ strbuf_addf(&b_head, "refs/remotes/%s/HEAD", argv[0]);
if (!opt_a && !opt_d && argc == 2) {
head_name = xstrdup(argv[1]);
@@ -1434,25 +1435,25 @@ static int set_head(int argc, const char **argv, const char *prefix)
head_name = xstrdup(states.heads.items[0].string);
free_remote_ref_states(&states);
} else if (opt_d && !opt_a && argc == 1) {
- if (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF))
- result |= error(_("Could not delete %s"), buf.buf);
+ if (refs_delete_ref(refs, NULL, b_head.buf, NULL, REF_NO_DEREF))
+ result |= error(_("Could not delete %s"), b_head.buf);
} else
usage_with_options(builtin_remote_sethead_usage, options);
if (head_name) {
- strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name);
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", argv[0], head_name);
/* make sure it's valid */
- if (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))
- result |= error(_("Not a valid ref: %s"), buf2.buf);
- else if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote set-head"))
- result |= error(_("Could not setup %s"), buf.buf);
+ if (!refs_ref_exists(refs, b_remote_head.buf))
+ result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
+ else if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, "remote set-head"))
+ result |= error(_("Could not setup %s"), b_head.buf);
else if (opt_a)
printf("%s/HEAD set to %s\n", argv[0], head_name);
free(head_name);
}
- strbuf_release(&buf);
- strbuf_release(&buf2);
+ strbuf_release(&b_head);
+ strbuf_release(&b_remote_head);
return result;
}
--
2.47.0.94.gb64850d498
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v11 4/8] remote set-head: better output for --auto
2024-10-22 19:45 ` [PATCH v11 0/8] " Bence Ferdinandy
` (2 preceding siblings ...)
2024-10-22 19:45 ` [PATCH v11 3/8] remote set-head: refactor for readability Bence Ferdinandy
@ 2024-10-22 19:45 ` Bence Ferdinandy
2024-10-22 19:45 ` [PATCH v11 5/8] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
` (4 subsequent siblings)
8 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-22 19:45 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy
Currently, set-head --auto will print a message saying "remote/HEAD set
to branch", which implies something was changed.
Change the output of --auto, so the output actually reflects what was
done: a) set a previously unset HEAD, b) change HEAD because remote
changed or c) no updates. As a fourth output, if HEAD is changed from
a previous value that was not a remote branch, explicitly call attention
to this fact.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v1-v2:
was RFC in https://lore.kernel.org/git/20240910203835.2288291-1-bence@ferdinandy.com/
v3:
This patch was originally sent along when I thought set-head was
going to be invoked by fetch, but the discussion on the RFC
concluded that it should be not. This opened the possibility to make
it more explicit.
Note: although I feel both things the patch does are really just
cosmetic, an argument could be made for breaking it into two, one
for the no-op part and one for the --auto print update.
Was sent in:
https://lore.kernel.org/git/20240915221055.904107-1-bence@ferdinandy.com/
v4:
- changes are now handled atomically via the ref update transaction
- outputs have changed along the lines of Junio's suggestion
- minor refactor to set_head for improved legibility
v5: - the minor refactor has been split out into its own patch
v6: - fixed uninitialized prev_head
- fixed case of unusual previous target
- fixed a test that would have been actually broken at this patch
(the output was only correct with the later update to fetch)
- added 4 tests for the 4 output cases
v7: - change commit prefix to be more in line with project standards
- fixed tests to also work with the reftable backend
- renamed report function, fixed style issue with checking buf len
- fixed not releasing an strbuf
v8: no change
v9: - mark output strings in report_set_head_auto as translatable
- rename buf_prev to b_local_head for consistency
- use ${SQ} in tests instead of '\''
v10: no change
v11: no change
builtin/remote.c | 33 +++++++++++++++++++++++++++---
t/t5505-remote.sh | 51 ++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 80 insertions(+), 4 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 1d68c5b2ba..108f1271d3 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1399,10 +1399,35 @@ static int show(int argc, const char **argv, const char *prefix)
return result;
}
+static void report_set_head_auto(const char *remote, const char *head_name,
+ struct strbuf *b_local_head) {
+ struct strbuf buf_prefix = STRBUF_INIT;
+ const char *prev_head = NULL;
+
+ strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
+ skip_prefix(b_local_head->buf, buf_prefix.buf, &prev_head);
+
+ if (prev_head && !strcmp(prev_head, head_name))
+ printf(_("'%s/HEAD' is unchanged and points to '%s'\n"),
+ remote, head_name);
+ else if (prev_head)
+ printf(_("'%s/HEAD' has changed from '%s' and now points to '%s'\n"),
+ remote, prev_head, head_name);
+ else if (!b_local_head->len)
+ printf(_("'%s/HEAD' is now created and points to '%s'\n"),
+ remote, head_name);
+ else
+ printf(_("'%s/HEAD' used to point to '%s' "
+ "(which is not a remote branch), but now points to '%s'\n"),
+ remote, b_local_head->buf, head_name);
+ strbuf_release(&buf_prefix);
+}
+
static int set_head(int argc, const char **argv, const char *prefix)
{
int i, opt_a = 0, opt_d = 0, result = 0;
- struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
+ b_local_head = STRBUF_INIT;
char *head_name = NULL;
struct ref_store *refs = get_main_ref_store(the_repository);
@@ -1445,15 +1470,17 @@ static int set_head(int argc, const char **argv, const char *prefix)
/* make sure it's valid */
if (!refs_ref_exists(refs, b_remote_head.buf))
result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
- else if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, "remote set-head"))
+ else if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
+ "remote set-head", &b_local_head))
result |= error(_("Could not setup %s"), b_head.buf);
else if (opt_a)
- printf("%s/HEAD set to %s\n", argv[0], head_name);
+ report_set_head_auto(argv[0], head_name, &b_local_head);
free(head_name);
}
strbuf_release(&b_head);
strbuf_release(&b_remote_head);
+ strbuf_release(&b_local_head);
return result;
}
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 9b50276646..0ea86d51a4 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -432,12 +432,51 @@ test_expect_success 'set-head --auto' '
)
'
+test_expect_success 'set-head --auto detects creation' '
+ (
+ cd test &&
+ git symbolic-ref -d refs/remotes/origin/HEAD &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects no change' '
+ (
+ cd test &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects change' '
+ (
+ cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/ahead &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} has changed from ${SQ}ahead${SQ} and now points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects strange ref' '
+ (
+ cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD refs/heads/main &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} used to point to ${SQ}refs/heads/main${SQ} (which is not a remote branch), but now points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
(
cd test &&
git fetch two "refs/heads/*:refs/remotes/two/*" &&
git remote set-head --auto two >output 2>&1 &&
- echo "two/HEAD set to main" >expect &&
+ echo "${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
test_cmp expect output
)
'
@@ -456,6 +495,16 @@ test_expect_success 'set-head explicit' '
)
'
+test_expect_success 'set-head --auto reports change' '
+ (
+ cd test &&
+ git remote set-head origin side2 &&
+ git remote set-head --auto origin >output 2>&1 &&
+ echo "${SQ}origin/HEAD${SQ} has changed from ${SQ}side2${SQ} and now points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
cat >test/expect <<EOF
Pruning origin
URL: $(pwd)/one
--
2.47.0.94.gb64850d498
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v11 5/8] refs: add TRANSACTION_CREATE_EXISTS error
2024-10-22 19:45 ` [PATCH v11 0/8] " Bence Ferdinandy
` (3 preceding siblings ...)
2024-10-22 19:45 ` [PATCH v11 4/8] remote set-head: better output for --auto Bence Ferdinandy
@ 2024-10-22 19:45 ` Bence Ferdinandy
2024-10-22 19:45 ` [PATCH v11 6/8] refs: add create_only option to refs_update_symref_extended Bence Ferdinandy
` (3 subsequent siblings)
8 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-22 19:45 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy
Currently there is only one special error for transaction, for when
there is a naming conflict, all other errors are dumped under a generic
error. Add a new special error case for when the caller requests the
reference to be updated only when it does not yet exist and the
reference actually does exist.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: no change
v6: no change
v7: - change commit prefix to be more in line with project standards
- changed error checking to Karthik's suggestion
v8: no change
v9: - no change
v10: no change
v11: no change
refs.h | 4 +++-
refs/files-backend.c | 24 ++++++++++++++++--------
refs/reftable-backend.c | 6 ++++--
3 files changed, 23 insertions(+), 11 deletions(-)
diff --git a/refs.h b/refs.h
index 259191a485..a5bc25442b 100644
--- a/refs.h
+++ b/refs.h
@@ -762,8 +762,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,
/* Naming conflict (for example, the ref names A and A/B conflict). */
#define TRANSACTION_NAME_CONFLICT -1
+/* When only creation was requested, but the ref already exists. */
+#define TRANSACTION_CREATE_EXISTS -2
/* All other errors. */
-#define TRANSACTION_GENERIC_ERROR -2
+#define TRANSACTION_GENERIC_ERROR -3
/*
* Perform the preparatory stages of committing `transaction`. Acquire
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 0824c0b8a9..e743ec44b5 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2502,14 +2502,18 @@ static int split_symref_update(struct ref_update *update,
static int check_old_oid(struct ref_update *update, struct object_id *oid,
struct strbuf *err)
{
+ int ret = TRANSACTION_GENERIC_ERROR;
+
if (!(update->flags & REF_HAVE_OLD) ||
oideq(oid, &update->old_oid))
return 0;
- if (is_null_oid(&update->old_oid))
+ if (is_null_oid(&update->old_oid)) {
strbuf_addf(err, "cannot lock ref '%s': "
"reference already exists",
ref_update_original_update_refname(update));
+ ret = TRANSACTION_CREATE_EXISTS;
+ }
else if (is_null_oid(oid))
strbuf_addf(err, "cannot lock ref '%s': "
"reference is missing but expected %s",
@@ -2522,7 +2526,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,
oid_to_hex(oid),
oid_to_hex(&update->old_oid));
- return -1;
+ return ret;
}
/*
@@ -2602,9 +2606,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
- } else if (check_old_oid(update, &lock->old_oid, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto out;
+ } else {
+ ret = check_old_oid(update, &lock->old_oid, err);
+ if (ret) {
+ goto out;
+ }
}
} else {
/*
@@ -2635,9 +2641,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,
update->old_target);
ret = TRANSACTION_GENERIC_ERROR;
goto out;
- } else if (check_old_oid(update, &lock->old_oid, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto out;
+ } else {
+ ret = check_old_oid(update, &lock->old_oid, err);
+ if (ret) {
+ goto out;
+ }
}
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 3c6107c7ce..b3b5ce77dd 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1206,10 +1206,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
goto done;
}
} else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) {
- if (is_null_oid(&u->old_oid))
+ ret = TRANSACTION_NAME_CONFLICT;
+ if (is_null_oid(&u->old_oid)) {
strbuf_addf(err, _("cannot lock ref '%s': "
"reference already exists"),
ref_update_original_update_refname(u));
+ ret = TRANSACTION_CREATE_EXISTS;
+ }
else if (is_null_oid(¤t_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
"reference is missing but expected %s"),
@@ -1221,7 +1224,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
ref_update_original_update_refname(u),
oid_to_hex(¤t_oid),
oid_to_hex(&u->old_oid));
- ret = -1;
goto done;
}
--
2.47.0.94.gb64850d498
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v11 6/8] refs: add create_only option to refs_update_symref_extended
2024-10-22 19:45 ` [PATCH v11 0/8] " Bence Ferdinandy
` (4 preceding siblings ...)
2024-10-22 19:45 ` [PATCH v11 5/8] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
@ 2024-10-22 19:45 ` Bence Ferdinandy
2024-10-22 19:45 ` [PATCH v11 7/8] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
` (2 subsequent siblings)
8 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-22 19:45 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy
Allow the caller to specify that it only wants to update the symref if
it does not already exist. Silently ignore the error from the
transaction API if the symref already exists.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: no change
v6: - switched from bool to int for create_only
- refactored logic in refs_update_symref with goto as layed out by
Junio
v7: - change commit prefix to be more in line with project standards
- refactored code to accommodate changes in the first patch, but
otherwise no change
v8: no change
v9: - no change (except for following through the
refs_update_symref_extended refactoring)
v10: no change
v11: no change
builtin/remote.c | 2 +-
refs.c | 32 +++++++++++++++++++++++---------
refs.h | 2 +-
3 files changed, 25 insertions(+), 11 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 108f1271d3..b1eba75a2b 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1471,7 +1471,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
if (!refs_ref_exists(refs, b_remote_head.buf))
result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
else if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
- "remote set-head", &b_local_head))
+ "remote set-head", &b_local_head, 0))
result |= error(_("Could not setup %s"), b_head.buf);
else if (opt_a)
report_set_head_auto(argv[0], head_name, &b_local_head);
diff --git a/refs.c b/refs.c
index 24a4172cd2..093ee11ab0 100644
--- a/refs.c
+++ b/refs.c
@@ -2116,31 +2116,45 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
int refs_update_symref(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg)
{
- return refs_update_symref_extended(refs, ref, target, logmsg, NULL);
+ return refs_update_symref_extended(refs, ref, target, logmsg, NULL, 0);
}
int refs_update_symref_extended(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg,
- struct strbuf *referent)
+ struct strbuf *referent, int create_only)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
- int ret = 0;
+ int ret = 0, prepret = 0;
transaction = ref_store_transaction_begin(refs, &err);
- if (!transaction ||
- ref_transaction_update(transaction, ref, NULL, NULL,
- target, NULL, REF_NO_DEREF,
- logmsg, &err) ||
- ref_transaction_prepare(transaction, &err)) {
+ if (!transaction) {
+ error_return:
ret = error("%s", err.buf);
goto cleanup;
}
+ if (create_only) {
+ if (ref_transaction_create(transaction, ref, NULL, target,
+ REF_NO_DEREF, logmsg, &err))
+ goto error_return;
+ prepret = ref_transaction_prepare(transaction, &err);
+ if (prepret && prepret != TRANSACTION_CREATE_EXISTS)
+ goto error_return;
+ } else {
+ if (ref_transaction_update(transaction, ref, NULL, NULL,
+ target, NULL, REF_NO_DEREF,
+ logmsg, &err) ||
+ ref_transaction_prepare(transaction, &err))
+ goto error_return;
+ }
+
if (referent)
refs_read_symbolic_ref(refs, ref, referent);
+ if (prepret == TRANSACTION_CREATE_EXISTS)
+ goto cleanup;
if (ref_transaction_commit(transaction, &err))
- ret = error("%s", err.buf);
+ goto error_return;
cleanup:
strbuf_release(&err);
diff --git a/refs.h b/refs.h
index a5bc25442b..458582ebcf 100644
--- a/refs.h
+++ b/refs.h
@@ -575,7 +575,7 @@ int refs_update_symref(struct ref_store *refs, const char *refname,
int refs_update_symref_extended(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg,
- struct strbuf *referent);
+ struct strbuf *referent, int create_only);
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
--
2.47.0.94.gb64850d498
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v11 7/8] fetch: set remote/HEAD if it does not exist
2024-10-22 19:45 ` [PATCH v11 0/8] " Bence Ferdinandy
` (5 preceding siblings ...)
2024-10-22 19:45 ` [PATCH v11 6/8] refs: add create_only option to refs_update_symref_extended Bence Ferdinandy
@ 2024-10-22 19:45 ` Bence Ferdinandy
2024-10-22 19:45 ` [PATCH v11 8/8] fetch set_head: handle mirrored bare repositories Bence Ferdinandy
2024-10-23 15:36 ` [PATCH v12 0/8] set-head/fetch remote/HEAD updates Bence Ferdinandy
8 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-22 19:45 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy
If the user has remote/HEAD set already and it looks like it has changed
on the server, then print a message, otherwise set it if we can.
Silently pass if the user already has the same remote/HEAD set as
reported by the server or if we encounter any errors along the way.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v3: - does not rely on remote set-head anymore so it only authenticates
once
- uses the new REF_CREATE_ONLY to atomically check if the ref exists
and only write it if it doesn't
- in all other cases the maximum it does is print a warning
v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,
but updated transaction api to request a silent create only
- it now uses the atomic before_target to determine reporting
- refactored for legibility
v5: - instead of printing a not too useful message, it now fails
silently, this in line with the objective to only set up
remote/HEAD automatically if the right thing is trivial, for
everything else there is remote set-head
- fixed all failing tests
- added two new tests, one for checking if remote/HEAD is set to the
correct one, and one to test that we do not override remote/HEAD
if it has changed on the server from what we have locally
v6: - fixed style issues and unintended extra empty line
- updated function call with bool to int from previous patch's
change
- removed calls to error(...) inherited from builtin/remote.c so we
actually fail silently
- set the test for remote set-head --auto to the correct value here,
which was previously erronously set in the remote set-head patch
v7: - no change
v8: - changed logmsg in call to refs_update_symref from "remote
set-head" to "fetch"
v9: - follow through with refs_update_symref_extended
- fix test errors uncovered by the new patch
v10: no change
v11: fixed some memory leaks
builtin/fetch.c | 86 ++++++++++++
t/t4207-log-decoration-colors.sh | 3 +-
t/t5505-remote.sh | 21 ++-
t/t5510-fetch.sh | 229 ++++++++++++++++---------------
t/t5512-ls-remote.sh | 2 +
t/t5514-fetch-multiple.sh | 17 ++-
t/t5516-fetch-push.sh | 3 +-
t/t5527-fetch-odd-refs.sh | 3 +-
t/t7900-maintenance.sh | 3 +-
t/t9210-scalar.sh | 5 +-
t/t9211-scalar-clone.sh | 6 +-
t/t9902-completion.sh | 65 +++++++++
12 files changed, 319 insertions(+), 124 deletions(-)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index d9027e4dc9..cadfd2407a 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1578,6 +1578,84 @@ static int backfill_tags(struct display_state *display_state,
return retcode;
}
+static void report_set_head(const char *remote, const char *head_name,
+ struct strbuf *buf_prev) {
+ struct strbuf buf_prefix = STRBUF_INIT;
+ const char *prev_head = NULL;
+
+ strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
+ skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);
+
+ if (prev_head && strcmp(prev_head, head_name)) {
+ printf("'HEAD' at '%s' has changed from '%s' to '%s'\n",
+ remote, prev_head, head_name);
+ printf("Run 'git remote set-head %s %s' to follow the change.\n",
+ remote, head_name);
+ }
+ strbuf_release(&buf_prefix);
+}
+
+static const char *strip_refshead(const char *name){
+ skip_prefix(name, "refs/heads/", &name);
+ return name;
+}
+
+static int set_head(const struct ref *remote_refs)
+{
+ int result = 0;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
+ b_local_head = STRBUF_INIT;
+ const char *remote = gtransport->remote->name;
+ char *head_name = NULL;
+ struct ref *ref, *matches;
+ struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
+ struct refspec_item refspec = {
+ .force = 0,
+ .pattern = 1,
+ .src = (char *) "refs/heads/*",
+ .dst = (char *) "refs/heads/*",
+ };
+ struct string_list heads = STRING_LIST_INIT_DUP;
+ struct ref_store *refs = get_main_ref_store(the_repository);
+
+ get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
+ matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
+ fetch_map, 1);
+ for (ref = matches; ref; ref = ref->next) {
+ string_list_append(&heads, strip_refshead(ref->name));
+ }
+
+
+ if (!heads.nr)
+ result = 1;
+ else if (heads.nr > 1)
+ result = 1;
+ else
+ head_name = xstrdup(heads.items[0].string);
+ if (head_name) {
+ strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name);
+ /* make sure it's valid */
+ if (!refs_ref_exists(refs, b_remote_head.buf))
+ result = 1;
+ else if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
+ "fetch", &b_local_head, 1))
+ result = 1;
+ else
+ report_set_head(remote, head_name, &b_local_head);
+
+ free(head_name);
+ }
+
+ free_refs(fetch_map);
+ free_refs(matches);
+ string_list_clear(&heads, 0);
+ strbuf_release(&b_head);
+ strbuf_release(&b_local_head);
+ strbuf_release(&b_remote_head);
+ return result;
+}
+
static int do_fetch(struct transport *transport,
struct refspec *rs,
const struct fetch_config *config)
@@ -1647,6 +1725,8 @@ static int do_fetch(struct transport *transport,
"refs/tags/");
}
+ strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
+
if (must_list_refs) {
trace2_region_enter("fetch", "remote_refs", the_repository);
remote_refs = transport_get_remote_refs(transport,
@@ -1791,6 +1871,12 @@ static int do_fetch(struct transport *transport,
"you need to specify exactly one branch with the --set-upstream option"));
}
}
+ if (set_head(remote_refs))
+ ;
+ /*
+ * Way too many cases where this can go wrong
+ * so let's just fail silently for now.
+ */
cleanup:
if (retcode) {
diff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh
index 73ea9e5155..d55d22cb2f 100755
--- a/t/t4207-log-decoration-colors.sh
+++ b/t/t4207-log-decoration-colors.sh
@@ -59,7 +59,8 @@ ${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \
${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \
-${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1
+${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit}, \
+${c_reset}${c_remoteBranch}other/HEAD${c_reset}${c_commit})${c_reset} A1
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
${c_stash}refs/stash${c_reset}${c_commit})${c_reset} On main: Changes to A.t
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 0ea86d51a4..4990d00209 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -74,7 +74,7 @@ test_expect_success 'add another remote' '
cd test &&
git remote add -f second ../two &&
tokens_match "origin second" "$(git remote)" &&
- check_tracking_branch second main side another &&
+ check_tracking_branch second main side another HEAD &&
git for-each-ref "--format=%(refname)" refs/remotes |
sed -e "/^refs\/remotes\/origin\//d" \
-e "/^refs\/remotes\/second\//d" >actual &&
@@ -476,7 +476,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
cd test &&
git fetch two "refs/heads/*:refs/remotes/two/*" &&
git remote set-head --auto two >output 2>&1 &&
- echo "${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
+ echo "${SQ}two/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}" >expect &&
test_cmp expect output
)
'
@@ -764,8 +764,10 @@ test_expect_success 'reject --no-no-tags' '
'
cat >one/expect <<\EOF
+ apis/HEAD -> apis/main
apis/main
apis/side
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -783,11 +785,14 @@ test_expect_success 'update' '
'
cat >one/expect <<\EOF
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
+ manduca/HEAD -> manduca/main
manduca/main
manduca/side
+ megaloprepus/HEAD -> megaloprepus/main
megaloprepus/main
megaloprepus/side
EOF
@@ -795,7 +800,7 @@ EOF
test_expect_success 'update with arguments' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -827,10 +832,13 @@ test_expect_success 'update --prune' '
'
cat >one/expect <<-\EOF
+ apis/HEAD -> apis/main
apis/main
apis/side
+ manduca/HEAD -> manduca/main
manduca/main
manduca/side
+ megaloprepus/HEAD -> megaloprepus/main
megaloprepus/main
megaloprepus/side
EOF
@@ -838,7 +846,7 @@ EOF
test_expect_success 'update default' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -850,6 +858,7 @@ test_expect_success 'update default' '
'
cat >one/expect <<\EOF
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -858,7 +867,7 @@ EOF
test_expect_success 'update default (overridden, with funny whitespace)' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -872,7 +881,7 @@ test_expect_success 'update default (overridden, with funny whitespace)' '
test_expect_success 'update (with remotes.default defined)' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 0890b9f61c..dfc8d748ba 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -75,6 +75,30 @@ test_expect_success "fetch test for-merge" '
cut -f -2 .git/FETCH_HEAD >actual &&
test_cmp expected actual'
+test_expect_success "fetch test remote HEAD" '
+ cd "$D" &&
+ cd two &&
+ git fetch &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"'
+
+test_expect_success "fetch test remote HEAD change" '
+ cd "$D" &&
+ cd two &&
+ git switch -c other &&
+ git push -u origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git fetch &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"'
+
test_expect_success 'fetch --prune on its own works as expected' '
cd "$D" &&
git clone . prune &&
@@ -478,7 +502,6 @@ test_expect_success 'unbundle 1' '
test_must_fail git fetch "$D/bundle1" main:main
'
-
test_expect_success 'bundle 1 has only 3 files ' '
cd "$D" &&
test_bundle_object_count bundle1 3
@@ -819,7 +842,7 @@ test_expect_success 'fetch from multiple configured URLs in single remote' '
# configured prune tests
-set_config_tristate () {
+set_config_tristate() {
# var=$1 val=$2
case "$2" in
unset)
@@ -833,12 +856,12 @@ set_config_tristate () {
esac
}
-test_configured_prune () {
+test_configured_prune() {
test_configured_prune_type "$@" "name"
test_configured_prune_type "$@" "link"
}
-test_configured_prune_type () {
+test_configured_prune_type() {
fetch_prune=$1
remote_origin_prune=$2
fetch_prune_tags=$3
@@ -848,8 +871,7 @@ test_configured_prune_type () {
cmdline=$7
mode=$8
- if test -z "$cmdline_setup"
- then
+ if test -z "$cmdline_setup"; then
test_expect_success 'setup cmdline_setup variable for subsequent test' '
remote_url="file://$(git -C one config remote.origin.url)" &&
remote_fetch="$(git -C one config remote.origin.fetch)" &&
@@ -857,12 +879,10 @@ test_configured_prune_type () {
'
fi
- if test "$mode" = 'link'
- then
+ if test "$mode" = 'link'; then
new_cmdline=""
- if test "$cmdline" = ""
- then
+ if test "$cmdline" = ""; then
new_cmdline=$cmdline_setup
else
new_cmdline=$(perl -e '
@@ -873,10 +893,8 @@ test_configured_prune_type () {
fi
if test "$fetch_prune_tags" = 'true' ||
- test "$remote_origin_prune_tags" = 'true'
- then
- if ! printf '%s' "$cmdline\n" | grep -q refs/remotes/origin/
- then
+ test "$remote_origin_prune_tags" = 'true'; then
+ if ! printf '%s' "$cmdline\n" | grep -q refs/remotes/origin/; then
new_cmdline="$new_cmdline refs/tags/*:refs/tags/*"
fi
fi
@@ -946,100 +964,100 @@ test_configured_prune_type () {
# $7 git-fetch $cmdline:
#
# $1 $2 $3 $4 $5 $6 $7
-test_configured_prune unset unset unset unset kept kept ""
-test_configured_prune unset unset unset unset kept kept "--no-prune"
-test_configured_prune unset unset unset unset pruned kept "--prune"
-test_configured_prune unset unset unset unset kept pruned \
+test_configured_prune unset unset unset unset kept kept ""
+test_configured_prune unset unset unset unset kept kept "--no-prune"
+test_configured_prune unset unset unset unset pruned kept "--prune"
+test_configured_prune unset unset unset unset kept pruned \
"--prune origin refs/tags/*:refs/tags/*"
test_configured_prune unset unset unset unset pruned pruned \
"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
-test_configured_prune false unset unset unset kept kept ""
-test_configured_prune false unset unset unset kept kept "--no-prune"
-test_configured_prune false unset unset unset pruned kept "--prune"
+test_configured_prune false unset unset unset kept kept ""
+test_configured_prune false unset unset unset kept kept "--no-prune"
+test_configured_prune false unset unset unset pruned kept "--prune"
-test_configured_prune true unset unset unset pruned kept ""
-test_configured_prune true unset unset unset pruned kept "--prune"
-test_configured_prune true unset unset unset kept kept "--no-prune"
+test_configured_prune true unset unset unset pruned kept ""
+test_configured_prune true unset unset unset pruned kept "--prune"
+test_configured_prune true unset unset unset kept kept "--no-prune"
-test_configured_prune unset false unset unset kept kept ""
-test_configured_prune unset false unset unset kept kept "--no-prune"
-test_configured_prune unset false unset unset pruned kept "--prune"
+test_configured_prune unset false unset unset kept kept ""
+test_configured_prune unset false unset unset kept kept "--no-prune"
+test_configured_prune unset false unset unset pruned kept "--prune"
-test_configured_prune false false unset unset kept kept ""
-test_configured_prune false false unset unset kept kept "--no-prune"
-test_configured_prune false false unset unset pruned kept "--prune"
-test_configured_prune false false unset unset kept pruned \
+test_configured_prune false false unset unset kept kept ""
+test_configured_prune false false unset unset kept kept "--no-prune"
+test_configured_prune false false unset unset pruned kept "--prune"
+test_configured_prune false false unset unset kept pruned \
"--prune origin refs/tags/*:refs/tags/*"
test_configured_prune false false unset unset pruned pruned \
"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
-test_configured_prune true false unset unset kept kept ""
-test_configured_prune true false unset unset pruned kept "--prune"
-test_configured_prune true false unset unset kept kept "--no-prune"
+test_configured_prune true false unset unset kept kept ""
+test_configured_prune true false unset unset pruned kept "--prune"
+test_configured_prune true false unset unset kept kept "--no-prune"
-test_configured_prune unset true unset unset pruned kept ""
-test_configured_prune unset true unset unset kept kept "--no-prune"
-test_configured_prune unset true unset unset pruned kept "--prune"
+test_configured_prune unset true unset unset pruned kept ""
+test_configured_prune unset true unset unset kept kept "--no-prune"
+test_configured_prune unset true unset unset pruned kept "--prune"
-test_configured_prune false true unset unset pruned kept ""
-test_configured_prune false true unset unset kept kept "--no-prune"
-test_configured_prune false true unset unset pruned kept "--prune"
+test_configured_prune false true unset unset pruned kept ""
+test_configured_prune false true unset unset kept kept "--no-prune"
+test_configured_prune false true unset unset pruned kept "--prune"
-test_configured_prune true true unset unset pruned kept ""
-test_configured_prune true true unset unset pruned kept "--prune"
-test_configured_prune true true unset unset kept kept "--no-prune"
-test_configured_prune true true unset unset kept pruned \
+test_configured_prune true true unset unset pruned kept ""
+test_configured_prune true true unset unset pruned kept "--prune"
+test_configured_prune true true unset unset kept kept "--no-prune"
+test_configured_prune true true unset unset kept pruned \
"--prune origin refs/tags/*:refs/tags/*"
-test_configured_prune true true unset unset pruned pruned \
+test_configured_prune true true unset unset pruned pruned \
"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
# --prune-tags on its own does nothing, needs --prune as well, same
# for fetch.pruneTags without fetch.prune
-test_configured_prune unset unset unset unset kept kept "--prune-tags"
-test_configured_prune unset unset true unset kept kept ""
-test_configured_prune unset unset unset true kept kept ""
+test_configured_prune unset unset unset unset kept kept "--prune-tags"
+test_configured_prune unset unset true unset kept kept ""
+test_configured_prune unset unset unset true kept kept ""
# These will prune the tags
test_configured_prune unset unset unset unset pruned pruned "--prune --prune-tags"
-test_configured_prune true unset true unset pruned pruned ""
-test_configured_prune unset true unset true pruned pruned ""
+test_configured_prune true unset true unset pruned pruned ""
+test_configured_prune unset true unset true pruned pruned ""
# remote.<name>.pruneTags overrides fetch.pruneTags, just like
# remote.<name>.prune overrides fetch.prune if set.
-test_configured_prune true unset true unset pruned pruned ""
-test_configured_prune false true false true pruned pruned ""
-test_configured_prune true false true false kept kept ""
+test_configured_prune true unset true unset pruned pruned ""
+test_configured_prune false true false true pruned pruned ""
+test_configured_prune true false true false kept kept ""
# When --prune-tags is supplied it's ignored if an explicit refspec is
# given, same for the configuration options.
test_configured_prune unset unset unset unset pruned kept \
"--prune --prune-tags origin +refs/heads/*:refs/remotes/origin/*"
-test_configured_prune unset unset true unset pruned kept \
+test_configured_prune unset unset true unset pruned kept \
"--prune origin +refs/heads/*:refs/remotes/origin/*"
-test_configured_prune unset unset unset true pruned kept \
+test_configured_prune unset unset unset true pruned kept \
"--prune origin +refs/heads/*:refs/remotes/origin/*"
# Pruning that also takes place if a file:// url replaces a named
# remote. However, because there's no implicit
# +refs/heads/*:refs/remotes/origin/* refspec and supplying it on the
# command-line negates --prune-tags, the branches will not be pruned.
-test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "name"
-test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "link"
+test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "name"
+test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "link"
test_configured_prune_type unset unset unset unset pruned pruned "origin --prune --prune-tags" "name"
-test_configured_prune_type unset unset unset unset kept pruned "origin --prune --prune-tags" "link"
+test_configured_prune_type unset unset unset unset kept pruned "origin --prune --prune-tags" "link"
test_configured_prune_type unset unset unset unset pruned pruned "--prune --prune-tags origin" "name"
-test_configured_prune_type unset unset unset unset kept pruned "--prune --prune-tags origin" "link"
-test_configured_prune_type unset unset true unset pruned pruned "--prune origin" "name"
-test_configured_prune_type unset unset true unset kept pruned "--prune origin" "link"
-test_configured_prune_type unset unset unset true pruned pruned "--prune origin" "name"
-test_configured_prune_type unset unset unset true kept pruned "--prune origin" "link"
-test_configured_prune_type true unset true unset pruned pruned "origin" "name"
-test_configured_prune_type true unset true unset kept pruned "origin" "link"
-test_configured_prune_type unset true true unset pruned pruned "origin" "name"
-test_configured_prune_type unset true true unset kept pruned "origin" "link"
-test_configured_prune_type unset true unset true pruned pruned "origin" "name"
-test_configured_prune_type unset true unset true kept pruned "origin" "link"
+test_configured_prune_type unset unset unset unset kept pruned "--prune --prune-tags origin" "link"
+test_configured_prune_type unset unset true unset pruned pruned "--prune origin" "name"
+test_configured_prune_type unset unset true unset kept pruned "--prune origin" "link"
+test_configured_prune_type unset unset unset true pruned pruned "--prune origin" "name"
+test_configured_prune_type unset unset unset true kept pruned "--prune origin" "link"
+test_configured_prune_type true unset true unset pruned pruned "origin" "name"
+test_configured_prune_type true unset true unset kept pruned "origin" "link"
+test_configured_prune_type unset true true unset pruned pruned "origin" "name"
+test_configured_prune_type unset true true unset kept pruned "origin" "link"
+test_configured_prune_type unset true unset true pruned pruned "origin" "name"
+test_configured_prune_type unset true unset true kept pruned "origin" "link"
# When all remote.origin.fetch settings are deleted a --prune
# --prune-tags still implicitly supplies refs/tags/*:refs/tags/* so
@@ -1137,8 +1155,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '
)
'
-for section in fetch transfer
-do
+for section in fetch transfer; do
test_expect_success "$section.hideRefs affects connectivity check" '
GIT_TRACE="$PWD"/trace git -c $section.hideRefs=refs -c \
$section.hideRefs="!refs/tags/" fetch &&
@@ -1157,21 +1174,23 @@ test_expect_success 'prepare source branch' '
test 3 -le $(wc -l <actual)
'
-validate_store_type () {
+validate_store_type() {
git -C dest count-objects -v >actual &&
- case "$store_type" in
- packed)
- grep "^count: 0$" actual ;;
- loose)
- grep "^packs: 0$" actual ;;
- esac || {
+ case "$store_type" in
+ packed)
+ grep "^count: 0$" actual
+ ;;
+ loose)
+ grep "^packs: 0$" actual
+ ;;
+ esac || {
echo "store_type is $store_type"
cat actual
false
}
}
-test_unpack_limit () {
+test_unpack_limit() {
store_type=$1
case "$store_type" in
@@ -1192,43 +1211,39 @@ test_unpack_limit () {
test_unpack_limit packed
test_unpack_limit loose
-setup_negotiation_tip () {
+setup_negotiation_tip() {
SERVER="$1"
URL="$2"
USE_PROTOCOL_V2="$3"
rm -rf "$SERVER" client trace &&
- git init -b main "$SERVER" &&
- test_commit -C "$SERVER" alpha_1 &&
- test_commit -C "$SERVER" alpha_2 &&
- git -C "$SERVER" checkout --orphan beta &&
- test_commit -C "$SERVER" beta_1 &&
- test_commit -C "$SERVER" beta_2 &&
-
- git clone "$URL" client &&
-
- if test "$USE_PROTOCOL_V2" -eq 1
- then
- git -C "$SERVER" config protocol.version 2 &&
- git -C client config protocol.version 2
- fi &&
-
- test_commit -C "$SERVER" beta_s &&
- git -C "$SERVER" checkout main &&
- test_commit -C "$SERVER" alpha_s &&
- git -C "$SERVER" tag -d alpha_1 alpha_2 beta_1 beta_2
+ git init -b main "$SERVER" &&
+ test_commit -C "$SERVER" alpha_1 &&
+ test_commit -C "$SERVER" alpha_2 &&
+ git -C "$SERVER" checkout --orphan beta &&
+ test_commit -C "$SERVER" beta_1 &&
+ test_commit -C "$SERVER" beta_2 &&
+ git clone "$URL" client &&
+ if test "$USE_PROTOCOL_V2" -eq 1; then
+ git -C "$SERVER" config protocol.version 2 &&
+ git -C client config protocol.version 2
+ fi &&
+ test_commit -C "$SERVER" beta_s &&
+ git -C "$SERVER" checkout main &&
+ test_commit -C "$SERVER" alpha_s &&
+ git -C "$SERVER" tag -d alpha_1 alpha_2 beta_1 beta_2
}
-check_negotiation_tip () {
+check_negotiation_tip() {
# Ensure that {alpha,beta}_1 are sent as "have", but not {alpha_beta}_2
ALPHA_1=$(git -C client rev-parse alpha_1) &&
- grep "fetch> have $ALPHA_1" trace &&
- BETA_1=$(git -C client rev-parse beta_1) &&
- grep "fetch> have $BETA_1" trace &&
- ALPHA_2=$(git -C client rev-parse alpha_2) &&
- ! grep "fetch> have $ALPHA_2" trace &&
- BETA_2=$(git -C client rev-parse beta_2) &&
- ! grep "fetch> have $BETA_2" trace
+ grep "fetch> have $ALPHA_1" trace &&
+ BETA_1=$(git -C client rev-parse beta_1) &&
+ grep "fetch> have $BETA_1" trace &&
+ ALPHA_2=$(git -C client rev-parse alpha_2) &&
+ ! grep "fetch> have $ALPHA_2" trace &&
+ BETA_2=$(git -C client rev-parse beta_2) &&
+ ! grep "fetch> have $BETA_2" trace
}
test_expect_success '--negotiation-tip limits "have" lines sent' '
diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh
index 64b3491e4e..1b3865e154 100755
--- a/t/t5512-ls-remote.sh
+++ b/t/t5512-ls-remote.sh
@@ -293,6 +293,8 @@ test_expect_success 'ls-remote with filtered symref (refname)' '
cat >expect <<-EOF &&
ref: refs/heads/main HEAD
$rev HEAD
+ ref: refs/remotes/origin/main refs/remotes/origin/HEAD
+ $rev refs/remotes/origin/HEAD
EOF
git ls-remote --symref . HEAD >actual &&
test_cmp expect actual
diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh
index 579872c258..e3482b27b2 100755
--- a/t/t5514-fetch-multiple.sh
+++ b/t/t5514-fetch-multiple.sh
@@ -45,14 +45,17 @@ test_expect_success setup '
'
cat > test/expect << EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -97,6 +100,7 @@ cat > expect << EOF
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
@@ -112,8 +116,10 @@ test_expect_success 'git fetch --multiple (but only one remote)' '
'
cat > expect << EOF
+ one/HEAD -> one/main
one/main
one/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -141,7 +147,7 @@ test_expect_success 'git fetch --multiple (bad remote names)' '
test_expect_success 'git fetch --all (skipFetchAll)' '
(cd test4 &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -153,11 +159,14 @@ test_expect_success 'git fetch --all (skipFetchAll)' '
'
cat > expect << EOF
+ one/HEAD -> one/main
one/main
one/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -165,7 +174,7 @@ EOF
test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '
(cd test4 &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -221,14 +230,17 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' '
create_fetch_all_expect () {
cat >expect <<-\EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -265,6 +277,7 @@ test_expect_success 'git fetch (fetch all remotes with fetch.all = true)' '
create_fetch_one_expect () {
cat >expect <<-\EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 331778bd42..5a051aa0c7 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -1395,7 +1395,8 @@ test_expect_success 'fetch follows tags by default' '
git tag -m "annotated" tag &&
git for-each-ref >tmp1 &&
sed -n "p; s|refs/heads/main$|refs/remotes/origin/main|p" tmp1 |
- sort -k 3 >../expect
+ sed -n "p; s|refs/heads/main$|refs/remotes/origin/HEAD|p" |
+ sort -k 4 >../expect
) &&
test_when_finished "rm -rf dst" &&
git init dst &&
diff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh
index 98ece27c6a..d3996af6ee 100755
--- a/t/t5527-fetch-odd-refs.sh
+++ b/t/t5527-fetch-odd-refs.sh
@@ -52,7 +52,8 @@ test_expect_success LONG_REF 'fetch handles extremely long refname' '
long
main
EOF
- git for-each-ref --format="%(subject)" refs/remotes/long >actual &&
+ git for-each-ref --format="%(subject)" refs/remotes/long \
+ --exclude=refs/remotes/long/HEAD >actual &&
test_cmp expect actual
'
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index a66d0e089d..88331d1ceb 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -329,7 +329,8 @@ test_expect_success 'incremental-repack task' '
# Delete refs that have not been repacked in these packs.
git for-each-ref --format="delete %(refname)" \
- refs/prefetch refs/tags refs/remotes >refs &&
+ refs/prefetch refs/tags refs/remotes \
+ --exclude=refs/remotes/*/HEAD >refs &&
git update-ref --stdin <refs &&
# Replace the object directory with this pack layout.
diff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh
index a30b2c9f70..2237844550 100755
--- a/t/t9210-scalar.sh
+++ b/t/t9210-scalar.sh
@@ -151,7 +151,8 @@ test_expect_success 'scalar clone' '
"$(pwd)" &&
git for-each-ref --format="%(refname)" refs/remotes/origin/ >actual &&
- echo "refs/remotes/origin/parallel" >expect &&
+ echo "refs/remotes/origin/HEAD" >>expect &&
+ echo "refs/remotes/origin/parallel" >>expect &&
test_cmp expect actual &&
test_path_is_missing 1/2 &&
@@ -220,7 +221,7 @@ test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '
done
'
- test_expect_success 'scalar reconfigure --all with detached HEADs' '
+test_expect_success 'scalar reconfigure --all with detached HEADs' '
repos="two three four" &&
for num in $repos
do
diff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh
index c16ea67c1d..d9cb6b9a3e 100755
--- a/t/t9211-scalar-clone.sh
+++ b/t/t9211-scalar-clone.sh
@@ -32,7 +32,7 @@ test_expect_success 'set up repository to clone' '
)
'
-cleanup_clone () {
+cleanup_clone() {
rm -rf "$1"
}
@@ -128,7 +128,7 @@ test_expect_success '--single-branch clones HEAD only' '
(
cd $enlistment/src &&
git for-each-ref refs/remotes/origin >out &&
- test_line_count = 1 out &&
+ test_line_count = 2 out &&
grep "refs/remotes/origin/base" out
) &&
@@ -142,7 +142,7 @@ test_expect_success '--no-single-branch clones all branches' '
(
cd $enlistment/src &&
git for-each-ref refs/remotes/origin >out &&
- test_line_count = 2 out &&
+ test_line_count = 3 out &&
grep "refs/remotes/origin/base" out &&
grep "refs/remotes/origin/parallel" out
) &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index cc6aa9f0cd..b663c4609e 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -658,6 +658,7 @@ test_expect_success '__git_refs - simple' '
HEAD
main
matching-branch
+ other/HEAD
other/branch-in-other
other/main-in-other
matching-tag
@@ -673,6 +674,7 @@ test_expect_success '__git_refs - full refs' '
cat >expected <<-EOF &&
refs/heads/main
refs/heads/matching-branch
+ refs/remotes/other/HEAD
refs/remotes/other/branch-in-other
refs/remotes/other/main-in-other
refs/tags/matching-tag
@@ -729,6 +731,7 @@ test_expect_success '__git_refs - remote on local file system - full refs' '
test_expect_success '__git_refs - configured remote' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -756,6 +759,7 @@ test_expect_success '__git_refs - configured remote - full refs' '
test_expect_success '__git_refs - configured remote - repo given on the command line' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -787,6 +791,7 @@ test_expect_success '__git_refs - configured remote - full refs - repo given on
test_expect_success '__git_refs - configured remote - remote name matches a directory' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -875,12 +880,14 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer
HEAD
main
matching-branch
+ other/HEAD
other/ambiguous
other/branch-in-other
other/main-in-other
remote/ambiguous
remote/branch-in-remote
matching-tag
+ HEAD
branch-in-other
branch-in-remote
main-in-other
@@ -904,6 +911,7 @@ test_expect_success '__git_refs - after --opt=' '
HEAD
main
matching-branch
+ other/HEAD
other/branch-in-other
other/main-in-other
matching-tag
@@ -919,6 +927,7 @@ test_expect_success '__git_refs - after --opt= - full refs' '
cat >expected <<-EOF &&
refs/heads/main
refs/heads/matching-branch
+ refs/remotes/other/HEAD
refs/remotes/other/branch-in-other
refs/remotes/other/main-in-other
refs/tags/matching-tag
@@ -935,6 +944,7 @@ test_expect_success '__git refs - excluding refs' '
^HEAD
^main
^matching-branch
+ ^other/HEAD
^other/branch-in-other
^other/main-in-other
^matching-tag
@@ -950,6 +960,7 @@ test_expect_success '__git refs - excluding full refs' '
cat >expected <<-EOF &&
^refs/heads/main
^refs/heads/matching-branch
+ ^refs/remotes/other/HEAD
^refs/remotes/other/branch-in-other
^refs/remotes/other/main-in-other
^refs/tags/matching-tag
@@ -975,6 +986,7 @@ test_expect_success '__git_refs - do not filter refs unless told so' '
main
matching-branch
matching/branch
+ other/HEAD
other/branch-in-other
other/main-in-other
other/matching/branch-in-other
@@ -1095,6 +1107,7 @@ test_expect_success '__git_complete_refs - simple' '
HEAD Z
main Z
matching-branch Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
matching-tag Z
@@ -1123,6 +1136,7 @@ test_expect_success '__git_complete_refs - matching' '
test_expect_success '__git_complete_refs - remote' '
sed -e "s/Z$//" >expected <<-EOF &&
HEAD Z
+ HEAD Z
branch-in-other Z
main-in-other Z
EOF
@@ -1139,9 +1153,11 @@ test_expect_success '__git_complete_refs - track' '
HEAD Z
main Z
matching-branch Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
matching-tag Z
+ HEAD Z
branch-in-other Z
main-in-other Z
EOF
@@ -1184,6 +1200,7 @@ test_expect_success '__git_complete_refs - suffix' '
HEAD.
main.
matching-branch.
+ other/HEAD.
other/branch-in-other.
other/main-in-other.
matching-tag.
@@ -1199,6 +1216,7 @@ test_expect_success '__git_complete_refs - suffix' '
test_expect_success '__git_complete_fetch_refspecs - simple' '
sed -e "s/Z$//" >expected <<-EOF &&
HEAD:HEAD Z
+ HEAD:HEAD Z
branch-in-other:branch-in-other Z
main-in-other:main-in-other Z
EOF
@@ -1225,6 +1243,7 @@ test_expect_success '__git_complete_fetch_refspecs - matching' '
test_expect_success '__git_complete_fetch_refspecs - prefix' '
sed -e "s/Z$//" >expected <<-EOF &&
+HEAD:HEAD Z
+ +HEAD:HEAD Z
+branch-in-other:branch-in-other Z
+main-in-other:main-in-other Z
EOF
@@ -1289,6 +1308,7 @@ test_expect_success '__git_complete_worktree_paths with -C' '
test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '
test_completion "git switch " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1435,11 +1455,13 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena
test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '
test_completion "git checkout " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1461,6 +1483,7 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl
test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '
GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git switch --guess " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1470,6 +1493,7 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G
test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '
test_completion "git switch --no-guess --guess " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1490,6 +1514,7 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1498,11 +1523,13 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete
test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '
GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git checkout --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1514,6 +1541,7 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1522,11 +1550,13 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '
test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '
test_completion "git checkout --no-guess --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1538,6 +1568,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1550,6 +1581,7 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1559,11 +1591,13 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a
test_config checkout.guess true &&
test_completion "git checkout " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1573,11 +1607,13 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.
test_config checkout.guess false &&
test_completion "git checkout --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1590,6 +1626,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1601,6 +1638,7 @@ test_expect_success 'git switch - with --detach, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1612,6 +1650,7 @@ test_expect_success 'git checkout - with --detach, complete only references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1783,6 +1822,7 @@ test_expect_success 'git switch - with -d, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1794,6 +1834,7 @@ test_expect_success 'git checkout - with -d, complete only references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1801,10 +1842,12 @@ test_expect_success 'git checkout - with -d, complete only references' '
test_expect_success 'git switch - with --track, complete only remote branches' '
test_completion "git switch --track " <<-\EOF &&
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
test_completion "git switch -t " <<-\EOF
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1812,10 +1855,12 @@ test_expect_success 'git switch - with --track, complete only remote branches' '
test_expect_success 'git checkout - with --track, complete only remote branches' '
test_completion "git checkout --track " <<-\EOF &&
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
test_completion "git checkout -t " <<-\EOF
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1834,6 +1879,7 @@ test_expect_success 'git checkout - with --no-track, complete only local referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1845,6 +1891,7 @@ test_expect_success 'git switch - with -c, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1856,6 +1903,7 @@ test_expect_success 'git switch - with -C, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1867,6 +1915,7 @@ test_expect_success 'git switch - with -c and --track, complete all references'
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1878,6 +1927,7 @@ test_expect_success 'git switch - with -C and --track, complete all references'
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1889,6 +1939,7 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1900,6 +1951,7 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1911,6 +1963,7 @@ test_expect_success 'git checkout - with -b, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1922,6 +1975,7 @@ test_expect_success 'git checkout - with -B, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1933,6 +1987,7 @@ test_expect_success 'git checkout - with -b and --track, complete all references
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1944,6 +1999,7 @@ test_expect_success 'git checkout - with -B and --track, complete all references
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1955,6 +2011,7 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1966,6 +2023,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1973,6 +2031,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen
test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '
test_completion "git switch -c " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1982,6 +2041,7 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem
test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '
test_completion "git switch -C " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2019,6 +2079,7 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche
test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '
test_completion "git checkout -b " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2028,6 +2089,7 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r
test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '
test_completion "git checkout -B " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2065,6 +2127,7 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc
test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '
test_completion "git switch --orphan " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2080,6 +2143,7 @@ test_expect_success 'git switch - --orphan with branch already provided complete
test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '
test_completion "git checkout --orphan " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2093,6 +2157,7 @@ test_expect_success 'git checkout - --orphan with branch already provided comple
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
--
2.47.0.94.gb64850d498
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v11 8/8] fetch set_head: handle mirrored bare repositories
2024-10-22 19:45 ` [PATCH v11 0/8] " Bence Ferdinandy
` (6 preceding siblings ...)
2024-10-22 19:45 ` [PATCH v11 7/8] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
@ 2024-10-22 19:45 ` Bence Ferdinandy
2024-10-23 15:36 ` [PATCH v12 0/8] set-head/fetch remote/HEAD updates Bence Ferdinandy
8 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-22 19:45 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy
When adding a remote to bare repository with "git remote add --mirror",
running fetch will fail to update HEAD to the remote's HEAD, since it
does not know how to handle bare repositories. On the other hand HEAD
already has content, since "git init --bare" has already set HEAD to
whatever is the default branch set for the user. Unless this - by chance
- is the same as the remote's HEAD, HEAD will be pointing to a bad
symref. Teach set_head to handle bare repositories, by overwriting HEAD
so it mirrors the remote's HEAD.
Note, that in this case overriding the local HEAD reference is
necessary, since HEAD will exist before fetch can be run, but this
should not be an issue, since the whole purpose of --mirror is to be an
exact mirror of the remote, so following any changes to HEAD makes
sense.
Also note, that although "git remote set-head" also fails when trying to
update the remote's locally tracked HEAD in a mirrored bare repository,
the usage of the command does not make much sense after this patch:
fetch will update the remote HEAD correctly, and setting it manually to
something else is antithetical to the concept of mirroring.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v10: - new patch
- handles the issue discovered in
https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/T/
v11: no change
builtin/fetch.c | 16 ++++++++++++----
t/t5505-remote.sh | 10 ++++++++++
2 files changed, 22 insertions(+), 4 deletions(-)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index cadfd2407a..8af3efd6ac 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1633,14 +1633,22 @@ static int set_head(const struct ref *remote_refs)
else
head_name = xstrdup(heads.items[0].string);
if (head_name) {
- strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
- strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name);
+ int is_bare = is_bare_repository();
+ if (is_bare) {
+ strbuf_addstr(&b_head, "HEAD");
+ strbuf_addf(&b_remote_head, "refs/heads/%s", head_name);
+ } else {
+ strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name);
+ }
/* make sure it's valid */
- if (!refs_ref_exists(refs, b_remote_head.buf))
+ if (!is_bare && !refs_ref_exists(refs, b_remote_head.buf)) {
result = 1;
+ }
else if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
- "fetch", &b_local_head, 1))
+ "fetch", &b_local_head, !is_bare)) {
result = 1;
+ }
else
report_set_head(remote, head_name, &b_local_head);
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 4990d00209..dfa78f3e8d 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -545,6 +545,16 @@ test_expect_success 'add --mirror && prune' '
)
'
+test_expect_success 'add --mirror setting HEAD' '
+ mkdir headmirror &&
+ (
+ cd headmirror &&
+ git init --bare -b notmain &&
+ git remote add --mirror -f origin ../one &&
+ test "$(git symbolic-ref HEAD)" = "refs/heads/main"
+ )
+'
+
test_expect_success 'add --mirror=fetch' '
mkdir mirror-fetch &&
git init -b main mirror-fetch/parent &&
--
2.47.0.94.gb64850d498
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v12 0/8] set-head/fetch remote/HEAD updates
2024-10-22 19:45 ` [PATCH v11 0/8] " Bence Ferdinandy
` (7 preceding siblings ...)
2024-10-22 19:45 ` [PATCH v11 8/8] fetch set_head: handle mirrored bare repositories Bence Ferdinandy
@ 2024-10-23 15:36 ` Bence Ferdinandy
2024-10-23 15:36 ` [PATCH v12 1/8] t/t5505-remote: set default branch to main Bence Ferdinandy
` (9 more replies)
8 siblings, 10 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-23 15:36 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy,
ferdinandy.bence
v12: added missing S-o-b
v11: fixed leaks found by running tests after `make SANITIZE=leak`
green pipeline:
https://github.com/ferdinandyb/git/actions/runs/11466803523
v10: added a new patch (8/8) on top of v9 to handle HEAD in mirrored bare
repositories, rest of the patches are unchanged
v9:
the two most notable changes are
- the new patch 1/7 which address
https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/, but see
https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/
- the refs_update_symref -> refs_update_symref_extended change in 2/7,
reflecting on Phillip's comments (see
https://lore.kernel.org/git/a7cb48e5-d8ba-44c1-9dbe-d1e8f8a63e3c@gmail.com/)
Bence Ferdinandy (8):
t/t5505-remote: set default branch to main
refs: atomically record overwritten ref in update_symref
remote set-head: refactor for readability
remote set-head: better output for --auto
refs: add TRANSACTION_CREATE_EXISTS error
refs: add create_only option to refs_update_symref_extended
fetch: set remote/HEAD if it does not exist
fetch set_head: handle mirrored bare repositories
builtin/fetch.c | 94 +++++++++++++
builtin/remote.c | 52 +++++--
refs.c | 41 +++++-
refs.h | 8 +-
refs/files-backend.c | 24 ++--
refs/reftable-backend.c | 6 +-
t/t4207-log-decoration-colors.sh | 3 +-
t/t5505-remote.sh | 83 ++++++++++-
t/t5510-fetch.sh | 229 ++++++++++++++++---------------
t/t5512-ls-remote.sh | 2 +
t/t5514-fetch-multiple.sh | 17 ++-
t/t5516-fetch-push.sh | 3 +-
t/t5527-fetch-odd-refs.sh | 3 +-
t/t7900-maintenance.sh | 3 +-
t/t9210-scalar.sh | 5 +-
t/t9211-scalar-clone.sh | 6 +-
t/t9902-completion.sh | 65 +++++++++
17 files changed, 490 insertions(+), 154 deletions(-)
--
2.47.0.94.g1247fb88fd.dirty
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v12 1/8] t/t5505-remote: set default branch to main
2024-10-23 15:36 ` [PATCH v12 0/8] set-head/fetch remote/HEAD updates Bence Ferdinandy
@ 2024-10-23 15:36 ` Bence Ferdinandy
2024-11-15 4:52 ` Junio C Hamano
2024-10-23 15:36 ` [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
` (8 subsequent siblings)
9 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-23 15:36 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy,
ferdinandy.bence
Consider the bare repository called "mirror" in the test. Running `git
remote add --mirror -f origin ../one` will not change HEAD, consequently
if init.defaultBranch is not the same as what HEAD in the remote
("one"), HEAD in "mirror" will be pointing to a non-existent reference.
Hence if "mirror" is used as a remote by yet another repository,
ls-remote will not show HEAD. On the other hand, if init.defaultBranch
happens to match HEAD in "one", then ls-remote will show HEAD.
Since the CI globally exports GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main,
there's a drift between how the test repositories are set up in the CI
and during local testing. This issue does not manifest currently, as the
test does not do any remote HEAD manipulation where this would come up,
but should such things be added, a locally passing test would break the
CI vice-versa.
Set GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main in the test to be
consistent with the CI.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v9: - new patch
- a bandaid for the CI issue noticed by Taylor (cf:
https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/), but
see
https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/ for the root cause in detail
v10: no change
v11: no change
v12: added forgotten sign-off
t/t5505-remote.sh | 3 +++
1 file changed, 3 insertions(+)
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 532035933f..9b50276646 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -2,6 +2,9 @@
test_description='git remote porcelain-ish'
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
--
2.47.0.94.g1247fb88fd.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v12 1/8] t/t5505-remote: set default branch to main
2024-10-23 15:36 ` [PATCH v12 1/8] t/t5505-remote: set default branch to main Bence Ferdinandy
@ 2024-11-15 4:52 ` Junio C Hamano
2024-11-15 22:03 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Junio C Hamano @ 2024-11-15 4:52 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, ferdinandy.bence
Bence Ferdinandy <bence@ferdinandy.com> writes:
> Consider the bare repository called "mirror" in the test. Running `git
> remote add --mirror -f origin ../one` will not change HEAD, consequently
> if init.defaultBranch is not the same as what HEAD in the remote
> ("one"), HEAD in "mirror" will be pointing to a non-existent reference.
> Hence if "mirror" is used as a remote by yet another repository,
> ls-remote will not show HEAD. On the other hand, if init.defaultBranch
> happens to match HEAD in "one", then ls-remote will show HEAD.
Making sure that the hardcoded (in the Git binary) default branch
name would not affect the outcome of the test is a good thing to do.
I like the patch text, but ...
> Since the CI globally exports GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main,
> there's a drift between how the test repositories are set up in the CI
> and during local testing. This issue does not manifest currently, as the
> test does not do any remote HEAD manipulation where this would come up,
> but should such things be added, a locally passing test would break the
> CI vice-versa.
... this description may not quite be accurate. Don't some jobs of
CI use 'main' while the rest use 'master'?
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v12 1/8] t/t5505-remote: set default branch to main
2024-11-15 4:52 ` Junio C Hamano
@ 2024-11-15 22:03 ` Bence Ferdinandy
0 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-15 22:03 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, ferdinandy.bence
On Fri Nov 15, 2024 at 05:52, Junio C Hamano <gitster@pobox.com> wrote:
> Bence Ferdinandy <bence@ferdinandy.com> writes:
>
>> Consider the bare repository called "mirror" in the test. Running `git
>> remote add --mirror -f origin ../one` will not change HEAD, consequently
>> if init.defaultBranch is not the same as what HEAD in the remote
>> ("one"), HEAD in "mirror" will be pointing to a non-existent reference.
>> Hence if "mirror" is used as a remote by yet another repository,
>> ls-remote will not show HEAD. On the other hand, if init.defaultBranch
>> happens to match HEAD in "one", then ls-remote will show HEAD.
>
> Making sure that the hardcoded (in the Git binary) default branch
> name would not affect the outcome of the test is a good thing to do.
> I like the patch text, but ...
>
>> Since the CI globally exports GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main,
>> there's a drift between how the test repositories are set up in the CI
>> and during local testing. This issue does not manifest currently, as the
>> test does not do any remote HEAD manipulation where this would come up,
>> but should such things be added, a locally passing test would break the
>> CI vice-versa.
>
> ... this description may not quite be accurate. Don't some jobs of
> CI use 'main' while the rest use 'master'?
True, it's that one specific job, and of course "there may be a drift depending
on the particular test" is more accurate. I'll update for a v13.
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref
2024-10-23 15:36 ` [PATCH v12 0/8] set-head/fetch remote/HEAD updates Bence Ferdinandy
2024-10-23 15:36 ` [PATCH v12 1/8] t/t5505-remote: set default branch to main Bence Ferdinandy
@ 2024-10-23 15:36 ` Bence Ferdinandy
2024-11-15 5:50 ` Junio C Hamano
2024-10-23 15:36 ` [PATCH v12 3/8] remote set-head: refactor for readability Bence Ferdinandy
` (7 subsequent siblings)
9 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-23 15:36 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy,
ferdinandy.bence
When updating a symref with update_symref it's currently not possible to
know for sure what was the previous value that was overwritten. Extend
refs_update_symref under a new function name, to record the value after
the ref has been locked if the caller of refs_update_symref_extended
requests it via a new variable in the function call. Keep the original
refs_update_symref function with the same signature, but now as
a wrapper around refs_update_symref_extended.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: - added before_target to reftables backend
- added an extra safety check for transaction's existence in refs.c
v6: - no change
v7: - remove the whole before_target concept from the backends and
handle checking it in refs.c instead (thanks Karthik)
- rename the before_target to referent which is how the same concept
is called in the backends
- change commit prefix to be more in line with project standards
v8: no change
v9: - instead of adding parameters to refs_update_symref, rename what
was in v8 as refs_update_symref_extended and make refs_update_symref
a wrapper for that. This significantly reduces the number of files
that need to be touched, and avoids adding a lot of dummy NULL-s
in unrelated places.
v10: no change
v11: no change
v12: no change
refs.c | 19 ++++++++++++++++---
refs.h | 4 ++++
2 files changed, 20 insertions(+), 3 deletions(-)
diff --git a/refs.c b/refs.c
index 5f729ed412..24a4172cd2 100644
--- a/refs.c
+++ b/refs.c
@@ -2115,6 +2115,13 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
int refs_update_symref(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg)
+{
+ return refs_update_symref_extended(refs, ref, target, logmsg, NULL);
+}
+
+int refs_update_symref_extended(struct ref_store *refs, const char *ref,
+ const char *target, const char *logmsg,
+ struct strbuf *referent)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
@@ -2122,13 +2129,20 @@ int refs_update_symref(struct ref_store *refs, const char *ref,
transaction = ref_store_transaction_begin(refs, &err);
if (!transaction ||
- ref_transaction_update(transaction, ref, NULL, NULL,
+ ref_transaction_update(transaction, ref, NULL, NULL,
target, NULL, REF_NO_DEREF,
logmsg, &err) ||
- ref_transaction_commit(transaction, &err)) {
+ ref_transaction_prepare(transaction, &err)) {
ret = error("%s", err.buf);
+ goto cleanup;
}
+ if (referent)
+ refs_read_symbolic_ref(refs, ref, referent);
+ if (ref_transaction_commit(transaction, &err))
+ ret = error("%s", err.buf);
+
+cleanup:
strbuf_release(&err);
if (transaction)
ref_transaction_free(transaction);
@@ -2948,4 +2962,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)
return (update->flags & REF_HAVE_OLD) &&
(!is_null_oid(&update->old_oid) || update->old_target);
}
-
diff --git a/refs.h b/refs.h
index 108dfc93b3..259191a485 100644
--- a/refs.h
+++ b/refs.h
@@ -573,6 +573,10 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
int refs_update_symref(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg);
+int refs_update_symref_extended(struct ref_store *refs, const char *refname,
+ const char *target, const char *logmsg,
+ struct strbuf *referent);
+
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
UPDATE_REFS_DIE_ON_ERR,
--
2.47.0.94.g1247fb88fd.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref
2024-10-23 15:36 ` [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
@ 2024-11-15 5:50 ` Junio C Hamano
2024-11-15 22:18 ` Bence Ferdinandy
2024-11-17 23:39 ` Bence Ferdinandy
0 siblings, 2 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-11-15 5:50 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, ferdinandy.bence
Bence Ferdinandy <bence@ferdinandy.com> writes:
> int refs_update_symref(struct ref_store *refs, const char *ref,
> const char *target, const char *logmsg)
> +{
> + return refs_update_symref_extended(refs, ref, target, logmsg, NULL);
> +}
OK. As the enhanced and renamed function is also external, we do
not have to worry about reordering the old one to come after the new
one.
> +int refs_update_symref_extended(struct ref_store *refs, const char *ref,
> + const char *target, const char *logmsg,
> + struct strbuf *referent)
> {
> struct ref_transaction *transaction;
> struct strbuf err = STRBUF_INIT;
> @@ -2122,13 +2129,20 @@ int refs_update_symref(struct ref_store *refs, const char *ref,
>
> transaction = ref_store_transaction_begin(refs, &err);
> if (!transaction ||
> - ref_transaction_update(transaction, ref, NULL, NULL,
> + ref_transaction_update(transaction, ref, NULL, NULL,
An unwanted patch noise?
> target, NULL, REF_NO_DEREF,
> logmsg, &err) ||
> - ref_transaction_commit(transaction, &err)) {
> + ref_transaction_prepare(transaction, &err)) {
Likewise, but the noise distracts from the real change made on this
line, which is even worse.
The real change here is to only call _prepare(), which also asks the
transaction hook if we are OK to proceed. If we fail, we stop here
> ret = error("%s", err.buf);
> + goto cleanup;
This is also a real change. We could instead make the additional
code below into the else clause (see below).
> }
> + if (referent)
> + refs_read_symbolic_ref(refs, ref, referent);
And if we were asked to give the value of the symbolic ref, we make
this call. What should this code do when reading fails (I know it
ignores, as written, but I am asking what it _should_ do)?
> + if (ref_transaction_commit(transaction, &err))
> + ret = error("%s", err.buf);
And then we commit, or we fail to commit.
> +cleanup:
We could write the whole thing as a single "do these and leave as
soon as we see any failure" ||-cascade,
if (!(transaction = ref_store_transaction_begin(refs, &err)) ||
ref_transaction_update(transaction, ref, NULL, NULL,
target, NULL, REF_NO_DEREF,
logmsg, &err) ||
ref_transaction_prepare(transaction, &err)) ||
(referent
? refs_read_symbolic_ref(refs, ref, referent)
: 0) ||
ref_transaction_commit(transaction, &err)) {
if (!err.len)
... stuff default error message to err ...;
ret = error("%s", err.buf);
}
which may not necessarily easier to follow (and in fact it is rather
ugly), but at least, the resulting code does not have to special
case the "optionally peek into the symref" step.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref
2024-11-15 5:50 ` Junio C Hamano
@ 2024-11-15 22:18 ` Bence Ferdinandy
2024-11-15 23:27 ` Bence Ferdinandy
2024-11-17 23:39 ` Bence Ferdinandy
1 sibling, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-15 22:18 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, ferdinandy.bence
On Fri Nov 15, 2024 at 06:50, Junio C Hamano <gitster@pobox.com> wrote:
> Bence Ferdinandy <bence@ferdinandy.com> writes:
>
>> int refs_update_symref(struct ref_store *refs, const char *ref,
>> const char *target, const char *logmsg)
>> +{
>> + return refs_update_symref_extended(refs, ref, target, logmsg, NULL);
>> +}
>
> OK. As the enhanced and renamed function is also external, we do
> not have to worry about reordering the old one to come after the new
> one.
I guess this also decides that the name "_extended" is fine :)
>
>> +int refs_update_symref_extended(struct ref_store *refs, const char *ref,
>> + const char *target, const char *logmsg,
>> + struct strbuf *referent)
>> {
>> struct ref_transaction *transaction;
>> struct strbuf err = STRBUF_INIT;
>> @@ -2122,13 +2129,20 @@ int refs_update_symref(struct ref_store *refs, const char *ref,
>>
>> transaction = ref_store_transaction_begin(refs, &err);
>> if (!transaction ||
>> - ref_transaction_update(transaction, ref, NULL, NULL,
>> + ref_transaction_update(transaction, ref, NULL, NULL,
>
> An unwanted patch noise?
>
>> target, NULL, REF_NO_DEREF,
>> logmsg, &err) ||
>> - ref_transaction_commit(transaction, &err)) {
>> + ref_transaction_prepare(transaction, &err)) {
>
> Likewise, but the noise distracts from the real change made on this
> line, which is even worse.
Mea culpa, I'll get this cleaned up.
>
> The real change here is to only call _prepare(), which also asks the
> transaction hook if we are OK to proceed. If we fail, we stop here
>
>> ret = error("%s", err.buf);
>> + goto cleanup;
>
> This is also a real change. We could instead make the additional
> code below into the else clause (see below).
>
>> }
>> + if (referent)
>> + refs_read_symbolic_ref(refs, ref, referent);
>
> And if we were asked to give the value of the symbolic ref, we make
> this call. What should this code do when reading fails (I know it
> ignores, as written, but I am asking what it _should_ do)?
I think this should do _nothing_ if it fails (although should it stay this way,
I guess it should be marked with a comment that this is on purpose). My
reasoning is that running `git fetch` will be running this part of the code,
which means that should reading the symbolic ref fail for any reason, a `fetch`
that previously ran without error would now fail. We now pass up an empty
string as the previous which does mask that there was an error here. What
I think we could maybe do is pass up a special string that means there was an
error? Something that for sure cannot be a valid value for an existing
reference? I'm not sure how much sense that makes.
>
>> + if (ref_transaction_commit(transaction, &err))
>> + ret = error("%s", err.buf);
>
> And then we commit, or we fail to commit.
>
>> +cleanup:
>
> We could write the whole thing as a single "do these and leave as
> soon as we see any failure" ||-cascade,
>
> if (!(transaction = ref_store_transaction_begin(refs, &err)) ||
>
> ref_transaction_update(transaction, ref, NULL, NULL,
> target, NULL, REF_NO_DEREF,
> logmsg, &err) ||
>
> ref_transaction_prepare(transaction, &err)) ||
>
> (referent
> ? refs_read_symbolic_ref(refs, ref, referent)
> : 0) ||
>
> ref_transaction_commit(transaction, &err)) {
> if (!err.len)
> ... stuff default error message to err ...;
> ret = error("%s", err.buf);
> }
>
> which may not necessarily easier to follow (and in fact it is rather
> ugly), but at least, the resulting code does not have to special
> case the "optionally peek into the symref" step.
As I said above, I don't think we actually want to fail the update even if the
symbolic ref reading fails, so I think the special casing should stay. I'll
wait here to see more clearly on what to do here.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref
2024-11-15 22:18 ` Bence Ferdinandy
@ 2024-11-15 23:27 ` Bence Ferdinandy
2024-11-16 7:58 ` Junio C Hamano
0 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-15 23:27 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, ferdinandy.bence
2024. nov. 15. 23:18:10 Bence Ferdinandy <bence@ferdinandy.com>:
>
> On Fri Nov 15, 2024 at 06:50, Junio C Hamano <gitster@pobox.com> wrote:
>> Bence Ferdinandy <bence@ferdinandy.com> writes:
>>
>>> int refs_update_symref(struct ref_store *refs, const char *ref,
>>> const char *target, const char *logmsg)
>>> +{
>>> + return refs_update_symref_extended(refs, ref, target, logmsg,
>>> NULL);
>>> +}
>>
>> OK. As the enhanced and renamed function is also external, we do
>> not have to worry about reordering the old one to come after the new
>> one.
>
> I guess this also decides that the name "_extended" is fine :)
>
>>
>>> +int refs_update_symref_extended(struct ref_store *refs, const char
>>> *ref,
>>> + const char *target, const char *logmsg,
>>> + struct strbuf *referent)
>>> {
>>> struct ref_transaction *transaction;
>>> struct strbuf err = STRBUF_INIT;
>>> @@ -2122,13 +2129,20 @@ int refs_update_symref(struct ref_store
>>> *refs, const char *ref,
>>>
>>> transaction = ref_store_transaction_begin(refs, &err);
>>> if (!transaction ||
>>> - ref_transaction_update(transaction, ref, NULL, NULL,
>>> + ref_transaction_update(transaction, ref, NULL, NULL,
>>
>> An unwanted patch noise?
>>
>>> target, NULL, REF_NO_DEREF,
>>> logmsg, &err) ||
>>> - ref_transaction_commit(transaction, &err)) {
>>> + ref_transaction_prepare(transaction, &err)) {
>>
>> Likewise, but the noise distracts from the real change made on this
>> line, which is even worse.
>
> Mea culpa, I'll get this cleaned up.
>
>>
>> The real change here is to only call _prepare(), which also asks the
>> transaction hook if we are OK to proceed. If we fail, we stop here
>>
>>> ret = error("%s", err.buf);
>>> + goto cleanup;
>>
>> This is also a real change. We could instead make the additional
>> code below into the else clause (see below).
>>
>>> }
>>> + if (referent)
>>> + refs_read_symbolic_ref(refs, ref, referent);
>>
>> And if we were asked to give the value of the symbolic ref, we make
>> this call. What should this code do when reading fails (I know it
>> ignores, as written, but I am asking what it _should_ do)?
>
> I think this should do _nothing_ if it fails (although should it stay
> this way,
> I guess it should be marked with a comment that this is on purpose). My
> reasoning is that running `git fetch` will be running this part of the
> code,
> which means that should reading the symbolic ref fail for any reason, a
> `fetch`
> that previously ran without error would now fail. We now pass up an
> empty
> string as the previous which does mask that there was an error here.
> What
> I think we could maybe do is pass up a special string that means there
> was an
> error? Something that for sure cannot be a valid value for an existing
> reference? I'm not sure how much sense that makes.
Sorry, it's late. The above is slightly bollocks since fetch ignores any
set_head errors later :)
But the idea stands that if we can set the head, let's do it.
The previous head is not important enough to die on.
As mentioned on the other patch, we could try to read a non-symref
instead also, if symref fails.
>
>>
>>> + if (ref_transaction_commit(transaction, &err))
>>> + ret = error("%s", err.buf);
>>
>> And then we commit, or we fail to commit.
>>
>>> +cleanup:
>>
>> We could write the whole thing as a single "do these and leave as
>> soon as we see any failure" ||-cascade,
>>
>> if (!(transaction = ref_store_transaction_begin(refs, &err)) ||
>>
>> ref_transaction_update(transaction, ref, NULL, NULL,
>> target, NULL, REF_NO_DEREF,
>> logmsg, &err) ||
>>
>> ref_transaction_prepare(transaction, &err)) ||
>>
>> (referent
>> ? refs_read_symbolic_ref(refs, ref, referent)
>> : 0) ||
>>
>> ref_transaction_commit(transaction, &err)) {
>> if (!err.len)
>> ... stuff default error message to err ...;
>> ret = error("%s", err.buf);
>> }
>>
>> which may not necessarily easier to follow (and in fact it is rather
>> ugly), but at least, the resulting code does not have to special
>> case the "optionally peek into the symref" step.
>
> As I said above, I don't think we actually want to fail the update even
> if the
> symbolic ref reading fails, so I think the special casing should stay.
> I'll
> wait here to see more clearly on what to do here.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref
2024-11-15 23:27 ` Bence Ferdinandy
@ 2024-11-16 7:58 ` Junio C Hamano
0 siblings, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-11-16 7:58 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, ferdinandy.bence
Bence Ferdinandy <bence@ferdinandy.com> writes:
> Sorry, it's late. The above is slightly bollocks since fetch ignores
> any set_head errors later :)
> But the idea stands that if we can set the head, let's do it.
> The previous head is not important enough to die on.
Yes, I didn't mean to suggest aborting the fetch. I just wanted to
make sure we _react_ correctly to a failing call to read a symref,
possibly because the refs/remotes/$there/HEAD (1) does not exist, or
(2) is not a symbolic ref but signals that the other side has its
HEAD detached. Treating any failure to read a symref as if the
symref does not exist would miss the latter case.
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref
2024-11-15 5:50 ` Junio C Hamano
2024-11-15 22:18 ` Bence Ferdinandy
@ 2024-11-17 23:39 ` Bence Ferdinandy
2024-11-18 0:39 ` Junio C Hamano
2024-11-18 7:22 ` Patrick Steinhardt
1 sibling, 2 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-17 23:39 UTC (permalink / raw)
To: Junio C Hamano, Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau
>
>> } + if (referent) + refs_read_symbolic_ref(refs, ref,
>> referent);
So I've been working on detecting a detached remote/HEAD, and it seems that
"refs_read_symbolic_ref" behaves differently for the files and the reftables
backend. These are the exit codes in the various states:
reftables files
detached -1 1
doesn't exist -1 -1
I would assume this is a bug in reftables? At least the behaviour of files is
more useful for this case ...
This now works fine with the files backend:
if (referent && refs_read_symbolic_ref(refs, ref, referent) == 1) {
struct object_id oid;
refs_read_ref(refs, ref, &oid);
strbuf_addstr(referent, oid_to_hex(&oid));
ret = -1;
}
And 4/8 can now also detect being detached, by checking the return value using
the test you suggested, but this fails for reftables. Just in case it might be
something about the test not being correct:
test_expect_success 'set-head --auto to update a non symbolic ref' '
(
cd test &&
git update-ref --no-deref -d refs/remotes/origin/HEAD &&
git update-ref refs/remotes/origin/HEAD HEAD &&
HEAD=$(git log --pretty="%H") &&
git remote set-head --auto origin >output &&
echo "${SQ}origin/HEAD${SQ} was detached at ${SQ}${HEAD}${SQ} and now points to ${SQ}main${SQ}" >expect &&
test_cmp expect output
)
'
Should I fix the reftables problem or should I mark the above test as a known
breakage? Can that be done for just reftables somehow? The only problem
reftables suffers this way is to incorrectly report "creation" instead of
correctly saying it was a detached head.
> We could write the whole thing as a single "do these and leave as
> soon as we see any failure" ||-cascade,
>
> if (!(transaction = ref_store_transaction_begin(refs, &err)) ||
>
> ref_transaction_update(transaction, ref, NULL, NULL,
> target, NULL, REF_NO_DEREF,
> logmsg, &err) ||
>
> ref_transaction_prepare(transaction, &err)) ||
>
> (referent
> ? refs_read_symbolic_ref(refs, ref, referent)
> : 0) ||
>
> ref_transaction_commit(transaction, &err)) {
> if (!err.len)
> ... stuff default error message to err ...;
> ret = error("%s", err.buf);
> }
>
> which may not necessarily easier to follow (and in fact it is rather
> ugly), but at least, the resulting code does not have to special
> case the "optionally peek into the symref" step.
I realised I misread the above snippet the last time I looked at it, but at
a first glance that would make 6/8 painful.
Thanks,
Bence
--
bence.ferdinandy.com
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref
2024-11-17 23:39 ` Bence Ferdinandy
@ 2024-11-18 0:39 ` Junio C Hamano
2024-11-18 7:22 ` Patrick Steinhardt
1 sibling, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-11-18 0:39 UTC (permalink / raw)
To: Bence Ferdinandy, Patrick Steinhardt
Cc: Bence Ferdinandy, git, phillip.wood, René Scharfe,
Johannes Schindelin, karthik.188, Taylor Blau
"Bence Ferdinandy" <ferdinandy.bence@ttk.elte.hu> writes:
>>
>>> } + if (referent) + refs_read_symbolic_ref(refs, ref,
>>> referent);
>
>
> So I've been working on detecting a detached remote/HEAD, and it seems that
> "refs_read_symbolic_ref" behaves differently for the files and the reftables
> backend. These are the exit codes in the various states:
>
>
> reftables files
> detached -1 1
> doesn't exist -1 -1
>
> I would assume this is a bug in reftables? At least the behaviour of files is
> more useful for this case ...
Interesting. Patrick, comments?
> This now works fine with the files backend:
>
> if (referent && refs_read_symbolic_ref(refs, ref, referent) == 1) {
> struct object_id oid;
> refs_read_ref(refs, ref, &oid);
> strbuf_addstr(referent, oid_to_hex(&oid));
> ret = -1;
> }
>
> And 4/8 can now also detect being detached, by checking the return value using
> the test you suggested, but this fails for reftables. Just in case it might be
> something about the test not being correct:
>
>
> test_expect_success 'set-head --auto to update a non symbolic ref' '
> (
> cd test &&
> git update-ref --no-deref -d refs/remotes/origin/HEAD &&
> git update-ref refs/remotes/origin/HEAD HEAD &&
> HEAD=$(git log --pretty="%H") &&
> git remote set-head --auto origin >output &&
> echo "${SQ}origin/HEAD${SQ} was detached at ${SQ}${HEAD}${SQ} and now points to ${SQ}main${SQ}" >expect &&
> test_cmp expect output
> )
> '
>
> Should I fix the reftables problem or should I mark the above test as a known
> breakage? Can that be done for just reftables somehow? The only problem
> reftables suffers this way is to incorrectly report "creation" instead of
> correctly saying it was a detached head.
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref
2024-11-17 23:39 ` Bence Ferdinandy
2024-11-18 0:39 ` Junio C Hamano
@ 2024-11-18 7:22 ` Patrick Steinhardt
2024-11-18 8:08 ` Bence Ferdinandy
1 sibling, 1 reply; 258+ messages in thread
From: Patrick Steinhardt @ 2024-11-18 7:22 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: Junio C Hamano, Bence Ferdinandy, git, phillip.wood,
René Scharfe, Johannes Schindelin, karthik.188, Taylor Blau
On Mon, Nov 18, 2024 at 12:39:43AM +0100, Bence Ferdinandy wrote:
> >
> >> } + if (referent) + refs_read_symbolic_ref(refs, ref,
> >> referent);
>
>
> So I've been working on detecting a detached remote/HEAD, and it seems that
> "refs_read_symbolic_ref" behaves differently for the files and the reftables
> backend. These are the exit codes in the various states:
>
>
> reftables files
> detached -1 1
> doesn't exist -1 -1
>
> I would assume this is a bug in reftables? At least the behaviour of files is
> more useful for this case ...
>
> This now works fine with the files backend:
>
> if (referent && refs_read_symbolic_ref(refs, ref, referent) == 1) {
> struct object_id oid;
> refs_read_ref(refs, ref, &oid);
> strbuf_addstr(referent, oid_to_hex(&oid));
> ret = -1;
> }
>
> And 4/8 can now also detect being detached, by checking the return value using
> the test you suggested, but this fails for reftables. Just in case it might be
> something about the test not being correct:
So from what I understand you try to execute `refs_read_symbolic_ref()`
on a non-symbolic-ref, and your expectation is:
- It returns -1 when reading the ref has failed.
- It returns 0 when reading the ref was successful and it was a
symref.
- It retuns 1 when reading the ref was successful, but it was a
regular ref.
This behaviour isn't documented anywhere, so I wouldn't declare it a bug
in the reftable backend. But what is a bug is that the two backends
behave differently, and that should be fixed indeed.
I couldn't find any callsites of `refs_read_symbolic_ref()` where we
rely on the current behaviour of either of the backends. We do have a
check whether `refs_read_symbolic_ref()` returns negative in "refs.c" in
`migrate_one_ref()`, but that one should be mostly fine given that we
check for the type of the ref beforehand. "Mostly" though because it can
happen that we race with another writer that happened to convert the ref
we are about to migrate from a symbolic ref into a normal ref. Unlikely,
but it can happen in theory.
I think it's an easy mistake to make to check for a negative return
code. So maybe we should adapt both backends to return -1 for generic
failures and -2 in case the ref is a regular ref?
Patrick
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref
2024-11-18 7:22 ` Patrick Steinhardt
@ 2024-11-18 8:08 ` Bence Ferdinandy
2024-11-18 8:24 ` Patrick Steinhardt
0 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-18 8:08 UTC (permalink / raw)
To: Patrick Steinhardt, Bence Ferdinandy
Cc: Junio C Hamano, git, phillip.wood, René Scharfe,
Johannes Schindelin, karthik.188, Taylor Blau
On Mon Nov 18, 2024 at 08:22, Patrick Steinhardt <ps@pks.im> wrote:
>
> So from what I understand you try to execute `refs_read_symbolic_ref()`
> on a non-symbolic-ref, and your expectation is:
>
> - It returns -1 when reading the ref has failed.
>
> - It returns 0 when reading the ref was successful and it was a
> symref.
>
> - It retuns 1 when reading the ref was successful, but it was a
> regular ref.
Well, the other way around (it's how files backend does it), but I guess that
is irrelevant at this point.
>
> This behaviour isn't documented anywhere, so I wouldn't declare it a bug
> in the reftable backend. But what is a bug is that the two backends
> behave differently, and that should be fixed indeed.
>
> I couldn't find any callsites of `refs_read_symbolic_ref()` where we
> rely on the current behaviour of either of the backends. We do have a
> check whether `refs_read_symbolic_ref()` returns negative in "refs.c" in
> `migrate_one_ref()`, but that one should be mostly fine given that we
> check for the type of the ref beforehand. "Mostly" though because it can
> happen that we race with another writer that happened to convert the ref
> we are about to migrate from a symbolic ref into a normal ref. Unlikely,
> but it can happen in theory.
>
> I think it's an easy mistake to make to check for a negative return
> code. So maybe we should adapt both backends to return -1 for generic
> failures and -2 in case the ref is a regular ref?
I've been wondering about this when writing other parts of the series and now
is a good a time as any to ask: I've already seen this pattern of returning
various negative integers as error codes, but never quite got the logic behind
it. Why not just return the same numbers but positive?
Anyhow, the proposed solution sounds good and as far as I see how things are
done in the code. I guess if I want the series to land I should just fix that
as well, there are already a couple of not-entirely-related fixes in there :)
Two questions about that:
- what would be the ideal place to document this behaviour? In refs.c with
`refs_read_symbolic_ref` or with the `struct ref_storage_be` in
refs/refs-internal.h?
- should I look into adding specific tests for this? Since the rest of the
series will depend on this behaviour it will be implicit tested anyway, so
I don't particularly think it would be necessary, but I don't know what the
general approach is.
Thanks,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref
2024-11-18 8:08 ` Bence Ferdinandy
@ 2024-11-18 8:24 ` Patrick Steinhardt
2024-11-18 11:37 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Patrick Steinhardt @ 2024-11-18 8:24 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: Bence Ferdinandy, Junio C Hamano, git, phillip.wood,
René Scharfe, Johannes Schindelin, karthik.188, Taylor Blau
On Mon, Nov 18, 2024 at 09:08:13AM +0100, Bence Ferdinandy wrote:
> On Mon Nov 18, 2024 at 08:22, Patrick Steinhardt <ps@pks.im> wrote:
> > This behaviour isn't documented anywhere, so I wouldn't declare it a bug
> > in the reftable backend. But what is a bug is that the two backends
> > behave differently, and that should be fixed indeed.
> >
> > I couldn't find any callsites of `refs_read_symbolic_ref()` where we
> > rely on the current behaviour of either of the backends. We do have a
> > check whether `refs_read_symbolic_ref()` returns negative in "refs.c" in
> > `migrate_one_ref()`, but that one should be mostly fine given that we
> > check for the type of the ref beforehand. "Mostly" though because it can
> > happen that we race with another writer that happened to convert the ref
> > we are about to migrate from a symbolic ref into a normal ref. Unlikely,
> > but it can happen in theory.
> >
> > I think it's an easy mistake to make to check for a negative return
> > code. So maybe we should adapt both backends to return -1 for generic
> > failures and -2 in case the ref is a regular ref?
>
> I've been wondering about this when writing other parts of the series and now
> is a good a time as any to ask: I've already seen this pattern of returning
> various negative integers as error codes, but never quite got the logic behind
> it. Why not just return the same numbers but positive?
It's a matter of style, I guess. Many functions use the return value as
both an indicator for error and as the actual returned value. Think e.g.
function calls like open(3p), where a negative value indicates an error
and everything else is an actual file descriptor. This carries over into
our codebase for many functions, but we're not consistent.
> Anyhow, the proposed solution sounds good and as far as I see how things are
> done in the code. I guess if I want the series to land I should just fix that
> as well, there are already a couple of not-entirely-related fixes in there :)
>
> Two questions about that:
>
> - what would be the ideal place to document this behaviour? In refs.c with
> `refs_read_symbolic_ref` or with the `struct ref_storage_be` in
> refs/refs-internal.h?
I'd document this in "refs.h", where the user-facing function is
declared, and in "refs-internal.h", where the callback is defined.
> - should I look into adding specific tests for this? Since the rest of the
> series will depend on this behaviour it will be implicit tested anyway, so
> I don't particularly think it would be necessary, but I don't know what the
> general approach is.
I had a look and couldn't find another way to test the behaviour because
we use `refs_read_symbolic_ref()` sparingly, only. So I think it's okay
to implicitly test this, only.
Patrick
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref
2024-11-18 8:24 ` Patrick Steinhardt
@ 2024-11-18 11:37 ` Bence Ferdinandy
0 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-18 11:37 UTC (permalink / raw)
To: Patrick Steinhardt
Cc: Bence Ferdinandy, Junio C Hamano, git, phillip.wood,
René Scharfe, Johannes Schindelin, karthik.188, Taylor Blau
On Mon Nov 18, 2024 at 09:24, Patrick Steinhardt <ps@pks.im> wrote:
> On Mon, Nov 18, 2024 at 09:08:13AM +0100, Bence Ferdinandy wrote:
>> On Mon Nov 18, 2024 at 08:22, Patrick Steinhardt <ps@pks.im> wrote:
>> > This behaviour isn't documented anywhere, so I wouldn't declare it a bug
>> > in the reftable backend. But what is a bug is that the two backends
>> > behave differently, and that should be fixed indeed.
>> >
>> > I couldn't find any callsites of `refs_read_symbolic_ref()` where we
>> > rely on the current behaviour of either of the backends. We do have a
>> > check whether `refs_read_symbolic_ref()` returns negative in "refs.c" in
>> > `migrate_one_ref()`, but that one should be mostly fine given that we
>> > check for the type of the ref beforehand. "Mostly" though because it can
>> > happen that we race with another writer that happened to convert the ref
>> > we are about to migrate from a symbolic ref into a normal ref. Unlikely,
>> > but it can happen in theory.
>> >
>> > I think it's an easy mistake to make to check for a negative return
>> > code. So maybe we should adapt both backends to return -1 for generic
>> > failures and -2 in case the ref is a regular ref?
>>
>> I've been wondering about this when writing other parts of the series and now
>> is a good a time as any to ask: I've already seen this pattern of returning
>> various negative integers as error codes, but never quite got the logic behind
>> it. Why not just return the same numbers but positive?
>
> It's a matter of style, I guess. Many functions use the return value as
> both an indicator for error and as the actual returned value. Think e.g.
> function calls like open(3p), where a negative value indicates an error
> and everything else is an actual file descriptor. This carries over into
> our codebase for many functions, but we're not consistent.
>
>> Anyhow, the proposed solution sounds good and as far as I see how things are
>> done in the code. I guess if I want the series to land I should just fix that
>> as well, there are already a couple of not-entirely-related fixes in there :)
>>
>> Two questions about that:
>>
>> - what would be the ideal place to document this behaviour? In refs.c with
>> `refs_read_symbolic_ref` or with the `struct ref_storage_be` in
>> refs/refs-internal.h?
>
> I'd document this in "refs.h", where the user-facing function is
> declared, and in "refs-internal.h", where the callback is defined.
>
>> - should I look into adding specific tests for this? Since the rest of the
>> series will depend on this behaviour it will be implicit tested anyway, so
>> I don't particularly think it would be necessary, but I don't know what the
>> general approach is.
>
> I had a look and couldn't find another way to test the behaviour because
> we use `refs_read_symbolic_ref()` sparingly, only. So I think it's okay
> to implicitly test this, only.
Thanks, it was surprisingly easy to do, I'll do a touchup of the other patches
and will send a v13 today with the fix.
Best,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v12 3/8] remote set-head: refactor for readability
2024-10-23 15:36 ` [PATCH v12 0/8] set-head/fetch remote/HEAD updates Bence Ferdinandy
2024-10-23 15:36 ` [PATCH v12 1/8] t/t5505-remote: set default branch to main Bence Ferdinandy
2024-10-23 15:36 ` [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
@ 2024-10-23 15:36 ` Bence Ferdinandy
2024-11-15 5:50 ` Junio C Hamano
2024-10-23 15:36 ` [PATCH v12 4/8] remote set-head: better output for --auto Bence Ferdinandy
` (6 subsequent siblings)
9 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-23 15:36 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy,
ferdinandy.bence
Rename buf and buf2 to something more explanatory.
Instead of calling get_main_ref_store(the_repository) multiple times,
call it once and store in a new refs variable. Although this change
probably offers some performance benefits, the main purpose is to
shorten the line lengths of function calls using this variable.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v5: new patch (split from the next patch as a preparatory step)
v6: no change
v7: - change commit prefix to be more in line with project standards
v8: no change
v9: - further improve readability by renaming buf, and buf2 consistently
with how patch 6 was already done
v10: no change
v11: no change
v12: no change
builtin/remote.c | 23 ++++++++++++-----------
1 file changed, 12 insertions(+), 11 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 76670ddd8b..1d68c5b2ba 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1402,8 +1402,9 @@ static int show(int argc, const char **argv, const char *prefix)
static int set_head(int argc, const char **argv, const char *prefix)
{
int i, opt_a = 0, opt_d = 0, result = 0;
- struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
char *head_name = NULL;
+ struct ref_store *refs = get_main_ref_store(the_repository);
struct option options[] = {
OPT_BOOL('a', "auto", &opt_a,
@@ -1415,7 +1416,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options,
builtin_remote_sethead_usage, 0);
if (argc)
- strbuf_addf(&buf, "refs/remotes/%s/HEAD", argv[0]);
+ strbuf_addf(&b_head, "refs/remotes/%s/HEAD", argv[0]);
if (!opt_a && !opt_d && argc == 2) {
head_name = xstrdup(argv[1]);
@@ -1434,25 +1435,25 @@ static int set_head(int argc, const char **argv, const char *prefix)
head_name = xstrdup(states.heads.items[0].string);
free_remote_ref_states(&states);
} else if (opt_d && !opt_a && argc == 1) {
- if (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF))
- result |= error(_("Could not delete %s"), buf.buf);
+ if (refs_delete_ref(refs, NULL, b_head.buf, NULL, REF_NO_DEREF))
+ result |= error(_("Could not delete %s"), b_head.buf);
} else
usage_with_options(builtin_remote_sethead_usage, options);
if (head_name) {
- strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name);
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", argv[0], head_name);
/* make sure it's valid */
- if (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))
- result |= error(_("Not a valid ref: %s"), buf2.buf);
- else if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote set-head"))
- result |= error(_("Could not setup %s"), buf.buf);
+ if (!refs_ref_exists(refs, b_remote_head.buf))
+ result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
+ else if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, "remote set-head"))
+ result |= error(_("Could not setup %s"), b_head.buf);
else if (opt_a)
printf("%s/HEAD set to %s\n", argv[0], head_name);
free(head_name);
}
- strbuf_release(&buf);
- strbuf_release(&buf2);
+ strbuf_release(&b_head);
+ strbuf_release(&b_remote_head);
return result;
}
--
2.47.0.94.g1247fb88fd.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v12 3/8] remote set-head: refactor for readability
2024-10-23 15:36 ` [PATCH v12 3/8] remote set-head: refactor for readability Bence Ferdinandy
@ 2024-11-15 5:50 ` Junio C Hamano
0 siblings, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-11-15 5:50 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, ferdinandy.bence
Bence Ferdinandy <bence@ferdinandy.com> writes:
> Rename buf and buf2 to something more explanatory.
>
> Instead of calling get_main_ref_store(the_repository) multiple times,
> call it once and store in a new refs variable. Although this change
> probably offers some performance benefits, the main purpose is to
> shorten the line lengths of function calls using this variable.
Also two strbufs are renamed, which is in line with the objective of
the other change---to make the resulting code easier to follow.
Looking good.
> v9: - further improve readability by renaming buf, and buf2 consistently
> with how patch 6 was already done
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v12 4/8] remote set-head: better output for --auto
2024-10-23 15:36 ` [PATCH v12 0/8] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (2 preceding siblings ...)
2024-10-23 15:36 ` [PATCH v12 3/8] remote set-head: refactor for readability Bence Ferdinandy
@ 2024-10-23 15:36 ` Bence Ferdinandy
2024-11-15 5:50 ` Junio C Hamano
2024-10-23 15:36 ` [PATCH v12 5/8] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
` (5 subsequent siblings)
9 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-23 15:36 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy,
ferdinandy.bence
Currently, set-head --auto will print a message saying "remote/HEAD set
to branch", which implies something was changed.
Change the output of --auto, so the output actually reflects what was
done: a) set a previously unset HEAD, b) change HEAD because remote
changed or c) no updates. As a fourth output, if HEAD is changed from
a previous value that was not a remote branch, explicitly call attention
to this fact.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v1-v2:
was RFC in https://lore.kernel.org/git/20240910203835.2288291-1-bence@ferdinandy.com/
v3:
This patch was originally sent along when I thought set-head was
going to be invoked by fetch, but the discussion on the RFC
concluded that it should be not. This opened the possibility to make
it more explicit.
Note: although I feel both things the patch does are really just
cosmetic, an argument could be made for breaking it into two, one
for the no-op part and one for the --auto print update.
Was sent in:
https://lore.kernel.org/git/20240915221055.904107-1-bence@ferdinandy.com/
v4:
- changes are now handled atomically via the ref update transaction
- outputs have changed along the lines of Junio's suggestion
- minor refactor to set_head for improved legibility
v5: - the minor refactor has been split out into its own patch
v6: - fixed uninitialized prev_head
- fixed case of unusual previous target
- fixed a test that would have been actually broken at this patch
(the output was only correct with the later update to fetch)
- added 4 tests for the 4 output cases
v7: - change commit prefix to be more in line with project standards
- fixed tests to also work with the reftable backend
- renamed report function, fixed style issue with checking buf len
- fixed not releasing an strbuf
v8: no change
v9: - mark output strings in report_set_head_auto as translatable
- rename buf_prev to b_local_head for consistency
- use ${SQ} in tests instead of '\''
v10: no change
v11: no change
v12: no change
builtin/remote.c | 33 +++++++++++++++++++++++++++---
t/t5505-remote.sh | 51 ++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 80 insertions(+), 4 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 1d68c5b2ba..108f1271d3 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1399,10 +1399,35 @@ static int show(int argc, const char **argv, const char *prefix)
return result;
}
+static void report_set_head_auto(const char *remote, const char *head_name,
+ struct strbuf *b_local_head) {
+ struct strbuf buf_prefix = STRBUF_INIT;
+ const char *prev_head = NULL;
+
+ strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
+ skip_prefix(b_local_head->buf, buf_prefix.buf, &prev_head);
+
+ if (prev_head && !strcmp(prev_head, head_name))
+ printf(_("'%s/HEAD' is unchanged and points to '%s'\n"),
+ remote, head_name);
+ else if (prev_head)
+ printf(_("'%s/HEAD' has changed from '%s' and now points to '%s'\n"),
+ remote, prev_head, head_name);
+ else if (!b_local_head->len)
+ printf(_("'%s/HEAD' is now created and points to '%s'\n"),
+ remote, head_name);
+ else
+ printf(_("'%s/HEAD' used to point to '%s' "
+ "(which is not a remote branch), but now points to '%s'\n"),
+ remote, b_local_head->buf, head_name);
+ strbuf_release(&buf_prefix);
+}
+
static int set_head(int argc, const char **argv, const char *prefix)
{
int i, opt_a = 0, opt_d = 0, result = 0;
- struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
+ b_local_head = STRBUF_INIT;
char *head_name = NULL;
struct ref_store *refs = get_main_ref_store(the_repository);
@@ -1445,15 +1470,17 @@ static int set_head(int argc, const char **argv, const char *prefix)
/* make sure it's valid */
if (!refs_ref_exists(refs, b_remote_head.buf))
result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
- else if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, "remote set-head"))
+ else if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
+ "remote set-head", &b_local_head))
result |= error(_("Could not setup %s"), b_head.buf);
else if (opt_a)
- printf("%s/HEAD set to %s\n", argv[0], head_name);
+ report_set_head_auto(argv[0], head_name, &b_local_head);
free(head_name);
}
strbuf_release(&b_head);
strbuf_release(&b_remote_head);
+ strbuf_release(&b_local_head);
return result;
}
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 9b50276646..0ea86d51a4 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -432,12 +432,51 @@ test_expect_success 'set-head --auto' '
)
'
+test_expect_success 'set-head --auto detects creation' '
+ (
+ cd test &&
+ git symbolic-ref -d refs/remotes/origin/HEAD &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects no change' '
+ (
+ cd test &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects change' '
+ (
+ cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/ahead &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} has changed from ${SQ}ahead${SQ} and now points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects strange ref' '
+ (
+ cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD refs/heads/main &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} used to point to ${SQ}refs/heads/main${SQ} (which is not a remote branch), but now points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
(
cd test &&
git fetch two "refs/heads/*:refs/remotes/two/*" &&
git remote set-head --auto two >output 2>&1 &&
- echo "two/HEAD set to main" >expect &&
+ echo "${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
test_cmp expect output
)
'
@@ -456,6 +495,16 @@ test_expect_success 'set-head explicit' '
)
'
+test_expect_success 'set-head --auto reports change' '
+ (
+ cd test &&
+ git remote set-head origin side2 &&
+ git remote set-head --auto origin >output 2>&1 &&
+ echo "${SQ}origin/HEAD${SQ} has changed from ${SQ}side2${SQ} and now points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
cat >test/expect <<EOF
Pruning origin
URL: $(pwd)/one
--
2.47.0.94.g1247fb88fd.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v12 4/8] remote set-head: better output for --auto
2024-10-23 15:36 ` [PATCH v12 4/8] remote set-head: better output for --auto Bence Ferdinandy
@ 2024-11-15 5:50 ` Junio C Hamano
2024-11-15 22:49 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Junio C Hamano @ 2024-11-15 5:50 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, ferdinandy.bence
Bence Ferdinandy <bence@ferdinandy.com> writes:
> Currently, set-head --auto will print a message saying "remote/HEAD set
> to branch", which implies something was changed.
>
> Change the output of --auto, so the output actually reflects what was
> done: a) set a previously unset HEAD, b) change HEAD because remote
> changed or c) no updates. As a fourth output, if HEAD is changed from
> a previous value that was not a remote branch, explicitly call attention
> to this fact.
OK. That's sensible.
There is a slight variant of the fourth case. HEAD may have been a
symbolic ref that pointed at an unexpected place (which you
addressed), or HEAD may have been a non-symbolic ref (which the new
code would mistakenly say "HEAD is now created", if I am reading the
patch correctly).
> diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
> index 9b50276646..0ea86d51a4 100755
> --- a/t/t5505-remote.sh
> +++ b/t/t5505-remote.sh
> @@ -432,12 +432,51 @@ test_expect_success 'set-head --auto' '
> )
> '
>
> +test_expect_success 'set-head --auto detects creation' '
> + (
> + cd test &&
> + git symbolic-ref -d refs/remotes/origin/HEAD &&
Are we sure refs/remotes/origin/HEAD exists at this point in the
test, regardless of which earlier tests were skipped or failed? If
not, perhaps
git update-ref --no-deref -d refs/remotes/origin/HEAD &&
is a better alternative.
> + git remote set-head --auto origin >output &&
> + echo "${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
> + test_cmp expect output
> + )
> +'
Here, we could insert another one:
test_expect_success 'set-head --auto to update a non symbolic ref' '
(
cd test &&
git update-ref --no-deref -d refs/remotes/origin/HEAD &&
git update-ref refs/remotes/origin/HEAD HEAD &&
git remote set-head --auto origin >output &&
I'd imagine "output" should at least say that we are setting up a
symref origin/HEAD to point at some ref the --auto option figured
out, and if we wanted to report its previous state, it was a non
symbolic ref that pointed at some commit. In any case,
echo "${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
is not what we want to see here, I suspect.
Can we detect the case where we overwrite a non symref with a symref
without going back to step 2/8 and doing a major surgery?
test_cmp expect output
)
'
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v12 4/8] remote set-head: better output for --auto
2024-11-15 5:50 ` Junio C Hamano
@ 2024-11-15 22:49 ` Bence Ferdinandy
2024-11-15 23:13 ` Bence Ferdinandy
2024-11-16 0:15 ` Junio C Hamano
0 siblings, 2 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-15 22:49 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, ferdinandy.bence
On Fri Nov 15, 2024 at 06:50, Junio C Hamano <gitster@pobox.com> wrote:
> Bence Ferdinandy <bence@ferdinandy.com> writes:
>
>> Currently, set-head --auto will print a message saying "remote/HEAD set
>> to branch", which implies something was changed.
>>
>> Change the output of --auto, so the output actually reflects what was
>> done: a) set a previously unset HEAD, b) change HEAD because remote
>> changed or c) no updates. As a fourth output, if HEAD is changed from
>> a previous value that was not a remote branch, explicitly call attention
>> to this fact.
>
> OK. That's sensible.
>
> There is a slight variant of the fourth case. HEAD may have been a
> symbolic ref that pointed at an unexpected place (which you
> addressed), or HEAD may have been a non-symbolic ref (which the new
> code would mistakenly say "HEAD is now created", if I am reading the
> patch correctly).
Good point, and yes, that is what happens. (Although I'm not quite sure how
valid that state is where a remote's HEAD is not a branch).
>
>> diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
>> index 9b50276646..0ea86d51a4 100755
>> --- a/t/t5505-remote.sh
>> +++ b/t/t5505-remote.sh
>> @@ -432,12 +432,51 @@ test_expect_success 'set-head --auto' '
>> )
>> '
>>
>> +test_expect_success 'set-head --auto detects creation' '
>> + (
>> + cd test &&
>> + git symbolic-ref -d refs/remotes/origin/HEAD &&
>
> Are we sure refs/remotes/origin/HEAD exists at this point in the
> test, regardless of which earlier tests were skipped or failed? If
> not, perhaps
>
> git update-ref --no-deref -d refs/remotes/origin/HEAD &&
>
> is a better alternative.
Ack.
>
>> + git remote set-head --auto origin >output &&
>> + echo "${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
>> + test_cmp expect output
>> + )
>> +'
>
> Here, we could insert another one:
>
> test_expect_success 'set-head --auto to update a non symbolic ref' '
> (
> cd test &&
> git update-ref --no-deref -d refs/remotes/origin/HEAD &&
> git update-ref refs/remotes/origin/HEAD HEAD &&
> git remote set-head --auto origin >output &&
>
> I'd imagine "output" should at least say that we are setting up a
> symref origin/HEAD to point at some ref the --auto option figured
> out, and if we wanted to report its previous state, it was a non
> symbolic ref that pointed at some commit. In any case,
>
> echo "${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
>
> is not what we want to see here, I suspect.
>
> Can we detect the case where we overwrite a non symref with a symref
> without going back to step 2/8 and doing a major surgery?
>
> test_cmp expect output
> )
> '
I agree, adding this makes sense. And this also takes us back to the question
of what we should do in 2/8 when refs_read_symbolic_ref exits with 1. I now
tested the behaviour and if origin/HEAD is gibberish, git already dies before
with
error: cannot lock ref 'refs/remotes/origin/HEAD': unable to resolve reference 'refs/remotes/origin/HEAD': reference broken
so refs_read_symbolic_ref -> 1 only happens if there's a valid non-symbolic ref
in origin/HEAD. So maybe if we put "Not a symbolic reference." in the referent
(which should be an invalid symref), the caller could check for that and then
should be able to distinguish this special case?
Thanks,
Bence
--
bence.ferdinandy.com
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v12 4/8] remote set-head: better output for --auto
2024-11-15 22:49 ` Bence Ferdinandy
@ 2024-11-15 23:13 ` Bence Ferdinandy
2024-11-16 0:22 ` Junio C Hamano
2024-11-16 0:15 ` Junio C Hamano
1 sibling, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-15 23:13 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, ferdinandy.bence
On Fri Nov 15, 2024 at 23:49, Bence Ferdinandy <bence@ferdinandy.com> wrote:
>
> On Fri Nov 15, 2024 at 06:50, Junio C Hamano <gitster@pobox.com> wrote:
>> Bence Ferdinandy <bence@ferdinandy.com> writes:
>>
>>> Currently, set-head --auto will print a message saying "remote/HEAD set
>>> to branch", which implies something was changed.
>>>
>>> Change the output of --auto, so the output actually reflects what was
>>> done: a) set a previously unset HEAD, b) change HEAD because remote
>>> changed or c) no updates. As a fourth output, if HEAD is changed from
>>> a previous value that was not a remote branch, explicitly call attention
>>> to this fact.
>>
>> OK. That's sensible.
>>
>> There is a slight variant of the fourth case. HEAD may have been a
>> symbolic ref that pointed at an unexpected place (which you
>> addressed), or HEAD may have been a non-symbolic ref (which the new
>> code would mistakenly say "HEAD is now created", if I am reading the
>> patch correctly).
>
> Good point, and yes, that is what happens. (Although I'm not quite sure how
> valid that state is where a remote's HEAD is not a branch).
>
>>
>>> diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
>>> index 9b50276646..0ea86d51a4 100755
>>> --- a/t/t5505-remote.sh
>>> +++ b/t/t5505-remote.sh
>>> @@ -432,12 +432,51 @@ test_expect_success 'set-head --auto' '
>>> )
>>> '
>>>
>>> +test_expect_success 'set-head --auto detects creation' '
>>> + (
>>> + cd test &&
>>> + git symbolic-ref -d refs/remotes/origin/HEAD &&
>>
>> Are we sure refs/remotes/origin/HEAD exists at this point in the
>> test, regardless of which earlier tests were skipped or failed? If
>> not, perhaps
>>
>> git update-ref --no-deref -d refs/remotes/origin/HEAD &&
>>
>> is a better alternative.
>
> Ack.
>
>>
>>> + git remote set-head --auto origin >output &&
>>> + echo "${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
>>> + test_cmp expect output
>>> + )
>>> +'
>>
>> Here, we could insert another one:
>>
>> test_expect_success 'set-head --auto to update a non symbolic ref' '
>> (
>> cd test &&
>> git update-ref --no-deref -d refs/remotes/origin/HEAD &&
>> git update-ref refs/remotes/origin/HEAD HEAD &&
>> git remote set-head --auto origin >output &&
>>
>> I'd imagine "output" should at least say that we are setting up a
>> symref origin/HEAD to point at some ref the --auto option figured
>> out, and if we wanted to report its previous state, it was a non
>> symbolic ref that pointed at some commit. In any case,
>>
>> echo "${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
>>
>> is not what we want to see here, I suspect.
>>
>> Can we detect the case where we overwrite a non symref with a symref
>> without going back to step 2/8 and doing a major surgery?
>>
>> test_cmp expect output
>> )
>> '
>
> I agree, adding this makes sense. And this also takes us back to the question
> of what we should do in 2/8 when refs_read_symbolic_ref exits with 1. I now
> tested the behaviour and if origin/HEAD is gibberish, git already dies before
> with
>
> error: cannot lock ref 'refs/remotes/origin/HEAD': unable to resolve reference 'refs/remotes/origin/HEAD': reference broken
>
> so refs_read_symbolic_ref -> 1 only happens if there's a valid non-symbolic ref
> in origin/HEAD. So maybe if we put "Not a symbolic reference." in the referent
> (which should be an invalid symref), the caller could check for that and then
> should be able to distinguish this special case?
On second thought, it would maybe make even more sense to get the reference
hash and put that into referent. In that case the output could still be
"'%s/HEAD' has changed from '%s' and now points to '%s'\n"
but with a non-symref after from.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v12 4/8] remote set-head: better output for --auto
2024-11-15 23:13 ` Bence Ferdinandy
@ 2024-11-16 0:22 ` Junio C Hamano
0 siblings, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-11-16 0:22 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, ferdinandy.bence
"Bence Ferdinandy" <bence@ferdinandy.com> writes:
>> so refs_read_symbolic_ref -> 1 only happens if there's a valid non-symbolic ref
>> in origin/HEAD. So maybe if we put "Not a symbolic reference." in the referent
>> (which should be an invalid symref), the caller could check for that and then
>> should be able to distinguish this special case?
>
> On second thought, it would maybe make even more sense to get the reference
> hash and put that into referent. In that case the output could still be
>
> "'%s/HEAD' has changed from '%s' and now points to '%s'\n"
>
> but with a non-symref after from.
The output may look like the above, but people would certainly wish
to clarify and/or translate "from '%s'" part. And to allow that, we
may need to signal a bit more explicitly that we saw a non-symref
HEAD there so that we can tell between a detached HEAD and a
confusingly named branch whose name is 40-hex.
Otherwise, one who tries to update that "has changed from '%s'"
message to clarify between "pointed at branch '%s'" and "pointed at
commit '%s'" (or translate into a language where the distinction
matters, perhaps because branch and commit have different gender
there that affects conjugation for "has changed from" part, or
something hand-wavy) have insufficient information to work with.
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v12 4/8] remote set-head: better output for --auto
2024-11-15 22:49 ` Bence Ferdinandy
2024-11-15 23:13 ` Bence Ferdinandy
@ 2024-11-16 0:15 ` Junio C Hamano
2024-11-16 14:43 ` Bence Ferdinandy
1 sibling, 1 reply; 258+ messages in thread
From: Junio C Hamano @ 2024-11-16 0:15 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, ferdinandy.bence
"Bence Ferdinandy" <bence@ferdinandy.com> writes:
> Good point, and yes, that is what happens. (Although I'm not quite sure how
> valid that state is where a remote's HEAD is not a branch).
Not often when you cloned from a publishing repository, but if you
cloned from a repository for your own work with an worktree, the
HEAD at such an origin repository may have been detached, so it
would be sensible (I think "git clone" historically guesses over
eagerly to hide the detached HEAD state of the other side, though)
if the clone's remote-tracking HEAD reflects it, I would think.
> ... So maybe if we put "Not a symbolic reference." in the referent
> (which should be an invalid symref), the caller could check for that and then
> should be able to distinguish this special case?
Yuck.
Are we limited by the narrow interface that only passes "referent",
or are you in the position that allows you to extend the interface
to "do it right"?
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v12 4/8] remote set-head: better output for --auto
2024-11-16 0:15 ` Junio C Hamano
@ 2024-11-16 14:43 ` Bence Ferdinandy
0 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-16 14:43 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, ferdinandy.bence
On Sat Nov 16, 2024 at 01:15, Junio C Hamano <gitster@pobox.com> wrote:
> "Bence Ferdinandy" <bence@ferdinandy.com> writes:
>
>> Good point, and yes, that is what happens. (Although I'm not quite sure how
>> valid that state is where a remote's HEAD is not a branch).
>
> Not often when you cloned from a publishing repository, but if you
> cloned from a repository for your own work with an worktree, the
> HEAD at such an origin repository may have been detached, so it
> would be sensible (I think "git clone" historically guesses over
> eagerly to hide the detached HEAD state of the other side, though)
> if the clone's remote-tracking HEAD reflects it, I would think.
>
>> ... So maybe if we put "Not a symbolic reference." in the referent
>> (which should be an invalid symref), the caller could check for that and then
>> should be able to distinguish this special case?
>
> Yuck.
>
> Are we limited by the narrow interface that only passes "referent",
> or are you in the position that allows you to extend the interface
> to "do it right"?
How about we do the following if we can't read a symref:
1) remember to return a new exit code for refs_update_symref_extended (I guess 2),
but continue on with updating HEAD
2) read the non-symref ref into "referent" (I think this should not be able to
fail since, otherwise locking HEAD will already fail earlier)
this way the caller can check the exit code, see that it's 2, so not entirely
a success, but HEAD has been updated and can report things accordingly.
Does that sounds more reasonable?
Thanks,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v12 5/8] refs: add TRANSACTION_CREATE_EXISTS error
2024-10-23 15:36 ` [PATCH v12 0/8] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (3 preceding siblings ...)
2024-10-23 15:36 ` [PATCH v12 4/8] remote set-head: better output for --auto Bence Ferdinandy
@ 2024-10-23 15:36 ` Bence Ferdinandy
2024-10-23 15:36 ` [PATCH v12 6/8] refs: add create_only option to refs_update_symref_extended Bence Ferdinandy
` (4 subsequent siblings)
9 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-23 15:36 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy,
ferdinandy.bence
Currently there is only one special error for transaction, for when
there is a naming conflict, all other errors are dumped under a generic
error. Add a new special error case for when the caller requests the
reference to be updated only when it does not yet exist and the
reference actually does exist.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: no change
v6: no change
v7: - change commit prefix to be more in line with project standards
- changed error checking to Karthik's suggestion
v8: no change
v9: - no change
v10: no change
v11: no change
v12: no change
refs.h | 4 +++-
refs/files-backend.c | 24 ++++++++++++++++--------
refs/reftable-backend.c | 6 ++++--
3 files changed, 23 insertions(+), 11 deletions(-)
diff --git a/refs.h b/refs.h
index 259191a485..a5bc25442b 100644
--- a/refs.h
+++ b/refs.h
@@ -762,8 +762,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,
/* Naming conflict (for example, the ref names A and A/B conflict). */
#define TRANSACTION_NAME_CONFLICT -1
+/* When only creation was requested, but the ref already exists. */
+#define TRANSACTION_CREATE_EXISTS -2
/* All other errors. */
-#define TRANSACTION_GENERIC_ERROR -2
+#define TRANSACTION_GENERIC_ERROR -3
/*
* Perform the preparatory stages of committing `transaction`. Acquire
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 0824c0b8a9..e743ec44b5 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2502,14 +2502,18 @@ static int split_symref_update(struct ref_update *update,
static int check_old_oid(struct ref_update *update, struct object_id *oid,
struct strbuf *err)
{
+ int ret = TRANSACTION_GENERIC_ERROR;
+
if (!(update->flags & REF_HAVE_OLD) ||
oideq(oid, &update->old_oid))
return 0;
- if (is_null_oid(&update->old_oid))
+ if (is_null_oid(&update->old_oid)) {
strbuf_addf(err, "cannot lock ref '%s': "
"reference already exists",
ref_update_original_update_refname(update));
+ ret = TRANSACTION_CREATE_EXISTS;
+ }
else if (is_null_oid(oid))
strbuf_addf(err, "cannot lock ref '%s': "
"reference is missing but expected %s",
@@ -2522,7 +2526,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,
oid_to_hex(oid),
oid_to_hex(&update->old_oid));
- return -1;
+ return ret;
}
/*
@@ -2602,9 +2606,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
- } else if (check_old_oid(update, &lock->old_oid, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto out;
+ } else {
+ ret = check_old_oid(update, &lock->old_oid, err);
+ if (ret) {
+ goto out;
+ }
}
} else {
/*
@@ -2635,9 +2641,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,
update->old_target);
ret = TRANSACTION_GENERIC_ERROR;
goto out;
- } else if (check_old_oid(update, &lock->old_oid, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto out;
+ } else {
+ ret = check_old_oid(update, &lock->old_oid, err);
+ if (ret) {
+ goto out;
+ }
}
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 3c6107c7ce..b3b5ce77dd 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1206,10 +1206,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
goto done;
}
} else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) {
- if (is_null_oid(&u->old_oid))
+ ret = TRANSACTION_NAME_CONFLICT;
+ if (is_null_oid(&u->old_oid)) {
strbuf_addf(err, _("cannot lock ref '%s': "
"reference already exists"),
ref_update_original_update_refname(u));
+ ret = TRANSACTION_CREATE_EXISTS;
+ }
else if (is_null_oid(¤t_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
"reference is missing but expected %s"),
@@ -1221,7 +1224,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
ref_update_original_update_refname(u),
oid_to_hex(¤t_oid),
oid_to_hex(&u->old_oid));
- ret = -1;
goto done;
}
--
2.47.0.94.g1247fb88fd.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v12 6/8] refs: add create_only option to refs_update_symref_extended
2024-10-23 15:36 ` [PATCH v12 0/8] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (4 preceding siblings ...)
2024-10-23 15:36 ` [PATCH v12 5/8] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
@ 2024-10-23 15:36 ` Bence Ferdinandy
2024-10-23 15:36 ` [PATCH v12 7/8] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
` (3 subsequent siblings)
9 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-23 15:36 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy,
ferdinandy.bence
Allow the caller to specify that it only wants to update the symref if
it does not already exist. Silently ignore the error from the
transaction API if the symref already exists.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: no change
v6: - switched from bool to int for create_only
- refactored logic in refs_update_symref with goto as layed out by
Junio
v7: - change commit prefix to be more in line with project standards
- refactored code to accommodate changes in the first patch, but
otherwise no change
v8: no change
v9: - no change (except for following through the
refs_update_symref_extended refactoring)
v10: no change
v11: no change
v12: no change
builtin/remote.c | 2 +-
refs.c | 32 +++++++++++++++++++++++---------
refs.h | 2 +-
3 files changed, 25 insertions(+), 11 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 108f1271d3..b1eba75a2b 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1471,7 +1471,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
if (!refs_ref_exists(refs, b_remote_head.buf))
result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
else if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
- "remote set-head", &b_local_head))
+ "remote set-head", &b_local_head, 0))
result |= error(_("Could not setup %s"), b_head.buf);
else if (opt_a)
report_set_head_auto(argv[0], head_name, &b_local_head);
diff --git a/refs.c b/refs.c
index 24a4172cd2..093ee11ab0 100644
--- a/refs.c
+++ b/refs.c
@@ -2116,31 +2116,45 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
int refs_update_symref(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg)
{
- return refs_update_symref_extended(refs, ref, target, logmsg, NULL);
+ return refs_update_symref_extended(refs, ref, target, logmsg, NULL, 0);
}
int refs_update_symref_extended(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg,
- struct strbuf *referent)
+ struct strbuf *referent, int create_only)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
- int ret = 0;
+ int ret = 0, prepret = 0;
transaction = ref_store_transaction_begin(refs, &err);
- if (!transaction ||
- ref_transaction_update(transaction, ref, NULL, NULL,
- target, NULL, REF_NO_DEREF,
- logmsg, &err) ||
- ref_transaction_prepare(transaction, &err)) {
+ if (!transaction) {
+ error_return:
ret = error("%s", err.buf);
goto cleanup;
}
+ if (create_only) {
+ if (ref_transaction_create(transaction, ref, NULL, target,
+ REF_NO_DEREF, logmsg, &err))
+ goto error_return;
+ prepret = ref_transaction_prepare(transaction, &err);
+ if (prepret && prepret != TRANSACTION_CREATE_EXISTS)
+ goto error_return;
+ } else {
+ if (ref_transaction_update(transaction, ref, NULL, NULL,
+ target, NULL, REF_NO_DEREF,
+ logmsg, &err) ||
+ ref_transaction_prepare(transaction, &err))
+ goto error_return;
+ }
+
if (referent)
refs_read_symbolic_ref(refs, ref, referent);
+ if (prepret == TRANSACTION_CREATE_EXISTS)
+ goto cleanup;
if (ref_transaction_commit(transaction, &err))
- ret = error("%s", err.buf);
+ goto error_return;
cleanup:
strbuf_release(&err);
diff --git a/refs.h b/refs.h
index a5bc25442b..458582ebcf 100644
--- a/refs.h
+++ b/refs.h
@@ -575,7 +575,7 @@ int refs_update_symref(struct ref_store *refs, const char *refname,
int refs_update_symref_extended(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg,
- struct strbuf *referent);
+ struct strbuf *referent, int create_only);
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
--
2.47.0.94.g1247fb88fd.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v12 7/8] fetch: set remote/HEAD if it does not exist
2024-10-23 15:36 ` [PATCH v12 0/8] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (5 preceding siblings ...)
2024-10-23 15:36 ` [PATCH v12 6/8] refs: add create_only option to refs_update_symref_extended Bence Ferdinandy
@ 2024-10-23 15:36 ` Bence Ferdinandy
2024-10-23 16:50 ` Kristoffer Haugsbakk
2024-10-23 15:36 ` [PATCH v12 8/8] fetch set_head: handle mirrored bare repositories Bence Ferdinandy
` (2 subsequent siblings)
9 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-23 15:36 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy,
ferdinandy.bence
If the user has remote/HEAD set already and it looks like it has changed
on the server, then print a message, otherwise set it if we can.
Silently pass if the user already has the same remote/HEAD set as
reported by the server or if we encounter any errors along the way.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v3: - does not rely on remote set-head anymore so it only authenticates
once
- uses the new REF_CREATE_ONLY to atomically check if the ref exists
and only write it if it doesn't
- in all other cases the maximum it does is print a warning
v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,
but updated transaction api to request a silent create only
- it now uses the atomic before_target to determine reporting
- refactored for legibility
v5: - instead of printing a not too useful message, it now fails
silently, this in line with the objective to only set up
remote/HEAD automatically if the right thing is trivial, for
everything else there is remote set-head
- fixed all failing tests
- added two new tests, one for checking if remote/HEAD is set to the
correct one, and one to test that we do not override remote/HEAD
if it has changed on the server from what we have locally
v6: - fixed style issues and unintended extra empty line
- updated function call with bool to int from previous patch's
change
- removed calls to error(...) inherited from builtin/remote.c so we
actually fail silently
- set the test for remote set-head --auto to the correct value here,
which was previously erronously set in the remote set-head patch
v7: - no change
v8: - changed logmsg in call to refs_update_symref from "remote
set-head" to "fetch"
v9: - follow through with refs_update_symref_extended
- fix test errors uncovered by the new patch
v10: no change
v11: fixed some memory leaks
v12: no change
builtin/fetch.c | 86 ++++++++++++
t/t4207-log-decoration-colors.sh | 3 +-
t/t5505-remote.sh | 21 ++-
t/t5510-fetch.sh | 229 ++++++++++++++++---------------
t/t5512-ls-remote.sh | 2 +
t/t5514-fetch-multiple.sh | 17 ++-
t/t5516-fetch-push.sh | 3 +-
t/t5527-fetch-odd-refs.sh | 3 +-
t/t7900-maintenance.sh | 3 +-
t/t9210-scalar.sh | 5 +-
t/t9211-scalar-clone.sh | 6 +-
t/t9902-completion.sh | 65 +++++++++
12 files changed, 319 insertions(+), 124 deletions(-)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index d9027e4dc9..cadfd2407a 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1578,6 +1578,84 @@ static int backfill_tags(struct display_state *display_state,
return retcode;
}
+static void report_set_head(const char *remote, const char *head_name,
+ struct strbuf *buf_prev) {
+ struct strbuf buf_prefix = STRBUF_INIT;
+ const char *prev_head = NULL;
+
+ strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
+ skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);
+
+ if (prev_head && strcmp(prev_head, head_name)) {
+ printf("'HEAD' at '%s' has changed from '%s' to '%s'\n",
+ remote, prev_head, head_name);
+ printf("Run 'git remote set-head %s %s' to follow the change.\n",
+ remote, head_name);
+ }
+ strbuf_release(&buf_prefix);
+}
+
+static const char *strip_refshead(const char *name){
+ skip_prefix(name, "refs/heads/", &name);
+ return name;
+}
+
+static int set_head(const struct ref *remote_refs)
+{
+ int result = 0;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
+ b_local_head = STRBUF_INIT;
+ const char *remote = gtransport->remote->name;
+ char *head_name = NULL;
+ struct ref *ref, *matches;
+ struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
+ struct refspec_item refspec = {
+ .force = 0,
+ .pattern = 1,
+ .src = (char *) "refs/heads/*",
+ .dst = (char *) "refs/heads/*",
+ };
+ struct string_list heads = STRING_LIST_INIT_DUP;
+ struct ref_store *refs = get_main_ref_store(the_repository);
+
+ get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
+ matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
+ fetch_map, 1);
+ for (ref = matches; ref; ref = ref->next) {
+ string_list_append(&heads, strip_refshead(ref->name));
+ }
+
+
+ if (!heads.nr)
+ result = 1;
+ else if (heads.nr > 1)
+ result = 1;
+ else
+ head_name = xstrdup(heads.items[0].string);
+ if (head_name) {
+ strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name);
+ /* make sure it's valid */
+ if (!refs_ref_exists(refs, b_remote_head.buf))
+ result = 1;
+ else if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
+ "fetch", &b_local_head, 1))
+ result = 1;
+ else
+ report_set_head(remote, head_name, &b_local_head);
+
+ free(head_name);
+ }
+
+ free_refs(fetch_map);
+ free_refs(matches);
+ string_list_clear(&heads, 0);
+ strbuf_release(&b_head);
+ strbuf_release(&b_local_head);
+ strbuf_release(&b_remote_head);
+ return result;
+}
+
static int do_fetch(struct transport *transport,
struct refspec *rs,
const struct fetch_config *config)
@@ -1647,6 +1725,8 @@ static int do_fetch(struct transport *transport,
"refs/tags/");
}
+ strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
+
if (must_list_refs) {
trace2_region_enter("fetch", "remote_refs", the_repository);
remote_refs = transport_get_remote_refs(transport,
@@ -1791,6 +1871,12 @@ static int do_fetch(struct transport *transport,
"you need to specify exactly one branch with the --set-upstream option"));
}
}
+ if (set_head(remote_refs))
+ ;
+ /*
+ * Way too many cases where this can go wrong
+ * so let's just fail silently for now.
+ */
cleanup:
if (retcode) {
diff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh
index 73ea9e5155..d55d22cb2f 100755
--- a/t/t4207-log-decoration-colors.sh
+++ b/t/t4207-log-decoration-colors.sh
@@ -59,7 +59,8 @@ ${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \
${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \
-${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1
+${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit}, \
+${c_reset}${c_remoteBranch}other/HEAD${c_reset}${c_commit})${c_reset} A1
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
${c_stash}refs/stash${c_reset}${c_commit})${c_reset} On main: Changes to A.t
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 0ea86d51a4..4990d00209 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -74,7 +74,7 @@ test_expect_success 'add another remote' '
cd test &&
git remote add -f second ../two &&
tokens_match "origin second" "$(git remote)" &&
- check_tracking_branch second main side another &&
+ check_tracking_branch second main side another HEAD &&
git for-each-ref "--format=%(refname)" refs/remotes |
sed -e "/^refs\/remotes\/origin\//d" \
-e "/^refs\/remotes\/second\//d" >actual &&
@@ -476,7 +476,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
cd test &&
git fetch two "refs/heads/*:refs/remotes/two/*" &&
git remote set-head --auto two >output 2>&1 &&
- echo "${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
+ echo "${SQ}two/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}" >expect &&
test_cmp expect output
)
'
@@ -764,8 +764,10 @@ test_expect_success 'reject --no-no-tags' '
'
cat >one/expect <<\EOF
+ apis/HEAD -> apis/main
apis/main
apis/side
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -783,11 +785,14 @@ test_expect_success 'update' '
'
cat >one/expect <<\EOF
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
+ manduca/HEAD -> manduca/main
manduca/main
manduca/side
+ megaloprepus/HEAD -> megaloprepus/main
megaloprepus/main
megaloprepus/side
EOF
@@ -795,7 +800,7 @@ EOF
test_expect_success 'update with arguments' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -827,10 +832,13 @@ test_expect_success 'update --prune' '
'
cat >one/expect <<-\EOF
+ apis/HEAD -> apis/main
apis/main
apis/side
+ manduca/HEAD -> manduca/main
manduca/main
manduca/side
+ megaloprepus/HEAD -> megaloprepus/main
megaloprepus/main
megaloprepus/side
EOF
@@ -838,7 +846,7 @@ EOF
test_expect_success 'update default' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -850,6 +858,7 @@ test_expect_success 'update default' '
'
cat >one/expect <<\EOF
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -858,7 +867,7 @@ EOF
test_expect_success 'update default (overridden, with funny whitespace)' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -872,7 +881,7 @@ test_expect_success 'update default (overridden, with funny whitespace)' '
test_expect_success 'update (with remotes.default defined)' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 0890b9f61c..dfc8d748ba 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -75,6 +75,30 @@ test_expect_success "fetch test for-merge" '
cut -f -2 .git/FETCH_HEAD >actual &&
test_cmp expected actual'
+test_expect_success "fetch test remote HEAD" '
+ cd "$D" &&
+ cd two &&
+ git fetch &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"'
+
+test_expect_success "fetch test remote HEAD change" '
+ cd "$D" &&
+ cd two &&
+ git switch -c other &&
+ git push -u origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git fetch &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"'
+
test_expect_success 'fetch --prune on its own works as expected' '
cd "$D" &&
git clone . prune &&
@@ -478,7 +502,6 @@ test_expect_success 'unbundle 1' '
test_must_fail git fetch "$D/bundle1" main:main
'
-
test_expect_success 'bundle 1 has only 3 files ' '
cd "$D" &&
test_bundle_object_count bundle1 3
@@ -819,7 +842,7 @@ test_expect_success 'fetch from multiple configured URLs in single remote' '
# configured prune tests
-set_config_tristate () {
+set_config_tristate() {
# var=$1 val=$2
case "$2" in
unset)
@@ -833,12 +856,12 @@ set_config_tristate () {
esac
}
-test_configured_prune () {
+test_configured_prune() {
test_configured_prune_type "$@" "name"
test_configured_prune_type "$@" "link"
}
-test_configured_prune_type () {
+test_configured_prune_type() {
fetch_prune=$1
remote_origin_prune=$2
fetch_prune_tags=$3
@@ -848,8 +871,7 @@ test_configured_prune_type () {
cmdline=$7
mode=$8
- if test -z "$cmdline_setup"
- then
+ if test -z "$cmdline_setup"; then
test_expect_success 'setup cmdline_setup variable for subsequent test' '
remote_url="file://$(git -C one config remote.origin.url)" &&
remote_fetch="$(git -C one config remote.origin.fetch)" &&
@@ -857,12 +879,10 @@ test_configured_prune_type () {
'
fi
- if test "$mode" = 'link'
- then
+ if test "$mode" = 'link'; then
new_cmdline=""
- if test "$cmdline" = ""
- then
+ if test "$cmdline" = ""; then
new_cmdline=$cmdline_setup
else
new_cmdline=$(perl -e '
@@ -873,10 +893,8 @@ test_configured_prune_type () {
fi
if test "$fetch_prune_tags" = 'true' ||
- test "$remote_origin_prune_tags" = 'true'
- then
- if ! printf '%s' "$cmdline\n" | grep -q refs/remotes/origin/
- then
+ test "$remote_origin_prune_tags" = 'true'; then
+ if ! printf '%s' "$cmdline\n" | grep -q refs/remotes/origin/; then
new_cmdline="$new_cmdline refs/tags/*:refs/tags/*"
fi
fi
@@ -946,100 +964,100 @@ test_configured_prune_type () {
# $7 git-fetch $cmdline:
#
# $1 $2 $3 $4 $5 $6 $7
-test_configured_prune unset unset unset unset kept kept ""
-test_configured_prune unset unset unset unset kept kept "--no-prune"
-test_configured_prune unset unset unset unset pruned kept "--prune"
-test_configured_prune unset unset unset unset kept pruned \
+test_configured_prune unset unset unset unset kept kept ""
+test_configured_prune unset unset unset unset kept kept "--no-prune"
+test_configured_prune unset unset unset unset pruned kept "--prune"
+test_configured_prune unset unset unset unset kept pruned \
"--prune origin refs/tags/*:refs/tags/*"
test_configured_prune unset unset unset unset pruned pruned \
"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
-test_configured_prune false unset unset unset kept kept ""
-test_configured_prune false unset unset unset kept kept "--no-prune"
-test_configured_prune false unset unset unset pruned kept "--prune"
+test_configured_prune false unset unset unset kept kept ""
+test_configured_prune false unset unset unset kept kept "--no-prune"
+test_configured_prune false unset unset unset pruned kept "--prune"
-test_configured_prune true unset unset unset pruned kept ""
-test_configured_prune true unset unset unset pruned kept "--prune"
-test_configured_prune true unset unset unset kept kept "--no-prune"
+test_configured_prune true unset unset unset pruned kept ""
+test_configured_prune true unset unset unset pruned kept "--prune"
+test_configured_prune true unset unset unset kept kept "--no-prune"
-test_configured_prune unset false unset unset kept kept ""
-test_configured_prune unset false unset unset kept kept "--no-prune"
-test_configured_prune unset false unset unset pruned kept "--prune"
+test_configured_prune unset false unset unset kept kept ""
+test_configured_prune unset false unset unset kept kept "--no-prune"
+test_configured_prune unset false unset unset pruned kept "--prune"
-test_configured_prune false false unset unset kept kept ""
-test_configured_prune false false unset unset kept kept "--no-prune"
-test_configured_prune false false unset unset pruned kept "--prune"
-test_configured_prune false false unset unset kept pruned \
+test_configured_prune false false unset unset kept kept ""
+test_configured_prune false false unset unset kept kept "--no-prune"
+test_configured_prune false false unset unset pruned kept "--prune"
+test_configured_prune false false unset unset kept pruned \
"--prune origin refs/tags/*:refs/tags/*"
test_configured_prune false false unset unset pruned pruned \
"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
-test_configured_prune true false unset unset kept kept ""
-test_configured_prune true false unset unset pruned kept "--prune"
-test_configured_prune true false unset unset kept kept "--no-prune"
+test_configured_prune true false unset unset kept kept ""
+test_configured_prune true false unset unset pruned kept "--prune"
+test_configured_prune true false unset unset kept kept "--no-prune"
-test_configured_prune unset true unset unset pruned kept ""
-test_configured_prune unset true unset unset kept kept "--no-prune"
-test_configured_prune unset true unset unset pruned kept "--prune"
+test_configured_prune unset true unset unset pruned kept ""
+test_configured_prune unset true unset unset kept kept "--no-prune"
+test_configured_prune unset true unset unset pruned kept "--prune"
-test_configured_prune false true unset unset pruned kept ""
-test_configured_prune false true unset unset kept kept "--no-prune"
-test_configured_prune false true unset unset pruned kept "--prune"
+test_configured_prune false true unset unset pruned kept ""
+test_configured_prune false true unset unset kept kept "--no-prune"
+test_configured_prune false true unset unset pruned kept "--prune"
-test_configured_prune true true unset unset pruned kept ""
-test_configured_prune true true unset unset pruned kept "--prune"
-test_configured_prune true true unset unset kept kept "--no-prune"
-test_configured_prune true true unset unset kept pruned \
+test_configured_prune true true unset unset pruned kept ""
+test_configured_prune true true unset unset pruned kept "--prune"
+test_configured_prune true true unset unset kept kept "--no-prune"
+test_configured_prune true true unset unset kept pruned \
"--prune origin refs/tags/*:refs/tags/*"
-test_configured_prune true true unset unset pruned pruned \
+test_configured_prune true true unset unset pruned pruned \
"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
# --prune-tags on its own does nothing, needs --prune as well, same
# for fetch.pruneTags without fetch.prune
-test_configured_prune unset unset unset unset kept kept "--prune-tags"
-test_configured_prune unset unset true unset kept kept ""
-test_configured_prune unset unset unset true kept kept ""
+test_configured_prune unset unset unset unset kept kept "--prune-tags"
+test_configured_prune unset unset true unset kept kept ""
+test_configured_prune unset unset unset true kept kept ""
# These will prune the tags
test_configured_prune unset unset unset unset pruned pruned "--prune --prune-tags"
-test_configured_prune true unset true unset pruned pruned ""
-test_configured_prune unset true unset true pruned pruned ""
+test_configured_prune true unset true unset pruned pruned ""
+test_configured_prune unset true unset true pruned pruned ""
# remote.<name>.pruneTags overrides fetch.pruneTags, just like
# remote.<name>.prune overrides fetch.prune if set.
-test_configured_prune true unset true unset pruned pruned ""
-test_configured_prune false true false true pruned pruned ""
-test_configured_prune true false true false kept kept ""
+test_configured_prune true unset true unset pruned pruned ""
+test_configured_prune false true false true pruned pruned ""
+test_configured_prune true false true false kept kept ""
# When --prune-tags is supplied it's ignored if an explicit refspec is
# given, same for the configuration options.
test_configured_prune unset unset unset unset pruned kept \
"--prune --prune-tags origin +refs/heads/*:refs/remotes/origin/*"
-test_configured_prune unset unset true unset pruned kept \
+test_configured_prune unset unset true unset pruned kept \
"--prune origin +refs/heads/*:refs/remotes/origin/*"
-test_configured_prune unset unset unset true pruned kept \
+test_configured_prune unset unset unset true pruned kept \
"--prune origin +refs/heads/*:refs/remotes/origin/*"
# Pruning that also takes place if a file:// url replaces a named
# remote. However, because there's no implicit
# +refs/heads/*:refs/remotes/origin/* refspec and supplying it on the
# command-line negates --prune-tags, the branches will not be pruned.
-test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "name"
-test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "link"
+test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "name"
+test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "link"
test_configured_prune_type unset unset unset unset pruned pruned "origin --prune --prune-tags" "name"
-test_configured_prune_type unset unset unset unset kept pruned "origin --prune --prune-tags" "link"
+test_configured_prune_type unset unset unset unset kept pruned "origin --prune --prune-tags" "link"
test_configured_prune_type unset unset unset unset pruned pruned "--prune --prune-tags origin" "name"
-test_configured_prune_type unset unset unset unset kept pruned "--prune --prune-tags origin" "link"
-test_configured_prune_type unset unset true unset pruned pruned "--prune origin" "name"
-test_configured_prune_type unset unset true unset kept pruned "--prune origin" "link"
-test_configured_prune_type unset unset unset true pruned pruned "--prune origin" "name"
-test_configured_prune_type unset unset unset true kept pruned "--prune origin" "link"
-test_configured_prune_type true unset true unset pruned pruned "origin" "name"
-test_configured_prune_type true unset true unset kept pruned "origin" "link"
-test_configured_prune_type unset true true unset pruned pruned "origin" "name"
-test_configured_prune_type unset true true unset kept pruned "origin" "link"
-test_configured_prune_type unset true unset true pruned pruned "origin" "name"
-test_configured_prune_type unset true unset true kept pruned "origin" "link"
+test_configured_prune_type unset unset unset unset kept pruned "--prune --prune-tags origin" "link"
+test_configured_prune_type unset unset true unset pruned pruned "--prune origin" "name"
+test_configured_prune_type unset unset true unset kept pruned "--prune origin" "link"
+test_configured_prune_type unset unset unset true pruned pruned "--prune origin" "name"
+test_configured_prune_type unset unset unset true kept pruned "--prune origin" "link"
+test_configured_prune_type true unset true unset pruned pruned "origin" "name"
+test_configured_prune_type true unset true unset kept pruned "origin" "link"
+test_configured_prune_type unset true true unset pruned pruned "origin" "name"
+test_configured_prune_type unset true true unset kept pruned "origin" "link"
+test_configured_prune_type unset true unset true pruned pruned "origin" "name"
+test_configured_prune_type unset true unset true kept pruned "origin" "link"
# When all remote.origin.fetch settings are deleted a --prune
# --prune-tags still implicitly supplies refs/tags/*:refs/tags/* so
@@ -1137,8 +1155,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '
)
'
-for section in fetch transfer
-do
+for section in fetch transfer; do
test_expect_success "$section.hideRefs affects connectivity check" '
GIT_TRACE="$PWD"/trace git -c $section.hideRefs=refs -c \
$section.hideRefs="!refs/tags/" fetch &&
@@ -1157,21 +1174,23 @@ test_expect_success 'prepare source branch' '
test 3 -le $(wc -l <actual)
'
-validate_store_type () {
+validate_store_type() {
git -C dest count-objects -v >actual &&
- case "$store_type" in
- packed)
- grep "^count: 0$" actual ;;
- loose)
- grep "^packs: 0$" actual ;;
- esac || {
+ case "$store_type" in
+ packed)
+ grep "^count: 0$" actual
+ ;;
+ loose)
+ grep "^packs: 0$" actual
+ ;;
+ esac || {
echo "store_type is $store_type"
cat actual
false
}
}
-test_unpack_limit () {
+test_unpack_limit() {
store_type=$1
case "$store_type" in
@@ -1192,43 +1211,39 @@ test_unpack_limit () {
test_unpack_limit packed
test_unpack_limit loose
-setup_negotiation_tip () {
+setup_negotiation_tip() {
SERVER="$1"
URL="$2"
USE_PROTOCOL_V2="$3"
rm -rf "$SERVER" client trace &&
- git init -b main "$SERVER" &&
- test_commit -C "$SERVER" alpha_1 &&
- test_commit -C "$SERVER" alpha_2 &&
- git -C "$SERVER" checkout --orphan beta &&
- test_commit -C "$SERVER" beta_1 &&
- test_commit -C "$SERVER" beta_2 &&
-
- git clone "$URL" client &&
-
- if test "$USE_PROTOCOL_V2" -eq 1
- then
- git -C "$SERVER" config protocol.version 2 &&
- git -C client config protocol.version 2
- fi &&
-
- test_commit -C "$SERVER" beta_s &&
- git -C "$SERVER" checkout main &&
- test_commit -C "$SERVER" alpha_s &&
- git -C "$SERVER" tag -d alpha_1 alpha_2 beta_1 beta_2
+ git init -b main "$SERVER" &&
+ test_commit -C "$SERVER" alpha_1 &&
+ test_commit -C "$SERVER" alpha_2 &&
+ git -C "$SERVER" checkout --orphan beta &&
+ test_commit -C "$SERVER" beta_1 &&
+ test_commit -C "$SERVER" beta_2 &&
+ git clone "$URL" client &&
+ if test "$USE_PROTOCOL_V2" -eq 1; then
+ git -C "$SERVER" config protocol.version 2 &&
+ git -C client config protocol.version 2
+ fi &&
+ test_commit -C "$SERVER" beta_s &&
+ git -C "$SERVER" checkout main &&
+ test_commit -C "$SERVER" alpha_s &&
+ git -C "$SERVER" tag -d alpha_1 alpha_2 beta_1 beta_2
}
-check_negotiation_tip () {
+check_negotiation_tip() {
# Ensure that {alpha,beta}_1 are sent as "have", but not {alpha_beta}_2
ALPHA_1=$(git -C client rev-parse alpha_1) &&
- grep "fetch> have $ALPHA_1" trace &&
- BETA_1=$(git -C client rev-parse beta_1) &&
- grep "fetch> have $BETA_1" trace &&
- ALPHA_2=$(git -C client rev-parse alpha_2) &&
- ! grep "fetch> have $ALPHA_2" trace &&
- BETA_2=$(git -C client rev-parse beta_2) &&
- ! grep "fetch> have $BETA_2" trace
+ grep "fetch> have $ALPHA_1" trace &&
+ BETA_1=$(git -C client rev-parse beta_1) &&
+ grep "fetch> have $BETA_1" trace &&
+ ALPHA_2=$(git -C client rev-parse alpha_2) &&
+ ! grep "fetch> have $ALPHA_2" trace &&
+ BETA_2=$(git -C client rev-parse beta_2) &&
+ ! grep "fetch> have $BETA_2" trace
}
test_expect_success '--negotiation-tip limits "have" lines sent' '
diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh
index 64b3491e4e..1b3865e154 100755
--- a/t/t5512-ls-remote.sh
+++ b/t/t5512-ls-remote.sh
@@ -293,6 +293,8 @@ test_expect_success 'ls-remote with filtered symref (refname)' '
cat >expect <<-EOF &&
ref: refs/heads/main HEAD
$rev HEAD
+ ref: refs/remotes/origin/main refs/remotes/origin/HEAD
+ $rev refs/remotes/origin/HEAD
EOF
git ls-remote --symref . HEAD >actual &&
test_cmp expect actual
diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh
index 579872c258..e3482b27b2 100755
--- a/t/t5514-fetch-multiple.sh
+++ b/t/t5514-fetch-multiple.sh
@@ -45,14 +45,17 @@ test_expect_success setup '
'
cat > test/expect << EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -97,6 +100,7 @@ cat > expect << EOF
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
@@ -112,8 +116,10 @@ test_expect_success 'git fetch --multiple (but only one remote)' '
'
cat > expect << EOF
+ one/HEAD -> one/main
one/main
one/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -141,7 +147,7 @@ test_expect_success 'git fetch --multiple (bad remote names)' '
test_expect_success 'git fetch --all (skipFetchAll)' '
(cd test4 &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -153,11 +159,14 @@ test_expect_success 'git fetch --all (skipFetchAll)' '
'
cat > expect << EOF
+ one/HEAD -> one/main
one/main
one/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -165,7 +174,7 @@ EOF
test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '
(cd test4 &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -221,14 +230,17 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' '
create_fetch_all_expect () {
cat >expect <<-\EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -265,6 +277,7 @@ test_expect_success 'git fetch (fetch all remotes with fetch.all = true)' '
create_fetch_one_expect () {
cat >expect <<-\EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 331778bd42..5a051aa0c7 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -1395,7 +1395,8 @@ test_expect_success 'fetch follows tags by default' '
git tag -m "annotated" tag &&
git for-each-ref >tmp1 &&
sed -n "p; s|refs/heads/main$|refs/remotes/origin/main|p" tmp1 |
- sort -k 3 >../expect
+ sed -n "p; s|refs/heads/main$|refs/remotes/origin/HEAD|p" |
+ sort -k 4 >../expect
) &&
test_when_finished "rm -rf dst" &&
git init dst &&
diff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh
index 98ece27c6a..d3996af6ee 100755
--- a/t/t5527-fetch-odd-refs.sh
+++ b/t/t5527-fetch-odd-refs.sh
@@ -52,7 +52,8 @@ test_expect_success LONG_REF 'fetch handles extremely long refname' '
long
main
EOF
- git for-each-ref --format="%(subject)" refs/remotes/long >actual &&
+ git for-each-ref --format="%(subject)" refs/remotes/long \
+ --exclude=refs/remotes/long/HEAD >actual &&
test_cmp expect actual
'
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index a66d0e089d..88331d1ceb 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -329,7 +329,8 @@ test_expect_success 'incremental-repack task' '
# Delete refs that have not been repacked in these packs.
git for-each-ref --format="delete %(refname)" \
- refs/prefetch refs/tags refs/remotes >refs &&
+ refs/prefetch refs/tags refs/remotes \
+ --exclude=refs/remotes/*/HEAD >refs &&
git update-ref --stdin <refs &&
# Replace the object directory with this pack layout.
diff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh
index a30b2c9f70..2237844550 100755
--- a/t/t9210-scalar.sh
+++ b/t/t9210-scalar.sh
@@ -151,7 +151,8 @@ test_expect_success 'scalar clone' '
"$(pwd)" &&
git for-each-ref --format="%(refname)" refs/remotes/origin/ >actual &&
- echo "refs/remotes/origin/parallel" >expect &&
+ echo "refs/remotes/origin/HEAD" >>expect &&
+ echo "refs/remotes/origin/parallel" >>expect &&
test_cmp expect actual &&
test_path_is_missing 1/2 &&
@@ -220,7 +221,7 @@ test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '
done
'
- test_expect_success 'scalar reconfigure --all with detached HEADs' '
+test_expect_success 'scalar reconfigure --all with detached HEADs' '
repos="two three four" &&
for num in $repos
do
diff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh
index c16ea67c1d..d9cb6b9a3e 100755
--- a/t/t9211-scalar-clone.sh
+++ b/t/t9211-scalar-clone.sh
@@ -32,7 +32,7 @@ test_expect_success 'set up repository to clone' '
)
'
-cleanup_clone () {
+cleanup_clone() {
rm -rf "$1"
}
@@ -128,7 +128,7 @@ test_expect_success '--single-branch clones HEAD only' '
(
cd $enlistment/src &&
git for-each-ref refs/remotes/origin >out &&
- test_line_count = 1 out &&
+ test_line_count = 2 out &&
grep "refs/remotes/origin/base" out
) &&
@@ -142,7 +142,7 @@ test_expect_success '--no-single-branch clones all branches' '
(
cd $enlistment/src &&
git for-each-ref refs/remotes/origin >out &&
- test_line_count = 2 out &&
+ test_line_count = 3 out &&
grep "refs/remotes/origin/base" out &&
grep "refs/remotes/origin/parallel" out
) &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index cc6aa9f0cd..b663c4609e 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -658,6 +658,7 @@ test_expect_success '__git_refs - simple' '
HEAD
main
matching-branch
+ other/HEAD
other/branch-in-other
other/main-in-other
matching-tag
@@ -673,6 +674,7 @@ test_expect_success '__git_refs - full refs' '
cat >expected <<-EOF &&
refs/heads/main
refs/heads/matching-branch
+ refs/remotes/other/HEAD
refs/remotes/other/branch-in-other
refs/remotes/other/main-in-other
refs/tags/matching-tag
@@ -729,6 +731,7 @@ test_expect_success '__git_refs - remote on local file system - full refs' '
test_expect_success '__git_refs - configured remote' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -756,6 +759,7 @@ test_expect_success '__git_refs - configured remote - full refs' '
test_expect_success '__git_refs - configured remote - repo given on the command line' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -787,6 +791,7 @@ test_expect_success '__git_refs - configured remote - full refs - repo given on
test_expect_success '__git_refs - configured remote - remote name matches a directory' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -875,12 +880,14 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer
HEAD
main
matching-branch
+ other/HEAD
other/ambiguous
other/branch-in-other
other/main-in-other
remote/ambiguous
remote/branch-in-remote
matching-tag
+ HEAD
branch-in-other
branch-in-remote
main-in-other
@@ -904,6 +911,7 @@ test_expect_success '__git_refs - after --opt=' '
HEAD
main
matching-branch
+ other/HEAD
other/branch-in-other
other/main-in-other
matching-tag
@@ -919,6 +927,7 @@ test_expect_success '__git_refs - after --opt= - full refs' '
cat >expected <<-EOF &&
refs/heads/main
refs/heads/matching-branch
+ refs/remotes/other/HEAD
refs/remotes/other/branch-in-other
refs/remotes/other/main-in-other
refs/tags/matching-tag
@@ -935,6 +944,7 @@ test_expect_success '__git refs - excluding refs' '
^HEAD
^main
^matching-branch
+ ^other/HEAD
^other/branch-in-other
^other/main-in-other
^matching-tag
@@ -950,6 +960,7 @@ test_expect_success '__git refs - excluding full refs' '
cat >expected <<-EOF &&
^refs/heads/main
^refs/heads/matching-branch
+ ^refs/remotes/other/HEAD
^refs/remotes/other/branch-in-other
^refs/remotes/other/main-in-other
^refs/tags/matching-tag
@@ -975,6 +986,7 @@ test_expect_success '__git_refs - do not filter refs unless told so' '
main
matching-branch
matching/branch
+ other/HEAD
other/branch-in-other
other/main-in-other
other/matching/branch-in-other
@@ -1095,6 +1107,7 @@ test_expect_success '__git_complete_refs - simple' '
HEAD Z
main Z
matching-branch Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
matching-tag Z
@@ -1123,6 +1136,7 @@ test_expect_success '__git_complete_refs - matching' '
test_expect_success '__git_complete_refs - remote' '
sed -e "s/Z$//" >expected <<-EOF &&
HEAD Z
+ HEAD Z
branch-in-other Z
main-in-other Z
EOF
@@ -1139,9 +1153,11 @@ test_expect_success '__git_complete_refs - track' '
HEAD Z
main Z
matching-branch Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
matching-tag Z
+ HEAD Z
branch-in-other Z
main-in-other Z
EOF
@@ -1184,6 +1200,7 @@ test_expect_success '__git_complete_refs - suffix' '
HEAD.
main.
matching-branch.
+ other/HEAD.
other/branch-in-other.
other/main-in-other.
matching-tag.
@@ -1199,6 +1216,7 @@ test_expect_success '__git_complete_refs - suffix' '
test_expect_success '__git_complete_fetch_refspecs - simple' '
sed -e "s/Z$//" >expected <<-EOF &&
HEAD:HEAD Z
+ HEAD:HEAD Z
branch-in-other:branch-in-other Z
main-in-other:main-in-other Z
EOF
@@ -1225,6 +1243,7 @@ test_expect_success '__git_complete_fetch_refspecs - matching' '
test_expect_success '__git_complete_fetch_refspecs - prefix' '
sed -e "s/Z$//" >expected <<-EOF &&
+HEAD:HEAD Z
+ +HEAD:HEAD Z
+branch-in-other:branch-in-other Z
+main-in-other:main-in-other Z
EOF
@@ -1289,6 +1308,7 @@ test_expect_success '__git_complete_worktree_paths with -C' '
test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '
test_completion "git switch " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1435,11 +1455,13 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena
test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '
test_completion "git checkout " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1461,6 +1483,7 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl
test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '
GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git switch --guess " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1470,6 +1493,7 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G
test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '
test_completion "git switch --no-guess --guess " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1490,6 +1514,7 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1498,11 +1523,13 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete
test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '
GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git checkout --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1514,6 +1541,7 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1522,11 +1550,13 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '
test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '
test_completion "git checkout --no-guess --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1538,6 +1568,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1550,6 +1581,7 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1559,11 +1591,13 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a
test_config checkout.guess true &&
test_completion "git checkout " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1573,11 +1607,13 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.
test_config checkout.guess false &&
test_completion "git checkout --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1590,6 +1626,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1601,6 +1638,7 @@ test_expect_success 'git switch - with --detach, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1612,6 +1650,7 @@ test_expect_success 'git checkout - with --detach, complete only references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1783,6 +1822,7 @@ test_expect_success 'git switch - with -d, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1794,6 +1834,7 @@ test_expect_success 'git checkout - with -d, complete only references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1801,10 +1842,12 @@ test_expect_success 'git checkout - with -d, complete only references' '
test_expect_success 'git switch - with --track, complete only remote branches' '
test_completion "git switch --track " <<-\EOF &&
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
test_completion "git switch -t " <<-\EOF
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1812,10 +1855,12 @@ test_expect_success 'git switch - with --track, complete only remote branches' '
test_expect_success 'git checkout - with --track, complete only remote branches' '
test_completion "git checkout --track " <<-\EOF &&
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
test_completion "git checkout -t " <<-\EOF
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1834,6 +1879,7 @@ test_expect_success 'git checkout - with --no-track, complete only local referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1845,6 +1891,7 @@ test_expect_success 'git switch - with -c, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1856,6 +1903,7 @@ test_expect_success 'git switch - with -C, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1867,6 +1915,7 @@ test_expect_success 'git switch - with -c and --track, complete all references'
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1878,6 +1927,7 @@ test_expect_success 'git switch - with -C and --track, complete all references'
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1889,6 +1939,7 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1900,6 +1951,7 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1911,6 +1963,7 @@ test_expect_success 'git checkout - with -b, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1922,6 +1975,7 @@ test_expect_success 'git checkout - with -B, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1933,6 +1987,7 @@ test_expect_success 'git checkout - with -b and --track, complete all references
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1944,6 +1999,7 @@ test_expect_success 'git checkout - with -B and --track, complete all references
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1955,6 +2011,7 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1966,6 +2023,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1973,6 +2031,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen
test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '
test_completion "git switch -c " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1982,6 +2041,7 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem
test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '
test_completion "git switch -C " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2019,6 +2079,7 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche
test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '
test_completion "git checkout -b " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2028,6 +2089,7 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r
test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '
test_completion "git checkout -B " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2065,6 +2127,7 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc
test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '
test_completion "git switch --orphan " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2080,6 +2143,7 @@ test_expect_success 'git switch - --orphan with branch already provided complete
test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '
test_completion "git checkout --orphan " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2093,6 +2157,7 @@ test_expect_success 'git checkout - --orphan with branch already provided comple
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
--
2.47.0.94.g1247fb88fd.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v12 7/8] fetch: set remote/HEAD if it does not exist
2024-10-23 15:36 ` [PATCH v12 7/8] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
@ 2024-10-23 16:50 ` Kristoffer Haugsbakk
2024-10-23 17:07 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Kristoffer Haugsbakk @ 2024-10-23 16:50 UTC (permalink / raw)
To: Bence Ferdinandy, git
Cc: Phillip Wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, Karthik Nayak, Taylor Blau, ferdinandy.bence
On Wed, Oct 23, 2024, at 17:36, Bence Ferdinandy wrote:
> If the user has remote/HEAD set already and it looks like it has changed
> on the server, then print a message, otherwise set it if we can.
> Silently pass if the user already has the same remote/HEAD set as
> reported by the server or if we encounter any errors along the way.
>
> Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
> ---
>
> Notes:
> v3: - does not rely on remote set-head anymore so it only authenticates
> once
> - uses the new REF_CREATE_ONLY to atomically check if the ref exists
> and only write it if it doesn't
> - in all other cases the maximum it does is print a warning
>
> v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,
> but updated transaction api to request a silent create only
> - it now uses the atomic before_target to determine reporting
> - refactored for legibility
>
> v5: - instead of printing a not too useful message, it now fails
> silently, this in line with the objective to only set up
> remote/HEAD automatically if the right thing is trivial, for
> everything else there is remote set-head
> - fixed all failing tests
> - added two new tests, one for checking if remote/HEAD is set to the
> correct one, and one to test that we do not override remote/HEAD
> if it has changed on the server from what we have locally
>
> v6: - fixed style issues and unintended extra empty line
> - updated function call with bool to int from previous patch's
> change
> - removed calls to error(...) inherited from builtin/remote.c so we
> actually fail silently
> - set the test for remote set-head --auto to the correct value here,
> which was previously erronously set in the remote set-head patch
>
> v7: - no change
>
> v8: - changed logmsg in call to refs_update_symref from "remote
> set-head" to "fetch"
>
> v9: - follow through with refs_update_symref_extended
> - fix test errors uncovered by the new patch
>
> v10: no change
>
> v11: fixed some memory leaks
>
> v12: no change
I think it would be better to reverse-order these patch changelog
comments so that the newest is on top/first. (for next time)
Thanks for the careful versioning here.
--
Kristoffer Haugsbakk
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v12 7/8] fetch: set remote/HEAD if it does not exist
2024-10-23 16:50 ` Kristoffer Haugsbakk
@ 2024-10-23 17:07 ` Bence Ferdinandy
0 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-23 17:07 UTC (permalink / raw)
To: Kristoffer Haugsbakk, git
Cc: Phillip Wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, Karthik Nayak, Taylor Blau, ferdinandy.bence
On Wed Oct 23, 2024 at 18:50, Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com> wrote:
> On Wed, Oct 23, 2024, at 17:36, Bence Ferdinandy wrote:
>> If the user has remote/HEAD set already and it looks like it has changed
>> on the server, then print a message, otherwise set it if we can.
>> Silently pass if the user already has the same remote/HEAD set as
>> reported by the server or if we encounter any errors along the way.
>>
>> Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
>> ---
>>
>> Notes:
>> v3: - does not rely on remote set-head anymore so it only authenticates
>> once
>> - uses the new REF_CREATE_ONLY to atomically check if the ref exists
>> and only write it if it doesn't
>> - in all other cases the maximum it does is print a warning
>>
>> v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,
>> but updated transaction api to request a silent create only
>> - it now uses the atomic before_target to determine reporting
>> - refactored for legibility
>>
>> v5: - instead of printing a not too useful message, it now fails
>> silently, this in line with the objective to only set up
>> remote/HEAD automatically if the right thing is trivial, for
>> everything else there is remote set-head
>> - fixed all failing tests
>> - added two new tests, one for checking if remote/HEAD is set to the
>> correct one, and one to test that we do not override remote/HEAD
>> if it has changed on the server from what we have locally
>>
>> v6: - fixed style issues and unintended extra empty line
>> - updated function call with bool to int from previous patch's
>> change
>> - removed calls to error(...) inherited from builtin/remote.c so we
>> actually fail silently
>> - set the test for remote set-head --auto to the correct value here,
>> which was previously erronously set in the remote set-head patch
>>
>> v7: - no change
>>
>> v8: - changed logmsg in call to refs_update_symref from "remote
>> set-head" to "fetch"
>>
>> v9: - follow through with refs_update_symref_extended
>> - fix test errors uncovered by the new patch
>>
>> v10: no change
>>
>> v11: fixed some memory leaks
>>
>> v12: no change
>
> I think it would be better to reverse-order these patch changelog
> comments so that the newest is on top/first. (for next time)
>
> Thanks for the careful versioning here.
Yeah, this works fine when you only go up to v3 and it all fits on a screen :D
This is definitely the longest patch series I've made thus far, both in number
of commits and versions. I also noticed this today when I realized that I did the
cover letter in reverse and how much better readable that was than some of the
patches ...
One thing that would not be easy is that with so many patches, how I start out
the notes is
git rev-list HEAD~8..HEAD | xargs -i git notes append {} -m "v12: no change"
and there's no "git notes prepend". That could be another patch for another
time :) It also bothers me, that the branch description that can be used to
save cover letters is not a note, just a local configuration, but that's
a third issue.
Anyhow, thanks for calling it out, if we do come to a v13 I might just spend
some time on reversing (or be lazy and just continue at the top ...).
Best,
Bence
--
bence.ferdinandy.com
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v12 8/8] fetch set_head: handle mirrored bare repositories
2024-10-23 15:36 ` [PATCH v12 0/8] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (6 preceding siblings ...)
2024-10-23 15:36 ` [PATCH v12 7/8] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
@ 2024-10-23 15:36 ` Bence Ferdinandy
2024-11-14 20:23 ` [PATCH v12 0/8] set-head/fetch remote/HEAD updates Bence Ferdinandy
2024-11-18 15:09 ` [PATCH v13 0/9] " Bence Ferdinandy
9 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-23 15:36 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Bence Ferdinandy,
ferdinandy.bence
When adding a remote to bare repository with "git remote add --mirror",
running fetch will fail to update HEAD to the remote's HEAD, since it
does not know how to handle bare repositories. On the other hand HEAD
already has content, since "git init --bare" has already set HEAD to
whatever is the default branch set for the user. Unless this - by chance
- is the same as the remote's HEAD, HEAD will be pointing to a bad
symref. Teach set_head to handle bare repositories, by overwriting HEAD
so it mirrors the remote's HEAD.
Note, that in this case overriding the local HEAD reference is
necessary, since HEAD will exist before fetch can be run, but this
should not be an issue, since the whole purpose of --mirror is to be an
exact mirror of the remote, so following any changes to HEAD makes
sense.
Also note, that although "git remote set-head" also fails when trying to
update the remote's locally tracked HEAD in a mirrored bare repository,
the usage of the command does not make much sense after this patch:
fetch will update the remote HEAD correctly, and setting it manually to
something else is antithetical to the concept of mirroring.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v10: - new patch
- handles the issue discovered in
https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/T/
v11: no change
v12: no change
builtin/fetch.c | 16 ++++++++++++----
t/t5505-remote.sh | 10 ++++++++++
2 files changed, 22 insertions(+), 4 deletions(-)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index cadfd2407a..8af3efd6ac 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1633,14 +1633,22 @@ static int set_head(const struct ref *remote_refs)
else
head_name = xstrdup(heads.items[0].string);
if (head_name) {
- strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
- strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name);
+ int is_bare = is_bare_repository();
+ if (is_bare) {
+ strbuf_addstr(&b_head, "HEAD");
+ strbuf_addf(&b_remote_head, "refs/heads/%s", head_name);
+ } else {
+ strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name);
+ }
/* make sure it's valid */
- if (!refs_ref_exists(refs, b_remote_head.buf))
+ if (!is_bare && !refs_ref_exists(refs, b_remote_head.buf)) {
result = 1;
+ }
else if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
- "fetch", &b_local_head, 1))
+ "fetch", &b_local_head, !is_bare)) {
result = 1;
+ }
else
report_set_head(remote, head_name, &b_local_head);
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 4990d00209..dfa78f3e8d 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -545,6 +545,16 @@ test_expect_success 'add --mirror && prune' '
)
'
+test_expect_success 'add --mirror setting HEAD' '
+ mkdir headmirror &&
+ (
+ cd headmirror &&
+ git init --bare -b notmain &&
+ git remote add --mirror -f origin ../one &&
+ test "$(git symbolic-ref HEAD)" = "refs/heads/main"
+ )
+'
+
test_expect_success 'add --mirror=fetch' '
mkdir mirror-fetch &&
git init -b main mirror-fetch/parent &&
--
2.47.0.94.g1247fb88fd.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v12 0/8] set-head/fetch remote/HEAD updates
2024-10-23 15:36 ` [PATCH v12 0/8] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (7 preceding siblings ...)
2024-10-23 15:36 ` [PATCH v12 8/8] fetch set_head: handle mirrored bare repositories Bence Ferdinandy
@ 2024-11-14 20:23 ` Bence Ferdinandy
2024-11-18 15:09 ` [PATCH v13 0/9] " Bence Ferdinandy
9 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-14 20:23 UTC (permalink / raw)
To: Bence Ferdinandy, git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau
Hi all,
I just wanted to ping this thread in hopes of another round of reviews.
Aside from some smaller fixes, the main things that have not yet been reviewed are:
- the new patches 1/8 and 8/8
(https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/, but see
https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/)
- changes to 2/8 (refs_update_symref -> refs_update_symref_extended change
reflecting on Phillip's comments see
https://lore.kernel.org/git/a7cb48e5-d8ba-44c1-9dbe-d1e8f8a63e3c@gmail.com/)
Regarding the latter name: reading another thread about code style here I was
wondering if refs_update_symref_1 would be a better choice.
I'm hoping that otherwise the series is mature now.
Thanks,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v13 0/9] set-head/fetch remote/HEAD updates
2024-10-23 15:36 ` [PATCH v12 0/8] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (8 preceding siblings ...)
2024-11-14 20:23 ` [PATCH v12 0/8] set-head/fetch remote/HEAD updates Bence Ferdinandy
@ 2024-11-18 15:09 ` Bence Ferdinandy
2024-11-18 15:09 ` [PATCH v13 1/9] t/t5505-remote: set default branch to main Bence Ferdinandy
` (9 more replies)
9 siblings, 10 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-18 15:09 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
This iteration handles the special case when the previous HEAD was detached.
This case is now specifically printed by both remote set-head -a and fetch.
There were also some small cleanups in commit messages and unintended
formattings.
Although the patches themselves are versioned:
- 2/9 is completely new (the above uncovered a discrepancy between backends)
- 3/9 had smaller changes to accomodate this
- 5/9 and 8/9 needed more substantial changes
Bence Ferdinandy (9):
t/t5505-remote: set default branch to main
refs: standardize output of refs_read_symbolic_ref
refs: atomically record overwritten ref in update_symref
remote set-head: refactor for readability
remote set-head: better output for --auto
refs: add TRANSACTION_CREATE_EXISTS error
refs: add create_only option to refs_update_symref_extended
fetch: set remote/HEAD if it does not exist
fetch set_head: handle mirrored bare repositories
builtin/fetch.c | 105 +++++++++++++++++++++++++++++++
builtin/remote.c | 72 +++++++++++++++------
refs.c | 49 ++++++++++++---
refs.h | 14 ++++-
refs/files-backend.c | 31 +++++----
refs/refs-internal.h | 6 ++
refs/reftable-backend.c | 10 ++-
t/t4207-log-decoration-colors.sh | 3 +-
t/t5505-remote.sh | 95 ++++++++++++++++++++++++++--
t/t5510-fetch.sh | 24 +++++++
t/t5512-ls-remote.sh | 2 +
t/t5514-fetch-multiple.sh | 17 ++++-
t/t5516-fetch-push.sh | 3 +-
t/t5527-fetch-odd-refs.sh | 3 +-
t/t7900-maintenance.sh | 3 +-
t/t9210-scalar.sh | 5 +-
t/t9211-scalar-clone.sh | 6 +-
t/t9902-completion.sh | 65 +++++++++++++++++++
18 files changed, 455 insertions(+), 58 deletions(-)
--
2.47.0.296.gda1ecfef29.dirty
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v13 1/9] t/t5505-remote: set default branch to main
2024-11-18 15:09 ` [PATCH v13 0/9] " Bence Ferdinandy
@ 2024-11-18 15:09 ` Bence Ferdinandy
2024-11-20 3:46 ` Junio C Hamano
2024-11-18 15:09 ` [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref Bence Ferdinandy
` (8 subsequent siblings)
9 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-18 15:09 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
Consider the bare repository called "mirror" in the test. Running `git
remote add --mirror -f origin ../one` will not change HEAD, consequently
if init.defaultBranch is not the same as what HEAD in the remote
("one"), HEAD in "mirror" will be pointing to a non-existent reference.
Hence if "mirror" is used as a remote by yet another repository,
ls-remote will not show HEAD. On the other hand, if init.defaultBranch
happens to match HEAD in "one", then ls-remote will show HEAD.
Since the "ci/run-build-and-tests.sh" script globally exports
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main, there may be a drift in some
tests between how the test repositories are set up in the CI and during
local testing, if the test itself uses "master" as default instead of
"main". In particular, this happens in t5505-remote.sh. This issue does
not manifest currently, as the test does not do any remote HEAD
manipulation where this would come up, but should such things be added,
a locally passing test would break the CI and vice-versa.
Set GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main in t5505-remote to be
consistent with the CI.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v9: - new patch
- a bandaid for the CI issue noticed by Taylor (cf:
https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/), but
see
https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/ for the root cause in detail
v10: no change
v11: no change
v12: added forgotten sign-off
v13: commit message udpated to be more precise
t/t5505-remote.sh | 3 +++
1 file changed, 3 insertions(+)
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 532035933f..9b50276646 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -2,6 +2,9 @@
test_description='git remote porcelain-ish'
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
--
2.47.0.296.gda1ecfef29.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v13 1/9] t/t5505-remote: set default branch to main
2024-11-18 15:09 ` [PATCH v13 1/9] t/t5505-remote: set default branch to main Bence Ferdinandy
@ 2024-11-20 3:46 ` Junio C Hamano
0 siblings, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-11-20 3:46 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
Bence Ferdinandy <bence@ferdinandy.com> writes:
> Consider the bare repository called "mirror" in the test. Running `git
> remote add --mirror -f origin ../one` will not change HEAD, consequently
> if init.defaultBranch is not the same as what HEAD in the remote
> ("one"), HEAD in "mirror" will be pointing to a non-existent reference.
> Hence if "mirror" is used as a remote by yet another repository,
> ls-remote will not show HEAD. On the other hand, if init.defaultBranch
> happens to match HEAD in "one", then ls-remote will show HEAD.
>
> Since the "ci/run-build-and-tests.sh" script globally exports
> GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main, there may be a drift in some
"main," -> "main for some (but not all) jobs," or something. IOW,
the point of that forced export is to test both.
> tests between how the test repositories are set up in the CI and during
> local testing, if the test itself uses "master" as default instead of
> "main".
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref
2024-11-18 15:09 ` [PATCH v13 0/9] " Bence Ferdinandy
2024-11-18 15:09 ` [PATCH v13 1/9] t/t5505-remote: set default branch to main Bence Ferdinandy
@ 2024-11-18 15:09 ` Bence Ferdinandy
2024-11-19 1:22 ` Junio C Hamano
` (2 more replies)
2024-11-18 15:09 ` [PATCH v13 3/9] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
` (7 subsequent siblings)
9 siblings, 3 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-18 15:09 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
When the symbolic reference we want to read with refs_read_symbolic_ref
is actually not a symbolic reference, the files and the reftable
backends return different values (1 and -1 respectively). Standardize
the returned values so that 0 is success, -1 is a generic error and -2
is that the reference was actually non-symbolic.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v13: new patch
refs.h | 6 ++++++
refs/files-backend.c | 7 +++----
refs/refs-internal.h | 6 ++++++
refs/reftable-backend.c | 4 +++-
4 files changed, 18 insertions(+), 5 deletions(-)
diff --git a/refs.h b/refs.h
index 108dfc93b3..f8b714ca1d 100644
--- a/refs.h
+++ b/refs.h
@@ -83,6 +83,12 @@ int refs_read_ref_full(struct ref_store *refs, const char *refname,
int refs_read_ref(struct ref_store *refs, const char *refname, struct object_id *oid);
+/*
+ * Return 0 if the symbolic reference could be read without error.
+ * Return -1 for generic errors.
+ * Return -2 if the reference was actually non-symbolic.
+ */
+
int refs_read_symbolic_ref(struct ref_store *ref_store, const char *refname,
struct strbuf *referent);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 0824c0b8a9..81e650ec48 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -596,10 +596,9 @@ static int files_read_symbolic_ref(struct ref_store *ref_store, const char *refn
unsigned int type;
ret = read_ref_internal(ref_store, refname, &oid, referent, &type, &failure_errno, 1);
- if (ret)
- return ret;
-
- return !(type & REF_ISSYMREF);
+ if (!ret && !(type & REF_ISSYMREF))
+ return -2;
+ return ret;
}
int parse_loose_ref_contents(const struct git_hash_algo *algop,
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 2313c830d8..f0ef354bce 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -673,6 +673,12 @@ struct ref_storage_be {
ref_iterator_begin_fn *iterator_begin;
read_raw_ref_fn *read_raw_ref;
+
+ /*
+ * Return 0 if the symbolic reference could be read without error.
+ * Return -1 for generic errors.
+ * Return -2 if the reference was actually non-symbolic.
+ */
read_symbolic_ref_fn *read_symbolic_ref;
reflog_iterator_begin_fn *reflog_iterator_begin;
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 38eb14d591..60cb83f23a 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -830,7 +830,9 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,
return ret;
ret = reftable_stack_read_ref(stack, refname, &ref);
- if (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)
+ if (!ret && (ref.value_type != REFTABLE_REF_SYMREF))
+ ret = -2;
+ else if (!ret && (ref.value_type == REFTABLE_REF_SYMREF))
strbuf_addstr(referent, ref.value.symref);
else
ret = -1;
--
2.47.0.296.gda1ecfef29.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref
2024-11-18 15:09 ` [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref Bence Ferdinandy
@ 2024-11-19 1:22 ` Junio C Hamano
2024-11-19 6:44 ` Patrick Steinhardt
2024-11-19 5:10 ` Junio C Hamano
2024-11-19 6:48 ` Patrick Steinhardt
2 siblings, 1 reply; 258+ messages in thread
From: Junio C Hamano @ 2024-11-19 1:22 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
Bence Ferdinandy <bence@ferdinandy.com> writes:
> Subject: Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref
"output" -> "return values", or something.
> When the symbolic reference we want to read with refs_read_symbolic_ref
> is actually not a symbolic reference, the files and the reftable
> backends return different values (1 and -1 respectively). Standardize
> the returned values so that 0 is success, -1 is a generic error and -2
> is that the reference was actually non-symbolic.
Are all the existing callers OK with this switch from 1 to -2?
IOW, if a caller using the ref-files backend start behaving
differently with this change upon seeing a return value of -2 (where
it previously was expecting to see 1), that would not be nice.
Because "reftable was already broken" is not a good excuse to
introduce a separate regression to ref-files users, we'd want to be
careful if we want to do this kind of "while at it" change.
> +/*
> + * Return 0 if the symbolic reference could be read without error.
> + * Return -1 for generic errors.
> + * Return -2 if the reference was actually non-symbolic.
> + */
As this is an implementation of ref_stroage_be.read_symbolic_ref,
the above comment must stay in sync with the comment over there (and
a comment before the corresponding function in the other backend).
I personally would not add the above comment for that reason, but as
long as we are consistent, I am OK either way. So if we add this,
we should do the same to the reftable side as well.
By the way, it is arguable that it is an error when a caller asks
"here is a ref, please read it as a symbolic ref" and the ref turns
out to be not a symbolic ref. I'd say that it is a valid view that
the caller asked the question to find out if the ref was a symbolic
before making the call, and "that ref is not symbolic" is one of the
valid and expected results. So if you wanted to change the value
from 1 to -2 only because "you called read_symbolic_ref() without
checking if it is a symbolic to begin with, which is your fault and
you deserve to get an error", I am not sure if I agree with that
view and reasoning.
In any case, this "not a symbolic ref" may want to have a symbolic
constant. With NOT_A_SYMREF being a non-error, you could structure
the caller like so:
status = read_symref()
if (status < 0)
... we got an error so we stop here ...
... we do not behave differently on error type ...
switch (status) {
case 0:
... everything is peachy ...
break ;;
case NOT_A_SYMREF:
... we want to handle non-symref here ...
break ;;
default:
BUG("unhandled case");
}
Also, even if we decide that we want to treat this as an error,
lumping everything else as "generic error" might make it awkward to
evolve the API, I suspect.
> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
> index 38eb14d591..60cb83f23a 100644
> --- a/refs/reftable-backend.c
> +++ b/refs/reftable-backend.c
> @@ -830,7 +830,9 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,
> return ret;
>
> ret = reftable_stack_read_ref(stack, refname, &ref);
> - if (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)
> + if (!ret && (ref.value_type != REFTABLE_REF_SYMREF))
> + ret = -2;
> + else if (!ret && (ref.value_type == REFTABLE_REF_SYMREF))
> strbuf_addstr(referent, ref.value.symref);
> else
> ret = -1;
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref
2024-11-19 1:22 ` Junio C Hamano
@ 2024-11-19 6:44 ` Patrick Steinhardt
2024-11-19 6:54 ` Junio C Hamano
0 siblings, 1 reply; 258+ messages in thread
From: Patrick Steinhardt @ 2024-11-19 6:44 UTC (permalink / raw)
To: Junio C Hamano
Cc: Bence Ferdinandy, git, phillip.wood, René Scharfe,
Johannes Schindelin, karthik.188, Taylor Blau
On Tue, Nov 19, 2024 at 10:22:31AM +0900, Junio C Hamano wrote:
> Bence Ferdinandy <bence@ferdinandy.com> writes:
>
> > Subject: Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref
>
> "output" -> "return values", or something.
>
> > When the symbolic reference we want to read with refs_read_symbolic_ref
> > is actually not a symbolic reference, the files and the reftable
> > backends return different values (1 and -1 respectively). Standardize
> > the returned values so that 0 is success, -1 is a generic error and -2
> > is that the reference was actually non-symbolic.
>
> Are all the existing callers OK with this switch from 1 to -2?
>
> IOW, if a caller using the ref-files backend start behaving
> differently with this change upon seeing a return value of -2 (where
> it previously was expecting to see 1), that would not be nice.
>
> Because "reftable was already broken" is not a good excuse to
> introduce a separate regression to ref-files users, we'd want to be
> careful if we want to do this kind of "while at it" change.
There are only three callers:
- "remote.c:ignore_symref_update()" only cares whether the return
value is 0 or not.
- "builtin/remote.c:mv()" is the same.
- "refs.c:migrate_one_ref()" assumes the behaviour of the reftable
backend and only checks for negative error codes.
So you could argue that it's the "files" backend that is broken, not the
"reftable" backend. Doesn't matter ultimately though, the real problem
is that this wasn't ever documented anywhere.
I agree that this should be part of the commit message.
> > +/*
> > + * Return 0 if the symbolic reference could be read without error.
> > + * Return -1 for generic errors.
> > + * Return -2 if the reference was actually non-symbolic.
> > + */
>
> As this is an implementation of ref_stroage_be.read_symbolic_ref,
> the above comment must stay in sync with the comment over there (and
> a comment before the corresponding function in the other backend).
>
> I personally would not add the above comment for that reason, but as
> long as we are consistent, I am OK either way. So if we add this,
> we should do the same to the reftable side as well.
Another solution could be to have the comment in "refs.h" for the
caller-facing function, while the backend pointer simply says "Please
refer to the documentation of `refs_read_symbolic_ref()`."
> By the way, it is arguable that it is an error when a caller asks
> "here is a ref, please read it as a symbolic ref" and the ref turns
> out to be not a symbolic ref. I'd say that it is a valid view that
> the caller asked the question to find out if the ref was a symbolic
> before making the call, and "that ref is not symbolic" is one of the
> valid and expected results. So if you wanted to change the value
> from 1 to -2 only because "you called read_symbolic_ref() without
> checking if it is a symbolic to begin with, which is your fault and
> you deserve to get an error", I am not sure if I agree with that
> view and reasoning.
The reason why I've been proposing to return negative is because we have
the idiom of checking `err < 0` in many places, so a function that
returns a positive value in the case where it didn't return the expected
result can easily lead to bugs.
> In any case, this "not a symbolic ref" may want to have a symbolic
> constant. With NOT_A_SYMREF being a non-error, you could structure
> the caller like so:
That'd be a good idea.
> status = read_symref()
> if (status < 0)
> ... we got an error so we stop here ...
> ... we do not behave differently on error type ...
>
> switch (status) {
> case 0:
> ... everything is peachy ...
> break ;;
> case NOT_A_SYMREF:
> ... we want to handle non-symref here ...
> break ;;
> default:
> BUG("unhandled case");
> }
>
> Also, even if we decide that we want to treat this as an error,
> lumping everything else as "generic error" might make it awkward to
> evolve the API, I suspect.
I guess most callers don't care about the exact error code, they only
care about whether or not they have been able to read the symref. I
think this idiom would easily be extensible in the future if all callers
know to check for `err < 0` by default, because introducing a new error
code would continue to work for them. And if they want to handle that
new error code they can adapt as you propose above with the switch.
Patrick
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref
2024-11-19 6:44 ` Patrick Steinhardt
@ 2024-11-19 6:54 ` Junio C Hamano
2024-11-19 7:26 ` Patrick Steinhardt
0 siblings, 1 reply; 258+ messages in thread
From: Junio C Hamano @ 2024-11-19 6:54 UTC (permalink / raw)
To: Patrick Steinhardt
Cc: Bence Ferdinandy, git, phillip.wood, René Scharfe,
Johannes Schindelin, karthik.188, Taylor Blau
Patrick Steinhardt <ps@pks.im> writes:
> There are only three callers:
>
> - "remote.c:ignore_symref_update()" only cares whether the return
> value is 0 or not.
>
> - "builtin/remote.c:mv()" is the same.
>
> - "refs.c:migrate_one_ref()" assumes the behaviour of the reftable
> backend and only checks for negative error codes.
>
> So you could argue that it's the "files" backend that is broken, not the
> "reftable" backend. Doesn't matter ultimately though, the real problem
> is that this wasn't ever documented anywhere.
You're correct that it does not matter ultimately. But as a general
rule, which also applies here, anything newly introduced one does
differently without having a good reason is a bug in the new thing,
I would say.
> Another solution could be to have the comment in "refs.h" for the
> caller-facing function, while the backend pointer simply says "Please
> refer to the documentation of `refs_read_symbolic_ref()`."
Yup, avoiding unnecessary duplication is a good thing.
> The reason why I've been proposing to return negative is because we have
> the idiom of checking `err < 0` in many places, so a function that
> returns a positive value in the case where it didn't return the expected
> result can easily lead to bugs.
I agree with the general reasoning. I am saying this may or may not
be an error, and if it turns out that it is not an error but is just
one of the generally expected outcome, treating it as an error and
having "if (status < 0)" to lump the case together with other error
cases may not be nice to the callers.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref
2024-11-19 6:54 ` Junio C Hamano
@ 2024-11-19 7:26 ` Patrick Steinhardt
2024-11-19 10:10 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Patrick Steinhardt @ 2024-11-19 7:26 UTC (permalink / raw)
To: Junio C Hamano
Cc: Bence Ferdinandy, git, phillip.wood, René Scharfe,
Johannes Schindelin, karthik.188, Taylor Blau
On Tue, Nov 19, 2024 at 03:54:05PM +0900, Junio C Hamano wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> > The reason why I've been proposing to return negative is because we have
> > the idiom of checking `err < 0` in many places, so a function that
> > returns a positive value in the case where it didn't return the expected
> > result can easily lead to bugs.
>
> I agree with the general reasoning. I am saying this may or may not
> be an error, and if it turns out that it is not an error but is just
> one of the generally expected outcome, treating it as an error and
> having "if (status < 0)" to lump the case together with other error
> cases may not be nice to the callers.
The question to me is whether the function returns something sensible in
all non-error cases that a caller can use properly without having to
explicitly check for the value. And I'd say that this is not the case
with `refs_read_symbolic_ref()`, which wouldn't end up setting the value
of `referent`.
So regardless of whether we define this as error or non-error, the
caller would have to exlicitly handle the case where it's not a symref
in order to make sense of it because the result is not well-defined.
Patrick
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref
2024-11-19 7:26 ` Patrick Steinhardt
@ 2024-11-19 10:10 ` Bence Ferdinandy
0 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-19 10:10 UTC (permalink / raw)
To: Patrick Steinhardt, Junio C Hamano
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau
On Tue Nov 19, 2024 at 08:26, Patrick Steinhardt <ps@pks.im> wrote:
> On Tue, Nov 19, 2024 at 03:54:05PM +0900, Junio C Hamano wrote:
>> Patrick Steinhardt <ps@pks.im> writes:
>> > The reason why I've been proposing to return negative is because we have
>> > the idiom of checking `err < 0` in many places, so a function that
>> > returns a positive value in the case where it didn't return the expected
>> > result can easily lead to bugs.
>>
>> I agree with the general reasoning. I am saying this may or may not
>> be an error, and if it turns out that it is not an error but is just
>> one of the generally expected outcome, treating it as an error and
>> having "if (status < 0)" to lump the case together with other error
>> cases may not be nice to the callers.
>
> The question to me is whether the function returns something sensible in
> all non-error cases that a caller can use properly without having to
> explicitly check for the value. And I'd say that this is not the case
> with `refs_read_symbolic_ref()`, which wouldn't end up setting the value
> of `referent`.
>
> So regardless of whether we define this as error or non-error, the
> caller would have to exlicitly handle the case where it's not a symref
> in order to make sense of it because the result is not well-defined.
I agree with Patrick re the -1, -2 return values. The non-error behavior should
be when referent is set, anything else is something the caller would need to
consider if they want to do something with it or not.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref
2024-11-18 15:09 ` [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref Bence Ferdinandy
2024-11-19 1:22 ` Junio C Hamano
@ 2024-11-19 5:10 ` Junio C Hamano
2024-11-19 10:04 ` Bence Ferdinandy
2024-11-19 6:48 ` Patrick Steinhardt
2 siblings, 1 reply; 258+ messages in thread
From: Junio C Hamano @ 2024-11-19 5:10 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
Bence Ferdinandy <bence@ferdinandy.com> writes:
> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
> index 38eb14d591..60cb83f23a 100644
> --- a/refs/reftable-backend.c
> +++ b/refs/reftable-backend.c
> @@ -830,7 +830,9 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,
> return ret;
>
> ret = reftable_stack_read_ref(stack, refname, &ref);
> - if (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)
> + if (!ret && (ref.value_type != REFTABLE_REF_SYMREF))
> + ret = -2;
> + else if (!ret && (ref.value_type == REFTABLE_REF_SYMREF))
> strbuf_addstr(referent, ref.value.symref);
> else
> ret = -1;
The ref.value_type can be either equal to REFTABLE_REF_SYMREF or not
equal to it, and there is no other choice.
Wouldn't it be easier to reason about if the above code were written
more like this:
if (ret)
ret = -1;
else if (ref.value_type == REFTABLE_REF_SYMREF)
strbuf_addstr(...);
else
ret = -2;
I found it curious when I read it again while attempting to resolve
conflicts with 5413d69f (refs/reftable: refactor reading symbolic
refs to use reftable backend, 2024-11-05). The resolution has to
update this part of code to use the new implementation that asks
reftable_backend_read_ref() and becomes a lot simpler, so the way it
is written in your topic does not make much difference in the longer
term when both topics graduate.
IOW, if we were rebuilding your topic on top of Patrick's topoic
that includes 5413d69f, this part would read like so, I think.
diff --git c/refs/reftable-backend.c w/refs/reftable-backend.c
index 6298991da7..b6bc3039a5 100644
--- c/refs/reftable-backend.c
+++ w/refs/reftable-backend.c
@@ -920,8 +920,10 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,
return ret;
ret = reftable_backend_read_ref(be, refname, &oid, referent, &type);
- if (type != REF_ISSYMREF)
+ if (ret)
ret = -1;
+ else if (type != REF_ISSYMREF)
+ ret = -2;
return ret;
}
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref
2024-11-19 5:10 ` Junio C Hamano
@ 2024-11-19 10:04 ` Bence Ferdinandy
0 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-19 10:04 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
On Tue Nov 19, 2024 at 06:10, Junio C Hamano <gitster@pobox.com> wrote:
> Bence Ferdinandy <bence@ferdinandy.com> writes:
>
>> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
>> index 38eb14d591..60cb83f23a 100644
>> --- a/refs/reftable-backend.c
>> +++ b/refs/reftable-backend.c
>> @@ -830,7 +830,9 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,
>> return ret;
>>
>> ret = reftable_stack_read_ref(stack, refname, &ref);
>> - if (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)
>> + if (!ret && (ref.value_type != REFTABLE_REF_SYMREF))
>> + ret = -2;
>> + else if (!ret && (ref.value_type == REFTABLE_REF_SYMREF))
>> strbuf_addstr(referent, ref.value.symref);
>> else
>> ret = -1;
>
> The ref.value_type can be either equal to REFTABLE_REF_SYMREF or not
> equal to it, and there is no other choice.
Ah, ok, I didn't realize this.
>
> Wouldn't it be easier to reason about if the above code were written
> more like this:
>
> if (ret)
> ret = -1;
> else if (ref.value_type == REFTABLE_REF_SYMREF)
> strbuf_addstr(...);
> else
> ret = -2;
>
> I found it curious when I read it again while attempting to resolve
> conflicts with 5413d69f (refs/reftable: refactor reading symbolic
> refs to use reftable backend, 2024-11-05). The resolution has to
> update this part of code to use the new implementation that asks
> reftable_backend_read_ref() and becomes a lot simpler, so the way it
> is written in your topic does not make much difference in the longer
> term when both topics graduate.
I'll update the patch with the above,
>
> IOW, if we were rebuilding your topic on top of Patrick's topoic
> that includes 5413d69f, this part would read like so, I think.
>
> diff --git c/refs/reftable-backend.c w/refs/reftable-backend.c
> index 6298991da7..b6bc3039a5 100644
> --- c/refs/reftable-backend.c
> +++ w/refs/reftable-backend.c
> @@ -920,8 +920,10 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,
> return ret;
>
> ret = reftable_backend_read_ref(be, refname, &oid, referent, &type);
> - if (type != REF_ISSYMREF)
> + if (ret)
> ret = -1;
> + else if (type != REF_ISSYMREF)
> + ret = -2;
> return ret;
> }
>
but I'll save this as well, I would not be completely surprised if Patrick's
topic makes it in sooner :)
Thanks,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref
2024-11-18 15:09 ` [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref Bence Ferdinandy
2024-11-19 1:22 ` Junio C Hamano
2024-11-19 5:10 ` Junio C Hamano
@ 2024-11-19 6:48 ` Patrick Steinhardt
2024-11-19 10:17 ` Bence Ferdinandy
2 siblings, 1 reply; 258+ messages in thread
From: Patrick Steinhardt @ 2024-11-19 6:48 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau
On Mon, Nov 18, 2024 at 04:09:21PM +0100, Bence Ferdinandy wrote:
> diff --git a/refs.h b/refs.h
> index 108dfc93b3..f8b714ca1d 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -83,6 +83,12 @@ int refs_read_ref_full(struct ref_store *refs, const char *refname,
>
> int refs_read_ref(struct ref_store *refs, const char *refname, struct object_id *oid);
>
> +/*
> + * Return 0 if the symbolic reference could be read without error.
> + * Return -1 for generic errors.
> + * Return -2 if the reference was actually non-symbolic.
> + */
> +
Extraneous empty newline.
Also, how about the following:
/*
* Read the symbolic ref named "refname" and write its immediate
* referent into the provided buffer. This does not resolve the
* symbolic ref recursively in case the target is a symbolic ref, as
* well.
*
* Returns 0 on success, -2 if the "refname" is not a symbolic ref,
* -1 otherwise.
*/
> diff --git a/refs/refs-internal.h b/refs/refs-internal.h
> index 2313c830d8..f0ef354bce 100644
> --- a/refs/refs-internal.h
> +++ b/refs/refs-internal.h
> @@ -673,6 +673,12 @@ struct ref_storage_be {
>
> ref_iterator_begin_fn *iterator_begin;
> read_raw_ref_fn *read_raw_ref;
> +
> + /*
> + * Return 0 if the symbolic reference could be read without error.
> + * Return -1 for generic errors.
> + * Return -2 if the reference was actually non-symbolic.
> + */
> read_symbolic_ref_fn *read_symbolic_ref;
As proposed in the other thread, this could instead be:
/*
* Please refer to `refs_read_symbolic_ref()` for the expected
* behaviour.
/
Patrick
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref
2024-11-19 6:48 ` Patrick Steinhardt
@ 2024-11-19 10:17 ` Bence Ferdinandy
0 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-19 10:17 UTC (permalink / raw)
To: Patrick Steinhardt
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau
On Tue Nov 19, 2024 at 07:48, Patrick Steinhardt <ps@pks.im> wrote:
> On Mon, Nov 18, 2024 at 04:09:21PM +0100, Bence Ferdinandy wrote:
>> diff --git a/refs.h b/refs.h
>> index 108dfc93b3..f8b714ca1d 100644
>> --- a/refs.h
>> +++ b/refs.h
>> @@ -83,6 +83,12 @@ int refs_read_ref_full(struct ref_store *refs, const char *refname,
>>
>> int refs_read_ref(struct ref_store *refs, const char *refname, struct object_id *oid);
>>
>> +/*
>> + * Return 0 if the symbolic reference could be read without error.
>> + * Return -1 for generic errors.
>> + * Return -2 if the reference was actually non-symbolic.
>> + */
>> +
>
> Extraneous empty newline.
>
> Also, how about the following:
>
> /*
> * Read the symbolic ref named "refname" and write its immediate
> * referent into the provided buffer. This does not resolve the
> * symbolic ref recursively in case the target is a symbolic ref, as
> * well.
> *
> * Returns 0 on success, -2 if the "refname" is not a symbolic ref,
> * -1 otherwise.
> */
>
>> diff --git a/refs/refs-internal.h b/refs/refs-internal.h
>> index 2313c830d8..f0ef354bce 100644
>> --- a/refs/refs-internal.h
>> +++ b/refs/refs-internal.h
>> @@ -673,6 +673,12 @@ struct ref_storage_be {
>>
>> ref_iterator_begin_fn *iterator_begin;
>> read_raw_ref_fn *read_raw_ref;
>> +
>> + /*
>> + * Return 0 if the symbolic reference could be read without error.
>> + * Return -1 for generic errors.
>> + * Return -2 if the reference was actually non-symbolic.
>> + */
>> read_symbolic_ref_fn *read_symbolic_ref;
>
> As proposed in the other thread, this could instead be:
>
> /*
> * Please refer to `refs_read_symbolic_ref()` for the expected
> * behaviour.
> /
Thanks, that makes sense. So as a summary, I'll update the comments and also
the implementation detail Junio pointed out.
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v13 3/9] refs: atomically record overwritten ref in update_symref
2024-11-18 15:09 ` [PATCH v13 0/9] " Bence Ferdinandy
2024-11-18 15:09 ` [PATCH v13 1/9] t/t5505-remote: set default branch to main Bence Ferdinandy
2024-11-18 15:09 ` [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref Bence Ferdinandy
@ 2024-11-18 15:09 ` Bence Ferdinandy
2024-11-18 15:09 ` [PATCH v13 4/9] remote set-head: refactor for readability Bence Ferdinandy
` (6 subsequent siblings)
9 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-18 15:09 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
When updating a symref with update_symref it's currently not possible to
know for sure what was the previous value that was overwritten. Extend
refs_update_symref under a new function name, to record the value after
the ref has been locked if the caller of refs_update_symref_extended
requests it via a new variable in the function call. Keep the original
refs_update_symref function with the same signature, but now as
a wrapper around refs_update_symref_extended.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: - added before_target to reftables backend
- added an extra safety check for transaction's existence in refs.c
v6: - no change
v7: - remove the whole before_target concept from the backends and
handle checking it in refs.c instead (thanks Karthik)
- rename the before_target to referent which is how the same concept
is called in the backends
- change commit prefix to be more in line with project standards
v8: no change
v9: - instead of adding parameters to refs_update_symref, rename what
was in v8 as refs_update_symref_extended and make refs_update_symref
a wrapper for that. This significantly reduces the number of files
that need to be touched, and avoids adding a lot of dummy NULL-s
in unrelated places.
v10: no change
v11: no change
v12: no change
v13: if referent is a non-symbolic ref, record the hash in referent and
signal this with a return value of -1
refs.c | 24 ++++++++++++++++++++++--
refs.h | 4 ++++
2 files changed, 26 insertions(+), 2 deletions(-)
diff --git a/refs.c b/refs.c
index 5f729ed412..c4500a7582 100644
--- a/refs.c
+++ b/refs.c
@@ -2115,6 +2115,13 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
int refs_update_symref(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg)
+{
+ return refs_update_symref_extended(refs, ref, target, logmsg, NULL);
+}
+
+int refs_update_symref_extended(struct ref_store *refs, const char *ref,
+ const char *target, const char *logmsg,
+ struct strbuf *referent)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
@@ -2125,10 +2132,24 @@ int refs_update_symref(struct ref_store *refs, const char *ref,
ref_transaction_update(transaction, ref, NULL, NULL,
target, NULL, REF_NO_DEREF,
logmsg, &err) ||
- ref_transaction_commit(transaction, &err)) {
+ ref_transaction_prepare(transaction, &err)) {
ret = error("%s", err.buf);
+ goto cleanup;
+ }
+ if (referent && refs_read_symbolic_ref(refs, ref, referent) == -2) {
+ struct object_id oid;
+ if (!refs_read_ref(refs, ref, &oid)) {
+ strbuf_addstr(referent, oid_to_hex(&oid));
+ ret = -1;
+ } else {
+ ret = 1;
+ }
}
+ if (ref_transaction_commit(transaction, &err))
+ ret = error("%s", err.buf);
+
+cleanup:
strbuf_release(&err);
if (transaction)
ref_transaction_free(transaction);
@@ -2948,4 +2969,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)
return (update->flags & REF_HAVE_OLD) &&
(!is_null_oid(&update->old_oid) || update->old_target);
}
-
diff --git a/refs.h b/refs.h
index f8b714ca1d..92622e807d 100644
--- a/refs.h
+++ b/refs.h
@@ -579,6 +579,10 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
int refs_update_symref(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg);
+int refs_update_symref_extended(struct ref_store *refs, const char *refname,
+ const char *target, const char *logmsg,
+ struct strbuf *referent);
+
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
UPDATE_REFS_DIE_ON_ERR,
--
2.47.0.296.gda1ecfef29.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v13 4/9] remote set-head: refactor for readability
2024-11-18 15:09 ` [PATCH v13 0/9] " Bence Ferdinandy
` (2 preceding siblings ...)
2024-11-18 15:09 ` [PATCH v13 3/9] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
@ 2024-11-18 15:09 ` Bence Ferdinandy
2024-11-18 15:09 ` [PATCH v13 5/9] remote set-head: better output for --auto Bence Ferdinandy
` (5 subsequent siblings)
9 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-18 15:09 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
Make two different readability refactors:
Rename strbufs "buf" and "buf2" to something more explanatory.
Instead of calling get_main_ref_store(the_repository) multiple times,
call it once and store the result in a new refs variable. Although this
change probably offers some performance benefits, the main purpose is to
shorten the line lengths of function calls using this variable.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v5: new patch (split from the next patch as a preparatory step)
v6: no change
v7: - change commit prefix to be more in line with project standards
v8: no change
v9: - further improve readability by renaming buf, and buf2 consistently
with how patch 6 was already done
v10: no change
v11: no change
v12: no change
v13: more precise wording for commit message
builtin/remote.c | 23 ++++++++++++-----------
1 file changed, 12 insertions(+), 11 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 76670ddd8b..1d68c5b2ba 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1402,8 +1402,9 @@ static int show(int argc, const char **argv, const char *prefix)
static int set_head(int argc, const char **argv, const char *prefix)
{
int i, opt_a = 0, opt_d = 0, result = 0;
- struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
char *head_name = NULL;
+ struct ref_store *refs = get_main_ref_store(the_repository);
struct option options[] = {
OPT_BOOL('a', "auto", &opt_a,
@@ -1415,7 +1416,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options,
builtin_remote_sethead_usage, 0);
if (argc)
- strbuf_addf(&buf, "refs/remotes/%s/HEAD", argv[0]);
+ strbuf_addf(&b_head, "refs/remotes/%s/HEAD", argv[0]);
if (!opt_a && !opt_d && argc == 2) {
head_name = xstrdup(argv[1]);
@@ -1434,25 +1435,25 @@ static int set_head(int argc, const char **argv, const char *prefix)
head_name = xstrdup(states.heads.items[0].string);
free_remote_ref_states(&states);
} else if (opt_d && !opt_a && argc == 1) {
- if (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF))
- result |= error(_("Could not delete %s"), buf.buf);
+ if (refs_delete_ref(refs, NULL, b_head.buf, NULL, REF_NO_DEREF))
+ result |= error(_("Could not delete %s"), b_head.buf);
} else
usage_with_options(builtin_remote_sethead_usage, options);
if (head_name) {
- strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name);
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", argv[0], head_name);
/* make sure it's valid */
- if (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))
- result |= error(_("Not a valid ref: %s"), buf2.buf);
- else if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote set-head"))
- result |= error(_("Could not setup %s"), buf.buf);
+ if (!refs_ref_exists(refs, b_remote_head.buf))
+ result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
+ else if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, "remote set-head"))
+ result |= error(_("Could not setup %s"), b_head.buf);
else if (opt_a)
printf("%s/HEAD set to %s\n", argv[0], head_name);
free(head_name);
}
- strbuf_release(&buf);
- strbuf_release(&buf2);
+ strbuf_release(&b_head);
+ strbuf_release(&b_remote_head);
return result;
}
--
2.47.0.296.gda1ecfef29.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v13 5/9] remote set-head: better output for --auto
2024-11-18 15:09 ` [PATCH v13 0/9] " Bence Ferdinandy
` (3 preceding siblings ...)
2024-11-18 15:09 ` [PATCH v13 4/9] remote set-head: refactor for readability Bence Ferdinandy
@ 2024-11-18 15:09 ` Bence Ferdinandy
2024-11-19 2:27 ` Junio C Hamano
2024-11-18 15:09 ` [PATCH v13 6/9] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
` (4 subsequent siblings)
9 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-18 15:09 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
Currently, set-head --auto will print a message saying "remote/HEAD set
to branch", which implies something was changed.
Change the output of --auto, so the output actually reflects what was
done: a) set a previously unset HEAD, b) change HEAD because remote
changed or c) no updates. As edge cases, if HEAD is changed from
a previous symbolic reference that was not a remote branch, explicitly
call attention to this fact, and also notify the user if the previous
reference was not a symbolic reference.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v1-v2:
was RFC in https://lore.kernel.org/git/20240910203835.2288291-1-bence@ferdinandy.com/
v3:
This patch was originally sent along when I thought set-head was
going to be invoked by fetch, but the discussion on the RFC
concluded that it should be not. This opened the possibility to make
it more explicit.
Note: although I feel both things the patch does are really just
cosmetic, an argument could be made for breaking it into two, one
for the no-op part and one for the --auto print update.
Was sent in:
https://lore.kernel.org/git/20240915221055.904107-1-bence@ferdinandy.com/
v4:
- changes are now handled atomically via the ref update transaction
- outputs have changed along the lines of Junio's suggestion
- minor refactor to set_head for improved legibility
v5: - the minor refactor has been split out into its own patch
v6: - fixed uninitialized prev_head
- fixed case of unusual previous target
- fixed a test that would have been actually broken at this patch
(the output was only correct with the later update to fetch)
- added 4 tests for the 4 output cases
v7: - change commit prefix to be more in line with project standards
- fixed tests to also work with the reftable backend
- renamed report function, fixed style issue with checking buf len
- fixed not releasing an strbuf
v8: no change
v9: - mark output strings in report_set_head_auto as translatable
- rename buf_prev to b_local_head for consistency
- use ${SQ} in tests instead of '\''
v10: no change
v11: no change
v12: no change
v13: added handling the edge case of previous remote/HEAD being
a detached HEAD
builtin/remote.c | 59 +++++++++++++++++++++++++++++++++++---------
t/t5505-remote.sh | 63 ++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 109 insertions(+), 13 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 1d68c5b2ba..a682ef5df2 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1399,10 +1399,38 @@ static int show(int argc, const char **argv, const char *prefix)
return result;
}
+static void report_set_head_auto(const char *remote, const char *head_name,
+ struct strbuf *b_local_head, int updateres) {
+ struct strbuf buf_prefix = STRBUF_INIT;
+ const char *prev_head = NULL;
+
+ strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
+ skip_prefix(b_local_head->buf, buf_prefix.buf, &prev_head);
+
+ if (prev_head && !strcmp(prev_head, head_name))
+ printf(_("'%s/HEAD' is unchanged and points to '%s'\n"),
+ remote, head_name);
+ else if (prev_head)
+ printf(_("'%s/HEAD' has changed from '%s' and now points to '%s'\n"),
+ remote, prev_head, head_name);
+ else if (!b_local_head->len)
+ printf(_("'%s/HEAD' is now created and points to '%s'\n"),
+ remote, head_name);
+ else if (!!updateres && b_local_head->len)
+ printf(_("'%s/HEAD' was detached at '%s' and now points to '%s'\n"),
+ remote, b_local_head->buf, head_name);
+ else
+ printf(_("'%s/HEAD' used to point to '%s' "
+ "(which is not a remote branch), but now points to '%s'\n"),
+ remote, b_local_head->buf, head_name);
+ strbuf_release(&buf_prefix);
+}
+
static int set_head(int argc, const char **argv, const char *prefix)
{
- int i, opt_a = 0, opt_d = 0, result = 0;
- struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
+ int i, opt_a = 0, opt_d = 0, result = 0, updateres;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
+ b_local_head = STRBUF_INIT;
char *head_name = NULL;
struct ref_store *refs = get_main_ref_store(the_repository);
@@ -1440,20 +1468,27 @@ static int set_head(int argc, const char **argv, const char *prefix)
} else
usage_with_options(builtin_remote_sethead_usage, options);
- if (head_name) {
- strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", argv[0], head_name);
- /* make sure it's valid */
- if (!refs_ref_exists(refs, b_remote_head.buf))
- result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
- else if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, "remote set-head"))
- result |= error(_("Could not setup %s"), b_head.buf);
- else if (opt_a)
- printf("%s/HEAD set to %s\n", argv[0], head_name);
- free(head_name);
+ if (!head_name)
+ goto cleanup;
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", argv[0], head_name);
+ if (!refs_ref_exists(refs, b_remote_head.buf)) {
+ result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
+ goto cleanup;
+ }
+ updateres = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
+ "remote set-head", &b_local_head);
+ if (updateres == -2) {
+ result |= error(_("Could not setup %s"), b_head.buf);
+ goto cleanup;
}
+ if (opt_a)
+ report_set_head_auto(argv[0], head_name, &b_local_head, updateres);
+cleanup:
+ free(head_name);
strbuf_release(&b_head);
strbuf_release(&b_remote_head);
+ strbuf_release(&b_local_head);
return result;
}
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 9b50276646..807df00ba7 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -432,12 +432,63 @@ test_expect_success 'set-head --auto' '
)
'
+test_expect_success 'set-head --auto detects creation' '
+ (
+ cd test &&
+ git update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto to update a non symbolic ref' '
+ (
+ cd test &&
+ git update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ git update-ref refs/remotes/origin/HEAD HEAD &&
+ HEAD=$(git log --pretty="%H") &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} was detached at ${SQ}${HEAD}${SQ} and now points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects no change' '
+ (
+ cd test &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects change' '
+ (
+ cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/ahead &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} has changed from ${SQ}ahead${SQ} and now points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects strange ref' '
+ (
+ cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD refs/heads/main &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} used to point to ${SQ}refs/heads/main${SQ} (which is not a remote branch), but now points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
(
cd test &&
git fetch two "refs/heads/*:refs/remotes/two/*" &&
git remote set-head --auto two >output 2>&1 &&
- echo "two/HEAD set to main" >expect &&
+ echo "${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
test_cmp expect output
)
'
@@ -456,6 +507,16 @@ test_expect_success 'set-head explicit' '
)
'
+test_expect_success 'set-head --auto reports change' '
+ (
+ cd test &&
+ git remote set-head origin side2 &&
+ git remote set-head --auto origin >output 2>&1 &&
+ echo "${SQ}origin/HEAD${SQ} has changed from ${SQ}side2${SQ} and now points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
cat >test/expect <<EOF
Pruning origin
URL: $(pwd)/one
--
2.47.0.296.gda1ecfef29.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v13 5/9] remote set-head: better output for --auto
2024-11-18 15:09 ` [PATCH v13 5/9] remote set-head: better output for --auto Bence Ferdinandy
@ 2024-11-19 2:27 ` Junio C Hamano
2024-11-19 10:29 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Junio C Hamano @ 2024-11-19 2:27 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
Bence Ferdinandy <bence@ferdinandy.com> writes:
> +static void report_set_head_auto(const char *remote, const char *head_name,
> + struct strbuf *b_local_head, int updateres) {
"updateres" was too mysterious a name. "res" stands for what,
"resource"?
Looking at the way the parameter is used by the code, it seems to
indicate that the remote HEAD originally was in a detached state, so
"was_detached" may be a better name, perhaps?
> + else if (!!updateres && b_local_head->len)
> + printf(_("'%s/HEAD' was detached at '%s' and now points to '%s'\n"),
> + remote, b_local_head->buf, head_name);
There is no need for !!; any non-zero integer is true. !! is useful
only in a context that takes only 0 and 1 (like when you are making
an assignment to a variable or a structure member that takes only 0
or 1).
> static int set_head(int argc, const char **argv, const char *prefix)
> {
> - int i, opt_a = 0, opt_d = 0, result = 0;
> - struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
> + int i, opt_a = 0, opt_d = 0, result = 0, updateres;
> + struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
> + b_local_head = STRBUF_INIT;
> @@ -1440,20 +1468,27 @@ static int set_head(int argc, const char **argv, const char *prefix)
> } else
> usage_with_options(builtin_remote_sethead_usage, options);
>
> - if (head_name) {
> - strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", argv[0], head_name);
> - /* make sure it's valid */
> - if (!refs_ref_exists(refs, b_remote_head.buf))
> - result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
> - else if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, "remote set-head"))
> - result |= error(_("Could not setup %s"), b_head.buf);
> - else if (opt_a)
> - printf("%s/HEAD set to %s\n", argv[0], head_name);
> - free(head_name);
> + if (!head_name)
> + goto cleanup;
> + strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", argv[0], head_name);
> + if (!refs_ref_exists(refs, b_remote_head.buf)) {
> + result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
> + goto cleanup;
> + }
OK, we refuse to allow a manual "remote set-head" to create a
dangling symref, which is a faithful rewrite from the original.
> + updateres = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
> + "remote set-head", &b_local_head);
> + if (updateres == -2) {
Where does this -2 come from? It is not the "you asked to read it
as a symref but it wasn't a symref" thing, which was mapped to -1
with [PATCH 3/9].
It is an unusual way to construct an extensible API function to say
"all different kinds of errors we happen to know when this
particular caller was written return -2, but some special cases are
not -2".
Rather, "all negatives, other than these selected few values we
special-case and handle, are errors" is more natural, isn't it?
Maybe I am misreading the code and missing where the -2 comes from
or the significance of the value? I dunno.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v13 5/9] remote set-head: better output for --auto
2024-11-19 2:27 ` Junio C Hamano
@ 2024-11-19 10:29 ` Bence Ferdinandy
2024-11-19 10:54 ` Junio C Hamano
0 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-19 10:29 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
On Tue Nov 19, 2024 at 03:27, Junio C Hamano <gitster@pobox.com> wrote:
> Bence Ferdinandy <bence@ferdinandy.com> writes:
>
>> +static void report_set_head_auto(const char *remote, const char *head_name,
>> + struct strbuf *b_local_head, int updateres) {
>
> "updateres" was too mysterious a name. "res" stands for what,
> "resource"?
>
> Looking at the way the parameter is used by the code, it seems to
> indicate that the remote HEAD originally was in a detached state, so
> "was_detached" may be a better name, perhaps?
"res" wanted to be short for result, but "was_detached" is definitely more readable.
>
>> + else if (!!updateres && b_local_head->len)
>> + printf(_("'%s/HEAD' was detached at '%s' and now points to '%s'\n"),
>> + remote, b_local_head->buf, head_name);
>
> There is no need for !!; any non-zero integer is true. !! is useful
> only in a context that takes only 0 and 1 (like when you are making
> an assignment to a variable or a structure member that takes only 0
> or 1).
>
>> static int set_head(int argc, const char **argv, const char *prefix)
>> {
>> - int i, opt_a = 0, opt_d = 0, result = 0;
>> - struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
>> + int i, opt_a = 0, opt_d = 0, result = 0, updateres;
>> + struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
>> + b_local_head = STRBUF_INIT;
>
>> @@ -1440,20 +1468,27 @@ static int set_head(int argc, const char **argv, const char *prefix)
>> } else
>> usage_with_options(builtin_remote_sethead_usage, options);
>>
>> - if (head_name) {
>> - strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", argv[0], head_name);
>> - /* make sure it's valid */
>> - if (!refs_ref_exists(refs, b_remote_head.buf))
>> - result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
>> - else if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, "remote set-head"))
>> - result |= error(_("Could not setup %s"), b_head.buf);
>> - else if (opt_a)
>> - printf("%s/HEAD set to %s\n", argv[0], head_name);
>> - free(head_name);
>> + if (!head_name)
>> + goto cleanup;
>> + strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", argv[0], head_name);
>> + if (!refs_ref_exists(refs, b_remote_head.buf)) {
>> + result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
>> + goto cleanup;
>> + }
>
> OK, we refuse to allow a manual "remote set-head" to create a
> dangling symref, which is a faithful rewrite from the original.
>
>> + updateres = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
>> + "remote set-head", &b_local_head);
>
>> + if (updateres == -2) {
>
> Where does this -2 come from? It is not the "you asked to read it
> as a symref but it wasn't a symref" thing, which was mapped to -1
> with [PATCH 3/9].
No, it is not, but it's also a mistake. It should be `updateres == 1`.
refs_update_symref_extended outputs -1 for "not a symref" and 1 for any other
error currently. Before I touched the code it was 1 for any error, so I left
that as is. So we want to error out on set_head if we get a 1 and continue if
we get 0 or -1 (and handle the difference in the report_set_head_auto).
Thanks for noticing, I'll get that fixed in v14.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v13 5/9] remote set-head: better output for --auto
2024-11-19 10:29 ` Bence Ferdinandy
@ 2024-11-19 10:54 ` Junio C Hamano
2024-11-19 11:33 ` Bence Ferdinandy
2024-11-20 12:49 ` Bence Ferdinandy
0 siblings, 2 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-11-19 10:54 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
"Bence Ferdinandy" <bence@ferdinandy.com> writes:
> No, it is not, but it's also a mistake. It should be `updateres == 1`.
> refs_update_symref_extended outputs -1 for "not a symref" and 1 for any other
> error currently. Before I touched the code it was 1 for any error, so I left
> that as is. So we want to error out on set_head if we get a 1 and continue if
> we get 0 or -1 (and handle the difference in the report_set_head_auto).
>
> Thanks for noticing, I'll get that fixed in v14.
It is good that somebody noticed it (and it may have happened to be
me), but if it is a "mistake" as you said, I wonder why none of your
tests caught it. Do we have a gap in test coverage?
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v13 5/9] remote set-head: better output for --auto
2024-11-19 10:54 ` Junio C Hamano
@ 2024-11-19 11:33 ` Bence Ferdinandy
2024-11-20 12:49 ` Bence Ferdinandy
1 sibling, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-19 11:33 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
On Tue Nov 19, 2024 at 11:54, Junio C Hamano <gitster@pobox.com> wrote:
> "Bence Ferdinandy" <bence@ferdinandy.com> writes:
>
>> No, it is not, but it's also a mistake. It should be `updateres == 1`.
>> refs_update_symref_extended outputs -1 for "not a symref" and 1 for any other
>> error currently. Before I touched the code it was 1 for any error, so I left
>> that as is. So we want to error out on set_head if we get a 1 and continue if
>> we get 0 or -1 (and handle the difference in the report_set_head_auto).
>>
>> Thanks for noticing, I'll get that fixed in v14.
>
> It is good that somebody noticed it (and it may have happened to be
> me), but if it is a "mistake" as you said, I wonder why none of your
> tests caught it. Do we have a gap in test coverage?
I think there is no test that is testing this branch:
updateres = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
"remote set-head", &b_local_head, 0);
if (updateres == 1) {
result |= error(_("Could not setup %s"), b_head.buf);
goto cleanup;
Running this in t/
grep -r "Could not setup"
also yield nothing, so that's probably true. I'm wondering what would be the
best way to trigger this error, refs_update_symref needs to fail for this.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v13 5/9] remote set-head: better output for --auto
2024-11-19 10:54 ` Junio C Hamano
2024-11-19 11:33 ` Bence Ferdinandy
@ 2024-11-20 12:49 ` Bence Ferdinandy
2024-11-20 23:56 ` Junio C Hamano
1 sibling, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-20 12:49 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
On Tue Nov 19, 2024 at 11:54, Junio C Hamano <gitster@pobox.com> wrote:
> "Bence Ferdinandy" <bence@ferdinandy.com> writes:
>
>> No, it is not, but it's also a mistake. It should be `updateres == 1`.
>> refs_update_symref_extended outputs -1 for "not a symref" and 1 for any other
>> error currently. Before I touched the code it was 1 for any error, so I left
>> that as is. So we want to error out on set_head if we get a 1 and continue if
>> we get 0 or -1 (and handle the difference in the report_set_head_auto).
>>
>> Thanks for noticing, I'll get that fixed in v14.
>
> It is good that somebody noticed it (and it may have happened to be
> me), but if it is a "mistake" as you said, I wonder why none of your
> tests caught it. Do we have a gap in test coverage?
>
> Thanks.
I've been looking into how I can force an error on remote set-head to test the
error branch. For the files backend it seems to be pretty easy
touch .git/refs/remotes/origin/HEAD.lock
git remote set-head [something]
rm .git/refs/remotes/origin/HEAD.lock
My problem is that in this case, since I want to force an error on a user
command, I don't see a way that is possible with git commands and I'm not sure
how I could manipulate the reftable file in a way that can be reversed and only
errors out the particular thing I need.
I've been looking through the functions called here and it seems other errors
all depend passing wrong parameters but that is not possible (and should not be
possible) to trigger with remote set-head, so I think something "manual" needs
to be done, like the above.
Since this particular test just wants to test what happens if
`refs_update_symref_extended` returns with 1 and not testing correct behaviour
of backends and such, would it be acceptable if this particular test case would
check for the backend and if it is reftables it will just pass without actually
checking and do the manually locking thing above for the files backend?
Thanks,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v13 5/9] remote set-head: better output for --auto
2024-11-20 12:49 ` Bence Ferdinandy
@ 2024-11-20 23:56 ` Junio C Hamano
0 siblings, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-11-20 23:56 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
"Bence Ferdinandy" <bence@ferdinandy.com> writes:
> Since this particular test just wants to test what happens if
> `refs_update_symref_extended` returns with 1 and not testing correct behaviour
> of backends and such, would it be acceptable if this particular test case would
> check for the backend and if it is reftables it will just pass without actually
> checking and do the manually locking thing above for the files backend?
I think we have some pre-made test prerequisite to skip tests unless
run with a particular ref backend, exactly for that. Perhaps
test_expect_success REFFILES 'block update to check error' '
... manually block update and see how the operation
... errors out
'
or something?
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v13 6/9] refs: add TRANSACTION_CREATE_EXISTS error
2024-11-18 15:09 ` [PATCH v13 0/9] " Bence Ferdinandy
` (4 preceding siblings ...)
2024-11-18 15:09 ` [PATCH v13 5/9] remote set-head: better output for --auto Bence Ferdinandy
@ 2024-11-18 15:09 ` Bence Ferdinandy
2024-11-18 15:09 ` [PATCH v13 7/9] refs: add create_only option to refs_update_symref_extended Bence Ferdinandy
` (3 subsequent siblings)
9 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-18 15:09 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
Currently there is only one special error for transaction, for when
there is a naming conflict, all other errors are dumped under a generic
error. Add a new special error case for when the caller requests the
reference to be updated only when it does not yet exist and the
reference actually does exist.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: no change
v6: no change
v7: - change commit prefix to be more in line with project standards
- changed error checking to Karthik's suggestion
v8: no change
v9: - no change
v10: no change
v11: no change
v12: no change
v13: no change
refs.h | 4 +++-
refs/files-backend.c | 24 ++++++++++++++++--------
refs/reftable-backend.c | 6 ++++--
3 files changed, 23 insertions(+), 11 deletions(-)
diff --git a/refs.h b/refs.h
index 92622e807d..6a32f82f8c 100644
--- a/refs.h
+++ b/refs.h
@@ -768,8 +768,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,
/* Naming conflict (for example, the ref names A and A/B conflict). */
#define TRANSACTION_NAME_CONFLICT -1
+/* When only creation was requested, but the ref already exists. */
+#define TRANSACTION_CREATE_EXISTS -2
/* All other errors. */
-#define TRANSACTION_GENERIC_ERROR -2
+#define TRANSACTION_GENERIC_ERROR -3
/*
* Perform the preparatory stages of committing `transaction`. Acquire
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 81e650ec48..86674705a7 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2501,14 +2501,18 @@ static int split_symref_update(struct ref_update *update,
static int check_old_oid(struct ref_update *update, struct object_id *oid,
struct strbuf *err)
{
+ int ret = TRANSACTION_GENERIC_ERROR;
+
if (!(update->flags & REF_HAVE_OLD) ||
oideq(oid, &update->old_oid))
return 0;
- if (is_null_oid(&update->old_oid))
+ if (is_null_oid(&update->old_oid)) {
strbuf_addf(err, "cannot lock ref '%s': "
"reference already exists",
ref_update_original_update_refname(update));
+ ret = TRANSACTION_CREATE_EXISTS;
+ }
else if (is_null_oid(oid))
strbuf_addf(err, "cannot lock ref '%s': "
"reference is missing but expected %s",
@@ -2521,7 +2525,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,
oid_to_hex(oid),
oid_to_hex(&update->old_oid));
- return -1;
+ return ret;
}
/*
@@ -2601,9 +2605,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
- } else if (check_old_oid(update, &lock->old_oid, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto out;
+ } else {
+ ret = check_old_oid(update, &lock->old_oid, err);
+ if (ret) {
+ goto out;
+ }
}
} else {
/*
@@ -2634,9 +2640,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,
update->old_target);
ret = TRANSACTION_GENERIC_ERROR;
goto out;
- } else if (check_old_oid(update, &lock->old_oid, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto out;
+ } else {
+ ret = check_old_oid(update, &lock->old_oid, err);
+ if (ret) {
+ goto out;
+ }
}
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 60cb83f23a..4f2e615eae 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1208,10 +1208,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
goto done;
}
} else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) {
- if (is_null_oid(&u->old_oid))
+ ret = TRANSACTION_NAME_CONFLICT;
+ if (is_null_oid(&u->old_oid)) {
strbuf_addf(err, _("cannot lock ref '%s': "
"reference already exists"),
ref_update_original_update_refname(u));
+ ret = TRANSACTION_CREATE_EXISTS;
+ }
else if (is_null_oid(¤t_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
"reference is missing but expected %s"),
@@ -1223,7 +1226,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
ref_update_original_update_refname(u),
oid_to_hex(¤t_oid),
oid_to_hex(&u->old_oid));
- ret = -1;
goto done;
}
--
2.47.0.296.gda1ecfef29.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v13 7/9] refs: add create_only option to refs_update_symref_extended
2024-11-18 15:09 ` [PATCH v13 0/9] " Bence Ferdinandy
` (5 preceding siblings ...)
2024-11-18 15:09 ` [PATCH v13 6/9] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
@ 2024-11-18 15:09 ` Bence Ferdinandy
2024-11-19 2:54 ` Junio C Hamano
2024-11-18 15:09 ` [PATCH v13 8/9] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
` (2 subsequent siblings)
9 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-18 15:09 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
Allow the caller to specify that it only wants to update the symref if
it does not already exist. Silently ignore the error from the
transaction API if the symref already exists.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: no change
v6: - switched from bool to int for create_only
- refactored logic in refs_update_symref with goto as layed out by
Junio
v7: - change commit prefix to be more in line with project standards
- refactored code to accommodate changes in the first patch, but
otherwise no change
v8: no change
v9: - no change (except for following through the
refs_update_symref_extended refactoring)
v10: no change
v11: no change
v12: no change
v13: changes only due to changes in previous patch
builtin/remote.c | 2 +-
refs.c | 33 ++++++++++++++++++++++++---------
refs.h | 2 +-
3 files changed, 26 insertions(+), 11 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index a682ef5df2..7b4eb0e30b 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1476,7 +1476,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
goto cleanup;
}
updateres = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
- "remote set-head", &b_local_head);
+ "remote set-head", &b_local_head, 0);
if (updateres == -2) {
result |= error(_("Could not setup %s"), b_head.buf);
goto cleanup;
diff --git a/refs.c b/refs.c
index c4500a7582..f4d8a8dcb1 100644
--- a/refs.c
+++ b/refs.c
@@ -2116,26 +2116,38 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
int refs_update_symref(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg)
{
- return refs_update_symref_extended(refs, ref, target, logmsg, NULL);
+ return refs_update_symref_extended(refs, ref, target, logmsg, NULL, 0);
}
int refs_update_symref_extended(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg,
- struct strbuf *referent)
+ struct strbuf *referent, int create_only)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
- int ret = 0;
+ int ret = 0, prepret = 0;
transaction = ref_store_transaction_begin(refs, &err);
- if (!transaction ||
- ref_transaction_update(transaction, ref, NULL, NULL,
- target, NULL, REF_NO_DEREF,
- logmsg, &err) ||
- ref_transaction_prepare(transaction, &err)) {
+ if (!transaction) {
+ error_return:
ret = error("%s", err.buf);
goto cleanup;
}
+ if (create_only) {
+ if (ref_transaction_create(transaction, ref, NULL, target,
+ REF_NO_DEREF, logmsg, &err))
+ goto error_return;
+ prepret = ref_transaction_prepare(transaction, &err);
+ if (prepret && prepret != TRANSACTION_CREATE_EXISTS)
+ goto error_return;
+ } else {
+ if (ref_transaction_update(transaction, ref, NULL, NULL,
+ target, NULL, REF_NO_DEREF,
+ logmsg, &err) ||
+ ref_transaction_prepare(transaction, &err))
+ goto error_return;
+ }
+
if (referent && refs_read_symbolic_ref(refs, ref, referent) == -2) {
struct object_id oid;
if (!refs_read_ref(refs, ref, &oid)) {
@@ -2146,8 +2158,11 @@ int refs_update_symref_extended(struct ref_store *refs, const char *ref,
}
}
+ if (prepret == TRANSACTION_CREATE_EXISTS)
+ goto cleanup;
+
if (ref_transaction_commit(transaction, &err))
- ret = error("%s", err.buf);
+ goto error_return;
cleanup:
strbuf_release(&err);
diff --git a/refs.h b/refs.h
index 6a32f82f8c..f131e89fe6 100644
--- a/refs.h
+++ b/refs.h
@@ -581,7 +581,7 @@ int refs_update_symref(struct ref_store *refs, const char *refname,
int refs_update_symref_extended(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg,
- struct strbuf *referent);
+ struct strbuf *referent, int create_only);
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
--
2.47.0.296.gda1ecfef29.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v13 7/9] refs: add create_only option to refs_update_symref_extended
2024-11-18 15:09 ` [PATCH v13 7/9] refs: add create_only option to refs_update_symref_extended Bence Ferdinandy
@ 2024-11-19 2:54 ` Junio C Hamano
0 siblings, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-11-19 2:54 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
Bence Ferdinandy <bence@ferdinandy.com> writes:
> Allow the caller to specify that it only wants to update the symref if
> it does not already exist. Silently ignore the error from the
> transaction API if the symref already exists.
Because it is not even an error, from the point of view of "create
only if it does not exist", checking the current condition and
finding that there already is one, this makes perfect sense.
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v13 8/9] fetch: set remote/HEAD if it does not exist
2024-11-18 15:09 ` [PATCH v13 0/9] " Bence Ferdinandy
` (6 preceding siblings ...)
2024-11-18 15:09 ` [PATCH v13 7/9] refs: add create_only option to refs_update_symref_extended Bence Ferdinandy
@ 2024-11-18 15:09 ` Bence Ferdinandy
2024-11-19 3:16 ` Junio C Hamano
2024-11-18 15:09 ` [PATCH v13 9/9] fetch set_head: handle mirrored bare repositories Bence Ferdinandy
2024-11-21 22:55 ` [PATCH v14 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
9 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-18 15:09 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
If the user has remote/HEAD set already and it looks like it has changed
on the server, then print a message, otherwise set it if we can.
Silently pass if the user already has the same remote/HEAD set as
reported by the server or if we encounter any errors along the way.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v3: - does not rely on remote set-head anymore so it only authenticates
once
- uses the new REF_CREATE_ONLY to atomically check if the ref exists
and only write it if it doesn't
- in all other cases the maximum it does is print a warning
v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,
but updated transaction api to request a silent create only
- it now uses the atomic before_target to determine reporting
- refactored for legibility
v5: - instead of printing a not too useful message, it now fails
silently, this in line with the objective to only set up
remote/HEAD automatically if the right thing is trivial, for
everything else there is remote set-head
- fixed all failing tests
- added two new tests, one for checking if remote/HEAD is set to the
correct one, and one to test that we do not override remote/HEAD
if it has changed on the server from what we have locally
v6: - fixed style issues and unintended extra empty line
- updated function call with bool to int from previous patch's
change
- removed calls to error(...) inherited from builtin/remote.c so we
actually fail silently
- set the test for remote set-head --auto to the correct value here,
which was previously erronously set in the remote set-head patch
v7: - no change
v8: - changed logmsg in call to refs_update_symref from "remote
set-head" to "fetch"
v9: - follow through with refs_update_symref_extended
- fix test errors uncovered by the new patch
v10: no change
v11: fixed some memory leaks
v12: no change
v13: - fix printed information if the local HEAD is detached
- remove accidental formatting noise in a test
builtin/fetch.c | 99 ++++++++++++++++++++++++++++++++
t/t4207-log-decoration-colors.sh | 3 +-
t/t5505-remote.sh | 21 +++++--
t/t5510-fetch.sh | 24 ++++++++
t/t5512-ls-remote.sh | 2 +
t/t5514-fetch-multiple.sh | 17 +++++-
t/t5516-fetch-push.sh | 3 +-
t/t5527-fetch-odd-refs.sh | 3 +-
t/t7900-maintenance.sh | 3 +-
t/t9210-scalar.sh | 5 +-
t/t9211-scalar-clone.sh | 6 +-
t/t9902-completion.sh | 65 +++++++++++++++++++++
12 files changed, 234 insertions(+), 17 deletions(-)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 18eff4e5fa..3d70cd1add 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1578,6 +1578,97 @@ static int backfill_tags(struct display_state *display_state,
return retcode;
}
+static void report_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;
+
+ strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
+ skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);
+
+ if (prev_head && strcmp(prev_head, head_name)) {
+ printf("'HEAD' at '%s' has changed from '%s' to '%s'\n",
+ remote, prev_head, head_name);
+ printf("Run 'git remote set-head %s %s' to follow the change.\n",
+ remote, head_name);
+ }
+ else if (!!updateres && buf_prev->len) {
+ printf("detached 'HEAD' at '%s' has changed from '%s' to '%s'\n",
+ remote, buf_prev->buf, head_name);
+ printf("Run 'git remote set-head %s %s' to follow the change.\n",
+ remote, head_name);
+ }
+ strbuf_release(&buf_prefix);
+}
+
+static const char *strip_refshead(const char *name){
+ skip_prefix(name, "refs/heads/", &name);
+ return name;
+}
+
+static int set_head(const struct ref *remote_refs)
+{
+ int result = 0, updateres;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
+ b_local_head = STRBUF_INIT;
+ const char *remote = gtransport->remote->name;
+ char *head_name = NULL;
+ struct ref *ref, *matches;
+ struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
+ struct refspec_item refspec = {
+ .force = 0,
+ .pattern = 1,
+ .src = (char *) "refs/heads/*",
+ .dst = (char *) "refs/heads/*",
+ };
+ struct string_list heads = STRING_LIST_INIT_DUP;
+ struct ref_store *refs = get_main_ref_store(the_repository);
+
+ get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
+ matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
+ fetch_map, 1);
+ for (ref = matches; ref; ref = ref->next) {
+ string_list_append(&heads, strip_refshead(ref->name));
+ }
+
+
+ if (!heads.nr)
+ result = 1;
+ else if (heads.nr > 1)
+ result = 1;
+ else
+ head_name = xstrdup(heads.items[0].string);
+
+ if (!head_name)
+ goto cleanup;
+ strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name);
+ /* make sure it's valid */
+ if (!refs_ref_exists(refs, b_remote_head.buf)) {
+ result = 1;
+ goto cleanup;
+ }
+ updateres = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
+ "fetch", &b_local_head, 1);
+ if (updateres == -1) {
+ result = 1;
+ goto cleanup;
+ }
+ report_set_head(remote, head_name, &b_local_head, updateres);
+
+
+
+cleanup:
+ free(head_name);
+ free_refs(fetch_map);
+ free_refs(matches);
+ string_list_clear(&heads, 0);
+ strbuf_release(&b_head);
+ strbuf_release(&b_local_head);
+ strbuf_release(&b_remote_head);
+ return result;
+}
+
static int do_fetch(struct transport *transport,
struct refspec *rs,
const struct fetch_config *config)
@@ -1647,6 +1738,8 @@ static int do_fetch(struct transport *transport,
"refs/tags/");
}
+ strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
+
if (must_list_refs) {
trace2_region_enter("fetch", "remote_refs", the_repository);
remote_refs = transport_get_remote_refs(transport,
@@ -1791,6 +1884,12 @@ static int do_fetch(struct transport *transport,
"you need to specify exactly one branch with the --set-upstream option"));
}
}
+ if (set_head(remote_refs))
+ ;
+ /*
+ * Way too many cases where this can go wrong
+ * so let's just fail silently for now.
+ */
cleanup:
if (retcode) {
diff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh
index 73ea9e5155..d55d22cb2f 100755
--- a/t/t4207-log-decoration-colors.sh
+++ b/t/t4207-log-decoration-colors.sh
@@ -59,7 +59,8 @@ ${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \
${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \
-${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1
+${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit}, \
+${c_reset}${c_remoteBranch}other/HEAD${c_reset}${c_commit})${c_reset} A1
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
${c_stash}refs/stash${c_reset}${c_commit})${c_reset} On main: Changes to A.t
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 807df00ba7..660310239c 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -74,7 +74,7 @@ test_expect_success 'add another remote' '
cd test &&
git remote add -f second ../two &&
tokens_match "origin second" "$(git remote)" &&
- check_tracking_branch second main side another &&
+ check_tracking_branch second main side another HEAD &&
git for-each-ref "--format=%(refname)" refs/remotes |
sed -e "/^refs\/remotes\/origin\//d" \
-e "/^refs\/remotes\/second\//d" >actual &&
@@ -488,7 +488,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
cd test &&
git fetch two "refs/heads/*:refs/remotes/two/*" &&
git remote set-head --auto two >output 2>&1 &&
- echo "${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
+ echo "${SQ}two/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}" >expect &&
test_cmp expect output
)
'
@@ -776,8 +776,10 @@ test_expect_success 'reject --no-no-tags' '
'
cat >one/expect <<\EOF
+ apis/HEAD -> apis/main
apis/main
apis/side
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -795,11 +797,14 @@ test_expect_success 'update' '
'
cat >one/expect <<\EOF
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
+ manduca/HEAD -> manduca/main
manduca/main
manduca/side
+ megaloprepus/HEAD -> megaloprepus/main
megaloprepus/main
megaloprepus/side
EOF
@@ -807,7 +812,7 @@ EOF
test_expect_success 'update with arguments' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -839,10 +844,13 @@ test_expect_success 'update --prune' '
'
cat >one/expect <<-\EOF
+ apis/HEAD -> apis/main
apis/main
apis/side
+ manduca/HEAD -> manduca/main
manduca/main
manduca/side
+ megaloprepus/HEAD -> megaloprepus/main
megaloprepus/main
megaloprepus/side
EOF
@@ -850,7 +858,7 @@ EOF
test_expect_success 'update default' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -862,6 +870,7 @@ test_expect_success 'update default' '
'
cat >one/expect <<\EOF
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -870,7 +879,7 @@ EOF
test_expect_success 'update default (overridden, with funny whitespace)' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -884,7 +893,7 @@ test_expect_success 'update default (overridden, with funny whitespace)' '
test_expect_success 'update (with remotes.default defined)' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 0890b9f61c..87698341f5 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -75,6 +75,30 @@ test_expect_success "fetch test for-merge" '
cut -f -2 .git/FETCH_HEAD >actual &&
test_cmp expected actual'
+test_expect_success "fetch test remote HEAD" '
+ cd "$D" &&
+ cd two &&
+ git fetch &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"'
+
+test_expect_success "fetch test remote HEAD change" '
+ cd "$D" &&
+ cd two &&
+ git switch -c other &&
+ git push -u origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git fetch &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"'
+
test_expect_success 'fetch --prune on its own works as expected' '
cd "$D" &&
git clone . prune &&
diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh
index 64b3491e4e..1b3865e154 100755
--- a/t/t5512-ls-remote.sh
+++ b/t/t5512-ls-remote.sh
@@ -293,6 +293,8 @@ test_expect_success 'ls-remote with filtered symref (refname)' '
cat >expect <<-EOF &&
ref: refs/heads/main HEAD
$rev HEAD
+ ref: refs/remotes/origin/main refs/remotes/origin/HEAD
+ $rev refs/remotes/origin/HEAD
EOF
git ls-remote --symref . HEAD >actual &&
test_cmp expect actual
diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh
index 579872c258..e3482b27b2 100755
--- a/t/t5514-fetch-multiple.sh
+++ b/t/t5514-fetch-multiple.sh
@@ -45,14 +45,17 @@ test_expect_success setup '
'
cat > test/expect << EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -97,6 +100,7 @@ cat > expect << EOF
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
@@ -112,8 +116,10 @@ test_expect_success 'git fetch --multiple (but only one remote)' '
'
cat > expect << EOF
+ one/HEAD -> one/main
one/main
one/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -141,7 +147,7 @@ test_expect_success 'git fetch --multiple (bad remote names)' '
test_expect_success 'git fetch --all (skipFetchAll)' '
(cd test4 &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -153,11 +159,14 @@ test_expect_success 'git fetch --all (skipFetchAll)' '
'
cat > expect << EOF
+ one/HEAD -> one/main
one/main
one/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -165,7 +174,7 @@ EOF
test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '
(cd test4 &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -221,14 +230,17 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' '
create_fetch_all_expect () {
cat >expect <<-\EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -265,6 +277,7 @@ test_expect_success 'git fetch (fetch all remotes with fetch.all = true)' '
create_fetch_one_expect () {
cat >expect <<-\EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 331778bd42..5a051aa0c7 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -1395,7 +1395,8 @@ test_expect_success 'fetch follows tags by default' '
git tag -m "annotated" tag &&
git for-each-ref >tmp1 &&
sed -n "p; s|refs/heads/main$|refs/remotes/origin/main|p" tmp1 |
- sort -k 3 >../expect
+ sed -n "p; s|refs/heads/main$|refs/remotes/origin/HEAD|p" |
+ sort -k 4 >../expect
) &&
test_when_finished "rm -rf dst" &&
git init dst &&
diff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh
index 98ece27c6a..d3996af6ee 100755
--- a/t/t5527-fetch-odd-refs.sh
+++ b/t/t5527-fetch-odd-refs.sh
@@ -52,7 +52,8 @@ test_expect_success LONG_REF 'fetch handles extremely long refname' '
long
main
EOF
- git for-each-ref --format="%(subject)" refs/remotes/long >actual &&
+ git for-each-ref --format="%(subject)" refs/remotes/long \
+ --exclude=refs/remotes/long/HEAD >actual &&
test_cmp expect actual
'
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index c224c8450c..edb85b7145 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -329,7 +329,8 @@ test_expect_success 'incremental-repack task' '
# Delete refs that have not been repacked in these packs.
git for-each-ref --format="delete %(refname)" \
- refs/prefetch refs/tags refs/remotes >refs &&
+ refs/prefetch refs/tags refs/remotes \
+ --exclude=refs/remotes/*/HEAD >refs &&
git update-ref --stdin <refs &&
# Replace the object directory with this pack layout.
diff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh
index a30b2c9f70..2237844550 100755
--- a/t/t9210-scalar.sh
+++ b/t/t9210-scalar.sh
@@ -151,7 +151,8 @@ test_expect_success 'scalar clone' '
"$(pwd)" &&
git for-each-ref --format="%(refname)" refs/remotes/origin/ >actual &&
- echo "refs/remotes/origin/parallel" >expect &&
+ echo "refs/remotes/origin/HEAD" >>expect &&
+ echo "refs/remotes/origin/parallel" >>expect &&
test_cmp expect actual &&
test_path_is_missing 1/2 &&
@@ -220,7 +221,7 @@ test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '
done
'
- test_expect_success 'scalar reconfigure --all with detached HEADs' '
+test_expect_success 'scalar reconfigure --all with detached HEADs' '
repos="two three four" &&
for num in $repos
do
diff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh
index c16ea67c1d..d9cb6b9a3e 100755
--- a/t/t9211-scalar-clone.sh
+++ b/t/t9211-scalar-clone.sh
@@ -32,7 +32,7 @@ test_expect_success 'set up repository to clone' '
)
'
-cleanup_clone () {
+cleanup_clone() {
rm -rf "$1"
}
@@ -128,7 +128,7 @@ test_expect_success '--single-branch clones HEAD only' '
(
cd $enlistment/src &&
git for-each-ref refs/remotes/origin >out &&
- test_line_count = 1 out &&
+ test_line_count = 2 out &&
grep "refs/remotes/origin/base" out
) &&
@@ -142,7 +142,7 @@ test_expect_success '--no-single-branch clones all branches' '
(
cd $enlistment/src &&
git for-each-ref refs/remotes/origin >out &&
- test_line_count = 2 out &&
+ test_line_count = 3 out &&
grep "refs/remotes/origin/base" out &&
grep "refs/remotes/origin/parallel" out
) &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index cc6aa9f0cd..b663c4609e 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -658,6 +658,7 @@ test_expect_success '__git_refs - simple' '
HEAD
main
matching-branch
+ other/HEAD
other/branch-in-other
other/main-in-other
matching-tag
@@ -673,6 +674,7 @@ test_expect_success '__git_refs - full refs' '
cat >expected <<-EOF &&
refs/heads/main
refs/heads/matching-branch
+ refs/remotes/other/HEAD
refs/remotes/other/branch-in-other
refs/remotes/other/main-in-other
refs/tags/matching-tag
@@ -729,6 +731,7 @@ test_expect_success '__git_refs - remote on local file system - full refs' '
test_expect_success '__git_refs - configured remote' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -756,6 +759,7 @@ test_expect_success '__git_refs - configured remote - full refs' '
test_expect_success '__git_refs - configured remote - repo given on the command line' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -787,6 +791,7 @@ test_expect_success '__git_refs - configured remote - full refs - repo given on
test_expect_success '__git_refs - configured remote - remote name matches a directory' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -875,12 +880,14 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer
HEAD
main
matching-branch
+ other/HEAD
other/ambiguous
other/branch-in-other
other/main-in-other
remote/ambiguous
remote/branch-in-remote
matching-tag
+ HEAD
branch-in-other
branch-in-remote
main-in-other
@@ -904,6 +911,7 @@ test_expect_success '__git_refs - after --opt=' '
HEAD
main
matching-branch
+ other/HEAD
other/branch-in-other
other/main-in-other
matching-tag
@@ -919,6 +927,7 @@ test_expect_success '__git_refs - after --opt= - full refs' '
cat >expected <<-EOF &&
refs/heads/main
refs/heads/matching-branch
+ refs/remotes/other/HEAD
refs/remotes/other/branch-in-other
refs/remotes/other/main-in-other
refs/tags/matching-tag
@@ -935,6 +944,7 @@ test_expect_success '__git refs - excluding refs' '
^HEAD
^main
^matching-branch
+ ^other/HEAD
^other/branch-in-other
^other/main-in-other
^matching-tag
@@ -950,6 +960,7 @@ test_expect_success '__git refs - excluding full refs' '
cat >expected <<-EOF &&
^refs/heads/main
^refs/heads/matching-branch
+ ^refs/remotes/other/HEAD
^refs/remotes/other/branch-in-other
^refs/remotes/other/main-in-other
^refs/tags/matching-tag
@@ -975,6 +986,7 @@ test_expect_success '__git_refs - do not filter refs unless told so' '
main
matching-branch
matching/branch
+ other/HEAD
other/branch-in-other
other/main-in-other
other/matching/branch-in-other
@@ -1095,6 +1107,7 @@ test_expect_success '__git_complete_refs - simple' '
HEAD Z
main Z
matching-branch Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
matching-tag Z
@@ -1123,6 +1136,7 @@ test_expect_success '__git_complete_refs - matching' '
test_expect_success '__git_complete_refs - remote' '
sed -e "s/Z$//" >expected <<-EOF &&
HEAD Z
+ HEAD Z
branch-in-other Z
main-in-other Z
EOF
@@ -1139,9 +1153,11 @@ test_expect_success '__git_complete_refs - track' '
HEAD Z
main Z
matching-branch Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
matching-tag Z
+ HEAD Z
branch-in-other Z
main-in-other Z
EOF
@@ -1184,6 +1200,7 @@ test_expect_success '__git_complete_refs - suffix' '
HEAD.
main.
matching-branch.
+ other/HEAD.
other/branch-in-other.
other/main-in-other.
matching-tag.
@@ -1199,6 +1216,7 @@ test_expect_success '__git_complete_refs - suffix' '
test_expect_success '__git_complete_fetch_refspecs - simple' '
sed -e "s/Z$//" >expected <<-EOF &&
HEAD:HEAD Z
+ HEAD:HEAD Z
branch-in-other:branch-in-other Z
main-in-other:main-in-other Z
EOF
@@ -1225,6 +1243,7 @@ test_expect_success '__git_complete_fetch_refspecs - matching' '
test_expect_success '__git_complete_fetch_refspecs - prefix' '
sed -e "s/Z$//" >expected <<-EOF &&
+HEAD:HEAD Z
+ +HEAD:HEAD Z
+branch-in-other:branch-in-other Z
+main-in-other:main-in-other Z
EOF
@@ -1289,6 +1308,7 @@ test_expect_success '__git_complete_worktree_paths with -C' '
test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '
test_completion "git switch " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1435,11 +1455,13 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena
test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '
test_completion "git checkout " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1461,6 +1483,7 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl
test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '
GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git switch --guess " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1470,6 +1493,7 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G
test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '
test_completion "git switch --no-guess --guess " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1490,6 +1514,7 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1498,11 +1523,13 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete
test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '
GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git checkout --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1514,6 +1541,7 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1522,11 +1550,13 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '
test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '
test_completion "git checkout --no-guess --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1538,6 +1568,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1550,6 +1581,7 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1559,11 +1591,13 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a
test_config checkout.guess true &&
test_completion "git checkout " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1573,11 +1607,13 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.
test_config checkout.guess false &&
test_completion "git checkout --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1590,6 +1626,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1601,6 +1638,7 @@ test_expect_success 'git switch - with --detach, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1612,6 +1650,7 @@ test_expect_success 'git checkout - with --detach, complete only references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1783,6 +1822,7 @@ test_expect_success 'git switch - with -d, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1794,6 +1834,7 @@ test_expect_success 'git checkout - with -d, complete only references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1801,10 +1842,12 @@ test_expect_success 'git checkout - with -d, complete only references' '
test_expect_success 'git switch - with --track, complete only remote branches' '
test_completion "git switch --track " <<-\EOF &&
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
test_completion "git switch -t " <<-\EOF
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1812,10 +1855,12 @@ test_expect_success 'git switch - with --track, complete only remote branches' '
test_expect_success 'git checkout - with --track, complete only remote branches' '
test_completion "git checkout --track " <<-\EOF &&
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
test_completion "git checkout -t " <<-\EOF
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1834,6 +1879,7 @@ test_expect_success 'git checkout - with --no-track, complete only local referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1845,6 +1891,7 @@ test_expect_success 'git switch - with -c, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1856,6 +1903,7 @@ test_expect_success 'git switch - with -C, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1867,6 +1915,7 @@ test_expect_success 'git switch - with -c and --track, complete all references'
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1878,6 +1927,7 @@ test_expect_success 'git switch - with -C and --track, complete all references'
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1889,6 +1939,7 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1900,6 +1951,7 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1911,6 +1963,7 @@ test_expect_success 'git checkout - with -b, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1922,6 +1975,7 @@ test_expect_success 'git checkout - with -B, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1933,6 +1987,7 @@ test_expect_success 'git checkout - with -b and --track, complete all references
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1944,6 +1999,7 @@ test_expect_success 'git checkout - with -B and --track, complete all references
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1955,6 +2011,7 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1966,6 +2023,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1973,6 +2031,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen
test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '
test_completion "git switch -c " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1982,6 +2041,7 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem
test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '
test_completion "git switch -C " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2019,6 +2079,7 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche
test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '
test_completion "git checkout -b " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2028,6 +2089,7 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r
test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '
test_completion "git checkout -B " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2065,6 +2127,7 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc
test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '
test_completion "git switch --orphan " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2080,6 +2143,7 @@ test_expect_success 'git switch - --orphan with branch already provided complete
test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '
test_completion "git checkout --orphan " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2093,6 +2157,7 @@ test_expect_success 'git checkout - --orphan with branch already provided comple
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
--
2.47.0.296.gda1ecfef29.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v13 8/9] fetch: set remote/HEAD if it does not exist
2024-11-18 15:09 ` [PATCH v13 8/9] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
@ 2024-11-19 3:16 ` Junio C Hamano
2024-11-19 11:27 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Junio C Hamano @ 2024-11-19 3:16 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
Bence Ferdinandy <bence@ferdinandy.com> writes:
> If the user has remote/HEAD set already and it looks like it has changed
> on the server, then print a message, otherwise set it if we can.
The user uses remote/origin/HEAD to point at the "primary thing the
user is interested in at the remote 'origin'". "git clone"
propagates remote's HEAD to local because their HEAD is the opinion
of the remote which branch they want the users to consider the
primary thing. But the user can have valid reasons to consider a
branch different from what the remote suggests as the primary thing,
and an explicit "set-head" is a way to do so.
After such a set-up is made and the user is perfectly happy, would
it make sense to repeatedly remind that their HEAD points at
something the user is not interested in?
Perhaps it may make sense when given the "--verbose" option, or
after the first time the difference was noticed, but otherwise, I
suspect it becomes annoying to those who keep them deliberately
different from each other.
I wonder if it is more sensible to automate for these two different
classes of users with a new configuration per remote.
- When "git clone" initially sets up, or "git remote set-head
--auto" matches the local to the remote, we set the new
"remote.$name.autoUpdateHEAD" configuration variable to true.
- When "git remote set-head" explicitly sets remote/$name/HEAD
to some value (if we can detect the case where it is different
from what the remote has, that would be a plus, but it incurrs
network traffic, so care must be taken to design this part), we
drop the "remote.$name.autoUpdateHEAD" configuration variable,
if and only if it is set to true.
- When remote.$name.autoUpdateHEAD configuration is true, "git
fetch" automatically matches remote/$name/HEAD to what the HEAD
at the remote points at. If remote.$name.autoUpdateHEAD is set
to some other value (e.g., "warn"), show the warning so that the
user can choose to do "git remote set-head" to a specific value
(which would not drop the remote.$name.autoUpdateHEAD set to
"warn") or "git remote set-head --auto" (which would start to
follow whatever remote uses).
or something like that?
With something like the above, those who just want to follow along
would get remote.$name.autoUpdateHEAD=true and silently follow
along, while those who want to explicitly control would be able to
do so. I dunno, but I am fairly negative on a persistent warning
message that tells the user that the only normal case is to have
remote/origin/HEAD point at the same branch the remote's HEAD points
at.
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v13 8/9] fetch: set remote/HEAD if it does not exist
2024-11-19 3:16 ` Junio C Hamano
@ 2024-11-19 11:27 ` Bence Ferdinandy
2024-11-20 1:00 ` Junio C Hamano
0 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-19 11:27 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
On Tue Nov 19, 2024 at 04:16, Junio C Hamano <gitster@pobox.com> wrote:
> Bence Ferdinandy <bence@ferdinandy.com> writes:
>
>> If the user has remote/HEAD set already and it looks like it has changed
>> on the server, then print a message, otherwise set it if we can.
>
> The user uses remote/origin/HEAD to point at the "primary thing the
> user is interested in at the remote 'origin'". "git clone"
> propagates remote's HEAD to local because their HEAD is the opinion
> of the remote which branch they want the users to consider the
> primary thing. But the user can have valid reasons to consider a
> branch different from what the remote suggests as the primary thing,
> and an explicit "set-head" is a way to do so.
>
> After such a set-up is made and the user is perfectly happy, would
> it make sense to repeatedly remind that their HEAD points at
> something the user is not interested in?
>
> Perhaps it may make sense when given the "--verbose" option, or
> after the first time the difference was noticed, but otherwise, I
> suspect it becomes annoying to those who keep them deliberately
> different from each other.
>
> I wonder if it is more sensible to automate for these two different
> classes of users with a new configuration per remote.
Since this discussion reaches a bit back I looked up the first message where
the current behaviour was discussed, and where in the end it was said we don't
need configuration (the very first implementation always updated HEAD).
Starting here:
https://lore.kernel.org/git/xmqqseu7cfsl.fsf@gitster.g/
That being said:
Your suggestion seems to be the original tri-state configuration that came up
in one of the original discussions. It was recently requested again for fetch
to just do this automatically
(https://lore.kernel.org/git/CAAHKNRGv19rhnqCkJMpE2FomNQBHvSS36aC=fh0UwO+9-6RRfA@mail.gmail.com),
so at least some people would like to have this fully automated.
>
> - When "git clone" initially sets up, or "git remote set-head
> --auto" matches the local to the remote, we set the new
> "remote.$name.autoUpdateHEAD" configuration variable to true.
I think this is a good default. -> Coming back here I think "warn" is a better
default see below.
>
> - When "git remote set-head" explicitly sets remote/$name/HEAD
> to some value (if we can detect the case where it is different
> from what the remote has, that would be a plus, but it incurrs
> network traffic, so care must be taken to design this part), we
> drop the "remote.$name.autoUpdateHEAD" configuration variable,
> if and only if it is set to true.
I also agree with this. I don't think it would be necessary to do a network
check at this point. If somebody is deliberately changing the remote head
manually, they are probably aware of the current situation and can always set
their configuration to "warn" if they care, or just check manually once in
a while. At least I feel needing network connection to do this would be more
a hindrance than useful.
>
> - When remote.$name.autoUpdateHEAD configuration is true, "git
> fetch" automatically matches remote/$name/HEAD to what the HEAD
> at the remote points at. If remote.$name.autoUpdateHEAD is set
> to some other value (e.g., "warn"), show the warning so that the
> user can choose to do "git remote set-head" to a specific value
> (which would not drop the remote.$name.autoUpdateHEAD set to
> "warn") or "git remote set-head --auto" (which would start to
> follow whatever remote uses).
Make sense.
One thing you haven't mentioned is when you start locally with git init && git
remote add && git fetch. But since this whole series started by wanting to make
this the same as cloning I assume it should be "true" as well. In fact, if the
default behaviour is true this would just work.
On the other hand: considering your points in the linked very early discussion,
maybe "warn" would be a better default? I'm not sure. Most people would not see
warnings anyway since I doubt a) many users change their remote/HEAD manually
or b) many projects change their HEAD at all or if they do, more than once.
Gaving "true" as default would change the current behaviour with silently
updating existing remote/HEADs. The warning could also tell them that `remote
set-head --auto` will set this to automatic in the future as well and thus
remove any warnings or that if they know what they want the can set it to false.
>
> With something like the above, those who just want to follow along
> would get remote.$name.autoUpdateHEAD=true and silently follow
> along, while those who want to explicitly control would be able to
> do so. I dunno, but I am fairly negative on a persistent warning
> message that tells the user that the only normal case is to have
> remote/origin/HEAD point at the same branch the remote's HEAD points
> at.
Fair point.
This series has a tendency for growing in complication in each iteration :)
Best,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v13 8/9] fetch: set remote/HEAD if it does not exist
2024-11-19 11:27 ` Bence Ferdinandy
@ 2024-11-20 1:00 ` Junio C Hamano
2024-11-20 2:28 ` Junio C Hamano
2024-11-20 10:45 ` Bence Ferdinandy
0 siblings, 2 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-11-20 1:00 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
"Bence Ferdinandy" <bence@ferdinandy.com> writes:
> Your suggestion seems to be the original tri-state configuration that came up
> in one of the original discussions. It was recently requested again for fetch
> to just do this automatically
> (https://lore.kernel.org/git/CAAHKNRGv19rhnqCkJMpE2FomNQBHvSS36aC=fh0UwO+9-6RRfA@mail.gmail.com),
> so at least some people would like to have this fully automated.
And that is *not* the only option, and I am trying to help others
by preventing unconditional warning messages from annoying them.
> On the other hand: considering your points in the linked very early discussion,
> maybe "warn" would be a better default?
Those who want to stick to their manual choice would have to do an
explicit set-head once, whether the default is "true" or "warn", so
their inconvenience would not affect the choice of the default.
I think "warn" is a good default _only_ for folks who want to verify
and then expect they will follow what the upstream does 99% of the
time. For them, when they do not want to switch to what the
upstream says (which is the rest 1%), there is no good way to
squelch the warning other than dropping the configuration, but then
they will lose the ability to notice further flips at the remote
after that, which may turn out to be a problem.
Instead of "true", "warn", and "false", we might need "if it is set
to _this_ value, do not warn and keep the current setting" to help
those who cautiously only allow the remote affect what they use
after inspecting, but expect that the choice made by the remote will
not be outrageous most of the time for them.
If we want to solve this completely for them, that is. I dunno.
Those who want to blindly follow what the upstream says 100% of the
time will have to switch to "true" just once manually, if the
default were "warn", but once may be one time too many for them.
And that is why I said "clone" (and "fetch" setting a non-existing
HEAD automatically) may want to default to "true".
> Fair point.
>
> This series has a tendency for growing in complication in each iteration :)
Anytime somebody gets swayed by an opinion to add unconditional
warning or unconditional futzing without considering that such a
move may annoy some users that are different from the opinion giver,
we need to make sure there are escape hatches. Under the assumption
that most projects only flip HEAD very rarely, and considering the
fact that we have lived with manual setting with set-head and
nothing else, we could drop the automatic setting by "fetch"
altogether to reduce the scope of the series without losing much
*and* more importantly without harming existing users.
I think we could make it a multi-step transition in order to keep
changes to "git fetch" we make in each step smaller, which may go
like so:
1) Let "git fetch" create missing remotes/$name/HEAD. No other
changes.
2) Introduce a remote.$name.followRemoteHEAD that can say "the last
branch we observed that they point at is 'main'". Set it when
we clone, When "git fetch" notices that the remote flipped its
HEAD to a different value, warn and update (which would give us
the "warn only once, do not annoy users by constantly warning").
The user can delete the configuration to squelch "git fetch" (in
other words, the repository does not remember the last setting,
so we refrain from warning "it has changed since the last time").
No other changes.
3) Allow remote.$name.followRemoteHEAD to be set to
3-a) "always", which would squelch the warning and causes "git
fetch" to always repoint the local remotes/$name/HEAD to
whatever the remote says,
3-b) "warn", which would cause "git fetch" to warn when the
remote's HEAD is different from local remotes/$name/HEAD,
3-c) "never", which would cause "git fetch" to even omit the
"copy if it does not exist" we add in 1) to help those who
deliberately want to keep remotes/$name/HEAD missing (to force
themselves to be more explicit, saying things like "git checkout
-b topic origin/maint" and "git rebase origin/master", but never
"git checkout --detach origin").
And we can stop this round at the first step (plus any other changes
outside "git fetch", like "set-head --auto").
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v13 8/9] fetch: set remote/HEAD if it does not exist
2024-11-20 1:00 ` Junio C Hamano
@ 2024-11-20 2:28 ` Junio C Hamano
2024-11-20 10:45 ` Bence Ferdinandy
1 sibling, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-11-20 2:28 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
Junio C Hamano <gitster@pobox.com> writes:
> "Bence Ferdinandy" <bence@ferdinandy.com> writes:
>
>> Your suggestion seems to be the original tri-state configuration that came up
>> in one of the original discussions. It was recently requested again for fetch
>> to just do this automatically
>> (https://lore.kernel.org/git/CAAHKNRGv19rhnqCkJMpE2FomNQBHvSS36aC=fh0UwO+9-6RRfA@mail.gmail.com),
>> so at least some people would like to have this fully automated.
>
> And that is *not* the only option, and I am trying to help others
> by preventing unconditional warning messages from annoying them.
By the way, because I've left it out from my messages, some folks
who are reading from sideways might not realize this, so let's make
it a bit more explicit. One thing to note is that the assumption
that HEAD at the remote rarely changes _only_ holds for forges and
the like, a typically bare repository used for publishing without a
working tree.
If you have a clone from a repository with a working tree (this
happens when you have two places to work, either on two machines or
two separate repositories on a same machine) to allow you to work
here during the day and work there after work, the HEAD at the clone
source may change to the branch you happen to be working on when
left there. In such a setting, it is reasonable to make the
repositories aware of each other (i.e. the after-work repository may
have refs/remotes/at-work/* remote-tracking branches while the
at-work repository keeps refs/remotes/after-work/* remote-tracking
branches, to keep track of each other), and "git fetch" that warns
every time it notices the remote end has HEAD pointing at a
different branch would be annoying, as they would likely be working
on different "project" when they leave after work.
Users who fall outside of a typical "we have a central repository
and the only interaction with the repository is we clone and
fetch/pull from it and nothing else" hosting site setting, are why
I'll encourage folks to tread carefully when they are tempted to
make anything unconditional.
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v13 8/9] fetch: set remote/HEAD if it does not exist
2024-11-20 1:00 ` Junio C Hamano
2024-11-20 2:28 ` Junio C Hamano
@ 2024-11-20 10:45 ` Bence Ferdinandy
1 sibling, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-20 10:45 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
On Wed Nov 20, 2024 at 02:00, Junio C Hamano <gitster@pobox.com> wrote:
> "Bence Ferdinandy" <bence@ferdinandy.com> writes:
>
>> Your suggestion seems to be the original tri-state configuration that came up
>> in one of the original discussions. It was recently requested again for fetch
>> to just do this automatically
>> (https://lore.kernel.org/git/CAAHKNRGv19rhnqCkJMpE2FomNQBHvSS36aC=fh0UwO+9-6RRfA@mail.gmail.com),
>> so at least some people would like to have this fully automated.
>
> And that is *not* the only option, and I am trying to help others
> by preventing unconditional warning messages from annoying them.
>
>> On the other hand: considering your points in the linked very early discussion,
>> maybe "warn" would be a better default?
>
> Those who want to stick to their manual choice would have to do an
> explicit set-head once, whether the default is "true" or "warn", so
> their inconvenience would not affect the choice of the default.
>
> I think "warn" is a good default _only_ for folks who want to verify
> and then expect they will follow what the upstream does 99% of the
> time. For them, when they do not want to switch to what the
> upstream says (which is the rest 1%), there is no good way to
> squelch the warning other than dropping the configuration, but then
> they will lose the ability to notice further flips at the remote
> after that, which may turn out to be a problem.
>
> Instead of "true", "warn", and "false", we might need "if it is set
> to _this_ value, do not warn and keep the current setting" to help
> those who cautiously only allow the remote affect what they use
> after inspecting, but expect that the choice made by the remote will
> not be outrageous most of the time for them.
>
> If we want to solve this completely for them, that is. I dunno.
I initially thought you meant a fourth setting here, but what you are
suggesting here is that in case the user has "remote.origin.followremotehead
= never" set even in case of "init && remote add && fetch" HEAD should not be
created, which is a bit different, because originally we discussed only not
changing after the initial creation. This makes a lot of sense.
>
> Those who want to blindly follow what the upstream says 100% of the
> time will have to switch to "true" just once manually, if the
> default were "warn", but once may be one time too many for them.
>
> And that is why I said "clone" (and "fetch" setting a non-existing
> HEAD automatically) may want to default to "true".
Fair point again.
>
>> Fair point.
>>
>> This series has a tendency for growing in complication in each iteration :)
>
> Anytime somebody gets swayed by an opinion to add unconditional
> warning or unconditional futzing without considering that such a
> move may annoy some users that are different from the opinion giver,
> we need to make sure there are escape hatches. Under the assumption
> that most projects only flip HEAD very rarely, and considering the
> fact that we have lived with manual setting with set-head and
> nothing else, we could drop the automatic setting by "fetch"
> altogether to reduce the scope of the series without losing much
> *and* more importantly without harming existing users.
Don't get me wrong, I'd like to do the right thing here, and appreciate that my
perspective is rather limited. I was just reflecting on the fact that the first
"working" version of the series was only two patches. Writing this is fun and
educational for me.
>
> I think we could make it a multi-step transition in order to keep
> changes to "git fetch" we make in each step smaller, which may go
> like so:
That being said, if it's possible to graduate a part of the series when it's
ready and where it makes sense to split, that would be welcome. As you write
below the first such place would be remote set-head --auto. Not that that is
ready yet, I need to fix the mistake you spotted, update a commit message, and
add a test-coverage. But in that case I might send a v14 with just these
updates and work on getting the fetch settings in parallel for a v15.
Technically I would just need to build the series off next for a while, right?
>
> 1) Let "git fetch" create missing remotes/$name/HEAD. No other
> changes.
So this is a bit simpler version of what we have now (as long as it doesn't
bother for example you, that there is a state which will create HEAD without
being able to disable it :)) Should this even print anything? Probably not,
clone also doesn't tell you that the HEAD was set.
>
> 2) Introduce a remote.$name.followRemoteHEAD that can say "the last
> branch we observed that they point at is 'main'". Set it when
> we clone, When "git fetch" notices that the remote flipped its
> HEAD to a different value, warn and update (which would give us
> the "warn only once, do not annoy users by constantly warning").
> The user can delete the configuration to squelch "git fetch" (in
> other words, the repository does not remember the last setting,
> so we refrain from warning "it has changed since the last time").
> No other changes.
>
> 3) Allow remote.$name.followRemoteHEAD to be set to
>
> 3-a) "always", which would squelch the warning and causes "git
> fetch" to always repoint the local remotes/$name/HEAD to
> whatever the remote says,
But this should print similar messages as remote set-head --auto when there is
an actual change done, right? And should probably respect --quiet?
>
> 3-b) "warn", which would cause "git fetch" to warn when the
> remote's HEAD is different from local remotes/$name/HEAD,
This should also respect --quite I assume.
>
> 3-c) "never", which would cause "git fetch" to even omit the
> "copy if it does not exist" we add in 1) to help those who
> deliberately want to keep remotes/$name/HEAD missing (to force
> themselves to be more explicit, saying things like "git checkout
> -b topic origin/maint" and "git rebase origin/master", but never
> "git checkout --detach origin").
I think skipping 2 and jumping straight to the final configuration would be
easier, no need to wrap one's head around a configuration state that will
essentially never really exist and most of the extra complexity (at least from
my perspective) comes from having to have a configuration at all.
Thanks,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v13 9/9] fetch set_head: handle mirrored bare repositories
2024-11-18 15:09 ` [PATCH v13 0/9] " Bence Ferdinandy
` (7 preceding siblings ...)
2024-11-18 15:09 ` [PATCH v13 8/9] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
@ 2024-11-18 15:09 ` Bence Ferdinandy
2024-11-21 22:55 ` [PATCH v14 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
9 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-18 15:09 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
When adding a remote to bare repository with "git remote add --mirror",
running fetch will fail to update HEAD to the remote's HEAD, since it
does not know how to handle bare repositories. On the other hand HEAD
already has content, since "git init --bare" has already set HEAD to
whatever is the default branch set for the user. Unless this - by chance
- is the same as the remote's HEAD, HEAD will be pointing to a bad
symref. Teach set_head to handle bare repositories, by overwriting HEAD
so it mirrors the remote's HEAD.
Note, that in this case overriding the local HEAD reference is
necessary, since HEAD will exist before fetch can be run, but this
should not be an issue, since the whole purpose of --mirror is to be an
exact mirror of the remote, so following any changes to HEAD makes
sense.
Also note, that although "git remote set-head" also fails when trying to
update the remote's locally tracked HEAD in a mirrored bare repository,
the usage of the command does not make much sense after this patch:
fetch will update the remote HEAD correctly, and setting it manually to
something else is antithetical to the concept of mirroring.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v10: - new patch
- handles the issue discovered in
https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/T/
v11: no change
v12: no change
v13: properly print the previous head if it was detached
builtin/fetch.c | 16 +++++++++++-----
t/t5505-remote.sh | 10 ++++++++++
2 files changed, 21 insertions(+), 5 deletions(-)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 3d70cd1add..5e37b9c00f 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1608,7 +1608,7 @@ static const char *strip_refshead(const char *name){
static int set_head(const struct ref *remote_refs)
{
- int result = 0, updateres;
+ int result = 0, updateres, is_bare;
struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
b_local_head = STRBUF_INIT;
const char *remote = gtransport->remote->name;
@@ -1641,15 +1641,21 @@ static int set_head(const struct ref *remote_refs)
if (!head_name)
goto cleanup;
- strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
- strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name);
+ is_bare = is_bare_repository();
+ if (is_bare) {
+ strbuf_addstr(&b_head, "HEAD");
+ strbuf_addf(&b_remote_head, "refs/heads/%s", head_name);
+ } else {
+ strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name);
+ }
/* make sure it's valid */
- if (!refs_ref_exists(refs, b_remote_head.buf)) {
+ if (!is_bare && !refs_ref_exists(refs, b_remote_head.buf)) {
result = 1;
goto cleanup;
}
updateres = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
- "fetch", &b_local_head, 1);
+ "fetch", &b_local_head, !is_bare);
if (updateres == -1) {
result = 1;
goto cleanup;
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 660310239c..4cc5df2e61 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -557,6 +557,16 @@ test_expect_success 'add --mirror && prune' '
)
'
+test_expect_success 'add --mirror setting HEAD' '
+ mkdir headmirror &&
+ (
+ cd headmirror &&
+ git init --bare -b notmain &&
+ git remote add --mirror -f origin ../one &&
+ test "$(git symbolic-ref HEAD)" = "refs/heads/main"
+ )
+'
+
test_expect_success 'add --mirror=fetch' '
mkdir mirror-fetch &&
git init -b main mirror-fetch/parent &&
--
2.47.0.296.gda1ecfef29.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v14 00/10] set-head/fetch remote/HEAD updates
2024-11-18 15:09 ` [PATCH v13 0/9] " Bence Ferdinandy
` (8 preceding siblings ...)
2024-11-18 15:09 ` [PATCH v13 9/9] fetch set_head: handle mirrored bare repositories Bence Ferdinandy
@ 2024-11-21 22:55 ` Bence Ferdinandy
2024-11-21 22:55 ` [PATCH v14 01/10] t/t5505-remote: set default branch to main Bence Ferdinandy
` (10 more replies)
9 siblings, 11 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-21 22:55 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
Notable changes since v13:
- As discussed in
https://lore.kernel.org/git/xmqqed36btxm.fsf@gitster.g/ this iteration
has dropped printing any warning during fetch and now only sets HEAD
if it doesn't already exist, otherwise it doesn't do anything.
- A new patch adds a new test case for testing the failure branch of
remote set-head. This also allowed to clear up a bug in a later patch.
I will start working on a follow up patch that will add configuration
options to control how fetch sets HEAD or not. The current state should
be fine for most people, except those who explicitly wish NOT to have
remote/HEAD set. Other than that the current state is now hopefully
mature enough to graduate (unless I made yet another mistake :)).
Bence Ferdinandy (10):
t/t5505-remote: set default branch to main
t/t5505-remote: test failure of set-head
refs: standardize output of refs_read_symbolic_ref
refs: atomically record overwritten ref in update_symref
remote set-head: refactor for readability
remote set-head: better output for --auto
refs: add TRANSACTION_CREATE_EXISTS error
refs: add create_only option to refs_update_symref_extended
fetch: set remote/HEAD if it does not exist
fetch set_head: handle mirrored bare repositories
builtin/fetch.c | 74 +++++++++++++++++++++
builtin/remote.c | 72 +++++++++++++++------
refs.c | 47 ++++++++++++--
refs.h | 19 +++++-
refs/files-backend.c | 31 +++++----
refs/refs-internal.h | 5 ++
refs/reftable-backend.c | 14 ++--
t/t4207-log-decoration-colors.sh | 3 +-
t/t5505-remote.sh | 107 +++++++++++++++++++++++++++++--
t/t5510-fetch.sh | 24 +++++++
t/t5512-ls-remote.sh | 2 +
t/t5514-fetch-multiple.sh | 17 ++++-
t/t5516-fetch-push.sh | 3 +-
t/t5527-fetch-odd-refs.sh | 3 +-
t/t7900-maintenance.sh | 3 +-
t/t9210-scalar.sh | 5 +-
t/t9211-scalar-clone.sh | 6 +-
t/t9902-completion.sh | 65 +++++++++++++++++++
18 files changed, 440 insertions(+), 60 deletions(-)
--
2.47.0.298.g52a96ec17b
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v14 01/10] t/t5505-remote: set default branch to main
2024-11-21 22:55 ` [PATCH v14 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
@ 2024-11-21 22:55 ` Bence Ferdinandy
2024-11-21 22:55 ` [PATCH v14 02/10] t/t5505-remote: test failure of set-head Bence Ferdinandy
` (9 subsequent siblings)
10 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-21 22:55 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
Consider the bare repository called "mirror" in the test. Running `git
remote add --mirror -f origin ../one` will not change HEAD, consequently
if init.defaultBranch is not the same as what HEAD in the remote
("one"), HEAD in "mirror" will be pointing to a non-existent reference.
Hence if "mirror" is used as a remote by yet another repository,
ls-remote will not show HEAD. On the other hand, if init.defaultBranch
happens to match HEAD in "one", then ls-remote will show HEAD.
Since the "ci/run-build-and-tests.sh" script globally exports
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main for some (but not all) jobs,
there may be a drift in some tests between how the test repositories are
set up in the CI and during local testing, if the test itself uses
"master" as default instead of "main". In particular, this happens in
t5505-remote.sh. This issue does not manifest currently, as the test
does not do any remote HEAD manipulation where this would come up, but
should such things be added, a locally passing test would break the CI
and vice-versa.
Set GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main in t5505-remote to be
consistent with the CI.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v9: - new patch
- a bandaid for the CI issue noticed by Taylor (cf:
https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/), but
see
https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/ for the root cause in detail
v10: no change
v11: no change
v12: added forgotten sign-off
v13: commit message udpated to be more precise
v14: made explicit in the commit message that the default branch is only
set for some CI jobs
t/t5505-remote.sh | 3 +++
1 file changed, 3 insertions(+)
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 532035933f..9b50276646 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -2,6 +2,9 @@
test_description='git remote porcelain-ish'
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
--
2.47.0.298.g52a96ec17b
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v14 02/10] t/t5505-remote: test failure of set-head
2024-11-21 22:55 ` [PATCH v14 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
2024-11-21 22:55 ` [PATCH v14 01/10] t/t5505-remote: set default branch to main Bence Ferdinandy
@ 2024-11-21 22:55 ` Bence Ferdinandy
2024-11-22 4:54 ` Junio C Hamano
2024-11-21 22:55 ` [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref Bence Ferdinandy
` (8 subsequent siblings)
10 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-21 22:55 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
The test coverage was missing a test for the failure branch of remote
set-head auto's output. Add the missing text and while we are at it,
correct a small grammatical mistake in the error's output ("setup" is
the noun, "set up" is the verb).
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v14: new patch
builtin/remote.c | 2 +-
t/t5505-remote.sh | 12 ++++++++++++
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 76670ddd8b..8a182439f2 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1445,7 +1445,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
if (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))
result |= error(_("Not a valid ref: %s"), buf2.buf);
else if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote set-head"))
- result |= error(_("Could not setup %s"), buf.buf);
+ result |= error(_("Could not set up %s"), buf.buf);
else if (opt_a)
printf("%s/HEAD set to %s\n", argv[0], head_name);
free(head_name);
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 9b50276646..4e127bf5b8 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -432,6 +432,18 @@ test_expect_success 'set-head --auto' '
)
'
+test_expect_success REFFILES 'set-head --auto failure' '
+ (
+ cd test &&
+ touch .git/refs/remotes/origin/HEAD.lock &&
+ git remote set-head --auto origin 2>errormsg ||
+ tail -n1 errormsg >output &&
+ rm .git/refs/remotes/origin/HEAD.lock &&
+ echo "error: Could not set up refs/remotes/origin/HEAD" >expect &&
+ test_cmp expect output
+ )
+'
+
test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
(
cd test &&
--
2.47.0.298.g52a96ec17b
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v14 02/10] t/t5505-remote: test failure of set-head
2024-11-21 22:55 ` [PATCH v14 02/10] t/t5505-remote: test failure of set-head Bence Ferdinandy
@ 2024-11-22 4:54 ` Junio C Hamano
0 siblings, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-11-22 4:54 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
Bence Ferdinandy <bence@ferdinandy.com> writes:
> - result |= error(_("Could not setup %s"), buf.buf);
> + result |= error(_("Could not set up %s"), buf.buf);
Good eyes.
> diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
> index 9b50276646..4e127bf5b8 100755
> --- a/t/t5505-remote.sh
> +++ b/t/t5505-remote.sh
> @@ -432,6 +432,18 @@ test_expect_success 'set-head --auto' '
> )
> '
>
> +test_expect_success REFFILES 'set-head --auto failure' '
Writing
test_when_finished "rm -f test/.git/refs/remotes/origin/HEAD.lock" &&
here allows us not to worry about commands in the sequence before
the "rm" below failing unexpectedly.
> + (
> + cd test &&
> + touch .git/refs/remotes/origin/HEAD.lock &&
> + git remote set-head --auto origin 2>errormsg ||
> + tail -n1 errormsg >output &&
Are we saying that it is OK for set-head not to fail here? If not,
then this should be
test_must_fail git remote set-head ... 2>err &&
tail -n 1 err >actual &&
instead.
> + rm .git/refs/remotes/origin/HEAD.lock &&
> + echo "error: Could not set up refs/remotes/origin/HEAD" >expect &&
> + test_cmp expect output
> + )
> +'
> +
> test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
> (
> cd test &&
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref
2024-11-21 22:55 ` [PATCH v14 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
2024-11-21 22:55 ` [PATCH v14 01/10] t/t5505-remote: set default branch to main Bence Ferdinandy
2024-11-21 22:55 ` [PATCH v14 02/10] t/t5505-remote: test failure of set-head Bence Ferdinandy
@ 2024-11-21 22:55 ` Bence Ferdinandy
2024-11-22 10:37 ` karthik nayak
2024-11-21 22:55 ` [PATCH v14 04/10] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
` (7 subsequent siblings)
10 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-21 22:55 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
When the symbolic reference we want to read with refs_read_symbolic_ref
is actually not a symbolic reference, the files and the reftable
backends return different values (1 and -1 respectively). Standardize
the returned values so that 0 is success, -1 is a generic error and -2
is that the reference was actually non-symbolic.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v13: new patch
v14: - simplified the logic in reftables backend (thanks Junio)
- update the comment in refs.h (thanks Patrick)
- rewrote comment in refs-internal.h to point to the one in refs.h
- created NOT_A_SYMREF=-2 constant
refs.h | 11 +++++++++++
refs/files-backend.c | 7 +++----
refs/refs-internal.h | 5 +++++
refs/reftable-backend.c | 8 +++++---
4 files changed, 24 insertions(+), 7 deletions(-)
diff --git a/refs.h b/refs.h
index 108dfc93b3..22c2b45b8b 100644
--- a/refs.h
+++ b/refs.h
@@ -83,6 +83,17 @@ int refs_read_ref_full(struct ref_store *refs, const char *refname,
int refs_read_ref(struct ref_store *refs, const char *refname, struct object_id *oid);
+#define NOT_A_SYMREF -2
+
+/*
+ * Read the symbolic ref named "refname" and write its immediate referent into
+ * the provided buffer. Referent is left empty if "refname" is not a symbolic
+ * ref. It does not resolve the symbolic reference recursively in case the
+ * target is also a symbolic ref.
+ *
+ * Returns 0 on success, -2 if the "refname" is not a symbolic ref,
+ * -1 otherwise.
+ */
int refs_read_symbolic_ref(struct ref_store *ref_store, const char *refname,
struct strbuf *referent);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 0824c0b8a9..4cc43c32f2 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -596,10 +596,9 @@ static int files_read_symbolic_ref(struct ref_store *ref_store, const char *refn
unsigned int type;
ret = read_ref_internal(ref_store, refname, &oid, referent, &type, &failure_errno, 1);
- if (ret)
- return ret;
-
- return !(type & REF_ISSYMREF);
+ if (!ret && !(type & REF_ISSYMREF))
+ return NOT_A_SYMREF;
+ return ret;
}
int parse_loose_ref_contents(const struct git_hash_algo *algop,
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 2313c830d8..1399fee61c 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -673,6 +673,11 @@ struct ref_storage_be {
ref_iterator_begin_fn *iterator_begin;
read_raw_ref_fn *read_raw_ref;
+
+ /*
+ * Please refer to `refs_read_symbolic_ref()` for the expected
+ * behaviour.
+ */
read_symbolic_ref_fn *read_symbolic_ref;
reflog_iterator_begin_fn *reflog_iterator_begin;
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 38eb14d591..1809e3426a 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -830,10 +830,12 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,
return ret;
ret = reftable_stack_read_ref(stack, refname, &ref);
- if (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)
+ if (ret)
+ ret = -1;
+ else if (ref.value_type == REFTABLE_REF_SYMREF)
strbuf_addstr(referent, ref.value.symref);
- else
- ret = -1;
+ else
+ ret = NOT_A_SYMREF;
reftable_ref_record_release(&ref);
return ret;
--
2.47.0.298.g52a96ec17b
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref
2024-11-21 22:55 ` [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref Bence Ferdinandy
@ 2024-11-22 10:37 ` karthik nayak
2024-11-22 10:53 ` Bence Ferdinandy
2024-11-22 11:27 ` Bence Ferdinandy
0 siblings, 2 replies; 258+ messages in thread
From: karthik nayak @ 2024-11-22 10:37 UTC (permalink / raw)
To: Bence Ferdinandy, git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, Taylor Blau, Patrick Steinhardt
[-- Attachment #1: Type: text/plain, Size: 948 bytes --]
Bence Ferdinandy <bence@ferdinandy.com> writes:
[snip]
> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
> index 38eb14d591..1809e3426a 100644
> --- a/refs/reftable-backend.c
> +++ b/refs/reftable-backend.c
> @@ -830,10 +830,12 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,
> return ret;
>
> ret = reftable_stack_read_ref(stack, refname, &ref);
> - if (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)
> + if (ret)
> + ret = -1;
> + else if (ref.value_type == REFTABLE_REF_SYMREF)
> strbuf_addstr(referent, ref.value.symref);
> - else
> - ret = -1;
> + else
> + ret = NOT_A_SYMREF;
>
I was building my series on top of this, and noticed whitespace issues
here. A simple way to check your series is to run:
$ git log --check --pretty=format:"---% h% s"
> reftable_ref_record_release(&ref);
> return ret;
> --
> 2.47.0.298.g52a96ec17b
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref
2024-11-22 10:37 ` karthik nayak
@ 2024-11-22 10:53 ` Bence Ferdinandy
2024-11-22 10:55 ` Bence Ferdinandy
2024-11-22 11:30 ` karthik nayak
2024-11-22 11:27 ` Bence Ferdinandy
1 sibling, 2 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-22 10:53 UTC (permalink / raw)
To: karthik nayak, git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, Taylor Blau, Patrick Steinhardt
On Fri Nov 22, 2024 at 11:37, karthik nayak <karthik.188@gmail.com> wrote:
> Bence Ferdinandy <bence@ferdinandy.com> writes:
>
> [snip]
>
>> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
>> index 38eb14d591..1809e3426a 100644
>> --- a/refs/reftable-backend.c
>> +++ b/refs/reftable-backend.c
>> @@ -830,10 +830,12 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,
>> return ret;
>>
>> ret = reftable_stack_read_ref(stack, refname, &ref);
>> - if (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)
>> + if (ret)
>> + ret = -1;
>> + else if (ref.value_type == REFTABLE_REF_SYMREF)
>> strbuf_addstr(referent, ref.value.symref);
>> - else
>> - ret = -1;
>> + else
>> + ret = NOT_A_SYMREF;
>>
>
> I was building my series on top of this, and noticed whitespace issues
> here. A simple way to check your series is to run:
>
> $ git log --check --pretty=format:"---% h% s"
I ran this on v15 and it didn't produce any output. I read what --check is in
the manpages, although the format is a bit cryptic for me. What does that do
exactly?
Anyhow if there was no output for v15 I should be fine, right?
>
>> reftable_ref_record_release(&ref);
>> return ret;
>> --
>> 2.47.0.298.g52a96ec17b
--
bence.ferdinandy.com
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref
2024-11-22 10:53 ` Bence Ferdinandy
@ 2024-11-22 10:55 ` Bence Ferdinandy
2024-11-22 11:30 ` karthik nayak
1 sibling, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-22 10:55 UTC (permalink / raw)
To: karthik nayak, git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, Taylor Blau, Patrick Steinhardt
On Fri Nov 22, 2024 at 11:53, Bence Ferdinandy <bence@ferdinandy.com> wrote:
>
> On Fri Nov 22, 2024 at 11:37, karthik nayak <karthik.188@gmail.com> wrote:
>> Bence Ferdinandy <bence@ferdinandy.com> writes:
>>
>> [snip]
>>
>>> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
>>> index 38eb14d591..1809e3426a 100644
>>> --- a/refs/reftable-backend.c
>>> +++ b/refs/reftable-backend.c
>>> @@ -830,10 +830,12 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,
>>> return ret;
>>>
>>> ret = reftable_stack_read_ref(stack, refname, &ref);
>>> - if (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)
>>> + if (ret)
>>> + ret = -1;
>>> + else if (ref.value_type == REFTABLE_REF_SYMREF)
>>> strbuf_addstr(referent, ref.value.symref);
>>> - else
>>> - ret = -1;
>>> + else
>>> + ret = NOT_A_SYMREF;
>>>
>>
>> I was building my series on top of this, and noticed whitespace issues
>> here. A simple way to check your series is to run:
>>
>> $ git log --check --pretty=format:"---% h% s"
>
> I ran this on v15 and it didn't produce any output. I read what --check is in
> the manpages, although the format is a bit cryptic for me. What does that do
> exactly?
>
> Anyhow if there was no output for v15 I should be fine, right?
But then again: this patch is exactly like v14 so I'm not so sure about that ...
--
bence.ferdinandy.com
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref
2024-11-22 10:53 ` Bence Ferdinandy
2024-11-22 10:55 ` Bence Ferdinandy
@ 2024-11-22 11:30 ` karthik nayak
2024-11-22 12:23 ` Bence Ferdinandy
1 sibling, 1 reply; 258+ messages in thread
From: karthik nayak @ 2024-11-22 11:30 UTC (permalink / raw)
To: Bence Ferdinandy, git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, Taylor Blau, Patrick Steinhardt
[-- Attachment #1: Type: text/plain, Size: 2124 bytes --]
"Bence Ferdinandy" <bence@ferdinandy.com> writes:
> On Fri Nov 22, 2024 at 11:37, karthik nayak <karthik.188@gmail.com> wrote:
>> Bence Ferdinandy <bence@ferdinandy.com> writes:
>>
>> [snip]
>>
>>> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
>>> index 38eb14d591..1809e3426a 100644
>>> --- a/refs/reftable-backend.c
>>> +++ b/refs/reftable-backend.c
>>> @@ -830,10 +830,12 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,
>>> return ret;
>>>
>>> ret = reftable_stack_read_ref(stack, refname, &ref);
>>> - if (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)
>>> + if (ret)
>>> + ret = -1;
>>> + else if (ref.value_type == REFTABLE_REF_SYMREF)
>>> strbuf_addstr(referent, ref.value.symref);
>>> - else
>>> - ret = -1;
>>> + else
>>> + ret = NOT_A_SYMREF;
>>>
>>
>> I was building my series on top of this, and noticed whitespace issues
>> here. A simple way to check your series is to run:
>>
>> $ git log --check --pretty=format:"---% h% s"
>
> I ran this on v15 and it didn't produce any output.
Did you already post v15? I only see v14
> I read what --check is in
> the manpages, although the format is a bit cryptic for me. What does that do
> exactly?
>
It ensures that commits don't have conflict markers and that there are
no trailing whitespaces and spaces followed by tabs by default.
Also this is included in the CI checks (see ci/check-whitespace.sh), so
if you use either GitLab or GitHub you should see these shown as errors
on the CI. You'll have to raise a MR/PR to trigger the CI I believe.
On a sidenote, do you think we should modify the manpage? I found it
comprehensible, but would be nice to clarify anything cryptic.
> Anyhow if there was no output for v15 I should be fine, right?
>
At the least you should see `git log`'s output, but if there are issues
they should be shown inline. So when you say 'no output' do you mean you
see absolutely no output?
>>
>>> reftable_ref_record_release(&ref);
>>> return ret;
>>> --
>>> 2.47.0.298.g52a96ec17b
>
>
>
>
> --
> bence.ferdinandy.com
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref
2024-11-22 11:30 ` karthik nayak
@ 2024-11-22 12:23 ` Bence Ferdinandy
2024-11-25 2:56 ` Junio C Hamano
0 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-22 12:23 UTC (permalink / raw)
To: karthik nayak, git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, Taylor Blau, Patrick Steinhardt
On Fri Nov 22, 2024 at 12:30, karthik nayak <karthik.188@gmail.com> wrote:
> "Bence Ferdinandy" <bence@ferdinandy.com> writes:
>
>> On Fri Nov 22, 2024 at 11:37, karthik nayak <karthik.188@gmail.com> wrote:
>>> Bence Ferdinandy <bence@ferdinandy.com> writes:
>>>
>>> [snip]
>>>
>>>> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
>>>> index 38eb14d591..1809e3426a 100644
>>>> --- a/refs/reftable-backend.c
>>>> +++ b/refs/reftable-backend.c
>>>> @@ -830,10 +830,12 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,
>>>> return ret;
>>>>
>>>> ret = reftable_stack_read_ref(stack, refname, &ref);
>>>> - if (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)
>>>> + if (ret)
>>>> + ret = -1;
>>>> + else if (ref.value_type == REFTABLE_REF_SYMREF)
>>>> strbuf_addstr(referent, ref.value.symref);
>>>> - else
>>>> - ret = -1;
>>>> + else
>>>> + ret = NOT_A_SYMREF;
>>>>
>>>
>>> I was building my series on top of this, and noticed whitespace issues
>>> here. A simple way to check your series is to run:
>>>
>>> $ git log --check --pretty=format:"---% h% s"
>>
>> I ran this on v15 and it didn't produce any output.
>
> Did you already post v15? I only see v14
Not yet, but I'll be sending it in a pinch.
>
>> I read what --check is in
>> the manpages, although the format is a bit cryptic for me. What does that do
>> exactly?
>>
>
> It ensures that commits don't have conflict markers and that there are
> no trailing whitespaces and spaces followed by tabs by default.
>
> Also this is included in the CI checks (see ci/check-whitespace.sh), so
> if you use either GitLab or GitHub you should see these shown as errors
> on the CI. You'll have to raise a MR/PR to trigger the CI I believe.
I've been running the CI by pushing to a fork since Taylor first caught an
error I didn't see locally and it never flagged. Now that you mention it, I'll
also add log --check to my CI-s.
>
> On a sidenote, do you think we should modify the manpage? I found it
> comprehensible, but would be nice to clarify anything cryptic.
No, --check was quite clear, the `--pretty=format:"---% h% s"` part was what
was cryptic.
>
>> Anyhow if there was no output for v15 I should be fine, right?
>>
>
> At the least you should see `git log`'s output, but if there are issues
> they should be shown inline. So when you say 'no output' do you mean you
> see absolutely no output?
Absolutely no output:
https://asciinema.org/a/lsqp4e1bNst6cFWw9M2jX1IqC
But I figured out why: the whitespace and the tabs were not mixed on the line,
just across lines. As I read it, that is not an error to have tabs on one line
and spaces on the next.
Anyhow that should be now cleared up, thanks. Gotta say, I was expecting to
learn about internals doing this, but I also ended up picking up a couple of
usage things as well, like --range-diff for format patch and such.
Thanks,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref
2024-11-22 12:23 ` Bence Ferdinandy
@ 2024-11-25 2:56 ` Junio C Hamano
2024-11-26 14:57 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Junio C Hamano @ 2024-11-25 2:56 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: karthik nayak, git, phillip.wood, René Scharfe,
Johannes Schindelin, Taylor Blau, Patrick Steinhardt
"Bence Ferdinandy" <bence@ferdinandy.com> writes:
>> At the least you should see `git log`'s output, but if there are issues
>> they should be shown inline. So when you say 'no output' do you mean you
>> see absolutely no output?
>
> Absolutely no output:
> https://asciinema.org/a/lsqp4e1bNst6cFWw9M2jX1IqC
>
> But I figured out why: the whitespace and the tabs were not mixed on the line,
> just across lines. As I read it, that is not an error to have tabs on one line
> and spaces on the next.
Our .gitattribute starts like so:
* whitespace=!indent,trail,space
*.[ch] whitespace=indent,trail,space diff=cpp
so, unless otherwise specified, we frown upon trailing whitespace
and space before tab and indenting with non tab is permitted, but C
source and header files have further care about "indent" (short for
"indent-with-non-tab".
So mixed or not, if you indented with spaces and not tabs, that
would be noticed.
> Anyhow that should be now cleared up, thanks. Gotta say, I was expecting to
> learn about internals doing this, but I also ended up picking up a couple of
> usage things as well, like --range-diff for format patch and such.
I usually have "--whitespace=fix" so if you did "git log" on the
commits I made out of your patches, it is not surprising if your
"log --check" was silent.
I re-applied your v14 with "git am -s --whitespace=nowarn" and here
is what I saw.
commit 75a6a3e6597d5f3959eb269122e8c5f4e4baac0e
Author: Bence Ferdinandy <bence@ferdinandy.com>
Date: Thu Nov 21 23:55:03 2024 +0100
refs: standardize output of refs_read_symbolic_ref
When the symbolic reference we want to read with refs_read_symbolic_ref
is actually not a symbolic reference, the files and the reftable
backends return different values (1 and -1 respectively). Standardize
the returned values so that 0 is success, -1 is a generic error and -2
is that the reference was actually non-symbolic.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
refs/reftable-backend.c:833: indent with spaces.
+ if (ret)
refs/reftable-backend.c:834: indent with spaces.
+ ret = -1;
refs/reftable-backend.c:835: indent with spaces.
+ else if (ref.value_type == REFTABLE_REF_SYMREF)
refs/reftable-backend.c:837: indent with spaces.
+ else
refs/reftable-backend.c:838: indent with spaces.
+ ret = NOT_A_SYMREF;
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref
2024-11-25 2:56 ` Junio C Hamano
@ 2024-11-26 14:57 ` Bence Ferdinandy
2024-11-26 16:53 ` karthik nayak
0 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-26 14:57 UTC (permalink / raw)
To: Junio C Hamano
Cc: karthik nayak, git, phillip.wood, René Scharfe,
Johannes Schindelin, Taylor Blau, Patrick Steinhardt
sorry about the other thread, I saw "extend whitespace checks" and I thought
based on what Kartik wrote that `log --check` should have caught it, which is
why I thought it might be appropriate there.
On Mon Nov 25, 2024 at 03:56, Junio C Hamano <gitster@pobox.com> wrote:
> "Bence Ferdinandy" <bence@ferdinandy.com> writes:
>
>>> At the least you should see `git log`'s output, but if there are issues
>>> they should be shown inline. So when you say 'no output' do you mean you
>>> see absolutely no output?
>>
>> Absolutely no output:
>> https://asciinema.org/a/lsqp4e1bNst6cFWw9M2jX1IqC
>>
>> But I figured out why: the whitespace and the tabs were not mixed on the line,
>> just across lines. As I read it, that is not an error to have tabs on one line
>> and spaces on the next.
>
> Our .gitattribute starts like so:
>
> * whitespace=!indent,trail,space
> *.[ch] whitespace=indent,trail,space diff=cpp
>
> so, unless otherwise specified, we frown upon trailing whitespace
> and space before tab and indenting with non tab is permitted, but C
> source and header files have further care about "indent" (short for
> "indent-with-non-tab".
>
> So mixed or not, if you indented with spaces and not tabs, that
> would be noticed.
So `git log --check --pretty=format:"---% h% s"` was _not_ supposed to catch
this?
I ran the CI with this patch again:
https://github.com/ferdinandyb/git/actions/runs/12031250376
and it's all green, so wherever this _should_ have been caught: it didn't work.
>
>> Anyhow that should be now cleared up, thanks. Gotta say, I was expecting to
>> learn about internals doing this, but I also ended up picking up a couple of
>> usage things as well, like --range-diff for format patch and such.
>
> I usually have "--whitespace=fix" so if you did "git log" on the
> commits I made out of your patches, it is not surprising if your
> "log --check" was silent.
>
> I re-applied your v14 with "git am -s --whitespace=nowarn" and here
> is what I saw.
>
> commit 75a6a3e6597d5f3959eb269122e8c5f4e4baac0e
> Author: Bence Ferdinandy <bence@ferdinandy.com>
> Date: Thu Nov 21 23:55:03 2024 +0100
>
> refs: standardize output of refs_read_symbolic_ref
>
> When the symbolic reference we want to read with refs_read_symbolic_ref
> is actually not a symbolic reference, the files and the reftable
> backends return different values (1 and -1 respectively). Standardize
> the returned values so that 0 is success, -1 is a generic error and -2
> is that the reference was actually non-symbolic.
>
> Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
>
> refs/reftable-backend.c:833: indent with spaces.
> + if (ret)
> refs/reftable-backend.c:834: indent with spaces.
> + ret = -1;
> refs/reftable-backend.c:835: indent with spaces.
> + else if (ref.value_type == REFTABLE_REF_SYMREF)
> refs/reftable-backend.c:837: indent with spaces.
> + else
> refs/reftable-backend.c:838: indent with spaces.
> + ret = NOT_A_SYMREF;
So this did find it. Maybe something is misconfigured in the CI?
Best,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref
2024-11-26 14:57 ` Bence Ferdinandy
@ 2024-11-26 16:53 ` karthik nayak
2024-11-26 20:02 ` Junio C Hamano
2024-11-26 20:44 ` Bence Ferdinandy
0 siblings, 2 replies; 258+ messages in thread
From: karthik nayak @ 2024-11-26 16:53 UTC (permalink / raw)
To: Bence Ferdinandy, Junio C Hamano
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
Taylor Blau, Patrick Steinhardt
[-- Attachment #1: Type: text/plain, Size: 3086 bytes --]
"Bence Ferdinandy" <bence@ferdinandy.com> writes:
> sorry about the other thread, I saw "extend whitespace checks" and I thought
> based on what Kartik wrote that `log --check` should have caught it, which is
> why I thought it might be appropriate there.
>
With v14, running `git log --check --pretty=format:"---% h% s" master..`
gives me:
--- f73b2c577b fetch set_head: handle mirrored bare repositories
--- c0c84fb7f9 fetch: set remote/HEAD if it does not exist
--- c366911074 refs: add create_only option to refs_update_symref_extended
--- c47704d4df refs: add TRANSACTION_CREATE_EXISTS error
--- 22e7a9bcae remote set-head: better output for --auto
--- 25d9944eb2 remote set-head: refactor for readability
--- 01fe46c842 refs: atomically record overwritten ref in update_symref
--- fed56bc6cc refs: standardize output of refs_read_symbolic_ref
refs/reftable-backend.c:833: indent with spaces.
+ if (ret)
refs/reftable-backend.c:834: indent with spaces.
+ ret = -1;
refs/reftable-backend.c:835: indent with spaces.
+ else if (ref.value_type == REFTABLE_REF_SYMREF)
refs/reftable-backend.c:837: indent with spaces.
+ else
refs/reftable-backend.c:838: indent with spaces.
+ ret = NOT_A_SYMREF;
--- d5534d6c67 t/t5505-remote: test failure of set-head
--- a77b3b7774 t/t5505-remote: set default branch to main
> On Mon Nov 25, 2024 at 03:56, Junio C Hamano <gitster@pobox.com> wrote:
>> "Bence Ferdinandy" <bence@ferdinandy.com> writes:
>>
>>>> At the least you should see `git log`'s output, but if there are issues
>>>> they should be shown inline. So when you say 'no output' do you mean you
>>>> see absolutely no output?
>>>
>>> Absolutely no output:
>>> https://asciinema.org/a/lsqp4e1bNst6cFWw9M2jX1IqC
>>>
>>> But I figured out why: the whitespace and the tabs were not mixed on the line,
>>> just across lines. As I read it, that is not an error to have tabs on one line
>>> and spaces on the next.
>>
>> Our .gitattribute starts like so:
>>
>> * whitespace=!indent,trail,space
>> *.[ch] whitespace=indent,trail,space diff=cpp
>>
>> so, unless otherwise specified, we frown upon trailing whitespace
>> and space before tab and indenting with non tab is permitted, but C
>> source and header files have further care about "indent" (short for
>> "indent-with-non-tab".
>>
>> So mixed or not, if you indented with spaces and not tabs, that
>> would be noticed.
>
> So `git log --check --pretty=format:"---% h% s"` was _not_ supposed to catch
> this?
>
> I ran the CI with this patch again:
> https://github.com/ferdinandyb/git/actions/runs/12031250376
>
> and it's all green, so wherever this _should_ have been caught: it didn't work.
I'm not an expert in GitHub actions, but if you look at
`.github/workflows/check-whitespace.yml`, it says it is only triggered
for `pull_request`. Did you raise a pull request? From your link, I
don't see the whitespace job being triggered, so `it didn't work` would
be inaccurate, since it didn't even trigger the job ;)
[snip]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref
2024-11-26 16:53 ` karthik nayak
@ 2024-11-26 20:02 ` Junio C Hamano
2024-11-26 20:56 ` Bence Ferdinandy
2024-11-26 20:44 ` Bence Ferdinandy
1 sibling, 1 reply; 258+ messages in thread
From: Junio C Hamano @ 2024-11-26 20:02 UTC (permalink / raw)
To: karthik nayak
Cc: Bence Ferdinandy, git, phillip.wood, René Scharfe,
Johannes Schindelin, Taylor Blau, Patrick Steinhardt
karthik nayak <karthik.188@gmail.com> writes:
> With v14, running `git log --check --pretty=format:"---% h% s" master..`
> gives me:
> ...
> --- fed56bc6cc refs: standardize output of refs_read_symbolic_ref
> refs/reftable-backend.c:833: indent with spaces.
> + if (ret)
> refs/reftable-backend.c:834: indent with spaces.
> + ret = -1;
> ...
Thanks, the above matches what I saw in my message Bence responded
to (to which you are responding). I was unsure if/how the status of
each "diff --check" is propagated up to the driving "git log", but
the job is reading from the output of the command and not using the
exit status of "git log --check", so even if "git log --check" did
not signal a check failure with its status, it did not matter ;-) A
quick local check says "git log --check" does exit with 2 if there
is an offending commit in the range, so we should be OK.
By the way, I appreciate the cuteness of "% s" but I do not see the
point of "% h", as I do not think of a situation where '%h' can ever
be empty. It seems that this was carried from the first day when
GitHub CI learned the whitespace checks 32c83afc (ci: github action
- add check for whitespace errors, 2020-09-22).
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref
2024-11-26 20:02 ` Junio C Hamano
@ 2024-11-26 20:56 ` Bence Ferdinandy
2024-11-26 21:39 ` Junio C Hamano
0 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-26 20:56 UTC (permalink / raw)
To: Junio C Hamano, karthik nayak
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
Taylor Blau, Patrick Steinhardt
On Tue Nov 26, 2024 at 21:02, Junio C Hamano <gitster@pobox.com> wrote:
> karthik nayak <karthik.188@gmail.com> writes:
>
>> With v14, running `git log --check --pretty=format:"---% h% s" master..`
>> gives me:
>> ...
>> --- fed56bc6cc refs: standardize output of refs_read_symbolic_ref
>> refs/reftable-backend.c:833: indent with spaces.
>> + if (ret)
>> refs/reftable-backend.c:834: indent with spaces.
>> + ret = -1;
>> ...
>
> Thanks, the above matches what I saw in my message Bence responded
> to (to which you are responding). I was unsure if/how the status of
> each "diff --check" is propagated up to the driving "git log", but
> the job is reading from the output of the command and not using the
> exit status of "git log --check", so even if "git log --check" did
> not signal a check failure with its status, it did not matter ;-) A
> quick local check says "git log --check" does exit with 2 if there
> is an offending commit in the range, so we should be OK.
Ok, so `git diff --check master` at least does produce the output for me, and
now that you mentioned the exit code, I checked that even though I get zero
output from git log --check, the exit code is indeed 2. So now I just don't get
why I don't see any output ...
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref
2024-11-26 20:56 ` Bence Ferdinandy
@ 2024-11-26 21:39 ` Junio C Hamano
0 siblings, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-11-26 21:39 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: karthik nayak, git, phillip.wood, René Scharfe,
Johannes Schindelin, Taylor Blau, Patrick Steinhardt
"Bence Ferdinandy" <bence@ferdinandy.com> writes:
> Ok, so `git diff --check master` at least does produce the output for me, and
> now that you mentioned the exit code, I checked that even though I get zero
> output from git log --check, the exit code is indeed 2. So now I just don't get
> why I don't see any output ...
I was about to say "perhaps you are accidentally giving a wrong
revision range, like 'git checkout master && git log --check
master..'?" but then the command should exit with 0 for an empty
range, so that is not it.
Do let us know when you find out why ;-)
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref
2024-11-26 16:53 ` karthik nayak
2024-11-26 20:02 ` Junio C Hamano
@ 2024-11-26 20:44 ` Bence Ferdinandy
1 sibling, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-26 20:44 UTC (permalink / raw)
To: karthik nayak, Junio C Hamano
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
Taylor Blau, Patrick Steinhardt
On Tue Nov 26, 2024 at 17:53, karthik nayak <karthik.188@gmail.com> wrote:
> "Bence Ferdinandy" <bence@ferdinandy.com> writes:
>
>> sorry about the other thread, I saw "extend whitespace checks" and I thought
>> based on what Kartik wrote that `log --check` should have caught it, which is
>> why I thought it might be appropriate there.
>>
>
> With v14, running `git log --check --pretty=format:"---% h% s" master..`
> gives me:
>
> --- f73b2c577b fetch set_head: handle mirrored bare repositories
>
> --- c0c84fb7f9 fetch: set remote/HEAD if it does not exist
>
> --- c366911074 refs: add create_only option to refs_update_symref_extended
>
> --- c47704d4df refs: add TRANSACTION_CREATE_EXISTS error
>
> --- 22e7a9bcae remote set-head: better output for --auto
>
> --- 25d9944eb2 remote set-head: refactor for readability
>
> --- 01fe46c842 refs: atomically record overwritten ref in update_symref
>
> --- fed56bc6cc refs: standardize output of refs_read_symbolic_ref
> refs/reftable-backend.c:833: indent with spaces.
> + if (ret)
> refs/reftable-backend.c:834: indent with spaces.
> + ret = -1;
> refs/reftable-backend.c:835: indent with spaces.
> + else if (ref.value_type == REFTABLE_REF_SYMREF)
> refs/reftable-backend.c:837: indent with spaces.
> + else
> refs/reftable-backend.c:838: indent with spaces.
> + ret = NOT_A_SYMREF;
>
> --- d5534d6c67 t/t5505-remote: test failure of set-head
>
> --- a77b3b7774 t/t5505-remote: set default branch to main
So at least now I know why I'm confused :) I copied your exact command (well,
with upstream/master..), and I still have zero output. Since .gitattributes is
committed to the repository, what else could be the difference?
[snip]
>>
>> So `git log --check --pretty=format:"---% h% s"` was _not_ supposed to catch
>> this?
>>
>> I ran the CI with this patch again:
>> https://github.com/ferdinandyb/git/actions/runs/12031250376
>>
>> and it's all green, so wherever this _should_ have been caught: it didn't work.
>
> I'm not an expert in GitHub actions, but if you look at
> `.github/workflows/check-whitespace.yml`, it says it is only triggered
> for `pull_request`. Did you raise a pull request? From your link, I
> don't see the whitespace job being triggered, so `it didn't work` would
> be inaccurate, since it didn't even trigger the job ;)
Well that make sense, the commit introducing it also talks about pull requests.
On the other hand: why does this run only on a pull request? The main.yml runs
on both pull requests and pushes and since the project doesn't really use pull
requests, it would make more sense to also run this on push? The
check-style.yml is similar.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref
2024-11-22 10:37 ` karthik nayak
2024-11-22 10:53 ` Bence Ferdinandy
@ 2024-11-22 11:27 ` Bence Ferdinandy
1 sibling, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-22 11:27 UTC (permalink / raw)
To: karthik nayak, git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, Taylor Blau, Patrick Steinhardt
On Fri Nov 22, 2024 at 11:37, karthik nayak <karthik.188@gmail.com> wrote:
> Bence Ferdinandy <bence@ferdinandy.com> writes:
>
> [snip]
>
>> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
>> index 38eb14d591..1809e3426a 100644
>> --- a/refs/reftable-backend.c
>> +++ b/refs/reftable-backend.c
>> @@ -830,10 +830,12 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,
>> return ret;
>>
>> ret = reftable_stack_read_ref(stack, refname, &ref);
>> - if (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)
>> + if (ret)
>> + ret = -1;
>> + else if (ref.value_type == REFTABLE_REF_SYMREF)
>> strbuf_addstr(referent, ref.value.symref);
>> - else
>> - ret = -1;
>> + else
>> + ret = NOT_A_SYMREF;
>>
>
> I was building my series on top of this, and noticed whitespace issues
> here. A simple way to check your series is to run:
Found it, thanks for catching, I had tabs and whitespaces mixed.
git log --check didn't find a problem with that though.
>
>> reftable_ref_record_release(&ref);
>> return ret;
>> --
>> 2.47.0.298.g52a96ec17b
--
bence.ferdinandy.com
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v14 04/10] refs: atomically record overwritten ref in update_symref
2024-11-21 22:55 ` [PATCH v14 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (2 preceding siblings ...)
2024-11-21 22:55 ` [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref Bence Ferdinandy
@ 2024-11-21 22:55 ` Bence Ferdinandy
2024-11-21 22:55 ` [PATCH v14 05/10] remote set-head: refactor for readability Bence Ferdinandy
` (6 subsequent siblings)
10 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-21 22:55 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
When updating a symref with update_symref it's currently not possible to
know for sure what was the previous value that was overwritten. Extend
refs_update_symref under a new function name, to record the value after
the ref has been locked if the caller of refs_update_symref_extended
requests it via a new variable in the function call. Make the return
value of the function notify the caller, if the previous value was
actually not a symbolic reference. Keep the original refs_update_symref
function with the same signature, but now as a wrapper around
refs_update_symref_extended.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: - added before_target to reftables backend
- added an extra safety check for transaction's existence in refs.c
v6: - no change
v7: - remove the whole before_target concept from the backends and
handle checking it in refs.c instead (thanks Karthik)
- rename the before_target to referent which is how the same concept
is called in the backends
- change commit prefix to be more in line with project standards
v8: no change
v9: - instead of adding parameters to refs_update_symref, rename what
was in v8 as refs_update_symref_extended and make refs_update_symref
a wrapper for that. This significantly reduces the number of files
that need to be touched, and avoids adding a lot of dummy NULL-s
in unrelated places.
v10: no change
v11: no change
v12: no change
v13: if referent is a non-symbolic ref, record the hash in referent and
signal this with a return value of -1
v14: - mistakes were made: trying to read the referent should NOT lead to
an actual error, even if it doesn't work out in the end, that part of
the code should only set the return to -2 or not touch it
- the returned error code by the above was also incorrect and now
is -2 (and uses NOT_A_SYMREF now)
refs.c | 22 ++++++++++++++++++++--
refs.h | 4 ++++
2 files changed, 24 insertions(+), 2 deletions(-)
diff --git a/refs.c b/refs.c
index 5f729ed412..d80efd58f0 100644
--- a/refs.c
+++ b/refs.c
@@ -2115,6 +2115,13 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
int refs_update_symref(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg)
+{
+ return refs_update_symref_extended(refs, ref, target, logmsg, NULL);
+}
+
+int refs_update_symref_extended(struct ref_store *refs, const char *ref,
+ const char *target, const char *logmsg,
+ struct strbuf *referent)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
@@ -2125,10 +2132,22 @@ int refs_update_symref(struct ref_store *refs, const char *ref,
ref_transaction_update(transaction, ref, NULL, NULL,
target, NULL, REF_NO_DEREF,
logmsg, &err) ||
- ref_transaction_commit(transaction, &err)) {
+ ref_transaction_prepare(transaction, &err)) {
ret = error("%s", err.buf);
+ goto cleanup;
}
+ if (referent && refs_read_symbolic_ref(refs, ref, referent) == NOT_A_SYMREF) {
+ struct object_id oid;
+ if (!refs_read_ref(refs, ref, &oid)) {
+ strbuf_addstr(referent, oid_to_hex(&oid));
+ ret = NOT_A_SYMREF;
+ }
+ }
+
+ if (ref_transaction_commit(transaction, &err))
+ ret = error("%s", err.buf);
+cleanup:
strbuf_release(&err);
if (transaction)
ref_transaction_free(transaction);
@@ -2948,4 +2967,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)
return (update->flags & REF_HAVE_OLD) &&
(!is_null_oid(&update->old_oid) || update->old_target);
}
-
diff --git a/refs.h b/refs.h
index 22c2b45b8b..5c46ac9f34 100644
--- a/refs.h
+++ b/refs.h
@@ -584,6 +584,10 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
int refs_update_symref(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg);
+int refs_update_symref_extended(struct ref_store *refs, const char *refname,
+ const char *target, const char *logmsg,
+ struct strbuf *referent);
+
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
UPDATE_REFS_DIE_ON_ERR,
--
2.47.0.298.g52a96ec17b
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v14 05/10] remote set-head: refactor for readability
2024-11-21 22:55 ` [PATCH v14 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (3 preceding siblings ...)
2024-11-21 22:55 ` [PATCH v14 04/10] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
@ 2024-11-21 22:55 ` Bence Ferdinandy
2024-11-21 22:55 ` [PATCH v14 06/10] remote set-head: better output for --auto Bence Ferdinandy
` (5 subsequent siblings)
10 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-21 22:55 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
Make two different readability refactors:
Rename strbufs "buf" and "buf2" to something more explanatory.
Instead of calling get_main_ref_store(the_repository) multiple times,
call it once and store the result in a new refs variable. Although this
change probably offers some performance benefits, the main purpose is to
shorten the line lengths of function calls using this variable.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v5: new patch (split from the next patch as a preparatory step)
v6: no change
v7: - change commit prefix to be more in line with project standards
v8: no change
v9: - further improve readability by renaming buf, and buf2 consistently
with how patch 6 was already done
v10: no change
v11: no change
v12: no change
v13: more precise wording for commit message
v14: no change
builtin/remote.c | 23 ++++++++++++-----------
1 file changed, 12 insertions(+), 11 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 8a182439f2..bcc3ee91ef 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1402,8 +1402,9 @@ static int show(int argc, const char **argv, const char *prefix)
static int set_head(int argc, const char **argv, const char *prefix)
{
int i, opt_a = 0, opt_d = 0, result = 0;
- struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
char *head_name = NULL;
+ struct ref_store *refs = get_main_ref_store(the_repository);
struct option options[] = {
OPT_BOOL('a', "auto", &opt_a,
@@ -1415,7 +1416,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options,
builtin_remote_sethead_usage, 0);
if (argc)
- strbuf_addf(&buf, "refs/remotes/%s/HEAD", argv[0]);
+ strbuf_addf(&b_head, "refs/remotes/%s/HEAD", argv[0]);
if (!opt_a && !opt_d && argc == 2) {
head_name = xstrdup(argv[1]);
@@ -1434,25 +1435,25 @@ static int set_head(int argc, const char **argv, const char *prefix)
head_name = xstrdup(states.heads.items[0].string);
free_remote_ref_states(&states);
} else if (opt_d && !opt_a && argc == 1) {
- if (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF))
- result |= error(_("Could not delete %s"), buf.buf);
+ if (refs_delete_ref(refs, NULL, b_head.buf, NULL, REF_NO_DEREF))
+ result |= error(_("Could not delete %s"), b_head.buf);
} else
usage_with_options(builtin_remote_sethead_usage, options);
if (head_name) {
- strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name);
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", argv[0], head_name);
/* make sure it's valid */
- if (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))
- result |= error(_("Not a valid ref: %s"), buf2.buf);
- else if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote set-head"))
- result |= error(_("Could not set up %s"), buf.buf);
+ if (!refs_ref_exists(refs, b_remote_head.buf))
+ result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
+ else if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, "remote set-head"))
+ result |= error(_("Could not set up %s"), b_head.buf);
else if (opt_a)
printf("%s/HEAD set to %s\n", argv[0], head_name);
free(head_name);
}
- strbuf_release(&buf);
- strbuf_release(&buf2);
+ strbuf_release(&b_head);
+ strbuf_release(&b_remote_head);
return result;
}
--
2.47.0.298.g52a96ec17b
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v14 06/10] remote set-head: better output for --auto
2024-11-21 22:55 ` [PATCH v14 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (4 preceding siblings ...)
2024-11-21 22:55 ` [PATCH v14 05/10] remote set-head: refactor for readability Bence Ferdinandy
@ 2024-11-21 22:55 ` Bence Ferdinandy
2024-11-21 22:55 ` [PATCH v14 07/10] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
` (4 subsequent siblings)
10 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-21 22:55 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
Currently, set-head --auto will print a message saying "remote/HEAD set
to branch", which implies something was changed.
Change the output of --auto, so the output actually reflects what was
done: a) set a previously unset HEAD, b) change HEAD because remote
changed or c) no updates. As edge cases, if HEAD is changed from
a previous symbolic reference that was not a remote branch, explicitly
call attention to this fact, and also notify the user if the previous
reference was not a symbolic reference.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v1-v2:
was RFC in https://lore.kernel.org/git/20240910203835.2288291-1-bence@ferdinandy.com/
v3:
This patch was originally sent along when I thought set-head was
going to be invoked by fetch, but the discussion on the RFC
concluded that it should be not. This opened the possibility to make
it more explicit.
Note: although I feel both things the patch does are really just
cosmetic, an argument could be made for breaking it into two, one
for the no-op part and one for the --auto print update.
Was sent in:
https://lore.kernel.org/git/20240915221055.904107-1-bence@ferdinandy.com/
v4:
- changes are now handled atomically via the ref update transaction
- outputs have changed along the lines of Junio's suggestion
- minor refactor to set_head for improved legibility
v5: - the minor refactor has been split out into its own patch
v6: - fixed uninitialized prev_head
- fixed case of unusual previous target
- fixed a test that would have been actually broken at this patch
(the output was only correct with the later update to fetch)
- added 4 tests for the 4 output cases
v7: - change commit prefix to be more in line with project standards
- fixed tests to also work with the reftable backend
- renamed report function, fixed style issue with checking buf len
- fixed not releasing an strbuf
v8: no change
v9: - mark output strings in report_set_head_auto as translatable
- rename buf_prev to b_local_head for consistency
- use ${SQ} in tests instead of '\''
v10: no change
v11: no change
v12: no change
v13: added handling the edge case of previous remote/HEAD being
a detached HEAD
v14: - fixed badly named variable
- fixed not reporting errors correctly
builtin/remote.c | 59 +++++++++++++++++++++++++++++++++++---------
t/t5505-remote.sh | 63 ++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 109 insertions(+), 13 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index bcc3ee91ef..6d659d63ca 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1399,10 +1399,38 @@ static int show(int argc, const char **argv, const char *prefix)
return result;
}
+static void report_set_head_auto(const char *remote, const char *head_name,
+ struct strbuf *b_local_head, int was_detached) {
+ struct strbuf buf_prefix = STRBUF_INIT;
+ const char *prev_head = NULL;
+
+ strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
+ skip_prefix(b_local_head->buf, buf_prefix.buf, &prev_head);
+
+ if (prev_head && !strcmp(prev_head, head_name))
+ printf(_("'%s/HEAD' is unchanged and points to '%s'\n"),
+ remote, head_name);
+ else if (prev_head)
+ printf(_("'%s/HEAD' has changed from '%s' and now points to '%s'\n"),
+ remote, prev_head, head_name);
+ else if (!b_local_head->len)
+ printf(_("'%s/HEAD' is now created and points to '%s'\n"),
+ remote, head_name);
+ else if (was_detached && b_local_head->len)
+ printf(_("'%s/HEAD' was detached at '%s' and now points to '%s'\n"),
+ remote, b_local_head->buf, head_name);
+ else
+ printf(_("'%s/HEAD' used to point to '%s' "
+ "(which is not a remote branch), but now points to '%s'\n"),
+ remote, b_local_head->buf, head_name);
+ strbuf_release(&buf_prefix);
+}
+
static int set_head(int argc, const char **argv, const char *prefix)
{
- int i, opt_a = 0, opt_d = 0, result = 0;
- struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
+ int i, opt_a = 0, opt_d = 0, result = 0, was_detached;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
+ b_local_head = STRBUF_INIT;
char *head_name = NULL;
struct ref_store *refs = get_main_ref_store(the_repository);
@@ -1440,20 +1468,27 @@ static int set_head(int argc, const char **argv, const char *prefix)
} else
usage_with_options(builtin_remote_sethead_usage, options);
- if (head_name) {
- strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", argv[0], head_name);
- /* make sure it's valid */
- if (!refs_ref_exists(refs, b_remote_head.buf))
- result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
- else if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, "remote set-head"))
- result |= error(_("Could not set up %s"), b_head.buf);
- else if (opt_a)
- printf("%s/HEAD set to %s\n", argv[0], head_name);
- free(head_name);
+ if (!head_name)
+ goto cleanup;
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", argv[0], head_name);
+ if (!refs_ref_exists(refs, b_remote_head.buf)) {
+ result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
+ goto cleanup;
+ }
+ was_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
+ "remote set-head", &b_local_head);
+ if (was_detached == -1) {
+ result |= error(_("Could not set up %s"), b_head.buf);
+ goto cleanup;
}
+ if (opt_a)
+ report_set_head_auto(argv[0], head_name, &b_local_head, was_detached);
+cleanup:
+ free(head_name);
strbuf_release(&b_head);
strbuf_release(&b_remote_head);
+ strbuf_release(&b_local_head);
return result;
}
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 4e127bf5b8..e6acff4afe 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -444,12 +444,63 @@ test_expect_success REFFILES 'set-head --auto failure' '
)
'
+test_expect_success 'set-head --auto detects creation' '
+ (
+ cd test &&
+ git update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto to update a non symbolic ref' '
+ (
+ cd test &&
+ git update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ git update-ref refs/remotes/origin/HEAD HEAD &&
+ HEAD=$(git log --pretty="%H") &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} was detached at ${SQ}${HEAD}${SQ} and now points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects no change' '
+ (
+ cd test &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects change' '
+ (
+ cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/ahead &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} has changed from ${SQ}ahead${SQ} and now points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects strange ref' '
+ (
+ cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD refs/heads/main &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} used to point to ${SQ}refs/heads/main${SQ} (which is not a remote branch), but now points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
(
cd test &&
git fetch two "refs/heads/*:refs/remotes/two/*" &&
git remote set-head --auto two >output 2>&1 &&
- echo "two/HEAD set to main" >expect &&
+ echo "${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
test_cmp expect output
)
'
@@ -468,6 +519,16 @@ test_expect_success 'set-head explicit' '
)
'
+test_expect_success 'set-head --auto reports change' '
+ (
+ cd test &&
+ git remote set-head origin side2 &&
+ git remote set-head --auto origin >output 2>&1 &&
+ echo "${SQ}origin/HEAD${SQ} has changed from ${SQ}side2${SQ} and now points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
cat >test/expect <<EOF
Pruning origin
URL: $(pwd)/one
--
2.47.0.298.g52a96ec17b
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v14 07/10] refs: add TRANSACTION_CREATE_EXISTS error
2024-11-21 22:55 ` [PATCH v14 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (5 preceding siblings ...)
2024-11-21 22:55 ` [PATCH v14 06/10] remote set-head: better output for --auto Bence Ferdinandy
@ 2024-11-21 22:55 ` Bence Ferdinandy
2024-11-21 22:55 ` [PATCH v14 08/10] refs: add create_only option to refs_update_symref_extended Bence Ferdinandy
` (3 subsequent siblings)
10 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-21 22:55 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
Currently there is only one special error for transaction, for when
there is a naming conflict, all other errors are dumped under a generic
error. Add a new special error case for when the caller requests the
reference to be updated only when it does not yet exist and the
reference actually does exist.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: no change
v6: no change
v7: - change commit prefix to be more in line with project standards
- changed error checking to Karthik's suggestion
v8: no change
v9: - no change
v10: no change
v11: no change
v12: no change
v13: no change
v14: no change
refs.h | 4 +++-
refs/files-backend.c | 24 ++++++++++++++++--------
refs/reftable-backend.c | 6 ++++--
3 files changed, 23 insertions(+), 11 deletions(-)
diff --git a/refs.h b/refs.h
index 5c46ac9f34..b243739e4b 100644
--- a/refs.h
+++ b/refs.h
@@ -773,8 +773,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,
/* Naming conflict (for example, the ref names A and A/B conflict). */
#define TRANSACTION_NAME_CONFLICT -1
+/* When only creation was requested, but the ref already exists. */
+#define TRANSACTION_CREATE_EXISTS -2
/* All other errors. */
-#define TRANSACTION_GENERIC_ERROR -2
+#define TRANSACTION_GENERIC_ERROR -3
/*
* Perform the preparatory stages of committing `transaction`. Acquire
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 4cc43c32f2..23ae74089d 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2501,14 +2501,18 @@ static int split_symref_update(struct ref_update *update,
static int check_old_oid(struct ref_update *update, struct object_id *oid,
struct strbuf *err)
{
+ int ret = TRANSACTION_GENERIC_ERROR;
+
if (!(update->flags & REF_HAVE_OLD) ||
oideq(oid, &update->old_oid))
return 0;
- if (is_null_oid(&update->old_oid))
+ if (is_null_oid(&update->old_oid)) {
strbuf_addf(err, "cannot lock ref '%s': "
"reference already exists",
ref_update_original_update_refname(update));
+ ret = TRANSACTION_CREATE_EXISTS;
+ }
else if (is_null_oid(oid))
strbuf_addf(err, "cannot lock ref '%s': "
"reference is missing but expected %s",
@@ -2521,7 +2525,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,
oid_to_hex(oid),
oid_to_hex(&update->old_oid));
- return -1;
+ return ret;
}
/*
@@ -2601,9 +2605,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
- } else if (check_old_oid(update, &lock->old_oid, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto out;
+ } else {
+ ret = check_old_oid(update, &lock->old_oid, err);
+ if (ret) {
+ goto out;
+ }
}
} else {
/*
@@ -2634,9 +2640,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,
update->old_target);
ret = TRANSACTION_GENERIC_ERROR;
goto out;
- } else if (check_old_oid(update, &lock->old_oid, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto out;
+ } else {
+ ret = check_old_oid(update, &lock->old_oid, err);
+ if (ret) {
+ goto out;
+ }
}
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 1809e3426a..75b04e84a2 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1208,10 +1208,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
goto done;
}
} else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) {
- if (is_null_oid(&u->old_oid))
+ ret = TRANSACTION_NAME_CONFLICT;
+ if (is_null_oid(&u->old_oid)) {
strbuf_addf(err, _("cannot lock ref '%s': "
"reference already exists"),
ref_update_original_update_refname(u));
+ ret = TRANSACTION_CREATE_EXISTS;
+ }
else if (is_null_oid(¤t_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
"reference is missing but expected %s"),
@@ -1223,7 +1226,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
ref_update_original_update_refname(u),
oid_to_hex(¤t_oid),
oid_to_hex(&u->old_oid));
- ret = -1;
goto done;
}
--
2.47.0.298.g52a96ec17b
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v14 08/10] refs: add create_only option to refs_update_symref_extended
2024-11-21 22:55 ` [PATCH v14 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (6 preceding siblings ...)
2024-11-21 22:55 ` [PATCH v14 07/10] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
@ 2024-11-21 22:55 ` Bence Ferdinandy
2024-11-21 22:55 ` [PATCH v14 09/10] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
` (2 subsequent siblings)
10 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-21 22:55 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
Allow the caller to specify that it only wants to update the symref if
it does not already exist. Silently ignore the error from the
transaction API if the symref already exists.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: no change
v6: - switched from bool to int for create_only
- refactored logic in refs_update_symref with goto as layed out by
Junio
v7: - change commit prefix to be more in line with project standards
- refactored code to accommodate changes in the first patch, but
otherwise no change
v8: no change
v9: - no change (except for following through the
refs_update_symref_extended refactoring)
v10: no change
v11: no change
v12: no change
v13: changes only due to changes in previous patch
v14: no change
builtin/remote.c | 2 +-
refs.c | 33 ++++++++++++++++++++++++---------
refs.h | 2 +-
3 files changed, 26 insertions(+), 11 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 6d659d63ca..3dc1135b5c 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1476,7 +1476,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
goto cleanup;
}
was_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
- "remote set-head", &b_local_head);
+ "remote set-head", &b_local_head, 0);
if (was_detached == -1) {
result |= error(_("Could not set up %s"), b_head.buf);
goto cleanup;
diff --git a/refs.c b/refs.c
index d80efd58f0..2efa6bcc5c 100644
--- a/refs.c
+++ b/refs.c
@@ -2116,26 +2116,38 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
int refs_update_symref(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg)
{
- return refs_update_symref_extended(refs, ref, target, logmsg, NULL);
+ return refs_update_symref_extended(refs, ref, target, logmsg, NULL, 0);
}
int refs_update_symref_extended(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg,
- struct strbuf *referent)
+ struct strbuf *referent, int create_only)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
- int ret = 0;
+ int ret = 0, prepret = 0;
transaction = ref_store_transaction_begin(refs, &err);
- if (!transaction ||
- ref_transaction_update(transaction, ref, NULL, NULL,
- target, NULL, REF_NO_DEREF,
- logmsg, &err) ||
- ref_transaction_prepare(transaction, &err)) {
+ if (!transaction) {
+ error_return:
ret = error("%s", err.buf);
goto cleanup;
}
+ if (create_only) {
+ if (ref_transaction_create(transaction, ref, NULL, target,
+ REF_NO_DEREF, logmsg, &err))
+ goto error_return;
+ prepret = ref_transaction_prepare(transaction, &err);
+ if (prepret && prepret != TRANSACTION_CREATE_EXISTS)
+ goto error_return;
+ } else {
+ if (ref_transaction_update(transaction, ref, NULL, NULL,
+ target, NULL, REF_NO_DEREF,
+ logmsg, &err) ||
+ ref_transaction_prepare(transaction, &err))
+ goto error_return;
+ }
+
if (referent && refs_read_symbolic_ref(refs, ref, referent) == NOT_A_SYMREF) {
struct object_id oid;
if (!refs_read_ref(refs, ref, &oid)) {
@@ -2144,8 +2156,11 @@ int refs_update_symref_extended(struct ref_store *refs, const char *ref,
}
}
+ if (prepret == TRANSACTION_CREATE_EXISTS)
+ goto cleanup;
+
if (ref_transaction_commit(transaction, &err))
- ret = error("%s", err.buf);
+ goto error_return;
cleanup:
strbuf_release(&err);
diff --git a/refs.h b/refs.h
index b243739e4b..be38377b1f 100644
--- a/refs.h
+++ b/refs.h
@@ -586,7 +586,7 @@ int refs_update_symref(struct ref_store *refs, const char *refname,
int refs_update_symref_extended(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg,
- struct strbuf *referent);
+ struct strbuf *referent, int create_only);
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
--
2.47.0.298.g52a96ec17b
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v14 09/10] fetch: set remote/HEAD if it does not exist
2024-11-21 22:55 ` [PATCH v14 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (7 preceding siblings ...)
2024-11-21 22:55 ` [PATCH v14 08/10] refs: add create_only option to refs_update_symref_extended Bence Ferdinandy
@ 2024-11-21 22:55 ` Bence Ferdinandy
2024-11-21 22:55 ` [PATCH v14 10/10] fetch set_head: handle mirrored bare repositories Bence Ferdinandy
2024-11-22 12:28 ` [PATCH v15 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
10 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-21 22:55 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
When cloning a repository remote/HEAD is created, but when the user
creates a repository with git init, and later adds a remote, remote/HEAD
is only created if the user explicitly runs a variant of "remote
set-head". Attempt to set remote/HEAD during fetch, if the user does not
have it already set. Silently ignore any errors.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v3: - does not rely on remote set-head anymore so it only authenticates
once
- uses the new REF_CREATE_ONLY to atomically check if the ref exists
and only write it if it doesn't
- in all other cases the maximum it does is print a warning
v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,
but updated transaction api to request a silent create only
- it now uses the atomic before_target to determine reporting
- refactored for legibility
v5: - instead of printing a not too useful message, it now fails
silently, this in line with the objective to only set up
remote/HEAD automatically if the right thing is trivial, for
everything else there is remote set-head
- fixed all failing tests
- added two new tests, one for checking if remote/HEAD is set to the
correct one, and one to test that we do not override remote/HEAD
if it has changed on the server from what we have locally
v6: - fixed style issues and unintended extra empty line
- updated function call with bool to int from previous patch's
change
- removed calls to error(...) inherited from builtin/remote.c so we
actually fail silently
- set the test for remote set-head --auto to the correct value here,
which was previously erronously set in the remote set-head patch
v7: - no change
v8: - changed logmsg in call to refs_update_symref from "remote
set-head" to "fetch"
v9: - follow through with refs_update_symref_extended
- fix test errors uncovered by the new patch
v10: no change
v11: fixed some memory leaks
v12: no change
v13: - fix printed information if the local HEAD is detached
- remove accidental formatting noise in a test
v14: remove report_set_head_auto: as discussed, any noise should only be
created with an opt out, and the configuration for this will be added in
a later patch
builtin/fetch.c | 68 ++++++++++++++++++++++++++++++++
t/t4207-log-decoration-colors.sh | 3 +-
t/t5505-remote.sh | 21 +++++++---
t/t5510-fetch.sh | 24 +++++++++++
t/t5512-ls-remote.sh | 2 +
t/t5514-fetch-multiple.sh | 17 +++++++-
t/t5516-fetch-push.sh | 3 +-
t/t5527-fetch-odd-refs.sh | 3 +-
t/t7900-maintenance.sh | 3 +-
t/t9210-scalar.sh | 5 ++-
t/t9211-scalar-clone.sh | 6 +--
t/t9902-completion.sh | 65 ++++++++++++++++++++++++++++++
12 files changed, 203 insertions(+), 17 deletions(-)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 18eff4e5fa..b569c87f97 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1578,6 +1578,66 @@ static int backfill_tags(struct display_state *display_state,
return retcode;
}
+static const char *strip_refshead(const char *name){
+ skip_prefix(name, "refs/heads/", &name);
+ return name;
+}
+
+static int set_head(const struct ref *remote_refs)
+{
+ int result = 0;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
+ const char *remote = gtransport->remote->name;
+ char *head_name = NULL;
+ struct ref *ref, *matches;
+ struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
+ struct refspec_item refspec = {
+ .force = 0,
+ .pattern = 1,
+ .src = (char *) "refs/heads/*",
+ .dst = (char *) "refs/heads/*",
+ };
+ struct string_list heads = STRING_LIST_INIT_DUP;
+ struct ref_store *refs = get_main_ref_store(the_repository);
+
+ get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
+ matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
+ fetch_map, 1);
+ for (ref = matches; ref; ref = ref->next) {
+ string_list_append(&heads, strip_refshead(ref->name));
+ }
+
+
+ if (!heads.nr)
+ result = 1;
+ else if (heads.nr > 1)
+ result = 1;
+ else
+ head_name = xstrdup(heads.items[0].string);
+
+ if (!head_name)
+ goto cleanup;
+ strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name);
+ /* make sure it's valid */
+ if (!refs_ref_exists(refs, b_remote_head.buf)) {
+ result = 1;
+ goto cleanup;
+ }
+ if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
+ "fetch", NULL, 1))
+ result = 1;
+
+cleanup:
+ free(head_name);
+ free_refs(fetch_map);
+ free_refs(matches);
+ string_list_clear(&heads, 0);
+ strbuf_release(&b_head);
+ strbuf_release(&b_remote_head);
+ return result;
+}
+
static int do_fetch(struct transport *transport,
struct refspec *rs,
const struct fetch_config *config)
@@ -1647,6 +1707,8 @@ static int do_fetch(struct transport *transport,
"refs/tags/");
}
+ strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
+
if (must_list_refs) {
trace2_region_enter("fetch", "remote_refs", the_repository);
remote_refs = transport_get_remote_refs(transport,
@@ -1791,6 +1853,12 @@ static int do_fetch(struct transport *transport,
"you need to specify exactly one branch with the --set-upstream option"));
}
}
+ if (set_head(remote_refs))
+ ;
+ /*
+ * Way too many cases where this can go wrong
+ * so let's just fail silently for now.
+ */
cleanup:
if (retcode) {
diff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh
index 73ea9e5155..d55d22cb2f 100755
--- a/t/t4207-log-decoration-colors.sh
+++ b/t/t4207-log-decoration-colors.sh
@@ -59,7 +59,8 @@ ${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \
${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \
-${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1
+${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit}, \
+${c_reset}${c_remoteBranch}other/HEAD${c_reset}${c_commit})${c_reset} A1
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
${c_stash}refs/stash${c_reset}${c_commit})${c_reset} On main: Changes to A.t
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index e6acff4afe..01d2601174 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -74,7 +74,7 @@ test_expect_success 'add another remote' '
cd test &&
git remote add -f second ../two &&
tokens_match "origin second" "$(git remote)" &&
- check_tracking_branch second main side another &&
+ check_tracking_branch second main side another HEAD &&
git for-each-ref "--format=%(refname)" refs/remotes |
sed -e "/^refs\/remotes\/origin\//d" \
-e "/^refs\/remotes\/second\//d" >actual &&
@@ -500,7 +500,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
cd test &&
git fetch two "refs/heads/*:refs/remotes/two/*" &&
git remote set-head --auto two >output 2>&1 &&
- echo "${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
+ echo "${SQ}two/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}" >expect &&
test_cmp expect output
)
'
@@ -788,8 +788,10 @@ test_expect_success 'reject --no-no-tags' '
'
cat >one/expect <<\EOF
+ apis/HEAD -> apis/main
apis/main
apis/side
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -807,11 +809,14 @@ test_expect_success 'update' '
'
cat >one/expect <<\EOF
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
+ manduca/HEAD -> manduca/main
manduca/main
manduca/side
+ megaloprepus/HEAD -> megaloprepus/main
megaloprepus/main
megaloprepus/side
EOF
@@ -819,7 +824,7 @@ EOF
test_expect_success 'update with arguments' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -851,10 +856,13 @@ test_expect_success 'update --prune' '
'
cat >one/expect <<-\EOF
+ apis/HEAD -> apis/main
apis/main
apis/side
+ manduca/HEAD -> manduca/main
manduca/main
manduca/side
+ megaloprepus/HEAD -> megaloprepus/main
megaloprepus/main
megaloprepus/side
EOF
@@ -862,7 +870,7 @@ EOF
test_expect_success 'update default' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -874,6 +882,7 @@ test_expect_success 'update default' '
'
cat >one/expect <<\EOF
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -882,7 +891,7 @@ EOF
test_expect_success 'update default (overridden, with funny whitespace)' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -896,7 +905,7 @@ test_expect_success 'update default (overridden, with funny whitespace)' '
test_expect_success 'update (with remotes.default defined)' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 0890b9f61c..87698341f5 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -75,6 +75,30 @@ test_expect_success "fetch test for-merge" '
cut -f -2 .git/FETCH_HEAD >actual &&
test_cmp expected actual'
+test_expect_success "fetch test remote HEAD" '
+ cd "$D" &&
+ cd two &&
+ git fetch &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"'
+
+test_expect_success "fetch test remote HEAD change" '
+ cd "$D" &&
+ cd two &&
+ git switch -c other &&
+ git push -u origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git fetch &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"'
+
test_expect_success 'fetch --prune on its own works as expected' '
cd "$D" &&
git clone . prune &&
diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh
index 64b3491e4e..1b3865e154 100755
--- a/t/t5512-ls-remote.sh
+++ b/t/t5512-ls-remote.sh
@@ -293,6 +293,8 @@ test_expect_success 'ls-remote with filtered symref (refname)' '
cat >expect <<-EOF &&
ref: refs/heads/main HEAD
$rev HEAD
+ ref: refs/remotes/origin/main refs/remotes/origin/HEAD
+ $rev refs/remotes/origin/HEAD
EOF
git ls-remote --symref . HEAD >actual &&
test_cmp expect actual
diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh
index 579872c258..e3482b27b2 100755
--- a/t/t5514-fetch-multiple.sh
+++ b/t/t5514-fetch-multiple.sh
@@ -45,14 +45,17 @@ test_expect_success setup '
'
cat > test/expect << EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -97,6 +100,7 @@ cat > expect << EOF
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
@@ -112,8 +116,10 @@ test_expect_success 'git fetch --multiple (but only one remote)' '
'
cat > expect << EOF
+ one/HEAD -> one/main
one/main
one/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -141,7 +147,7 @@ test_expect_success 'git fetch --multiple (bad remote names)' '
test_expect_success 'git fetch --all (skipFetchAll)' '
(cd test4 &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -153,11 +159,14 @@ test_expect_success 'git fetch --all (skipFetchAll)' '
'
cat > expect << EOF
+ one/HEAD -> one/main
one/main
one/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -165,7 +174,7 @@ EOF
test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '
(cd test4 &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -221,14 +230,17 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' '
create_fetch_all_expect () {
cat >expect <<-\EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -265,6 +277,7 @@ test_expect_success 'git fetch (fetch all remotes with fetch.all = true)' '
create_fetch_one_expect () {
cat >expect <<-\EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 331778bd42..5a051aa0c7 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -1395,7 +1395,8 @@ test_expect_success 'fetch follows tags by default' '
git tag -m "annotated" tag &&
git for-each-ref >tmp1 &&
sed -n "p; s|refs/heads/main$|refs/remotes/origin/main|p" tmp1 |
- sort -k 3 >../expect
+ sed -n "p; s|refs/heads/main$|refs/remotes/origin/HEAD|p" |
+ sort -k 4 >../expect
) &&
test_when_finished "rm -rf dst" &&
git init dst &&
diff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh
index 98ece27c6a..d3996af6ee 100755
--- a/t/t5527-fetch-odd-refs.sh
+++ b/t/t5527-fetch-odd-refs.sh
@@ -52,7 +52,8 @@ test_expect_success LONG_REF 'fetch handles extremely long refname' '
long
main
EOF
- git for-each-ref --format="%(subject)" refs/remotes/long >actual &&
+ git for-each-ref --format="%(subject)" refs/remotes/long \
+ --exclude=refs/remotes/long/HEAD >actual &&
test_cmp expect actual
'
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index c224c8450c..edb85b7145 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -329,7 +329,8 @@ test_expect_success 'incremental-repack task' '
# Delete refs that have not been repacked in these packs.
git for-each-ref --format="delete %(refname)" \
- refs/prefetch refs/tags refs/remotes >refs &&
+ refs/prefetch refs/tags refs/remotes \
+ --exclude=refs/remotes/*/HEAD >refs &&
git update-ref --stdin <refs &&
# Replace the object directory with this pack layout.
diff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh
index a30b2c9f70..2237844550 100755
--- a/t/t9210-scalar.sh
+++ b/t/t9210-scalar.sh
@@ -151,7 +151,8 @@ test_expect_success 'scalar clone' '
"$(pwd)" &&
git for-each-ref --format="%(refname)" refs/remotes/origin/ >actual &&
- echo "refs/remotes/origin/parallel" >expect &&
+ echo "refs/remotes/origin/HEAD" >>expect &&
+ echo "refs/remotes/origin/parallel" >>expect &&
test_cmp expect actual &&
test_path_is_missing 1/2 &&
@@ -220,7 +221,7 @@ test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '
done
'
- test_expect_success 'scalar reconfigure --all with detached HEADs' '
+test_expect_success 'scalar reconfigure --all with detached HEADs' '
repos="two three four" &&
for num in $repos
do
diff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh
index c16ea67c1d..d9cb6b9a3e 100755
--- a/t/t9211-scalar-clone.sh
+++ b/t/t9211-scalar-clone.sh
@@ -32,7 +32,7 @@ test_expect_success 'set up repository to clone' '
)
'
-cleanup_clone () {
+cleanup_clone() {
rm -rf "$1"
}
@@ -128,7 +128,7 @@ test_expect_success '--single-branch clones HEAD only' '
(
cd $enlistment/src &&
git for-each-ref refs/remotes/origin >out &&
- test_line_count = 1 out &&
+ test_line_count = 2 out &&
grep "refs/remotes/origin/base" out
) &&
@@ -142,7 +142,7 @@ test_expect_success '--no-single-branch clones all branches' '
(
cd $enlistment/src &&
git for-each-ref refs/remotes/origin >out &&
- test_line_count = 2 out &&
+ test_line_count = 3 out &&
grep "refs/remotes/origin/base" out &&
grep "refs/remotes/origin/parallel" out
) &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index cc6aa9f0cd..b663c4609e 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -658,6 +658,7 @@ test_expect_success '__git_refs - simple' '
HEAD
main
matching-branch
+ other/HEAD
other/branch-in-other
other/main-in-other
matching-tag
@@ -673,6 +674,7 @@ test_expect_success '__git_refs - full refs' '
cat >expected <<-EOF &&
refs/heads/main
refs/heads/matching-branch
+ refs/remotes/other/HEAD
refs/remotes/other/branch-in-other
refs/remotes/other/main-in-other
refs/tags/matching-tag
@@ -729,6 +731,7 @@ test_expect_success '__git_refs - remote on local file system - full refs' '
test_expect_success '__git_refs - configured remote' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -756,6 +759,7 @@ test_expect_success '__git_refs - configured remote - full refs' '
test_expect_success '__git_refs - configured remote - repo given on the command line' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -787,6 +791,7 @@ test_expect_success '__git_refs - configured remote - full refs - repo given on
test_expect_success '__git_refs - configured remote - remote name matches a directory' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -875,12 +880,14 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer
HEAD
main
matching-branch
+ other/HEAD
other/ambiguous
other/branch-in-other
other/main-in-other
remote/ambiguous
remote/branch-in-remote
matching-tag
+ HEAD
branch-in-other
branch-in-remote
main-in-other
@@ -904,6 +911,7 @@ test_expect_success '__git_refs - after --opt=' '
HEAD
main
matching-branch
+ other/HEAD
other/branch-in-other
other/main-in-other
matching-tag
@@ -919,6 +927,7 @@ test_expect_success '__git_refs - after --opt= - full refs' '
cat >expected <<-EOF &&
refs/heads/main
refs/heads/matching-branch
+ refs/remotes/other/HEAD
refs/remotes/other/branch-in-other
refs/remotes/other/main-in-other
refs/tags/matching-tag
@@ -935,6 +944,7 @@ test_expect_success '__git refs - excluding refs' '
^HEAD
^main
^matching-branch
+ ^other/HEAD
^other/branch-in-other
^other/main-in-other
^matching-tag
@@ -950,6 +960,7 @@ test_expect_success '__git refs - excluding full refs' '
cat >expected <<-EOF &&
^refs/heads/main
^refs/heads/matching-branch
+ ^refs/remotes/other/HEAD
^refs/remotes/other/branch-in-other
^refs/remotes/other/main-in-other
^refs/tags/matching-tag
@@ -975,6 +986,7 @@ test_expect_success '__git_refs - do not filter refs unless told so' '
main
matching-branch
matching/branch
+ other/HEAD
other/branch-in-other
other/main-in-other
other/matching/branch-in-other
@@ -1095,6 +1107,7 @@ test_expect_success '__git_complete_refs - simple' '
HEAD Z
main Z
matching-branch Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
matching-tag Z
@@ -1123,6 +1136,7 @@ test_expect_success '__git_complete_refs - matching' '
test_expect_success '__git_complete_refs - remote' '
sed -e "s/Z$//" >expected <<-EOF &&
HEAD Z
+ HEAD Z
branch-in-other Z
main-in-other Z
EOF
@@ -1139,9 +1153,11 @@ test_expect_success '__git_complete_refs - track' '
HEAD Z
main Z
matching-branch Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
matching-tag Z
+ HEAD Z
branch-in-other Z
main-in-other Z
EOF
@@ -1184,6 +1200,7 @@ test_expect_success '__git_complete_refs - suffix' '
HEAD.
main.
matching-branch.
+ other/HEAD.
other/branch-in-other.
other/main-in-other.
matching-tag.
@@ -1199,6 +1216,7 @@ test_expect_success '__git_complete_refs - suffix' '
test_expect_success '__git_complete_fetch_refspecs - simple' '
sed -e "s/Z$//" >expected <<-EOF &&
HEAD:HEAD Z
+ HEAD:HEAD Z
branch-in-other:branch-in-other Z
main-in-other:main-in-other Z
EOF
@@ -1225,6 +1243,7 @@ test_expect_success '__git_complete_fetch_refspecs - matching' '
test_expect_success '__git_complete_fetch_refspecs - prefix' '
sed -e "s/Z$//" >expected <<-EOF &&
+HEAD:HEAD Z
+ +HEAD:HEAD Z
+branch-in-other:branch-in-other Z
+main-in-other:main-in-other Z
EOF
@@ -1289,6 +1308,7 @@ test_expect_success '__git_complete_worktree_paths with -C' '
test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '
test_completion "git switch " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1435,11 +1455,13 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena
test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '
test_completion "git checkout " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1461,6 +1483,7 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl
test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '
GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git switch --guess " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1470,6 +1493,7 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G
test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '
test_completion "git switch --no-guess --guess " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1490,6 +1514,7 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1498,11 +1523,13 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete
test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '
GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git checkout --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1514,6 +1541,7 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1522,11 +1550,13 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '
test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '
test_completion "git checkout --no-guess --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1538,6 +1568,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1550,6 +1581,7 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1559,11 +1591,13 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a
test_config checkout.guess true &&
test_completion "git checkout " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1573,11 +1607,13 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.
test_config checkout.guess false &&
test_completion "git checkout --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1590,6 +1626,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1601,6 +1638,7 @@ test_expect_success 'git switch - with --detach, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1612,6 +1650,7 @@ test_expect_success 'git checkout - with --detach, complete only references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1783,6 +1822,7 @@ test_expect_success 'git switch - with -d, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1794,6 +1834,7 @@ test_expect_success 'git checkout - with -d, complete only references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1801,10 +1842,12 @@ test_expect_success 'git checkout - with -d, complete only references' '
test_expect_success 'git switch - with --track, complete only remote branches' '
test_completion "git switch --track " <<-\EOF &&
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
test_completion "git switch -t " <<-\EOF
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1812,10 +1855,12 @@ test_expect_success 'git switch - with --track, complete only remote branches' '
test_expect_success 'git checkout - with --track, complete only remote branches' '
test_completion "git checkout --track " <<-\EOF &&
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
test_completion "git checkout -t " <<-\EOF
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1834,6 +1879,7 @@ test_expect_success 'git checkout - with --no-track, complete only local referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1845,6 +1891,7 @@ test_expect_success 'git switch - with -c, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1856,6 +1903,7 @@ test_expect_success 'git switch - with -C, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1867,6 +1915,7 @@ test_expect_success 'git switch - with -c and --track, complete all references'
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1878,6 +1927,7 @@ test_expect_success 'git switch - with -C and --track, complete all references'
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1889,6 +1939,7 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1900,6 +1951,7 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1911,6 +1963,7 @@ test_expect_success 'git checkout - with -b, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1922,6 +1975,7 @@ test_expect_success 'git checkout - with -B, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1933,6 +1987,7 @@ test_expect_success 'git checkout - with -b and --track, complete all references
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1944,6 +1999,7 @@ test_expect_success 'git checkout - with -B and --track, complete all references
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1955,6 +2011,7 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1966,6 +2023,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1973,6 +2031,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen
test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '
test_completion "git switch -c " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1982,6 +2041,7 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem
test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '
test_completion "git switch -C " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2019,6 +2079,7 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche
test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '
test_completion "git checkout -b " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2028,6 +2089,7 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r
test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '
test_completion "git checkout -B " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2065,6 +2127,7 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc
test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '
test_completion "git switch --orphan " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2080,6 +2143,7 @@ test_expect_success 'git switch - --orphan with branch already provided complete
test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '
test_completion "git checkout --orphan " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2093,6 +2157,7 @@ test_expect_success 'git checkout - --orphan with branch already provided comple
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
--
2.47.0.298.g52a96ec17b
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v14 10/10] fetch set_head: handle mirrored bare repositories
2024-11-21 22:55 ` [PATCH v14 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (8 preceding siblings ...)
2024-11-21 22:55 ` [PATCH v14 09/10] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
@ 2024-11-21 22:55 ` Bence Ferdinandy
2024-11-22 12:28 ` [PATCH v15 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
10 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-21 22:55 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
When adding a remote to bare repository with "git remote add --mirror",
running fetch will fail to update HEAD to the remote's HEAD, since it
does not know how to handle bare repositories. On the other hand HEAD
already has content, since "git init --bare" has already set HEAD to
whatever is the default branch set for the user. Unless this - by chance
- is the same as the remote's HEAD, HEAD will be pointing to a bad
symref. Teach set_head to handle bare repositories, by overwriting HEAD
so it mirrors the remote's HEAD.
Note, that in this case overriding the local HEAD reference is
necessary, since HEAD will exist before fetch can be run, but this
should not be an issue, since the whole purpose of --mirror is to be an
exact mirror of the remote, so following any changes to HEAD makes
sense.
Also note, that although "git remote set-head" also fails when trying to
update the remote's locally tracked HEAD in a mirrored bare repository,
the usage of the command does not make much sense after this patch:
fetch will update the remote HEAD correctly, and setting it manually to
something else is antithetical to the concept of mirroring.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
state:
* last review: none
v10: - new patch
- handles the issue discovered in
https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/T/
v11: no change
v12: no change
v13: properly print the previous head if it was detached
v14: no change
builtin/fetch.c | 16 +++++++++++-----
t/t5505-remote.sh | 10 ++++++++++
2 files changed, 21 insertions(+), 5 deletions(-)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index b569c87f97..5103793191 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1585,7 +1585,7 @@ static const char *strip_refshead(const char *name){
static int set_head(const struct ref *remote_refs)
{
- int result = 0;
+ int result = 0, is_bare;
struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
const char *remote = gtransport->remote->name;
char *head_name = NULL;
@@ -1617,15 +1617,21 @@ static int set_head(const struct ref *remote_refs)
if (!head_name)
goto cleanup;
- strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
- strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name);
+ is_bare = is_bare_repository();
+ if (is_bare) {
+ strbuf_addstr(&b_head, "HEAD");
+ strbuf_addf(&b_remote_head, "refs/heads/%s", head_name);
+ } else {
+ strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name);
+ }
/* make sure it's valid */
- if (!refs_ref_exists(refs, b_remote_head.buf)) {
+ if (!is_bare && !refs_ref_exists(refs, b_remote_head.buf)) {
result = 1;
goto cleanup;
}
if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
- "fetch", NULL, 1))
+ "fetch", NULL, !is_bare))
result = 1;
cleanup:
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 01d2601174..b4f1ae5fb1 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -569,6 +569,16 @@ test_expect_success 'add --mirror && prune' '
)
'
+test_expect_success 'add --mirror setting HEAD' '
+ mkdir headmirror &&
+ (
+ cd headmirror &&
+ git init --bare -b notmain &&
+ git remote add --mirror -f origin ../one &&
+ test "$(git symbolic-ref HEAD)" = "refs/heads/main"
+ )
+'
+
test_expect_success 'add --mirror=fetch' '
mkdir mirror-fetch &&
git init -b main mirror-fetch/parent &&
--
2.47.0.298.g52a96ec17b
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v15 00/10] set-head/fetch remote/HEAD updates
2024-11-21 22:55 ` [PATCH v14 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (9 preceding siblings ...)
2024-11-21 22:55 ` [PATCH v14 10/10] fetch set_head: handle mirrored bare repositories Bence Ferdinandy
@ 2024-11-22 12:28 ` Bence Ferdinandy
2024-11-22 12:28 ` [PATCH v15 01/10] t/t5505-remote: set default branch to main Bence Ferdinandy
` (10 more replies)
10 siblings, 11 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-22 12:28 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
2/10: use test_must_fail and test_when_finished
3/10: whitespace fix
Bence Ferdinandy (10):
t/t5505-remote: set default branch to main
t/t5505-remote: test failure of set-head
refs: standardize output of refs_read_symbolic_ref
refs: atomically record overwritten ref in update_symref
remote set-head: refactor for readability
remote set-head: better output for --auto
refs: add TRANSACTION_CREATE_EXISTS error
refs: add create_only option to refs_update_symref_extended
fetch: set remote/HEAD if it does not exist
fetch set_head: handle mirrored bare repositories
builtin/fetch.c | 74 +++++++++++++++++++++
builtin/remote.c | 72 +++++++++++++++------
refs.c | 47 ++++++++++++--
refs.h | 19 +++++-
refs/files-backend.c | 31 +++++----
refs/refs-internal.h | 5 ++
refs/reftable-backend.c | 12 ++--
t/t4207-log-decoration-colors.sh | 3 +-
t/t5505-remote.sh | 107 +++++++++++++++++++++++++++++--
t/t5510-fetch.sh | 24 +++++++
t/t5512-ls-remote.sh | 2 +
t/t5514-fetch-multiple.sh | 17 ++++-
t/t5516-fetch-push.sh | 3 +-
t/t5527-fetch-odd-refs.sh | 3 +-
t/t7900-maintenance.sh | 3 +-
t/t9210-scalar.sh | 5 +-
t/t9211-scalar-clone.sh | 6 +-
t/t9902-completion.sh | 65 +++++++++++++++++++
18 files changed, 439 insertions(+), 59 deletions(-)
Range-diff against v14:
1: 49cfd222d5 = 1: 2e1d001d1a t/t5505-remote: set default branch to main
2: 54bf9c7fff ! 2: d24e62035a t/t5505-remote: test failure of set-head
@@ Commit message
## Notes ##
v14: new patch
+ v15: - use test_must_fail and test_when_finished
+
## builtin/remote.c ##
@@ builtin/remote.c: static int set_head(int argc, const char **argv, const char *prefix)
if (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))
@@ t/t5505-remote.sh: test_expect_success 'set-head --auto' '
'
+test_expect_success REFFILES 'set-head --auto failure' '
++ test_when_finished "rm -f test/.git/refs/remotes/origin/HEAD.lock" &&
+ (
+ cd test &&
+ touch .git/refs/remotes/origin/HEAD.lock &&
-+ git remote set-head --auto origin 2>errormsg ||
-+ tail -n1 errormsg >output &&
-+ rm .git/refs/remotes/origin/HEAD.lock &&
++ test_must_fail git remote set-head --auto origin 2>err &&
++ tail -n1 err >output &&
+ echo "error: Could not set up refs/remotes/origin/HEAD" >expect &&
+ test_cmp expect output
+ )
3: c95362236e ! 3: 1218d521e5 refs: standardize output of refs_read_symbolic_ref
@@ Notes
- rewrote comment in refs-internal.h to point to the one in refs.h
- created NOT_A_SYMREF=-2 constant
+ v15: fix whitespace issues
+
## refs.h ##
@@ refs.h: int refs_read_ref_full(struct ref_store *refs, const char *refname,
@@ refs/reftable-backend.c: static int reftable_be_read_symbolic_ref(struct ref_sto
ret = reftable_stack_read_ref(stack, refname, &ref);
- if (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)
-+ if (ret)
-+ ret = -1;
-+ else if (ref.value_type == REFTABLE_REF_SYMREF)
++ if (ret)
++ ret = -1;
++ else if (ref.value_type == REFTABLE_REF_SYMREF)
strbuf_addstr(referent, ref.value.symref);
-- else
+ else
- ret = -1;
-+ else
-+ ret = NOT_A_SYMREF;
++ ret = NOT_A_SYMREF;
reftable_ref_record_release(&ref);
return ret;
4: 21533e0e6b ! 4: 4d5f3f4f01 refs: atomically record overwritten ref in update_symref
@@ Notes
- the returned error code by the above was also incorrect and now
is -2 (and uses NOT_A_SYMREF now)
+ v15: no change
+
## refs.c ##
@@ refs.c: int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
5: 2455768c24 ! 5: 6f2cb51b0b remote set-head: refactor for readability
@@ Notes
v14: no change
+ v15: no change
+
## builtin/remote.c ##
@@ builtin/remote.c: static int show(int argc, const char **argv, const char *prefix)
static int set_head(int argc, const char **argv, const char *prefix)
6: 396537a598 ! 6: ca8687bae1 remote set-head: better output for --auto
@@ Notes
v14: - fixed badly named variable
- fixed not reporting errors correctly
+ v15: no change
+
## builtin/remote.c ##
@@ builtin/remote.c: static int show(int argc, const char **argv, const char *prefix)
return result;
7: 05e97155d1 ! 7: 45bcb33b52 refs: add TRANSACTION_CREATE_EXISTS error
@@ Notes
v14: no change
+ v15: no change
+
## refs.h ##
@@ refs.h: int ref_transaction_verify(struct ref_transaction *transaction,
8: 3ea8b1b8b6 ! 8: a8a522a089 refs: add create_only option to refs_update_symref_extended
@@ Notes
v14: no change
+ v15: no change
+
## builtin/remote.c ##
@@ builtin/remote.c: static int set_head(int argc, const char **argv, const char *prefix)
goto cleanup;
9: 287c25b801 ! 9: ff294461d8 fetch: set remote/HEAD if it does not exist
@@ Notes
created with an opt out, and the configuration for this will be added in
a later patch
+ v15: no change
+
## builtin/fetch.c ##
@@ builtin/fetch.c: static int backfill_tags(struct display_state *display_state,
return retcode;
10: 05d8526399 ! 10: 0e96260ab6 fetch set_head: handle mirrored bare repositories
@@ Notes
v14: no change
+ v15: no change
+
## builtin/fetch.c ##
@@ builtin/fetch.c: static const char *strip_refshead(const char *name){
--
2.47.0.298.g52a96ec17b
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v15 01/10] t/t5505-remote: set default branch to main
2024-11-22 12:28 ` [PATCH v15 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
@ 2024-11-22 12:28 ` Bence Ferdinandy
2024-11-22 12:28 ` [PATCH v15 02/10] t/t5505-remote: test failure of set-head Bence Ferdinandy
` (9 subsequent siblings)
10 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-22 12:28 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
Consider the bare repository called "mirror" in the test. Running `git
remote add --mirror -f origin ../one` will not change HEAD, consequently
if init.defaultBranch is not the same as what HEAD in the remote
("one"), HEAD in "mirror" will be pointing to a non-existent reference.
Hence if "mirror" is used as a remote by yet another repository,
ls-remote will not show HEAD. On the other hand, if init.defaultBranch
happens to match HEAD in "one", then ls-remote will show HEAD.
Since the "ci/run-build-and-tests.sh" script globally exports
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main for some (but not all) jobs,
there may be a drift in some tests between how the test repositories are
set up in the CI and during local testing, if the test itself uses
"master" as default instead of "main". In particular, this happens in
t5505-remote.sh. This issue does not manifest currently, as the test
does not do any remote HEAD manipulation where this would come up, but
should such things be added, a locally passing test would break the CI
and vice-versa.
Set GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main in t5505-remote to be
consistent with the CI.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v9: - new patch
- a bandaid for the CI issue noticed by Taylor (cf:
https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/), but
see
https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/ for the root cause in detail
v10: no change
v11: no change
v12: added forgotten sign-off
v13: commit message udpated to be more precise
v14: made explicit in the commit message that the default branch is only
set for some CI jobs
v15: no change
t/t5505-remote.sh | 3 +++
1 file changed, 3 insertions(+)
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 532035933f..9b50276646 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -2,6 +2,9 @@
test_description='git remote porcelain-ish'
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
--
2.47.0.298.g52a96ec17b
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v15 02/10] t/t5505-remote: test failure of set-head
2024-11-22 12:28 ` [PATCH v15 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
2024-11-22 12:28 ` [PATCH v15 01/10] t/t5505-remote: set default branch to main Bence Ferdinandy
@ 2024-11-22 12:28 ` Bence Ferdinandy
2024-11-22 12:28 ` [PATCH v15 03/10] refs: standardize output of refs_read_symbolic_ref Bence Ferdinandy
` (8 subsequent siblings)
10 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-22 12:28 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
The test coverage was missing a test for the failure branch of remote
set-head auto's output. Add the missing text and while we are at it,
correct a small grammatical mistake in the error's output ("setup" is
the noun, "set up" is the verb).
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v14: new patch
v15: - use test_must_fail and test_when_finished
builtin/remote.c | 2 +-
t/t5505-remote.sh | 12 ++++++++++++
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 9093600965..8bca3fb04e 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1445,7 +1445,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
if (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))
result |= error(_("Not a valid ref: %s"), buf2.buf);
else if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote set-head"))
- result |= error(_("Could not setup %s"), buf.buf);
+ result |= error(_("Could not set up %s"), buf.buf);
else if (opt_a)
printf("%s/HEAD set to %s\n", argv[0], head_name);
free(head_name);
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 9b50276646..61e3ecc1af 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -432,6 +432,18 @@ test_expect_success 'set-head --auto' '
)
'
+test_expect_success REFFILES 'set-head --auto failure' '
+ test_when_finished "rm -f test/.git/refs/remotes/origin/HEAD.lock" &&
+ (
+ cd test &&
+ touch .git/refs/remotes/origin/HEAD.lock &&
+ test_must_fail git remote set-head --auto origin 2>err &&
+ tail -n1 err >output &&
+ echo "error: Could not set up refs/remotes/origin/HEAD" >expect &&
+ test_cmp expect output
+ )
+'
+
test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
(
cd test &&
--
2.47.0.298.g52a96ec17b
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v15 03/10] refs: standardize output of refs_read_symbolic_ref
2024-11-22 12:28 ` [PATCH v15 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
2024-11-22 12:28 ` [PATCH v15 01/10] t/t5505-remote: set default branch to main Bence Ferdinandy
2024-11-22 12:28 ` [PATCH v15 02/10] t/t5505-remote: test failure of set-head Bence Ferdinandy
@ 2024-11-22 12:28 ` Bence Ferdinandy
2024-11-22 12:28 ` [PATCH v15 04/10] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
` (7 subsequent siblings)
10 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-22 12:28 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
When the symbolic reference we want to read with refs_read_symbolic_ref
is actually not a symbolic reference, the files and the reftable
backends return different values (1 and -1 respectively). Standardize
the returned values so that 0 is success, -1 is a generic error and -2
is that the reference was actually non-symbolic.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v13: new patch
v14: - simplified the logic in reftables backend (thanks Junio)
- update the comment in refs.h (thanks Patrick)
- rewrote comment in refs-internal.h to point to the one in refs.h
- created NOT_A_SYMREF=-2 constant
v15: fix whitespace issues
refs.h | 11 +++++++++++
refs/files-backend.c | 7 +++----
refs/refs-internal.h | 5 +++++
refs/reftable-backend.c | 6 ++++--
4 files changed, 23 insertions(+), 6 deletions(-)
diff --git a/refs.h b/refs.h
index 108dfc93b3..22c2b45b8b 100644
--- a/refs.h
+++ b/refs.h
@@ -83,6 +83,17 @@ int refs_read_ref_full(struct ref_store *refs, const char *refname,
int refs_read_ref(struct ref_store *refs, const char *refname, struct object_id *oid);
+#define NOT_A_SYMREF -2
+
+/*
+ * Read the symbolic ref named "refname" and write its immediate referent into
+ * the provided buffer. Referent is left empty if "refname" is not a symbolic
+ * ref. It does not resolve the symbolic reference recursively in case the
+ * target is also a symbolic ref.
+ *
+ * Returns 0 on success, -2 if the "refname" is not a symbolic ref,
+ * -1 otherwise.
+ */
int refs_read_symbolic_ref(struct ref_store *ref_store, const char *refname,
struct strbuf *referent);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 0824c0b8a9..4cc43c32f2 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -596,10 +596,9 @@ static int files_read_symbolic_ref(struct ref_store *ref_store, const char *refn
unsigned int type;
ret = read_ref_internal(ref_store, refname, &oid, referent, &type, &failure_errno, 1);
- if (ret)
- return ret;
-
- return !(type & REF_ISSYMREF);
+ if (!ret && !(type & REF_ISSYMREF))
+ return NOT_A_SYMREF;
+ return ret;
}
int parse_loose_ref_contents(const struct git_hash_algo *algop,
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 2313c830d8..1399fee61c 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -673,6 +673,11 @@ struct ref_storage_be {
ref_iterator_begin_fn *iterator_begin;
read_raw_ref_fn *read_raw_ref;
+
+ /*
+ * Please refer to `refs_read_symbolic_ref()` for the expected
+ * behaviour.
+ */
read_symbolic_ref_fn *read_symbolic_ref;
reflog_iterator_begin_fn *reflog_iterator_begin;
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 38eb14d591..e8afcda1f9 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -830,10 +830,12 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,
return ret;
ret = reftable_stack_read_ref(stack, refname, &ref);
- if (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)
+ if (ret)
+ ret = -1;
+ else if (ref.value_type == REFTABLE_REF_SYMREF)
strbuf_addstr(referent, ref.value.symref);
else
- ret = -1;
+ ret = NOT_A_SYMREF;
reftable_ref_record_release(&ref);
return ret;
--
2.47.0.298.g52a96ec17b
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v15 04/10] refs: atomically record overwritten ref in update_symref
2024-11-22 12:28 ` [PATCH v15 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (2 preceding siblings ...)
2024-11-22 12:28 ` [PATCH v15 03/10] refs: standardize output of refs_read_symbolic_ref Bence Ferdinandy
@ 2024-11-22 12:28 ` Bence Ferdinandy
2024-11-22 12:28 ` [PATCH v15 05/10] remote set-head: refactor for readability Bence Ferdinandy
` (6 subsequent siblings)
10 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-22 12:28 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
When updating a symref with update_symref it's currently not possible to
know for sure what was the previous value that was overwritten. Extend
refs_update_symref under a new function name, to record the value after
the ref has been locked if the caller of refs_update_symref_extended
requests it via a new variable in the function call. Make the return
value of the function notify the caller, if the previous value was
actually not a symbolic reference. Keep the original refs_update_symref
function with the same signature, but now as a wrapper around
refs_update_symref_extended.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: - added before_target to reftables backend
- added an extra safety check for transaction's existence in refs.c
v6: - no change
v7: - remove the whole before_target concept from the backends and
handle checking it in refs.c instead (thanks Karthik)
- rename the before_target to referent which is how the same concept
is called in the backends
- change commit prefix to be more in line with project standards
v8: no change
v9: - instead of adding parameters to refs_update_symref, rename what
was in v8 as refs_update_symref_extended and make refs_update_symref
a wrapper for that. This significantly reduces the number of files
that need to be touched, and avoids adding a lot of dummy NULL-s
in unrelated places.
v10: no change
v11: no change
v12: no change
v13: if referent is a non-symbolic ref, record the hash in referent and
signal this with a return value of -1
v14: - mistakes were made: trying to read the referent should NOT lead to
an actual error, even if it doesn't work out in the end, that part of
the code should only set the return to -2 or not touch it
- the returned error code by the above was also incorrect and now
is -2 (and uses NOT_A_SYMREF now)
v15: no change
refs.c | 22 ++++++++++++++++++++--
refs.h | 4 ++++
2 files changed, 24 insertions(+), 2 deletions(-)
diff --git a/refs.c b/refs.c
index 5f729ed412..d80efd58f0 100644
--- a/refs.c
+++ b/refs.c
@@ -2115,6 +2115,13 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
int refs_update_symref(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg)
+{
+ return refs_update_symref_extended(refs, ref, target, logmsg, NULL);
+}
+
+int refs_update_symref_extended(struct ref_store *refs, const char *ref,
+ const char *target, const char *logmsg,
+ struct strbuf *referent)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
@@ -2125,10 +2132,22 @@ int refs_update_symref(struct ref_store *refs, const char *ref,
ref_transaction_update(transaction, ref, NULL, NULL,
target, NULL, REF_NO_DEREF,
logmsg, &err) ||
- ref_transaction_commit(transaction, &err)) {
+ ref_transaction_prepare(transaction, &err)) {
ret = error("%s", err.buf);
+ goto cleanup;
}
+ if (referent && refs_read_symbolic_ref(refs, ref, referent) == NOT_A_SYMREF) {
+ struct object_id oid;
+ if (!refs_read_ref(refs, ref, &oid)) {
+ strbuf_addstr(referent, oid_to_hex(&oid));
+ ret = NOT_A_SYMREF;
+ }
+ }
+
+ if (ref_transaction_commit(transaction, &err))
+ ret = error("%s", err.buf);
+cleanup:
strbuf_release(&err);
if (transaction)
ref_transaction_free(transaction);
@@ -2948,4 +2967,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)
return (update->flags & REF_HAVE_OLD) &&
(!is_null_oid(&update->old_oid) || update->old_target);
}
-
diff --git a/refs.h b/refs.h
index 22c2b45b8b..5c46ac9f34 100644
--- a/refs.h
+++ b/refs.h
@@ -584,6 +584,10 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
int refs_update_symref(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg);
+int refs_update_symref_extended(struct ref_store *refs, const char *refname,
+ const char *target, const char *logmsg,
+ struct strbuf *referent);
+
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
UPDATE_REFS_DIE_ON_ERR,
--
2.47.0.298.g52a96ec17b
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v15 05/10] remote set-head: refactor for readability
2024-11-22 12:28 ` [PATCH v15 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (3 preceding siblings ...)
2024-11-22 12:28 ` [PATCH v15 04/10] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
@ 2024-11-22 12:28 ` Bence Ferdinandy
2024-11-22 12:28 ` [PATCH v15 06/10] remote set-head: better output for --auto Bence Ferdinandy
` (5 subsequent siblings)
10 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-22 12:28 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
Make two different readability refactors:
Rename strbufs "buf" and "buf2" to something more explanatory.
Instead of calling get_main_ref_store(the_repository) multiple times,
call it once and store the result in a new refs variable. Although this
change probably offers some performance benefits, the main purpose is to
shorten the line lengths of function calls using this variable.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v5: new patch (split from the next patch as a preparatory step)
v6: no change
v7: - change commit prefix to be more in line with project standards
v8: no change
v9: - further improve readability by renaming buf, and buf2 consistently
with how patch 6 was already done
v10: no change
v11: no change
v12: no change
v13: more precise wording for commit message
v14: no change
v15: no change
builtin/remote.c | 23 ++++++++++++-----------
1 file changed, 12 insertions(+), 11 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 8bca3fb04e..47ca650de8 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1402,8 +1402,9 @@ static int show(int argc, const char **argv, const char *prefix)
static int set_head(int argc, const char **argv, const char *prefix)
{
int i, opt_a = 0, opt_d = 0, result = 0;
- struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
char *head_name = NULL;
+ struct ref_store *refs = get_main_ref_store(the_repository);
struct option options[] = {
OPT_BOOL('a', "auto", &opt_a,
@@ -1415,7 +1416,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options,
builtin_remote_sethead_usage, 0);
if (argc)
- strbuf_addf(&buf, "refs/remotes/%s/HEAD", argv[0]);
+ strbuf_addf(&b_head, "refs/remotes/%s/HEAD", argv[0]);
if (!opt_a && !opt_d && argc == 2) {
head_name = xstrdup(argv[1]);
@@ -1434,25 +1435,25 @@ static int set_head(int argc, const char **argv, const char *prefix)
head_name = xstrdup(states.heads.items[0].string);
free_remote_ref_states(&states);
} else if (opt_d && !opt_a && argc == 1) {
- if (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF))
- result |= error(_("Could not delete %s"), buf.buf);
+ if (refs_delete_ref(refs, NULL, b_head.buf, NULL, REF_NO_DEREF))
+ result |= error(_("Could not delete %s"), b_head.buf);
} else
usage_with_options(builtin_remote_sethead_usage, options);
if (head_name) {
- strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name);
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", argv[0], head_name);
/* make sure it's valid */
- if (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))
- result |= error(_("Not a valid ref: %s"), buf2.buf);
- else if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote set-head"))
- result |= error(_("Could not set up %s"), buf.buf);
+ if (!refs_ref_exists(refs, b_remote_head.buf))
+ result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
+ else if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, "remote set-head"))
+ result |= error(_("Could not set up %s"), b_head.buf);
else if (opt_a)
printf("%s/HEAD set to %s\n", argv[0], head_name);
free(head_name);
}
- strbuf_release(&buf);
- strbuf_release(&buf2);
+ strbuf_release(&b_head);
+ strbuf_release(&b_remote_head);
return result;
}
--
2.47.0.298.g52a96ec17b
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v15 06/10] remote set-head: better output for --auto
2024-11-22 12:28 ` [PATCH v15 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (4 preceding siblings ...)
2024-11-22 12:28 ` [PATCH v15 05/10] remote set-head: refactor for readability Bence Ferdinandy
@ 2024-11-22 12:28 ` Bence Ferdinandy
2024-11-22 12:28 ` [PATCH v15 07/10] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
` (4 subsequent siblings)
10 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-22 12:28 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
Currently, set-head --auto will print a message saying "remote/HEAD set
to branch", which implies something was changed.
Change the output of --auto, so the output actually reflects what was
done: a) set a previously unset HEAD, b) change HEAD because remote
changed or c) no updates. As edge cases, if HEAD is changed from
a previous symbolic reference that was not a remote branch, explicitly
call attention to this fact, and also notify the user if the previous
reference was not a symbolic reference.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v1-v2:
was RFC in https://lore.kernel.org/git/20240910203835.2288291-1-bence@ferdinandy.com/
v3:
This patch was originally sent along when I thought set-head was
going to be invoked by fetch, but the discussion on the RFC
concluded that it should be not. This opened the possibility to make
it more explicit.
Note: although I feel both things the patch does are really just
cosmetic, an argument could be made for breaking it into two, one
for the no-op part and one for the --auto print update.
Was sent in:
https://lore.kernel.org/git/20240915221055.904107-1-bence@ferdinandy.com/
v4:
- changes are now handled atomically via the ref update transaction
- outputs have changed along the lines of Junio's suggestion
- minor refactor to set_head for improved legibility
v5: - the minor refactor has been split out into its own patch
v6: - fixed uninitialized prev_head
- fixed case of unusual previous target
- fixed a test that would have been actually broken at this patch
(the output was only correct with the later update to fetch)
- added 4 tests for the 4 output cases
v7: - change commit prefix to be more in line with project standards
- fixed tests to also work with the reftable backend
- renamed report function, fixed style issue with checking buf len
- fixed not releasing an strbuf
v8: no change
v9: - mark output strings in report_set_head_auto as translatable
- rename buf_prev to b_local_head for consistency
- use ${SQ} in tests instead of '\''
v10: no change
v11: no change
v12: no change
v13: added handling the edge case of previous remote/HEAD being
a detached HEAD
v14: - fixed badly named variable
- fixed not reporting errors correctly
v15: no change
builtin/remote.c | 59 +++++++++++++++++++++++++++++++++++---------
t/t5505-remote.sh | 63 ++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 109 insertions(+), 13 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 47ca650de8..e1f15e68f4 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1399,10 +1399,38 @@ static int show(int argc, const char **argv, const char *prefix)
return result;
}
+static void report_set_head_auto(const char *remote, const char *head_name,
+ struct strbuf *b_local_head, int was_detached) {
+ struct strbuf buf_prefix = STRBUF_INIT;
+ const char *prev_head = NULL;
+
+ strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
+ skip_prefix(b_local_head->buf, buf_prefix.buf, &prev_head);
+
+ if (prev_head && !strcmp(prev_head, head_name))
+ printf(_("'%s/HEAD' is unchanged and points to '%s'\n"),
+ remote, head_name);
+ else if (prev_head)
+ printf(_("'%s/HEAD' has changed from '%s' and now points to '%s'\n"),
+ remote, prev_head, head_name);
+ else if (!b_local_head->len)
+ printf(_("'%s/HEAD' is now created and points to '%s'\n"),
+ remote, head_name);
+ else if (was_detached && b_local_head->len)
+ printf(_("'%s/HEAD' was detached at '%s' and now points to '%s'\n"),
+ remote, b_local_head->buf, head_name);
+ else
+ printf(_("'%s/HEAD' used to point to '%s' "
+ "(which is not a remote branch), but now points to '%s'\n"),
+ remote, b_local_head->buf, head_name);
+ strbuf_release(&buf_prefix);
+}
+
static int set_head(int argc, const char **argv, const char *prefix)
{
- int i, opt_a = 0, opt_d = 0, result = 0;
- struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
+ int i, opt_a = 0, opt_d = 0, result = 0, was_detached;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
+ b_local_head = STRBUF_INIT;
char *head_name = NULL;
struct ref_store *refs = get_main_ref_store(the_repository);
@@ -1440,20 +1468,27 @@ static int set_head(int argc, const char **argv, const char *prefix)
} else
usage_with_options(builtin_remote_sethead_usage, options);
- if (head_name) {
- strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", argv[0], head_name);
- /* make sure it's valid */
- if (!refs_ref_exists(refs, b_remote_head.buf))
- result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
- else if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, "remote set-head"))
- result |= error(_("Could not set up %s"), b_head.buf);
- else if (opt_a)
- printf("%s/HEAD set to %s\n", argv[0], head_name);
- free(head_name);
+ if (!head_name)
+ goto cleanup;
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", argv[0], head_name);
+ if (!refs_ref_exists(refs, b_remote_head.buf)) {
+ result |= error(_("Not a valid ref: %s"), b_remote_head.buf);
+ goto cleanup;
+ }
+ was_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
+ "remote set-head", &b_local_head);
+ if (was_detached == -1) {
+ result |= error(_("Could not set up %s"), b_head.buf);
+ goto cleanup;
}
+ if (opt_a)
+ report_set_head_auto(argv[0], head_name, &b_local_head, was_detached);
+cleanup:
+ free(head_name);
strbuf_release(&b_head);
strbuf_release(&b_remote_head);
+ strbuf_release(&b_local_head);
return result;
}
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 61e3ecc1af..d15b579c95 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -444,12 +444,63 @@ test_expect_success REFFILES 'set-head --auto failure' '
)
'
+test_expect_success 'set-head --auto detects creation' '
+ (
+ cd test &&
+ git update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto to update a non symbolic ref' '
+ (
+ cd test &&
+ git update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ git update-ref refs/remotes/origin/HEAD HEAD &&
+ HEAD=$(git log --pretty="%H") &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} was detached at ${SQ}${HEAD}${SQ} and now points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects no change' '
+ (
+ cd test &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects change' '
+ (
+ cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/ahead &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} has changed from ${SQ}ahead${SQ} and now points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success 'set-head --auto detects strange ref' '
+ (
+ cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD refs/heads/main &&
+ git remote set-head --auto origin >output &&
+ echo "${SQ}origin/HEAD${SQ} used to point to ${SQ}refs/heads/main${SQ} (which is not a remote branch), but now points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
(
cd test &&
git fetch two "refs/heads/*:refs/remotes/two/*" &&
git remote set-head --auto two >output 2>&1 &&
- echo "two/HEAD set to main" >expect &&
+ echo "${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
test_cmp expect output
)
'
@@ -468,6 +519,16 @@ test_expect_success 'set-head explicit' '
)
'
+test_expect_success 'set-head --auto reports change' '
+ (
+ cd test &&
+ git remote set-head origin side2 &&
+ git remote set-head --auto origin >output 2>&1 &&
+ echo "${SQ}origin/HEAD${SQ} has changed from ${SQ}side2${SQ} and now points to ${SQ}main${SQ}" >expect &&
+ test_cmp expect output
+ )
+'
+
cat >test/expect <<EOF
Pruning origin
URL: $(pwd)/one
--
2.47.0.298.g52a96ec17b
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v15 07/10] refs: add TRANSACTION_CREATE_EXISTS error
2024-11-22 12:28 ` [PATCH v15 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (5 preceding siblings ...)
2024-11-22 12:28 ` [PATCH v15 06/10] remote set-head: better output for --auto Bence Ferdinandy
@ 2024-11-22 12:28 ` Bence Ferdinandy
2024-11-22 12:28 ` [PATCH v15 08/10] refs: add create_only option to refs_update_symref_extended Bence Ferdinandy
` (3 subsequent siblings)
10 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-22 12:28 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
Currently there is only one special error for transaction, for when
there is a naming conflict, all other errors are dumped under a generic
error. Add a new special error case for when the caller requests the
reference to be updated only when it does not yet exist and the
reference actually does exist.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: no change
v6: no change
v7: - change commit prefix to be more in line with project standards
- changed error checking to Karthik's suggestion
v8: no change
v9: - no change
v10: no change
v11: no change
v12: no change
v13: no change
v14: no change
v15: no change
refs.h | 4 +++-
refs/files-backend.c | 24 ++++++++++++++++--------
refs/reftable-backend.c | 6 ++++--
3 files changed, 23 insertions(+), 11 deletions(-)
diff --git a/refs.h b/refs.h
index 5c46ac9f34..b243739e4b 100644
--- a/refs.h
+++ b/refs.h
@@ -773,8 +773,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,
/* Naming conflict (for example, the ref names A and A/B conflict). */
#define TRANSACTION_NAME_CONFLICT -1
+/* When only creation was requested, but the ref already exists. */
+#define TRANSACTION_CREATE_EXISTS -2
/* All other errors. */
-#define TRANSACTION_GENERIC_ERROR -2
+#define TRANSACTION_GENERIC_ERROR -3
/*
* Perform the preparatory stages of committing `transaction`. Acquire
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 4cc43c32f2..23ae74089d 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2501,14 +2501,18 @@ static int split_symref_update(struct ref_update *update,
static int check_old_oid(struct ref_update *update, struct object_id *oid,
struct strbuf *err)
{
+ int ret = TRANSACTION_GENERIC_ERROR;
+
if (!(update->flags & REF_HAVE_OLD) ||
oideq(oid, &update->old_oid))
return 0;
- if (is_null_oid(&update->old_oid))
+ if (is_null_oid(&update->old_oid)) {
strbuf_addf(err, "cannot lock ref '%s': "
"reference already exists",
ref_update_original_update_refname(update));
+ ret = TRANSACTION_CREATE_EXISTS;
+ }
else if (is_null_oid(oid))
strbuf_addf(err, "cannot lock ref '%s': "
"reference is missing but expected %s",
@@ -2521,7 +2525,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,
oid_to_hex(oid),
oid_to_hex(&update->old_oid));
- return -1;
+ return ret;
}
/*
@@ -2601,9 +2605,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
- } else if (check_old_oid(update, &lock->old_oid, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto out;
+ } else {
+ ret = check_old_oid(update, &lock->old_oid, err);
+ if (ret) {
+ goto out;
+ }
}
} else {
/*
@@ -2634,9 +2640,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,
update->old_target);
ret = TRANSACTION_GENERIC_ERROR;
goto out;
- } else if (check_old_oid(update, &lock->old_oid, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto out;
+ } else {
+ ret = check_old_oid(update, &lock->old_oid, err);
+ if (ret) {
+ goto out;
+ }
}
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index e8afcda1f9..965c17d795 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1208,10 +1208,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
goto done;
}
} else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) {
- if (is_null_oid(&u->old_oid))
+ ret = TRANSACTION_NAME_CONFLICT;
+ if (is_null_oid(&u->old_oid)) {
strbuf_addf(err, _("cannot lock ref '%s': "
"reference already exists"),
ref_update_original_update_refname(u));
+ ret = TRANSACTION_CREATE_EXISTS;
+ }
else if (is_null_oid(¤t_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
"reference is missing but expected %s"),
@@ -1223,7 +1226,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
ref_update_original_update_refname(u),
oid_to_hex(¤t_oid),
oid_to_hex(&u->old_oid));
- ret = -1;
goto done;
}
--
2.47.0.298.g52a96ec17b
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v15 08/10] refs: add create_only option to refs_update_symref_extended
2024-11-22 12:28 ` [PATCH v15 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (6 preceding siblings ...)
2024-11-22 12:28 ` [PATCH v15 07/10] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
@ 2024-11-22 12:28 ` Bence Ferdinandy
2024-11-22 12:28 ` [PATCH v15 09/10] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
` (2 subsequent siblings)
10 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-22 12:28 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
Allow the caller to specify that it only wants to update the symref if
it does not already exist. Silently ignore the error from the
transaction API if the symref already exists.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
v5: no change
v6: - switched from bool to int for create_only
- refactored logic in refs_update_symref with goto as layed out by
Junio
v7: - change commit prefix to be more in line with project standards
- refactored code to accommodate changes in the first patch, but
otherwise no change
v8: no change
v9: - no change (except for following through the
refs_update_symref_extended refactoring)
v10: no change
v11: no change
v12: no change
v13: changes only due to changes in previous patch
v14: no change
v15: no change
builtin/remote.c | 2 +-
refs.c | 33 ++++++++++++++++++++++++---------
refs.h | 2 +-
3 files changed, 26 insertions(+), 11 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index e1f15e68f4..4a8b2ef678 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1476,7 +1476,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
goto cleanup;
}
was_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
- "remote set-head", &b_local_head);
+ "remote set-head", &b_local_head, 0);
if (was_detached == -1) {
result |= error(_("Could not set up %s"), b_head.buf);
goto cleanup;
diff --git a/refs.c b/refs.c
index d80efd58f0..2efa6bcc5c 100644
--- a/refs.c
+++ b/refs.c
@@ -2116,26 +2116,38 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
int refs_update_symref(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg)
{
- return refs_update_symref_extended(refs, ref, target, logmsg, NULL);
+ return refs_update_symref_extended(refs, ref, target, logmsg, NULL, 0);
}
int refs_update_symref_extended(struct ref_store *refs, const char *ref,
const char *target, const char *logmsg,
- struct strbuf *referent)
+ struct strbuf *referent, int create_only)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
- int ret = 0;
+ int ret = 0, prepret = 0;
transaction = ref_store_transaction_begin(refs, &err);
- if (!transaction ||
- ref_transaction_update(transaction, ref, NULL, NULL,
- target, NULL, REF_NO_DEREF,
- logmsg, &err) ||
- ref_transaction_prepare(transaction, &err)) {
+ if (!transaction) {
+ error_return:
ret = error("%s", err.buf);
goto cleanup;
}
+ if (create_only) {
+ if (ref_transaction_create(transaction, ref, NULL, target,
+ REF_NO_DEREF, logmsg, &err))
+ goto error_return;
+ prepret = ref_transaction_prepare(transaction, &err);
+ if (prepret && prepret != TRANSACTION_CREATE_EXISTS)
+ goto error_return;
+ } else {
+ if (ref_transaction_update(transaction, ref, NULL, NULL,
+ target, NULL, REF_NO_DEREF,
+ logmsg, &err) ||
+ ref_transaction_prepare(transaction, &err))
+ goto error_return;
+ }
+
if (referent && refs_read_symbolic_ref(refs, ref, referent) == NOT_A_SYMREF) {
struct object_id oid;
if (!refs_read_ref(refs, ref, &oid)) {
@@ -2144,8 +2156,11 @@ int refs_update_symref_extended(struct ref_store *refs, const char *ref,
}
}
+ if (prepret == TRANSACTION_CREATE_EXISTS)
+ goto cleanup;
+
if (ref_transaction_commit(transaction, &err))
- ret = error("%s", err.buf);
+ goto error_return;
cleanup:
strbuf_release(&err);
diff --git a/refs.h b/refs.h
index b243739e4b..be38377b1f 100644
--- a/refs.h
+++ b/refs.h
@@ -586,7 +586,7 @@ int refs_update_symref(struct ref_store *refs, const char *refname,
int refs_update_symref_extended(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg,
- struct strbuf *referent);
+ struct strbuf *referent, int create_only);
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
--
2.47.0.298.g52a96ec17b
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v15 09/10] fetch: set remote/HEAD if it does not exist
2024-11-22 12:28 ` [PATCH v15 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (7 preceding siblings ...)
2024-11-22 12:28 ` [PATCH v15 08/10] refs: add create_only option to refs_update_symref_extended Bence Ferdinandy
@ 2024-11-22 12:28 ` Bence Ferdinandy
2024-12-05 18:58 ` Josh Steadmon
2024-11-22 12:28 ` [PATCH v15 10/10] fetch set_head: handle mirrored bare repositories Bence Ferdinandy
2024-11-27 9:16 ` [PATCH v1] fetch: add configuration for set_head behaviour Bence Ferdinandy
10 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-22 12:28 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
When cloning a repository remote/HEAD is created, but when the user
creates a repository with git init, and later adds a remote, remote/HEAD
is only created if the user explicitly runs a variant of "remote
set-head". Attempt to set remote/HEAD during fetch, if the user does not
have it already set. Silently ignore any errors.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v3: - does not rely on remote set-head anymore so it only authenticates
once
- uses the new REF_CREATE_ONLY to atomically check if the ref exists
and only write it if it doesn't
- in all other cases the maximum it does is print a warning
v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,
but updated transaction api to request a silent create only
- it now uses the atomic before_target to determine reporting
- refactored for legibility
v5: - instead of printing a not too useful message, it now fails
silently, this in line with the objective to only set up
remote/HEAD automatically if the right thing is trivial, for
everything else there is remote set-head
- fixed all failing tests
- added two new tests, one for checking if remote/HEAD is set to the
correct one, and one to test that we do not override remote/HEAD
if it has changed on the server from what we have locally
v6: - fixed style issues and unintended extra empty line
- updated function call with bool to int from previous patch's
change
- removed calls to error(...) inherited from builtin/remote.c so we
actually fail silently
- set the test for remote set-head --auto to the correct value here,
which was previously erronously set in the remote set-head patch
v7: - no change
v8: - changed logmsg in call to refs_update_symref from "remote
set-head" to "fetch"
v9: - follow through with refs_update_symref_extended
- fix test errors uncovered by the new patch
v10: no change
v11: fixed some memory leaks
v12: no change
v13: - fix printed information if the local HEAD is detached
- remove accidental formatting noise in a test
v14: remove report_set_head_auto: as discussed, any noise should only be
created with an opt out, and the configuration for this will be added in
a later patch
v15: no change
builtin/fetch.c | 68 ++++++++++++++++++++++++++++++++
t/t4207-log-decoration-colors.sh | 3 +-
t/t5505-remote.sh | 21 +++++++---
t/t5510-fetch.sh | 24 +++++++++++
t/t5512-ls-remote.sh | 2 +
t/t5514-fetch-multiple.sh | 17 +++++++-
t/t5516-fetch-push.sh | 3 +-
t/t5527-fetch-odd-refs.sh | 3 +-
t/t7900-maintenance.sh | 3 +-
t/t9210-scalar.sh | 5 ++-
t/t9211-scalar-clone.sh | 6 +--
t/t9902-completion.sh | 65 ++++++++++++++++++++++++++++++
12 files changed, 203 insertions(+), 17 deletions(-)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 06b4611958..bbfaf50b63 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1574,6 +1574,66 @@ static int backfill_tags(struct display_state *display_state,
return retcode;
}
+static const char *strip_refshead(const char *name){
+ skip_prefix(name, "refs/heads/", &name);
+ return name;
+}
+
+static int set_head(const struct ref *remote_refs)
+{
+ int result = 0;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
+ const char *remote = gtransport->remote->name;
+ char *head_name = NULL;
+ struct ref *ref, *matches;
+ struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
+ struct refspec_item refspec = {
+ .force = 0,
+ .pattern = 1,
+ .src = (char *) "refs/heads/*",
+ .dst = (char *) "refs/heads/*",
+ };
+ struct string_list heads = STRING_LIST_INIT_DUP;
+ struct ref_store *refs = get_main_ref_store(the_repository);
+
+ get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
+ matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
+ fetch_map, 1);
+ for (ref = matches; ref; ref = ref->next) {
+ string_list_append(&heads, strip_refshead(ref->name));
+ }
+
+
+ if (!heads.nr)
+ result = 1;
+ else if (heads.nr > 1)
+ result = 1;
+ else
+ head_name = xstrdup(heads.items[0].string);
+
+ if (!head_name)
+ goto cleanup;
+ strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name);
+ /* make sure it's valid */
+ if (!refs_ref_exists(refs, b_remote_head.buf)) {
+ result = 1;
+ goto cleanup;
+ }
+ if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
+ "fetch", NULL, 1))
+ result = 1;
+
+cleanup:
+ free(head_name);
+ free_refs(fetch_map);
+ free_refs(matches);
+ string_list_clear(&heads, 0);
+ strbuf_release(&b_head);
+ strbuf_release(&b_remote_head);
+ return result;
+}
+
static int do_fetch(struct transport *transport,
struct refspec *rs,
const struct fetch_config *config)
@@ -1643,6 +1703,8 @@ static int do_fetch(struct transport *transport,
"refs/tags/");
}
+ strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
+
if (must_list_refs) {
trace2_region_enter("fetch", "remote_refs", the_repository);
remote_refs = transport_get_remote_refs(transport,
@@ -1787,6 +1849,12 @@ static int do_fetch(struct transport *transport,
"you need to specify exactly one branch with the --set-upstream option"));
}
}
+ if (set_head(remote_refs))
+ ;
+ /*
+ * Way too many cases where this can go wrong
+ * so let's just fail silently for now.
+ */
cleanup:
if (retcode) {
diff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh
index 73ea9e5155..d55d22cb2f 100755
--- a/t/t4207-log-decoration-colors.sh
+++ b/t/t4207-log-decoration-colors.sh
@@ -59,7 +59,8 @@ ${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \
${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \
-${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1
+${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit}, \
+${c_reset}${c_remoteBranch}other/HEAD${c_reset}${c_commit})${c_reset} A1
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
${c_stash}refs/stash${c_reset}${c_commit})${c_reset} On main: Changes to A.t
${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index d15b579c95..afa261409f 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -74,7 +74,7 @@ test_expect_success 'add another remote' '
cd test &&
git remote add -f second ../two &&
tokens_match "origin second" "$(git remote)" &&
- check_tracking_branch second main side another &&
+ check_tracking_branch second main side another HEAD &&
git for-each-ref "--format=%(refname)" refs/remotes |
sed -e "/^refs\/remotes\/origin\//d" \
-e "/^refs\/remotes\/second\//d" >actual &&
@@ -500,7 +500,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
cd test &&
git fetch two "refs/heads/*:refs/remotes/two/*" &&
git remote set-head --auto two >output 2>&1 &&
- echo "${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect &&
+ echo "${SQ}two/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}" >expect &&
test_cmp expect output
)
'
@@ -788,8 +788,10 @@ test_expect_success 'reject --no-no-tags' '
'
cat >one/expect <<\EOF
+ apis/HEAD -> apis/main
apis/main
apis/side
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -807,11 +809,14 @@ test_expect_success 'update' '
'
cat >one/expect <<\EOF
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
+ manduca/HEAD -> manduca/main
manduca/main
manduca/side
+ megaloprepus/HEAD -> megaloprepus/main
megaloprepus/main
megaloprepus/side
EOF
@@ -819,7 +824,7 @@ EOF
test_expect_success 'update with arguments' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -851,10 +856,13 @@ test_expect_success 'update --prune' '
'
cat >one/expect <<-\EOF
+ apis/HEAD -> apis/main
apis/main
apis/side
+ manduca/HEAD -> manduca/main
manduca/main
manduca/side
+ megaloprepus/HEAD -> megaloprepus/main
megaloprepus/main
megaloprepus/side
EOF
@@ -862,7 +870,7 @@ EOF
test_expect_success 'update default' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -874,6 +882,7 @@ test_expect_success 'update default' '
'
cat >one/expect <<\EOF
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -882,7 +891,7 @@ EOF
test_expect_success 'update default (overridden, with funny whitespace)' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -896,7 +905,7 @@ test_expect_success 'update default (overridden, with funny whitespace)' '
test_expect_success 'update (with remotes.default defined)' '
(
cd one &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 0890b9f61c..87698341f5 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -75,6 +75,30 @@ test_expect_success "fetch test for-merge" '
cut -f -2 .git/FETCH_HEAD >actual &&
test_cmp expected actual'
+test_expect_success "fetch test remote HEAD" '
+ cd "$D" &&
+ cd two &&
+ git fetch &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"'
+
+test_expect_success "fetch test remote HEAD change" '
+ cd "$D" &&
+ cd two &&
+ git switch -c other &&
+ git push -u origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git fetch &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"'
+
test_expect_success 'fetch --prune on its own works as expected' '
cd "$D" &&
git clone . prune &&
diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh
index 64b3491e4e..1b3865e154 100755
--- a/t/t5512-ls-remote.sh
+++ b/t/t5512-ls-remote.sh
@@ -293,6 +293,8 @@ test_expect_success 'ls-remote with filtered symref (refname)' '
cat >expect <<-EOF &&
ref: refs/heads/main HEAD
$rev HEAD
+ ref: refs/remotes/origin/main refs/remotes/origin/HEAD
+ $rev refs/remotes/origin/HEAD
EOF
git ls-remote --symref . HEAD >actual &&
test_cmp expect actual
diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh
index 579872c258..e3482b27b2 100755
--- a/t/t5514-fetch-multiple.sh
+++ b/t/t5514-fetch-multiple.sh
@@ -45,14 +45,17 @@ test_expect_success setup '
'
cat > test/expect << EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -97,6 +100,7 @@ cat > expect << EOF
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
@@ -112,8 +116,10 @@ test_expect_success 'git fetch --multiple (but only one remote)' '
'
cat > expect << EOF
+ one/HEAD -> one/main
one/main
one/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -141,7 +147,7 @@ test_expect_success 'git fetch --multiple (bad remote names)' '
test_expect_success 'git fetch --all (skipFetchAll)' '
(cd test4 &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -153,11 +159,14 @@ test_expect_success 'git fetch --all (skipFetchAll)' '
'
cat > expect << EOF
+ one/HEAD -> one/main
one/main
one/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -165,7 +174,7 @@ EOF
test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '
(cd test4 &&
- for b in $(git branch -r)
+ for b in $(git branch -r | grep -v HEAD)
do
git branch -r -d $b || exit 1
done &&
@@ -221,14 +230,17 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' '
create_fetch_all_expect () {
cat >expect <<-\EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
origin/main
origin/side
+ three/HEAD -> three/main
three/another
three/main
three/side
+ two/HEAD -> two/main
two/another
two/main
two/side
@@ -265,6 +277,7 @@ test_expect_success 'git fetch (fetch all remotes with fetch.all = true)' '
create_fetch_one_expect () {
cat >expect <<-\EOF
+ one/HEAD -> one/main
one/main
one/side
origin/HEAD -> origin/main
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 331778bd42..5a051aa0c7 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -1395,7 +1395,8 @@ test_expect_success 'fetch follows tags by default' '
git tag -m "annotated" tag &&
git for-each-ref >tmp1 &&
sed -n "p; s|refs/heads/main$|refs/remotes/origin/main|p" tmp1 |
- sort -k 3 >../expect
+ sed -n "p; s|refs/heads/main$|refs/remotes/origin/HEAD|p" |
+ sort -k 4 >../expect
) &&
test_when_finished "rm -rf dst" &&
git init dst &&
diff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh
index 98ece27c6a..d3996af6ee 100755
--- a/t/t5527-fetch-odd-refs.sh
+++ b/t/t5527-fetch-odd-refs.sh
@@ -52,7 +52,8 @@ test_expect_success LONG_REF 'fetch handles extremely long refname' '
long
main
EOF
- git for-each-ref --format="%(subject)" refs/remotes/long >actual &&
+ git for-each-ref --format="%(subject)" refs/remotes/long \
+ --exclude=refs/remotes/long/HEAD >actual &&
test_cmp expect actual
'
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index c224c8450c..edb85b7145 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -329,7 +329,8 @@ test_expect_success 'incremental-repack task' '
# Delete refs that have not been repacked in these packs.
git for-each-ref --format="delete %(refname)" \
- refs/prefetch refs/tags refs/remotes >refs &&
+ refs/prefetch refs/tags refs/remotes \
+ --exclude=refs/remotes/*/HEAD >refs &&
git update-ref --stdin <refs &&
# Replace the object directory with this pack layout.
diff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh
index a30b2c9f70..2237844550 100755
--- a/t/t9210-scalar.sh
+++ b/t/t9210-scalar.sh
@@ -151,7 +151,8 @@ test_expect_success 'scalar clone' '
"$(pwd)" &&
git for-each-ref --format="%(refname)" refs/remotes/origin/ >actual &&
- echo "refs/remotes/origin/parallel" >expect &&
+ echo "refs/remotes/origin/HEAD" >>expect &&
+ echo "refs/remotes/origin/parallel" >>expect &&
test_cmp expect actual &&
test_path_is_missing 1/2 &&
@@ -220,7 +221,7 @@ test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '
done
'
- test_expect_success 'scalar reconfigure --all with detached HEADs' '
+test_expect_success 'scalar reconfigure --all with detached HEADs' '
repos="two three four" &&
for num in $repos
do
diff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh
index c16ea67c1d..d9cb6b9a3e 100755
--- a/t/t9211-scalar-clone.sh
+++ b/t/t9211-scalar-clone.sh
@@ -32,7 +32,7 @@ test_expect_success 'set up repository to clone' '
)
'
-cleanup_clone () {
+cleanup_clone() {
rm -rf "$1"
}
@@ -128,7 +128,7 @@ test_expect_success '--single-branch clones HEAD only' '
(
cd $enlistment/src &&
git for-each-ref refs/remotes/origin >out &&
- test_line_count = 1 out &&
+ test_line_count = 2 out &&
grep "refs/remotes/origin/base" out
) &&
@@ -142,7 +142,7 @@ test_expect_success '--no-single-branch clones all branches' '
(
cd $enlistment/src &&
git for-each-ref refs/remotes/origin >out &&
- test_line_count = 2 out &&
+ test_line_count = 3 out &&
grep "refs/remotes/origin/base" out &&
grep "refs/remotes/origin/parallel" out
) &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index cc6aa9f0cd..b663c4609e 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -658,6 +658,7 @@ test_expect_success '__git_refs - simple' '
HEAD
main
matching-branch
+ other/HEAD
other/branch-in-other
other/main-in-other
matching-tag
@@ -673,6 +674,7 @@ test_expect_success '__git_refs - full refs' '
cat >expected <<-EOF &&
refs/heads/main
refs/heads/matching-branch
+ refs/remotes/other/HEAD
refs/remotes/other/branch-in-other
refs/remotes/other/main-in-other
refs/tags/matching-tag
@@ -729,6 +731,7 @@ test_expect_success '__git_refs - remote on local file system - full refs' '
test_expect_success '__git_refs - configured remote' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -756,6 +759,7 @@ test_expect_success '__git_refs - configured remote - full refs' '
test_expect_success '__git_refs - configured remote - repo given on the command line' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -787,6 +791,7 @@ test_expect_success '__git_refs - configured remote - full refs - repo given on
test_expect_success '__git_refs - configured remote - remote name matches a directory' '
cat >expected <<-EOF &&
HEAD
+ HEAD
branch-in-other
main-in-other
EOF
@@ -875,12 +880,14 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer
HEAD
main
matching-branch
+ other/HEAD
other/ambiguous
other/branch-in-other
other/main-in-other
remote/ambiguous
remote/branch-in-remote
matching-tag
+ HEAD
branch-in-other
branch-in-remote
main-in-other
@@ -904,6 +911,7 @@ test_expect_success '__git_refs - after --opt=' '
HEAD
main
matching-branch
+ other/HEAD
other/branch-in-other
other/main-in-other
matching-tag
@@ -919,6 +927,7 @@ test_expect_success '__git_refs - after --opt= - full refs' '
cat >expected <<-EOF &&
refs/heads/main
refs/heads/matching-branch
+ refs/remotes/other/HEAD
refs/remotes/other/branch-in-other
refs/remotes/other/main-in-other
refs/tags/matching-tag
@@ -935,6 +944,7 @@ test_expect_success '__git refs - excluding refs' '
^HEAD
^main
^matching-branch
+ ^other/HEAD
^other/branch-in-other
^other/main-in-other
^matching-tag
@@ -950,6 +960,7 @@ test_expect_success '__git refs - excluding full refs' '
cat >expected <<-EOF &&
^refs/heads/main
^refs/heads/matching-branch
+ ^refs/remotes/other/HEAD
^refs/remotes/other/branch-in-other
^refs/remotes/other/main-in-other
^refs/tags/matching-tag
@@ -975,6 +986,7 @@ test_expect_success '__git_refs - do not filter refs unless told so' '
main
matching-branch
matching/branch
+ other/HEAD
other/branch-in-other
other/main-in-other
other/matching/branch-in-other
@@ -1095,6 +1107,7 @@ test_expect_success '__git_complete_refs - simple' '
HEAD Z
main Z
matching-branch Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
matching-tag Z
@@ -1123,6 +1136,7 @@ test_expect_success '__git_complete_refs - matching' '
test_expect_success '__git_complete_refs - remote' '
sed -e "s/Z$//" >expected <<-EOF &&
HEAD Z
+ HEAD Z
branch-in-other Z
main-in-other Z
EOF
@@ -1139,9 +1153,11 @@ test_expect_success '__git_complete_refs - track' '
HEAD Z
main Z
matching-branch Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
matching-tag Z
+ HEAD Z
branch-in-other Z
main-in-other Z
EOF
@@ -1184,6 +1200,7 @@ test_expect_success '__git_complete_refs - suffix' '
HEAD.
main.
matching-branch.
+ other/HEAD.
other/branch-in-other.
other/main-in-other.
matching-tag.
@@ -1199,6 +1216,7 @@ test_expect_success '__git_complete_refs - suffix' '
test_expect_success '__git_complete_fetch_refspecs - simple' '
sed -e "s/Z$//" >expected <<-EOF &&
HEAD:HEAD Z
+ HEAD:HEAD Z
branch-in-other:branch-in-other Z
main-in-other:main-in-other Z
EOF
@@ -1225,6 +1243,7 @@ test_expect_success '__git_complete_fetch_refspecs - matching' '
test_expect_success '__git_complete_fetch_refspecs - prefix' '
sed -e "s/Z$//" >expected <<-EOF &&
+HEAD:HEAD Z
+ +HEAD:HEAD Z
+branch-in-other:branch-in-other Z
+main-in-other:main-in-other Z
EOF
@@ -1289,6 +1308,7 @@ test_expect_success '__git_complete_worktree_paths with -C' '
test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '
test_completion "git switch " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1435,11 +1455,13 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena
test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '
test_completion "git checkout " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1461,6 +1483,7 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl
test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '
GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git switch --guess " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1470,6 +1493,7 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G
test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '
test_completion "git switch --no-guess --guess " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1490,6 +1514,7 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1498,11 +1523,13 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete
test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '
GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git checkout --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1514,6 +1541,7 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1522,11 +1550,13 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '
test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '
test_completion "git checkout --no-guess --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1538,6 +1568,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1550,6 +1581,7 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1559,11 +1591,13 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a
test_config checkout.guess true &&
test_completion "git checkout " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1573,11 +1607,13 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.
test_config checkout.guess false &&
test_completion "git checkout --guess " <<-\EOF
HEAD Z
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1590,6 +1626,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1601,6 +1638,7 @@ test_expect_success 'git switch - with --detach, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1612,6 +1650,7 @@ test_expect_success 'git checkout - with --detach, complete only references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1783,6 +1822,7 @@ test_expect_success 'git switch - with -d, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1794,6 +1834,7 @@ test_expect_success 'git checkout - with -d, complete only references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1801,10 +1842,12 @@ test_expect_success 'git checkout - with -d, complete only references' '
test_expect_success 'git switch - with --track, complete only remote branches' '
test_completion "git switch --track " <<-\EOF &&
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
test_completion "git switch -t " <<-\EOF
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1812,10 +1855,12 @@ test_expect_success 'git switch - with --track, complete only remote branches' '
test_expect_success 'git checkout - with --track, complete only remote branches' '
test_completion "git checkout --track " <<-\EOF &&
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
test_completion "git checkout -t " <<-\EOF
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1834,6 +1879,7 @@ test_expect_success 'git checkout - with --no-track, complete only local referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1845,6 +1891,7 @@ test_expect_success 'git switch - with -c, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1856,6 +1903,7 @@ test_expect_success 'git switch - with -C, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1867,6 +1915,7 @@ test_expect_success 'git switch - with -c and --track, complete all references'
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1878,6 +1927,7 @@ test_expect_success 'git switch - with -C and --track, complete all references'
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1889,6 +1939,7 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1900,6 +1951,7 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1911,6 +1963,7 @@ test_expect_success 'git checkout - with -b, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1922,6 +1975,7 @@ test_expect_success 'git checkout - with -B, complete all references' '
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1933,6 +1987,7 @@ test_expect_success 'git checkout - with -b and --track, complete all references
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1944,6 +1999,7 @@ test_expect_success 'git checkout - with -B and --track, complete all references
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1955,6 +2011,7 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1966,6 +2023,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
@@ -1973,6 +2031,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen
test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '
test_completion "git switch -c " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -1982,6 +2041,7 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem
test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '
test_completion "git switch -C " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2019,6 +2079,7 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche
test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '
test_completion "git checkout -b " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2028,6 +2089,7 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r
test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '
test_completion "git checkout -B " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2065,6 +2127,7 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc
test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '
test_completion "git switch --orphan " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2080,6 +2143,7 @@ test_expect_success 'git switch - --orphan with branch already provided complete
test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '
test_completion "git checkout --orphan " <<-\EOF
+ HEAD Z
branch-in-other Z
main Z
main-in-other Z
@@ -2093,6 +2157,7 @@ test_expect_success 'git checkout - --orphan with branch already provided comple
main Z
matching-branch Z
matching-tag Z
+ other/HEAD Z
other/branch-in-other Z
other/main-in-other Z
EOF
--
2.47.0.298.g52a96ec17b
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v15 09/10] fetch: set remote/HEAD if it does not exist
2024-11-22 12:28 ` [PATCH v15 09/10] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
@ 2024-12-05 18:58 ` Josh Steadmon
2024-12-05 19:50 ` Josh Steadmon
0 siblings, 1 reply; 258+ messages in thread
From: Josh Steadmon @ 2024-12-05 18:58 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
jonathantanmy
On 2024.11.22 13:28, Bence Ferdinandy wrote:
> When cloning a repository remote/HEAD is created, but when the user
> creates a repository with git init, and later adds a remote, remote/HEAD
> is only created if the user explicitly runs a variant of "remote
> set-head". Attempt to set remote/HEAD during fetch, if the user does not
> have it already set. Silently ignore any errors.
>
> Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
At $DAYJOB, we noticed that this breaks `git fetch --tags`, although I
haven't had a chance to figure out what causes the error just yet.
I was able to bisect down to this commit using Jonathan Tan's
reproduction script:
rm -rf test_tag_1 test_tag_2
GIT=~/git/bin-wrappers/git
mkdir test_tag_1 && cd test_tag_1
REMOTE=$(pwd)
$GIT init .
touch foo.txt
$GIT add foo.txt
$GIT commit foo.txt -m "commit one"
$GIT tag foo
cd ..
mkdir test_tag_2 && cd test_tag_2
$GIT init .
echo fetch --tags
$GIT fetch --tags "file://$REMOTE"
echo regular fetch
$GIT fetch "file://$REMOTE" 'refs/tags/*:refs/tags/*'
$GIT --version
Prior to this change, the first `$GIT fetch --tags "file://$REMOTE"`
fetches the `foo` tag; with this change, it does not.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v15 09/10] fetch: set remote/HEAD if it does not exist
2024-12-05 18:58 ` Josh Steadmon
@ 2024-12-05 19:50 ` Josh Steadmon
2024-12-05 20:09 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Josh Steadmon @ 2024-12-05 19:50 UTC (permalink / raw)
To: Bence Ferdinandy, git, phillip.wood, René Scharfe,
Johannes Schindelin, Junio C Hamano, karthik.188, Taylor Blau,
Patrick Steinhardt, jonathantanmy
On 2024.12.05 10:58, Josh Steadmon wrote:
> On 2024.11.22 13:28, Bence Ferdinandy wrote:
> > When cloning a repository remote/HEAD is created, but when the user
> > creates a repository with git init, and later adds a remote, remote/HEAD
> > is only created if the user explicitly runs a variant of "remote
> > set-head". Attempt to set remote/HEAD during fetch, if the user does not
> > have it already set. Silently ignore any errors.
> >
> > Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
>
> At $DAYJOB, we noticed that this breaks `git fetch --tags`, although I
> haven't had a chance to figure out what causes the error just yet.
>
> I was able to bisect down to this commit using Jonathan Tan's
> reproduction script:
>
> rm -rf test_tag_1 test_tag_2
> GIT=~/git/bin-wrappers/git
> mkdir test_tag_1 && cd test_tag_1
> REMOTE=$(pwd)
> $GIT init .
> touch foo.txt
> $GIT add foo.txt
> $GIT commit foo.txt -m "commit one"
> $GIT tag foo
> cd ..
> mkdir test_tag_2 && cd test_tag_2
> $GIT init .
> echo fetch --tags
> $GIT fetch --tags "file://$REMOTE"
> echo regular fetch
> $GIT fetch "file://$REMOTE" 'refs/tags/*:refs/tags/*'
> $GIT --version
>
>
> Prior to this change, the first `$GIT fetch --tags "file://$REMOTE"`
> fetches the `foo` tag; with this change, it does not.
FWIW, moving this:
@@ -1643,6 +1703,8 @@ static int do_fetch(struct transport *transport,
"refs/tags/");
}
+ strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
+
to just above the prior `if` block fixes our issue and doesn't break any
tests. However, I'm not sure yet why the order of ref_prefixes should
matter here.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v15 09/10] fetch: set remote/HEAD if it does not exist
2024-12-05 19:50 ` Josh Steadmon
@ 2024-12-05 20:09 ` Bence Ferdinandy
2024-12-05 20:11 ` Josh Steadmon
0 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-12-05 20:09 UTC (permalink / raw)
To: Josh Steadmon, git, phillip.wood, René Scharfe,
Johannes Schindelin, Junio C Hamano, karthik.188, Taylor Blau,
Patrick Steinhardt, jonathantanmy
On Thu Dec 05, 2024 at 20:50, Josh Steadmon <steadmon@google.com> wrote:
> On 2024.12.05 10:58, Josh Steadmon wrote:
>> On 2024.11.22 13:28, Bence Ferdinandy wrote:
>> > When cloning a repository remote/HEAD is created, but when the user
>> > creates a repository with git init, and later adds a remote, remote/HEAD
>> > is only created if the user explicitly runs a variant of "remote
>> > set-head". Attempt to set remote/HEAD during fetch, if the user does not
>> > have it already set. Silently ignore any errors.
>> >
>> > Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
>>
>> At $DAYJOB, we noticed that this breaks `git fetch --tags`, although I
>> haven't had a chance to figure out what causes the error just yet.
>>
>> I was able to bisect down to this commit using Jonathan Tan's
>> reproduction script:
>>
>> rm -rf test_tag_1 test_tag_2
>> GIT=~/git/bin-wrappers/git
>> mkdir test_tag_1 && cd test_tag_1
>> REMOTE=$(pwd)
>> $GIT init .
>> touch foo.txt
>> $GIT add foo.txt
>> $GIT commit foo.txt -m "commit one"
>> $GIT tag foo
>> cd ..
>> mkdir test_tag_2 && cd test_tag_2
>> $GIT init .
>> echo fetch --tags
>> $GIT fetch --tags "file://$REMOTE"
>> echo regular fetch
>> $GIT fetch "file://$REMOTE" 'refs/tags/*:refs/tags/*'
>> $GIT --version
>>
>>
>> Prior to this change, the first `$GIT fetch --tags "file://$REMOTE"`
>> fetches the `foo` tag; with this change, it does not.
>
>
> FWIW, moving this:
>
> @@ -1643,6 +1703,8 @@ static int do_fetch(struct transport *transport,
> "refs/tags/");
> }
>
> + strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
> +
>
> to just above the prior `if` block fixes our issue and doesn't break any
> tests. However, I'm not sure yet why the order of ref_prefixes should
> matter here.
Thanks for looking into this! I think the issue is with
$GIT fetch --tags "file://$REMOTE"
instead of adding a proper remote. Tbh, I've never seen the above syntax before, so first I just ran your script, which reproduced the issue for me, but then I modified it to use a proper remote which works as expected:
rm -rf test_tag_1 test_tag_2
GIT=~/git/bin-wrappers/git
mkdir test_tag_1 && cd test_tag_1
REMOTE=$(pwd)
$GIT init .
touch foo.txt
$GIT add foo.txt
$GIT commit foo.txt -m "commit one"
$GIT tag foo
cd ..
mkdir test_tag_2 && cd test_tag_2
$GIT init .
$GIT remote add origin $REMOTE
echo fetch --tags
$GIT fetch origin --tags
echo regular fetch
$GIT fetch origin 'refs/tags/*:refs/tags/*'
$GIT --version
So I'm assuming this is why also the tests never caught this, since probably
all of them are using `git remote add`.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v15 09/10] fetch: set remote/HEAD if it does not exist
2024-12-05 20:09 ` Bence Ferdinandy
@ 2024-12-05 20:11 ` Josh Steadmon
2024-12-05 20:27 ` [PATCH] Fix `git fetch --tags` in repo with no configured remote Josh Steadmon
2024-12-05 20:57 ` [PATCH v15 09/10] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
0 siblings, 2 replies; 258+ messages in thread
From: Josh Steadmon @ 2024-12-05 20:11 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
jonathantanmy
On 2024.12.05 21:09, Bence Ferdinandy wrote:
>
> On Thu Dec 05, 2024 at 20:50, Josh Steadmon <steadmon@google.com> wrote:
> > On 2024.12.05 10:58, Josh Steadmon wrote:
> >> On 2024.11.22 13:28, Bence Ferdinandy wrote:
> >> > When cloning a repository remote/HEAD is created, but when the user
> >> > creates a repository with git init, and later adds a remote, remote/HEAD
> >> > is only created if the user explicitly runs a variant of "remote
> >> > set-head". Attempt to set remote/HEAD during fetch, if the user does not
> >> > have it already set. Silently ignore any errors.
> >> >
> >> > Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
> >>
> >> At $DAYJOB, we noticed that this breaks `git fetch --tags`, although I
> >> haven't had a chance to figure out what causes the error just yet.
> >>
> >> I was able to bisect down to this commit using Jonathan Tan's
> >> reproduction script:
> >>
> >> rm -rf test_tag_1 test_tag_2
> >> GIT=~/git/bin-wrappers/git
> >> mkdir test_tag_1 && cd test_tag_1
> >> REMOTE=$(pwd)
> >> $GIT init .
> >> touch foo.txt
> >> $GIT add foo.txt
> >> $GIT commit foo.txt -m "commit one"
> >> $GIT tag foo
> >> cd ..
> >> mkdir test_tag_2 && cd test_tag_2
> >> $GIT init .
> >> echo fetch --tags
> >> $GIT fetch --tags "file://$REMOTE"
> >> echo regular fetch
> >> $GIT fetch "file://$REMOTE" 'refs/tags/*:refs/tags/*'
> >> $GIT --version
> >>
> >>
> >> Prior to this change, the first `$GIT fetch --tags "file://$REMOTE"`
> >> fetches the `foo` tag; with this change, it does not.
> >
> >
> > FWIW, moving this:
> >
> > @@ -1643,6 +1703,8 @@ static int do_fetch(struct transport *transport,
> > "refs/tags/");
> > }
> >
> > + strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
> > +
> >
> > to just above the prior `if` block fixes our issue and doesn't break any
> > tests. However, I'm not sure yet why the order of ref_prefixes should
> > matter here.
>
> Thanks for looking into this! I think the issue is with
>
> $GIT fetch --tags "file://$REMOTE"
>
> instead of adding a proper remote. Tbh, I've never seen the above syntax before, so first I just ran your script, which reproduced the issue for me, but then I modified it to use a proper remote which works as expected:
>
> rm -rf test_tag_1 test_tag_2
> GIT=~/git/bin-wrappers/git
> mkdir test_tag_1 && cd test_tag_1
> REMOTE=$(pwd)
> $GIT init .
> touch foo.txt
> $GIT add foo.txt
> $GIT commit foo.txt -m "commit one"
> $GIT tag foo
> cd ..
> mkdir test_tag_2 && cd test_tag_2
> $GIT init .
> $GIT remote add origin $REMOTE
> echo fetch --tags
> $GIT fetch origin --tags
> echo regular fetch
> $GIT fetch origin 'refs/tags/*:refs/tags/*'
> $GIT --version
>
> So I'm assuming this is why also the tests never caught this, since probably
> all of them are using `git remote add`.
>
Yeah, I think the issue is that we check the `--tags` flag first, but
only add the ref_prefixes entry if it's not empty already. Then after
that we unconditionally add HEAD. So that's why moving your strvec_push
earlier fixes it. I'll send a fix + test patch in just a minute.
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH] Fix `git fetch --tags` in repo with no configured remote
2024-12-05 20:11 ` Josh Steadmon
@ 2024-12-05 20:27 ` Josh Steadmon
2024-12-06 3:07 ` Junio C Hamano
2024-12-06 3:28 ` Junio C Hamano
2024-12-05 20:57 ` [PATCH v15 09/10] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
1 sibling, 2 replies; 258+ messages in thread
From: Josh Steadmon @ 2024-12-05 20:27 UTC (permalink / raw)
To: git
Cc: bence, phillip.wood, l.s.r, Johannes.Schindelin, gitster,
karthik.188, me, ps, jonathantanmy
In 3f763ddf28 (fetch: set remote/HEAD if it does not exist, 2024-11-22),
git-fetch learned to opportunistically set $REMOTE/HEAD when fetching.
However, this broke the logic for the `--tags` flag. Specifically, we
now unconditionally add HEAD to the ref_prefixes list, but we did this
*after* deciding whether we also need to explicitly request tags.
Fix this by adding HEAD to the ref_prefixes list prior to handling the
`--tags` flag, and removing the now obsolete check whether ref_prefixes
is empty or not.
Signed-off-by: Josh Steadmon <steadmon@google.com>
---
builtin/fetch.c | 9 ++++-----
t/t5510-fetch.sh | 17 +++++++++++++++++
2 files changed, 21 insertions(+), 5 deletions(-)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index b2a36a5d95..e7b0c79678 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1699,15 +1699,14 @@ static int do_fetch(struct transport *transport,
}
}
+ strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
+
if (tags == TAGS_SET || tags == TAGS_DEFAULT) {
must_list_refs = 1;
- if (transport_ls_refs_options.ref_prefixes.nr)
- strvec_push(&transport_ls_refs_options.ref_prefixes,
- "refs/tags/");
+ strvec_push(&transport_ls_refs_options.ref_prefixes,
+ "refs/tags/");
}
- strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
-
if (must_list_refs) {
trace2_region_enter("fetch", "remote_refs", the_repository);
remote_refs = transport_get_remote_refs(transport,
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 87698341f5..d7602333ff 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -189,6 +189,23 @@ test_expect_success 'fetch --prune --tags with refspec prunes based on refspec'
git rev-parse sometag
'
+test_expect_success 'fetch --tags gets tags even without a configured remote' '
+ REMOTE="$(pwd)/test_tag_1" &&
+ git init test_tag_1 &&
+ (
+ cd test_tag_1 &&
+ test_commit foo
+ ) &&
+ git init test_tag_2 &&
+ (
+ cd test_tag_2 &&
+ git fetch --tags "file://$REMOTE" &&
+ echo "foo" >expect &&
+ git tag >actual &&
+ test_cmp expect actual
+ )
+'
+
test_expect_success REFFILES 'fetch --prune fails to delete branches' '
cd "$D" &&
git clone . prune-fail &&
base-commit: 3f763ddf28d28fe63963991513c8db4045eabadc
--
2.47.0.338.g60cca15819-goog
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH] Fix `git fetch --tags` in repo with no configured remote
2024-12-05 20:27 ` [PATCH] Fix `git fetch --tags` in repo with no configured remote Josh Steadmon
@ 2024-12-06 3:07 ` Junio C Hamano
2024-12-06 3:28 ` Junio C Hamano
1 sibling, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-12-06 3:07 UTC (permalink / raw)
To: Josh Steadmon
Cc: git, bence, phillip.wood, l.s.r, Johannes.Schindelin, karthik.188,
me, ps, jonathantanmy
Josh Steadmon <steadmon@google.com> writes:
> In 3f763ddf28 (fetch: set remote/HEAD if it does not exist, 2024-11-22),
> git-fetch learned to opportunistically set $REMOTE/HEAD when fetching.
> However, this broke the logic for the `--tags` flag. Specifically, we
> now unconditionally add HEAD to the ref_prefixes list, but we did this
> *after* deciding whether we also need to explicitly request tags.
>
> Fix this by adding HEAD to the ref_prefixes list prior to handling the
> `--tags` flag, and removing the now obsolete check whether ref_prefixes
> is empty or not.
>
> Signed-off-by: Josh Steadmon <steadmon@google.com>
> ---
> builtin/fetch.c | 9 ++++-----
> t/t5510-fetch.sh | 17 +++++++++++++++++
> 2 files changed, 21 insertions(+), 5 deletions(-)
I see Bence is happy with the fix in a nearby message, so let me
queue (and perhaps later amend it with Acked-by from Bence if we see
one) the fix.
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH] Fix `git fetch --tags` in repo with no configured remote
2024-12-05 20:27 ` [PATCH] Fix `git fetch --tags` in repo with no configured remote Josh Steadmon
2024-12-06 3:07 ` Junio C Hamano
@ 2024-12-06 3:28 ` Junio C Hamano
2024-12-06 4:00 ` Junio C Hamano
` (2 more replies)
1 sibling, 3 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-12-06 3:28 UTC (permalink / raw)
To: Josh Steadmon
Cc: git, bence, phillip.wood, l.s.r, Johannes.Schindelin, karthik.188,
me, ps, jonathantanmy
Josh Steadmon <steadmon@google.com> writes:
> In 3f763ddf28 (fetch: set remote/HEAD if it does not exist, 2024-11-22),
> git-fetch learned to opportunistically set $REMOTE/HEAD when fetching.
> However, this broke the logic for the `--tags` flag. Specifically, we
> now unconditionally add HEAD to the ref_prefixes list, but we did this
> *after* deciding whether we also need to explicitly request tags.
>
> Fix this by adding HEAD to the ref_prefixes list prior to handling the
> `--tags` flag, and removing the now obsolete check whether ref_prefixes
> is empty or not.
>
> Signed-off-by: Josh Steadmon <steadmon@google.com>
> ---
> builtin/fetch.c | 9 ++++-----
> t/t5510-fetch.sh | 17 +++++++++++++++++
> 2 files changed, 21 insertions(+), 5 deletions(-)
>
> diff --git a/builtin/fetch.c b/builtin/fetch.c
> index b2a36a5d95..e7b0c79678 100644
> --- a/builtin/fetch.c
> +++ b/builtin/fetch.c
> @@ -1699,15 +1699,14 @@ static int do_fetch(struct transport *transport,
> }
> }
>
> + strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
> +
> if (tags == TAGS_SET || tags == TAGS_DEFAULT) {
> must_list_refs = 1;
> - if (transport_ls_refs_options.ref_prefixes.nr)
> - strvec_push(&transport_ls_refs_options.ref_prefixes,
> - "refs/tags/");
> + strvec_push(&transport_ls_refs_options.ref_prefixes,
> + "refs/tags/");
> }
Stepping back a bit, do we even need to learn where HEAD points at
in the remote, when we are not doing the "opportunistically set
$REMOTE/HEAD"? Your example is "in repo with no configured remote",
which by definition means that we do not use any refs/remotes/*/ ref
hierarchy to keep track of the remote-tracking branches for the
remote we are fetching from. There is no place we record what we
learn by running ls-remote HEAD against them, so should we even push
"HEAD" to the ls-remote prefixes in such a case?
While this change may hide the breakage you saw in your set-up, we
may be now asking to ls-remote HEAD even in cases we do not need to.
> Fix this by adding HEAD to the ref_prefixes list prior to handling the
> `--tags` flag, and removing the now obsolete check whether ref_prefixes
> is empty or not.
And if we unconditionally add HEAD even when we do not need to,
especially with the loss of the ref-prefixes condition that was
there in order to implement "learn refs/tags/* hierarchy only when
we are doing the default fetch", wouldn't it mean we may learn
refs/tags/* even when we do not have to?
> Signed-off-by: Josh Steadmon <steadmon@google.com>
> ---
> builtin/fetch.c | 9 ++++-----
> t/t5510-fetch.sh | 17 +++++++++++++++++
> 2 files changed, 21 insertions(+), 5 deletions(-)
>
> diff --git a/builtin/fetch.c b/builtin/fetch.c
> index b2a36a5d95..e7b0c79678 100644
> --- a/builtin/fetch.c
> +++ b/builtin/fetch.c
> @@ -1699,15 +1699,14 @@ static int do_fetch(struct transport *transport,
> }
> }
>
> + strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
> +
> if (tags == TAGS_SET || tags == TAGS_DEFAULT) {
> must_list_refs = 1;
> - if (transport_ls_refs_options.ref_prefixes.nr)
> - strvec_push(&transport_ls_refs_options.ref_prefixes,
> - "refs/tags/");
> + strvec_push(&transport_ls_refs_options.ref_prefixes,
> + "refs/tags/");
> }
>
> - strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
> -
> if (must_list_refs) {
> trace2_region_enter("fetch", "remote_refs", the_repository);
> remote_refs = transport_get_remote_refs(transport,
> diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
> index 87698341f5..d7602333ff 100755
> --- a/t/t5510-fetch.sh
> +++ b/t/t5510-fetch.sh
> @@ -189,6 +189,23 @@ test_expect_success 'fetch --prune --tags with refspec prunes based on refspec'
> git rev-parse sometag
> '
>
> +test_expect_success 'fetch --tags gets tags even without a configured remote' '
> + REMOTE="$(pwd)/test_tag_1" &&
> + git init test_tag_1 &&
> + (
> + cd test_tag_1 &&
> + test_commit foo
> + ) &&
> + git init test_tag_2 &&
> + (
> + cd test_tag_2 &&
> + git fetch --tags "file://$REMOTE" &&
> + echo "foo" >expect &&
> + git tag >actual &&
> + test_cmp expect actual
> + )
> +'
> +
> test_expect_success REFFILES 'fetch --prune fails to delete branches' '
> cd "$D" &&
> git clone . prune-fail &&
>
> base-commit: 3f763ddf28d28fe63963991513c8db4045eabadc
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH] Fix `git fetch --tags` in repo with no configured remote
2024-12-06 3:28 ` Junio C Hamano
@ 2024-12-06 4:00 ` Junio C Hamano
2024-12-06 8:07 ` Re* " Junio C Hamano
2024-12-06 8:08 ` Junio C Hamano
2 siblings, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-12-06 4:00 UTC (permalink / raw)
To: Josh Steadmon
Cc: git, bence, phillip.wood, l.s.r, Johannes.Schindelin, karthik.188,
me, ps, jonathantanmy
Junio C Hamano <gitster@pobox.com> writes:
> And if we unconditionally add HEAD even when we do not need to,
> especially with the loss of the ref-prefixes condition that was
> there in order to implement "learn refs/tags/* hierarchy only when
> we are doing the default fetch", wouldn't it mean we may learn
> refs/tags/* even when we do not have to?
Micro-correction. "We grab tags only when we are fetching something
else as well" is what I should have said.
The general direction my comment leads to does not change, though.
Because we ask for "HEAD" even when we do not need to, the
additional change this patch names to unconditionally add "refs/tags/"
would make us ask to list all the tags even when we do not need to
see them.
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re* [PATCH] Fix `git fetch --tags` in repo with no configured remote
2024-12-06 3:28 ` Junio C Hamano
2024-12-06 4:00 ` Junio C Hamano
@ 2024-12-06 8:07 ` Junio C Hamano
2024-12-06 8:08 ` Junio C Hamano
2 siblings, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-12-06 8:07 UTC (permalink / raw)
To: Josh Steadmon
Cc: git, bence, phillip.wood, l.s.r, Johannes.Schindelin, karthik.188,
me, ps, jonathantanmy
Junio C Hamano <gitster@pobox.com> writes:
> Stepping back a bit, do we even need to learn where HEAD points at
> in the remote, when we are not doing the "opportunistically set
> $REMOTE/HEAD"? Your example is "in repo with no configured remote",
> which by definition means that we do not use any refs/remotes/*/ ref
> hierarchy to keep track of the remote-tracking branches for the
> remote we are fetching from. There is no place we record what we
> learn by running ls-remote HEAD against them, so should we even push
> "HEAD" to the ls-remote prefixes in such a case?
>
> While this change may hide the breakage you saw in your set-up, we
> may be now asking to ls-remote HEAD even in cases we do not need to.
>
>> Fix this by adding HEAD to the ref_prefixes list prior to handling the
>> `--tags` flag, and removing the now obsolete check whether ref_prefixes
>> is empty or not.
>
> And if we unconditionally add HEAD even when we do not need to,
> especially with the loss of the ref-prefixes condition that was
> there in order to implement "learn refs/tags/* hierarchy only when
> we are doing the default fetch", wouldn't it mean we may learn
> refs/tags/* even when we do not have to?
In other words, what I think the "fix" should look like is more like
the attached. It seems to pass your test, as well as existing tests
Bence added and other tests about "git fetch".
One thing I am not happy about is the abstraction violation that is
needed to make the uses_remote_tracking() helper aware of the "use
the rs, the refspec given from the command line, or if it is empty,
use the configured 'fetch' refspec from the remote" rule, which is
primarily used by get_ref_map() that is much later called, but the
layering violation started when we started limiting the ls-remote
request with narrowing common prefixes, and it would take a larger
surgery to fix, I would think.
0000---- >8 ----
Subject: [PATCH] fetch: do not ask for HEAD unnecessarily
In 3f763ddf28 (fetch: set remote/HEAD if it does not exist,
2024-11-22), git-fetch learned to opportunistically set $REMOTE/HEAD
when fetching by always asking for remote HEAD, in the hope that it
will help setting refs/remotes/<name>/HEAD if missing.
But it is not needed to always ask for remote HEAD. When we are
fetching from a remote, for which we have remote-tracking branches,
we do need to know about HEAD. But if we are doing one-shot fetch,
e.g.,
$ git fetch --tags https://github.com/git/git
we do not even know what sub-hierarchy of refs/remotes/<remote>/
we need to adjust the remote HEAD for. There is no need to ask for
HEAD in such a case.
Incidentally, because the unconditional request to list "HEAD"
affected the number of ref-prefixes requested in the ls-remote
request, this affected how the requests for tags are added to the
same ls-remote request, breaking "git fetch --tags $URL" performed
against a URL that is not configured as a remote.
Make sure we ask to list "HEAD" from the remote only when we are
fetching with configured remote for which we use remote-tracking
branches.
Reported-by: Josh Steadmon <steadmon@google.com>
[jc: tests are also borrowed from Josh's patch]
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
* Even though I borrowed some part of the commit log message from
Josh's version, it not clear to me how "*after* deciding" led to
whatever the observed breakage (which was not described in the
log message), in the following part.
Specifically, we now unconditionally add HEAD to the
ref_prefixes list, but we did this *after* deciding whether we
also need to explicitly request tags.
Bence's change asks "HEAD" after "if we are fetching something,
then also ask about refs/tags/" logic thought we are not fetching
anything (i.e. ref_prefixes.nr == 0 at that point). But before
Bence's series, the same refs/tags/ logic saw that (ref_prefix.nr
== 0), didn't it? So that does not sound like a sufficient
explanation on how the series regressed.
builtin/fetch.c | 20 +++++++++++++++++++-
t/t5510-fetch.sh | 17 +++++++++++++++++
2 files changed, 36 insertions(+), 1 deletion(-)
diff --git c/builtin/fetch.c w/builtin/fetch.c
index a64de4485f..3eb6f3acc9 100644
--- c/builtin/fetch.c
+++ w/builtin/fetch.c
@@ -1643,6 +1643,21 @@ static int set_head(const struct ref *remote_refs)
return result;
}
+static int uses_remote_tracking(struct transport *transport, struct refspec *rs)
+{
+ if (!remote_is_configured(transport->remote, 0))
+ return 0;
+
+ if (!rs->nr)
+ rs = &transport->remote->fetch;
+
+ for (int i = 0; i < rs->nr; i++)
+ if (rs->items[i].dst)
+ return 1;
+
+ return 0;
+}
+
static int do_fetch(struct transport *transport,
struct refspec *rs,
const struct fetch_config *config)
@@ -1712,7 +1727,10 @@ static int do_fetch(struct transport *transport,
"refs/tags/");
}
- strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
+ if (uses_remote_tracking(transport, rs)) {
+ must_list_refs = 1;
+ strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
+ }
if (must_list_refs) {
trace2_region_enter("fetch", "remote_refs", the_repository);
diff --git c/t/t5510-fetch.sh w/t/t5510-fetch.sh
index 87698341f5..d7602333ff 100755
--- c/t/t5510-fetch.sh
+++ w/t/t5510-fetch.sh
@@ -189,6 +189,23 @@ test_expect_success 'fetch --prune --tags with refspec prunes based on refspec'
git rev-parse sometag
'
+test_expect_success 'fetch --tags gets tags even without a configured remote' '
+ REMOTE="$(pwd)/test_tag_1" &&
+ git init test_tag_1 &&
+ (
+ cd test_tag_1 &&
+ test_commit foo
+ ) &&
+ git init test_tag_2 &&
+ (
+ cd test_tag_2 &&
+ git fetch --tags "file://$REMOTE" &&
+ echo "foo" >expect &&
+ git tag >actual &&
+ test_cmp expect actual
+ )
+'
+
test_expect_success REFFILES 'fetch --prune fails to delete branches' '
cd "$D" &&
git clone . prune-fail &&
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re* [PATCH] Fix `git fetch --tags` in repo with no configured remote
2024-12-06 3:28 ` Junio C Hamano
2024-12-06 4:00 ` Junio C Hamano
2024-12-06 8:07 ` Re* " Junio C Hamano
@ 2024-12-06 8:08 ` Junio C Hamano
2024-12-06 11:23 ` Bence Ferdinandy
2024-12-06 11:30 ` Junio C Hamano
2 siblings, 2 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-12-06 8:08 UTC (permalink / raw)
To: Josh Steadmon
Cc: git, bence, phillip.wood, l.s.r, Johannes.Schindelin, karthik.188,
me, ps, jonathantanmy
Junio C Hamano <gitster@pobox.com> writes:
> Stepping back a bit, do we even need to learn where HEAD points at
> in the remote, when we are not doing the "opportunistically set
> $REMOTE/HEAD"? Your example is "in repo with no configured remote",
> which by definition means that we do not use any refs/remotes/*/ ref
> hierarchy to keep track of the remote-tracking branches for the
> remote we are fetching from. There is no place we record what we
> learn by running ls-remote HEAD against them, so should we even push
> "HEAD" to the ls-remote prefixes in such a case?
>
> While this change may hide the breakage you saw in your set-up, we
> may be now asking to ls-remote HEAD even in cases we do not need to.
>
>> Fix this by adding HEAD to the ref_prefixes list prior to handling the
>> `--tags` flag, and removing the now obsolete check whether ref_prefixes
>> is empty or not.
>
> And if we unconditionally add HEAD even when we do not need to,
> especially with the loss of the ref-prefixes condition that was
> there in order to implement "learn refs/tags/* hierarchy only when
> we are doing the default fetch", wouldn't it mean we may learn
> refs/tags/* even when we do not have to?
In other words, what I think the "fix" should look like is more like
the attached. It seems to pass your test, as well as existing tests
Bence added and other tests about "git fetch".
One thing I am not happy about is the abstraction violation that is
needed to make the uses_remote_tracking() helper aware of the "use
the rs, the refspec given from the command line, or if it is empty,
use the configured 'fetch' refspec from the remote" rule, which is
primarily used by get_ref_map() that is much later called, but the
layering violation started when we started limiting the ls-remote
request with narrowing common prefixes, and it would take a larger
surgery to fix, I would think.
---- >8 ----
Subject: [PATCH] fetch: do not ask for HEAD unnecessarily
In 3f763ddf28 (fetch: set remote/HEAD if it does not exist,
2024-11-22), git-fetch learned to opportunistically set $REMOTE/HEAD
when fetching by always asking for remote HEAD, in the hope that it
will help setting refs/remotes/<name>/HEAD if missing.
But it is not needed to always ask for remote HEAD. When we are
fetching from a remote, for which we have remote-tracking branches,
we do need to know about HEAD. But if we are doing one-shot fetch,
e.g.,
$ git fetch --tags https://github.com/git/git
we do not even know what sub-hierarchy of refs/remotes/<remote>/
we need to adjust the remote HEAD for. There is no need to ask for
HEAD in such a case.
Incidentally, because the unconditional request to list "HEAD"
affected the number of ref-prefixes requested in the ls-remote
request, this affected how the requests for tags are added to the
same ls-remote request, breaking "git fetch --tags $URL" performed
against a URL that is not configured as a remote.
Reported-by: Josh Steadmon <steadmon@google.com>
[jc: tests are also borrowed from Josh's patch]
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
* Even though I borrowed some part of the commit log message from
Josh's version, it not clear to me how "*after* deciding" led to
whatever the observed breakage (which was not described in the
log message), in the following part.
Specifically, we now unconditionally add HEAD to the
ref_prefixes list, but we did this *after* deciding whether we
also need to explicitly request tags.
Bence's change asks "HEAD" after "if we are fetching something,
then also ask about refs/tags/" logic thought we are not fetching
anything (i.e. ref_prefixes.nr == 0 at that point). But before
Bence's series, the same refs/tags/ logic saw that (ref_prefix.nr
== 0), didn't it? So that does not sound like a sufficient
explanation on how the series regressed.
---
builtin/fetch.c | 20 +++++++++++++++++++-
t/t5510-fetch.sh | 17 +++++++++++++++++
2 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index a64de4485f..3eb6f3acc9 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1643,6 +1643,21 @@ static int set_head(const struct ref *remote_refs)
return result;
}
+static int uses_remote_tracking(struct transport *transport, struct refspec *rs)
+{
+ if (!remote_is_configured(transport->remote, 0))
+ return 0;
+
+ if (!rs->nr)
+ rs = &transport->remote->fetch;
+
+ for (int i = 0; i < rs->nr; i++)
+ if (rs->items[i].dst)
+ return 1;
+
+ return 0;
+}
+
static int do_fetch(struct transport *transport,
struct refspec *rs,
const struct fetch_config *config)
@@ -1712,7 +1727,10 @@ static int do_fetch(struct transport *transport,
"refs/tags/");
}
- strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
+ if (uses_remote_tracking(transport, rs)) {
+ must_list_refs = 1;
+ strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
+ }
if (must_list_refs) {
trace2_region_enter("fetch", "remote_refs", the_repository);
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 87698341f5..d7602333ff 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -189,6 +189,23 @@ test_expect_success 'fetch --prune --tags with refspec prunes based on refspec'
git rev-parse sometag
'
+test_expect_success 'fetch --tags gets tags even without a configured remote' '
+ REMOTE="$(pwd)/test_tag_1" &&
+ git init test_tag_1 &&
+ (
+ cd test_tag_1 &&
+ test_commit foo
+ ) &&
+ git init test_tag_2 &&
+ (
+ cd test_tag_2 &&
+ git fetch --tags "file://$REMOTE" &&
+ echo "foo" >expect &&
+ git tag >actual &&
+ test_cmp expect actual
+ )
+'
+
test_expect_success REFFILES 'fetch --prune fails to delete branches' '
cd "$D" &&
git clone . prune-fail &&
--
2.47.1-576-g9f8c8eb655
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: Re* [PATCH] Fix `git fetch --tags` in repo with no configured remote
2024-12-06 8:08 ` Junio C Hamano
@ 2024-12-06 11:23 ` Bence Ferdinandy
2024-12-06 11:30 ` Junio C Hamano
1 sibling, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-12-06 11:23 UTC (permalink / raw)
To: Junio C Hamano, Josh Steadmon
Cc: git, phillip.wood, l.s.r, Johannes.Schindelin, karthik.188, me,
ps, jonathantanmy
On Fri Dec 06, 2024 at 09:08, Junio C Hamano <gitster@pobox.com> wrote:
> Junio C Hamano <gitster@pobox.com> writes:
>
>> Stepping back a bit, do we even need to learn where HEAD points at
>> in the remote, when we are not doing the "opportunistically set
>> $REMOTE/HEAD"? Your example is "in repo with no configured remote",
>> which by definition means that we do not use any refs/remotes/*/ ref
>> hierarchy to keep track of the remote-tracking branches for the
>> remote we are fetching from. There is no place we record what we
>> learn by running ls-remote HEAD against them, so should we even push
>> "HEAD" to the ls-remote prefixes in such a case?
Yes, it probably doesn't make any sense to do that.
>>
>> While this change may hide the breakage you saw in your set-up, we
>> may be now asking to ls-remote HEAD even in cases we do not need to.
>>
>>> Fix this by adding HEAD to the ref_prefixes list prior to handling the
>>> `--tags` flag, and removing the now obsolete check whether ref_prefixes
>>> is empty or not.
>>
>> And if we unconditionally add HEAD even when we do not need to,
>> especially with the loss of the ref-prefixes condition that was
>> there in order to implement "learn refs/tags/* hierarchy only when
>> we are doing the default fetch", wouldn't it mean we may learn
>> refs/tags/* even when we do not have to?
>
> In other words, what I think the "fix" should look like is more like
> the attached. It seems to pass your test, as well as existing tests
> Bence added and other tests about "git fetch".
>
> One thing I am not happy about is the abstraction violation that is
> needed to make the uses_remote_tracking() helper aware of the "use
> the rs, the refspec given from the command line, or if it is empty,
> use the configured 'fetch' refspec from the remote" rule, which is
> primarily used by get_ref_map() that is much later called, but the
> layering violation started when we started limiting the ls-remote
> request with narrowing common prefixes, and it would take a larger
> surgery to fix, I would think.
>
> ---- >8 ----
> Subject: [PATCH] fetch: do not ask for HEAD unnecessarily
>
> In 3f763ddf28 (fetch: set remote/HEAD if it does not exist,
> 2024-11-22), git-fetch learned to opportunistically set $REMOTE/HEAD
> when fetching by always asking for remote HEAD, in the hope that it
> will help setting refs/remotes/<name>/HEAD if missing.
>
> But it is not needed to always ask for remote HEAD. When we are
> fetching from a remote, for which we have remote-tracking branches,
> we do need to know about HEAD. But if we are doing one-shot fetch,
> e.g.,
>
> $ git fetch --tags https://github.com/git/git
>
> we do not even know what sub-hierarchy of refs/remotes/<remote>/
> we need to adjust the remote HEAD for. There is no need to ask for
> HEAD in such a case.
>
> Incidentally, because the unconditional request to list "HEAD"
> affected the number of ref-prefixes requested in the ls-remote
> request, this affected how the requests for tags are added to the
> same ls-remote request, breaking "git fetch --tags $URL" performed
> against a URL that is not configured as a remote.
>
> Reported-by: Josh Steadmon <steadmon@google.com>
> [jc: tests are also borrowed from Josh's patch]
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>
> * Even though I borrowed some part of the commit log message from
> Josh's version, it not clear to me how "*after* deciding" led to
> whatever the observed breakage (which was not described in the
> log message), in the following part.
>
> Specifically, we now unconditionally add HEAD to the
> ref_prefixes list, but we did this *after* deciding whether we
> also need to explicitly request tags.
>
> Bence's change asks "HEAD" after "if we are fetching something,
> then also ask about refs/tags/" logic thought we are not fetching
> anything (i.e. ref_prefixes.nr == 0 at that point). But before
> Bence's series, the same refs/tags/ logic saw that (ref_prefix.nr
> == 0), didn't it? So that does not sound like a sufficient
> explanation on how the series regressed.
I did a bit of poking around on what is happening. For one I can confirm, that
both before and after the set_head series
`transport_ls_refs_options.ref_prefixes.nr` is 0. So the difference must be
happening somewhere after that, and is not a side effect of calling set_head
either, but I didn't manage to pin it down further.
I also checked what happens in set_head, just to be on the safe side: `heads`
is empty so we reach the if where we check `heads.nr` which is 0. So at least
no strange refs are created :)
> ---
> builtin/fetch.c | 20 +++++++++++++++++++-
> t/t5510-fetch.sh | 17 +++++++++++++++++
> 2 files changed, 36 insertions(+), 1 deletion(-)
>
> diff --git a/builtin/fetch.c b/builtin/fetch.c
> index a64de4485f..3eb6f3acc9 100644
> --- a/builtin/fetch.c
> +++ b/builtin/fetch.c
> @@ -1643,6 +1643,21 @@ static int set_head(const struct ref *remote_refs)
> return result;
> }
>
> +static int uses_remote_tracking(struct transport *transport, struct refspec *rs)
> +{
> + if (!remote_is_configured(transport->remote, 0))
> + return 0;
> +
> + if (!rs->nr)
> + rs = &transport->remote->fetch;
> +
> + for (int i = 0; i < rs->nr; i++)
> + if (rs->items[i].dst)
> + return 1;
> +
> + return 0;
> +}
> +
> static int do_fetch(struct transport *transport,
> struct refspec *rs,
> const struct fetch_config *config)
> @@ -1712,7 +1727,10 @@ static int do_fetch(struct transport *transport,
> "refs/tags/");
> }
>
> - strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
> + if (uses_remote_tracking(transport, rs)) {
> + must_list_refs = 1;
> + strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
> + }
>
> if (must_list_refs) {
> trace2_region_enter("fetch", "remote_refs", the_repository);
> diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
> index 87698341f5..d7602333ff 100755
> --- a/t/t5510-fetch.sh
> +++ b/t/t5510-fetch.sh
> @@ -189,6 +189,23 @@ test_expect_success 'fetch --prune --tags with refspec prunes based on refspec'
> git rev-parse sometag
> '
>
> +test_expect_success 'fetch --tags gets tags even without a configured remote' '
> + REMOTE="$(pwd)/test_tag_1" &&
> + git init test_tag_1 &&
> + (
> + cd test_tag_1 &&
> + test_commit foo
> + ) &&
> + git init test_tag_2 &&
> + (
> + cd test_tag_2 &&
> + git fetch --tags "file://$REMOTE" &&
> + echo "foo" >expect &&
> + git tag >actual &&
> + test_cmp expect actual
> + )
> +'
> +
> test_expect_success REFFILES 'fetch --prune fails to delete branches' '
> cd "$D" &&
> git clone . prune-fail &&
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: Re* [PATCH] Fix `git fetch --tags` in repo with no configured remote
2024-12-06 8:08 ` Junio C Hamano
2024-12-06 11:23 ` Bence Ferdinandy
@ 2024-12-06 11:30 ` Junio C Hamano
1 sibling, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-12-06 11:30 UTC (permalink / raw)
To: Josh Steadmon
Cc: git, bence, phillip.wood, l.s.r, Johannes.Schindelin, karthik.188,
me, ps, jonathantanmy
Junio C Hamano <gitster@pobox.com> writes:
> +static int uses_remote_tracking(struct transport *transport, struct refspec *rs)
> +{
> + if (!remote_is_configured(transport->remote, 0))
> + return 0;
> +
> + if (!rs->nr)
> + rs = &transport->remote->fetch;
> +
> + for (int i = 0; i < rs->nr; i++)
> + if (rs->items[i].dst)
> + return 1;
> +
> + return 0;
> +}
For the purpose of adjusting refs/remotes/<name>/HEAD, which is
expected to point at something that we copy from their refs/heads/,
it may be tempting to tighten the above logic to say, instead of "we
have a refspec item that stores to somewhere", do something like
for (int i = 0; i < rs->nr; i++) {
struct refspec_item *item = &rs->items[i];
if (item->dst &&
item->src && starts_with(item->src, "refs/heads/"))
return 1;
}
to make sure that we are tracking their branch refs, not some random
hierarchy for which refs/remotes/<name>/HEAD does not matter.
I will leave the simplest "just check we have .dst" version (in
other words, without any additional check on the .src side) in my
tree, but if anybody wants to pursue "let's make sure that .src
copies from somewhere in refs/heads/", please make sure that you
consider "--mirror", where an equivalent of "refs/*:refs/*" is used
as the refspec, i.e. there is a case where .src does not begin with
"refs/heads/" but does cover the hierarchy and wants to learn about
"HEAD".
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v15 09/10] fetch: set remote/HEAD if it does not exist
2024-12-05 20:11 ` Josh Steadmon
2024-12-05 20:27 ` [PATCH] Fix `git fetch --tags` in repo with no configured remote Josh Steadmon
@ 2024-12-05 20:57 ` Bence Ferdinandy
1 sibling, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-12-05 20:57 UTC (permalink / raw)
To: Josh Steadmon
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
jonathantanmy
On Thu Dec 05, 2024 at 21:11, Josh Steadmon <steadmon@google.com> wrote:
[snip]
>>
>> Thanks for looking into this! I think the issue is with
>>
>> $GIT fetch --tags "file://$REMOTE"
>>
>> instead of adding a proper remote. Tbh, I've never seen the above syntax before, so first I just ran your script, which reproduced the issue for me, but then I modified it to use a proper remote which works as expected:
>>
>> rm -rf test_tag_1 test_tag_2
>> GIT=~/git/bin-wrappers/git
>> mkdir test_tag_1 && cd test_tag_1
>> REMOTE=$(pwd)
>> $GIT init .
>> touch foo.txt
>> $GIT add foo.txt
>> $GIT commit foo.txt -m "commit one"
>> $GIT tag foo
>> cd ..
>> mkdir test_tag_2 && cd test_tag_2
>> $GIT init .
>> $GIT remote add origin $REMOTE
>> echo fetch --tags
>> $GIT fetch origin --tags
>> echo regular fetch
>> $GIT fetch origin 'refs/tags/*:refs/tags/*'
>> $GIT --version
>>
>> So I'm assuming this is why also the tests never caught this, since probably
>> all of them are using `git remote add`.
>>
>
> Yeah, I think the issue is that we check the `--tags` flag first, but
> only add the ref_prefixes entry if it's not empty already. Then after
> that we unconditionally add HEAD. So that's why moving your strvec_push
> earlier fixes it. I'll send a fix + test patch in just a minute.
Awesome, thanks!
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v15 10/10] fetch set_head: handle mirrored bare repositories
2024-11-22 12:28 ` [PATCH v15 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (8 preceding siblings ...)
2024-11-22 12:28 ` [PATCH v15 09/10] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
@ 2024-11-22 12:28 ` Bence Ferdinandy
2024-11-27 9:16 ` [PATCH v1] fetch: add configuration for set_head behaviour Bence Ferdinandy
10 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-22 12:28 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
When adding a remote to bare repository with "git remote add --mirror",
running fetch will fail to update HEAD to the remote's HEAD, since it
does not know how to handle bare repositories. On the other hand HEAD
already has content, since "git init --bare" has already set HEAD to
whatever is the default branch set for the user. Unless this - by chance
- is the same as the remote's HEAD, HEAD will be pointing to a bad
symref. Teach set_head to handle bare repositories, by overwriting HEAD
so it mirrors the remote's HEAD.
Note, that in this case overriding the local HEAD reference is
necessary, since HEAD will exist before fetch can be run, but this
should not be an issue, since the whole purpose of --mirror is to be an
exact mirror of the remote, so following any changes to HEAD makes
sense.
Also note, that although "git remote set-head" also fails when trying to
update the remote's locally tracked HEAD in a mirrored bare repository,
the usage of the command does not make much sense after this patch:
fetch will update the remote HEAD correctly, and setting it manually to
something else is antithetical to the concept of mirroring.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
state:
* last review: none
v10: - new patch
- handles the issue discovered in
https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/T/
v11: no change
v12: no change
v13: properly print the previous head if it was detached
v14: no change
v15: no change
builtin/fetch.c | 16 +++++++++++-----
t/t5505-remote.sh | 10 ++++++++++
2 files changed, 21 insertions(+), 5 deletions(-)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index bbfaf50b63..2f416cf867 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1581,7 +1581,7 @@ static const char *strip_refshead(const char *name){
static int set_head(const struct ref *remote_refs)
{
- int result = 0;
+ int result = 0, is_bare;
struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
const char *remote = gtransport->remote->name;
char *head_name = NULL;
@@ -1613,15 +1613,21 @@ static int set_head(const struct ref *remote_refs)
if (!head_name)
goto cleanup;
- strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
- strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name);
+ is_bare = is_bare_repository();
+ if (is_bare) {
+ strbuf_addstr(&b_head, "HEAD");
+ strbuf_addf(&b_remote_head, "refs/heads/%s", head_name);
+ } else {
+ strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
+ strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name);
+ }
/* make sure it's valid */
- if (!refs_ref_exists(refs, b_remote_head.buf)) {
+ if (!is_bare && !refs_ref_exists(refs, b_remote_head.buf)) {
result = 1;
goto cleanup;
}
if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
- "fetch", NULL, 1))
+ "fetch", NULL, !is_bare))
result = 1;
cleanup:
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index afa261409f..2600add82a 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -569,6 +569,16 @@ test_expect_success 'add --mirror && prune' '
)
'
+test_expect_success 'add --mirror setting HEAD' '
+ mkdir headmirror &&
+ (
+ cd headmirror &&
+ git init --bare -b notmain &&
+ git remote add --mirror -f origin ../one &&
+ test "$(git symbolic-ref HEAD)" = "refs/heads/main"
+ )
+'
+
test_expect_success 'add --mirror=fetch' '
mkdir mirror-fetch &&
git init -b main mirror-fetch/parent &&
--
2.47.0.298.g52a96ec17b
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v1] fetch: add configuration for set_head behaviour
2024-11-22 12:28 ` [PATCH v15 00/10] set-head/fetch remote/HEAD updates Bence Ferdinandy
` (9 preceding siblings ...)
2024-11-22 12:28 ` [PATCH v15 10/10] fetch set_head: handle mirrored bare repositories Bence Ferdinandy
@ 2024-11-27 9:16 ` Bence Ferdinandy
2024-11-27 13:46 ` Junio C Hamano
10 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-27 9:16 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin,
Junio C Hamano, karthik.188, Taylor Blau, Patrick Steinhardt,
Bence Ferdinandy
In the current implementatio, if refs/remotes/$remote/HEAD does not
exist, running fetch will create it, but if it does exist it will not do
anything, which is a somewhat safe and minimal approach. Unfortunately,
for users who wish to NOT have refs/remotes/$remote/HEAD set for any
reason (e.g. so that `git rev-parse origin` doesn't accidentally point
them somewhere they do not want to), there is no way to remove this
behaviour. On the other side of the spectrum, users may want fetch to
automatically update HEAD or at least give them a warning if something
changed on the remote.
Introduce a new setting, remote.$remote.followRemoteHEAD with four
options:
- "never": do not ever do anything, not even create
- "create": the current behaviour, now the default behaviour
- "warn": print a message if remote and local HEAD is different
- "always": silently update HEAD on every change
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
This patch is built off v15 of the set-head/fetch updates series:
https://lore.kernel.org/git/D5WEJJBMSO1K.2TPXDI1K08SHT@ferdinandy.com/T/#m13a0c3e8919872188ef07fd9fd984c78e8aa35ba
The patch is sent separately, because the rest of the series building up
to here seems ready, while this patch will likely trigger some
discussion.
Documentation/config/remote.txt | 11 ++++
builtin/fetch.c | 46 ++++++++++++--
remote.c | 9 +++
remote.h | 9 +++
t/t5510-fetch.sh | 102 ++++++++++++++++++++++++++++++++
5 files changed, 171 insertions(+), 6 deletions(-)
diff --git a/Documentation/config/remote.txt b/Documentation/config/remote.txt
index 6d8b7d6c63..024f92befc 100644
--- a/Documentation/config/remote.txt
+++ b/Documentation/config/remote.txt
@@ -101,6 +101,17 @@ remote.<name>.serverOption::
The default set of server options used when fetching from this remote.
These server options can be overridden by the `--server-option=` command
line arguments.
+
+remote.<name>.followRemoteHEAD::
+ How linkgit:git-fetch[1] should handle updates to `remotes/<name>/HEAD`.
+ The default value is "create", which will create `remotes/<name>/HEAD`
+ if it exists on the remote, but not locally, but will not touch an
+ already existing local reference. Setting to "warn" will print
+ a message if the remote has a different value, than the local one and
+ in case there is no local reference, it behaves like "create". Setting
+ to "always" will silently update it to the value on the remote.
+ Finally, setting it to "never" will never change or create the local
+ reference.
+
This is a multi-valued variable, and an empty value can be used in a higher
priority configuration file (e.g. `.git/config` in a repository) to clear
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 2f416cf867..b619bddd7a 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1579,10 +1579,35 @@ static const char *strip_refshead(const char *name){
return name;
}
-static int set_head(const struct ref *remote_refs)
+static void report_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;
+
+ strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
+ skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);
+
+ if (prev_head && strcmp(prev_head, head_name)) {
+ printf("'HEAD' at '%s' is '%s', but we have '%s' locally.\n",
+ remote, head_name, prev_head);
+ printf("Run 'git remote set-head %s %s' to follow the change.\n",
+ remote, head_name);
+ }
+ else if (updateres && buf_prev->len) {
+ printf("'HEAD' at '%s' is '%s', "
+ "but we have a detached HEAD pointing to '%s' locally.\n",
+ remote, head_name, buf_prev->buf);
+ printf("Run 'git remote set-head %s %s' to follow the change.\n",
+ remote, head_name);
+ }
+ strbuf_release(&buf_prefix);
+}
+
+static int set_head(const struct ref *remote_refs, int follow_remote_head)
{
- int result = 0, is_bare;
- struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
+ int result = 0, create_only, is_bare, was_detached;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
+ b_local_head = STRBUF_INIT;
const char *remote = gtransport->remote->name;
char *head_name = NULL;
struct ref *ref, *matches;
@@ -1603,6 +1628,8 @@ static int set_head(const struct ref *remote_refs)
string_list_append(&heads, strip_refshead(ref->name));
}
+ if (follow_remote_head < 0)
+ goto cleanup;
if (!heads.nr)
result = 1;
@@ -1614,6 +1641,7 @@ static int set_head(const struct ref *remote_refs)
if (!head_name)
goto cleanup;
is_bare = is_bare_repository();
+ create_only = follow_remote_head == 2 ? 0 : !is_bare;
if (is_bare) {
strbuf_addstr(&b_head, "HEAD");
strbuf_addf(&b_remote_head, "refs/heads/%s", head_name);
@@ -1626,9 +1654,14 @@ static int set_head(const struct ref *remote_refs)
result = 1;
goto cleanup;
}
- if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
- "fetch", NULL, !is_bare))
+ was_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
+ "fetch", &b_local_head, create_only);
+ if (was_detached == -1) {
result = 1;
+ goto cleanup;
+ }
+ if (follow_remote_head == 1 && verbosity >= 0)
+ report_set_head(remote, head_name, &b_local_head, was_detached);
cleanup:
free(head_name);
@@ -1636,6 +1669,7 @@ static int set_head(const struct ref *remote_refs)
free_refs(matches);
string_list_clear(&heads, 0);
strbuf_release(&b_head);
+ strbuf_release(&b_local_head);
strbuf_release(&b_remote_head);
return result;
}
@@ -1855,7 +1889,7 @@ static int do_fetch(struct transport *transport,
"you need to specify exactly one branch with the --set-upstream option"));
}
}
- if (set_head(remote_refs))
+ if (set_head(remote_refs, transport->remote->follow_remote_head))
;
/*
* Way too many cases where this can go wrong
diff --git a/remote.c b/remote.c
index 10104d11e3..5a768ddac2 100644
--- a/remote.c
+++ b/remote.c
@@ -514,6 +514,15 @@ static int handle_config(const char *key, const char *value,
} else if (!strcmp(subkey, "serveroption")) {
return parse_transport_option(key, value,
&remote->server_options);
+ } else if (!strcmp(subkey, "followremotehead")) {
+ if (!strcmp(value, "never"))
+ remote->follow_remote_head = -1;
+ else if (!strcmp(value, "create"))
+ remote->follow_remote_head = 0;
+ else if (!strcmp(value, "warn"))
+ remote->follow_remote_head = 1;
+ else if (!strcmp(value, "always"))
+ remote->follow_remote_head = 2;
}
return 0;
}
diff --git a/remote.h b/remote.h
index a7e5c4e07c..3ceadac820 100644
--- a/remote.h
+++ b/remote.h
@@ -107,6 +107,15 @@ struct remote {
char *http_proxy_authmethod;
struct string_list server_options;
+
+ /*
+ * The setting for whether to update HEAD for the remote.
+ * -1 never update
+ * 0 create only (default)
+ * 1 warn on change
+ * 2 always update
+ */
+ int follow_remote_head;
};
/**
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 87698341f5..2467027d34 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -99,6 +99,108 @@ test_expect_success "fetch test remote HEAD change" '
branch=$(git rev-parse refs/remotes/origin/other) &&
test "z$head" = "z$branch"'
+test_expect_success "fetch test followRemoteHEAD never" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ git config set remote.origin.followRemoteHEAD "never" &&
+ git fetch &&
+ test_must_fail git rev-parse --verify refs/remotes/origin/HEAD
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD warn no change" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git config set remote.origin.followRemoteHEAD "warn" &&
+ git fetch >output &&
+ echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
+ "but we have ${SQ}other${SQ} locally." >expect &&
+ echo "Run ${SQ}git remote set-head origin main${SQ} to follow the change." >>expect &&
+ test_cmp expect output &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD warn create" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ git config set remote.origin.followRemoteHEAD "warn" &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ output=$(git fetch) &&
+ test "z" = "z$output" &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD warn detached" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ git update-ref refs/remotes/origin/HEAD HEAD &&
+ HEAD=$(git log --pretty="%H") &&
+ git config set remote.origin.followRemoteHEAD "warn" &&
+ git 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 &&
+ echo "Run ${SQ}git remote set-head origin main${SQ} to follow the change." >>expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD warn quiet" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git config set remote.origin.followRemoteHEAD "warn" &&
+ output=$(git fetch --quiet) &&
+ test "z" = "z$output" &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD always" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git config set remote.origin.followRemoteHEAD "always" &&
+ git fetch &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"
+ )
+'
+
test_expect_success 'fetch --prune on its own works as expected' '
cd "$D" &&
git clone . prune &&
--
2.47.1.295.ge630f003e1.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v1] fetch: add configuration for set_head behaviour
2024-11-27 9:16 ` [PATCH v1] fetch: add configuration for set_head behaviour Bence Ferdinandy
@ 2024-11-27 13:46 ` Junio C Hamano
2024-11-27 19:20 ` Bence Ferdinandy
2024-11-28 11:19 ` [PATCH v2] fetch: add configuration for set_head behaviour Bence Ferdinandy
0 siblings, 2 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-11-27 13:46 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
Bence Ferdinandy <bence@ferdinandy.com> writes:
> Introduce a new setting, remote.$remote.followRemoteHEAD with four
> options:
>
> - "never": do not ever do anything, not even create
> - "create": the current behaviour, now the default behaviour
> - "warn": print a message if remote and local HEAD is different
> - "always": silently update HEAD on every change
That seems to be plenty of choices to please many classes of users.
Except for the one that I would want to use myself, which is "I
understand their HEAD points at branch X right now; please warn when
they flip their HEAD to a different branch, but until then please do
nothing". That's somewhere between "never" and "warn".
> @@ -1603,6 +1628,8 @@ static int set_head(const struct ref *remote_refs)
> string_list_append(&heads, strip_refshead(ref->name));
> }
>
> + if (follow_remote_head < 0)
> + goto cleanup;
There is some "magical" value(s) that is/are negative; we will find
out what they are later.
> @@ -1614,6 +1641,7 @@ static int set_head(const struct ref *remote_refs)
> if (!head_name)
> goto cleanup;
> is_bare = is_bare_repository();
> + create_only = follow_remote_head == 2 ? 0 : !is_bare;
There is one more "magical" value that follow_remote_head can take.
> @@ -1626,9 +1654,14 @@ static int set_head(const struct ref *remote_refs)
> result = 1;
> goto cleanup;
> }
> - if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
> - "fetch", NULL, !is_bare))
> + was_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
> + "fetch", &b_local_head, create_only);
> + if (was_detached == -1) {
> result = 1;
> + goto cleanup;
> + }
> + if (follow_remote_head == 1 && verbosity >= 0)
And there is one more.
> diff --git a/remote.c b/remote.c
> index 10104d11e3..5a768ddac2 100644
> --- a/remote.c
> +++ b/remote.c
> @@ -514,6 +514,15 @@ static int handle_config(const char *key, const char *value,
> } else if (!strcmp(subkey, "serveroption")) {
> return parse_transport_option(key, value,
> &remote->server_options);
> + } else if (!strcmp(subkey, "followremotehead")) {
> + if (!strcmp(value, "never"))
> + remote->follow_remote_head = -1;
> + else if (!strcmp(value, "create"))
> + remote->follow_remote_head = 0;
> + else if (!strcmp(value, "warn"))
> + remote->follow_remote_head = 1;
> + else if (!strcmp(value, "always"))
> + remote->follow_remote_head = 2;
Use something like
/* The setting for whether to update HEAD for the remote. */
enum follow_remote_head {
FOLLOW_REMOTE_NEVER = -1,
FOLLOW_REMOTE_CREATE = 0,
FOLLOW_REMOTE_WARN = 1,
FOLLOW_REMOTE_ALWAYS = 2,
};
or something? I have no strong preference between "enum" and
"#define" myself, but moderately strong preference for anything
symbolic over magic numbers.
> diff --git a/remote.h b/remote.h
> index a7e5c4e07c..3ceadac820 100644
> --- a/remote.h
> +++ b/remote.h
> @@ -107,6 +107,15 @@ struct remote {
> char *http_proxy_authmethod;
>
> struct string_list server_options;
> +
> + /*
> + * The setting for whether to update HEAD for the remote.
> + * -1 never update
> + * 0 create only (default)
> + * 1 warn on change
> + * 2 always update
> + */
> + int follow_remote_head;
> };
Other than that, looking good from a cursory read.
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v1] fetch: add configuration for set_head behaviour
2024-11-27 13:46 ` Junio C Hamano
@ 2024-11-27 19:20 ` Bence Ferdinandy
2024-11-28 0:12 ` Junio C Hamano
2024-11-28 11:19 ` [PATCH v2] fetch: add configuration for set_head behaviour Bence Ferdinandy
1 sibling, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-27 19:20 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
On Wed Nov 27, 2024 at 14:46, Junio C Hamano <gitster@pobox.com> wrote:
> Bence Ferdinandy <bence@ferdinandy.com> writes:
>
>> Introduce a new setting, remote.$remote.followRemoteHEAD with four
>> options:
>>
>> - "never": do not ever do anything, not even create
>> - "create": the current behaviour, now the default behaviour
>> - "warn": print a message if remote and local HEAD is different
>> - "always": silently update HEAD on every change
>
> That seems to be plenty of choices to please many classes of users.
>
> Except for the one that I would want to use myself, which is "I
> understand their HEAD points at branch X right now; please warn when
> they flip their HEAD to a different branch, but until then please do
> nothing". That's somewhere between "never" and "warn".
Just to clarify, an example: the remote's HEAD is set to "master", and you have
- git remote set-head origin next
- git config set remote.origin.followRemoteHead "manual"
- git config set remote.origin.followRemoteHeadManual "master"
you manually set these explicitly. As long as the remote's HEAD is still
"master" you do not get a warning when running fetch, but if it changes to
something else (even "next"), you _do_ get a warning, that is not silenced
until you set followRemoteHeadManual to "next".
Would you expect `git remote set-head origin next` to set followRemoteHead to
"manual" and to set the correct value for the followRemoteHeadManual to
"master" if it actually changed the current refs/remote/origin/HEAD from master
to next? Or is having this completely manual fine?
I toyed with the thought of rolling the two settings together (an unrecognized
string would mean the reference for which we must be silent), but then you
couldn't have a remote/HEAD called "create" for example, so I think we need to
store that separately. I'm also not quite happy with "followRemoteHeadManual"
...
>
>> @@ -1603,6 +1628,8 @@ static int set_head(const struct ref *remote_refs)
>> string_list_append(&heads, strip_refshead(ref->name));
>> }
>>
>> + if (follow_remote_head < 0)
>> + goto cleanup;
>
> There is some "magical" value(s) that is/are negative; we will find
> out what they are later.
>
>> @@ -1614,6 +1641,7 @@ static int set_head(const struct ref *remote_refs)
>> if (!head_name)
>> goto cleanup;
>> is_bare = is_bare_repository();
>> + create_only = follow_remote_head == 2 ? 0 : !is_bare;
>
> There is one more "magical" value that follow_remote_head can take.
>
>> @@ -1626,9 +1654,14 @@ static int set_head(const struct ref *remote_refs)
>> result = 1;
>> goto cleanup;
>> }
>> - if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
>> - "fetch", NULL, !is_bare))
>> + was_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
>> + "fetch", &b_local_head, create_only);
>> + if (was_detached == -1) {
>> result = 1;
>> + goto cleanup;
>> + }
>> + if (follow_remote_head == 1 && verbosity >= 0)
>
> And there is one more.
>
>> diff --git a/remote.c b/remote.c
>> index 10104d11e3..5a768ddac2 100644
>> --- a/remote.c
>> +++ b/remote.c
>> @@ -514,6 +514,15 @@ static int handle_config(const char *key, const char *value,
>> } else if (!strcmp(subkey, "serveroption")) {
>> return parse_transport_option(key, value,
>> &remote->server_options);
>> + } else if (!strcmp(subkey, "followremotehead")) {
>> + if (!strcmp(value, "never"))
>> + remote->follow_remote_head = -1;
>> + else if (!strcmp(value, "create"))
>> + remote->follow_remote_head = 0;
>> + else if (!strcmp(value, "warn"))
>> + remote->follow_remote_head = 1;
>> + else if (!strcmp(value, "always"))
>> + remote->follow_remote_head = 2;
>
> Use something like
>
> /* The setting for whether to update HEAD for the remote. */
> enum follow_remote_head {
> FOLLOW_REMOTE_NEVER = -1,
> FOLLOW_REMOTE_CREATE = 0,
> FOLLOW_REMOTE_WARN = 1,
> FOLLOW_REMOTE_ALWAYS = 2,
> };
>
> or something? I have no strong preference between "enum" and
> "#define" myself, but moderately strong preference for anything
> symbolic over magic numbers.
Ah, my mistake sorry. Magic number bad.
Best,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v1] fetch: add configuration for set_head behaviour
2024-11-27 19:20 ` Bence Ferdinandy
@ 2024-11-28 0:12 ` Junio C Hamano
2024-11-28 5:49 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Junio C Hamano @ 2024-11-28 0:12 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
"Bence Ferdinandy" <bence@ferdinandy.com> writes:
> Just to clarify, an example: the remote's HEAD is set to "master", and you have
> - git remote set-head origin next
> - git config set remote.origin.followRemoteHead "manual"
> - git config set remote.origin.followRemoteHeadManual "master"
>
> you manually set these explicitly. As long as the remote's HEAD is still
> "master" you do not get a warning when running fetch, but if it changes to
> something else (even "next"), you _do_ get a warning, that is not silenced
> until you set followRemoteHeadManual to "next".
Yup.
> Would you expect `git remote set-head origin next` to set followRemoteHead to
> "manual" and to set the correct value for the followRemoteHeadManual to
> "master" if it actually changed the current refs/remote/origin/HEAD from master
> to next?
What I found missing is:
"I know this is the value I expect them to have, because it was
the value I last observed there. Please let me know when they
changed their mind; I want to reconsider my position when it
happens, and your warning me would help me to do so."
My running "remote set-head" to manually change which of their
branches I am interested in does not tell Git anything about what
branch I expect them to be pointing at with HEAD. It may be the
action after such "reconsideration" of my position, but there is 0
bit of information on what I expect their HEAD to be pointing at.
> Or is having this completely manual fine?
If it can be automated, that would be nicer, but I do not think a
manual "remote set-head origin next" gives any information to help
automating it.
> I toyed with the thought of rolling the two settings together (an unrecognized
> string would mean the reference for which we must be silent), but then you
> couldn't have a remote/HEAD called "create" for example, so I think we need to
> store that separately. I'm also not quite happy with "followRemoteHeadManual"
> ...
Yeah, I was thinking a value like "warn-if-not-pointing-at-$branch"
where $branch part would be a token like 'bc/set-head-symref',
'master', etc.
I do not think any of the above should block this topic. It can be
added later without disrupting all other modes implemented there.
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v1] fetch: add configuration for set_head behaviour
2024-11-28 0:12 ` Junio C Hamano
@ 2024-11-28 5:49 ` Bence Ferdinandy
2024-11-28 6:06 ` Junio C Hamano
0 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-28 5:49 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
On Thu Nov 28, 2024 at 01:12, Junio C Hamano <gitster@pobox.com> wrote:
> "Bence Ferdinandy" <bence@ferdinandy.com> writes:
>
>> Just to clarify, an example: the remote's HEAD is set to "master", and you have
>> - git remote set-head origin next
>> - git config set remote.origin.followRemoteHead "manual"
>> - git config set remote.origin.followRemoteHeadManual "master"
>>
>> you manually set these explicitly. As long as the remote's HEAD is still
>> "master" you do not get a warning when running fetch, but if it changes to
>> something else (even "next"), you _do_ get a warning, that is not silenced
>> until you set followRemoteHeadManual to "next".
>
> Yup.
>
>> Would you expect `git remote set-head origin next` to set followRemoteHead to
>> "manual" and to set the correct value for the followRemoteHeadManual to
>> "master" if it actually changed the current refs/remote/origin/HEAD from master
>> to next?
>
> What I found missing is:
>
> "I know this is the value I expect them to have, because it was
> the value I last observed there. Please let me know when they
> changed their mind; I want to reconsider my position when it
> happens, and your warning me would help me to do so."
>
> My running "remote set-head" to manually change which of their
> branches I am interested in does not tell Git anything about what
> branch I expect them to be pointing at with HEAD. It may be the
> action after such "reconsideration" of my position, but there is 0
> bit of information on what I expect their HEAD to be pointing at.
Hmm. After a bit more thought: running `remote set-head` doesn't make much
sense if you have "always" and makes a bit of sense if you have "warn". So
maybe one thing set-head could do is _if_ you have always it could drop you to
"warn" and "warn" could also include a line not just about following with
"remote set-head" but something like "You can either follow with set-head, or
you can turn this off but still get a warning if origin changes by setting
...".
>
>> Or is having this completely manual fine?
>
> If it can be automated, that would be nicer, but I do not think a
> manual "remote set-head origin next" gives any information to help
> automating it.
>
>> I toyed with the thought of rolling the two settings together (an unrecognized
>> string would mean the reference for which we must be silent), but then you
>> couldn't have a remote/HEAD called "create" for example, so I think we need to
>> store that separately. I'm also not quite happy with "followRemoteHeadManual"
>> ...
>
> Yeah, I was thinking a value like "warn-if-not-pointing-at-$branch"
> where $branch part would be a token like 'bc/set-head-symref',
> 'master', etc.
Good idea. Probably shorter though like "warn-if-not-$branch".
>
> I do not think any of the above should block this topic. It can be
> added later without disrupting all other modes implemented there.
Ack, I'll send a respin with just enum fixed for now.
Thanks,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v1] fetch: add configuration for set_head behaviour
2024-11-28 5:49 ` Bence Ferdinandy
@ 2024-11-28 6:06 ` Junio C Hamano
2024-12-03 21:56 ` [RFC PATCH v1 0/2] set_head finishing touches Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Junio C Hamano @ 2024-11-28 6:06 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
"Bence Ferdinandy" <bence@ferdinandy.com> writes:
> Hmm. After a bit more thought: running `remote set-head` doesn't make much
> sense if you have "always" and makes a bit of sense if you have "warn".
Yeah, after "warn" tells the user what branch the remote points at,
the user may decide to match. Or the user may decide to flip the
remote-tracking HEAD to something unrelated. In the former case,
the next and subsequent fetch will not warn, until the remote flips
the HEAD again. In the latter case, the next and subsequent fetch
will warn, until they happen to change their HEAD again and their
choice happens to match ours.
> So
> maybe one thing set-head could do is _if_ you have always it could drop you to
> "warn" and "warn" could also include a line not just about following with
> "remote set-head" but something like "You can either follow with set-head, or
> you can turn this off but still get a warning if origin changes by setting
> ...".
Hmph, if you've been happy with "always", if we notice that the
remote flipped its HEAD, we switch to "warn" and the next and
subsequent fetch will not warn, until they flip again, and then we
will keep warning.
If you do not want to follow (but still monitor origin), the above
would not be sufficient, unless "by setting..." part gives a new
choice via your .followRemoteHEADManual setting.
>> Yeah, I was thinking a value like "warn-if-not-pointing-at-$branch"
>> where $branch part would be a token like 'bc/set-head-symref',
>> 'master', etc.
>
> Good idea. Probably shorter though like "warn-if-not-$branch".
Sorry, I wasn't offering an alternative I thought was better. It
was "we could do this, if we really want to avoid two variables".
But thinking about it further, not having to worry about two
variables may be a huge benefit. If you set .followRemoteHEAD to
"manual" and .followRemoteHEADManual to some value, and then changed
.followRemoteHEAD to something else, you may be very likely to
forget unsetting .followRemoteHEADManual that is no longer in effect.
>> I do not think any of the above should block this topic. It can be
>> added later without disrupting all other modes implemented there.
>
> Ack, I'll send a respin with just enum fixed for now.
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* [RFC PATCH v1 0/2] set_head finishing touches
2024-11-28 6:06 ` Junio C Hamano
@ 2024-12-03 21:56 ` Bence Ferdinandy
2024-12-03 21:56 ` [RFC PATCH v1 1/2] fetch set_head: add warn-if-not-$branch option Bence Ferdinandy
` (2 more replies)
0 siblings, 3 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-12-03 21:56 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin, karthik.188,
Taylor Blau, Patrick Steinhardt, Junio C Hamano, Bence Ferdinandy
This series is intended to be the finishing touches on the fetch/remote
set_head changes as in built off of bf/fetch-set-head-config
<20241128230806.2058962-1-bence@ferdinandy.com>.
Patch 1/2 contains the missing setting Junio was missing, while 2/2 is
an attempt to do the right thing.
I've marked them as RFC because I'm not sure about a couple of things:
- 1/2 contains a slight refactor by extracting the hints into a separate
function to avoid writing it twice. It could be argued that this
refactor could be in a separate patch.
- I'm not exactly sure what should be in this hint, I added the new
"warn-if-not-$branch", but arguments could be made for adding
"always", adding a generic "or see the other possible settings in the
manual" or any other combination
- I've seen some examples of advice stuff in yellow that can be
disabled, so maybe a complete explanation of possibilites could be
done with that mechanism (I haven't yet looked into how that works)
- I'm also not entirely sure about 2/2. I think it's a safe move, that
is very unlikely to be annoying, but it's hard to judge from my
limited perspective on all the different possible workflows.
Bence Ferdinandy (2):
fetch set_head: add warn-if-not-$branch option
remote set-head: set followRemoteHEAD to "warn" if "always"
builtin/fetch.c | 26 ++++++++++++++++++-------
builtin/remote.c | 12 +++++++++++-
remote.c | 5 +++++
remote.h | 6 ++++--
t/t5505-remote.sh | 11 +++++++++++
t/t5510-fetch.sh | 49 +++++++++++++++++++++++++++++++++++++++++++++--
6 files changed, 97 insertions(+), 12 deletions(-)
--
2.47.1.297.g6455f89743
^ permalink raw reply [flat|nested] 258+ messages in thread
* [RFC PATCH v1 1/2] fetch set_head: add warn-if-not-$branch option
2024-12-03 21:56 ` [RFC PATCH v1 0/2] set_head finishing touches Bence Ferdinandy
@ 2024-12-03 21:56 ` Bence Ferdinandy
2024-12-04 2:20 ` Junio C Hamano
2024-12-03 21:56 ` [RFC PATCH v1 2/2] remote set-head: set followRemoteHEAD to "warn" if "always" Bence Ferdinandy
2024-12-04 10:39 ` [PATCH v2 1/3] fetch set_head: move warn advice into advise_if_enabled Bence Ferdinandy
2 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-12-03 21:56 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin, karthik.188,
Taylor Blau, Patrick Steinhardt, Junio C Hamano, Bence Ferdinandy
Currently if we want to have a remote/HEAD locally that is different
from the one on the remote, but we still want to get a warning if remote
changes HEAD, our only option is to have an indiscriminate warning with
"follow_remote_head" set to "warn". Add a new option
"warn-if-not-$branch", where $branch is a branch name we do not wish to
get a warning about. If the remote HEAD is $branch do not warn,
otherwise, behave as "warn".
E.g. let's assume, that our remote origin has HEAD
set to "master", but locally we have "git remote set-head origin seen".
Setting 'remote.origin.followRemoteHEAD = "warn"' will always print
a warning, even though the remote has not changed HEAD from "master".
Setting 'remote.origin.followRemoteHEAD = "warn-if-not-master" will
squelch the warning message, unless the remote changes HEAD from
"master". Note, that should the remote change HEAD to "seen" (which we
have locally), there will still be no warning.
Improve the advice message in report_set_head to also include silencing
the warning message with "warn-if-not-$branch".
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
builtin/fetch.c | 26 ++++++++++++++++++-------
remote.c | 5 +++++
remote.h | 6 ++++--
t/t5510-fetch.sh | 49 ++++++++++++++++++++++++++++++++++++++++++++++--
4 files changed, 75 insertions(+), 11 deletions(-)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 88c5c5d781..fd7f3694cc 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1579,6 +1579,15 @@ static const char *strip_refshead(const char *name){
return name;
}
+static void set_head_advice_msg(const char *remote, const char *head_name) {
+
+ printf("Run 'git remote set-head %s %s' to follow the change, or\n"
+ "'git config set remote.%s.warn-if-not-%s' to disable this warning\n"
+ "until the remote changes HEAD again.\n",
+ remote, head_name, remote, head_name);
+
+}
+
static void report_set_head(const char *remote, const char *head_name,
struct strbuf *buf_prev, int updateres) {
struct strbuf buf_prefix = STRBUF_INIT;
@@ -1590,20 +1599,19 @@ static void report_set_head(const char *remote, const char *head_name,
if (prev_head && strcmp(prev_head, head_name)) {
printf("'HEAD' at '%s' is '%s', but we have '%s' locally.\n",
remote, head_name, prev_head);
- printf("Run 'git remote set-head %s %s' to follow the change.\n",
- remote, head_name);
+ set_head_advice_msg(remote, head_name);
}
else if (updateres && buf_prev->len) {
printf("'HEAD' at '%s' is '%s', "
"but we have a detached HEAD pointing to '%s' locally.\n",
remote, head_name, buf_prev->buf);
- printf("Run 'git remote set-head %s %s' to follow the change.\n",
- remote, head_name);
+ set_head_advice_msg(remote, head_name);
}
strbuf_release(&buf_prefix);
}
-static int set_head(const struct ref *remote_refs, int follow_remote_head)
+static int set_head(const struct ref *remote_refs, int follow_remote_head,
+ const char *no_warn_branch)
{
int result = 0, create_only, is_bare, was_detached;
struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
@@ -1660,7 +1668,10 @@ static int set_head(const struct ref *remote_refs, int follow_remote_head)
result = 1;
goto cleanup;
}
- if (follow_remote_head == FOLLOW_REMOTE_WARN && verbosity >= 0)
+ if ((follow_remote_head == FOLLOW_REMOTE_WARN ||
+ (follow_remote_head == FOLLOW_REMOTE_WARN_IF_NOT_BRANCH &&
+ strcmp(no_warn_branch, head_name))
+ ) && verbosity >= 0)
report_set_head(remote, head_name, &b_local_head, was_detached);
cleanup:
@@ -1889,7 +1900,8 @@ static int do_fetch(struct transport *transport,
"you need to specify exactly one branch with the --set-upstream option"));
}
}
- if (set_head(remote_refs, transport->remote->follow_remote_head))
+ if (set_head(remote_refs, transport->remote->follow_remote_head,
+ transport->remote->no_warn_branch))
;
/*
* Way too many cases where this can go wrong
diff --git a/remote.c b/remote.c
index 0b18840d43..f0e1b1b76a 100644
--- a/remote.c
+++ b/remote.c
@@ -515,6 +515,7 @@ static int handle_config(const char *key, const char *value,
return parse_transport_option(key, value,
&remote->server_options);
} else if (!strcmp(subkey, "followremotehead")) {
+ const char *no_warn_branch;
if (!strcmp(value, "never"))
remote->follow_remote_head = FOLLOW_REMOTE_NEVER;
else if (!strcmp(value, "create"))
@@ -523,6 +524,10 @@ static int handle_config(const char *key, const char *value,
remote->follow_remote_head = FOLLOW_REMOTE_WARN;
else if (!strcmp(value, "always"))
remote->follow_remote_head = FOLLOW_REMOTE_ALWAYS;
+ else if (skip_prefix(value, "warn-if-not-", &no_warn_branch)) {
+ remote->follow_remote_head = FOLLOW_REMOTE_WARN_IF_NOT_BRANCH;
+ remote->no_warn_branch = no_warn_branch;
+ }
}
return 0;
}
diff --git a/remote.h b/remote.h
index 184b35653d..75be3977ba 100644
--- a/remote.h
+++ b/remote.h
@@ -62,8 +62,9 @@ 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,
+ FOLLOW_REMOTE_WARN_IF_NOT_BRANCH = 1,
+ FOLLOW_REMOTE_WARN = 2,
+ FOLLOW_REMOTE_ALWAYS = 3,
};
struct remote {
@@ -116,6 +117,7 @@ struct remote {
struct string_list server_options;
enum follow_remote_head_settings follow_remote_head;
+ const char *no_warn_branch;
};
/**
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 2467027d34..be0c60be2c 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -124,7 +124,9 @@ test_expect_success "fetch test followRemoteHEAD warn no change" '
git fetch >output &&
echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
"but we have ${SQ}other${SQ} locally." >expect &&
- echo "Run ${SQ}git remote set-head origin main${SQ} to follow the change." >>expect &&
+ echo "Run ${SQ}git remote set-head origin main${SQ} to follow the change, or" >>expect &&
+ echo "${SQ}git config set remote.origin.warn-if-not-main${SQ} to disable this warning" >>expect &&
+ echo "until the remote changes HEAD again." >>expect &&
test_cmp expect output &&
head=$(git rev-parse refs/remotes/origin/HEAD) &&
branch=$(git rev-parse refs/remotes/origin/other) &&
@@ -161,7 +163,9 @@ test_expect_success "fetch test followRemoteHEAD warn detached" '
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 &&
- echo "Run ${SQ}git remote set-head origin main${SQ} to follow the change." >>expect &&
+ echo "Run ${SQ}git remote set-head origin main${SQ} to follow the change, or" >>expect &&
+ echo "${SQ}git config set remote.origin.warn-if-not-main${SQ} to disable this warning" >>expect &&
+ echo "until the remote changes HEAD again." >>expect &&
test_cmp expect output
)
'
@@ -184,6 +188,47 @@ test_expect_success "fetch test followRemoteHEAD warn quiet" '
)
'
+test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is same" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git config set remote.origin.followRemoteHEAD "warn-if-not-main" &&
+ output=$(git fetch) &&
+ test "z" = "z$output" &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is different" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git config set remote.origin.followRemoteHEAD "warn-if-not-some/different-branch" &&
+ git fetch >output &&
+ echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
+ "but we have ${SQ}other${SQ} locally." >expect &&
+ echo "Run ${SQ}git remote set-head origin main${SQ} to follow the change, or" >>expect &&
+ echo "${SQ}git config set remote.origin.warn-if-not-main${SQ} to disable this warning" >>expect &&
+ echo "until the remote changes HEAD again." >>expect &&
+ test_cmp expect output &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
+ )
+'
+
test_expect_success "fetch test followRemoteHEAD always" '
test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
(
--
2.47.1.297.g6455f89743
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [RFC PATCH v1 1/2] fetch set_head: add warn-if-not-$branch option
2024-12-03 21:56 ` [RFC PATCH v1 1/2] fetch set_head: add warn-if-not-$branch option Bence Ferdinandy
@ 2024-12-04 2:20 ` Junio C Hamano
2024-12-04 8:15 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Junio C Hamano @ 2024-12-04 2:20 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
Bence Ferdinandy <bence@ferdinandy.com> writes:
> +static void set_head_advice_msg(const char *remote, const char *head_name) {
> +
> + printf("Run 'git remote set-head %s %s' to follow the change, or\n"
> + "'git config set remote.%s.warn-if-not-%s' to disable this warning\n"
> + "until the remote changes HEAD again.\n",
> + remote, head_name, remote, head_name);
> +
> +}
Style. "{" that encloses the function body sits on a line of its
own.
Perhaps use the advise_if_enabled(), so that those who already
learned how to deal with the situation can squelch the "how to fix"
message.
> -static int set_head(const struct ref *remote_refs, int follow_remote_head)
> +static int set_head(const struct ref *remote_refs, int follow_remote_head,
> + const char *no_warn_branch)
> {
> int result = 0, create_only, is_bare, was_detached;
> struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
> @@ -1660,7 +1668,10 @@ static int set_head(const struct ref *remote_refs, int follow_remote_head)
> result = 1;
> goto cleanup;
> }
> - if (follow_remote_head == FOLLOW_REMOTE_WARN && verbosity >= 0)
> + if ((follow_remote_head == FOLLOW_REMOTE_WARN ||
> + (follow_remote_head == FOLLOW_REMOTE_WARN_IF_NOT_BRANCH &&
> + strcmp(no_warn_branch, head_name))
> + ) && verbosity >= 0)
Reorder conditions combined with &&- to have more expensive ones
later.
if (verbosity >= 0 &&
(follow_remote_head == FOLLOW_REMOTE_WARN ||
(follow_remote_head == FOLLOW_REMOTE_WARN_IF_NOT_BRANCH &&
strcmp(no_warn_branch, head_name)))
As human readers, we may know that no_warn_branch is always valid
when (follow_remote_head == FOLLOW_REMOTE_WARN_IF_NOT_BRANCH), but
semi clever compilers may not realize it and give a false warning
about using no_warn_branch potentially uninitialized.
We could do without adding FOLLOW_REMOTE_WARN_IF_NOT_BRANCH and reuse
FOLLOW_REMOTE_WARN, like so:
if (verbosity >= 0 &&
follow_remote_head == FOLLOW_REMOTE_WARN &&
(!no_warn_branch || strcmp(no_warn_branch, head_name)))
That is, "if set to remote-warn, always warn, or no_warn_branch is
not NULL, only warn if the current head is different from it".
> diff --git a/remote.c b/remote.c
> index 0b18840d43..f0e1b1b76a 100644
> --- a/remote.c
> +++ b/remote.c
> @@ -515,6 +515,7 @@ static int handle_config(const char *key, const char *value,
> return parse_transport_option(key, value,
> &remote->server_options);
> } else if (!strcmp(subkey, "followremotehead")) {
> + const char *no_warn_branch;
> if (!strcmp(value, "never"))
> remote->follow_remote_head = FOLLOW_REMOTE_NEVER;
> else if (!strcmp(value, "create"))
> @@ -523,6 +524,10 @@ static int handle_config(const char *key, const char *value,
> remote->follow_remote_head = FOLLOW_REMOTE_WARN;
> else if (!strcmp(value, "always"))
> remote->follow_remote_head = FOLLOW_REMOTE_ALWAYS;
> + else if (skip_prefix(value, "warn-if-not-", &no_warn_branch)) {
> + remote->follow_remote_head = FOLLOW_REMOTE_WARN_IF_NOT_BRANCH;
> + remote->no_warn_branch = no_warn_branch;
> + }
If we were to do without FOLLOW_REMOTE_WARN_IF_NOT_BRANCH, then the
above becomes
remote->follow_remote_head = FOLLOW_REMOTE_WARN;
remote->no_warn_branch = NULL;
} else if (skip_prefix(value, "warn-if-not-", &no_warn_branch)) {
remote->follow_remote_head = FOLLOW_REMOTE_WARN;
remote->no_warn_branch = no_warn_branch;
} else if (!strcmp(value, "always")) {
remote->follow_remote_head = FOLLOW_REMOTE_ALWAYS;
} else {
warn(_("unrecognized followRemoteHEAD value '%s' ignored"),
value);
}
We'd want the new choice documented before we graduate this topic
out of the RFC status.
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [RFC PATCH v1 1/2] fetch set_head: add warn-if-not-$branch option
2024-12-04 2:20 ` Junio C Hamano
@ 2024-12-04 8:15 ` Bence Ferdinandy
0 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-12-04 8:15 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
On Wed Dec 04, 2024 at 03:20, Junio C Hamano <gitster@pobox.com> wrote:
> Bence Ferdinandy <bence@ferdinandy.com> writes:
>
>> +static void set_head_advice_msg(const char *remote, const char *head_name) {
>> +
>> + printf("Run 'git remote set-head %s %s' to follow the change, or\n"
>> + "'git config set remote.%s.warn-if-not-%s' to disable this warning\n"
>> + "until the remote changes HEAD again.\n",
>> + remote, head_name, remote, head_name);
>> +
>> +}
>
> Style. "{" that encloses the function body sits on a line of its
> own.
>
> Perhaps use the advise_if_enabled(), so that those who already
> learned how to deal with the situation can squelch the "how to fix"
> message.
>
>> -static int set_head(const struct ref *remote_refs, int follow_remote_head)
>> +static int set_head(const struct ref *remote_refs, int follow_remote_head,
>> + const char *no_warn_branch)
>> {
>> int result = 0, create_only, is_bare, was_detached;
>> struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
>> @@ -1660,7 +1668,10 @@ static int set_head(const struct ref *remote_refs, int follow_remote_head)
>> result = 1;
>> goto cleanup;
>> }
>> - if (follow_remote_head == FOLLOW_REMOTE_WARN && verbosity >= 0)
>> + if ((follow_remote_head == FOLLOW_REMOTE_WARN ||
>> + (follow_remote_head == FOLLOW_REMOTE_WARN_IF_NOT_BRANCH &&
>> + strcmp(no_warn_branch, head_name))
>> + ) && verbosity >= 0)
>
> Reorder conditions combined with &&- to have more expensive ones
> later.
>
> if (verbosity >= 0 &&
> (follow_remote_head == FOLLOW_REMOTE_WARN ||
> (follow_remote_head == FOLLOW_REMOTE_WARN_IF_NOT_BRANCH &&
> strcmp(no_warn_branch, head_name)))
>
> As human readers, we may know that no_warn_branch is always valid
> when (follow_remote_head == FOLLOW_REMOTE_WARN_IF_NOT_BRANCH), but
> semi clever compilers may not realize it and give a false warning
> about using no_warn_branch potentially uninitialized.
>
> We could do without adding FOLLOW_REMOTE_WARN_IF_NOT_BRANCH and reuse
> FOLLOW_REMOTE_WARN, like so:
>
> if (verbosity >= 0 &&
> follow_remote_head == FOLLOW_REMOTE_WARN &&
> (!no_warn_branch || strcmp(no_warn_branch, head_name)))
>
> That is, "if set to remote-warn, always warn, or no_warn_branch is
> not NULL, only warn if the current head is different from it".
Ah, nice, this has the added benefit of a bad configuration where
"warn-if-not-" doesn't actually specify a branch to fall back to just "warn".
>
>> diff --git a/remote.c b/remote.c
>> index 0b18840d43..f0e1b1b76a 100644
>> --- a/remote.c
>> +++ b/remote.c
>> @@ -515,6 +515,7 @@ static int handle_config(const char *key, const char *value,
>> return parse_transport_option(key, value,
>> &remote->server_options);
>> } else if (!strcmp(subkey, "followremotehead")) {
>> + const char *no_warn_branch;
>> if (!strcmp(value, "never"))
>> remote->follow_remote_head = FOLLOW_REMOTE_NEVER;
>> else if (!strcmp(value, "create"))
>> @@ -523,6 +524,10 @@ static int handle_config(const char *key, const char *value,
>> remote->follow_remote_head = FOLLOW_REMOTE_WARN;
>> else if (!strcmp(value, "always"))
>> remote->follow_remote_head = FOLLOW_REMOTE_ALWAYS;
>> + else if (skip_prefix(value, "warn-if-not-", &no_warn_branch)) {
>> + remote->follow_remote_head = FOLLOW_REMOTE_WARN_IF_NOT_BRANCH;
>> + remote->no_warn_branch = no_warn_branch;
>> + }
>
> If we were to do without FOLLOW_REMOTE_WARN_IF_NOT_BRANCH, then the
> above becomes
>
> remote->follow_remote_head = FOLLOW_REMOTE_WARN;
> remote->no_warn_branch = NULL;
> } else if (skip_prefix(value, "warn-if-not-", &no_warn_branch)) {
> remote->follow_remote_head = FOLLOW_REMOTE_WARN;
> remote->no_warn_branch = no_warn_branch;
> } else if (!strcmp(value, "always")) {
> remote->follow_remote_head = FOLLOW_REMOTE_ALWAYS;
> } else {
> warn(_("unrecognized followRemoteHEAD value '%s' ignored"),
> value);
> }
>
> We'd want the new choice documented before we graduate this topic
> out of the RFC status.
>
> Thanks.
--
bence.ferdinandy.com
^ permalink raw reply [flat|nested] 258+ messages in thread
* [RFC PATCH v1 2/2] remote set-head: set followRemoteHEAD to "warn" if "always"
2024-12-03 21:56 ` [RFC PATCH v1 0/2] set_head finishing touches Bence Ferdinandy
2024-12-03 21:56 ` [RFC PATCH v1 1/2] fetch set_head: add warn-if-not-$branch option Bence Ferdinandy
@ 2024-12-03 21:56 ` Bence Ferdinandy
2024-12-04 2:22 ` Junio C Hamano
2024-12-04 10:39 ` [PATCH v2 1/3] fetch set_head: move warn advice into advise_if_enabled Bence Ferdinandy
2 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-12-03 21:56 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin, karthik.188,
Taylor Blau, Patrick Steinhardt, Junio C Hamano, Bence Ferdinandy
When running "remote set-head" manually it is unlikely, that the user
would actually like to have "fetch" always update the remote/HEAD. On
the contrary, it is more likely, that the user would expect remote/HEAD
to stay the way they manually set it, and just forgot about having
"followRemoteHEAD" set to "always".
When "followRemoteHEAD" is set to "always" make running "remote
set-head" change the config to "warn".
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
builtin/remote.c | 12 +++++++++++-
t/t5505-remote.sh | 11 +++++++++++
2 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 4a8b2ef678..9a30c17724 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1433,6 +1433,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
b_local_head = STRBUF_INIT;
char *head_name = NULL;
struct ref_store *refs = get_main_ref_store(the_repository);
+ struct remote *remote;
struct option options[] = {
OPT_BOOL('a', "auto", &opt_a,
@@ -1443,8 +1444,10 @@ static int set_head(int argc, const char **argv, const char *prefix)
};
argc = parse_options(argc, argv, prefix, options,
builtin_remote_sethead_usage, 0);
- if (argc)
+ if (argc) {
strbuf_addf(&b_head, "refs/remotes/%s/HEAD", argv[0]);
+ remote = remote_get(argv[0]);
+ }
if (!opt_a && !opt_d && argc == 2) {
head_name = xstrdup(argv[1]);
@@ -1483,6 +1486,13 @@ static int set_head(int argc, const char **argv, const char *prefix)
}
if (opt_a)
report_set_head_auto(argv[0], head_name, &b_local_head, was_detached);
+ if (remote->follow_remote_head == FOLLOW_REMOTE_ALWAYS) {
+ struct strbuf config_name = STRBUF_INIT;
+ strbuf_addf(&config_name,
+ "remote.%s.followremotehead", remote->name);
+ git_config_set(config_name.buf, "warn");
+ strbuf_release(&config_name);
+ }
cleanup:
free(head_name);
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 2600add82a..76ffdb2ec9 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -505,6 +505,17 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
)
'
+test_expect_success 'set-head changes followRemoteHEAD always to warn' '
+ (
+ cd test &&
+ git config set remote.origin.followRemoteHEAD "always" &&
+ git remote set-head --auto origin &&
+ git config get remote.origin.followRemoteHEAD >output &&
+ echo "warn" >expect &&
+ test_cmp expect output
+ )
+'
+
cat >test/expect <<\EOF
refs/remotes/origin/side2
EOF
--
2.47.1.297.g6455f89743
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [RFC PATCH v1 2/2] remote set-head: set followRemoteHEAD to "warn" if "always"
2024-12-03 21:56 ` [RFC PATCH v1 2/2] remote set-head: set followRemoteHEAD to "warn" if "always" Bence Ferdinandy
@ 2024-12-04 2:22 ` Junio C Hamano
0 siblings, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-12-04 2:22 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
Bence Ferdinandy <bence@ferdinandy.com> writes:
> When running "remote set-head" manually it is unlikely, that the user
> would actually like to have "fetch" always update the remote/HEAD. On
> the contrary, it is more likely, that the user would expect remote/HEAD
> to stay the way they manually set it, and just forgot about having
> "followRemoteHEAD" set to "always".
>
> When "followRemoteHEAD" is set to "always" make running "remote
> set-head" change the config to "warn".
If we were to take [1/2] of this series, then another plausible
option is to set it to whatever their current value is, but that
would require an extra probing of the remote state, so "warn" is
probably a reasonable choice here.
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v2 1/3] fetch set_head: move warn advice into advise_if_enabled
2024-12-03 21:56 ` [RFC PATCH v1 0/2] set_head finishing touches Bence Ferdinandy
2024-12-03 21:56 ` [RFC PATCH v1 1/2] fetch set_head: add warn-if-not-$branch option Bence Ferdinandy
2024-12-03 21:56 ` [RFC PATCH v1 2/2] remote set-head: set followRemoteHEAD to "warn" if "always" Bence Ferdinandy
@ 2024-12-04 10:39 ` Bence Ferdinandy
2024-12-04 10:39 ` [PATCH v2 2/3] fetch set_head: add warn-if-not-$branch option Bence Ferdinandy
` (2 more replies)
2 siblings, 3 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-12-04 10:39 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin, karthik.188,
Taylor Blau, Patrick Steinhardt, Junio C Hamano, Bence Ferdinandy
Advice about what to do when getting a warning is typed out explicitly
twice and is printed as regular output. The output is also tested for.
Extract the advice message into a single place and use a wrapper
function, so if later the advice is made more chatty the signature only
needs to be changed in once place. Remove the testing for the advice
output in the tests.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v2: new patch, with the refactor moved out from the following one
advice.c | 1 +
advice.h | 1 +
builtin/fetch.c | 17 +++++++++++++----
t/t5510-fetch.sh | 2 --
4 files changed, 15 insertions(+), 6 deletions(-)
diff --git a/advice.c b/advice.c
index 6b879d805c..66461fdce9 100644
--- a/advice.c
+++ b/advice.c
@@ -53,6 +53,7 @@ static struct {
[ADVICE_COMMIT_BEFORE_MERGE] = { "commitBeforeMerge" },
[ADVICE_DETACHED_HEAD] = { "detachedHead" },
[ADVICE_DIVERGING] = { "diverging" },
+ [ADVICE_FETCH_SET_HEAD_WARN] = { "fetchRemoteHEADWarn" },
[ADVICE_FETCH_SHOW_FORCED_UPDATES] = { "fetchShowForcedUpdates" },
[ADVICE_FORCE_DELETE_BRANCH] = { "forceDeleteBranch" },
[ADVICE_GRAFT_FILE_DEPRECATED] = { "graftFileDeprecated" },
diff --git a/advice.h b/advice.h
index d7466bc0ef..cf2284ec43 100644
--- a/advice.h
+++ b/advice.h
@@ -20,6 +20,7 @@ enum advice_type {
ADVICE_COMMIT_BEFORE_MERGE,
ADVICE_DETACHED_HEAD,
ADVICE_DIVERGING,
+ ADVICE_FETCH_SET_HEAD_WARN,
ADVICE_FETCH_SHOW_FORCED_UPDATES,
ADVICE_FORCE_DELETE_BRANCH,
ADVICE_GRAFT_FILE_DEPRECATED,
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 62769d1686..087beb4c92 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1579,6 +1579,17 @@ static const char *strip_refshead(const char *name){
return 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.");
+
+ advise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head),
+ remote, head_name, remote);
+}
+
static void report_set_head(const char *remote, const char *head_name,
struct strbuf *buf_prev, int updateres) {
struct strbuf buf_prefix = STRBUF_INIT;
@@ -1590,15 +1601,13 @@ static void report_set_head(const char *remote, const char *head_name,
if (prev_head && strcmp(prev_head, head_name)) {
printf("'HEAD' at '%s' is '%s', but we have '%s' locally.\n",
remote, head_name, prev_head);
- printf("Run 'git remote set-head %s %s' to follow the change.\n",
- remote, head_name);
+ set_head_advice_msg(remote, head_name);
}
else if (updateres && buf_prev->len) {
printf("'HEAD' at '%s' is '%s', "
"but we have a detached HEAD pointing to '%s' locally.\n",
remote, head_name, buf_prev->buf);
- printf("Run 'git remote set-head %s %s' to follow the change.\n",
- remote, head_name);
+ set_head_advice_msg(remote, head_name);
}
strbuf_release(&buf_prefix);
}
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index ccb74428bc..5d7ee1b550 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -123,7 +123,6 @@ test_expect_success "fetch test followRemoteHEAD warn no change" '
git fetch >output &&
echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
"but we have ${SQ}other${SQ} locally." >expect &&
- echo "Run ${SQ}git remote set-head origin main${SQ} to follow the change." >>expect &&
test_cmp expect output &&
head=$(git rev-parse refs/remotes/origin/HEAD) &&
branch=$(git rev-parse refs/remotes/origin/other) &&
@@ -160,7 +159,6 @@ test_expect_success "fetch test followRemoteHEAD warn detached" '
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 &&
- echo "Run ${SQ}git remote set-head origin main${SQ} to follow the change." >>expect &&
test_cmp expect output
)
'
--
2.47.1.398.g18e7475ebe
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v2 2/3] fetch set_head: add warn-if-not-$branch option
2024-12-04 10:39 ` [PATCH v2 1/3] fetch set_head: move warn advice into advise_if_enabled Bence Ferdinandy
@ 2024-12-04 10:39 ` Bence Ferdinandy
2024-12-04 10:39 ` [PATCH v2 3/3] remote set-head: set followRemoteHEAD to "warn" if "always" Bence Ferdinandy
2024-12-04 19:28 ` [PATCH v2 1/3] fetch set_head: move warn advice into advise_if_enabled Junio C Hamano
2 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-12-04 10:39 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin, karthik.188,
Taylor Blau, Patrick Steinhardt, Junio C Hamano, Bence Ferdinandy
Currently if we want to have a remote/HEAD locally that is different
from the one on the remote, but we still want to get a warning if remote
changes HEAD, our only option is to have an indiscriminate warning with
"follow_remote_head" set to "warn". Add a new option
"warn-if-not-$branch", where $branch is a branch name we do not wish to
get a warning about. If the remote HEAD is $branch do not warn,
otherwise, behave as "warn".
E.g. let's assume, that our remote origin has HEAD
set to "master", but locally we have "git remote set-head origin seen".
Setting 'remote.origin.followRemoteHEAD = "warn"' will always print
a warning, even though the remote has not changed HEAD from "master".
Setting 'remote.origin.followRemoteHEAD = "warn-if-not-master" will
squelch the warning message, unless the remote changes HEAD from
"master". Note, that should the remote change HEAD to "seen" (which we
have locally), there will still be no warning.
Improve the advice message in report_set_head to also include silencing
the warning message with "warn-if-not-$branch".
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v2: reuse FOLLOW_REMOTE_WARN
add documentation
Documentation/config/remote.txt | 8 ++++---
builtin/fetch.c | 16 +++++++++-----
remote.c | 13 +++++++++--
remote.h | 1 +
t/t5510-fetch.sh | 38 +++++++++++++++++++++++++++++++++
5 files changed, 66 insertions(+), 10 deletions(-)
diff --git a/Documentation/config/remote.txt b/Documentation/config/remote.txt
index 024f92befc..4118c219c1 100644
--- a/Documentation/config/remote.txt
+++ b/Documentation/config/remote.txt
@@ -106,10 +106,12 @@ remote.<name>.followRemoteHEAD::
How linkgit:git-fetch[1] should handle updates to `remotes/<name>/HEAD`.
The default value is "create", which will create `remotes/<name>/HEAD`
if it exists on the remote, but not locally, but will not touch an
- already existing local reference. Setting to "warn" will print
+ already existing local reference. Setting to "warn" will print
a message if the remote has a different value, than the local one and
- in case there is no local reference, it behaves like "create". Setting
- to "always" will silently update it to the value on the remote.
+ 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 to "always" will silently update it to the value on the remote.
Finally, setting it to "never" will never change or create the local
reference.
+
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 087beb4c92..b3f6793026 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1584,10 +1584,12 @@ 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.");
+ "if you do not want to see this message. Specifically running\n"
+ "'git config set remote.%s.followRemoteHEAD %s' will disable the warning\n"
+ "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, remote, remote, head_name);
}
static void report_set_head(const char *remote, const char *head_name,
@@ -1612,7 +1614,8 @@ 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, int follow_remote_head)
+static int set_head(const struct ref *remote_refs, int follow_remote_head,
+ const char *no_warn_branch)
{
int result = 0, create_only, is_bare, was_detached;
struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
@@ -1669,7 +1672,9 @@ static int set_head(const struct ref *remote_refs, int follow_remote_head)
result = 1;
goto cleanup;
}
- if (follow_remote_head == FOLLOW_REMOTE_WARN && verbosity >= 0)
+ if (verbosity >= 0 &&
+ follow_remote_head == FOLLOW_REMOTE_WARN &&
+ (!no_warn_branch || strcmp(no_warn_branch, head_name)))
report_set_head(remote, head_name, &b_local_head, was_detached);
cleanup:
@@ -1898,7 +1903,8 @@ static int do_fetch(struct transport *transport,
"you need to specify exactly one branch with the --set-upstream option"));
}
}
- if (set_head(remote_refs, transport->remote->follow_remote_head))
+ if (set_head(remote_refs, transport->remote->follow_remote_head,
+ transport->remote->no_warn_branch))
;
/*
* Way too many cases where this can go wrong
diff --git a/remote.c b/remote.c
index 0b18840d43..4ec5d3f47b 100644
--- a/remote.c
+++ b/remote.c
@@ -515,14 +515,23 @@ static int handle_config(const char *key, const char *value,
return parse_transport_option(key, value,
&remote->server_options);
} else if (!strcmp(subkey, "followremotehead")) {
+ const char *no_warn_branch;
if (!strcmp(value, "never"))
remote->follow_remote_head = FOLLOW_REMOTE_NEVER;
else if (!strcmp(value, "create"))
remote->follow_remote_head = FOLLOW_REMOTE_CREATE;
- else if (!strcmp(value, "warn"))
+ else if (!strcmp(value, "warn")) {
remote->follow_remote_head = FOLLOW_REMOTE_WARN;
- else if (!strcmp(value, "always"))
+ remote->no_warn_branch = NULL;
+ } else if (skip_prefix(value, "warn-if-not-", &no_warn_branch)) {
+ remote->follow_remote_head = FOLLOW_REMOTE_WARN;
+ remote->no_warn_branch = no_warn_branch;
+ } else if (!strcmp(value, "always")) {
remote->follow_remote_head = FOLLOW_REMOTE_ALWAYS;
+ } else {
+ warning(_("unrecognized followRemoteHEAD value '%s' ignored"),
+ value);
+ }
}
return 0;
}
diff --git a/remote.h b/remote.h
index 184b35653d..bda10dd5c8 100644
--- a/remote.h
+++ b/remote.h
@@ -116,6 +116,7 @@ struct remote {
struct string_list server_options;
enum follow_remote_head_settings follow_remote_head;
+ const char *no_warn_branch;
};
/**
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 5d7ee1b550..5c9915b836 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -181,6 +181,44 @@ test_expect_success "fetch test followRemoteHEAD warn quiet" '
)
'
+test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is same" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git config set remote.origin.followRemoteHEAD "warn-if-not-main" &&
+ output=$(git fetch) &&
+ test "z" = "z$output" &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is different" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git config set remote.origin.followRemoteHEAD "warn-if-not-some/different-branch" &&
+ git 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 rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
+ )
+'
+
test_expect_success "fetch test followRemoteHEAD always" '
test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
(
--
2.47.1.398.g18e7475ebe
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v2 3/3] remote set-head: set followRemoteHEAD to "warn" if "always"
2024-12-04 10:39 ` [PATCH v2 1/3] fetch set_head: move warn advice into advise_if_enabled Bence Ferdinandy
2024-12-04 10:39 ` [PATCH v2 2/3] fetch set_head: add warn-if-not-$branch option Bence Ferdinandy
@ 2024-12-04 10:39 ` Bence Ferdinandy
2024-12-04 11:43 ` Kristoffer Haugsbakk
2024-12-04 19:28 ` [PATCH v2 1/3] fetch set_head: move warn advice into advise_if_enabled Junio C Hamano
2 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-12-04 10:39 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin, karthik.188,
Taylor Blau, Patrick Steinhardt, Junio C Hamano, Bence Ferdinandy
When running "remote set-head" manually it is unlikely, that the user
would actually like to have "fetch" always update the remote/HEAD. On
the contrary, it is more likely, that the user would expect remote/HEAD
to stay the way they manually set it, and just forgot about having
"followRemoteHEAD" set to "always".
When "followRemoteHEAD" is set to "always" make running "remote
set-head" change the config to "warn".
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
builtin/remote.c | 12 +++++++++++-
t/t5505-remote.sh | 11 +++++++++++
2 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index d5b81445f2..32d02ca4a0 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1438,6 +1438,7 @@ static int set_head(int argc, const char **argv, const char *prefix,
b_local_head = STRBUF_INIT;
char *head_name = NULL;
struct ref_store *refs = get_main_ref_store(the_repository);
+ struct remote *remote;
struct option options[] = {
OPT_BOOL('a', "auto", &opt_a,
@@ -1448,8 +1449,10 @@ static int set_head(int argc, const char **argv, const char *prefix,
};
argc = parse_options(argc, argv, prefix, options,
builtin_remote_sethead_usage, 0);
- if (argc)
+ if (argc) {
strbuf_addf(&b_head, "refs/remotes/%s/HEAD", argv[0]);
+ remote = remote_get(argv[0]);
+ }
if (!opt_a && !opt_d && argc == 2) {
head_name = xstrdup(argv[1]);
@@ -1488,6 +1491,13 @@ static int set_head(int argc, const char **argv, const char *prefix,
}
if (opt_a)
report_set_head_auto(argv[0], head_name, &b_local_head, was_detached);
+ if (remote->follow_remote_head == FOLLOW_REMOTE_ALWAYS) {
+ struct strbuf config_name = STRBUF_INIT;
+ strbuf_addf(&config_name,
+ "remote.%s.followremotehead", remote->name);
+ git_config_set(config_name.buf, "warn");
+ strbuf_release(&config_name);
+ }
cleanup:
free(head_name);
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 7411aa770d..daf70406be 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -504,6 +504,17 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
)
'
+test_expect_success 'set-head changes followRemoteHEAD always to warn' '
+ (
+ cd test &&
+ git config set remote.origin.followRemoteHEAD "always" &&
+ git remote set-head --auto origin &&
+ git config get remote.origin.followRemoteHEAD >output &&
+ echo "warn" >expect &&
+ test_cmp expect output
+ )
+'
+
cat >test/expect <<\EOF
refs/remotes/origin/side2
EOF
--
2.47.1.398.g18e7475ebe
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v2 3/3] remote set-head: set followRemoteHEAD to "warn" if "always"
2024-12-04 10:39 ` [PATCH v2 3/3] remote set-head: set followRemoteHEAD to "warn" if "always" Bence Ferdinandy
@ 2024-12-04 11:43 ` Kristoffer Haugsbakk
2024-12-04 20:40 ` Junio C Hamano
0 siblings, 1 reply; 258+ messages in thread
From: Kristoffer Haugsbakk @ 2024-12-04 11:43 UTC (permalink / raw)
To: Bence Ferdinandy, git
Cc: Phillip Wood, René Scharfe, Johannes Schindelin,
Karthik Nayak, Taylor Blau, Patrick Steinhardt, Junio C Hamano
Hi
On Wed, Dec 4, 2024, at 11:39, Bence Ferdinandy wrote:
> diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
> index 7411aa770d..daf70406be 100755
> --- a/t/t5505-remote.sh
> +++ b/t/t5505-remote.sh
> @@ -504,6 +504,17 @@ test_expect_success 'set-head --auto has no
> problem w/multiple HEADs' '
> )
> '
>
> +test_expect_success 'set-head changes followRemoteHEAD always to warn' '
> + (
> + cd test &&
I think you need to `cd` in a subshell here. See `t/README`, “Don't
chdir around in tests.”.
> + git config set remote.origin.followRemoteHEAD "always" &&
> + git remote set-head --auto origin &&
> + git config get remote.origin.followRemoteHEAD >output &&
Nit: maybe `actual` instead of `output`? Just for uniformity.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v2 3/3] remote set-head: set followRemoteHEAD to "warn" if "always"
2024-12-04 11:43 ` Kristoffer Haugsbakk
@ 2024-12-04 20:40 ` Junio C Hamano
2024-12-04 20:44 ` Kristoffer Haugsbakk
0 siblings, 1 reply; 258+ messages in thread
From: Junio C Hamano @ 2024-12-04 20:40 UTC (permalink / raw)
To: Kristoffer Haugsbakk
Cc: Bence Ferdinandy, git, Phillip Wood, René Scharfe,
Johannes Schindelin, Karthik Nayak, Taylor Blau,
Patrick Steinhardt
"Kristoffer Haugsbakk" <kristofferhaugsbakk@fastmail.com> writes:
> On Wed, Dec 4, 2024, at 11:39, Bence Ferdinandy wrote:
>> diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
>> index 7411aa770d..daf70406be 100755
>> --- a/t/t5505-remote.sh
>> +++ b/t/t5505-remote.sh
>> @@ -504,6 +504,17 @@ test_expect_success 'set-head --auto has no
>> problem w/multiple HEADs' '
>> )
>> '
>>
>> +test_expect_success 'set-head changes followRemoteHEAD always to warn' '
>> + (
>> + cd test &&
>
> I think you need to `cd` in a subshell here. See `t/README`, “Don't
> chdir around in tests.”.
Puzzled. Isn't this inside a (subshell) already?
>> + git config set remote.origin.followRemoteHEAD "always" &&
>> + git remote set-head --auto origin &&
>> + git config get remote.origin.followRemoteHEAD >output &&
>
> Nit: maybe `actual` instead of `output`? Just for uniformity.
t5505 is already heavily contaminated with the pattern to compare
"expect" with "output", not with "actual", but that does not make
it a good idea to make it even worse by adding more instances ;-)
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v2 3/3] remote set-head: set followRemoteHEAD to "warn" if "always"
2024-12-04 20:40 ` Junio C Hamano
@ 2024-12-04 20:44 ` Kristoffer Haugsbakk
2024-12-05 8:14 ` Bence Ferdinandy
2024-12-05 12:16 ` [PATCH v3 1/3] fetch set_head: move warn advice into advise_if_enabled Bence Ferdinandy
0 siblings, 2 replies; 258+ messages in thread
From: Kristoffer Haugsbakk @ 2024-12-04 20:44 UTC (permalink / raw)
To: Junio C Hamano
Cc: Bence Ferdinandy, git, Phillip Wood, René Scharfe,
Johannes Schindelin, Karthik Nayak, Taylor Blau,
Patrick Steinhardt
On Wed, Dec 4, 2024, at 21:40, Junio C Hamano wrote:
> "Kristoffer Haugsbakk" <kristofferhaugsbakk@fastmail.com> writes:
>
>> On Wed, Dec 4, 2024, at 11:39, Bence Ferdinandy wrote:
>>> diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
>>> index 7411aa770d..daf70406be 100755
>>> --- a/t/t5505-remote.sh
>>> +++ b/t/t5505-remote.sh
>>> @@ -504,6 +504,17 @@ test_expect_success 'set-head --auto has no
>>> problem w/multiple HEADs' '
>>> )
>>> '
>>>
>>> +test_expect_success 'set-head changes followRemoteHEAD always to warn' '
>>> + (
>>> + cd test &&
>>
>> I think you need to `cd` in a subshell here. See `t/README`, “Don't
>> chdir around in tests.”.
>
> Puzzled. Isn't this inside a (subshell) already?
Aha, then I didn’t read the context properly.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v2 3/3] remote set-head: set followRemoteHEAD to "warn" if "always"
2024-12-04 20:44 ` Kristoffer Haugsbakk
@ 2024-12-05 8:14 ` Bence Ferdinandy
2024-12-05 12:16 ` [PATCH v3 1/3] fetch set_head: move warn advice into advise_if_enabled Bence Ferdinandy
1 sibling, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-12-05 8:14 UTC (permalink / raw)
To: Kristoffer Haugsbakk, Junio C Hamano
Cc: git, Phillip Wood, René Scharfe, Johannes Schindelin,
Karthik Nayak, Taylor Blau, Patrick Steinhardt
On Wed Dec 04, 2024 at 21:44, Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com> wrote:
> On Wed, Dec 4, 2024, at 21:40, Junio C Hamano wrote:
>> "Kristoffer Haugsbakk" <kristofferhaugsbakk@fastmail.com> writes:
>>
>>> On Wed, Dec 4, 2024, at 11:39, Bence Ferdinandy wrote:
>>>> diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
>>>> index 7411aa770d..daf70406be 100755
>>>> --- a/t/t5505-remote.sh
>>>> +++ b/t/t5505-remote.sh
>>>> @@ -504,6 +504,17 @@ test_expect_success 'set-head --auto has no
>>>> problem w/multiple HEADs' '
>>>> )
>>>> '
>>>>
>>>> +test_expect_success 'set-head changes followRemoteHEAD always to warn' '
>>>> + (
>>>> + cd test &&
>>>
>>> I think you need to `cd` in a subshell here. See `t/README`, “Don't
>>> chdir around in tests.”.
>>
>> Puzzled. Isn't this inside a (subshell) already?
>
> Aha, then I didn’t read the context properly.
Thanks for the review! What I gathered is that v3 needs an s/output/actual and
we're good. I'll send that soonish.
Thanks,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v3 1/3] fetch set_head: move warn advice into advise_if_enabled
2024-12-04 20:44 ` Kristoffer Haugsbakk
2024-12-05 8:14 ` Bence Ferdinandy
@ 2024-12-05 12:16 ` Bence Ferdinandy
2024-12-05 12:16 ` [PATCH v3 2/3] fetch set_head: add warn-if-not-$branch option Bence Ferdinandy
2024-12-05 12:16 ` [PATCH v3 3/3] remote set-head: set followRemoteHEAD to "warn" if "always" Bence Ferdinandy
1 sibling, 2 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-12-05 12:16 UTC (permalink / raw)
To: git
Cc: Bence Ferdinandy, Phillip Wood, René Scharfe,
Johannes Schindelin, Karthik Nayak, Taylor Blau,
Patrick Steinhardt
Advice about what to do when getting a warning is typed out explicitly
twice and is printed as regular output. The output is also tested for.
Extract the advice message into a single place and use a wrapper
function, so if later the advice is made more chatty the signature only
needs to be changed in once place. Remove the testing for the advice
output in the tests.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v2: new patch, with the refactor moved out from the following one
advice.c | 1 +
advice.h | 1 +
builtin/fetch.c | 17 +++++++++++++----
t/t5510-fetch.sh | 2 --
4 files changed, 15 insertions(+), 6 deletions(-)
diff --git a/advice.c b/advice.c
index 6b879d805c..66461fdce9 100644
--- a/advice.c
+++ b/advice.c
@@ -53,6 +53,7 @@ static struct {
[ADVICE_COMMIT_BEFORE_MERGE] = { "commitBeforeMerge" },
[ADVICE_DETACHED_HEAD] = { "detachedHead" },
[ADVICE_DIVERGING] = { "diverging" },
+ [ADVICE_FETCH_SET_HEAD_WARN] = { "fetchRemoteHEADWarn" },
[ADVICE_FETCH_SHOW_FORCED_UPDATES] = { "fetchShowForcedUpdates" },
[ADVICE_FORCE_DELETE_BRANCH] = { "forceDeleteBranch" },
[ADVICE_GRAFT_FILE_DEPRECATED] = { "graftFileDeprecated" },
diff --git a/advice.h b/advice.h
index d7466bc0ef..cf2284ec43 100644
--- a/advice.h
+++ b/advice.h
@@ -20,6 +20,7 @@ enum advice_type {
ADVICE_COMMIT_BEFORE_MERGE,
ADVICE_DETACHED_HEAD,
ADVICE_DIVERGING,
+ ADVICE_FETCH_SET_HEAD_WARN,
ADVICE_FETCH_SHOW_FORCED_UPDATES,
ADVICE_FORCE_DELETE_BRANCH,
ADVICE_GRAFT_FILE_DEPRECATED,
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 62769d1686..087beb4c92 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1579,6 +1579,17 @@ static const char *strip_refshead(const char *name){
return 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.");
+
+ advise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head),
+ remote, head_name, remote);
+}
+
static void report_set_head(const char *remote, const char *head_name,
struct strbuf *buf_prev, int updateres) {
struct strbuf buf_prefix = STRBUF_INIT;
@@ -1590,15 +1601,13 @@ static void report_set_head(const char *remote, const char *head_name,
if (prev_head && strcmp(prev_head, head_name)) {
printf("'HEAD' at '%s' is '%s', but we have '%s' locally.\n",
remote, head_name, prev_head);
- printf("Run 'git remote set-head %s %s' to follow the change.\n",
- remote, head_name);
+ set_head_advice_msg(remote, head_name);
}
else if (updateres && buf_prev->len) {
printf("'HEAD' at '%s' is '%s', "
"but we have a detached HEAD pointing to '%s' locally.\n",
remote, head_name, buf_prev->buf);
- printf("Run 'git remote set-head %s %s' to follow the change.\n",
- remote, head_name);
+ set_head_advice_msg(remote, head_name);
}
strbuf_release(&buf_prefix);
}
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index ccb74428bc..5d7ee1b550 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -123,7 +123,6 @@ test_expect_success "fetch test followRemoteHEAD warn no change" '
git fetch >output &&
echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
"but we have ${SQ}other${SQ} locally." >expect &&
- echo "Run ${SQ}git remote set-head origin main${SQ} to follow the change." >>expect &&
test_cmp expect output &&
head=$(git rev-parse refs/remotes/origin/HEAD) &&
branch=$(git rev-parse refs/remotes/origin/other) &&
@@ -160,7 +159,6 @@ test_expect_success "fetch test followRemoteHEAD warn detached" '
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 &&
- echo "Run ${SQ}git remote set-head origin main${SQ} to follow the change." >>expect &&
test_cmp expect output
)
'
--
2.47.1.398.g18e7475ebe
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v3 2/3] fetch set_head: add warn-if-not-$branch option
2024-12-05 12:16 ` [PATCH v3 1/3] fetch set_head: move warn advice into advise_if_enabled Bence Ferdinandy
@ 2024-12-05 12:16 ` Bence Ferdinandy
2025-01-05 11:42 ` Teng Long
2024-12-05 12:16 ` [PATCH v3 3/3] remote set-head: set followRemoteHEAD to "warn" if "always" Bence Ferdinandy
1 sibling, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-12-05 12:16 UTC (permalink / raw)
To: git
Cc: Bence Ferdinandy, Phillip Wood, René Scharfe,
Johannes Schindelin, Karthik Nayak, Taylor Blau,
Patrick Steinhardt
Currently if we want to have a remote/HEAD locally that is different
from the one on the remote, but we still want to get a warning if remote
changes HEAD, our only option is to have an indiscriminate warning with
"follow_remote_head" set to "warn". Add a new option
"warn-if-not-$branch", where $branch is a branch name we do not wish to
get a warning about. If the remote HEAD is $branch do not warn,
otherwise, behave as "warn".
E.g. let's assume, that our remote origin has HEAD
set to "master", but locally we have "git remote set-head origin seen".
Setting 'remote.origin.followRemoteHEAD = "warn"' will always print
a warning, even though the remote has not changed HEAD from "master".
Setting 'remote.origin.followRemoteHEAD = "warn-if-not-master" will
squelch the warning message, unless the remote changes HEAD from
"master". Note, that should the remote change HEAD to "seen" (which we
have locally), there will still be no warning.
Improve the advice message in report_set_head to also include silencing
the warning message with "warn-if-not-$branch".
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v2: reuse FOLLOW_REMOTE_WARN
add documentation
v3: s/output/actual in tests
Documentation/config/remote.txt | 8 ++++---
builtin/fetch.c | 16 +++++++++-----
remote.c | 13 +++++++++--
remote.h | 1 +
t/t5510-fetch.sh | 38 +++++++++++++++++++++++++++++++++
5 files changed, 66 insertions(+), 10 deletions(-)
diff --git a/Documentation/config/remote.txt b/Documentation/config/remote.txt
index 024f92befc..4118c219c1 100644
--- a/Documentation/config/remote.txt
+++ b/Documentation/config/remote.txt
@@ -106,10 +106,12 @@ remote.<name>.followRemoteHEAD::
How linkgit:git-fetch[1] should handle updates to `remotes/<name>/HEAD`.
The default value is "create", which will create `remotes/<name>/HEAD`
if it exists on the remote, but not locally, but will not touch an
- already existing local reference. Setting to "warn" will print
+ already existing local reference. Setting to "warn" will print
a message if the remote has a different value, than the local one and
- in case there is no local reference, it behaves like "create". Setting
- to "always" will silently update it to the value on the remote.
+ 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 to "always" will silently update it to the value on the remote.
Finally, setting it to "never" will never change or create the local
reference.
+
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 087beb4c92..b3f6793026 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1584,10 +1584,12 @@ 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.");
+ "if you do not want to see this message. Specifically running\n"
+ "'git config set remote.%s.followRemoteHEAD %s' will disable the warning\n"
+ "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, remote, remote, head_name);
}
static void report_set_head(const char *remote, const char *head_name,
@@ -1612,7 +1614,8 @@ 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, int follow_remote_head)
+static int set_head(const struct ref *remote_refs, int follow_remote_head,
+ const char *no_warn_branch)
{
int result = 0, create_only, is_bare, was_detached;
struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
@@ -1669,7 +1672,9 @@ static int set_head(const struct ref *remote_refs, int follow_remote_head)
result = 1;
goto cleanup;
}
- if (follow_remote_head == FOLLOW_REMOTE_WARN && verbosity >= 0)
+ if (verbosity >= 0 &&
+ follow_remote_head == FOLLOW_REMOTE_WARN &&
+ (!no_warn_branch || strcmp(no_warn_branch, head_name)))
report_set_head(remote, head_name, &b_local_head, was_detached);
cleanup:
@@ -1898,7 +1903,8 @@ static int do_fetch(struct transport *transport,
"you need to specify exactly one branch with the --set-upstream option"));
}
}
- if (set_head(remote_refs, transport->remote->follow_remote_head))
+ if (set_head(remote_refs, transport->remote->follow_remote_head,
+ transport->remote->no_warn_branch))
;
/*
* Way too many cases where this can go wrong
diff --git a/remote.c b/remote.c
index 0b18840d43..4ec5d3f47b 100644
--- a/remote.c
+++ b/remote.c
@@ -515,14 +515,23 @@ static int handle_config(const char *key, const char *value,
return parse_transport_option(key, value,
&remote->server_options);
} else if (!strcmp(subkey, "followremotehead")) {
+ const char *no_warn_branch;
if (!strcmp(value, "never"))
remote->follow_remote_head = FOLLOW_REMOTE_NEVER;
else if (!strcmp(value, "create"))
remote->follow_remote_head = FOLLOW_REMOTE_CREATE;
- else if (!strcmp(value, "warn"))
+ else if (!strcmp(value, "warn")) {
remote->follow_remote_head = FOLLOW_REMOTE_WARN;
- else if (!strcmp(value, "always"))
+ remote->no_warn_branch = NULL;
+ } else if (skip_prefix(value, "warn-if-not-", &no_warn_branch)) {
+ remote->follow_remote_head = FOLLOW_REMOTE_WARN;
+ remote->no_warn_branch = no_warn_branch;
+ } else if (!strcmp(value, "always")) {
remote->follow_remote_head = FOLLOW_REMOTE_ALWAYS;
+ } else {
+ warning(_("unrecognized followRemoteHEAD value '%s' ignored"),
+ value);
+ }
}
return 0;
}
diff --git a/remote.h b/remote.h
index 184b35653d..bda10dd5c8 100644
--- a/remote.h
+++ b/remote.h
@@ -116,6 +116,7 @@ struct remote {
struct string_list server_options;
enum follow_remote_head_settings follow_remote_head;
+ const char *no_warn_branch;
};
/**
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 5d7ee1b550..3885461ac1 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -181,6 +181,44 @@ test_expect_success "fetch test followRemoteHEAD warn quiet" '
)
'
+test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is same" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git config set remote.origin.followRemoteHEAD "warn-if-not-main" &&
+ actual=$(git fetch) &&
+ test "z" = "z$actual" &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is different" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git config set remote.origin.followRemoteHEAD "warn-if-not-some/different-branch" &&
+ git fetch >actual &&
+ echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
+ "but we have ${SQ}other${SQ} locally." >expect &&
+ test_cmp expect actual &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
+ )
+'
+
test_expect_success "fetch test followRemoteHEAD always" '
test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
(
--
2.47.1.398.g18e7475ebe
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v3 2/3] fetch set_head: add warn-if-not-$branch option
2024-12-05 12:16 ` [PATCH v3 2/3] fetch set_head: add warn-if-not-$branch option Bence Ferdinandy
@ 2025-01-05 11:42 ` Teng Long
2025-01-05 15:13 ` Bence Ferdinandy
2025-01-05 16:09 ` [PATCH] fetch: fix erroneous set_head advice message Bence Ferdinandy
0 siblings, 2 replies; 258+ messages in thread
From: Teng Long @ 2025-01-05 11:42 UTC (permalink / raw)
To: bence; +Cc: Johannes.Schindelin, git, karthik.188, l.s.r, me, phillip.wood,
ps
Bence Ferdinandy <bence@ferdinandy.com> wrote:
> @@ -1584,10 +1584,12 @@ 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.");
> + "if you do not want to see this message. Specifically running\n"
> + "'git config set remote.%s.followRemoteHEAD %s' will disable the warning\n"
> + "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, remote, remote, head_name);
>
Seems like the config is unsupported to be feed a variable named "head_name"?
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v3 2/3] fetch set_head: add warn-if-not-$branch option
2025-01-05 11:42 ` Teng Long
@ 2025-01-05 15:13 ` Bence Ferdinandy
2025-01-05 16:09 ` [PATCH] fetch: fix erroneous set_head advice message Bence Ferdinandy
1 sibling, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2025-01-05 15:13 UTC (permalink / raw)
To: Teng Long
Cc: Johannes.Schindelin, git, karthik.188, l.s.r, me, phillip.wood,
ps
On Sun Jan 05, 2025 at 12:42, Teng Long <dyroneteng@gmail.com> wrote:
>
> Bence Ferdinandy <bence@ferdinandy.com> wrote:
>
>> @@ -1584,10 +1584,12 @@ 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.");
>> + "if you do not want to see this message. Specifically running\n"
>> + "'git config set remote.%s.followRemoteHEAD %s' will disable the warning\n"
>> + "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, remote, remote, head_name);
>>
>
> Seems like the config is unsupported to be feed a variable named "head_name"?
>
> Thanks.
Ah, yes the warning seems to be off, it should read
git config set remote.%s.followRemoteHEAD warn-if-not-%s
thanks for catching this mistake!
Best,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH] fetch: fix erroneous set_head advice message
2025-01-05 11:42 ` Teng Long
2025-01-05 15:13 ` Bence Ferdinandy
@ 2025-01-05 16:09 ` Bence Ferdinandy
2025-01-06 14:50 ` Junio C Hamano
1 sibling, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2025-01-05 16:09 UTC (permalink / raw)
To: git
Cc: Johannes.Schindelin, karthik.188, l.s.r, me, phillip.wood, ps,
Teng Long, Bence Ferdinandy
9e2b7005be (fetch set_head: add warn-if-not-$branch option, 2024-12-05)
tried to expand the advice message for set_head with the new option, but
unfortunately did not manage to add the right incantation. Fix the
advice message with the correct usage of warn-if-not-$branch.
Reported-by: Teng Long <dyroneteng@gmail.com>
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
builtin/fetch.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 2d37a378ba..fe2b26c74a 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1588,8 +1588,8 @@ static void set_head_advice_msg(const char *remote, const char *head_name)
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 %s' will disable the warning\n"
- "until the remote changes HEAD to something else.");
+ "'git config set remote.%s.followRemoteHEAD warn-if-not-branch-%s'\n"
+ "will disable 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);
base-commit: 1b4e9a5f8b5f048972c21fe8acafe0404096f694
--
2.48.0.rc0.146.g306ab352f4
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH] fetch: fix erroneous set_head advice message
2025-01-05 16:09 ` [PATCH] fetch: fix erroneous set_head advice message Bence Ferdinandy
@ 2025-01-06 14:50 ` Junio C Hamano
0 siblings, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2025-01-06 14:50 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, Johannes.Schindelin, karthik.188, l.s.r, me, phillip.wood,
ps, Teng Long
Bence Ferdinandy <bence@ferdinandy.com> writes:
> 9e2b7005be (fetch set_head: add warn-if-not-$branch option, 2024-12-05)
> tried to expand the advice message for set_head with the new option, but
> unfortunately did not manage to add the right incantation. Fix the
> advice message with the correct usage of warn-if-not-$branch.
>
> Reported-by: Teng Long <dyroneteng@gmail.com>
> Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
> ---
> builtin/fetch.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/builtin/fetch.c b/builtin/fetch.c
> index 2d37a378ba..fe2b26c74a 100644
> --- a/builtin/fetch.c
> +++ b/builtin/fetch.c
> @@ -1588,8 +1588,8 @@ static void set_head_advice_msg(const char *remote, const char *head_name)
> 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 %s' will disable the warning\n"
> - "until the remote changes HEAD to something else.");
> + "'git config set remote.%s.followRemoteHEAD warn-if-not-branch-%s'\n"
> + "will disable 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);
Thanks, both. Will queue.
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v3 3/3] remote set-head: set followRemoteHEAD to "warn" if "always"
2024-12-05 12:16 ` [PATCH v3 1/3] fetch set_head: move warn advice into advise_if_enabled Bence Ferdinandy
2024-12-05 12:16 ` [PATCH v3 2/3] fetch set_head: add warn-if-not-$branch option Bence Ferdinandy
@ 2024-12-05 12:16 ` Bence Ferdinandy
1 sibling, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-12-05 12:16 UTC (permalink / raw)
To: git
Cc: Bence Ferdinandy, Phillip Wood, René Scharfe,
Johannes Schindelin, Karthik Nayak, Taylor Blau,
Patrick Steinhardt
When running "remote set-head" manually it is unlikely, that the user
would actually like to have "fetch" always update the remote/HEAD. On
the contrary, it is more likely, that the user would expect remote/HEAD
to stay the way they manually set it, and just forgot about having
"followRemoteHEAD" set to "always".
When "followRemoteHEAD" is set to "always" make running "remote
set-head" change the config to "warn".
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v3: s/output/actual
builtin/remote.c | 12 +++++++++++-
t/t5505-remote.sh | 11 +++++++++++
2 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index d5b81445f2..32d02ca4a0 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1438,6 +1438,7 @@ static int set_head(int argc, const char **argv, const char *prefix,
b_local_head = STRBUF_INIT;
char *head_name = NULL;
struct ref_store *refs = get_main_ref_store(the_repository);
+ struct remote *remote;
struct option options[] = {
OPT_BOOL('a', "auto", &opt_a,
@@ -1448,8 +1449,10 @@ static int set_head(int argc, const char **argv, const char *prefix,
};
argc = parse_options(argc, argv, prefix, options,
builtin_remote_sethead_usage, 0);
- if (argc)
+ if (argc) {
strbuf_addf(&b_head, "refs/remotes/%s/HEAD", argv[0]);
+ remote = remote_get(argv[0]);
+ }
if (!opt_a && !opt_d && argc == 2) {
head_name = xstrdup(argv[1]);
@@ -1488,6 +1491,13 @@ static int set_head(int argc, const char **argv, const char *prefix,
}
if (opt_a)
report_set_head_auto(argv[0], head_name, &b_local_head, was_detached);
+ if (remote->follow_remote_head == FOLLOW_REMOTE_ALWAYS) {
+ struct strbuf config_name = STRBUF_INIT;
+ strbuf_addf(&config_name,
+ "remote.%s.followremotehead", remote->name);
+ git_config_set(config_name.buf, "warn");
+ strbuf_release(&config_name);
+ }
cleanup:
free(head_name);
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 7411aa770d..519f7973e3 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -504,6 +504,17 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
)
'
+test_expect_success 'set-head changes followRemoteHEAD always to warn' '
+ (
+ cd test &&
+ git config set remote.origin.followRemoteHEAD "always" &&
+ git remote set-head --auto origin &&
+ git config get remote.origin.followRemoteHEAD >actual &&
+ echo "warn" >expect &&
+ test_cmp expect actual
+ )
+'
+
cat >test/expect <<\EOF
refs/remotes/origin/side2
EOF
--
2.47.1.398.g18e7475ebe
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v2 1/3] fetch set_head: move warn advice into advise_if_enabled
2024-12-04 10:39 ` [PATCH v2 1/3] fetch set_head: move warn advice into advise_if_enabled Bence Ferdinandy
2024-12-04 10:39 ` [PATCH v2 2/3] fetch set_head: add warn-if-not-$branch option Bence Ferdinandy
2024-12-04 10:39 ` [PATCH v2 3/3] remote set-head: set followRemoteHEAD to "warn" if "always" Bence Ferdinandy
@ 2024-12-04 19:28 ` Junio C Hamano
2 siblings, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-12-04 19:28 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
Bence Ferdinandy <bence@ferdinandy.com> writes:
> +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.");
> +
> + advise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head),
> + remote, head_name, remote);
> +}
> ...
> else if (updateres && buf_prev->len) {
> printf("'HEAD' at '%s' is '%s', "
> "but we have a detached HEAD pointing to '%s' locally.\n",
> remote, head_name, buf_prev->buf);
> - printf("Run 'git remote set-head %s %s' to follow the change.\n",
> - remote, head_name);
> + set_head_advice_msg(remote, head_name);
> }
> strbuf_release(&buf_prefix);
> }
Looking good.
> diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
> index ccb74428bc..5d7ee1b550 100755
> --- a/t/t5510-fetch.sh
> +++ b/t/t5510-fetch.sh
> @@ -123,7 +123,6 @@ test_expect_success "fetch test followRemoteHEAD warn no change" '
> git fetch >output &&
> echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
> "but we have ${SQ}other${SQ} locally." >expect &&
> - echo "Run ${SQ}git remote set-head origin main${SQ} to follow the change." >>expect &&
> test_cmp expect output &&
OK. We just lose this part of the test, not because the advice
messages are squelched but because they go to the standard error
stream. An obvious alternative is to capture it in a separate
file and grep, i.e.
git fetch >output 2>error &&
echo ... >expect &&
test_grep "Run ${SQ}git remote set-head" error &&
test_cmp expect output
If we were testing that the advice mechanism is used, do that with
and without the ADVICE_FETCH_SET_HEAD_WARN squelched, but probably
it is a bit too much, in the sense that we are not in the business
of testing that the advice mechanism works correctly in this series.
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v2] fetch: add configuration for set_head behaviour
2024-11-27 13:46 ` Junio C Hamano
2024-11-27 19:20 ` Bence Ferdinandy
@ 2024-11-28 11:19 ` Bence Ferdinandy
2024-11-28 23:06 ` [PATCH v3] " Bence Ferdinandy
2024-11-28 23:09 ` Bence Ferdinandy
1 sibling, 2 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-28 11:19 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin, karthik.188,
Taylor Blau, Patrick Steinhardt, Junio C Hamano, Bence Ferdinandy
In the current implementation, if refs/remotes/$remote/HEAD does not
exist, running fetch will create it, but if it does exist it will not do
anything, which is a somewhat safe and minimal approach. Unfortunately,
for users who wish to NOT have refs/remotes/$remote/HEAD set for any
reason (e.g. so that `git rev-parse origin` doesn't accidentally point
them somewhere they do not want to), there is no way to remove this
behaviour. On the other side of the spectrum, users may want fetch to
automatically update HEAD or at least give them a warning if something
changed on the remote.
Introduce a new setting, remote.$remote.followRemoteHEAD with four
options:
- "never": do not ever do anything, not even create
- "create": the current behaviour, now the default behaviour
- "warn": print a message if remote and local HEAD is different
- "always": silently update HEAD on every change
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
This patch is built off v15 of the set-head/fetch updates series:
https://lore.kernel.org/git/D5WEJJBMSO1K.2TPXDI1K08SHT@ferdinandy.com/T/#m13a0c3e8919872188ef07fd9fd984c78e8aa35ba
v2:
- magic numbers converted to enum
- since the enum is descriptive (well, along with the documentation)
I felt there is no need for the comment explaining the values
Documentation/config/remote.txt | 11 ++++
builtin/fetch.c | 46 ++++++++++++--
remote.c | 9 +++
remote.h | 9 +++
t/t5510-fetch.sh | 102 ++++++++++++++++++++++++++++++++
5 files changed, 171 insertions(+), 6 deletions(-)
diff --git a/Documentation/config/remote.txt b/Documentation/config/remote.txt
index 6d8b7d6c63..024f92befc 100644
--- a/Documentation/config/remote.txt
+++ b/Documentation/config/remote.txt
@@ -101,6 +101,17 @@ remote.<name>.serverOption::
The default set of server options used when fetching from this remote.
These server options can be overridden by the `--server-option=` command
line arguments.
+
+remote.<name>.followRemoteHEAD::
+ How linkgit:git-fetch[1] should handle updates to `remotes/<name>/HEAD`.
+ The default value is "create", which will create `remotes/<name>/HEAD`
+ if it exists on the remote, but not locally, but will not touch an
+ already existing local reference. Setting to "warn" will print
+ a message if the remote has a different value, than the local one and
+ in case there is no local reference, it behaves like "create". Setting
+ to "always" will silently update it to the value on the remote.
+ Finally, setting it to "never" will never change or create the local
+ reference.
+
This is a multi-valued variable, and an empty value can be used in a higher
priority configuration file (e.g. `.git/config` in a repository) to clear
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 2f416cf867..d74b20b49c 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1579,10 +1579,35 @@ static const char *strip_refshead(const char *name){
return name;
}
-static int set_head(const struct ref *remote_refs)
+static void report_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;
+
+ strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
+ skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);
+
+ if (prev_head && strcmp(prev_head, head_name)) {
+ printf("'HEAD' at '%s' is '%s', but we have '%s' locally.\n",
+ remote, head_name, prev_head);
+ printf("Run 'git remote set-head %s %s' to follow the change.\n",
+ remote, head_name);
+ }
+ else if (updateres && buf_prev->len) {
+ printf("'HEAD' at '%s' is '%s', "
+ "but we have a detached HEAD pointing to '%s' locally.\n",
+ remote, head_name, buf_prev->buf);
+ printf("Run 'git remote set-head %s %s' to follow the change.\n",
+ remote, head_name);
+ }
+ strbuf_release(&buf_prefix);
+}
+
+static int set_head(const struct ref *remote_refs, int follow_remote_head)
{
- int result = 0, is_bare;
- struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
+ int result = 0, create_only, is_bare, was_detached;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
+ b_local_head = STRBUF_INIT;
const char *remote = gtransport->remote->name;
char *head_name = NULL;
struct ref *ref, *matches;
@@ -1603,6 +1628,8 @@ static int set_head(const struct ref *remote_refs)
string_list_append(&heads, strip_refshead(ref->name));
}
+ if (follow_remote_head == FOLLOW_REMOTE_NEVER)
+ goto cleanup;
if (!heads.nr)
result = 1;
@@ -1614,6 +1641,7 @@ static int set_head(const struct ref *remote_refs)
if (!head_name)
goto cleanup;
is_bare = is_bare_repository();
+ create_only = follow_remote_head == 2 ? 0 : !is_bare;
if (is_bare) {
strbuf_addstr(&b_head, "HEAD");
strbuf_addf(&b_remote_head, "refs/heads/%s", head_name);
@@ -1626,9 +1654,14 @@ static int set_head(const struct ref *remote_refs)
result = 1;
goto cleanup;
}
- if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
- "fetch", NULL, !is_bare))
+ was_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
+ "fetch", &b_local_head, create_only);
+ if (was_detached == -1) {
result = 1;
+ goto cleanup;
+ }
+ if (follow_remote_head == FOLLOW_REMOTE_WARN && verbosity >= 0)
+ report_set_head(remote, head_name, &b_local_head, was_detached);
cleanup:
free(head_name);
@@ -1636,6 +1669,7 @@ static int set_head(const struct ref *remote_refs)
free_refs(matches);
string_list_clear(&heads, 0);
strbuf_release(&b_head);
+ strbuf_release(&b_local_head);
strbuf_release(&b_remote_head);
return result;
}
@@ -1855,7 +1889,7 @@ static int do_fetch(struct transport *transport,
"you need to specify exactly one branch with the --set-upstream option"));
}
}
- if (set_head(remote_refs))
+ if (set_head(remote_refs, transport->remote->follow_remote_head))
;
/*
* Way too many cases where this can go wrong
diff --git a/remote.c b/remote.c
index 10104d11e3..0b18840d43 100644
--- a/remote.c
+++ b/remote.c
@@ -514,6 +514,15 @@ static int handle_config(const char *key, const char *value,
} else if (!strcmp(subkey, "serveroption")) {
return parse_transport_option(key, value,
&remote->server_options);
+ } else if (!strcmp(subkey, "followremotehead")) {
+ if (!strcmp(value, "never"))
+ remote->follow_remote_head = FOLLOW_REMOTE_NEVER;
+ else if (!strcmp(value, "create"))
+ remote->follow_remote_head = FOLLOW_REMOTE_CREATE;
+ else if (!strcmp(value, "warn"))
+ remote->follow_remote_head = FOLLOW_REMOTE_WARN;
+ else if (!strcmp(value, "always"))
+ remote->follow_remote_head = FOLLOW_REMOTE_ALWAYS;
}
return 0;
}
diff --git a/remote.h b/remote.h
index a7e5c4e07c..184b35653d 100644
--- a/remote.h
+++ b/remote.h
@@ -59,6 +59,13 @@ 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,
+ };
+
struct remote {
struct hashmap_entry ent;
@@ -107,6 +114,8 @@ struct remote {
char *http_proxy_authmethod;
struct string_list server_options;
+
+ enum follow_remote_head_settings follow_remote_head;
};
/**
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 87698341f5..2467027d34 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -99,6 +99,108 @@ test_expect_success "fetch test remote HEAD change" '
branch=$(git rev-parse refs/remotes/origin/other) &&
test "z$head" = "z$branch"'
+test_expect_success "fetch test followRemoteHEAD never" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ git config set remote.origin.followRemoteHEAD "never" &&
+ git fetch &&
+ test_must_fail git rev-parse --verify refs/remotes/origin/HEAD
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD warn no change" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git config set remote.origin.followRemoteHEAD "warn" &&
+ git fetch >output &&
+ echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
+ "but we have ${SQ}other${SQ} locally." >expect &&
+ echo "Run ${SQ}git remote set-head origin main${SQ} to follow the change." >>expect &&
+ test_cmp expect output &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD warn create" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ git config set remote.origin.followRemoteHEAD "warn" &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ output=$(git fetch) &&
+ test "z" = "z$output" &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD warn detached" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ git update-ref refs/remotes/origin/HEAD HEAD &&
+ HEAD=$(git log --pretty="%H") &&
+ git config set remote.origin.followRemoteHEAD "warn" &&
+ git 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 &&
+ echo "Run ${SQ}git remote set-head origin main${SQ} to follow the change." >>expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD warn quiet" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git config set remote.origin.followRemoteHEAD "warn" &&
+ output=$(git fetch --quiet) &&
+ test "z" = "z$output" &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD always" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git config set remote.origin.followRemoteHEAD "always" &&
+ git fetch &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"
+ )
+'
+
test_expect_success 'fetch --prune on its own works as expected' '
cd "$D" &&
git clone . prune &&
--
2.47.1.295.g12e80f91dc.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v3] fetch: add configuration for set_head behaviour
2024-11-28 11:19 ` [PATCH v2] fetch: add configuration for set_head behaviour Bence Ferdinandy
@ 2024-11-28 23:06 ` Bence Ferdinandy
2024-12-02 0:58 ` Junio C Hamano
2024-11-28 23:09 ` Bence Ferdinandy
1 sibling, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-28 23:06 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin, karthik.188,
Taylor Blau, Patrick Steinhardt, Junio C Hamano, Bence Ferdinandy
In the current implementation, if refs/remotes/$remote/HEAD does not
exist, running fetch will create it, but if it does exist it will not do
anything, which is a somewhat safe and minimal approach. Unfortunately,
for users who wish to NOT have refs/remotes/$remote/HEAD set for any
reason (e.g. so that `git rev-parse origin` doesn't accidentally point
them somewhere they do not want to), there is no way to remove this
behaviour. On the other side of the spectrum, users may want fetch to
automatically update HEAD or at least give them a warning if something
changed on the remote.
Introduce a new setting, remote.$remote.followRemoteHEAD with four
options:
- "never": do not ever do anything, not even create
- "create": the current behaviour, now the default behaviour
- "warn": print a message if remote and local HEAD is different
- "always": silently update HEAD on every change
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
This patch is built off v15 of the set-head/fetch updates series:
https://lore.kernel.org/git/D5WEJJBMSO1K.2TPXDI1K08SHT@ferdinandy.com/T/#m13a0c3e8919872188ef07fd9fd984c78e8aa35ba
v2:
- magic numbers converted to enum
- since the enum is descriptive (well, along with the documentation)
I felt there is no need for the comment explaining the values
v3: missed a magic number in v2
Documentation/config/remote.txt | 11 ++++
builtin/fetch.c | 46 ++++++++++++--
remote.c | 9 +++
remote.h | 9 +++
t/t5510-fetch.sh | 102 ++++++++++++++++++++++++++++++++
5 files changed, 171 insertions(+), 6 deletions(-)
diff --git a/Documentation/config/remote.txt b/Documentation/config/remote.txt
index 6d8b7d6c63..024f92befc 100644
--- a/Documentation/config/remote.txt
+++ b/Documentation/config/remote.txt
@@ -101,6 +101,17 @@ remote.<name>.serverOption::
The default set of server options used when fetching from this remote.
These server options can be overridden by the `--server-option=` command
line arguments.
+
+remote.<name>.followRemoteHEAD::
+ How linkgit:git-fetch[1] should handle updates to `remotes/<name>/HEAD`.
+ The default value is "create", which will create `remotes/<name>/HEAD`
+ if it exists on the remote, but not locally, but will not touch an
+ already existing local reference. Setting to "warn" will print
+ a message if the remote has a different value, than the local one and
+ in case there is no local reference, it behaves like "create". Setting
+ to "always" will silently update it to the value on the remote.
+ Finally, setting it to "never" will never change or create the local
+ reference.
+
This is a multi-valued variable, and an empty value can be used in a higher
priority configuration file (e.g. `.git/config` in a repository) to clear
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 2f416cf867..88c5c5d781 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1579,10 +1579,35 @@ static const char *strip_refshead(const char *name){
return name;
}
-static int set_head(const struct ref *remote_refs)
+static void report_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;
+
+ strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
+ skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);
+
+ if (prev_head && strcmp(prev_head, head_name)) {
+ printf("'HEAD' at '%s' is '%s', but we have '%s' locally.\n",
+ remote, head_name, prev_head);
+ printf("Run 'git remote set-head %s %s' to follow the change.\n",
+ remote, head_name);
+ }
+ else if (updateres && buf_prev->len) {
+ printf("'HEAD' at '%s' is '%s', "
+ "but we have a detached HEAD pointing to '%s' locally.\n",
+ remote, head_name, buf_prev->buf);
+ printf("Run 'git remote set-head %s %s' to follow the change.\n",
+ remote, head_name);
+ }
+ strbuf_release(&buf_prefix);
+}
+
+static int set_head(const struct ref *remote_refs, int follow_remote_head)
{
- int result = 0, is_bare;
- struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
+ int result = 0, create_only, is_bare, was_detached;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
+ b_local_head = STRBUF_INIT;
const char *remote = gtransport->remote->name;
char *head_name = NULL;
struct ref *ref, *matches;
@@ -1603,6 +1628,8 @@ static int set_head(const struct ref *remote_refs)
string_list_append(&heads, strip_refshead(ref->name));
}
+ if (follow_remote_head == FOLLOW_REMOTE_NEVER)
+ goto cleanup;
if (!heads.nr)
result = 1;
@@ -1614,6 +1641,7 @@ static int set_head(const struct ref *remote_refs)
if (!head_name)
goto cleanup;
is_bare = is_bare_repository();
+ create_only = follow_remote_head == FOLLOW_REMOTE_ALWAYS ? 0 : !is_bare;
if (is_bare) {
strbuf_addstr(&b_head, "HEAD");
strbuf_addf(&b_remote_head, "refs/heads/%s", head_name);
@@ -1626,9 +1654,14 @@ static int set_head(const struct ref *remote_refs)
result = 1;
goto cleanup;
}
- if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
- "fetch", NULL, !is_bare))
+ was_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
+ "fetch", &b_local_head, create_only);
+ if (was_detached == -1) {
result = 1;
+ goto cleanup;
+ }
+ if (follow_remote_head == FOLLOW_REMOTE_WARN && verbosity >= 0)
+ report_set_head(remote, head_name, &b_local_head, was_detached);
cleanup:
free(head_name);
@@ -1636,6 +1669,7 @@ static int set_head(const struct ref *remote_refs)
free_refs(matches);
string_list_clear(&heads, 0);
strbuf_release(&b_head);
+ strbuf_release(&b_local_head);
strbuf_release(&b_remote_head);
return result;
}
@@ -1855,7 +1889,7 @@ static int do_fetch(struct transport *transport,
"you need to specify exactly one branch with the --set-upstream option"));
}
}
- if (set_head(remote_refs))
+ if (set_head(remote_refs, transport->remote->follow_remote_head))
;
/*
* Way too many cases where this can go wrong
diff --git a/remote.c b/remote.c
index 10104d11e3..0b18840d43 100644
--- a/remote.c
+++ b/remote.c
@@ -514,6 +514,15 @@ static int handle_config(const char *key, const char *value,
} else if (!strcmp(subkey, "serveroption")) {
return parse_transport_option(key, value,
&remote->server_options);
+ } else if (!strcmp(subkey, "followremotehead")) {
+ if (!strcmp(value, "never"))
+ remote->follow_remote_head = FOLLOW_REMOTE_NEVER;
+ else if (!strcmp(value, "create"))
+ remote->follow_remote_head = FOLLOW_REMOTE_CREATE;
+ else if (!strcmp(value, "warn"))
+ remote->follow_remote_head = FOLLOW_REMOTE_WARN;
+ else if (!strcmp(value, "always"))
+ remote->follow_remote_head = FOLLOW_REMOTE_ALWAYS;
}
return 0;
}
diff --git a/remote.h b/remote.h
index a7e5c4e07c..184b35653d 100644
--- a/remote.h
+++ b/remote.h
@@ -59,6 +59,13 @@ 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,
+ };
+
struct remote {
struct hashmap_entry ent;
@@ -107,6 +114,8 @@ struct remote {
char *http_proxy_authmethod;
struct string_list server_options;
+
+ enum follow_remote_head_settings follow_remote_head;
};
/**
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 87698341f5..2467027d34 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -99,6 +99,108 @@ test_expect_success "fetch test remote HEAD change" '
branch=$(git rev-parse refs/remotes/origin/other) &&
test "z$head" = "z$branch"'
+test_expect_success "fetch test followRemoteHEAD never" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ git config set remote.origin.followRemoteHEAD "never" &&
+ git fetch &&
+ test_must_fail git rev-parse --verify refs/remotes/origin/HEAD
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD warn no change" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git config set remote.origin.followRemoteHEAD "warn" &&
+ git fetch >output &&
+ echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
+ "but we have ${SQ}other${SQ} locally." >expect &&
+ echo "Run ${SQ}git remote set-head origin main${SQ} to follow the change." >>expect &&
+ test_cmp expect output &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD warn create" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ git config set remote.origin.followRemoteHEAD "warn" &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ output=$(git fetch) &&
+ test "z" = "z$output" &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD warn detached" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ git update-ref refs/remotes/origin/HEAD HEAD &&
+ HEAD=$(git log --pretty="%H") &&
+ git config set remote.origin.followRemoteHEAD "warn" &&
+ git 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 &&
+ echo "Run ${SQ}git remote set-head origin main${SQ} to follow the change." >>expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD warn quiet" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git config set remote.origin.followRemoteHEAD "warn" &&
+ output=$(git fetch --quiet) &&
+ test "z" = "z$output" &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD always" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git config set remote.origin.followRemoteHEAD "always" &&
+ git fetch &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"
+ )
+'
+
test_expect_success 'fetch --prune on its own works as expected' '
cd "$D" &&
git clone . prune &&
Range-diff against v2:
1: 60986e890e ! 1: 4e9ac5524a fetch: add configuration for set_head behaviour
@@ Notes
- since the enum is descriptive (well, along with the documentation)
I felt there is no need for the comment explaining the values
+ v3: missed a magic number in v2
+
## Documentation/config/remote.txt ##
@@ Documentation/config/remote.txt: remote.<name>.serverOption::
The default set of server options used when fetching from this remote.
@@ builtin/fetch.c: static int set_head(const struct ref *remote_refs)
if (!head_name)
goto cleanup;
is_bare = is_bare_repository();
-+ create_only = follow_remote_head == 2 ? 0 : !is_bare;
++ create_only = follow_remote_head == FOLLOW_REMOTE_ALWAYS ? 0 : !is_bare;
if (is_bare) {
strbuf_addstr(&b_head, "HEAD");
strbuf_addf(&b_remote_head, "refs/heads/%s", head_name);
--
2.47.1.295.g66bad26ef4
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v3] fetch: add configuration for set_head behaviour
2024-11-28 23:06 ` [PATCH v3] " Bence Ferdinandy
@ 2024-12-02 0:58 ` Junio C Hamano
0 siblings, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-12-02 0:58 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, René Scharfe, Johannes Schindelin,
karthik.188, Taylor Blau, Patrick Steinhardt
Bence Ferdinandy <bence@ferdinandy.com> writes:
> v2:
> - magic numbers converted to enum
> - since the enum is descriptive (well, along with the documentation)
> I felt there is no need for the comment explaining the values
>
> v3: missed a magic number in v2
Both changes look quite good. Will queue.
Let me mark the topic for 'next'.
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v3] fetch: add configuration for set_head behaviour
2024-11-28 11:19 ` [PATCH v2] fetch: add configuration for set_head behaviour Bence Ferdinandy
2024-11-28 23:06 ` [PATCH v3] " Bence Ferdinandy
@ 2024-11-28 23:09 ` Bence Ferdinandy
1 sibling, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-11-28 23:09 UTC (permalink / raw)
To: git
Cc: phillip.wood, René Scharfe, Johannes Schindelin, karthik.188,
Taylor Blau, Patrick Steinhardt, Junio C Hamano, Bence Ferdinandy
In the current implementation, if refs/remotes/$remote/HEAD does not
exist, running fetch will create it, but if it does exist it will not do
anything, which is a somewhat safe and minimal approach. Unfortunately,
for users who wish to NOT have refs/remotes/$remote/HEAD set for any
reason (e.g. so that `git rev-parse origin` doesn't accidentally point
them somewhere they do not want to), there is no way to remove this
behaviour. On the other side of the spectrum, users may want fetch to
automatically update HEAD or at least give them a warning if something
changed on the remote.
Introduce a new setting, remote.$remote.followRemoteHEAD with four
options:
- "never": do not ever do anything, not even create
- "create": the current behaviour, now the default behaviour
- "warn": print a message if remote and local HEAD is different
- "always": silently update HEAD on every change
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
This patch is built off v15 of the set-head/fetch updates series:
https://lore.kernel.org/git/D5WEJJBMSO1K.2TPXDI1K08SHT@ferdinandy.com/T/#m13a0c3e8919872188ef07fd9fd984c78e8aa35ba
v2:
- magic numbers converted to enum
- since the enum is descriptive (well, along with the documentation)
I felt there is no need for the comment explaining the values
v3: missed a magic number in v2
Documentation/config/remote.txt | 11 ++++
builtin/fetch.c | 46 ++++++++++++--
remote.c | 9 +++
remote.h | 9 +++
t/t5510-fetch.sh | 102 ++++++++++++++++++++++++++++++++
5 files changed, 171 insertions(+), 6 deletions(-)
diff --git a/Documentation/config/remote.txt b/Documentation/config/remote.txt
index 6d8b7d6c63..024f92befc 100644
--- a/Documentation/config/remote.txt
+++ b/Documentation/config/remote.txt
@@ -101,6 +101,17 @@ remote.<name>.serverOption::
The default set of server options used when fetching from this remote.
These server options can be overridden by the `--server-option=` command
line arguments.
+
+remote.<name>.followRemoteHEAD::
+ How linkgit:git-fetch[1] should handle updates to `remotes/<name>/HEAD`.
+ The default value is "create", which will create `remotes/<name>/HEAD`
+ if it exists on the remote, but not locally, but will not touch an
+ already existing local reference. Setting to "warn" will print
+ a message if the remote has a different value, than the local one and
+ in case there is no local reference, it behaves like "create". Setting
+ to "always" will silently update it to the value on the remote.
+ Finally, setting it to "never" will never change or create the local
+ reference.
+
This is a multi-valued variable, and an empty value can be used in a higher
priority configuration file (e.g. `.git/config` in a repository) to clear
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 2f416cf867..88c5c5d781 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1579,10 +1579,35 @@ static const char *strip_refshead(const char *name){
return name;
}
-static int set_head(const struct ref *remote_refs)
+static void report_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;
+
+ strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
+ skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);
+
+ if (prev_head && strcmp(prev_head, head_name)) {
+ printf("'HEAD' at '%s' is '%s', but we have '%s' locally.\n",
+ remote, head_name, prev_head);
+ printf("Run 'git remote set-head %s %s' to follow the change.\n",
+ remote, head_name);
+ }
+ else if (updateres && buf_prev->len) {
+ printf("'HEAD' at '%s' is '%s', "
+ "but we have a detached HEAD pointing to '%s' locally.\n",
+ remote, head_name, buf_prev->buf);
+ printf("Run 'git remote set-head %s %s' to follow the change.\n",
+ remote, head_name);
+ }
+ strbuf_release(&buf_prefix);
+}
+
+static int set_head(const struct ref *remote_refs, int follow_remote_head)
{
- int result = 0, is_bare;
- struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
+ int result = 0, create_only, is_bare, was_detached;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
+ b_local_head = STRBUF_INIT;
const char *remote = gtransport->remote->name;
char *head_name = NULL;
struct ref *ref, *matches;
@@ -1603,6 +1628,8 @@ static int set_head(const struct ref *remote_refs)
string_list_append(&heads, strip_refshead(ref->name));
}
+ if (follow_remote_head == FOLLOW_REMOTE_NEVER)
+ goto cleanup;
if (!heads.nr)
result = 1;
@@ -1614,6 +1641,7 @@ static int set_head(const struct ref *remote_refs)
if (!head_name)
goto cleanup;
is_bare = is_bare_repository();
+ create_only = follow_remote_head == FOLLOW_REMOTE_ALWAYS ? 0 : !is_bare;
if (is_bare) {
strbuf_addstr(&b_head, "HEAD");
strbuf_addf(&b_remote_head, "refs/heads/%s", head_name);
@@ -1626,9 +1654,14 @@ static int set_head(const struct ref *remote_refs)
result = 1;
goto cleanup;
}
- if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
- "fetch", NULL, !is_bare))
+ was_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
+ "fetch", &b_local_head, create_only);
+ if (was_detached == -1) {
result = 1;
+ goto cleanup;
+ }
+ if (follow_remote_head == FOLLOW_REMOTE_WARN && verbosity >= 0)
+ report_set_head(remote, head_name, &b_local_head, was_detached);
cleanup:
free(head_name);
@@ -1636,6 +1669,7 @@ static int set_head(const struct ref *remote_refs)
free_refs(matches);
string_list_clear(&heads, 0);
strbuf_release(&b_head);
+ strbuf_release(&b_local_head);
strbuf_release(&b_remote_head);
return result;
}
@@ -1855,7 +1889,7 @@ static int do_fetch(struct transport *transport,
"you need to specify exactly one branch with the --set-upstream option"));
}
}
- if (set_head(remote_refs))
+ if (set_head(remote_refs, transport->remote->follow_remote_head))
;
/*
* Way too many cases where this can go wrong
diff --git a/remote.c b/remote.c
index 10104d11e3..0b18840d43 100644
--- a/remote.c
+++ b/remote.c
@@ -514,6 +514,15 @@ static int handle_config(const char *key, const char *value,
} else if (!strcmp(subkey, "serveroption")) {
return parse_transport_option(key, value,
&remote->server_options);
+ } else if (!strcmp(subkey, "followremotehead")) {
+ if (!strcmp(value, "never"))
+ remote->follow_remote_head = FOLLOW_REMOTE_NEVER;
+ else if (!strcmp(value, "create"))
+ remote->follow_remote_head = FOLLOW_REMOTE_CREATE;
+ else if (!strcmp(value, "warn"))
+ remote->follow_remote_head = FOLLOW_REMOTE_WARN;
+ else if (!strcmp(value, "always"))
+ remote->follow_remote_head = FOLLOW_REMOTE_ALWAYS;
}
return 0;
}
diff --git a/remote.h b/remote.h
index a7e5c4e07c..184b35653d 100644
--- a/remote.h
+++ b/remote.h
@@ -59,6 +59,13 @@ 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,
+ };
+
struct remote {
struct hashmap_entry ent;
@@ -107,6 +114,8 @@ struct remote {
char *http_proxy_authmethod;
struct string_list server_options;
+
+ enum follow_remote_head_settings follow_remote_head;
};
/**
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 87698341f5..2467027d34 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -99,6 +99,108 @@ test_expect_success "fetch test remote HEAD change" '
branch=$(git rev-parse refs/remotes/origin/other) &&
test "z$head" = "z$branch"'
+test_expect_success "fetch test followRemoteHEAD never" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ git config set remote.origin.followRemoteHEAD "never" &&
+ git fetch &&
+ test_must_fail git rev-parse --verify refs/remotes/origin/HEAD
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD warn no change" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git config set remote.origin.followRemoteHEAD "warn" &&
+ git fetch >output &&
+ echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
+ "but we have ${SQ}other${SQ} locally." >expect &&
+ echo "Run ${SQ}git remote set-head origin main${SQ} to follow the change." >>expect &&
+ test_cmp expect output &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD warn create" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ git config set remote.origin.followRemoteHEAD "warn" &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ output=$(git fetch) &&
+ test "z" = "z$output" &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD warn detached" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ git update-ref refs/remotes/origin/HEAD HEAD &&
+ HEAD=$(git log --pretty="%H") &&
+ git config set remote.origin.followRemoteHEAD "warn" &&
+ git 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 &&
+ echo "Run ${SQ}git remote set-head origin main${SQ} to follow the change." >>expect &&
+ test_cmp expect output
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD warn quiet" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git config set remote.origin.followRemoteHEAD "warn" &&
+ output=$(git fetch --quiet) &&
+ test "z" = "z$output" &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
+ )
+'
+
+test_expect_success "fetch test followRemoteHEAD always" '
+ test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
+ (
+ cd "$D" &&
+ cd two &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ git config set remote.origin.followRemoteHEAD "always" &&
+ git fetch &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"
+ )
+'
+
test_expect_success 'fetch --prune on its own works as expected' '
cd "$D" &&
git clone . prune &&
Range-diff against v2:
1: 60986e890e ! 1: 4e9ac5524a fetch: add configuration for set_head behaviour
@@ Notes
- since the enum is descriptive (well, along with the documentation)
I felt there is no need for the comment explaining the values
+ v3: missed a magic number in v2
+
## Documentation/config/remote.txt ##
@@ Documentation/config/remote.txt: remote.<name>.serverOption::
The default set of server options used when fetching from this remote.
@@ builtin/fetch.c: static int set_head(const struct ref *remote_refs)
if (!head_name)
goto cleanup;
is_bare = is_bare_repository();
-+ create_only = follow_remote_head == 2 ? 0 : !is_bare;
++ create_only = follow_remote_head == FOLLOW_REMOTE_ALWAYS ? 0 : !is_bare;
if (is_bare) {
strbuf_addstr(&b_head, "HEAD");
strbuf_addf(&b_remote_head, "refs/heads/%s", head_name);
--
2.47.1.295.g66bad26ef4
^ permalink raw reply related [flat|nested] 258+ messages in thread