summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2025-12-23 11:33:17 +0900
committerJunio C Hamano <gitster@pobox.com>2025-12-23 11:33:17 +0900
commitd8000781eb7d3f3f6922ead64bbe0d5275d43bcb (patch)
treea05831816122347ba68d174d9054024172d2a75a
parent5d2be7425cbfecac75211f47128e8aeab029e8b5 (diff)
parentb7b17ec8a6b1cb176206ad69c194b84eb3490b99 (diff)
Merge branch 'kn/fix-fetch-backfill-tag-with-batched-ref-updates'
"git fetch" that involves fetching tags, when a tag being fetched needs to overwrite existing one, failed to fetch other tags, which has been corrected. * kn/fix-fetch-backfill-tag-with-batched-ref-updates: fetch: fix failed batched updates skipping operations fetch: fix non-conflicting tags not being committed fetch: extract out reference committing logic
-rw-r--r--builtin/fetch.c71
-rwxr-xr-xt/t5510-fetch.sh150
2 files changed, 195 insertions, 26 deletions
diff --git a/builtin/fetch.c b/builtin/fetch.c
index d1c475a22c..288d3772ea 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1681,6 +1681,36 @@ 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)
+ goto out;
+
+ if (!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);
+ }
+
+out:
+ ref_transaction_free(*transaction);
+ *transaction = NULL;
+ return retcode;
+}
+
static int do_fetch(struct transport *transport,
struct refspec *rs,
const struct fetch_config *config)
@@ -1853,33 +1883,14 @@ static int do_fetch(struct transport *transport,
if (retcode)
goto cleanup;
- 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;
+ retcode = commit_ref_transaction(&transaction, atomic_fetch,
+ transport->remote->name, &err);
+ /*
+ * With '--atomic', bail out if the transaction fails. Without '--atomic',
+ * continue to fetch head and perform other post-fetch operations.
+ */
+ if (retcode && atomic_fetch)
goto cleanup;
- }
-
- if (!atomic_fetch) {
- struct ref_rejection_data data = {
- .retcode = &retcode,
- .conflict_msg_shown = 0,
- .remote_name = transport->remote->name,
- };
-
- ref_transaction_for_each_rejected_update(transaction,
- ref_transaction_rejection_handler,
- &data);
- if (retcode) {
- ref_transaction_free(transaction);
- transaction = NULL;
- goto cleanup;
- }
- }
commit_fetch_head(&fetch_head);
@@ -1945,6 +1956,14 @@ static int do_fetch(struct transport *transport,
}
cleanup:
+ /*
+ * When using batched updates, we want to commit the non-rejected
+ * updates and also handle the rejections.
+ */
+ if (retcode && !atomic_fetch && transaction)
+ commit_ref_transaction(&transaction, false,
+ transport->remote->name, &err);
+
if (retcode) {
if (err.len) {
error("%s", err.buf);
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index b7059cccaa..ce1c23684e 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -1552,6 +1552,7 @@ test_expect_success CASE_INSENSITIVE_FS,REFFILES 'D/F conflict on case insensiti
'
test_expect_success REFFILES 'D/F conflict on case sensitive filesystem with lock' '
+ test_when_finished rm -rf base repo &&
(
git init --ref-format=reftable base &&
cd base &&
@@ -1577,6 +1578,155 @@ test_expect_success REFFILES 'D/F conflict on case sensitive filesystem with loc
)
'
+test_expect_success 'fetch --tags fetches existing tags' '
+ test_when_finished rm -rf base repo &&
+
+ git init base &&
+ git -C base commit --allow-empty -m "empty-commit" &&
+
+ git clone --bare base repo &&
+
+ git -C base tag tag-1 &&
+ git -C repo for-each-ref >out &&
+ test_grep ! "tag-1" out &&
+ git -C repo fetch --tags &&
+ git -C repo for-each-ref >out &&
+ test_grep "tag-1" out
+'
+
+test_expect_success 'fetch --tags fetches non-conflicting tags' '
+ test_when_finished rm -rf base repo &&
+
+ git init base &&
+ git -C base commit --allow-empty -m "empty-commit" &&
+ git -C base tag tag-1 &&
+
+ git clone --bare base repo &&
+
+ git -C base tag tag-2 &&
+ git -C repo for-each-ref >out &&
+ test_grep ! "tag-2" out &&
+
+ git -C base commit --allow-empty -m "second empty-commit" &&
+ git -C base tag -f tag-1 &&
+
+ test_must_fail git -C repo fetch --tags 2>out &&
+ test_grep "tag-1 (would clobber existing tag)" out &&
+ git -C repo for-each-ref >out &&
+ test_grep "tag-2" out
+'
+
+test_expect_success "backfill tags when providing a refspec" '
+ test_when_finished rm -rf source target &&
+
+ git init source &&
+ git -C source commit --allow-empty --message common &&
+ git clone file://"$(pwd)"/source target &&
+ (
+ cd source &&
+ test_commit history &&
+ test_commit fetch-me
+ ) &&
+
+ # The "history" tag is backfilled even though we requested
+ # to only fetch HEAD
+ git -C target fetch origin HEAD:branch &&
+ git -C target tag -l >actual &&
+ cat >expect <<-\EOF &&
+ fetch-me
+ history
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success REFFILES "FETCH_HEAD is updated even if ref updates fail" '
+ test_when_finished rm -rf base repo &&
+
+ git init base &&
+ (
+ cd base &&
+ test_commit "updated" &&
+
+ git update-ref refs/heads/foo @ &&
+ git update-ref refs/heads/branch @
+ ) &&
+
+ git init --bare repo &&
+ (
+ cd repo &&
+ rm -f FETCH_HEAD &&
+ git remote add origin ../base &&
+ >refs/heads/foo.lock &&
+ test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err &&
+ test_grep "error: fetching ref refs/heads/foo failed: reference already exists" err &&
+ test_grep "branch ${SQ}branch${SQ} of ../base" FETCH_HEAD &&
+ test_grep "branch ${SQ}foo${SQ} of ../base" FETCH_HEAD
+ )
+'
+
+test_expect_success "upstream tracking info is added with --set-upstream" '
+ test_when_finished rm -rf base repo &&
+
+ git init --initial-branch=main base &&
+ test_commit -C base "updated" &&
+
+ git init --bare --initial-branch=main repo &&
+ (
+ cd repo &&
+ git remote add origin ../base &&
+ git fetch origin --set-upstream main &&
+ git config get branch.main.remote >actual &&
+ echo "origin" >expect &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success REFFILES "upstream tracking info is added even with conflicts" '
+ test_when_finished rm -rf base repo &&
+
+ git init --initial-branch=main base &&
+ test_commit -C base "updated" &&
+
+ git init --bare --initial-branch=main repo &&
+ (
+ cd repo &&
+ git remote add origin ../base &&
+ test_must_fail git config get branch.main.remote &&
+
+ mkdir -p refs/remotes/origin &&
+ >refs/remotes/origin/main.lock &&
+ test_must_fail git fetch origin --set-upstream main &&
+ git config get branch.main.remote >actual &&
+ echo "origin" >expect &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success REFFILES "HEAD is updated even with conflicts" '
+ test_when_finished rm -rf base repo &&
+
+ git init base &&
+ (
+ cd base &&
+ test_commit "updated" &&
+
+ git update-ref refs/heads/foo @ &&
+ git update-ref refs/heads/branch @
+ ) &&
+
+ git init --bare repo &&
+ (
+ cd repo &&
+ git remote add origin ../base &&
+
+ test_path_is_missing refs/remotes/origin/HEAD &&
+ mkdir -p refs/remotes/origin &&
+ >refs/remotes/origin/branch.lock &&
+ test_must_fail git fetch origin &&
+ test -f refs/remotes/origin/HEAD
+ )
+'
+
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd