git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [PATCH v2 0/2] Add fetch.updateHead option
@ 2023-04-07  2:37 Felipe Contreras
  2023-04-07  2:37 ` [PATCH v2 1/2] " Felipe Contreras
  2023-04-07  2:37 ` [PATCH v2 2/2] fetch: add support for HEAD update on mirrors Felipe Contreras
  0 siblings, 2 replies; 3+ messages in thread
From: Felipe Contreras @ 2023-04-07  2:37 UTC (permalink / raw)
  To: git
  Cc: Ævar Arnfjörð Bjarmason, Jeff King,
	Patrick Steinhardt, Jonathan Nieder, Felipe Contreras

It's surprising that `git clone` and `git init && git remote add -f` don't
create the same remote state.

Fix this by introducing a new configuration: `fetch.updateHead` which updates
the remote `HEAD` when it's not present with "missing", or always with
"always".

By default it's "never", which retains the current behavior.

This has already been discussed before [1].

Changes since v1:

1. Make `fetch_update_head` a named enum as suggested by Ævar
2. Remove `need_update_head`: use switch case instead, per Ævar
3. Make `update_head` receive `fetch_missing` boolean, instead of enum,
   per Ævar
4. Make `update_head` receive an unconsted `struct remote`: worse, but
   simplifies the review process

[1] https://lore.kernel.org/git/20201118091219.3341585-1-felipe.contreras@gmail.com/

Felipe Contreras (2):
  Add fetch.updateHead option
  fetch: add support for HEAD update on mirrors

 Documentation/config/fetch.txt  |  4 ++
 Documentation/config/remote.txt |  3 ++
 builtin/fetch.c                 | 76 ++++++++++++++++++++++++++++++++-
 remote.c                        | 20 +++++++++
 remote.h                        | 12 ++++++
 t/t5510-fetch.sh                | 49 +++++++++++++++++++++
 6 files changed, 163 insertions(+), 1 deletion(-)

Range-diff against v1:
1:  1cb238c83d ! 1:  0b80baba39 Add fetch.updateHead option
    @@ Commit message
     
         For the next major version of Git, we might want to change this default.
     
    +    Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
         Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>
     
      ## Documentation/config/fetch.txt ##
    @@ builtin/fetch.c: static int fetch_prune_tags_config = -1; /* unspecified */
      static int prune_tags = -1; /* unspecified */
      #define PRUNE_TAGS_BY_DEFAULT 0 /* do we prune tags by default? */
      
    -+static int fetch_update_head = FETCH_UPDATE_HEAD_DEFAULT;
    ++static enum fetch_update_head fetch_update_head = FETCH_UPDATE_HEAD_DEFAULT;
     +
      static int all, append, dry_run, force, keep, multiple, update_head_ok;
      static int write_fetch_head = 1;
    @@ builtin/fetch.c: static int backfill_tags(struct transport *transport,
      	return retcode;
      }
      
    -+static void update_head(int config, const struct ref *head, const struct remote *remote)
    ++static void update_head(int fetch_missing, const struct ref *head,
    ++			struct remote *remote)
     +{
     +	char *ref, *target;
     +	const char *r;
    @@ builtin/fetch.c: static int backfill_tags(struct transport *transport,
     +	if (!head || !head->symref || !remote)
     +		return;
     +
    -+	ref = apply_refspecs((struct refspec *)&remote->fetch, "refs/heads/HEAD");
    -+	target = apply_refspecs((struct refspec *)&remote->fetch, head->symref);
    ++	ref = apply_refspecs(&remote->fetch, "refs/heads/HEAD");
    ++	target = apply_refspecs(&remote->fetch, head->symref);
     +
     +	if (!ref || !target) {
     +		warning(_("could not update remote head"));
    @@ builtin/fetch.c: static int backfill_tags(struct transport *transport,
     +	r = resolve_ref_unsafe(ref, 0, NULL, &flags);
     +
     +	if (r) {
    -+		if (config == FETCH_UPDATE_HEAD_MISSING) {
    -+			if (flags & REF_ISSYMREF)
    -+				/* already present */
    -+				return;
    -+		} else if (config == FETCH_UPDATE_HEAD_ALWAYS) {
    ++		if (!fetch_missing) {
     +			if (!strcmp(r, target))
     +				/* already up-to-date */
     +				return;
    -+		} else
    -+			/* should never happen */
    ++		} else if (flags & REF_ISSYMREF)
    ++			/* already present */
     +			return;
     +	}
     +
    @@ builtin/fetch.c: static int do_fetch(struct transport *transport,
      	int must_list_refs = 1;
      	struct fetch_head fetch_head = { 0 };
      	struct strbuf err = STRBUF_INIT;
    -+	int need_update_head = 0, update_head_config = 0;
    ++	enum fetch_update_head update_head_config = FETCH_UPDATE_HEAD_DEFAULT;
      
      	if (tags == TAGS_DEFAULT) {
      		if (transport->remote->fetch_tags == 2)
    @@ builtin/fetch.c: static int do_fetch(struct transport *transport,
     +			else
     +				update_head_config = fetch_update_head;
     +
    -+			need_update_head = update_head_config && update_head_config != FETCH_UPDATE_HEAD_NEVER;
    -+
    -+			if (need_update_head)
    ++			switch (update_head_config) {
    ++			case FETCH_UPDATE_HEAD_MISSING:
    ++			case FETCH_UPDATE_HEAD_ALWAYS:
     +				strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
    ++			default:
    ++				break;
    ++			}
      			refspec_ref_prefixes(&transport->remote->fetch,
      					     &transport_ls_refs_options.ref_prefixes);
     +		}
    @@ builtin/fetch.c: static int do_fetch(struct transport *transport,
      
      	commit_fetch_head(&fetch_head);
      
    -+	if (need_update_head)
    -+		update_head(update_head_config, find_ref_by_name(remote_refs, "HEAD"), transport->remote);
    ++	switch (update_head_config) {
    ++	case FETCH_UPDATE_HEAD_MISSING:
    ++	case FETCH_UPDATE_HEAD_ALWAYS:
    ++		update_head(update_head_config == FETCH_UPDATE_HEAD_MISSING,
    ++			    find_ref_by_name(remote_refs, "HEAD"),
    ++			    transport->remote);
    ++	default:
    ++		break;
    ++	}
     +
      	if (set_upstream) {
      		struct branch *branch = branch_get("HEAD");
    @@ remote.c: static void read_branches_file(struct remote_state *remote_state,
      	remote->fetch_tags = 1; /* always auto-follow */
      }
      
    -+int parse_update_head(int *r, const char *var, const char *value)
    ++int parse_update_head(enum fetch_update_head *r, const char *var,
    ++		      const char *value)
     +{
    -+	if (!r)
    -+		return -1;
    -+	else if (!value)
    ++	if (!value)
     +		return config_error_nonbool(var);
     +	else if (!strcmp(value, "never"))
     +		*r = FETCH_UPDATE_HEAD_NEVER;
    @@ remote.h: enum {
      	REMOTE_BRANCHES
      };
      
    -+enum {
    ++enum fetch_update_head {
     +	FETCH_UPDATE_HEAD_DEFAULT = 0,
     +	FETCH_UPDATE_HEAD_NEVER,
     +	FETCH_UPDATE_HEAD_MISSING,
    @@ remote.h: struct remote {
      	int prune;
      	int prune_tags;
      
    -+	int update_head;
    ++	enum fetch_update_head update_head;
     +
      	/**
      	 * The configured helper programs to run on the remote side, for
    @@ remote.h: void apply_push_cas(struct push_cas_option *, struct remote *, struct
      char *relative_url(const char *remote_url, const char *url,
      		   const char *up_path);
      
    -+int parse_update_head(int *r, const char *var, const char *value);
    ++int parse_update_head(enum fetch_update_head *r, const char *var,
    ++		      const char *value);
     +
      #endif
     
2:  fe6d62510b ! 2:  5c0f48b9cc fetch: add support for HEAD update on mirrors
    @@ Commit message
         Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>
     
      ## builtin/fetch.c ##
    -@@ builtin/fetch.c: static void update_head(int config, const struct ref *head, const struct remote
    +@@ builtin/fetch.c: static void update_head(int fetch_missing, const struct ref *head,
      	if (!head || !head->symref || !remote)
      		return;
      
    --	ref = apply_refspecs((struct refspec *)&remote->fetch, "refs/heads/HEAD");
    --	target = apply_refspecs((struct refspec *)&remote->fetch, head->symref);
    +-	ref = apply_refspecs(&remote->fetch, "refs/heads/HEAD");
    +-	target = apply_refspecs(&remote->fetch, head->symref);
     +	if (!remote->mirror) {
    -+		ref = apply_refspecs((struct refspec *)&remote->fetch, "refs/heads/HEAD");
    -+		target = apply_refspecs((struct refspec *)&remote->fetch, head->symref);
    ++		ref = apply_refspecs(&remote->fetch, "refs/heads/HEAD");
    ++		target = apply_refspecs(&remote->fetch, head->symref);
      
     -	if (!ref || !target) {
     -		warning(_("could not update remote head"));
-- 
2.40.0+fc1


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH v2 1/2] Add fetch.updateHead option
  2023-04-07  2:37 [PATCH v2 0/2] Add fetch.updateHead option Felipe Contreras
@ 2023-04-07  2:37 ` Felipe Contreras
  2023-04-07  2:37 ` [PATCH v2 2/2] fetch: add support for HEAD update on mirrors Felipe Contreras
  1 sibling, 0 replies; 3+ messages in thread
From: Felipe Contreras @ 2023-04-07  2:37 UTC (permalink / raw)
  To: git
  Cc: Ævar Arnfjörð Bjarmason, Jeff King,
	Patrick Steinhardt, Jonathan Nieder, Felipe Contreras

Users might change the behavior when running "git fetch" so that the
remote's HEAD symbolic ref is updated at certain point.

For example after running "git remote add" the remote HEAD is not
set like it is with "git clone".

Setting "fetch.updatehead = missing" would probably be a sensible
default that everyone would want, but for now the default behavior is to
never update HEAD, so there shouldn't be any functional changes.

For the next major version of Git, we might want to change this default.

Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>
---
 Documentation/config/fetch.txt  |  4 ++
 Documentation/config/remote.txt |  3 ++
 builtin/fetch.c                 | 71 ++++++++++++++++++++++++++++++++-
 remote.c                        | 20 ++++++++++
 remote.h                        | 12 ++++++
 t/t5510-fetch.sh                | 31 ++++++++++++++
 6 files changed, 140 insertions(+), 1 deletion(-)

diff --git a/Documentation/config/fetch.txt b/Documentation/config/fetch.txt
index 568f0f75b3..dc147ffb35 100644
--- a/Documentation/config/fetch.txt
+++ b/Documentation/config/fetch.txt
@@ -120,3 +120,7 @@ fetch.bundleCreationToken::
 The creation token values are chosen by the provider serving the specific
 bundle URI. If you modify the URI at `fetch.bundleURI`, then be sure to
 remove the value for the `fetch.bundleCreationToken` value before fetching.
+
+fetch.updateHead::
+	Defines when to update the remote HEAD symbolic ref. Values are 'never',
+	'missing' (update only when HEAD is missing), and 'always'.
diff --git a/Documentation/config/remote.txt b/Documentation/config/remote.txt
index 0678b4bcfe..9d739d2ed4 100644
--- a/Documentation/config/remote.txt
+++ b/Documentation/config/remote.txt
@@ -86,3 +86,6 @@ remote.<name>.partialclonefilter::
 	Changing or clearing this value will only affect fetches for new commits.
 	To fetch associated objects for commits already present in the local object
 	database, use the `--refetch` option of linkgit:git-fetch[1].
+
+remote.<name>.updateHead::
+	Defines when to update the remote HEAD symbolic ref. See `fetch.updateHead`.
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 7221e57f35..3f7b33ba78 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -59,6 +59,8 @@ static int fetch_prune_tags_config = -1; /* unspecified */
 static int prune_tags = -1; /* unspecified */
 #define PRUNE_TAGS_BY_DEFAULT 0 /* do we prune tags by default? */
 
+static enum fetch_update_head fetch_update_head = FETCH_UPDATE_HEAD_DEFAULT;
+
 static int all, append, dry_run, force, keep, multiple, update_head_ok;
 static int write_fetch_head = 1;
 static int verbosity, deepen_relative, set_upstream, refetch;
@@ -129,6 +131,9 @@ static int git_fetch_config(const char *k, const char *v, void *cb)
 		return 0;
 	}
 
+	if (!strcmp(k, "fetch.updatehead"))
+		return parse_update_head(&fetch_update_head, k, v);
+
 	return git_default_config(k, v, cb);
 }
 
@@ -1579,6 +1584,44 @@ static int backfill_tags(struct transport *transport,
 	return retcode;
 }
 
+static void update_head(int fetch_missing, const struct ref *head,
+			struct remote *remote)
+{
+	char *ref, *target;
+	const char *r;
+	int flags;
+
+	if (!head || !head->symref || !remote)
+		return;
+
+	ref = apply_refspecs(&remote->fetch, "refs/heads/HEAD");
+	target = apply_refspecs(&remote->fetch, head->symref);
+
+	if (!ref || !target) {
+		warning(_("could not update remote head"));
+		return;
+	}
+
+	r = resolve_ref_unsafe(ref, 0, NULL, &flags);
+
+	if (r) {
+		if (!fetch_missing) {
+			if (!strcmp(r, target))
+				/* already up-to-date */
+				return;
+		} else if (flags & REF_ISSYMREF)
+			/* already present */
+			return;
+	}
+
+	if (!create_symref(ref, target, "remote update head")) {
+		if (verbosity >= 0)
+			printf(_("Updated remote '%s' HEAD\n"), remote->name);
+	} else {
+		warning(_("could not update remote head"));
+	}
+}
+
 static int do_fetch(struct transport *transport,
 		    struct refspec *rs)
 {
@@ -1592,6 +1635,7 @@ static int do_fetch(struct transport *transport,
 	int must_list_refs = 1;
 	struct fetch_head fetch_head = { 0 };
 	struct strbuf err = STRBUF_INIT;
+	enum fetch_update_head update_head_config = FETCH_UPDATE_HEAD_DEFAULT;
 
 	if (tags == TAGS_DEFAULT) {
 		if (transport->remote->fetch_tags == 2)
@@ -1626,9 +1670,24 @@ static int do_fetch(struct transport *transport,
 	} else {
 		struct branch *branch = branch_get(NULL);
 
-		if (transport->remote->fetch.nr)
+		if (transport->remote->fetch.nr) {
+
+			if (transport->remote->update_head)
+				update_head_config = transport->remote->update_head;
+			else
+				update_head_config = fetch_update_head;
+
+			switch (update_head_config) {
+			case FETCH_UPDATE_HEAD_MISSING:
+			case FETCH_UPDATE_HEAD_ALWAYS:
+				strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
+			default:
+				break;
+			}
 			refspec_ref_prefixes(&transport->remote->fetch,
 					     &transport_ls_refs_options.ref_prefixes);
+		}
+
 		if (branch_has_merge_config(branch) &&
 		    !strcmp(branch->remote_name, transport->remote->name)) {
 			int i;
@@ -1737,6 +1796,16 @@ static int do_fetch(struct transport *transport,
 
 	commit_fetch_head(&fetch_head);
 
+	switch (update_head_config) {
+	case FETCH_UPDATE_HEAD_MISSING:
+	case FETCH_UPDATE_HEAD_ALWAYS:
+		update_head(update_head_config == FETCH_UPDATE_HEAD_MISSING,
+			    find_ref_by_name(remote_refs, "HEAD"),
+			    transport->remote);
+	default:
+		break;
+	}
+
 	if (set_upstream) {
 		struct branch *branch = branch_get("HEAD");
 		struct ref *rm;
diff --git a/remote.c b/remote.c
index 641b083d90..c833f1b143 100644
--- a/remote.c
+++ b/remote.c
@@ -344,6 +344,24 @@ static void read_branches_file(struct remote_state *remote_state,
 	remote->fetch_tags = 1; /* always auto-follow */
 }
 
+int parse_update_head(enum fetch_update_head *r, const char *var,
+		      const char *value)
+{
+	if (!value)
+		return config_error_nonbool(var);
+	else if (!strcmp(value, "never"))
+		*r = FETCH_UPDATE_HEAD_NEVER;
+	else if (!strcmp(value, "missing"))
+		*r = FETCH_UPDATE_HEAD_MISSING;
+	else if (!strcmp(value, "always"))
+		*r = FETCH_UPDATE_HEAD_ALWAYS;
+	else {
+		error(_("malformed value for %s: %s"), var, value);
+		return error(_("must be one of never, missing, or always"));
+	}
+	return 0;
+}
+
 static int handle_config(const char *key, const char *value, void *cb)
 {
 	const char *name;
@@ -473,6 +491,8 @@ static int handle_config(const char *key, const char *value, void *cb)
 					 key, value);
 	} else if (!strcmp(subkey, "vcs")) {
 		return git_config_string(&remote->foreign_vcs, key, value);
+	} else if (!strcmp(subkey, "updatehead")) {
+		return parse_update_head(&remote->update_head, key, value);
 	}
 	return 0;
 }
diff --git a/remote.h b/remote.h
index 73638cefeb..0810f55294 100644
--- a/remote.h
+++ b/remote.h
@@ -22,6 +22,13 @@ enum {
 	REMOTE_BRANCHES
 };
 
+enum fetch_update_head {
+	FETCH_UPDATE_HEAD_DEFAULT = 0,
+	FETCH_UPDATE_HEAD_NEVER,
+	FETCH_UPDATE_HEAD_MISSING,
+	FETCH_UPDATE_HEAD_ALWAYS,
+};
+
 struct rewrite {
 	const char *base;
 	size_t baselen;
@@ -97,6 +104,8 @@ struct remote {
 	int prune;
 	int prune_tags;
 
+	enum fetch_update_head update_head;
+
 	/**
 	 * The configured helper programs to run on the remote side, for
 	 * Git-native protocols.
@@ -449,4 +458,7 @@ void apply_push_cas(struct push_cas_option *, struct remote *, struct ref *);
 char *relative_url(const char *remote_url, const char *url,
 		   const char *up_path);
 
+int parse_update_head(enum fetch_update_head *r, const char *var,
+		      const char *value);
+
 #endif
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index dc44da9c79..dbeb2928ae 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -814,6 +814,37 @@ test_expect_success 'fetch from multiple configured URLs in single remote' '
 	git fetch multipleurls
 '
 
+test_cmp_symbolic_ref () {
+	git symbolic-ref "$1" >actual &&
+	echo "$2" >expected &&
+	test_cmp expected actual
+}
+
+test_expect_success 'updatehead' '
+	test_when_finished "rm -rf updatehead" &&
+
+	git init updatehead &&
+	(
+		cd updatehead &&
+
+		git config fetch.updateHead never &&
+		git remote add origin .. &&
+		git fetch &&
+		test_must_fail git rev-parse --verify refs/remotes/origin/HEAD &&
+
+		git config fetch.updateHead missing &&
+		git fetch &&
+		test_cmp_symbolic_ref refs/remotes/origin/HEAD refs/remotes/origin/main &&
+		git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/side &&
+		git fetch &&
+		test_cmp_symbolic_ref refs/remotes/origin/HEAD refs/remotes/origin/side &&
+
+		git config fetch.updateHead always &&
+		git fetch &&
+		test_cmp_symbolic_ref refs/remotes/origin/HEAD refs/remotes/origin/main
+	)
+'
+
 # configured prune tests
 
 set_config_tristate () {
-- 
2.40.0+fc1


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH v2 2/2] fetch: add support for HEAD update on mirrors
  2023-04-07  2:37 [PATCH v2 0/2] Add fetch.updateHead option Felipe Contreras
  2023-04-07  2:37 ` [PATCH v2 1/2] " Felipe Contreras
@ 2023-04-07  2:37 ` Felipe Contreras
  1 sibling, 0 replies; 3+ messages in thread
From: Felipe Contreras @ 2023-04-07  2:37 UTC (permalink / raw)
  To: git
  Cc: Ævar Arnfjörð Bjarmason, Jeff King,
	Patrick Steinhardt, Jonathan Nieder, Felipe Contreras

Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>
---
 builtin/fetch.c  | 15 ++++++++++-----
 t/t5510-fetch.sh | 18 ++++++++++++++++++
 2 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/builtin/fetch.c b/builtin/fetch.c
index 3f7b33ba78..80fcaf79c3 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1594,12 +1594,17 @@ static void update_head(int fetch_missing, const struct ref *head,
 	if (!head || !head->symref || !remote)
 		return;
 
-	ref = apply_refspecs(&remote->fetch, "refs/heads/HEAD");
-	target = apply_refspecs(&remote->fetch, head->symref);
+	if (!remote->mirror) {
+		ref = apply_refspecs(&remote->fetch, "refs/heads/HEAD");
+		target = apply_refspecs(&remote->fetch, head->symref);
 
-	if (!ref || !target) {
-		warning(_("could not update remote head"));
-		return;
+		if (!ref || !target) {
+			warning(_("could not update remote head"));
+			return;
+		}
+	} else {
+		ref = "HEAD";
+		target = head->symref;
 	}
 
 	r = resolve_ref_unsafe(ref, 0, NULL, &flags);
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index dbeb2928ae..d3f3b24378 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -845,6 +845,24 @@ test_expect_success 'updatehead' '
 	)
 '
 
+test_expect_success 'updatehead mirror' '
+	test_when_finished "rm -rf updatehead" &&
+
+	git clone --mirror . updatehead &&
+	(
+		cd updatehead &&
+
+		git config fetch.updateHead missing &&
+		git symbolic-ref HEAD refs/heads/side &&
+		git fetch &&
+		test_cmp_symbolic_ref HEAD refs/heads/side &&
+
+		git config fetch.updateHead always &&
+		git fetch &&
+		test_cmp_symbolic_ref HEAD refs/heads/main
+	)
+'
+
 # configured prune tests
 
 set_config_tristate () {
-- 
2.40.0+fc1


^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2023-04-07  2:37 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-04-07  2:37 [PATCH v2 0/2] Add fetch.updateHead option Felipe Contreras
2023-04-07  2:37 ` [PATCH v2 1/2] " Felipe Contreras
2023-04-07  2:37 ` [PATCH v2 2/2] fetch: add support for HEAD update on mirrors Felipe Contreras

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

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 read-only IMAP folder(s) and NNTP newsgroup(s).