git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH v2] fetch: fix non-conflicting tags not being committed

From
Patrick Steinhardt <ps@pks.im>
Date
Nov 6, 2025, 11:50 UTC
Message-ID
<aQyLfD_zx0ndCLvU@pks.im>
In-Reply-To
<20251106-fix-tags-not-fetching-v2-1-610cb4b0e7c8@gmail.com>
On Thu, Nov 06, 2025 at 09:39:25AM +0100, Karthik Nayak wrote:
Show 11 quoted lines
> The commit 0e358de64a (fetch: use batched reference updates, 2025-05-19)
> updated the 'git-fetch(1)' command to use batched updates. This batches
> updates to gain performance improvements. When fetching references, each
> update is added to the transaction. Finally, when committing, individual
> updates are allowed to fail with reason, while the transaction itself
> succeeds.
> 
> One scenario which was missed here, was fetching tags. When fetching
> conflicting tags, the `fetch_and_consume_refs()` function returns '1',
> which skipped committing the transaction and directly jumped to the
> cleanup section. This mean that no updates were applied.
Okay, this is obviously broken indeed.
> This also extends to backfilling tags when using the now deprecated
> 'branches/' format for remotes.

I'm a bit lost here -- what does backfilling have to do with the "branches/" directory? The backfill is supposed to create tags that point into the history that one has just fetched. So:

  - With `--tags` we fetch all tags announced by the remote.
  - With `--no-tags` we fetch no tags.
  - Otherwise we fetch those tags that point into our history.

The last behaviour is a bit more on the esoteric side, but it's described as such in git-fetch(1):

    By default, any tag that points into the histories being fetched is
    also fetched; the effect is to fetch tags that point at branches
    that you are interested in. This default behavior can be changed by
    using the --tags or --no-tags options or by configuring
    remote.<name>.tagOpt. By using a refspec that fetches tags
    explicitly, you can fetch tags that do not point into branches you
    are interested in as well.
The following test demonstrates this behaviour:
	test_expect_success "fetch single branch without explicit tag option" '
		git init source &&
		git -C source commit --allow-empty --message common &&
		git clone file://"$(pwd)"/source target &&
		(
			cd source &&
			git commit --allow-empty --message discard-me &&
			git tag discard-me &&
			git commit --amend --allow-empty --message fetch-me &&
			git tag fetch-me
		) &&
		# The "discard-me" tag does not point into the history that we are
		# about to fetch, so it should not have been created.
		git -C target fetch origin &&
		git -C target tag -l >actual &&
		echo "fetch-me" >expect &&
		# But with "--tags" we instruct git-fetch(1) to fetch all tags, so we
		# should now see it.
		git -C target fetch origin --tags &&
		git -C target tag -l >actual &&
		cat >expect <<-\EOF &&
		discard-me
		fetch-me
		EOF
		test_cmp expect actual
	'
Show 40 quoted lines
> diff --git a/builtin/fetch.c b/builtin/fetch.c
> index c7ff3480fb..d5aee5af10 100644
> --- a/builtin/fetch.c
> +++ b/builtin/fetch.c
> @@ -1686,6 +1686,42 @@ static void ref_transaction_rejection_handler(const char *refname,
>  	*data->retcode = 1;
>  }
>  
> +/*
> + * Commit the reference transaction. If it isn't an atomic transaction, handle
> + * rejected updates as part of using batched updates.
> + */
> +static int commit_ref_transaction(struct ref_transaction **transaction,
> +				  bool is_atomic, const char *remote_name,
> +				  struct strbuf *err)
> +{
> +	int retcode = ref_transaction_commit(*transaction, err);
> +	if (retcode) {
> +		/*
> +		 * Explicitly handle transaction cleanup to avoid
> +		 * aborting an already closed transaction.
> +		 */
> +		ref_transaction_free(*transaction);
> +		*transaction = NULL;
> +	}
> +
> +	if (*transaction && !is_atomic) {
> +		struct ref_rejection_data data = {
> +			.conflict_msg_shown = 0,
> +			.remote_name = remote_name,
> +			.retcode = &retcode,
> +		};
> +
> +		ref_transaction_for_each_rejected_update(*transaction,
> +							 ref_transaction_rejection_handler,
> +							 &data);
> +
> +		ref_transaction_free(*transaction);
> +		*transaction = NULL;
> +	}

Okay. Do we need to discern cases where this is called and we haven't managed to even queue a single reference update?

Show 6 quoted lines
> +	return retcode;
> +}
> +
>  static int do_fetch(struct transport *transport,
>  		    struct refspec *rs,
>  		    const struct fetch_config *config)
Nit: it might make sense to have a preparatory commit that extracts the
function but that is otherwise a no-op change.
Show 11 quoted lines
> @@ -1826,6 +1862,10 @@ static int do_fetch(struct transport *transport,
>  
>  	if (fetch_and_consume_refs(&display_state, transport, transaction, ref_map,
>  				   &fetch_head, config)) {
> +		/* As we're using batched updates, commit any pending updates. */
> +		if (!atomic_fetch)
> +			commit_ref_transaction(&transaction, false,
> +					       transport->remote->name, &err);
>  		retcode = 1;
>  		goto cleanup;
>  	}

Hm. Don't we also have to unset the transaction now? Ah, no, you pass the pointer to the transaction here and set it to `NULL` in `commit_ref_transaction()`. Makes sense.

Show 14 quoted lines
> @@ -1848,8 +1888,12 @@ static int do_fetch(struct transport *transport,
>  			 * the transaction and don't commit anything.
>  			 */
>  			if (backfill_tags(&display_state, transport, transaction, tags_ref_map,
> -					  &fetch_head, config))
> +					  &fetch_head, config)) {
> +				if (!atomic_fetch)
> +					commit_ref_transaction(&transaction, false,
> +							       transport->remote->name, &err);
>  				retcode = 1;
> +			}
>  		}
>  
>  		free_refs(tags_ref_map);

We now have three different callsites where we commit the transaction. It gets better due to the newly introduced function, but it overall feels somewhat fragile regardless of that.

Thanks!
Patrick
Previous: Karthik NayakNext: Junio C Hamano
Message 6 of 54 in “fetch: fix non-conflicting tags not being committed”
  1. fetch: fix non-conflicting tags not being committedKarthik Nayak, Nov 3, 2025
  2. Eric SunshineNov 3, 2025
  3. Karthik NayakNov 3, 2025
  4. Justin ToblerNov 3, 2025
  5. fetch: fix non-conflicting tags not being committedKarthik Nayak, Nov 6, 2025
  6. Patrick SteinhardtNov 6, 2025
  7. Junio C HamanoNov 6, 2025
  8. Karthik NayakNov 7, 2025
  9. Patrick SteinhardtNov 7, 2025
  10. Karthik NayakNov 7, 2025
  11. Justin ToblerNov 6, 2025
  12. Karthik NayakNov 7, 2025
  13. 0/2 fetch: fix non-conflicting tags not being committedKarthik Nayak, Nov 8, 2025
  14. 1/2 fetch: extract out reference committing logicKarthik Nayak, Nov 8, 2025
  15. Patrick SteinhardtNov 10, 2025
  16. Karthik NayakNov 10, 2025
  17. 2/2 fetch: fix non-conflicting tags not being committedKarthik Nayak, Nov 8, 2025
  18. Patrick SteinhardtNov 10, 2025
  19. Karthik NayakNov 10, 2025
  20. 0/2 fetch: fix non-conflicting tags not being committedKarthik Nayak, Nov 11, 2025
  21. 1/2 fetch: extract out reference committing logicKarthik Nayak, Nov 11, 2025
  22. 2/2 fetch: fix non-conflicting tags not being committedKarthik Nayak, Nov 11, 2025
  23. Patrick SteinhardtNov 12, 2025
  24. Karthik NayakNov 12, 2025
  25. Junio C HamanoNov 12, 2025
  26. 0/2 fetch: fix non-conflicting tags not being committedKarthik Nayak, Nov 13, 2025
  27. 1/2 fetch: extract out reference committing logicKarthik Nayak, Nov 13, 2025
  28. 2/2 fetch: fix non-conflicting tags not being committedKarthik Nayak, Nov 13, 2025
  29. Junio C HamanoNov 13, 2025
  30. Karthik NayakNov 15, 2025
  31. Junio C HamanoNov 17, 2025
  32. Karthik NayakNov 17, 2025
  33. 0/3 fetch: fix non-conflicting tags not being committedKarthik Nayak, Nov 18, 2025
  34. 1/3 fetch: extract out reference committing logicKarthik Nayak, Nov 18, 2025
  35. 2/3 fetch: fix non-conflicting tags not being committedKarthik Nayak, Nov 18, 2025
  36. 3/3 fetch: fix failed batched updates skipping operationsKarthik Nayak, Nov 18, 2025
  37. Junio C HamanoNov 18, 2025
  38. Karthik NayakNov 19, 2025
  39. 0/3 fetch: fix non-conflicting tags not being committedKarthik Nayak, Nov 19, 2025
  40. 1/3 fetch: extract out reference committing logicKarthik Nayak, Nov 19, 2025
  41. 2/3 fetch: fix non-conflicting tags not being committedKarthik Nayak, Nov 19, 2025
  42. 3/3 fetch: fix failed batched updates skipping operationsKarthik Nayak, Nov 19, 2025
  43. Eric SunshineNov 19, 2025
  44. Junio C HamanoNov 19, 2025
  45. Karthik NayakNov 21, 2025
  46. 0/3 fetch: fix non-conflicting tags not being committedKarthik Nayak, Nov 21, 2025
  47. 1/3 fetch: extract out reference committing logicKarthik Nayak, Nov 21, 2025
  48. 2/3 fetch: fix non-conflicting tags not being committedKarthik Nayak, Nov 21, 2025
  49. Patrick SteinhardtDec 1, 2025
  50. Karthik NayakDec 2, 2025
  51. 3/3 fetch: fix failed batched updates skipping operationsKarthik Nayak, Nov 21, 2025
  52. Patrick SteinhardtDec 1, 2025
  53. Karthik NayakDec 2, 2025
  54. Junio C HamanoNov 21, 2025

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.