* [RFC PATCH v2 0/2] set remote/HEAD with fetch
@ 2024-09-10 20:37 Bence Ferdinandy
2024-09-10 20:37 ` [RFC PATCH v2 1/2] fetch: set-head with --set-head option Bence Ferdinandy
` (2 more replies)
0 siblings, 3 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-09-10 20:37 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Taylor Blau, Patrick Steinhardt,
René Scharfe, Johannes Schindelin, Bence Ferdinandy
Sorry for the noise, I managed to badly mess up receipients in the previous
thread so v2 is just fixing that ...
Hi,
these two patches attempt to solve the issue raised in
https://lore.kernel.org/git/D3HBD7C1FR14.74FL1Q1S9UCB@ferdinandy.com/
As a quick summary: `clone` sets `refs/remotes/[remote]/HEAD` while going init
-> remote add -> fetch does not, one has to manually run `remote set-head -a
[remote]`.
The first patch adds a `--set-head` flag to `fetch` and `remote update` which
runs `remote set-head -a` for us. Unfortunately, with the current behaviour of
set-head this will always print a message, even though a no-op fetch doesn't
print anything, and I think this is also confusing for people who do not care
about remote/HEAD, so the second patch removes the print if `set-head -a` is
a no-op (and actually makes it into a no-op, instead of just idempotent).
Another way could of course be duplicating some of the code from remote
set-head in fetch.c instead of calling directly, but it didn't look like an
anti-pattern in the code-base and it felt the best way to insure identical
behaviour between a `git fetch --all --set-head` and a
`git fetch --all && git remote | xargs -i git remote set-head -a {}`.
What is missing for sure is:
- documentation
- tests (if needed)
- settings
For settings, my idea would be a fetch/remote.set_head that could take three values:
* never
* missing: run it only if the ref is missing, this setting would basically
allow replicating the result of a clone
* always (with the other patch, this would still be a no-op if it didn't change)
This would probably also require a --no-set-head flag, to disable an
always/missing setting. A --missing-set-head or something of the like also may
or may not make sense. Alternatively, only two behaviours might be enough
(missing and always) since clone already sort of does this.
I'm not sure if the general approach is fine or not, nor am I sure the code
itself is any good, but it "works on my computer" :) I'm also hoping that
I managed to read all the relevant parts for sending a patch.
Feedback would be highly appreciated!
Thanks,
Bence
Bence Ferdinandy (2):
fetch: set-head with --set-head option
set-head: do not update if there is no change
builtin/fetch.c | 29 ++++++++++++++++++++++++-----
builtin/remote.c | 15 +++++++++++----
2 files changed, 35 insertions(+), 9 deletions(-)
--
2.46.0
^ permalink raw reply [flat|nested] 258+ messages in thread
* [RFC PATCH v2 1/2] fetch: set-head with --set-head option
2024-09-10 20:37 [RFC PATCH v2 0/2] set remote/HEAD with fetch Bence Ferdinandy
@ 2024-09-10 20:37 ` Bence Ferdinandy
2024-09-10 22:19 ` Junio C Hamano
2024-09-10 20:37 ` [RFC PATCH v2 2/2] set-head: do not update if there is no change Bence Ferdinandy
2024-09-10 22:29 ` [RFC PATCH v2 0/2] set remote/HEAD with fetch Junio C Hamano
2 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-09-10 20:37 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Taylor Blau, Patrick Steinhardt,
René Scharfe, Johannes Schindelin, Bence Ferdinandy
When cloning a repository refs/remotes/origin/HEAD is set automatically.
In contrast, when using init, remote add and fetch to set a remote, one
needs to call remote set-head --auto to achieve the same result.
Add a --set-head option to git fetch to automatically set heads on
remotes.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
builtin/fetch.c | 29 ++++++++++++++++++++++++-----
builtin/remote.c | 5 +++++
2 files changed, 29 insertions(+), 5 deletions(-)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index b2b5aee5bf..6392314c6a 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1961,8 +1961,19 @@ static int fetch_finished(int result, struct strbuf *out,
return 0;
}
-static int fetch_multiple(struct string_list *list, int max_children,
- const struct fetch_config *config)
+static int run_set_head(const char *name)
+{
+ struct child_process cmd = CHILD_PROCESS_INIT;
+ strvec_push(&cmd.args, "remote");
+ strvec_push(&cmd.args, "set-head");
+ strvec_push(&cmd.args, "--auto");
+ strvec_push(&cmd.args, name);
+ cmd.git_cmd = 1;
+ return run_command(&cmd);
+}
+
+static int fetch_multiple(struct string_list *list, int max_children, int set_head,
+ const struct fetch_config *config)
{
int i, result = 0;
struct strvec argv = STRVEC_INIT;
@@ -2014,6 +2025,8 @@ static int fetch_multiple(struct string_list *list, int max_children,
error(_("could not fetch %s"), name);
result = 1;
}
+ if (set_head && run_set_head(name))
+ result = 1;
}
strvec_clear(&argv);
@@ -2062,7 +2075,7 @@ static inline void fetch_one_setup_partial(struct remote *remote)
}
static int fetch_one(struct remote *remote, int argc, const char **argv,
- int prune_tags_ok, int use_stdin_refspecs,
+ int prune_tags_ok, int set_head, int use_stdin_refspecs,
const struct fetch_config *config)
{
struct refspec rs = REFSPEC_INIT_FETCH;
@@ -2135,9 +2148,12 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
refspec_clear(&rs);
transport_disconnect(gtransport);
gtransport = NULL;
+ if (set_head && run_set_head(remote -> name))
+ exit_code = 1;
return exit_code;
}
+
int cmd_fetch(int argc, const char **argv, const char *prefix)
{
struct fetch_config config = {
@@ -2154,6 +2170,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
struct string_list list = STRING_LIST_INIT_DUP;
struct remote *remote = NULL;
int all = -1, multiple = 0;
+ int set_head = 0;
int result = 0;
int prune_tags_ok = 1;
int enable_auto_gc = 1;
@@ -2171,6 +2188,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
OPT__VERBOSITY(&verbosity),
OPT_BOOL(0, "all", &all,
N_("fetch from all remotes")),
+ OPT_BOOL(0, "set-head", &set_head,
+ N_("auto set remote HEAD")),
OPT_BOOL(0, "set-upstream", &set_upstream,
N_("set upstream for git pull/fetch")),
OPT_BOOL('a', "append", &append,
@@ -2436,7 +2455,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
trace2_region_leave("fetch", "setup-partial", the_repository);
}
trace2_region_enter("fetch", "fetch-one", the_repository);
- result = fetch_one(remote, argc, argv, prune_tags_ok, stdin_refspecs,
+ result = fetch_one(remote, argc, argv, prune_tags_ok, set_head, stdin_refspecs,
&config);
trace2_region_leave("fetch", "fetch-one", the_repository);
} else {
@@ -2459,7 +2478,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
/* TODO should this also die if we have a previous partial-clone? */
trace2_region_enter("fetch", "fetch-multiple", the_repository);
- result = fetch_multiple(&list, max_children, &config);
+ result = fetch_multiple(&list, max_children, set_head, &config);
trace2_region_leave("fetch", "fetch-multiple", the_repository);
}
diff --git a/builtin/remote.c b/builtin/remote.c
index 0acc547d69..35c54dd103 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1536,9 +1536,12 @@ static int get_remote_default(const char *key, const char *value UNUSED,
static int update(int argc, const char **argv, const char *prefix)
{
int i, prune = -1;
+ int set_head = 0;
struct option options[] = {
OPT_BOOL('p', "prune", &prune,
N_("prune remotes after fetching")),
+ OPT_BOOL(0, "set-head", &set_head,
+ N_("auto set remote HEAD")),
OPT_END()
};
struct child_process cmd = CHILD_PROCESS_INIT;
@@ -1552,6 +1555,8 @@ static int update(int argc, const char **argv, const char *prefix)
if (prune != -1)
strvec_push(&cmd.args, prune ? "--prune" : "--no-prune");
+ if (set_head)
+ strvec_push(&cmd.args, "--set-head");
if (verbose)
strvec_push(&cmd.args, "-v");
strvec_push(&cmd.args, "--multiple");
--
2.46.0
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [RFC PATCH v2 2/2] set-head: do not update if there is no change
2024-09-10 20:37 [RFC PATCH v2 0/2] set remote/HEAD with fetch Bence Ferdinandy
2024-09-10 20:37 ` [RFC PATCH v2 1/2] fetch: set-head with --set-head option Bence Ferdinandy
@ 2024-09-10 20:37 ` Bence Ferdinandy
2024-09-10 22:29 ` [RFC PATCH v2 0/2] set remote/HEAD with fetch Junio C Hamano
2 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-09-10 20:37 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Taylor Blau, Patrick Steinhardt,
René Scharfe, Johannes Schindelin, Bence Ferdinandy
Currently, even if there is no actual change to remote/HEAD calling
remote set-head will overwrite the appropriate file and if set to --auto
will also print a message saying "remote/HEAD set to branch", which
implies something was changed. In contrast, on a nil operation e.g. pull
will clearly state that nothing was done, while fetch will not output
anything.
Change the behaviour of remote set-head so that the reference is only
updated if it actually needs to change. Since set-head --auto is
essentially a fetch-like operation, align it's behaviour with fetch and
only print output if something was actually done.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
builtin/remote.c | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 35c54dd103..e220e51b84 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1400,8 +1400,8 @@ 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;
+ int i, opt_a = 0, opt_d = 0, is_ref_changed = 0, result = 0;
+ struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT;
char *head_name = NULL;
struct option options[] = {
@@ -1440,12 +1440,14 @@ 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);
+ refs_read_symbolic_ref(get_main_ref_store(the_repository),buf.buf,&buf3);
+ is_ref_changed = strcmp(buf2.buf,buf3.buf);
/* 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 (is_ref_changed && refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote set-head"))
result |= error(_("Could not setup %s"), buf.buf);
- else if (opt_a)
+ else if (opt_a && is_ref_changed)
printf("%s/HEAD set to %s\n", argv[0], head_name);
free(head_name);
}
--
2.46.0
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [RFC PATCH v2 1/2] fetch: set-head with --set-head option
2024-09-10 20:37 ` [RFC PATCH v2 1/2] fetch: set-head with --set-head option Bence Ferdinandy
@ 2024-09-10 22:19 ` Junio C Hamano
2024-09-11 12:13 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Junio C Hamano @ 2024-09-10 22:19 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, Taylor Blau, Patrick Steinhardt, René Scharfe,
Johannes Schindelin
Bence Ferdinandy <bence@ferdinandy.com> writes:
> When cloning a repository refs/remotes/origin/HEAD is set automatically.
> In contrast, when using init, remote add and fetch to set a remote, one
> needs to call remote set-head --auto to achieve the same result.
In other words, "clone" is roughly equivalent to "init && remote add
&& fetch && remote set-head".
I do not see a problem with it. You'd do such a repository creation
task only once per repository anyway.
> Add a --set-head option to git fetch to automatically set heads on
> remotes.
This practically would help only those who want to blindly follow
what the remote considers their primary branch. If the remote
flip-flops that every day betweein 'master' and 'main', you'd want
to either "fetch && remote set-head" or "fetch --set-head", but it
would be weird to flip-flop the HEAD every day to begin with.
And the feature does not help those who want to inspect and then
switch. When they say "--set-head", they will unconditionally
switch whatever the remote uses and there is no opportunity for them
to check.
Ideally, because every "git fetch" you do will automatically learn
what their HEAD of the day points at, even without "--set-head", it
may be nice to let the user know when their HEAD changed, so that
the user can inspect the situation and decide.
If "fetch $repo", when it notices that refs/remotes/$repo/HEAD is
missing, always unconditionally stores where their HEAD points at
in refs/remotes/$repo/HEAD, and did nothing else, wouldn't that be
sufficient?
The users have "remote set-head" to do this when needed. What is
the true motivation that "fetch" (which presumably happens a lot
more often) needs to be involved in this process? The *only* upside
I can see with "fetch --set-head" to blindly follow every switch of
HEAD on the remote end is that you can keep up with the remote that
flips its HEAD very often, but is that really a realistic need? If
we reject such a use case as invalid, I suspect that the end-user
experience would be simplified quite a lot. Imagine that we teach
"git fetch $repo" to notice refs/remotes/$repo/HEAD is missing, and
create it from the remote HEAD information automatically. And we do
NOTHING ELSE. What would the end-user experience look like?
* Usually, you start with "git clone" and 'origin' will know which
branch 'origin/HEAD' points at.
* You may run "git remote add -f $repo $URL" to add one. Because
this runs "git fetch $repo", the previous addition to the "git
fetch" will make sure refs/remotes/$repo/HEAD would be there.
* You may run "git remote add $repo $URL" to add one, and then
separately "git fetch $repo" yourself. The end result would be
the same; refs/remotes/$repo/HEAD will be there.
Having said all that, the implementation here ...
> +static int run_set_head(const char *name)
> +{
> + struct child_process cmd = CHILD_PROCESS_INIT;
> + strvec_push(&cmd.args, "remote");
> + strvec_push(&cmd.args, "set-head");
> + strvec_push(&cmd.args, "--auto");
> + strvec_push(&cmd.args, name);
> + cmd.git_cmd = 1;
> + return run_command(&cmd);
> +}
... does look quite straight-forward.
> +static int fetch_multiple(struct string_list *list, int max_children, int set_head,
> + const struct fetch_config *config)
> {
> int i, result = 0;
> struct strvec argv = STRVEC_INIT;
> @@ -2014,6 +2025,8 @@ static int fetch_multiple(struct string_list *list, int max_children,
> error(_("could not fetch %s"), name);
> result = 1;
> }
> + if (set_head && run_set_head(name))
> + result = 1;
This looked a bit questionable---I expected that we'd run the
subfetches with "--set-head" option, but this makes us do the
set-head step ourselves after the subfetches are done. What are
pros-and-cons considered to reach the decision to do it this way?
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [RFC PATCH v2 0/2] set remote/HEAD with fetch
2024-09-10 20:37 [RFC PATCH v2 0/2] set remote/HEAD with fetch Bence Ferdinandy
2024-09-10 20:37 ` [RFC PATCH v2 1/2] fetch: set-head with --set-head option Bence Ferdinandy
2024-09-10 20:37 ` [RFC PATCH v2 2/2] set-head: do not update if there is no change Bence Ferdinandy
@ 2024-09-10 22:29 ` Junio C Hamano
2024-09-11 12:24 ` Bence Ferdinandy
2 siblings, 1 reply; 258+ messages in thread
From: Junio C Hamano @ 2024-09-10 22:29 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, Taylor Blau, Patrick Steinhardt, René Scharfe,
Johannes Schindelin
Bence Ferdinandy <bence@ferdinandy.com> writes:
> What is missing for sure is:
> - documentation
> - tests (if needed)
What change does not need tests?
> - settings
>
> For settings, my idea would be a fetch/remote.set_head that could take three values:
> * never
> * missing: run it only if the ref is missing, this setting would basically
> allow replicating the result of a clone
> * always (with the other patch, this would still be a no-op if it didn't change)
>
> This would probably also require a --no-set-head flag, to disable an
> always/missing setting. A --missing-set-head or something of the like also may
> or may not make sense. Alternatively, only two behaviours might be enough
> (missing and always) since clone already sort of does this.
If we were to assume "always" is needed, then the tristate like the
above may be a reasonable way to go.
But as I outlined in my response to [1/2], I suspect that an
approach without configuration or command line option would give the
users the most smooth experience. They are used to seeing "clone"
to give them a remote tracking HEAD and nobody complained that we
lack the option to "clone" to prevent that. If "fetch" notices we
do not have remote tracking HEAD for a remote [*] and stores what it
observed at their HEAD in remote tracking HEAD, that should not
bother anybody. No matter what mechanism gave you the initial
remote tracking HEAD, if you want to update it to something else, we
already have the "remote set-head" command.
[Footnote]
* One thing we MUST be careful about is that some remotes may not
have ANY remote tracking branches (i.e. you only want to use the
remote mechanism to give you a shorthand for URL, but you do not
have fetch refspec at all). Even if refs/remotes/$repo/HEAD is
missing for such a remote, we should *not* attempt to create it,
as we are not populating refs/remotes/$repo/master and friends at
all in such a case.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [RFC PATCH v2 1/2] fetch: set-head with --set-head option
2024-09-10 22:19 ` Junio C Hamano
@ 2024-09-11 12:13 ` Bence Ferdinandy
2024-09-11 15:52 ` Junio C Hamano
2024-09-19 12:13 ` [PATCH v3 0/2] fetch: set remote/HEAD if missing Bence Ferdinandy
0 siblings, 2 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-09-11 12:13 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, Taylor Blau, Patrick Steinhardt, René Scharfe,
Johannes Schindelin
Hey Junio,
thanks for the very speedy feedback!
On Wed Sep 11, 2024 at 00:19, Junio C Hamano <gitster@pobox.com> wrote:
[snip]
>
> Ideally, because every "git fetch" you do will automatically learn
> what their HEAD of the day points at, even without "--set-head", it
> may be nice to let the user know when their HEAD changed, so that
> the user can inspect the situation and decide.
That would actually make sense, we could print a message saying HEAD has
changed and I guess helpfully print the exact set-head command they would need
to manually update should they wish to do so.
>
> If "fetch $repo", when it notices that refs/remotes/$repo/HEAD is
> missing, always unconditionally stores where their HEAD points at
> in refs/remotes/$repo/HEAD, and did nothing else, wouldn't that be
> sufficient?
>
> The users have "remote set-head" to do this when needed. What is
> the true motivation that "fetch" (which presumably happens a lot
> more often) needs to be involved in this process? The *only* upside
> I can see with "fetch --set-head" to blindly follow every switch of
> HEAD on the remote end is that you can keep up with the remote that
> flips its HEAD very often, but is that really a realistic need? If
> we reject such a use case as invalid, I suspect that the end-user
> experience would be simplified quite a lot. Imagine that we teach
> "git fetch $repo" to notice refs/remotes/$repo/HEAD is missing, and
> create it from the remote HEAD information automatically. And we do
> NOTHING ELSE. What would the end-user experience look like?
>
> * Usually, you start with "git clone" and 'origin' will know which
> branch 'origin/HEAD' points at.
>
> * You may run "git remote add -f $repo $URL" to add one. Because
> this runs "git fetch $repo", the previous addition to the "git
> fetch" will make sure refs/remotes/$repo/HEAD would be there.
>
> * You may run "git remote add $repo $URL" to add one, and then
> separately "git fetch $repo" yourself. The end result would be
> the same; refs/remotes/$repo/HEAD will be there.
I'm convinced :) It also does drop a lot of complexity. So there will be no
extra flag for fetch, rather:
- if the remote HEAD does not exist, we create it
- if it does exist, but has changed we print a message saying it was changed from X to Y and print the required command to update to this
- no configuration needed
The only place I can imagine this not being sufficient is in a CI/CD process
that is fetching into a cached repo needs to be updated, but then those people
can just always run remote set-head -a.
>
> Having said all that, the implementation here ...
>
> > +static int run_set_head(const char *name)
> > +{
> > + struct child_process cmd = CHILD_PROCESS_INIT;
> > + strvec_push(&cmd.args, "remote");
> > + strvec_push(&cmd.args, "set-head");
> > + strvec_push(&cmd.args, "--auto");
> > + strvec_push(&cmd.args, name);
> > + cmd.git_cmd = 1;
> > + return run_command(&cmd);
> > +}
>
> ... does look quite straight-forward.
Unfortunately, as Jeff has pointed out in the other thread, this implementation
requires the user to authenticate twice ... Now, we could still rely on
set-head to actually do the writing, since if you explicitly give it what to
set to it will not do a network query, but considering all of the above,
I think it would make more sense not to do this, rather just bring in the
required logic here. This would also allow for the second patch to be a bit
more explicit on what did or did not happen during a remote set-head.
>
> > +static int fetch_multiple(struct string_list *list, int max_children, int set_head,
> > + const struct fetch_config *config)
> > {
> > int i, result = 0;
> > struct strvec argv = STRVEC_INIT;
> > @@ -2014,6 +2025,8 @@ static int fetch_multiple(struct string_list *list, int max_children,
> > error(_("could not fetch %s"), name);
> > result = 1;
> > }
> > + if (set_head && run_set_head(name))
> > + result = 1;
>
> This looked a bit questionable---I expected that we'd run the
> subfetches with "--set-head" option, but this makes us do the
> set-head step ourselves after the subfetches are done. What are
> pros-and-cons considered to reach the decision to do it this way?
Just noobish oversight, I figured out how to do it in the subfetch, but with
the above there will be no need for this anyway I guess.
I'll send a v2 soonish.
Best,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [RFC PATCH v2 0/2] set remote/HEAD with fetch
2024-09-10 22:29 ` [RFC PATCH v2 0/2] set remote/HEAD with fetch Junio C Hamano
@ 2024-09-11 12:24 ` Bence Ferdinandy
2024-09-11 15:59 ` Junio C Hamano
0 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-09-11 12:24 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, Taylor Blau, Patrick Steinhardt, René Scharfe,
Johannes Schindelin
On Wed Sep 11, 2024 at 00:29, Junio C Hamano <gitster@pobox.com> wrote:
> Bence Ferdinandy <bence@ferdinandy.com> writes:
>
> > What is missing for sure is:
> > - documentation
> > - tests (if needed)
>
> What change does not need tests?
Fair enough, for the next iteration I'll look into tests as well!
>
> > - settings
> >
> > For settings, my idea would be a fetch/remote.set_head that could take three values:
> > * never
> > * missing: run it only if the ref is missing, this setting would basically
> > allow replicating the result of a clone
> > * always (with the other patch, this would still be a no-op if it didn't change)
> >
> > This would probably also require a --no-set-head flag, to disable an
> > always/missing setting. A --missing-set-head or something of the like also may
> > or may not make sense. Alternatively, only two behaviours might be enough
> > (missing and always) since clone already sort of does this.
>
> If we were to assume "always" is needed, then the tristate like the
> above may be a reasonable way to go.
>
> But as I outlined in my response to [1/2], I suspect that an
> approach without configuration or command line option would give the
> users the most smooth experience. They are used to seeing "clone"
> to give them a remote tracking HEAD and nobody complained that we
> lack the option to "clone" to prevent that. If "fetch" notices we
> do not have remote tracking HEAD for a remote [*] and stores what it
> observed at their HEAD in remote tracking HEAD, that should not
> bother anybody. No matter what mechanism gave you the initial
> remote tracking HEAD, if you want to update it to something else, we
> already have the "remote set-head" command.
So I guess we did conclude that no settings are actually needed.
>
>
> [Footnote]
>
> * One thing we MUST be careful about is that some remotes may not
> have ANY remote tracking branches (i.e. you only want to use the
> remote mechanism to give you a shorthand for URL, but you do not
> have fetch refspec at all). Even if refs/remotes/$repo/HEAD is
> missing for such a remote, we should *not* attempt to create it,
> as we are not populating refs/remotes/$repo/master and friends at
> all in such a case.
You mean that somebody does git init && git remote add origin $remote, but
never does calls fetch? Currently remote set-head -a origin will fail with
error: Not a valid ref: refs/remotes/origin/master
Although if everything is done _after_ a fetch, unless I misunderstood, we
shouldn't be able to be in this situation at all.
Best,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [RFC PATCH v2 1/2] fetch: set-head with --set-head option
2024-09-11 12:13 ` Bence Ferdinandy
@ 2024-09-11 15:52 ` Junio C Hamano
2024-09-19 12:13 ` [PATCH v3 0/2] fetch: set remote/HEAD if missing Bence Ferdinandy
1 sibling, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-09-11 15:52 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, Taylor Blau, Patrick Steinhardt, René Scharfe,
Johannes Schindelin
"Bence Ferdinandy" <bence@ferdinandy.com> writes:
>> Ideally, because every "git fetch" you do will automatically learn
>> what their HEAD of the day points at, even without "--set-head", it
>> may be nice to let the user know when their HEAD changed, so that
>> the user can inspect the situation and decide.
>
> That would actually make sense, we could print a message saying HEAD has
> changed and I guess helpfully print the exact set-head command they would need
> to manually update should they wish to do so.
We need to be careful if we were to do this, though. A user may see
that their HEAD points at their 'main' branch, and they know the
remote-tracking HEAD points at 'next' branch. But the only thing
the latter tells us is that 'next' is, as far as this user is
concerned, the primary branch they are interested in from this
remote. It may have been set earlier with "git remtoe set-head"
explicitly, or it may have been set back when "git clone" created
this repository and back then the remote had 'next' marked as its
primary branch. In other words, the HEAD we learned from remote
while we are fetching from it may or may not be the same from the
remote-tracking HEAD we have, but it does not mean that the remote
recently flipped its HEAD if these two are different, but what we
want to report is "their HEAD used to point at 'master' but now it
is pointing at 'main'".
> So there will be no
> extra flag for fetch, rather:
>
> - if the remote HEAD does not exist, we create it
>
> - if it does exist, but has changed we print a message saying it was changed from X to Y and print the required command to update to this
>
> - no configuration needed
Very good, with two reservations.
- if the remote HEAD does not exist, we may still not want to
create it. Imagine
[remote "his"]
url = https://git.kernel.org/pub/scm/git/git
push = refs/heads/maint
push = refs/heads/master
push = refs/heads/next
push = +refs/heads/seen
without any refspec for the fetching side. "git fetch his master"
may learn where the remote HEAD is, and it may even be pointing
at their 'master' branch, but because we do not maintain any
remote tracking information for their 'master' (in other words,
refs/remotes/his/master is not updated by this 'fetch' and there
is no configuration to make future 'fetch' to do so).
- it would be very nice to report when we notice that they changed
their HEAD, but I do not think we have enough information to do
so. Our existing refs/remotes/origin/HEAD may not have been set
to match their HEAD in the first place.
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [RFC PATCH v2 0/2] set remote/HEAD with fetch
2024-09-11 12:24 ` Bence Ferdinandy
@ 2024-09-11 15:59 ` Junio C Hamano
0 siblings, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-09-11 15:59 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, Taylor Blau, Patrick Steinhardt, René Scharfe,
Johannes Schindelin
"Bence Ferdinandy" <bence@ferdinandy.com> writes:
> On Wed Sep 11, 2024 at 00:29, Junio C Hamano <gitster@pobox.com> wrote:
>> Bence Ferdinandy <bence@ferdinandy.com> writes:
>>
>> > What is missing for sure is:
>> > - documentation
>> > - tests (if needed)
>>
>> What change does not need tests?
>
> Fair enough, for the next iteration I'll look into tests as well!
In this project, tests are written not because we want to
demonstrate the shiny new feature we just invented. We write tests
so that the invention we are shipping to our end users will keep
behaveing in the way we originall intended without getting broken by
future careless developers. The tests serve as watching eyes while
the original author of the feature is not watching ;-)
And that is why we also write tests that a feature does not trigger
when it should not, as well as it triggers when it should.
>> * One thing we MUST be careful about is that some remotes may not
>> have ANY remote tracking branches (i.e. you only want to use the
>> remote mechanism to give you a shorthand for URL, but you do not
>> have fetch refspec at all). Even if refs/remotes/$repo/HEAD is
>> missing for such a remote, we should *not* attempt to create it,
>> as we are not populating refs/remotes/$repo/master and friends at
>> all in such a case.
>
> You mean that somebody does git init && git remote add origin $remote, but
> never does calls fetch?
No. If the remote HEAD does not exist, we may still not want to
create it. Imagine
[remote "his"]
url = https://git.kernel.org/pub/scm/git/git
push = refs/heads/maint
push = refs/heads/master
push = refs/heads/next
push = +refs/heads/seen
without any refspec for the fetching side. "git fetch his master"
may learn where the remote HEAD is, and it may even be pointing at
their 'master' branch, but because we do not maintain any remote
tracking information for their 'master' (in other words,
refs/remotes/his/master is not updated by this 'fetch' and there is
no configuration to make future 'fetch' to do so).
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v3 0/2] fetch: set remote/HEAD if missing
2024-09-11 12:13 ` Bence Ferdinandy
2024-09-11 15:52 ` Junio C Hamano
@ 2024-09-19 12:13 ` Bence Ferdinandy
2024-09-19 12:13 ` [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option Bence Ferdinandy
2024-09-19 12:13 ` [PATCH v3 2/2] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
1 sibling, 2 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-09-19 12:13 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Taylor Blau, Patrick Steinhardt,
René Scharfe, Johannes Schindelin, Bence Ferdinandy
This series is a complete rewrite of the RFC without using or touching
the code of `git remote set-head` (that part of the RFC is being handled
in a new thread of it's own).
Based on the previous discussions it does an atomic update of
remote/HEAD if it doesn't exist and is able to. Since we have git remote
set-head for manual handling of remote/HEAD, my idea was that it only
outputs minimal information and in general send the user to use remote
set-head for figuring out issues. If there is a simple, clear HEAD and
is changed it will give the command to use for switching to this and if
something went wrong it tells the user to check with remote set-head.
I'm not sure if this is really needed to be honest.
A note on tests:
- the first patch changing refs_update_symref passes all tests
- the series in itself breaks many fetch tests currently, most of them
seem to stem from the fact that we now have remote/HEAD refs as extra
I'm planning to add a test to fetch for testing not to overwrite
existing refs, and of course I'll go and clean up all the tests, but it
would be nice to know beforehand if the approach is good and to finalize
all the outputs before changing 30 something tests.
Bence Ferdinandy (2):
update_symref: add REF_CREATE_ONLY option
fetch: set remote/HEAD if it does not exist
builtin/branch.c | 2 +-
builtin/checkout.c | 4 +--
builtin/clone.c | 6 ++--
builtin/fetch.c | 71 +++++++++++++++++++++++++++++++++++++++
builtin/notes.c | 2 +-
builtin/remote.c | 6 ++--
builtin/symbolic-ref.c | 2 +-
builtin/worktree.c | 2 +-
refs.c | 6 ++--
refs.h | 12 +++++--
refs/files-backend.c | 8 +++++
reset.c | 2 +-
sequencer.c | 2 +-
setup.c | 2 +-
t/helper/test-ref-store.c | 2 +-
15 files changed, 108 insertions(+), 21 deletions(-)
--
2.46.1.546.gaa0cd55cd3.dirty
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option
2024-09-19 12:13 ` [PATCH v3 0/2] fetch: set remote/HEAD if missing Bence Ferdinandy
@ 2024-09-19 12:13 ` Bence Ferdinandy
2024-09-19 22:46 ` Junio C Hamano
2024-09-21 13:40 ` Phillip Wood
2024-09-19 12:13 ` [PATCH v3 2/2] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
1 sibling, 2 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-09-19 12:13 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Taylor Blau, Patrick Steinhardt,
René Scharfe, Johannes Schindelin, Bence Ferdinandy
Add a new REF_CREATE_ONLY flag for use by the files backend which will
only update the symref if it doesn't already exist. Add the possibility
to pass extra flags to refs_update_symref so that it can utilize this
new flag.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v3: new patch, passes all tests on it's own
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 | 6 +++---
refs.h | 12 ++++++++++--
refs/files-backend.c | 8 ++++++++
reset.c | 2 +-
sequencer.c | 2 +-
setup.c | 2 +-
t/helper/test-ref-store.c | 2 +-
14 files changed, 37 insertions(+), 21 deletions(-)
diff --git a/builtin/branch.c b/builtin/branch.c
index c98601c6fe..6025bca45e 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, 0, logmsg))
ret = error(_("HEAD of working tree %s is not updated"),
worktrees[i]->path);
}
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 4cfe6fab50..23e28321d6 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1011,7 +1011,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, 0, msg.buf) < 0)
die(_("unable to update HEAD"));
if (!opts->quiet) {
if (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) {
@@ -1475,7 +1475,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, 0, "checkout -b");
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 269b6e18a4..43b7878a79 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -659,7 +659,7 @@ static void update_remote_refs(const struct ref *refs,
strbuf_addstr(&head_ref, branch_top);
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,
+ remote_head_points_at->peer_ref->name, 0,
msg) < 0)
die(_("unable to update %s"), head_ref.buf);
strbuf_release(&head_ref);
@@ -672,7 +672,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, 0, NULL) < 0)
die(_("unable to update HEAD"));
if (!option_bare) {
refs_update_ref(get_main_ref_store(the_repository),
@@ -701,7 +701,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, 0, 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 04f9dfb7fb..6b42d1139f 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -978,7 +978,7 @@ static int merge(int argc, const char **argv, const char *prefix)
die(_("a notes merge into %s is already in-progress at %s"),
default_notes_ref(), wt->path);
free_worktrees(worktrees);
- if (refs_update_symref(get_main_ref_store(the_repository), "NOTES_MERGE_REF", default_notes_ref(), NULL))
+ if (refs_update_symref(get_main_ref_store(the_repository), "NOTES_MERGE_REF", default_notes_ref(), 0, NULL))
die(_("failed to store link to current notes ref (%s)"),
default_notes_ref());
fprintf(stderr, _("Automatic notes merge failed. Fix conflicts in %s "
diff --git a/builtin/remote.c b/builtin/remote.c
index 0acc547d69..d28c65599d 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -243,7 +243,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, 0, "remote add"))
result = error(_("Could not setup master '%s'"), master);
}
@@ -863,7 +863,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, 0, buf3.buf))
die(_("creating '%s' failed"), buf.buf);
display_progress(progress, ++refs_renamed_nr);
}
@@ -1443,7 +1443,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, 0, "remote set-head"))
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 81abdd170f..52de3e02af 100644
--- a/builtin/symbolic-ref.c
+++ b/builtin/symbolic-ref.c
@@ -84,7 +84,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
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], 0, msg);
break;
default:
usage_with_options(git_symbolic_ref_usage, options);
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 41e7f6a327..71434737e8 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, 0, NULL);
if (ret)
goto done;
diff --git a/refs.c b/refs.c
index ceb72d4bd7..7afe46cadc 100644
--- a/refs.c
+++ b/refs.c
@@ -2085,8 +2085,9 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
return peel_object(r, base, peeled) ? -1 : 0;
}
+
int refs_update_symref(struct ref_store *refs, const char *ref,
- const char *target, const char *logmsg)
+ const char *target, const unsigned int extra_flags, const char *logmsg)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
@@ -2095,7 +2096,7 @@ 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,
- target, NULL, REF_NO_DEREF,
+ target, NULL, REF_NO_DEREF | extra_flags,
logmsg, &err) ||
ref_transaction_commit(transaction, &err)) {
ret = error("%s", err.buf);
@@ -2920,4 +2921,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 f8b919a138..d907451d13 100644
--- a/refs.h
+++ b/refs.h
@@ -569,7 +569,7 @@ 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 unsigned int extra_flags, const char *logmsg);
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
@@ -672,13 +672,21 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,
*/
#define REF_SKIP_CREATE_REFLOG (1 << 12)
+/*
+ * If the reference has already been created do not touch it.
+ */
+
+#define REF_CREATE_ONLY (1 << 13)
+
+
/*
* Bitmask of all of the flags that are allowed to be passed in to
* ref_transaction_update() and friends:
*/
#define REF_TRANSACTION_UPDATE_ALLOWED_FLAGS \
(REF_NO_DEREF | REF_FORCE_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION | \
- REF_SKIP_REFNAME_VERIFICATION | REF_SKIP_CREATE_REFLOG)
+ REF_SKIP_REFNAME_VERIFICATION | REF_SKIP_CREATE_REFLOG | \
+ REF_CREATE_ONLY)
/*
* Add a reference update to transaction. `new_oid` is the value that
diff --git a/refs/files-backend.c b/refs/files-backend.c
index c7f3f4e591..1440b69b87 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2993,6 +2993,8 @@ static int files_transaction_finish(struct ref_store *ref_store,
struct ref_update *update = transaction->updates[i];
struct ref_lock *lock = update->backend_data;
+ if (update->flags & REF_CREATE_ONLY && refs_ref_exists(ref_store, update->refname))
+ continue;
if (update->flags & REF_NEEDS_COMMIT ||
update->flags & REF_LOG_ONLY) {
if (parse_and_write_reflog(refs, update, lock, err)) {
@@ -3031,6 +3033,8 @@ static int files_transaction_finish(struct ref_store *ref_store,
*/
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
+ if (update->flags & REF_CREATE_ONLY && refs_ref_exists(ref_store, update->refname))
+ continue;
if (update->flags & REF_DELETING &&
!(update->flags & REF_LOG_ONLY) &&
!(update->flags & REF_IS_PRUNING)) {
@@ -3061,6 +3065,8 @@ static int files_transaction_finish(struct ref_store *ref_store,
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
struct ref_lock *lock = update->backend_data;
+ if (update->flags & REF_CREATE_ONLY && refs_ref_exists(ref_store, update->refname))
+ continue;
if (update->flags & REF_DELETING &&
!(update->flags & REF_LOG_ONLY)) {
@@ -3085,6 +3091,8 @@ static int files_transaction_finish(struct ref_store *ref_store,
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
+ if (update->flags & REF_CREATE_ONLY && refs_ref_exists(ref_store, update->refname))
+ continue;
if (update->flags & REF_DELETED_RMDIR) {
/*
diff --git a/reset.c b/reset.c
index b22b1be792..8dce5f2133 100644
--- a/reset.c
+++ b/reset.c
@@ -75,7 +75,7 @@ static int update_refs(const struct reset_head_opts *opts,
UPDATE_REFS_MSG_ON_ERR);
if (!ret)
ret = refs_update_symref(get_main_ref_store(the_repository),
- "HEAD", switch_to_branch,
+ "HEAD", switch_to_branch, 0,
reflog_head);
}
if (!ret && run_hook)
diff --git a/sequencer.c b/sequencer.c
index 8d01cd50ac..924a78dab8 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, 0, msg)) {
res = error(_("could not update HEAD to %s"),
head_ref.buf);
goto cleanup_head_ref;
diff --git a/setup.c b/setup.c
index 29f8673921..6a1fdef2c3 100644
--- a/setup.c
+++ b/setup.c
@@ -2169,7 +2169,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, 0, NULL) < 0)
exit(1);
free(ref);
}
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index 65346dee55..90af41edce 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, 0, logmsg);
}
static struct flag_definition transaction_flags[] = {
--
2.46.1.546.gaa0cd55cd3.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v3 2/2] fetch: set remote/HEAD if it does not exist
2024-09-19 12:13 ` [PATCH v3 0/2] fetch: set remote/HEAD if missing Bence Ferdinandy
2024-09-19 12:13 ` [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option Bence Ferdinandy
@ 2024-09-19 12:13 ` Bence Ferdinandy
2024-09-19 23:07 ` Junio C Hamano
1 sibling, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-09-19 12:13 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Taylor Blau, Patrick Steinhardt,
René Scharfe, Johannes Schindelin, 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.
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
builtin/fetch.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 71 insertions(+)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 55f97134aa..2746d40bb5 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1577,6 +1577,70 @@ static int backfill_tags(struct display_state *display_state,
return retcode;
}
+static const char *abbrev_ref(const char *name, const char *prefix)
+{
+ skip_prefix(name, prefix, &name);
+ return name;
+}
+#define abbrev_branch(name) abbrev_ref((name), "refs/heads/")
+
+static inline int set_head(const struct ref *remote_refs)
+{
+ int result, ref_changed = 0;
+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT, b_local_head = STRBUF_INIT, b_prefix = 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;
+
+ 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, abbrev_branch(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);
+ strbuf_addf(&b_prefix, "refs/remotes/%s/", remote);
+ if (!refs_read_symbolic_ref(get_main_ref_store(the_repository),b_head.buf,&b_local_head)) {
+ ref_changed = strcmp(b_remote_head.buf,b_local_head.buf);
+ if (heads.nr == 1 && ref_changed) {
+ printf("The ref \'%s/HEAD\' has changed from the locally recorded "
+ "\'%s\' to \'%s\'.\n",remote, abbrev_ref(b_local_head.buf,b_prefix.buf), head_name);
+ printf("Run \'git remote set-head -a %s\' to set it automatically.\n", remote);
+ }
+ }
+ /* make sure it's valid */
+ if (!refs_ref_exists(get_main_ref_store(the_repository), b_remote_head.buf))
+ result = 1;
+ else if (refs_update_symref(get_main_ref_store(the_repository), b_head.buf,
+ b_remote_head.buf, REF_CREATE_ONLY, "fetch"))
+ result = 1;
+ free(head_name);
+ }
+
+ strbuf_release(&b_head);
+ strbuf_release(&b_local_head);
+ strbuf_release(&b_remote_head);
+ strbuf_release(&b_prefix);
+ return result;
+}
+
static int do_fetch(struct transport *transport,
struct refspec *rs,
const struct fetch_config *config)
@@ -1646,6 +1710,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,
@@ -1790,6 +1856,10 @@ static int do_fetch(struct transport *transport,
"you need to specify exactly one branch with the --set-upstream option"));
}
}
+ if (!set_head(remote_refs))
+ printf("Ran into issues with \'%s/HEAD\',\n"
+ "use \'git remote set-head -a %s\' to investigate",
+ gtransport->remote->name,gtransport->remote->name);
cleanup:
if (retcode) {
@@ -2020,6 +2090,7 @@ static int fetch_multiple(struct string_list *list, int max_children,
return !!result;
}
+
/*
* Fetching from the promisor remote should use the given filter-spec
* or inherit the default filter-spec from the config.
--
2.46.1.546.gaa0cd55cd3.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option
2024-09-19 12:13 ` [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option Bence Ferdinandy
@ 2024-09-19 22:46 ` Junio C Hamano
2024-09-20 14:11 ` Bence Ferdinandy
2024-09-21 13:40 ` Phillip Wood
1 sibling, 1 reply; 258+ messages in thread
From: Junio C Hamano @ 2024-09-19 22:46 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, Taylor Blau, Patrick Steinhardt, René Scharfe,
Johannes Schindelin
Bence Ferdinandy <bence@ferdinandy.com> writes:
> Add a new REF_CREATE_ONLY flag for use by the files backend which will
> only update the symref if it doesn't already exist. Add the possibility
> to pass extra flags to refs_update_symref so that it can utilize this
> new flag.
If I wanted to create a symref that points at A, there are three cases:
(1) the symref does not exist.
(2) the symref exists and points at A.
(3) the symref exists and points at B.
I'll see a symref that points at A at the end in the first two
cases, and my request is silently ignored in the third case.
I'd expect that the caller can tell the failing case apart from the
successful case with the return value or something. The caller
might want to tell between the first two cases for reporting
purposes, but I do not care as much as I would care about detecting
true failures.
Nobody actually passes the flag yet, so we would not be able to tell
if any of the added code is buggy from this step alone. Let's see
what happens in the next patch ;-).
> diff --git a/refs.c b/refs.c
> index ceb72d4bd7..7afe46cadc 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -2085,8 +2085,9 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
> return peel_object(r, base, peeled) ? -1 : 0;
> }
>
> +
> int refs_update_symref(struct ref_store *refs, const char *ref,
> - const char *target, const char *logmsg)
> + const char *target, const unsigned int extra_flags, const char *logmsg)
While it is not _wrong_ per-se to mark an "unsigned int" parameter
as "const", it is a bit unusual in this code base. The only thing
it prevents us from doing is to mutate it until this function
returns, which does not help all that much in making the code safer,
as opposed to marking a parameter of a pointer type as a const
pointer.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v3 2/2] fetch: set remote/HEAD if it does not exist
2024-09-19 12:13 ` [PATCH v3 2/2] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
@ 2024-09-19 23:07 ` Junio C Hamano
2024-09-20 13:50 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Junio C Hamano @ 2024-09-19 23:07 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, Taylor Blau, Patrick Steinhardt, René Scharfe,
Johannes Schindelin
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.
>
> 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
Except for one thing, I can tell from the above that the design is
sensible without looking at the actual patch. Nicely described.
The "one thing" is what to do if you normally do *not* keep track of
the remote-tracking refs for this particular remote.
If I am only fetching refs (or HEAD) in FETCH_HEAD for immediate
consumtion by doing "git pull bence HEAD" with something like
[remote "bence"]
URL = http://github.com/bence/git
(with no other configuration under remote.bence.* hierarchy), I do
not think I want the code to create refs/remotes/bence/HEAD, with
no other remote-tracking refs in the same hierarchy, even if it finds
no existing refs/remotes/bence/HEAD there. For that, I suspect that
you'd need to see if the pointee of refs/remotes/bence/HEAD either
already exists or this fetch is about to create it, or something
like that, before deciding to create a ref with the REF_CREATE_ONLY
flag.
If you are doing so already in the code (I haven't started reading
it yet at this moment), you should mention it in your proposed log
message, so discount my earlier "Nicely described" a bit ;-).
Let's see what actually happens in the code now.
> +static const char *abbrev_ref(const char *name, const char *prefix)
> +{
> + skip_prefix(name, prefix, &name);
> + return name;
> +}
> +#define abbrev_branch(name) abbrev_ref((name), "refs/heads/")
We do not call the act of optionally skipping prefix "abbreviate" in
this project (object names are abbreviated but that is done by
chomping the later bytes of a long name).
I suspect that a caller of either of these two functions are
inherently buggy in that they _optionally_ skip the prefix so they
do the same thing for "refs/heads/main" and "main" but not
"heads/main". The callsites may need to be inspected to see how
they should deal with cases where skip_prefix() did *not* see
anything to skip.
> +static inline int set_head(const struct ref *remote_refs)
Drop "inline". You do not want to inline this much code if there
are many callers, and if you have only one caller, the compiler
would inline it into the sole caller if it is the more efficient
thing to do.
> +{
> + int result, ref_changed = 0;
> + struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT, b_local_head = STRBUF_INIT, b_prefix = 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;
> +
> + 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, abbrev_branch(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);
> + strbuf_addf(&b_prefix, "refs/remotes/%s/", remote);
> + if (!refs_read_symbolic_ref(get_main_ref_store(the_repository),b_head.buf,&b_local_head)) {
Overly long lines around here cannot be sanely reviewed on my
terminal, so I'll stop here for now.
Do not omit SP after ",", as that is the least effective way to
shorten overly long lines. Instead, consider introducing a
one-time-use temporary variables with meaningful name, e.g.
struct ref_store *store = get_main_ref_store(the_repository);
strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
...
if (!refs_read_symbolic_ref(store, b_head.buf, ¤t)) {
/* it's current value is in "current" */
...
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v3 2/2] fetch: set remote/HEAD if it does not exist
2024-09-19 23:07 ` Junio C Hamano
@ 2024-09-20 13:50 ` Bence Ferdinandy
0 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-09-20 13:50 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, Taylor Blau, Patrick Steinhardt, René Scharfe,
Johannes Schindelin
On Fri Sep 20, 2024 at 01:07, 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.
> >
> > 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
>
> Except for one thing, I can tell from the above that the design is
> sensible without looking at the actual patch. Nicely described.
>
> The "one thing" is what to do if you normally do *not* keep track of
> the remote-tracking refs for this particular remote.
>
> If I am only fetching refs (or HEAD) in FETCH_HEAD for immediate
> consumtion by doing "git pull bence HEAD" with something like
>
> [remote "bence"]
> URL = http://github.com/bence/git
>
> (with no other configuration under remote.bence.* hierarchy), I do
> not think I want the code to create refs/remotes/bence/HEAD, with
> no other remote-tracking refs in the same hierarchy, even if it finds
> no existing refs/remotes/bence/HEAD there. For that, I suspect that
> you'd need to see if the pointee of refs/remotes/bence/HEAD either
> already exists or this fetch is about to create it, or something
> like that, before deciding to create a ref with the REF_CREATE_ONLY
> flag.
I'll take another look at this.
>
> If you are doing so already in the code (I haven't started reading
> it yet at this moment), you should mention it in your proposed log
> message, so discount my earlier "Nicely described" a bit ;-).
:D
>
> Let's see what actually happens in the code now.
>
> > +static const char *abbrev_ref(const char *name, const char *prefix)
> > +{
> > + skip_prefix(name, prefix, &name);
> > + return name;
> > +}
> > +#define abbrev_branch(name) abbrev_ref((name), "refs/heads/")
>
> We do not call the act of optionally skipping prefix "abbreviate" in
> this project (object names are abbreviated but that is done by
> chomping the later bytes of a long name).
>
> I suspect that a caller of either of these two functions are
> inherently buggy in that they _optionally_ skip the prefix so they
> do the same thing for "refs/heads/main" and "main" but not
> "heads/main". The callsites may need to be inspected to see how
> they should deal with cases where skip_prefix() did *not* see
> anything to skip.
I copied this verbatim from builtin/remote.c
https://github.com/git/git/blob/master/builtin/remote.c#L267 so it looked like
a safe bet T_T I'm not 100% sure, but I think there's no use case where you
pass a ref directly to remote except for set_head, but there it's not used, so
it looks safe?
That being said, I can change the name(s) to not use abbrev here, especially if
you have a good suggestion. Maybe strip_prefix_ref/branch? Again in fetch, we
(should) know what type of refs we're calling it on, so should be safe?
>
> > +static inline int set_head(const struct ref *remote_refs)
>
> Drop "inline". You do not want to inline this much code if there
> are many callers, and if you have only one caller, the compiler
> would inline it into the sole caller if it is the more efficient
> thing to do.
Ack.
>
> > +{
> > + int result, ref_changed = 0;
> > + struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT, b_local_head = STRBUF_INIT, b_prefix = 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;
> > +
> > + 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, abbrev_branch(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);
> > + strbuf_addf(&b_prefix, "refs/remotes/%s/", remote);
> > + if (!refs_read_symbolic_ref(get_main_ref_store(the_repository),b_head.buf,&b_local_head)) {
>
> Overly long lines around here cannot be sanely reviewed on my
> terminal, so I'll stop here for now.
>
> Do not omit SP after ",", as that is the least effective way to
> shorten overly long lines. Instead, consider introducing a
> one-time-use temporary variables with meaningful name, e.g.
>
> struct ref_store *store = get_main_ref_store(the_repository);
>
> strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
> ...
> if (!refs_read_symbolic_ref(store, b_head.buf, ¤t)) {
> /* it's current value is in "current" */
> ...
Ah, yeah terribly sorry about this, I've gotten so used to automatic
formatters, that I just didn't take a second look. I'll be more careful in the
next round.
--
bence.ferdinandy.com
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option
2024-09-19 22:46 ` Junio C Hamano
@ 2024-09-20 14:11 ` Bence Ferdinandy
0 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-09-20 14:11 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, Taylor Blau, Patrick Steinhardt, René Scharfe,
Johannes Schindelin
On Fri Sep 20, 2024 at 00:46, Junio C Hamano <gitster@pobox.com> wrote:
> Bence Ferdinandy <bence@ferdinandy.com> writes:
[ snip]
> If I wanted to create a symref that points at A, there are three cases:
>
> (1) the symref does not exist.
> (2) the symref exists and points at A.
> (3) the symref exists and points at B.
>
> I'll see a symref that points at A at the end in the first two
> cases, and my request is silently ignored in the third case.
>
> I'd expect that the caller can tell the failing case apart from the
> successful case with the return value or something. The caller
> might want to tell between the first two cases for reporting
> purposes, but I do not care as much as I would care about detecting
> true failures.
Hmm. So in case I'm passing REF_CREATE_ONLY I would not expect the above cases
to be error in the sense that transaction_finish should not report a failure
and thus have all callers assume things went wrong. On the other hand it's
a valid concern, that the caller may want to check what happened. Actually, the
idea that I mentioned in
https://lore.kernel.org/git/D4AK4USDVP5T.10INJOFE2I8LE@ferdinandy.com/
may actually be useful here as well. We could record the state of the reference
atomically before the transaction in update, and then if the caller is
interested, they can match this against what they requested. That way they can
figure out which of the 3 cases they were in without raceconditions after the
situation. Actually, this way any feedback could be given to the user post
transaction here as well.
If this sounds sensible, then I guess it would make sense to rejoin the
set-head patch into this thread as well ...
[snip]
>
> > diff --git a/refs.c b/refs.c
> > index ceb72d4bd7..7afe46cadc 100644
> > --- a/refs.c
> > +++ b/refs.c
> > @@ -2085,8 +2085,9 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
> > return peel_object(r, base, peeled) ? -1 : 0;
> > }
> >
> > +
> > int refs_update_symref(struct ref_store *refs, const char *ref,
> > - const char *target, const char *logmsg)
> > + const char *target, const unsigned int extra_flags, const char *logmsg)
>
> While it is not _wrong_ per-se to mark an "unsigned int" parameter
> as "const", it is a bit unusual in this code base. The only thing
> it prevents us from doing is to mutate it until this function
> returns, which does not help all that much in making the code safer,
> as opposed to marking a parameter of a pointer type as a const
> pointer.
Makes sense, I'll drop it.
Thanks very much for your patience!
Best,
Bence
--
bence.ferdinandy.com
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option
2024-09-19 12:13 ` [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option Bence Ferdinandy
2024-09-19 22:46 ` Junio C Hamano
@ 2024-09-21 13:40 ` Phillip Wood
2024-09-21 22:19 ` Bence Ferdinandy
1 sibling, 1 reply; 258+ messages in thread
From: Phillip Wood @ 2024-09-21 13:40 UTC (permalink / raw)
To: Bence Ferdinandy, git
Cc: Junio C Hamano, Taylor Blau, Patrick Steinhardt,
René Scharfe, Johannes Schindelin
On 19/09/2024 13:13, Bence Ferdinandy wrote:
> Add a new REF_CREATE_ONLY flag for use by the files backend which will
> only update the symref if it doesn't already exist. Add the possibility
> to pass extra flags to refs_update_symref so that it can utilize this
> new flag.
I'm not sure we need a new flag to do this as it is already supported by
the ref transaction api.
struct ref_transaction *t;
struct strbuf err = STRBUF_INIT;
struct ref_store refs = get_main_ref_store(the_repository);
int ret = 0;
t = ref_transaction_begin(refs, &err);
if (!t ||
ref_transaction_create(t, b_head.buf, NULL, b_remote_head.buf,
REF_NO_DEREF, "fetch", &err) ||
ref_transaction_commit(t, &err))
ret = error(_("%s", err.buf));
ref_transaction_free(transaction);
strbuf_release(&err);
return ret;
Best Wishes
Phillip
> Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
> ---
>
> Notes:
> v3: new patch, passes all tests on it's own
>
> 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 | 6 +++---
> refs.h | 12 ++++++++++--
> refs/files-backend.c | 8 ++++++++
> reset.c | 2 +-
> sequencer.c | 2 +-
> setup.c | 2 +-
> t/helper/test-ref-store.c | 2 +-
> 14 files changed, 37 insertions(+), 21 deletions(-)
>
> diff --git a/builtin/branch.c b/builtin/branch.c
> index c98601c6fe..6025bca45e 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, 0, logmsg))
> ret = error(_("HEAD of working tree %s is not updated"),
> worktrees[i]->path);
> }
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index 4cfe6fab50..23e28321d6 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -1011,7 +1011,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, 0, msg.buf) < 0)
> die(_("unable to update HEAD"));
> if (!opts->quiet) {
> if (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) {
> @@ -1475,7 +1475,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, 0, "checkout -b");
> 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 269b6e18a4..43b7878a79 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -659,7 +659,7 @@ static void update_remote_refs(const struct ref *refs,
> strbuf_addstr(&head_ref, branch_top);
> 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,
> + remote_head_points_at->peer_ref->name, 0,
> msg) < 0)
> die(_("unable to update %s"), head_ref.buf);
> strbuf_release(&head_ref);
> @@ -672,7 +672,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, 0, NULL) < 0)
> die(_("unable to update HEAD"));
> if (!option_bare) {
> refs_update_ref(get_main_ref_store(the_repository),
> @@ -701,7 +701,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, 0, 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 04f9dfb7fb..6b42d1139f 100644
> --- a/builtin/notes.c
> +++ b/builtin/notes.c
> @@ -978,7 +978,7 @@ static int merge(int argc, const char **argv, const char *prefix)
> die(_("a notes merge into %s is already in-progress at %s"),
> default_notes_ref(), wt->path);
> free_worktrees(worktrees);
> - if (refs_update_symref(get_main_ref_store(the_repository), "NOTES_MERGE_REF", default_notes_ref(), NULL))
> + if (refs_update_symref(get_main_ref_store(the_repository), "NOTES_MERGE_REF", default_notes_ref(), 0, NULL))
> die(_("failed to store link to current notes ref (%s)"),
> default_notes_ref());
> fprintf(stderr, _("Automatic notes merge failed. Fix conflicts in %s "
> diff --git a/builtin/remote.c b/builtin/remote.c
> index 0acc547d69..d28c65599d 100644
> --- a/builtin/remote.c
> +++ b/builtin/remote.c
> @@ -243,7 +243,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, 0, "remote add"))
> result = error(_("Could not setup master '%s'"), master);
> }
>
> @@ -863,7 +863,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, 0, buf3.buf))
> die(_("creating '%s' failed"), buf.buf);
> display_progress(progress, ++refs_renamed_nr);
> }
> @@ -1443,7 +1443,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, 0, "remote set-head"))
> 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 81abdd170f..52de3e02af 100644
> --- a/builtin/symbolic-ref.c
> +++ b/builtin/symbolic-ref.c
> @@ -84,7 +84,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
> 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], 0, msg);
> break;
> default:
> usage_with_options(git_symbolic_ref_usage, options);
> diff --git a/builtin/worktree.c b/builtin/worktree.c
> index 41e7f6a327..71434737e8 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, 0, NULL);
> if (ret)
> goto done;
>
> diff --git a/refs.c b/refs.c
> index ceb72d4bd7..7afe46cadc 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -2085,8 +2085,9 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
> return peel_object(r, base, peeled) ? -1 : 0;
> }
>
> +
> int refs_update_symref(struct ref_store *refs, const char *ref,
> - const char *target, const char *logmsg)
> + const char *target, const unsigned int extra_flags, const char *logmsg)
> {
> struct ref_transaction *transaction;
> struct strbuf err = STRBUF_INIT;
> @@ -2095,7 +2096,7 @@ 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,
> - target, NULL, REF_NO_DEREF,
> + target, NULL, REF_NO_DEREF | extra_flags,
> logmsg, &err) ||
> ref_transaction_commit(transaction, &err)) {
> ret = error("%s", err.buf);
> @@ -2920,4 +2921,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 f8b919a138..d907451d13 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -569,7 +569,7 @@ 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 unsigned int extra_flags, const char *logmsg);
>
> enum action_on_err {
> UPDATE_REFS_MSG_ON_ERR,
> @@ -672,13 +672,21 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,
> */
> #define REF_SKIP_CREATE_REFLOG (1 << 12)
>
> +/*
> + * If the reference has already been created do not touch it.
> + */
> +
> +#define REF_CREATE_ONLY (1 << 13)
> +
> +
> /*
> * Bitmask of all of the flags that are allowed to be passed in to
> * ref_transaction_update() and friends:
> */
> #define REF_TRANSACTION_UPDATE_ALLOWED_FLAGS \
> (REF_NO_DEREF | REF_FORCE_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION | \
> - REF_SKIP_REFNAME_VERIFICATION | REF_SKIP_CREATE_REFLOG)
> + REF_SKIP_REFNAME_VERIFICATION | REF_SKIP_CREATE_REFLOG | \
> + REF_CREATE_ONLY)
>
> /*
> * Add a reference update to transaction. `new_oid` is the value that
> diff --git a/refs/files-backend.c b/refs/files-backend.c
> index c7f3f4e591..1440b69b87 100644
> --- a/refs/files-backend.c
> +++ b/refs/files-backend.c
> @@ -2993,6 +2993,8 @@ static int files_transaction_finish(struct ref_store *ref_store,
> struct ref_update *update = transaction->updates[i];
> struct ref_lock *lock = update->backend_data;
>
> + if (update->flags & REF_CREATE_ONLY && refs_ref_exists(ref_store, update->refname))
> + continue;
> if (update->flags & REF_NEEDS_COMMIT ||
> update->flags & REF_LOG_ONLY) {
> if (parse_and_write_reflog(refs, update, lock, err)) {
> @@ -3031,6 +3033,8 @@ static int files_transaction_finish(struct ref_store *ref_store,
> */
> for (i = 0; i < transaction->nr; i++) {
> struct ref_update *update = transaction->updates[i];
> + if (update->flags & REF_CREATE_ONLY && refs_ref_exists(ref_store, update->refname))
> + continue;
> if (update->flags & REF_DELETING &&
> !(update->flags & REF_LOG_ONLY) &&
> !(update->flags & REF_IS_PRUNING)) {
> @@ -3061,6 +3065,8 @@ static int files_transaction_finish(struct ref_store *ref_store,
> for (i = 0; i < transaction->nr; i++) {
> struct ref_update *update = transaction->updates[i];
> struct ref_lock *lock = update->backend_data;
> + if (update->flags & REF_CREATE_ONLY && refs_ref_exists(ref_store, update->refname))
> + continue;
>
> if (update->flags & REF_DELETING &&
> !(update->flags & REF_LOG_ONLY)) {
> @@ -3085,6 +3091,8 @@ static int files_transaction_finish(struct ref_store *ref_store,
>
> for (i = 0; i < transaction->nr; i++) {
> struct ref_update *update = transaction->updates[i];
> + if (update->flags & REF_CREATE_ONLY && refs_ref_exists(ref_store, update->refname))
> + continue;
>
> if (update->flags & REF_DELETED_RMDIR) {
> /*
> diff --git a/reset.c b/reset.c
> index b22b1be792..8dce5f2133 100644
> --- a/reset.c
> +++ b/reset.c
> @@ -75,7 +75,7 @@ static int update_refs(const struct reset_head_opts *opts,
> UPDATE_REFS_MSG_ON_ERR);
> if (!ret)
> ret = refs_update_symref(get_main_ref_store(the_repository),
> - "HEAD", switch_to_branch,
> + "HEAD", switch_to_branch, 0,
> reflog_head);
> }
> if (!ret && run_hook)
> diff --git a/sequencer.c b/sequencer.c
> index 8d01cd50ac..924a78dab8 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, 0, msg)) {
> res = error(_("could not update HEAD to %s"),
> head_ref.buf);
> goto cleanup_head_ref;
> diff --git a/setup.c b/setup.c
> index 29f8673921..6a1fdef2c3 100644
> --- a/setup.c
> +++ b/setup.c
> @@ -2169,7 +2169,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, 0, NULL) < 0)
> exit(1);
> free(ref);
> }
> diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
> index 65346dee55..90af41edce 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, 0, logmsg);
> }
>
> static struct flag_definition transaction_flags[] = {
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option
2024-09-21 13:40 ` Phillip Wood
@ 2024-09-21 22:19 ` Bence Ferdinandy
2024-09-22 16:56 ` Junio C Hamano
2024-09-29 22:58 ` Bence Ferdinandy
0 siblings, 2 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-09-21 22:19 UTC (permalink / raw)
To: phillip.wood, git
Cc: Junio C Hamano, Taylor Blau, Patrick Steinhardt,
René Scharfe, Johannes Schindelin
On Sat Sep 21, 2024 at 15:40, Phillip Wood <phillip.wood123@gmail.com> wrote:
> On 19/09/2024 13:13, Bence Ferdinandy wrote:
> > Add a new REF_CREATE_ONLY flag for use by the files backend which will
> > only update the symref if it doesn't already exist. Add the possibility
> > to pass extra flags to refs_update_symref so that it can utilize this
> > new flag.
>
> I'm not sure we need a new flag to do this as it is already supported by
> the ref transaction api.
Thanks, I was not aware of ref_transaction_create. It also seems to return with
TRANSACTION_NAME_CONFLICT so we should be able to see from the error code if
indeed the existence was the problem or something else went wrong.
Best,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option
2024-09-21 22:19 ` Bence Ferdinandy
@ 2024-09-22 16:56 ` Junio C Hamano
2024-09-29 22:58 ` Bence Ferdinandy
1 sibling, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-09-22 16:56 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: phillip.wood, git, Taylor Blau, Patrick Steinhardt,
René Scharfe, Johannes Schindelin
"Bence Ferdinandy" <bence@ferdinandy.com> writes:
> On Sat Sep 21, 2024 at 15:40, Phillip Wood <phillip.wood123@gmail.com> wrote:
>> On 19/09/2024 13:13, Bence Ferdinandy wrote:
>> > Add a new REF_CREATE_ONLY flag for use by the files backend which will
>> > only update the symref if it doesn't already exist. Add the possibility
>> > to pass extra flags to refs_update_symref so that it can utilize this
>> > new flag.
>>
>> I'm not sure we need a new flag to do this as it is already supported by
>> the ref transaction api.
>
> Thanks, I was not aware of ref_transaction_create. It also seems to return with
> TRANSACTION_NAME_CONFLICT so we should be able to see from the error code if
> indeed the existence was the problem or something else went wrong.
Thank you for working on this, and thanks, Phillip, for a useful
suggestion.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option
2024-09-21 22:19 ` Bence Ferdinandy
2024-09-22 16:56 ` Junio C Hamano
@ 2024-09-29 22:58 ` Bence Ferdinandy
2024-09-30 6:40 ` Patrick Steinhardt
1 sibling, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-09-29 22:58 UTC (permalink / raw)
To: phillip.wood, git
Cc: Junio C Hamano, Taylor Blau, Patrick Steinhardt,
René Scharfe, Johannes Schindelin
On Sun Sep 22, 2024 at 00:19, Bence Ferdinandy <bence@ferdinandy.com> wrote:
>
> On Sat Sep 21, 2024 at 15:40, Phillip Wood <phillip.wood123@gmail.com> wrote:
>> On 19/09/2024 13:13, Bence Ferdinandy wrote:
>> > Add a new REF_CREATE_ONLY flag for use by the files backend which will
>> > only update the symref if it doesn't already exist. Add the possibility
>> > to pass extra flags to refs_update_symref so that it can utilize this
>> > new flag.
>>
>> I'm not sure we need a new flag to do this as it is already supported by
>> the ref transaction api.
>
> Thanks, I was not aware of ref_transaction_create. It also seems to return with
> TRANSACTION_NAME_CONFLICT so we should be able to see from the error code if
> indeed the existence was the problem or something else went wrong.
Unfortunately, it seems that my reading of the code did not pass practice. When
using ref_transaction_create ref_transaction_commit will return with -2 if the
reference already exists, but it also returns with -2 for various other issues,
like if the lock file already exists. I could parse the error message to see
what was the cause, but that doesn't feel like a robust solution. Since fetch
should _not_ error out on this, I think the REF_CREATE_ONLY flag is warranted.
As it stands, it would serve a different purpose than ref_transaction_create,
i.e. a "silent" create-only.
I'll send a v4 tomorrow hopefully.
Best,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option
2024-09-29 22:58 ` Bence Ferdinandy
@ 2024-09-30 6:40 ` Patrick Steinhardt
2024-09-30 9:27 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Patrick Steinhardt @ 2024-09-30 6:40 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: phillip.wood, git, Junio C Hamano, Taylor Blau, René Scharfe,
Johannes Schindelin
On Mon, Sep 30, 2024 at 12:58:05AM +0200, Bence Ferdinandy wrote:
>
> On Sun Sep 22, 2024 at 00:19, Bence Ferdinandy <bence@ferdinandy.com> wrote:
> >
> > On Sat Sep 21, 2024 at 15:40, Phillip Wood <phillip.wood123@gmail.com> wrote:
> >> On 19/09/2024 13:13, Bence Ferdinandy wrote:
> >> > Add a new REF_CREATE_ONLY flag for use by the files backend which will
> >> > only update the symref if it doesn't already exist. Add the possibility
> >> > to pass extra flags to refs_update_symref so that it can utilize this
> >> > new flag.
> >>
> >> I'm not sure we need a new flag to do this as it is already supported by
> >> the ref transaction api.
> >
> > Thanks, I was not aware of ref_transaction_create. It also seems to return with
> > TRANSACTION_NAME_CONFLICT so we should be able to see from the error code if
> > indeed the existence was the problem or something else went wrong.
>
> Unfortunately, it seems that my reading of the code did not pass practice. When
> using ref_transaction_create ref_transaction_commit will return with -2 if the
> reference already exists, but it also returns with -2 for various other issues,
> like if the lock file already exists. I could parse the error message to see
> what was the cause, but that doesn't feel like a robust solution. Since fetch
> should _not_ error out on this, I think the REF_CREATE_ONLY flag is warranted.
> As it stands, it would serve a different purpose than ref_transaction_create,
> i.e. a "silent" create-only.
>
> I'll send a v4 tomorrow hopefully.
I don't think that is a good reason to introduce this new flag though.
If we need to have a proper way to identify this specific failure case
we should rather update the already-existing mechanism to give us useful
signals, shouldn't we?
The problem with this flag is that it basically duplicates functionality
that already exists, and it needs to be wired up by every ref backend
that we have and that we're adding in the future. Your patch for example
only implements the functionality for the "files" backend, but it must
also be wired up for the "reftable" backend or otherwise it would be
broken.
Another issue is that it gives you more ways to create nonsensical ref
updates. With it you could for example create requests with a non-zero
old object ID, and if it has `REF_CREATE_ONLY` set it would never be
possible to fulfill the request. There's probably other cases where you
can create nonsensical ref updates already, but we shouldn't add more
ways of doing that.
Mind you, if we go the way I propose and improve the error reporting
we'd also have to adapt both backends to do so. But that would be
plugging a gap for which we have no proper solution right now instead of
circumventing the current design by duplicating the functionality that
we already have in a way that makes us able to handle this.
Patrick
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option
2024-09-30 6:40 ` Patrick Steinhardt
@ 2024-09-30 9:27 ` Bence Ferdinandy
2024-09-30 22:19 ` [PATCH v4 0/5] improve handling of remote/HEAD Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-09-30 9:27 UTC (permalink / raw)
To: Patrick Steinhardt
Cc: phillip.wood, git, Junio C Hamano, Taylor Blau, René Scharfe,
Johannes Schindelin
On Mon Sep 30, 2024 at 08:40, Patrick Steinhardt <ps@pks.im> wrote:
> I don't think that is a good reason to introduce this new flag though.
> If we need to have a proper way to identify this specific failure case
> we should rather update the already-existing mechanism to give us useful
> signals, shouldn't we?
>
> The problem with this flag is that it basically duplicates functionality
> that already exists, and it needs to be wired up by every ref backend
> that we have and that we're adding in the future. Your patch for example
> only implements the functionality for the "files" backend, but it must
> also be wired up for the "reftable" backend or otherwise it would be
> broken.
>
> Another issue is that it gives you more ways to create nonsensical ref
> updates. With it you could for example create requests with a non-zero
> old object ID, and if it has `REF_CREATE_ONLY` set it would never be
> possible to fulfill the request. There's probably other cases where you
> can create nonsensical ref updates already, but we shouldn't add more
> ways of doing that.
>
> Mind you, if we go the way I propose and improve the error reporting
> we'd also have to adapt both backends to do so. But that would be
> plugging a gap for which we have no proper solution right now instead of
> circumventing the current design by duplicating the functionality that
> we already have in a way that makes us able to handle this.
>
> Patrick
You make a convincing argument :) I'll try to see if I can add another
transaction error code for when you only want to create not overwrite and the
ref already exists and pass it up (for both files and reftables, you also don't
mention it, but I think this would not concern packed after a quick glance at
the code).
Best,
Bence
--
bence.ferdinandy.com
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v4 0/5] improve handling of remote/HEAD
2024-09-30 9:27 ` Bence Ferdinandy
@ 2024-09-30 22:19 ` Bence Ferdinandy
2024-09-30 22:19 ` [PATCH v4 1/5] refs_update_symref: atomically record overwritten ref Bence Ferdinandy
` (5 more replies)
0 siblings, 6 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-09-30 22:19 UTC (permalink / raw)
To: git
Cc: phillip.wood, Junio C Hamano, Taylor Blau, René Scharfe,
Johannes Schindelin, Bence Ferdinandy
This version has dropped some previous patches in favour of new ones.
These solve the problem of atomically deciding what the head was before
we updated it and also not reinventing the wheel for having an "create
this ref, but if it already exists, fail silently" call.
Since now both changes to remote set-head and fetch utilize some common
preceding patches I have again included in this series my changes to
remote set-head. Sorry for the mess tangent there.
All tests (that run on my system) pass for the patches up to the
last patch, which changes fetch. For the last patch I updated some of
the test where it was trivial (lot of remote/HEAD -> something appeared
as expected), but there is still work to be done there (i.e. figure out
which tests need updating and which are actual bugs in the patch).
One specific thing I wanted to mention here. Junio asked earlier what
happens on fetch, when we have a config like this:
> If I am only fetching refs (or HEAD) in FETCH_HEAD for immediate
> consumtion by doing "git pull bence HEAD" with something like
>
> [remote "bence"]
> URL = http://github.com/bence/git
In this case setting head will error out with the generic error message
I put at the end.
Bence Ferdinandy (5):
refs_update_symref: atomically record overwritten ref
set-head: better output for --auto
transaction: add TRANSACTION_CREATE_EXISTS error
refs_update_symref: add create_only option
fetch: set remote/HEAD if it does not exist
builtin/branch.c | 2 +-
builtin/checkout.c | 5 ++-
builtin/clone.c | 8 ++--
builtin/fetch.c | 83 +++++++++++++++++++++++++++++++++++++++
builtin/notes.c | 3 +-
builtin/remote.c | 43 ++++++++++++++++----
builtin/symbolic-ref.c | 2 +-
builtin/worktree.c | 2 +-
refs.c | 35 ++++++++++++-----
refs.h | 7 +++-
refs/files-backend.c | 29 ++++++++++----
refs/refs-internal.h | 8 ++++
refs/reftable-backend.c | 6 ++-
reset.c | 2 +-
sequencer.c | 3 +-
setup.c | 3 +-
t/helper/test-ref-store.c | 2 +-
t/t5505-remote.sh | 13 +++++-
t/t5514-fetch-multiple.sh | 9 +++++
19 files changed, 222 insertions(+), 43 deletions(-)
--
2.47.0.rc0.5.gf1cffeb8df.dirty
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v4 1/5] refs_update_symref: atomically record overwritten ref
2024-09-30 22:19 ` [PATCH v4 0/5] improve handling of remote/HEAD Bence Ferdinandy
@ 2024-09-30 22:19 ` Bence Ferdinandy
2024-10-01 19:10 ` Junio C Hamano
2024-09-30 22:19 ` [PATCH v4 2/5] set-head: better output for --auto Bence Ferdinandy
` (4 subsequent siblings)
5 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-09-30 22:19 UTC (permalink / raw)
To: git
Cc: phillip.wood, Junio C Hamano, Taylor Blau, René Scharfe,
Johannes Schindelin, Bence Ferdinandy
When updating a symref it's currently not possible to know for sure what
was the previous value that was overwritten. Make use of
ref_transaction's atomicity and record the previous value there. Add
a new variable to refs_update_symref's signature to be able to pass this
information back up to callers.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
---
Notes:
v4: new patch
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 | 8 ++++++--
refs.h | 3 ++-
refs/files-backend.c | 1 +
refs/refs-internal.h | 8 ++++++++
reset.c | 2 +-
sequencer.c | 2 +-
setup.c | 2 +-
t/helper/test-ref-store.c | 2 +-
15 files changed, 33 insertions(+), 19 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..301db0dcdc 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 *before_target)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
@@ -2130,6 +2131,10 @@ int refs_update_symref(struct ref_store *refs, const char *ref,
}
strbuf_release(&err);
+
+ if (before_target && transaction->updates[0]->before_target)
+ strbuf_addstr(before_target, transaction->updates[0]->before_target);
+
if (transaction)
ref_transaction_free(transaction);
@@ -2948,4 +2953,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..f38616db84 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 *before_target);
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 0824c0b8a9..8415f2d020 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2577,6 +2577,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
}
update->backend_data = lock;
+ update->before_target = xstrdup_or_null(referent.buf);
if (update->type & REF_ISSYMREF) {
if (update->flags & REF_NO_DEREF) {
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 2313c830d8..7df3e6271e 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -104,6 +104,14 @@ struct ref_update {
*/
const char *old_target;
+ /*
+ * The previous target before applying new_target will be
+ * written here, to be used by callers when they do not want to
+ * check old_target during the transaction, but do want to know
+ * what it was.
+ */
+ const char *before_target;
+
/*
* One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,
* REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.
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.rc0.5.gf1cffeb8df.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v4 2/5] set-head: better output for --auto
2024-09-30 22:19 ` [PATCH v4 0/5] improve handling of remote/HEAD Bence Ferdinandy
2024-09-30 22:19 ` [PATCH v4 1/5] refs_update_symref: atomically record overwritten ref Bence Ferdinandy
@ 2024-09-30 22:19 ` Bence Ferdinandy
2024-10-01 22:54 ` Junio C Hamano
2024-09-30 22:19 ` [PATCH v4 3/5] transaction: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
` (3 subsequent siblings)
5 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-09-30 22:19 UTC (permalink / raw)
To: git
Cc: phillip.wood, Junio C Hamano, Taylor Blau, René Scharfe,
Johannes Schindelin, 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.
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
builtin/remote.c | 36 ++++++++++++++++++++++++++++++------
t/t5505-remote.sh | 13 ++++++++++++-
2 files changed, 42 insertions(+), 7 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index d8ff440027..c61a7800ff 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1399,12 +1399,34 @@ static int show(int argc, const char **argv, const char *prefix)
return result;
}
+static void report_auto(const char *remote, const char *head_name,
+ struct strbuf *buf_prev) {
+ struct strbuf buf_prefix = STRBUF_INIT;
+ const char *prev_head;
+
+ 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
+ printf("'%s/HEAD' is now created and points to '%s'\n",
+ remote, head_name);
+}
+
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);
+
struct option options[] = {
OPT_BOOL('a', "auto", &opt_a,
N_("set refs/remotes/<name>/HEAD according to remote")),
@@ -1434,7 +1456,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,17 +1464,19 @@ 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", &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_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..711d6e5598 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -434,7 +434,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 set to main" >expect &&
+ echo "'\''two/HEAD'\'' is now created and points to '\''main'\''" >expect &&
test_cmp expect output
)
'
@@ -453,6 +453,17 @@ 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.rc0.5.gf1cffeb8df.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v4 3/5] transaction: add TRANSACTION_CREATE_EXISTS error
2024-09-30 22:19 ` [PATCH v4 0/5] improve handling of remote/HEAD Bence Ferdinandy
2024-09-30 22:19 ` [PATCH v4 1/5] refs_update_symref: atomically record overwritten ref Bence Ferdinandy
2024-09-30 22:19 ` [PATCH v4 2/5] set-head: better output for --auto Bence Ferdinandy
@ 2024-09-30 22:19 ` Bence Ferdinandy
2024-09-30 22:19 ` [PATCH v4 4/5] refs_update_symref: add create_only option Bence Ferdinandy
` (2 subsequent siblings)
5 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-09-30 22:19 UTC (permalink / raw)
To: git
Cc: phillip.wood, Junio C Hamano, Taylor Blau, René Scharfe,
Johannes Schindelin, 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
refs.h | 4 +++-
refs/files-backend.c | 28 ++++++++++++++++++++--------
refs/reftable-backend.c | 6 ++++--
3 files changed, 27 insertions(+), 11 deletions(-)
diff --git a/refs.h b/refs.h
index f38616db84..166affbc89 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 8415f2d020..272ad81315 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;
}
/*
@@ -2603,9 +2607,13 @@ 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 {
+ int checkret;
+ checkret = check_old_oid(update, &lock->old_oid, err);
+ if (checkret) {
+ ret = checkret;
+ goto out;
+ }
}
} else {
/*
@@ -2636,9 +2644,13 @@ 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 {
+ int checkret;
+ checkret = check_old_oid(update, &lock->old_oid, err);
+ if (checkret) {
+ ret = checkret;
+ goto out;
+ }
}
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index f5f957e6de..f66bbf5d49 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1197,10 +1197,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"),
@@ -1212,7 +1215,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.rc0.5.gf1cffeb8df.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v4 4/5] refs_update_symref: add create_only option
2024-09-30 22:19 ` [PATCH v4 0/5] improve handling of remote/HEAD Bence Ferdinandy
` (2 preceding siblings ...)
2024-09-30 22:19 ` [PATCH v4 3/5] transaction: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
@ 2024-09-30 22:19 ` Bence Ferdinandy
2024-09-30 22:19 ` [PATCH v4 5/5] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
2024-10-03 19:21 ` [PATCH v4 0/5] improve handling of remote/HEAD Junio C Hamano
5 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-09-30 22:19 UTC (permalink / raw)
To: git
Cc: phillip.wood, Junio C Hamano, Taylor Blau, René Scharfe,
Johannes Schindelin, 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
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 | 29 +++++++++++++++++++++--------
refs.h | 2 +-
reset.c | 2 +-
sequencer.c | 3 ++-
setup.c | 3 ++-
t/helper/test-ref-store.c | 2 +-
13 files changed, 47 insertions(+), 25 deletions(-)
diff --git a/builtin/branch.c b/builtin/branch.c
index 6c87690b58..3c9bc39800 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, false))
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..d9514e9b7a 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, false) < 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, false);
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..25d0bcf3aa 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, false) < 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, false) < 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, false) < 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..fb49e491c3 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, false))
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 c61a7800ff..a1cec6276d 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, false))
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, false))
die(_("creating '%s' failed"), buf.buf);
display_progress(progress, ++refs_renamed_nr);
}
@@ -1466,7 +1468,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, false))
result |= error(_("Could not setup %s"), buf.buf);
else if (opt_a) {
report_auto(argv[0], head_name, &buf_prev);
diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c
index 7728fbc3c1..169f5f5340 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, false);
break;
default:
usage_with_options(git_symbolic_ref_usage, options);
diff --git a/builtin/worktree.c b/builtin/worktree.c
index a7ab4193c1..46b515a243 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, false);
if (ret)
goto done;
diff --git a/refs.c b/refs.c
index 301db0dcdc..e012b89ee9 100644
--- a/refs.c
+++ b/refs.c
@@ -2115,19 +2115,32 @@ 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 *before_target)
+ struct strbuf *before_target, bool create_only)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
- int ret = 0;
+ int ret = 0, create_ret = 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_commit(transaction, &err)) {
- ret = error("%s", err.buf);
+ if (create_only) {
+ if (!transaction ||
+ ref_transaction_create(transaction, ref, NULL, target,
+ REF_NO_DEREF, logmsg, &err)) {
+ ret = error("%s", err.buf);
+ }
+ else {
+ create_ret = ref_transaction_commit(transaction, &err);
+ if (create_ret && create_ret != TRANSACTION_CREATE_EXISTS)
+ ret = error("%s", err.buf);
+ }
+ }
+ else
+ if (!transaction ||
+ ref_transaction_update(transaction, ref, NULL, NULL,
+ target, NULL, REF_NO_DEREF,
+ logmsg, &err) ||
+ ref_transaction_commit(transaction, &err)) {
+ ret = error("%s", err.buf);
}
strbuf_release(&err);
diff --git a/refs.h b/refs.h
index 166affbc89..b64fd2318b 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 *before_target);
+ struct strbuf *before_target, bool create_only);
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
diff --git a/reset.c b/reset.c
index cc36a9ed56..674896fb61 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, false);
}
if (!ret && run_hook)
run_hooks_l(the_repository, "post-checkout",
diff --git a/sequencer.c b/sequencer.c
index 23b162924c..1a46ef56ba 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, false)) {
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..67abbfaf3c 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, false) < 0)
exit(1);
free(ref);
}
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index a911302bea..b6b06bb2e9 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, false);
}
static struct flag_definition transaction_flags[] = {
--
2.47.0.rc0.5.gf1cffeb8df.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v4 5/5] fetch: set remote/HEAD if it does not exist
2024-09-30 22:19 ` [PATCH v4 0/5] improve handling of remote/HEAD Bence Ferdinandy
` (3 preceding siblings ...)
2024-09-30 22:19 ` [PATCH v4 4/5] refs_update_symref: add create_only option Bence Ferdinandy
@ 2024-09-30 22:19 ` Bence Ferdinandy
2024-10-03 19:21 ` [PATCH v4 0/5] improve handling of remote/HEAD Junio C Hamano
5 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-09-30 22:19 UTC (permalink / raw)
To: git
Cc: phillip.wood, Junio C Hamano, Taylor Blau, René Scharfe,
Johannes Schindelin, 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.
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
builtin/fetch.c | 83 +++++++++++++++++++++++++++++++++++++++
t/t5514-fetch-multiple.sh | 9 +++++
2 files changed, 92 insertions(+)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index c900f57721..f10a9d5371 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1577,6 +1577,82 @@ 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;
+
+ 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))
+ ;
+ else if (prev_head) {
+ 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 |= 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", &b_local_head, true))
+ result |= error(_("Could not setup %s"), b_head.buf);
+ 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)
@@ -1646,6 +1722,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,
@@ -1790,6 +1868,10 @@ static int do_fetch(struct transport *transport,
"you need to specify exactly one branch with the --set-upstream option"));
}
}
+ if (set_head(remote_refs))
+ printf("Ran into issues with creating \'%s/HEAD\',\n"
+ "use \'git remote set-head -a %s\' to investigate.\n",
+ gtransport->remote->name,gtransport->remote->name);
cleanup:
if (retcode) {
@@ -2020,6 +2102,7 @@ static int fetch_multiple(struct string_list *list, int max_children,
return !!result;
}
+
/*
* Fetching from the promisor remote should use the given filter-spec
* or inherit the default filter-spec from the config.
diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh
index 579872c258..ef5874b7b3 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
@@ -221,14 +227,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
--
2.47.0.rc0.5.gf1cffeb8df.dirty
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v4 1/5] refs_update_symref: atomically record overwritten ref
2024-09-30 22:19 ` [PATCH v4 1/5] refs_update_symref: atomically record overwritten ref Bence Ferdinandy
@ 2024-10-01 19:10 ` Junio C Hamano
0 siblings, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-10-01 19:10 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, Taylor Blau, René Scharfe,
Johannes Schindelin
Bence Ferdinandy <bence@ferdinandy.com> writes:
> diff --git a/refs.c b/refs.c
> index 5f729ed412..301db0dcdc 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 *before_target)
> {
> struct ref_transaction *transaction;
> struct strbuf err = STRBUF_INIT;
> @@ -2130,6 +2131,10 @@ int refs_update_symref(struct ref_store *refs, const char *ref,
Let's extend the precontext of this hunk a bit. The function begins
like this:
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_commit(transaction, &err)) {
ret = error("%s", err.buf);
> }
> strbuf_release(&err);
We begin a transaction, update ref to point to target in the
transaction, and commit the transaction. An error at any stage of
this three-step process will bypass the rest and we give an error
message.
> +
> + if (before_target && transaction->updates[0]->before_target)
> + strbuf_addstr(before_target, transaction->updates[0]->before_target);
What if ref_store_transaction_begin() failed?
If we want to say "we append the before_target recorded in the
transaction to the caller-supplied strbuf only when we manage to do
the update, and we leave before_target intact otherwise" We'd at
least need
if (transaction && before_target &&
transaction->updates[0]->before_target)
wouldn't it? Like the code that frees it (below), this new call
should be prepared to see !transaction.
> if (transaction)
> ref_transaction_free(transaction);
> @@ -2948,4 +2953,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);
> }
> -
Good (even though it is unrelated to the topic of this series).
> diff --git a/refs.h b/refs.h
> index 108dfc93b3..f38616db84 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 *before_target);
>
> enum action_on_err {
> UPDATE_REFS_MSG_ON_ERR,
> diff --git a/refs/files-backend.c b/refs/files-backend.c
> index 0824c0b8a9..8415f2d020 100644
> --- a/refs/files-backend.c
> +++ b/refs/files-backend.c
> @@ -2577,6 +2577,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
> }
>
> update->backend_data = lock;
> + update->before_target = xstrdup_or_null(referent.buf);
OK, so this comes from the backends, as they are the only thing that
knows what the current value is (the caller can only indirectly infer
if it has old_target, in which case the backend checks if the attempt
is stale).
Do we need a corresponding change for the other, reftable, backend?
> 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 v4 2/5] set-head: better output for --auto
2024-09-30 22:19 ` [PATCH v4 2/5] set-head: better output for --auto Bence Ferdinandy
@ 2024-10-01 22:54 ` Junio C Hamano
0 siblings, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-10-01 22:54 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, Taylor Blau, René Scharfe,
Johannes Schindelin
Bence Ferdinandy <bence@ferdinandy.com> writes:
> +static void report_auto(const char *remote, const char *head_name,
> + struct strbuf *buf_prev) {
> + struct strbuf buf_prefix = STRBUF_INIT;
> + const char *prev_head;
> +
> + 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
> + printf("'%s/HEAD' is now created and points to '%s'\n",
> + remote, head_name);
> +}
OK.
> 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);
> +
What does the blank line between "head_name" and "refs" declaration
indicate? Is "refs" so special that it must stand separately from
all others?
The primary purpose of this step is to pass a strbuf to
refs_update_symref() so that we can learn what the original value of
the HEAD was, so that report_auto() can give a more intelligent
message. The introduction of "refs" to avoid repetitive calls to
get_main_ref_store() is orthogonal to that. It is a good change,
but it should stand on its own, as a separate "preparatory clean-up"
patch.
> diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
> index 532035933f..711d6e5598 100755
> --- a/t/t5505-remote.sh
> +++ b/t/t5505-remote.sh
> @@ -434,7 +434,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 set to main" >expect &&
> + echo "'\''two/HEAD'\'' is now created and points to '\''main'\''" >expect &&
> test_cmp expect output
> )
> '
> @@ -453,6 +453,17 @@ 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
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v4 0/5] improve handling of remote/HEAD
2024-09-30 22:19 ` [PATCH v4 0/5] improve handling of remote/HEAD Bence Ferdinandy
` (4 preceding siblings ...)
2024-09-30 22:19 ` [PATCH v4 5/5] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
@ 2024-10-03 19:21 ` Junio C Hamano
2024-10-03 19:48 ` Bence Ferdinandy
2024-10-09 13:57 ` [PATCH v5 1/6] refs_update_symref: atomically record overwritten ref Bence Ferdinandy
5 siblings, 2 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-10-03 19:21 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, Taylor Blau, René Scharfe,
Johannes Schindelin
Bence Ferdinandy <bence@ferdinandy.com> writes:
> Bence Ferdinandy (5):
> refs_update_symref: atomically record overwritten ref
> set-head: better output for --auto
> transaction: add TRANSACTION_CREATE_EXISTS error
> refs_update_symref: add create_only option
> fetch: set remote/HEAD if it does not exist
>
> builtin/branch.c | 2 +-
> builtin/checkout.c | 5 ++-
> builtin/clone.c | 8 ++--
> builtin/fetch.c | 83 +++++++++++++++++++++++++++++++++++++++
> builtin/notes.c | 3 +-
> builtin/remote.c | 43 ++++++++++++++++----
> builtin/symbolic-ref.c | 2 +-
> builtin/worktree.c | 2 +-
> refs.c | 35 ++++++++++++-----
> refs.h | 7 +++-
> refs/files-backend.c | 29 ++++++++++----
> refs/refs-internal.h | 8 ++++
> refs/reftable-backend.c | 6 ++-
> reset.c | 2 +-
> sequencer.c | 3 +-
> setup.c | 3 +-
> t/helper/test-ref-store.c | 2 +-
> t/t5505-remote.sh | 13 +++++-
> t/t5514-fetch-multiple.sh | 9 +++++
> 19 files changed, 222 insertions(+), 43 deletions(-)
These seem to break some of the tests, either standalone or when
merged to 'seen'.
I have the topic queued directly on top of e9356ba3 (another batch
after 2.47-rc0, 2024-09-30); here is how the summary report looks
like.
Thanks.
Test Summary Report
-------------------
t0410-partial-clone.sh (Wstat: 256 (exited 1) Tests: 38 Failed: 1)
Failed test: 32
Non-zero exit status: 1
t4207-log-decoration-colors.sh (Wstat: 256 (exited 1) Tests: 4 Failed: 1)
Failed test: 2
Non-zero exit status: 1
t5300-pack-object.sh (Wstat: 256 (exited 1) Tests: 56 Failed: 1)
Failed test: 51
Non-zero exit status: 1
t5512-ls-remote.sh (Wstat: 256 (exited 1) Tests: 40 Failed: 1)
Failed test: 32
Non-zero exit status: 1
t5527-fetch-odd-refs.sh (Wstat: 256 (exited 1) Tests: 5 Failed: 1)
Failed test: 4
Non-zero exit status: 1
t5514-fetch-multiple.sh (Wstat: 256 (exited 1) Tests: 25 Failed: 4)
Failed tests: 9-10, 18, 24
Non-zero exit status: 1
t5521-pull-options.sh (Wstat: 256 (exited 1) Tests: 22 Failed: 8)
Failed tests: 2-9
Non-zero exit status: 1
t5505-remote.sh (Wstat: 256 (exited 1) Tests: 114 Failed: 7)
Failed tests: 4, 31, 51-52, 54-56
Non-zero exit status: 1
t5574-fetch-output.sh (Wstat: 256 (exited 1) Tests: 14 Failed: 3)
Failed tests: 11-13
Non-zero exit status: 1
t5703-upload-pack-ref-in-want.sh (Wstat: 256 (exited 1) Tests: 26 Failed: 1)
Failed test: 12
Non-zero exit status: 1
t5616-partial-clone.sh (Wstat: 256 (exited 1) Tests: 46 Failed: 2)
Failed tests: 10, 22
Non-zero exit status: 1
t5516-fetch-push.sh (Wstat: 256 (exited 1) Tests: 120 Failed: 1)
Failed test: 102
Non-zero exit status: 1
t7814-grep-recurse-submodules.sh (Wstat: 256 (exited 1) Tests: 34 Failed: 1)
Failed test: 33
Non-zero exit status: 1
t7900-maintenance.sh (Wstat: 256 (exited 1) Tests: 53 Failed: 1)
Failed test: 23
Non-zero exit status: 1
t7406-submodule-update.sh (Wstat: 256 (exited 1) Tests: 68 Failed: 1)
Failed test: 59
Non-zero exit status: 1
t9211-scalar-clone.sh (Wstat: 256 (exited 1) Tests: 13 Failed: 2)
Failed tests: 8-9
Non-zero exit status: 1
t9210-scalar.sh (Wstat: 256 (exited 1) Tests: 21 Failed: 1)
Failed test: 11
Non-zero exit status: 1
t9902-completion.sh (Wstat: 256 (exited 1) Tests: 256 Failed: 56)
Failed tests: 54-55, 59, 61, 63, 71-75, 77, 86, 88-89
92-93, 95, 101, 113, 116-117, 119-129, 138-141
143-157, 162-163, 168, 170-171
Non-zero exit status: 1
Files=1029, Tests=31709, 371 wallclock secs (11.74 usr 4.74 sys + 853.87 cusr 6465.88 csys = 7336.23 CPU)
Result: FAIL
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v4 0/5] improve handling of remote/HEAD
2024-10-03 19:21 ` [PATCH v4 0/5] improve handling of remote/HEAD Junio C Hamano
@ 2024-10-03 19:48 ` Bence Ferdinandy
2024-10-09 13:57 ` [PATCH v5 1/6] refs_update_symref: atomically record overwritten ref Bence Ferdinandy
1 sibling, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-03 19:48 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, phillip.wood, Taylor Blau, René Scharfe,
Johannes Schindelin
2024. okt. 3. 21:22:13 Junio C Hamano <gitster@pobox.com>:
> Bence Ferdinandy <bence@ferdinandy.com> writes:
>
>> Bence Ferdinandy (5):
>> refs_update_symref: atomically record overwritten ref
>> set-head: better output for --auto
>> transaction: add TRANSACTION_CREATE_EXISTS error
>> refs_update_symref: add create_only option
>> fetch: set remote/HEAD if it does not exist
>>
>> builtin/branch.c | 2 +-
>> builtin/checkout.c | 5 ++-
>> builtin/clone.c | 8 ++--
>> builtin/fetch.c | 83 +++++++++++++++++++++++++++++++++++++++
>> builtin/notes.c | 3 +-
>> builtin/remote.c | 43 ++++++++++++++++----
>> builtin/symbolic-ref.c | 2 +-
>> builtin/worktree.c | 2 +-
>> refs.c | 35 ++++++++++++-----
>> refs.h | 7 +++-
>> refs/files-backend.c | 29 ++++++++++----
>> refs/refs-internal.h | 8 ++++
>> refs/reftable-backend.c | 6 ++-
>> reset.c | 2 +-
>> sequencer.c | 3 +-
>> setup.c | 3 +-
>> t/helper/test-ref-store.c | 2 +-
>> t/t5505-remote.sh | 13 +++++-
>> t/t5514-fetch-multiple.sh | 9 +++++
>> 19 files changed, 222 insertions(+), 43 deletions(-)
>
> These seem to break some of the tests, either standalone or when
> merged to 'seen'.
>
> I have the topic queued directly on top of e9356ba3 (another batch
> after 2.47-rc0, 2024-09-30); here is how the summary report looks
> like.
>
> Thanks.
Sorry, I should have probably left in the RFC prefix to be more clear
about this... As I noted in the cover letter, the first four patches pass
the
tests, the last one doesn't (yet), as I'm still not quite sure if even
the
approach is fine. If those look good I'll send a v5 and also fix the
tests
as well. Although based on your review of the first 2 patches there's
already
some changes needed there, so I can just do a v5 with that and tests
fixed
and see if that version looks good or not.
I'm currently on a holiday, so my ETA on this is early next week.
Thanks,
Bence
>
> Test Summary Report
> -------------------
> t0410-partial-clone.sh (Wstat: 256 (exited 1)
> Tests: 38 Failed: 1)
> Failed test: 32
> Non-zero exit status: 1
> t4207-log-decoration-colors.sh (Wstat: 256 (exited 1)
> Tests: 4 Failed: 1)
> Failed test: 2
> Non-zero exit status: 1
> t5300-pack-object.sh (Wstat: 256 (exited 1)
> Tests: 56 Failed: 1)
> Failed test: 51
> Non-zero exit status: 1
> t5512-ls-remote.sh (Wstat: 256 (exited 1)
> Tests: 40 Failed: 1)
> Failed test: 32
> Non-zero exit status: 1
> t5527-fetch-odd-refs.sh (Wstat: 256 (exited 1)
> Tests: 5 Failed: 1)
> Failed test: 4
> Non-zero exit status: 1
> t5514-fetch-multiple.sh (Wstat: 256 (exited 1)
> Tests: 25 Failed: 4)
> Failed tests: 9-10, 18, 24
> Non-zero exit status: 1
> t5521-pull-options.sh (Wstat: 256 (exited 1)
> Tests: 22 Failed: 8)
> Failed tests: 2-9
> Non-zero exit status: 1
> t5505-remote.sh (Wstat: 256 (exited 1)
> Tests: 114 Failed: 7)
> Failed tests: 4, 31, 51-52, 54-56
> Non-zero exit status: 1
> t5574-fetch-output.sh (Wstat: 256 (exited 1)
> Tests: 14 Failed: 3)
> Failed tests: 11-13
> Non-zero exit status: 1
> t5703-upload-pack-ref-in-want.sh (Wstat: 256 (exited 1)
> Tests: 26 Failed: 1)
> Failed test: 12
> Non-zero exit status: 1
> t5616-partial-clone.sh (Wstat: 256 (exited 1)
> Tests: 46 Failed: 2)
> Failed tests: 10, 22
> Non-zero exit status: 1
> t5516-fetch-push.sh (Wstat: 256 (exited 1)
> Tests: 120 Failed: 1)
> Failed test: 102
> Non-zero exit status: 1
> t7814-grep-recurse-submodules.sh (Wstat: 256 (exited 1)
> Tests: 34 Failed: 1)
> Failed test: 33
> Non-zero exit status: 1
> t7900-maintenance.sh (Wstat: 256 (exited 1)
> Tests: 53 Failed: 1)
> Failed test: 23
> Non-zero exit status: 1
> t7406-submodule-update.sh (Wstat: 256 (exited 1)
> Tests: 68 Failed: 1)
> Failed test: 59
> Non-zero exit status: 1
> t9211-scalar-clone.sh (Wstat: 256 (exited 1)
> Tests: 13 Failed: 2)
> Failed tests: 8-9
> Non-zero exit status: 1
> t9210-scalar.sh (Wstat: 256 (exited 1)
> Tests: 21 Failed: 1)
> Failed test: 11
> Non-zero exit status: 1
> t9902-completion.sh (Wstat: 256 (exited 1)
> Tests: 256 Failed: 56)
> Failed tests: 54-55, 59, 61, 63, 71-75, 77, 86, 88-89
> 92-93, 95, 101, 113, 116-117, 119-129, 138-141
> 143-157, 162-163, 168, 170-171
> Non-zero exit status: 1
> Files=1029, Tests=31709, 371 wallclock secs (11.74 usr 4.74 sys +
> 853.87 cusr 6465.88 csys = 7336.23 CPU)
> Result: FAIL
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v5 1/6] refs_update_symref: atomically record overwritten ref
2024-10-03 19:21 ` [PATCH v4 0/5] improve handling of remote/HEAD Junio C Hamano
2024-10-03 19:48 ` Bence Ferdinandy
@ 2024-10-09 13:57 ` Bence Ferdinandy
2024-10-09 13:57 ` [PATCH v5 2/6] set-head: add new variable for readability Bence Ferdinandy
` (4 more replies)
1 sibling, 5 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-09 13:57 UTC (permalink / raw)
To: git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano, Bence Ferdinandy
When updating a symref it's currently not possible to know for sure what
was the previous value that was overwritten. Make use of
ref_transaction's atomicity and record the previous value there. Add
a new variable to refs_update_symref's signature to be able to pass this
information back up to callers.
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
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 | 8 ++++++--
refs.h | 3 ++-
refs/files-backend.c | 1 +
refs/refs-internal.h | 8 ++++++++
refs/reftable-backend.c | 1 +
reset.c | 2 +-
sequencer.c | 2 +-
setup.c | 2 +-
t/helper/test-ref-store.c | 2 +-
16 files changed, 34 insertions(+), 19 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..91cacee6f9 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 *before_target)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
@@ -2130,6 +2131,10 @@ int refs_update_symref(struct ref_store *refs, const char *ref,
}
strbuf_release(&err);
+
+ if (transaction && before_target && transaction->updates[0]->before_target)
+ strbuf_addstr(before_target, transaction->updates[0]->before_target);
+
if (transaction)
ref_transaction_free(transaction);
@@ -2948,4 +2953,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..f38616db84 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 *before_target);
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 0824c0b8a9..8415f2d020 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2577,6 +2577,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
}
update->backend_data = lock;
+ update->before_target = xstrdup_or_null(referent.buf);
if (update->type & REF_ISSYMREF) {
if (update->flags & REF_NO_DEREF) {
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 2313c830d8..7df3e6271e 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -104,6 +104,14 @@ struct ref_update {
*/
const char *old_target;
+ /*
+ * The previous target before applying new_target will be
+ * written here, to be used by callers when they do not want to
+ * check old_target during the transaction, but do want to know
+ * what it was.
+ */
+ const char *before_target;
+
/*
* One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,
* REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 3c96fbf66f..32330b6bc6 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1244,6 +1244,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
if (ret)
goto done;
}
+ u->before_target = xstrdup_or_null(referent.buf);
}
transaction->backend_data = tx_data;
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.6.g9542df9767
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v5 2/6] set-head: add new variable for readability
2024-10-09 13:57 ` [PATCH v5 1/6] refs_update_symref: atomically record overwritten ref Bence Ferdinandy
@ 2024-10-09 13:57 ` Bence Ferdinandy
2024-10-09 19:26 ` Junio C Hamano
2024-10-09 13:57 ` [PATCH v5 3/6] set-head: better output for --auto Bence Ferdinandy
` (3 subsequent siblings)
4 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-09 13:57 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.
---
Notes:
v5: new patch (split from the next patch as a preparatory step)
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.6.g9542df9767
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v5 3/6] set-head: better output for --auto
2024-10-09 13:57 ` [PATCH v5 1/6] refs_update_symref: atomically record overwritten ref Bence Ferdinandy
2024-10-09 13:57 ` [PATCH v5 2/6] set-head: add new variable for readability Bence Ferdinandy
@ 2024-10-09 13:57 ` Bence Ferdinandy
2024-10-09 20:53 ` Junio C Hamano
2024-10-09 13:57 ` [PATCH v5 4/6] transaction: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
` (2 subsequent siblings)
4 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-09 13:57 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.
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
builtin/remote.c | 30 ++++++++++++++++++++++++++----
t/t5505-remote.sh | 13 ++++++++++++-
2 files changed, 38 insertions(+), 5 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 353ffd2c43..2480128b88 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1399,10 +1399,30 @@ static int show(int argc, const char **argv, const char *prefix)
return result;
}
+static void report_auto(const char *remote, const char *head_name,
+ struct strbuf *buf_prev) {
+ struct strbuf buf_prefix = STRBUF_INIT;
+ const char *prev_head;
+
+ 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
+ printf("'%s/HEAD' is now created and points to '%s'\n",
+ remote, head_name);
+}
+
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 +1465,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_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..262a4de0aa 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -434,7 +434,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 set to main" >expect &&
+ echo "'\''two/HEAD'\'' is unchanged and points to '\''main'\''" >expect &&
test_cmp expect output
)
'
@@ -453,6 +453,17 @@ 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.6.g9542df9767
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v5 4/6] transaction: add TRANSACTION_CREATE_EXISTS error
2024-10-09 13:57 ` [PATCH v5 1/6] refs_update_symref: atomically record overwritten ref Bence Ferdinandy
2024-10-09 13:57 ` [PATCH v5 2/6] set-head: add new variable for readability Bence Ferdinandy
2024-10-09 13:57 ` [PATCH v5 3/6] set-head: better output for --auto Bence Ferdinandy
@ 2024-10-09 13:57 ` Bence Ferdinandy
2024-10-09 21:08 ` Junio C Hamano
2024-10-09 13:57 ` [PATCH v5 5/6] refs_update_symref: add create_only option Bence Ferdinandy
2024-10-09 13:57 ` [PATCH v5 6/6] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
4 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-09 13:57 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
refs.h | 4 +++-
refs/files-backend.c | 28 ++++++++++++++++++++--------
refs/reftable-backend.c | 6 ++++--
3 files changed, 27 insertions(+), 11 deletions(-)
diff --git a/refs.h b/refs.h
index f38616db84..166affbc89 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 8415f2d020..272ad81315 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;
}
/*
@@ -2603,9 +2607,13 @@ 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 {
+ int checkret;
+ checkret = check_old_oid(update, &lock->old_oid, err);
+ if (checkret) {
+ ret = checkret;
+ goto out;
+ }
}
} else {
/*
@@ -2636,9 +2644,13 @@ 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 {
+ int checkret;
+ checkret = check_old_oid(update, &lock->old_oid, err);
+ if (checkret) {
+ ret = checkret;
+ goto out;
+ }
}
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 32330b6bc6..c6b25ebac4 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.6.g9542df9767
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v5 5/6] refs_update_symref: add create_only option
2024-10-09 13:57 ` [PATCH v5 1/6] refs_update_symref: atomically record overwritten ref Bence Ferdinandy
` (2 preceding siblings ...)
2024-10-09 13:57 ` [PATCH v5 4/6] transaction: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
@ 2024-10-09 13:57 ` Bence Ferdinandy
2024-10-09 21:37 ` Junio C Hamano
2024-10-09 13:57 ` [PATCH v5 6/6] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
4 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-09 13:57 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
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 | 29 +++++++++++++++++++++--------
refs.h | 2 +-
reset.c | 2 +-
sequencer.c | 3 ++-
setup.c | 3 ++-
t/helper/test-ref-store.c | 2 +-
13 files changed, 47 insertions(+), 25 deletions(-)
diff --git a/builtin/branch.c b/builtin/branch.c
index 6c87690b58..3c9bc39800 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, false))
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..d9514e9b7a 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, false) < 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, false);
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..25d0bcf3aa 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, false) < 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, false) < 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, false) < 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..fb49e491c3 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, false))
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 2480128b88..79f22467f2 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, false))
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, false))
die(_("creating '%s' failed"), buf.buf);
display_progress(progress, ++refs_renamed_nr);
}
@@ -1465,7 +1467,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, false))
result |= error(_("Could not setup %s"), buf.buf);
else if (opt_a) {
report_auto(argv[0], head_name, &buf_prev);
diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c
index 7728fbc3c1..169f5f5340 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, false);
break;
default:
usage_with_options(git_symbolic_ref_usage, options);
diff --git a/builtin/worktree.c b/builtin/worktree.c
index a7ab4193c1..46b515a243 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, false);
if (ret)
goto done;
diff --git a/refs.c b/refs.c
index 91cacee6f9..3d2c07dd67 100644
--- a/refs.c
+++ b/refs.c
@@ -2115,19 +2115,32 @@ 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 *before_target)
+ struct strbuf *before_target, bool create_only)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
- int ret = 0;
+ int ret = 0, create_ret = 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_commit(transaction, &err)) {
- ret = error("%s", err.buf);
+ if (create_only) {
+ if (!transaction ||
+ ref_transaction_create(transaction, ref, NULL, target,
+ REF_NO_DEREF, logmsg, &err)) {
+ ret = error("%s", err.buf);
+ }
+ else {
+ create_ret = ref_transaction_commit(transaction, &err);
+ if (create_ret && create_ret != TRANSACTION_CREATE_EXISTS)
+ ret = error("%s", err.buf);
+ }
+ }
+ else
+ if (!transaction ||
+ ref_transaction_update(transaction, ref, NULL, NULL,
+ target, NULL, REF_NO_DEREF,
+ logmsg, &err) ||
+ ref_transaction_commit(transaction, &err)) {
+ ret = error("%s", err.buf);
}
strbuf_release(&err);
diff --git a/refs.h b/refs.h
index 166affbc89..b64fd2318b 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 *before_target);
+ struct strbuf *before_target, bool create_only);
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
diff --git a/reset.c b/reset.c
index cc36a9ed56..674896fb61 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, false);
}
if (!ret && run_hook)
run_hooks_l(the_repository, "post-checkout",
diff --git a/sequencer.c b/sequencer.c
index 23b162924c..1a46ef56ba 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, false)) {
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..67abbfaf3c 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, false) < 0)
exit(1);
free(ref);
}
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index a911302bea..b6b06bb2e9 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, false);
}
static struct flag_definition transaction_flags[] = {
--
2.47.0.6.g9542df9767
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v5 6/6] fetch: set remote/HEAD if it does not exist
2024-10-09 13:57 ` [PATCH v5 1/6] refs_update_symref: atomically record overwritten ref Bence Ferdinandy
` (3 preceding siblings ...)
2024-10-09 13:57 ` [PATCH v5 5/6] refs_update_symref: add create_only option Bence Ferdinandy
@ 2024-10-09 13:57 ` Bence Ferdinandy
2024-10-09 22:01 ` Junio C Hamano
4 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-09 13:57 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.
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 to 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
builtin/fetch.c | 82 +++++++++++
t/t4207-log-decoration-colors.sh | 3 +-
t/t5505-remote.sh | 11 +-
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 | 67 ++++++++-
12 files changed, 308 insertions(+), 123 deletions(-)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 80a64d0d26..c3d3c05950 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1578,6 +1578,82 @@ 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;
+
+ 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))
+ ;
+ else if (prev_head) {
+ 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 |= 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", &b_local_head, true))
+ result |= error(_("Could not setup %s"), b_head.buf);
+ 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 +1723,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 +1869,9 @@ 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.
+ // Just fail silently.
cleanup:
if (retcode) {
@@ -2021,6 +2102,7 @@ static int fetch_multiple(struct string_list *list, int max_children,
return !!result;
}
+
/*
* Fetching from the promisor remote should use the given filter-spec
* or inherit the default filter-spec from the config.
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 262a4de0aa..399a501740 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 &&
@@ -725,6 +725,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
@@ -742,6 +743,7 @@ test_expect_success 'update' '
'
cat >one/expect <<\EOF
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -754,7 +756,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 &&
@@ -797,7 +799,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 &&
@@ -809,6 +811,7 @@ test_expect_success 'update default' '
'
cat >one/expect <<\EOF
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -831,7 +834,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..be87e92e5c 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
@@ -3005,7 +3070,7 @@ test_expect_success '__git_pseudoref_exists' '
# HEAD should exist, even if it points to an unborn branch.
__git_pseudoref_exists HEAD >output 2>&1 &&
test_must_be_empty output &&
-
+
# HEAD points to an existing branch, so it should exist.
test_commit A &&
__git_pseudoref_exists HEAD >output 2>&1 &&
--
2.47.0.6.g9542df9767
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v5 2/6] set-head: add new variable for readability
2024-10-09 13:57 ` [PATCH v5 2/6] set-head: add new variable for readability Bence Ferdinandy
@ 2024-10-09 19:26 ` Junio C Hamano
2024-10-09 19:47 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Junio C Hamano @ 2024-10-09 19:26 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, Taylor Blau, René Scharfe,
Johannes Schindelin
Bence Ferdinandy <bence@ferdinandy.com> writes:
> 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.
> ---
>
> Notes:
> v5: new patch (split from the next patch as a preparatory step)
It is a good idea to help making it obvious that we are accessing
the same ref store.
The patch needs to be signed-off, though.
> 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);
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v5 2/6] set-head: add new variable for readability
2024-10-09 19:26 ` Junio C Hamano
@ 2024-10-09 19:47 ` Bence Ferdinandy
0 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-09 19:47 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, phillip.wood, Taylor Blau, René Scharfe,
Johannes Schindelin
On Wed Oct 09, 2024 at 21:26, Junio C Hamano <gitster@pobox.com> wrote:
> Bence Ferdinandy <bence@ferdinandy.com> writes:
>
>> 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.
>> ---
>>
>> Notes:
>> v5: new patch (split from the next patch as a preparatory step)
>
> It is a good idea to help making it obvious that we are accessing
> the same ref store.
Indeed, although it was your idea ;)
>
> The patch needs to be signed-off, though.
Damn :/ Sorry, another oversight ... Should I send a v6 for the series with
this fixed now or wait a bit to see if there are other comments?
Thanks again for the patience!
Best,
Bence
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v5 3/6] set-head: better output for --auto
2024-10-09 13:57 ` [PATCH v5 3/6] set-head: better output for --auto Bence Ferdinandy
@ 2024-10-09 20:53 ` Junio C Hamano
2024-10-10 15:57 ` Junio C Hamano
0 siblings, 1 reply; 258+ messages in thread
From: Junio C Hamano @ 2024-10-09 20:53 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, Taylor Blau, René Scharfe,
Johannes Schindelin
Bence Ferdinandy <bence@ferdinandy.com> writes:
> +static void report_auto(const char *remote, const char *head_name,
> + struct strbuf *buf_prev) {
> + struct strbuf buf_prefix = STRBUF_INIT;
> + const char *prev_head;
I think we need to initialize prev_head to NULL.
> + strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
> + skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);
If the symref was not pointing into the "refs/remotes/<remote>/"
hierarchy previously, skip_prefix() comes back without touching
prev_head (i.e. not starting with the prefix does not clear it).
Assuming that we fix the initialization, the rest of the function
looks more or less correct.
> + 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
> + printf("'%s/HEAD' is now created and points to '%s'\n",
> + remote, head_name);
The "more or less" part is that the message does not let you tell
between refs/remotes/<name>/HEAD that did not exist, and
refs/remotes/<name>/HEAD that used to point at somewhere unexpected,
outside refs/remotes/<name>/ hierarchy.
For that, we can check if buf_prev->len is 0 (in which case, the
"now created and points at" applies) or non-zero (in which case, we
say something like "used to point at '%s' (which is unusual), but
now points at '%s' as requested").
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v5 4/6] transaction: add TRANSACTION_CREATE_EXISTS error
2024-10-09 13:57 ` [PATCH v5 4/6] transaction: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
@ 2024-10-09 21:08 ` Junio C Hamano
0 siblings, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-10-09 21:08 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, Taylor Blau, René Scharfe,
Johannes Schindelin
Bence Ferdinandy <bence@ferdinandy.com> writes:
> /* 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
My initial knee-jerk reaction to the name "CREATE_EXISTS" was that
the "CREATE" part is redundant, as the only case that the fact that
something exists is a valid reason for an error is when we attempted
to create it. EEXIST in errno(3) is not ECREATE_EXIST for the same
reason.
But let's let it pass; contrasting with the fact that what ought to
be named similarly to EISDIR is called NAME_CONFLICT, CREATE_EXISTS
is fine.
> diff --git a/refs/files-backend.c b/refs/files-backend.c
> index 8415f2d020..272ad81315 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;
> }
OK. So if a caller does not care which exact kind of error it got,
they will keep working just the way they used to work, and a caller
that cares can now check. A nice API enhancement.
> @@ -2603,9 +2607,13 @@ 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 {
> + int checkret;
> + checkret = check_old_oid(update, &lock->old_oid, err);
> + if (checkret) {
> + ret = checkret;
> + goto out;
> + }
> }
Likewise; this code is just propagating what check_old_oid() did
to its callers.
> @@ -2636,9 +2644,13 @@ 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 {
> + int checkret;
> + checkret = check_old_oid(update, &lock->old_oid, err);
> + if (checkret) {
> + ret = checkret;
> + goto out;
> + }
Ditto.
> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
> index 32330b6bc6..c6b25ebac4 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;
> }
So it remains a little mystery how the ultimate caller uses this new
bit of information.
Which is OK---the next step seems to have a new condition on the
error status.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v5 5/6] refs_update_symref: add create_only option
2024-10-09 13:57 ` [PATCH v5 5/6] refs_update_symref: add create_only option Bence Ferdinandy
@ 2024-10-09 21:37 ` Junio C Hamano
0 siblings, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-10-09 21:37 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, Taylor Blau, René Scharfe,
Johannes Schindelin
Bence Ferdinandy <bence@ferdinandy.com> writes:
> diff --git a/builtin/branch.c b/builtin/branch.c
> index 6c87690b58..3c9bc39800 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, false))
> ret = error(_("HEAD of working tree %s is not updated"),
> worktrees[i]->path);
> }
Didn't somebody tell you not to use "bool" in previous rounds of
reviews?
We are still smoking out platforms that have trouble with the type
by using it only in a tightly controlled weather balloon we raised
in 8277dbe9 (git-compat-util: convert skip_{prefix,suffix}{,_mem} to
bool, 2023-12-16), if I recall correctly.
Assuming either (1) I forgot that we are OK with bool everywhere, or
(2) the patch will be updated to use plain 0 for false and
everything else for true, let's keep reading. I as a reader expect
that some *.h file will tell us what the new parameter means.
> diff --git a/refs.c b/refs.c
> index 91cacee6f9..3d2c07dd67 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -2115,19 +2115,32 @@ 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 *before_target)
> + struct strbuf *before_target, bool create_only)
> {
> struct ref_transaction *transaction;
> struct strbuf err = STRBUF_INIT;
> - int ret = 0;
> + int ret = 0, create_ret = 0;
Isn't it sufficient for create_ret to be in the scope deep in
the nested if/else where it is used?
> transaction = ref_store_transaction_begin(refs, &err);
> + if (create_only) {
> + if (!transaction ||
> + ref_transaction_create(transaction, ref, NULL, target,
> + REF_NO_DEREF, logmsg, &err)) {
> + ret = error("%s", err.buf);
> + }
> + else {
> + create_ret = ref_transaction_commit(transaction, &err);
> + if (create_ret && create_ret != TRANSACTION_CREATE_EXISTS)
> + ret = error("%s", err.buf);
> + }
> + }
> + else
> + if (!transaction ||
> + ref_transaction_update(transaction, ref, NULL, NULL,
> + target, NULL, REF_NO_DEREF,
> + logmsg, &err) ||
> + ref_transaction_commit(transaction, &err)) {
> + ret = error("%s", err.buf);
> }
Style: in if/else if/... cascade, opening and closing {braces} sit
on the same line as these keywords, i.e.
if (create_only) {
if (...) {
ret = error(...);
} else {
...
}
} else if (!transaction ||
...) {
}
But more importantly, I think it is much cleaner to handle failure
from transaction_begin outside the if/else cascade. i.e.
transaction = ref_store_transaction_begin(refs, &err);
if (!transaction) {
error_return:
ret = error("%s", err.buf);
goto cleanup;
}
if (create_only) {
int create_ret;
if (ref_transaction_create(...))
goto error_return;
create_ret = ref_transaction_commit(...);
if (create_ret && create_ret != TRANSACTION_CREATE_EXISTS)
goto error_return;
} else {
if (ref_transaction_update(...) ||
ref_transaction_commit(...))
goto error_return;
}
cleanup:
strbuf_release(&err);
if (transaction) {
if (before_target && ...)
strbuf_addstr(before_target, ...);
ref_transaction_free(transaction);
}
return ret;
I didn't spot any caller that passes "true" to signal that it wants
the create_only behaviour in this step.
Which is OK---let's keep reading to find such a caller in the next
step.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v5 6/6] fetch: set remote/HEAD if it does not exist
2024-10-09 13:57 ` [PATCH v5 6/6] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
@ 2024-10-09 22:01 ` Junio C Hamano
2024-10-10 13:29 ` [PATCH v6 1/6] refs_update_symref: atomically record overwritten ref Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Junio C Hamano @ 2024-10-09 22:01 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, Taylor Blau, René Scharfe,
Johannes Schindelin
Bence Ferdinandy <bence@ferdinandy.com> writes:
> diff --git a/builtin/fetch.c b/builtin/fetch.c
> index 80a64d0d26..c3d3c05950 100644
> --- a/builtin/fetch.c
> +++ b/builtin/fetch.c
> @@ -1578,6 +1578,82 @@ 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;
> +
> + strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
> + skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);
The same "uninitialized prev_head" problem I discussed earlier for a
separate patch is here, I think.
> + if (prev_head && !strcmp(prev_head, head_name))
> + ;
> + else if (prev_head) {
> + 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);
> + }
If the condition is only for one arm, can't we do this without
else/if? Would the condition become too contorted to read if we did
so? Let's see.
if (prev_head && strcmp(prev_head, head_name)) {
HEAD has changed from prev_head to head_name;
}
I guess it is a bit subjective, but as long as you are used to read
"strcmp(a,b)" as "a and b are different", this should be easier to
read than the original.
> +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) {
Curious use of extra {braces}. In this case we can just lose them.
> + 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 |= 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", &b_local_head, true))
> + result |= error(_("Could not setup %s"), b_head.buf);
Two problems.
* we do not capitalize the beginning of the sentence in an error()
message, i.e. "Could not" -> "could not" (or "cannot").
* "setup" is not a verb.
I know both were inherited from builtin/remote.c but existing
breakage is not a good excuse to make new code worse.
> + else {
Curious use of extra {braces}. In this case we can just lose them.
> + 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 +1723,8 @@ static int do_fetch(struct transport *transport,
> "refs/tags/");
> }
>
> + strvec_push(&transport_ls_refs_options.ref_prefixes,"HEAD");
Missing SP after ",".
> if (must_list_refs) {
> trace2_region_enter("fetch", "remote_refs", the_repository);
> remote_refs = transport_get_remote_refs(transport,
> @@ -1791,6 +1869,9 @@ 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.
> + // Just fail silently.
/*
* our multi-line comments
* look like this.
*/
More importantly, we are not failing silently. As we saw in the
implementation of set_head() above, a non-zero return came from ret
that was assigned the return value of error() in the function, so an
error message is already given, no?
> @@ -2021,6 +2102,7 @@ static int fetch_multiple(struct string_list *list, int max_children,
> return !!result;
> }
>
> +
> /*
> * Fetching from the promisor remote should use the given filter-spec
> * or inherit the default filter-spec from the config.
An unintended change?
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v6 1/6] refs_update_symref: atomically record overwritten ref
2024-10-09 22:01 ` Junio C Hamano
@ 2024-10-10 13:29 ` Bence Ferdinandy
2024-10-10 13:29 ` [PATCH v6 2/6] set-head: add new variable for readability Bence Ferdinandy
` (5 more replies)
0 siblings, 6 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-10 13:29 UTC (permalink / raw)
To: git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano, Bence Ferdinandy
When updating a symref it's currently not possible to know for sure what
was the previous value that was overwritten. Make use of
ref_transaction's atomicity and record the previous value there. Add
a new variable to refs_update_symref's signature to be able to pass this
information back up to callers.
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
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 | 8 ++++++--
refs.h | 3 ++-
refs/files-backend.c | 1 +
refs/refs-internal.h | 8 ++++++++
refs/reftable-backend.c | 1 +
reset.c | 2 +-
sequencer.c | 2 +-
setup.c | 2 +-
t/helper/test-ref-store.c | 2 +-
16 files changed, 34 insertions(+), 19 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..91cacee6f9 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 *before_target)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
@@ -2130,6 +2131,10 @@ int refs_update_symref(struct ref_store *refs, const char *ref,
}
strbuf_release(&err);
+
+ if (transaction && before_target && transaction->updates[0]->before_target)
+ strbuf_addstr(before_target, transaction->updates[0]->before_target);
+
if (transaction)
ref_transaction_free(transaction);
@@ -2948,4 +2953,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..f38616db84 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 *before_target);
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 0824c0b8a9..8415f2d020 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2577,6 +2577,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
}
update->backend_data = lock;
+ update->before_target = xstrdup_or_null(referent.buf);
if (update->type & REF_ISSYMREF) {
if (update->flags & REF_NO_DEREF) {
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 2313c830d8..7df3e6271e 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -104,6 +104,14 @@ struct ref_update {
*/
const char *old_target;
+ /*
+ * The previous target before applying new_target will be
+ * written here, to be used by callers when they do not want to
+ * check old_target during the transaction, but do want to know
+ * what it was.
+ */
+ const char *before_target;
+
/*
* One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,
* REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 3c96fbf66f..32330b6bc6 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1244,6 +1244,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
if (ret)
goto done;
}
+ u->before_target = xstrdup_or_null(referent.buf);
}
transaction->backend_data = tx_data;
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.6.g07cb02250a
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v6 2/6] set-head: add new variable for readability
2024-10-10 13:29 ` [PATCH v6 1/6] refs_update_symref: atomically record overwritten ref Bence Ferdinandy
@ 2024-10-10 13:29 ` Bence Ferdinandy
2024-10-10 13:29 ` [PATCH v6 3/6] set-head: better output for --auto Bence Ferdinandy
` (4 subsequent siblings)
5 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-10 13:29 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
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.6.g07cb02250a
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v6 3/6] set-head: better output for --auto
2024-10-10 13:29 ` [PATCH v6 1/6] refs_update_symref: atomically record overwritten ref Bence Ferdinandy
2024-10-10 13:29 ` [PATCH v6 2/6] set-head: add new variable for readability Bence Ferdinandy
@ 2024-10-10 13:29 ` Bence Ferdinandy
2024-10-10 21:05 ` karthik nayak
2024-10-10 13:29 ` [PATCH v6 4/6] transaction: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
` (3 subsequent siblings)
5 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-10 13:29 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
builtin/remote.c | 34 +++++++++++++++++++++++++++----
t/t5505-remote.sh | 52 ++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 81 insertions(+), 5 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index 353ffd2c43..24f9caf149 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1399,10 +1399,34 @@ static int show(int argc, const char **argv, const char *prefix)
return result;
}
+static void report_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 == 0)
+ 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 unusual), but now points to '%s'\n",
+ remote, buf_prev->buf, head_name);
+}
+
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 +1469,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_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..77c12b8709 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 &&
+ rm .git/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 &&
+ echo "ref: refs/remotes/origin/ahead" >.git/refs/remotes/origin/HEAD &&
+ 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 &&
+ echo "ref: refs/heads/main" >.git/refs/remotes/origin/HEAD &&
+ git remote set-head --auto origin >output &&
+ echo "'\''origin/HEAD'\'' used to point to '\''refs/heads/main'\'' (which is unusual), 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,17 @@ 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.6.g07cb02250a
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v6 4/6] transaction: add TRANSACTION_CREATE_EXISTS error
2024-10-10 13:29 ` [PATCH v6 1/6] refs_update_symref: atomically record overwritten ref Bence Ferdinandy
2024-10-10 13:29 ` [PATCH v6 2/6] set-head: add new variable for readability Bence Ferdinandy
2024-10-10 13:29 ` [PATCH v6 3/6] set-head: better output for --auto Bence Ferdinandy
@ 2024-10-10 13:29 ` Bence Ferdinandy
2024-10-10 21:12 ` karthik nayak
2024-10-10 13:30 ` [PATCH v6 5/6] refs_update_symref: add create_only option Bence Ferdinandy
` (2 subsequent siblings)
5 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-10 13:29 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
refs.h | 4 +++-
refs/files-backend.c | 28 ++++++++++++++++++++--------
refs/reftable-backend.c | 6 ++++--
3 files changed, 27 insertions(+), 11 deletions(-)
diff --git a/refs.h b/refs.h
index f38616db84..166affbc89 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 8415f2d020..272ad81315 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;
}
/*
@@ -2603,9 +2607,13 @@ 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 {
+ int checkret;
+ checkret = check_old_oid(update, &lock->old_oid, err);
+ if (checkret) {
+ ret = checkret;
+ goto out;
+ }
}
} else {
/*
@@ -2636,9 +2644,13 @@ 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 {
+ int checkret;
+ checkret = check_old_oid(update, &lock->old_oid, err);
+ if (checkret) {
+ ret = checkret;
+ goto out;
+ }
}
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 32330b6bc6..c6b25ebac4 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.6.g07cb02250a
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v6 5/6] refs_update_symref: add create_only option
2024-10-10 13:29 ` [PATCH v6 1/6] refs_update_symref: atomically record overwritten ref Bence Ferdinandy
` (2 preceding siblings ...)
2024-10-10 13:29 ` [PATCH v6 4/6] transaction: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
@ 2024-10-10 13:30 ` Bence Ferdinandy
2024-10-10 13:30 ` [PATCH v6 6/6] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
2024-10-12 23:03 ` [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
5 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-10 13:30 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
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 | 36 +++++++++++++++++++++++++-----------
refs.h | 2 +-
reset.c | 2 +-
sequencer.c | 3 ++-
setup.c | 3 ++-
t/helper/test-ref-store.c | 2 +-
13 files changed, 51 insertions(+), 28 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 24f9caf149..9774d8ffe3 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);
}
@@ -1469,7 +1471,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_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 91cacee6f9..e23288b817 100644
--- a/refs.c
+++ b/refs.c
@@ -2115,29 +2115,43 @@ 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 *before_target)
+ struct strbuf *before_target, int create_only)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
int ret = 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_commit(transaction, &err)) {
+ if (!transaction) {
+ error_return:
ret = error("%s", err.buf);
+ goto cleanup;
}
+ if (create_only) {
+ int create_ret = 0;
+ if (ref_transaction_create(transaction, ref, NULL, target,
+ REF_NO_DEREF, logmsg, &err))
+ goto error_return;
+ create_ret = ref_transaction_commit(transaction, &err);
+ if (create_ret && create_ret != TRANSACTION_CREATE_EXISTS)
+ ret = error("%s", err.buf);
- strbuf_release(&err);
+ } else {
+ if (ref_transaction_update(transaction, ref, NULL, NULL,
+ target, NULL, REF_NO_DEREF,
+ logmsg, &err) ||
+ ref_transaction_commit(transaction, &err))
+ goto error_return;
+ }
- if (transaction && before_target && transaction->updates[0]->before_target)
- strbuf_addstr(before_target, transaction->updates[0]->before_target);
+cleanup:
+ strbuf_release(&err);
- if (transaction)
+ if (transaction) {
+ if (before_target && transaction->updates[0]->before_target)
+ strbuf_addstr(before_target, transaction->updates[0]->before_target);
ref_transaction_free(transaction);
-
+ }
return ret;
}
diff --git a/refs.h b/refs.h
index 166affbc89..35f5439cd1 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 *before_target);
+ struct strbuf *before_target, 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.6.g07cb02250a
^ permalink raw reply related [flat|nested] 258+ messages in thread
* [PATCH v6 6/6] fetch: set remote/HEAD if it does not exist
2024-10-10 13:29 ` [PATCH v6 1/6] refs_update_symref: atomically record overwritten ref Bence Ferdinandy
` (3 preceding siblings ...)
2024-10-10 13:30 ` [PATCH v6 5/6] refs_update_symref: add create_only option Bence Ferdinandy
@ 2024-10-10 13:30 ` Bence Ferdinandy
2024-10-12 23:03 ` [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
5 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-10 13:30 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
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 77c12b8709..4a62753af3 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
)
'
@@ -764,6 +764,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
@@ -781,6 +782,7 @@ test_expect_success 'update' '
'
cat >one/expect <<\EOF
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -793,7 +795,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 &&
@@ -836,7 +838,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 &&
@@ -848,6 +850,7 @@ test_expect_success 'update default' '
'
cat >one/expect <<\EOF
+ drosophila/HEAD -> drosophila/main
drosophila/another
drosophila/main
drosophila/side
@@ -870,7 +873,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.6.g07cb02250a
^ permalink raw reply related [flat|nested] 258+ messages in thread
* Re: [PATCH v5 3/6] set-head: better output for --auto
2024-10-09 20:53 ` Junio C Hamano
@ 2024-10-10 15:57 ` Junio C Hamano
2024-10-10 16:54 ` Ramsay Jones
2024-10-10 19:08 ` Bence Ferdinandy
0 siblings, 2 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-10-10 15:57 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: git, phillip.wood, Taylor Blau, René Scharfe,
Johannes Schindelin
Junio C Hamano <gitster@pobox.com> writes:
> Bence Ferdinandy <bence@ferdinandy.com> writes:
>
>> +static void report_auto(const char *remote, const char *head_name,
>> + struct strbuf *buf_prev) {
>> + struct strbuf buf_prefix = STRBUF_INIT;
>> + const char *prev_head;
>
> I think we need to initialize prev_head to NULL.
>
>> + strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
>> + skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);
>
> If the symref was not pointing into the "refs/remotes/<remote>/"
> hierarchy previously, skip_prefix() comes back without touching
> prev_head (i.e. not starting with the prefix does not clear it).
The two uninitialized prev_head were also noticed by compilers in
multiple jobs at GitHub CI.
https://github.com/git/git/actions/runs/11265515664
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v5 3/6] set-head: better output for --auto
2024-10-10 15:57 ` Junio C Hamano
@ 2024-10-10 16:54 ` Ramsay Jones
2024-10-10 19:08 ` Bence Ferdinandy
1 sibling, 0 replies; 258+ messages in thread
From: Ramsay Jones @ 2024-10-10 16:54 UTC (permalink / raw)
To: Junio C Hamano, Bence Ferdinandy
Cc: git, phillip.wood, Taylor Blau, René Scharfe,
Johannes Schindelin
On 10/10/2024 16:57, Junio C Hamano wrote:
> Junio C Hamano <gitster@pobox.com> writes:
>
>> Bence Ferdinandy <bence@ferdinandy.com> writes:
>>
>>> +static void report_auto(const char *remote, const char *head_name,
>>> + struct strbuf *buf_prev) {
>>> + struct strbuf buf_prefix = STRBUF_INIT;
>>> + const char *prev_head;
>>
>> I think we need to initialize prev_head to NULL.
>>
>>> + strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
>>> + skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);
>>
>> If the symref was not pointing into the "refs/remotes/<remote>/"
>> hierarchy previously, skip_prefix() comes back without touching
>> prev_head (i.e. not starting with the prefix does not clear it).
>
> The two uninitialized prev_head were also noticed by compilers in
> multiple jobs at GitHub CI.
>
> https://github.com/git/git/actions/runs/11265515664
Heh, I was just about to send a patch to fix up these two warnings
(errors with -Werror), so that I could build 'seen'. However, it
seems to be well in hand, so I won't bother! :)
ATB,
Ramsay Jones
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v5 3/6] set-head: better output for --auto
2024-10-10 15:57 ` Junio C Hamano
2024-10-10 16:54 ` Ramsay Jones
@ 2024-10-10 19:08 ` Bence Ferdinandy
1 sibling, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-10 19:08 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, phillip.wood, Taylor Blau, René Scharfe,
Johannes Schindelin
On Thu Oct 10, 2024 at 17:57, Junio C Hamano <gitster@pobox.com> wrote:
> Junio C Hamano <gitster@pobox.com> writes:
>
>> Bence Ferdinandy <bence@ferdinandy.com> writes:
>>
>>> +static void report_auto(const char *remote, const char *head_name,
>>> + struct strbuf *buf_prev) {
>>> + struct strbuf buf_prefix = STRBUF_INIT;
>>> + const char *prev_head;
>>
>> I think we need to initialize prev_head to NULL.
>>
>>> + strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);
>>> + skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);
>>
>> If the symref was not pointing into the "refs/remotes/<remote>/"
>> hierarchy previously, skip_prefix() comes back without touching
>> prev_head (i.e. not starting with the prefix does not clear it).
>
> The two uninitialized prev_head were also noticed by compilers in
> multiple jobs at GitHub CI.
>
> https://github.com/git/git/actions/runs/11265515664
I wonder why that didn't fail the build for me locally. I was running
make -j8 install PREFIX=$HOME/.local
for testing. Should I have added a flag or ENV variable somewhere?
Anyhow, the v6 I sent earlier today should fix this (and hopefully all your
other comments as well).
Thanks,
Bence
--
bence.ferdinandy.com
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v6 3/6] set-head: better output for --auto
2024-10-10 13:29 ` [PATCH v6 3/6] set-head: better output for --auto Bence Ferdinandy
@ 2024-10-10 21:05 ` karthik nayak
2024-10-11 9:03 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: karthik nayak @ 2024-10-10 21:05 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: 3131 bytes --]
Bence Ferdinandy <bence@ferdinandy.com> writes:
[snip]
> diff --git a/builtin/remote.c b/builtin/remote.c
> index 353ffd2c43..24f9caf149 100644
> --- a/builtin/remote.c
> +++ b/builtin/remote.c
> @@ -1399,10 +1399,34 @@ static int show(int argc, const char **argv, const char *prefix)
> return result;
> }
>
> +static void report_auto(const char *remote, const char *head_name,
> + struct strbuf *buf_prev) {
Nit: would be nicer if this was called 'report_set_head_auto'
> + 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 == 0)
Nit: we tend to do `if (!buf_prev->len)` in this project
> + 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 unusual), but now points to '%s'\n",
Isn't "which is unusual" a bit vague? Wouldn't be nicer to say why it is
unusual?
> + remote, buf_prev->buf, head_name);
Let's clear up buf_prefix by calling `strbuf_release` here.
> +}
> +
> 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 +1469,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_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..77c12b8709 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 &&
> + rm .git/refs/remotes/origin/HEAD &&
does this work with reftables too?
> + git remote set-head --auto origin >output &&
> + echo "'\''origin/HEAD'\'' is now created and points to '\''main'\''" >expect &&
Nit: might be cleaner to use `${SQ}` here and below
[snip]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v6 4/6] transaction: add TRANSACTION_CREATE_EXISTS error
2024-10-10 13:29 ` [PATCH v6 4/6] transaction: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
@ 2024-10-10 21:12 ` karthik nayak
2024-10-11 16:34 ` Junio C Hamano
0 siblings, 1 reply; 258+ messages in thread
From: karthik nayak @ 2024-10-10 21:12 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: 3523 bytes --]
Bence Ferdinandy <bence@ferdinandy.com> writes:
> transaction: add TRANSACTION_CREATE_EXISTS error
Nit: but it would be nice to have s/transaction/refs here and similarly
in other patches. Phrasing 'Documentation/SubmittingPatches':
It is also conventional in most cases to prefix the first line with
"area: " where the area is a filename or identifier for the general
area of the code being modified, e.g.
* doc: clarify distinction between sign-off and pgp-signing
* githooks.txt: improve the intro section
So in this sense, it would be the refs area, no?
> 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
>
> refs.h | 4 +++-
> refs/files-backend.c | 28 ++++++++++++++++++++--------
> refs/reftable-backend.c | 6 ++++--
> 3 files changed, 27 insertions(+), 11 deletions(-)
>
> diff --git a/refs.h b/refs.h
> index f38616db84..166affbc89 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 8415f2d020..272ad81315 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;
> }
>
> /*
> @@ -2603,9 +2607,13 @@ 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 {
> + int checkret;
> + checkret = check_old_oid(update, &lock->old_oid, err);
> + if (checkret) {
> + ret = checkret;
> + goto out;
> + }
Can't we simply do:
ret = check_old_oid(update, &lock->old_oid, err);
if (ret) {
goto out
}
if ret is '0', it shouldn't matter no?
[snip]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v6 3/6] set-head: better output for --auto
2024-10-10 21:05 ` karthik nayak
@ 2024-10-11 9:03 ` Bence Ferdinandy
2024-10-11 16:31 ` Junio C Hamano
2024-10-11 20:43 ` karthik nayak
0 siblings, 2 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-11 9:03 UTC (permalink / raw)
To: karthik nayak, git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano
Thanks for the very helpful review on the series! Most of it is clear to me and
I agree with, so for brevity I'll only comment where I have a question/comment.
On Thu Oct 10, 2024 at 23:05, karthik nayak <karthik.188@gmail.com> wrote:
[snip]
> > diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
> > index 532035933f..77c12b8709 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 &&
> > + rm .git/refs/remotes/origin/HEAD &&
>
> does this work with reftables too?
You got me there, probably not. I'm guessing I should use git update-ref or
something similar to set these values manually instead of editing the file.
On the other hand, is there a way to force the tests to run on a reftables
backend? t/README does not mention anything about this and since in a previous
round it already came up that I forgot to update the reftables backend, it
would be nice if I could actually test everything with that as well.
>
> > + git remote set-head --auto origin >output &&
> > + echo "'\''origin/HEAD'\'' is now created and points to '\''main'\''" >expect &&
>
> Nit: might be cleaner to use `${SQ}` here and below
You mean something like this?
git remote set-head --auto origin >output &&
HEAD="'\''origin/HEAD'\''" &&
echo "${HEAD} is now created and points to '\''main'\''" >expect &&
I tried a few variations and this is what I could get working, but I may be
simply missing something with the backtick.
Thanks,
Bence
--
bence.ferdinandy.com
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v6 3/6] set-head: better output for --auto
2024-10-11 9:03 ` Bence Ferdinandy
@ 2024-10-11 16:31 ` Junio C Hamano
2024-10-11 20:43 ` karthik nayak
1 sibling, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-10-11 16:31 UTC (permalink / raw)
To: Bence Ferdinandy
Cc: karthik nayak, git, phillip.wood, Taylor Blau, René Scharfe,
Johannes Schindelin
"Bence Ferdinandy" <bence@ferdinandy.com> writes:
> Thanks for the very helpful review on the series! Most of it is clear to me and
> I agree with, so for brevity I'll only comment where I have a question/comment.
>
> On Thu Oct 10, 2024 at 23:05, karthik nayak <karthik.188@gmail.com> wrote:
> [snip]
>
>> > diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
>> > index 532035933f..77c12b8709 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 &&
>> > + rm .git/refs/remotes/origin/HEAD &&
>>
>> does this work with reftables too?
>
> You got me there, probably not. I'm guessing I should use git update-ref or
> something similar to set these values manually instead of editing the file.
Yup, "git update-ref --no-deref -d refs/remotes/origin/HEAD"
> On the other hand, is there a way to force the tests to run on a reftables
> backend? t/README does not mention anything about this and since in a previous
> round it already came up that I forgot to update the reftables backend, it
> would be nice if I could actually test everything with that as well.
$ git grep GIT_TEST_DEFAULT_REF_FORMAT t/README
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v6 4/6] transaction: add TRANSACTION_CREATE_EXISTS error
2024-10-10 21:12 ` karthik nayak
@ 2024-10-11 16:34 ` Junio C Hamano
0 siblings, 0 replies; 258+ messages in thread
From: Junio C Hamano @ 2024-10-11 16:34 UTC (permalink / raw)
To: karthik nayak
Cc: Bence Ferdinandy, git, phillip.wood, Taylor Blau,
René Scharfe, Johannes Schindelin
karthik nayak <karthik.188@gmail.com> writes:
>> + } else {
>> + int checkret;
>> + checkret = check_old_oid(update, &lock->old_oid, err);
>> + if (checkret) {
>> + ret = checkret;
>> + goto out;
>> + }
>
> Can't we simply do:
>
> ret = check_old_oid(update, &lock->old_oid, err);
> if (ret) {
> goto out
> }
>
> if ret is '0', it shouldn't matter no?
That's nice. Yes, as long as "ret" has no useful information when
we enter this "else" block, reusing it like you did is just fine.
This is one of these moments I tell myself "oh, why didn't *I* think
of that" ;-).
Thanks.
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v6 3/6] set-head: better output for --auto
2024-10-11 9:03 ` Bence Ferdinandy
2024-10-11 16:31 ` Junio C Hamano
@ 2024-10-11 20:43 ` karthik nayak
2024-10-12 22:29 ` Bence Ferdinandy
1 sibling, 1 reply; 258+ messages in thread
From: karthik nayak @ 2024-10-11 20:43 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: 742 bytes --]
"Bence Ferdinandy" <bence@ferdinandy.com> writes:
[snip]
>>
>> > + git remote set-head --auto origin >output &&
>> > + echo "'\''origin/HEAD'\'' is now created and points to '\''main'\''" >expect &&
>>
>> Nit: might be cleaner to use `${SQ}` here and below
> You mean something like this?
>
> git remote set-head --auto origin >output &&
> HEAD="'\''origin/HEAD'\''" &&
> echo "${HEAD} is now created and points to '\''main'\''" >expect &&
>
> I tried a few variations and this is what I could get working, but I may be
> simply missing something with the backtick.
I mean simply this
git remote set-head --auto origin >output &&
echo "${SQ}origin/HEAD${SQ} is now created and points to
${SQ}main${SQ}" >expect &&
- Karthik
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v6 3/6] set-head: better output for --auto
2024-10-11 20:43 ` karthik nayak
@ 2024-10-12 22:29 ` Bence Ferdinandy
2024-10-15 7:51 ` karthik nayak
0 siblings, 1 reply; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-12 22:29 UTC (permalink / raw)
To: karthik nayak, git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano
On Fri Oct 11, 2024 at 22:43, karthik nayak <karthik.188@gmail.com> wrote:
> "Bence Ferdinandy" <bence@ferdinandy.com> writes:
>
> [snip]
>
>>>
>>> > + git remote set-head --auto origin >output &&
>>> > + echo "'\''origin/HEAD'\'' is now created and points to '\''main'\''" >expect &&
>>>
>>> Nit: might be cleaner to use `${SQ}` here and below
>> You mean something like this?
>>
>> git remote set-head --auto origin >output &&
>> HEAD="'\''origin/HEAD'\''" &&
>> echo "${HEAD} is now created and points to '\''main'\''" >expect &&
>>
>> I tried a few variations and this is what I could get working, but I may be
>> simply missing something with the backtick.
>
> I mean simply this
>
> git remote set-head --auto origin >output &&
> echo "${SQ}origin/HEAD${SQ} is now created and points to
> ${SQ}main${SQ}" >expect &&
Ah, I see in other tests this is used, but not in this particular test file.
It's a bit hard to decide which is more cryptic, but ${SQ} is nicer on the
eyes. On the other hand I would either switch the entire file in a separate
patch or leave in the '\'' here as well. Or I guess one could go through the
entire test base and switch everything to either one or the other for
consistency.
^ permalink raw reply [flat|nested] 258+ messages in thread
* [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref
2024-10-10 13:29 ` [PATCH v6 1/6] refs_update_symref: atomically record overwritten ref Bence Ferdinandy
` (4 preceding siblings ...)
2024-10-10 13:30 ` [PATCH v6 6/6] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
@ 2024-10-12 23:03 ` Bence Ferdinandy
2024-10-12 23:03 ` [PATCH v7 2/6] remote set-head: add new variable for readability Bence Ferdinandy
` (6 more replies)
5 siblings, 7 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
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
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
* [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
* [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 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 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 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 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
* [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
* [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 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 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 v6 3/6] set-head: better output for --auto
2024-10-12 22:29 ` Bence Ferdinandy
@ 2024-10-15 7:51 ` karthik nayak
2024-10-15 14:10 ` Phillip Wood
0 siblings, 1 reply; 258+ messages in thread
From: karthik nayak @ 2024-10-15 7:51 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: 1667 bytes --]
"Bence Ferdinandy" <bence@ferdinandy.com> writes:
> On Fri Oct 11, 2024 at 22:43, karthik nayak <karthik.188@gmail.com> wrote:
>> "Bence Ferdinandy" <bence@ferdinandy.com> writes:
>>
>> [snip]
>>
>>>>
>>>> > + git remote set-head --auto origin >output &&
>>>> > + echo "'\''origin/HEAD'\'' is now created and points to '\''main'\''" >expect &&
>>>>
>>>> Nit: might be cleaner to use `${SQ}` here and below
>>> You mean something like this?
>>>
>>> git remote set-head --auto origin >output &&
>>> HEAD="'\''origin/HEAD'\''" &&
>>> echo "${HEAD} is now created and points to '\''main'\''" >expect &&
>>>
>>> I tried a few variations and this is what I could get working, but I may be
>>> simply missing something with the backtick.
>>
>> I mean simply this
>>
>> git remote set-head --auto origin >output &&
>> echo "${SQ}origin/HEAD${SQ} is now created and points to
>> ${SQ}main${SQ}" >expect &&
>
> Ah, I see in other tests this is used, but not in this particular test file.
> It's a bit hard to decide which is more cryptic, but ${SQ} is nicer on the
> eyes. On the other hand I would either switch the entire file in a separate
> patch or leave in the '\'' here as well. Or I guess one could go through the
> entire test base and switch everything to either one or the other for
> consistency.
I'm not sure I entirely agree with this sentiment. Consistency is a
great goal to target, but it shouldn't hinder changes that are
beneficial. In our case, if you make the first change, there is now
reason for upcoming changes to the same file to also use '${SQ}' and
eventually we can reach consistency of using '${SQ}' throughout the file.
- Karthik
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ 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 v6 3/6] set-head: better output for --auto
2024-10-15 7:51 ` karthik nayak
@ 2024-10-15 14:10 ` Phillip Wood
2024-10-15 16:17 ` Bence Ferdinandy
0 siblings, 1 reply; 258+ messages in thread
From: Phillip Wood @ 2024-10-15 14:10 UTC (permalink / raw)
To: karthik nayak, Bence Ferdinandy, git
Cc: phillip.wood, Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano
On 15/10/2024 08:51, karthik nayak wrote:
> "Bence Ferdinandy" <bence@ferdinandy.com> writes:
>> On Fri Oct 11, 2024 at 22:43, karthik nayak <karthik.188@gmail.com> wrote:
>>> "Bence Ferdinandy" <bence@ferdinandy.com> writes:
>>> I mean simply this
>>>
>>> git remote set-head --auto origin >output &&
>>> echo "${SQ}origin/HEAD${SQ} is now created and points to
>>> ${SQ}main${SQ}" >expect &&
>>
>> Ah, I see in other tests this is used, but not in this particular test file.
>> It's a bit hard to decide which is more cryptic, but ${SQ} is nicer on the
>> eyes. On the other hand I would either switch the entire file in a separate
>> patch or leave in the '\'' here as well. Or I guess one could go through the
>> entire test base and switch everything to either one or the other for
>> consistency.
>
> I'm not sure I entirely agree with this sentiment. Consistency is a
> great goal to target, but it shouldn't hinder changes that are
> beneficial.
Exactly - if we wait for an entire test file to be modernized before
using our modern test idioms in it we'll be waiting forever. It is much
better to introduce the use of things like ${SQ} that improve
readability as we add new tests or modify existing ones.
Best Wishes
Phillip
In our case, if you make the first change, there is now
> reason for upcoming changes to the same file to also use '${SQ}' and
> eventually we can reach consistency of using '${SQ}' throughout the file.
>
> - Karthik
^ permalink raw reply [flat|nested] 258+ messages in thread
* Re: [PATCH v6 3/6] set-head: better output for --auto
2024-10-15 14:10 ` Phillip Wood
@ 2024-10-15 16:17 ` Bence Ferdinandy
0 siblings, 0 replies; 258+ messages in thread
From: Bence Ferdinandy @ 2024-10-15 16:17 UTC (permalink / raw)
To: phillip.wood, karthik nayak, git
Cc: Taylor Blau, René Scharfe, Johannes Schindelin,
Junio C Hamano
On Tue Oct 15, 2024 at 16:10, Phillip Wood <phillip.wood123@gmail.com> wrote:
> On 15/10/2024 08:51, karthik nayak wrote:
>> "Bence Ferdinandy" <bence@ferdinandy.com> writes:
>>> On Fri Oct 11, 2024 at 22:43, karthik nayak <karthik.188@gmail.com> wrote:
>>
>> I'm not sure I entirely agree with this sentiment. Consistency is a
>> great goal to target, but it shouldn't hinder changes that are
>> beneficial.
>
> Exactly - if we wait for an entire test file to be modernized before
> using our modern test idioms in it we'll be waiting forever. It is much
> better to introduce the use of things like ${SQ} that improve
> readability as we add new tests or modify existing ones.
Ok, I'll change the patch to use SQ. As I gather, it would be preferred if
_all_ the tests would use ${SQ}? If this patch series is finally resolved I can
send a patch for this style change, shouldn't be too complicated.
^ 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
* 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
* 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 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 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
* 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
* [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
* 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
* [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
* [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
* [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
* [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
* [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
* [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 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
* 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
* 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
* 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 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 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
* 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 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
* 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 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 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 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-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 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 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
* 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 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
* [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
* [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
* [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
* [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
* [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
* 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 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 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
* 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 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 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-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: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 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-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-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
* 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 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 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 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 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
* 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
* 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 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
* [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
* [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
* 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
* 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: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
* 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
* [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
* [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
* 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 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-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
* [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
* [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
* [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
* 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
* [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
* [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 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 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
* 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
* [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 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
* 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 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 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 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
* 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
* [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
end of thread, other threads:[~2025-01-06 14:50 UTC | newest]
Thread overview: 258+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-09-10 20:37 [RFC PATCH v2 0/2] set remote/HEAD with fetch Bence Ferdinandy
2024-09-10 20:37 ` [RFC PATCH v2 1/2] fetch: set-head with --set-head option Bence Ferdinandy
2024-09-10 22:19 ` Junio C Hamano
2024-09-11 12:13 ` Bence Ferdinandy
2024-09-11 15:52 ` Junio C Hamano
2024-09-19 12:13 ` [PATCH v3 0/2] fetch: set remote/HEAD if missing Bence Ferdinandy
2024-09-19 12:13 ` [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option Bence Ferdinandy
2024-09-19 22:46 ` Junio C Hamano
2024-09-20 14:11 ` Bence Ferdinandy
2024-09-21 13:40 ` Phillip Wood
2024-09-21 22:19 ` Bence Ferdinandy
2024-09-22 16:56 ` Junio C Hamano
2024-09-29 22:58 ` Bence Ferdinandy
2024-09-30 6:40 ` Patrick Steinhardt
2024-09-30 9:27 ` Bence Ferdinandy
2024-09-30 22:19 ` [PATCH v4 0/5] improve handling of remote/HEAD Bence Ferdinandy
2024-09-30 22:19 ` [PATCH v4 1/5] refs_update_symref: atomically record overwritten ref Bence Ferdinandy
2024-10-01 19:10 ` Junio C Hamano
2024-09-30 22:19 ` [PATCH v4 2/5] set-head: better output for --auto Bence Ferdinandy
2024-10-01 22:54 ` Junio C Hamano
2024-09-30 22:19 ` [PATCH v4 3/5] transaction: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
2024-09-30 22:19 ` [PATCH v4 4/5] refs_update_symref: add create_only option Bence Ferdinandy
2024-09-30 22:19 ` [PATCH v4 5/5] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
2024-10-03 19:21 ` [PATCH v4 0/5] improve handling of remote/HEAD Junio C Hamano
2024-10-03 19:48 ` Bence Ferdinandy
2024-10-09 13:57 ` [PATCH v5 1/6] refs_update_symref: atomically record overwritten ref Bence Ferdinandy
2024-10-09 13:57 ` [PATCH v5 2/6] set-head: add new variable for readability Bence Ferdinandy
2024-10-09 19:26 ` Junio C Hamano
2024-10-09 19:47 ` Bence Ferdinandy
2024-10-09 13:57 ` [PATCH v5 3/6] set-head: better output for --auto Bence Ferdinandy
2024-10-09 20:53 ` Junio C Hamano
2024-10-10 15:57 ` Junio C Hamano
2024-10-10 16:54 ` Ramsay Jones
2024-10-10 19:08 ` Bence Ferdinandy
2024-10-09 13:57 ` [PATCH v5 4/6] transaction: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
2024-10-09 21:08 ` Junio C Hamano
2024-10-09 13:57 ` [PATCH v5 5/6] refs_update_symref: add create_only option Bence Ferdinandy
2024-10-09 21:37 ` Junio C Hamano
2024-10-09 13:57 ` [PATCH v5 6/6] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
2024-10-09 22:01 ` Junio C Hamano
2024-10-10 13:29 ` [PATCH v6 1/6] refs_update_symref: atomically record overwritten ref Bence Ferdinandy
2024-10-10 13:29 ` [PATCH v6 2/6] set-head: add new variable for readability Bence Ferdinandy
2024-10-10 13:29 ` [PATCH v6 3/6] set-head: better output for --auto Bence Ferdinandy
2024-10-10 21:05 ` karthik nayak
2024-10-11 9:03 ` Bence Ferdinandy
2024-10-11 16:31 ` Junio C Hamano
2024-10-11 20:43 ` karthik nayak
2024-10-12 22:29 ` Bence Ferdinandy
2024-10-15 7:51 ` karthik nayak
2024-10-15 14:10 ` Phillip Wood
2024-10-15 16:17 ` Bence Ferdinandy
2024-10-10 13:29 ` [PATCH v6 4/6] transaction: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
2024-10-10 21:12 ` karthik nayak
2024-10-11 16:34 ` Junio C Hamano
2024-10-10 13:30 ` [PATCH v6 5/6] refs_update_symref: add create_only option Bence Ferdinandy
2024-10-10 13:30 ` [PATCH v6 6/6] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
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 ` [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
2024-10-14 8:48 ` Phillip Wood
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 ` [PATCH v7 6/6] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
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
2024-10-15 17:25 ` Bence Ferdinandy
2024-10-19 22:53 ` Bence Ferdinandy
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-15 7:41 ` karthik nayak
2024-10-15 16:24 ` 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 ` [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
2024-10-14 22:53 ` [PATCH v8 4/6] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
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 ` [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
2024-10-16 8:18 ` Bence Ferdinandy
2024-10-16 21:05 ` Taylor Blau
2024-10-17 15:23 ` Bence Ferdinandy
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 ` [PATCH v9 3/7] remote set-head: refactor for readability Bence Ferdinandy
2024-10-19 22:53 ` [PATCH v9 4/7] remote set-head: better output for --auto Bence Ferdinandy
2024-10-19 22:53 ` [PATCH v9 5/7] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
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 ` [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
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:53 ` Kristoffer Haugsbakk
2024-10-22 20:14 ` Bence Ferdinandy
2024-10-23 15:09 ` Taylor Blau
2024-10-23 15:34 ` Bence Ferdinandy
2024-10-23 18:45 ` Taylor Blau
2024-10-22 19:45 ` [PATCH v11 2/8] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
2024-10-22 19:45 ` [PATCH v11 3/8] remote set-head: refactor for readability Bence Ferdinandy
2024-10-22 19:45 ` [PATCH v11 4/8] remote set-head: better output for --auto Bence Ferdinandy
2024-10-22 19:45 ` [PATCH v11 5/8] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
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 ` [PATCH v11 7/8] fetch: set remote/HEAD if it does not exist 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
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
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-15 23:27 ` Bence Ferdinandy
2024-11-16 7:58 ` Junio C Hamano
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
2024-11-18 8:24 ` Patrick Steinhardt
2024-11-18 11:37 ` Bence Ferdinandy
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
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
2024-11-15 23:13 ` Bence Ferdinandy
2024-11-16 0:22 ` Junio C Hamano
2024-11-16 0:15 ` Junio C Hamano
2024-11-16 14:43 ` Bence Ferdinandy
2024-10-23 15:36 ` [PATCH v12 5/8] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
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 ` [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
2024-10-23 15:36 ` [PATCH v12 8/8] fetch set_head: handle mirrored bare repositories 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
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
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 6:54 ` Junio C Hamano
2024-11-19 7:26 ` Patrick Steinhardt
2024-11-19 10:10 ` Bence Ferdinandy
2024-11-19 5:10 ` Junio C Hamano
2024-11-19 10:04 ` Bence Ferdinandy
2024-11-19 6:48 ` Patrick Steinhardt
2024-11-19 10:17 ` Bence Ferdinandy
2024-11-18 15:09 ` [PATCH v13 3/9] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
2024-11-18 15:09 ` [PATCH v13 4/9] remote set-head: refactor for readability Bence Ferdinandy
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
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
2024-11-18 15:09 ` [PATCH v13 6/9] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
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
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
2024-11-20 1:00 ` Junio C Hamano
2024-11-20 2:28 ` Junio C Hamano
2024-11-20 10:45 ` Bence Ferdinandy
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
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-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
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 12:23 ` Bence Ferdinandy
2024-11-25 2:56 ` Junio C Hamano
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:56 ` Bence Ferdinandy
2024-11-26 21:39 ` Junio C Hamano
2024-11-26 20:44 ` Bence Ferdinandy
2024-11-22 11:27 ` Bence Ferdinandy
2024-11-21 22:55 ` [PATCH v14 04/10] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
2024-11-21 22:55 ` [PATCH v14 05/10] remote set-head: refactor for readability Bence Ferdinandy
2024-11-21 22:55 ` [PATCH v14 06/10] remote set-head: better output for --auto Bence Ferdinandy
2024-11-21 22:55 ` [PATCH v14 07/10] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
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 ` [PATCH v14 09/10] fetch: set remote/HEAD if it does not exist 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
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 ` [PATCH v15 03/10] refs: standardize output of refs_read_symbolic_ref Bence Ferdinandy
2024-11-22 12:28 ` [PATCH v15 04/10] refs: atomically record overwritten ref in update_symref Bence Ferdinandy
2024-11-22 12:28 ` [PATCH v15 05/10] remote set-head: refactor for readability Bence Ferdinandy
2024-11-22 12:28 ` [PATCH v15 06/10] remote set-head: better output for --auto Bence Ferdinandy
2024-11-22 12:28 ` [PATCH v15 07/10] refs: add TRANSACTION_CREATE_EXISTS error Bence Ferdinandy
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 ` [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
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-06 3:07 ` Junio C Hamano
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
2024-12-05 20:57 ` [PATCH v15 09/10] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
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
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 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
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
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
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 11:43 ` Kristoffer Haugsbakk
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
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
2025-01-06 14:50 ` Junio C Hamano
2024-12-05 12:16 ` [PATCH v3 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
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-12-02 0:58 ` Junio C Hamano
2024-11-28 23:09 ` Bence Ferdinandy
2024-09-19 12:13 ` [PATCH v3 2/2] fetch: set remote/HEAD if it does not exist Bence Ferdinandy
2024-09-19 23:07 ` Junio C Hamano
2024-09-20 13:50 ` Bence Ferdinandy
2024-09-10 20:37 ` [RFC PATCH v2 2/2] set-head: do not update if there is no change Bence Ferdinandy
2024-09-10 22:29 ` [RFC PATCH v2 0/2] set remote/HEAD with fetch Junio C Hamano
2024-09-11 12:24 ` Bence Ferdinandy
2024-09-11 15:59 ` Junio C Hamano
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).