mbox series

[v2,0/2] Add fetch.updateHead option

Message ID 20230407023736.49190-1-felipe.contreras@gmail.com (mailing list archive)
Headers show
Series Add fetch.updateHead option | expand

Message

Felipe Contreras April 7, 2023, 2:37 a.m. UTC
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"));