diff options
| author | Junio C Hamano <gitster@pobox.com> | 2025-08-29 09:44:37 -0700 |
|---|---|---|
| committer | Junio C Hamano <gitster@pobox.com> | 2025-08-29 09:44:37 -0700 |
| commit | fea9d18c534a445ef6e488d8ee711fa92fa0e6bd (patch) | |
| tree | 56fd0f9b7dc3c75e9b03bf024d5bff239977f5cc | |
| parent | 040f05e824b9683799148609ff0a77d468e97182 (diff) | |
| parent | 450fc2bace48ce7ba07a2431175923bf2d610635 (diff) | |
Merge branch 'jk/no-clobber-dangling-symref-with-fetch'
"git fetch" can clobber a symref that is dangling when the
remote-tracking HEAD is set to auto update, which has been
corrected.
* jk/no-clobber-dangling-symref-with-fetch:
refs: do not clobber dangling symrefs
t5510: prefer "git -C" to subshell for followRemoteHEAD tests
t5510: stop changing top-level working directory
t5510: make confusing config cleanup more explicit
| -rw-r--r-- | refs/files-backend.c | 34 | ||||
| -rw-r--r-- | refs/reftable-backend.c | 30 | ||||
| -rwxr-xr-x | t/t1400-update-ref.sh | 21 | ||||
| -rwxr-xr-x | t/t5510-fetch.sh | 543 |
4 files changed, 319 insertions, 309 deletions
diff --git a/refs/files-backend.c b/refs/files-backend.c index dfc8e9bc50..1b3bf26add 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -2515,13 +2515,37 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update, */ static enum ref_transaction_error check_old_oid(struct ref_update *update, struct object_id *oid, + struct strbuf *referent, struct strbuf *err) { if (update->flags & REF_LOG_ONLY || - !(update->flags & REF_HAVE_OLD) || - oideq(oid, &update->old_oid)) + !(update->flags & REF_HAVE_OLD)) return 0; + if (oideq(oid, &update->old_oid)) { + /* + * Normally matching the expected old oid is enough. Either we + * found the ref at the expected state, or we are creating and + * expect the null oid (and likewise found nothing). + * + * But there is one exception for the null oid: if we found a + * symref pointing to nothing we'll also get the null oid. In + * regular recursive mode, that's good (we'll write to what the + * symref points to, which doesn't exist). But in no-deref + * mode, it means we'll clobber the symref, even though the + * caller asked for this to be a creation event. So flag + * that case to preserve the dangling symref. + */ + if ((update->flags & REF_NO_DEREF) && referent->len && + is_null_oid(oid)) { + strbuf_addf(err, "cannot lock ref '%s': " + "dangling symref already exists", + ref_update_original_update_refname(update)); + return REF_TRANSACTION_ERROR_CREATE_EXISTS; + } + return 0; + } + if (is_null_oid(&update->old_oid)) { strbuf_addf(err, "cannot lock ref '%s': " "reference already exists", @@ -2661,7 +2685,8 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re if (update->old_target) ret = ref_update_check_old_target(referent.buf, update, err); else - ret = check_old_oid(update, &lock->old_oid, err); + ret = check_old_oid(update, &lock->old_oid, + &referent, err); if (ret) goto out; } else { @@ -2693,7 +2718,8 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re ret = REF_TRANSACTION_ERROR_EXPECTED_SYMREF; goto out; } else { - ret = check_old_oid(update, &lock->old_oid, err); + ret = check_old_oid(update, &lock->old_oid, + &referent, err); if (ret) { goto out; } diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index 5bccd0360a..9e889da2ff 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -1274,9 +1274,33 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor ret = ref_update_check_old_target(referent->buf, u, err); if (ret) return ret; - } else if ((u->flags & (REF_LOG_ONLY | REF_HAVE_OLD)) == REF_HAVE_OLD && - !oideq(¤t_oid, &u->old_oid)) { - if (is_null_oid(&u->old_oid)) { + } else if ((u->flags & (REF_LOG_ONLY | REF_HAVE_OLD)) == REF_HAVE_OLD) { + if (oideq(¤t_oid, &u->old_oid)) { + /* + * Normally matching the expected old oid is enough. Either we + * found the ref at the expected state, or we are creating and + * expect the null oid (and likewise found nothing). + * + * But there is one exception for the null oid: if we found a + * symref pointing to nothing we'll also get the null oid. In + * regular recursive mode, that's good (we'll write to what the + * symref points to, which doesn't exist). But in no-deref + * mode, it means we'll clobber the symref, even though the + * caller asked for this to be a creation event. So flag + * that case to preserve the dangling symref. + * + * Everything else is OK and we can fall through to the + * end of the conditional chain. + */ + if ((u->flags & REF_NO_DEREF) && + referent->len && + is_null_oid(&u->old_oid)) { + strbuf_addf(err, _("cannot lock ref '%s': " + "dangling symref already exists"), + ref_update_original_update_refname(u)); + return REF_TRANSACTION_ERROR_CREATE_EXISTS; + } + } else if (is_null_oid(&u->old_oid)) { strbuf_addf(err, _("cannot lock ref '%s': " "reference already exists"), ref_update_original_update_refname(u)); diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index 96648a6e5d..b7415ec9d5 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -2368,4 +2368,25 @@ test_expect_success REFFILES 'empty directories are pruned when not committing' test_path_is_missing .git/refs/heads/nested ' +test_expect_success 'dangling symref not overwritten by creation' ' + test_when_finished "git update-ref -d refs/heads/dangling" && + git symbolic-ref refs/heads/dangling refs/heads/does-not-exist && + test_must_fail git update-ref --no-deref --stdin 2>err <<-\EOF && + create refs/heads/dangling HEAD + EOF + test_grep "cannot lock.*dangling symref already exists" err && + test_must_fail git rev-parse --verify refs/heads/dangling && + test_must_fail git rev-parse --verify refs/heads/does-not-exist +' + +test_expect_success 'dangling symref overwritten without old oid' ' + test_when_finished "git update-ref -d refs/heads/dangling" && + git symbolic-ref refs/heads/dangling refs/heads/does-not-exist && + git update-ref --no-deref --stdin <<-\EOF && + update refs/heads/dangling HEAD + EOF + git rev-parse --verify refs/heads/dangling && + test_must_fail git rev-parse --verify refs/heads/does-not-exist +' + test_done diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index ebc696546b..83d1aadf9f 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -14,8 +14,6 @@ then test_done fi -D=$(pwd) - test_expect_success setup ' echo >file original && git add file && @@ -51,46 +49,50 @@ test_expect_success "clone and setup child repos" ' ' test_expect_success "fetch test" ' - cd "$D" && echo >file updated by origin && git commit -a -m "updated by origin" && - cd two && - git fetch && - git rev-parse --verify refs/heads/one && - mine=$(git rev-parse refs/heads/one) && - his=$(cd ../one && git rev-parse refs/heads/main) && - test "z$mine" = "z$his" + ( + cd two && + git fetch && + git rev-parse --verify refs/heads/one && + mine=$(git rev-parse refs/heads/one) && + his=$(cd ../one && git rev-parse refs/heads/main) && + test "z$mine" = "z$his" + ) ' test_expect_success "fetch test for-merge" ' - cd "$D" && - cd three && - git fetch && - git rev-parse --verify refs/heads/two && - git rev-parse --verify refs/heads/one && - main_in_two=$(cd ../two && git rev-parse main) && - one_in_two=$(cd ../two && git rev-parse one) && - { - echo "$one_in_two " && - echo "$main_in_two not-for-merge" - } >expected && - cut -f -2 .git/FETCH_HEAD >actual && - test_cmp expected actual' + ( + cd three && + git fetch && + git rev-parse --verify refs/heads/two && + git rev-parse --verify refs/heads/one && + main_in_two=$(cd ../two && git rev-parse main) && + one_in_two=$(cd ../two && git rev-parse one) && + { + echo "$one_in_two " && + echo "$main_in_two not-for-merge" + } >expected && + 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"' + ( + 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 in bare repository" ' test_when_finished rm -rf barerepo && ( - cd "$D" && git init --bare barerepo && cd barerepo && git remote add upstream ../two && @@ -105,262 +107,235 @@ test_expect_success "fetch test remote HEAD in bare repository" ' 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 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_TRACE_PACKET=$PWD/trace.out git fetch && - # Confirm that we do not even ask for HEAD when we are - # not going to act on it. - test_grep ! "ref-prefix HEAD" trace.out && - 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 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 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 && - test_cmp expect output && + 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 test followRemoteHEAD never" ' + git -C two update-ref --no-deref -d refs/remotes/origin/HEAD && + test_config -C two remote.origin.followRemoteHEAD "never" && + GIT_TRACE_PACKET=$PWD/trace.out git -C two fetch && + # Confirm that we do not even ask for HEAD when we are + # not going to act on it. + test_grep ! "ref-prefix HEAD" trace.out && + test_must_fail git -C two rev-parse --verify refs/remotes/origin/HEAD +' + +test_expect_success "fetch test followRemoteHEAD warn no change" ' + git -C two rev-parse --verify refs/remotes/origin/other && + git -C two remote set-head origin other && + git -C two rev-parse --verify refs/remotes/origin/HEAD && + git -C two rev-parse --verify refs/remotes/origin/main && + test_config -C two remote.origin.followRemoteHEAD "warn" && + git -C two 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 -C two rev-parse refs/remotes/origin/HEAD) && + branch=$(git -C two 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" - ) + git -C two update-ref --no-deref -d refs/remotes/origin/HEAD && + test_config -C two remote.origin.followRemoteHEAD "warn" && + git -C two rev-parse --verify refs/remotes/origin/main && + output=$(git -C two fetch) && + test "z" = "z$output" && + head=$(git -C two rev-parse refs/remotes/origin/HEAD) && + branch=$(git -C two 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 && - test_cmp expect output - ) + git -C two update-ref --no-deref -d refs/remotes/origin/HEAD && + git -C two update-ref refs/remotes/origin/HEAD HEAD && + HEAD=$(git -C two log --pretty="%H") && + test_config -C two remote.origin.followRemoteHEAD "warn" && + git -C two 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 && + 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" - ) + git -C two rev-parse --verify refs/remotes/origin/other && + git -C two remote set-head origin other && + git -C two rev-parse --verify refs/remotes/origin/HEAD && + git -C two rev-parse --verify refs/remotes/origin/main && + test_config -C two remote.origin.followRemoteHEAD "warn" && + output=$(git -C two fetch --quiet) && + test "z" = "z$output" && + head=$(git -C two rev-parse refs/remotes/origin/HEAD) && + branch=$(git -C two rev-parse refs/remotes/origin/other) && + test "z$head" = "z$branch" ' 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" - ) + git -C two rev-parse --verify refs/remotes/origin/other && + git -C two remote set-head origin other && + git -C two rev-parse --verify refs/remotes/origin/HEAD && + git -C two rev-parse --verify refs/remotes/origin/main && + test_config -C two remote.origin.followRemoteHEAD "warn-if-not-main" && + actual=$(git -C two fetch) && + test "z" = "z$actual" && + head=$(git -C two rev-parse refs/remotes/origin/HEAD) && + branch=$(git -C two 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" - ) + git -C two rev-parse --verify refs/remotes/origin/other && + git -C two remote set-head origin other && + git -C two rev-parse --verify refs/remotes/origin/HEAD && + git -C two rev-parse --verify refs/remotes/origin/main && + test_config -C two remote.origin.followRemoteHEAD "warn-if-not-some/different-branch" && + git -C two 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 -C two rev-parse refs/remotes/origin/HEAD) && + branch=$(git -C two 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" - ) + git -C two rev-parse --verify refs/remotes/origin/other && + git -C two remote set-head origin other && + git -C two rev-parse --verify refs/remotes/origin/HEAD && + git -C two rev-parse --verify refs/remotes/origin/main && + test_config -C two remote.origin.followRemoteHEAD "always" && + git -C two fetch && + head=$(git -C two rev-parse refs/remotes/origin/HEAD) && + branch=$(git -C two rev-parse refs/remotes/origin/main) && + test "z$head" = "z$branch" ' test_expect_success 'followRemoteHEAD does not kick in with refspecs' ' - test_when_finished "git config unset remote.origin.followRemoteHEAD" && - ( - cd "$D" && - cd two && - git remote set-head origin other && - git config set remote.origin.followRemoteHEAD always && - git fetch origin refs/heads/main:refs/remotes/origin/main && - echo refs/remotes/origin/other >expect && - git symbolic-ref refs/remotes/origin/HEAD >actual && - test_cmp expect actual - ) + git -C two remote set-head origin other && + test_config -C two remote.origin.followRemoteHEAD always && + git -C two fetch origin refs/heads/main:refs/remotes/origin/main && + echo refs/remotes/origin/other >expect && + git -C two symbolic-ref refs/remotes/origin/HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'followRemoteHEAD create does not overwrite dangling symref' ' + git -C two remote add -m does-not-exist custom-head ../one && + test_config -C two remote.custom-head.followRemoteHEAD create && + git -C two fetch custom-head && + echo refs/remotes/custom-head/does-not-exist >expect && + git -C two symbolic-ref refs/remotes/custom-head/HEAD >actual && + test_cmp expect actual ' test_expect_success 'fetch --prune on its own works as expected' ' - cd "$D" && git clone . prune && - cd prune && - git update-ref refs/remotes/origin/extrabranch main && + ( + cd prune && + git update-ref refs/remotes/origin/extrabranch main && - git fetch --prune origin && - test_must_fail git rev-parse origin/extrabranch + git fetch --prune origin && + test_must_fail git rev-parse origin/extrabranch + ) ' test_expect_success 'fetch --prune with a branch name keeps branches' ' - cd "$D" && git clone . prune-branch && - cd prune-branch && - git update-ref refs/remotes/origin/extrabranch main && + ( + cd prune-branch && + git update-ref refs/remotes/origin/extrabranch main && - git fetch --prune origin main && - git rev-parse origin/extrabranch + git fetch --prune origin main && + git rev-parse origin/extrabranch + ) ' test_expect_success 'fetch --prune with a namespace keeps other namespaces' ' - cd "$D" && git clone . prune-namespace && - cd prune-namespace && + ( + cd prune-namespace && - git fetch --prune origin refs/heads/a/*:refs/remotes/origin/a/* && - git rev-parse origin/main + git fetch --prune origin refs/heads/a/*:refs/remotes/origin/a/* && + git rev-parse origin/main + ) ' test_expect_success 'fetch --prune handles overlapping refspecs' ' - cd "$D" && git update-ref refs/pull/42/head main && git clone . prune-overlapping && - cd prune-overlapping && - git config --add remote.origin.fetch refs/pull/*/head:refs/remotes/origin/pr/* && + ( + cd prune-overlapping && + git config --add remote.origin.fetch refs/pull/*/head:refs/remotes/origin/pr/* && - git fetch --prune origin && - git rev-parse origin/main && - git rev-parse origin/pr/42 && + git fetch --prune origin && + git rev-parse origin/main && + git rev-parse origin/pr/42 && - git config --unset-all remote.origin.fetch && - git config remote.origin.fetch refs/pull/*/head:refs/remotes/origin/pr/* && - git config --add remote.origin.fetch refs/heads/*:refs/remotes/origin/* && + git config --unset-all remote.origin.fetch && + git config remote.origin.fetch refs/pull/*/head:refs/remotes/origin/pr/* && + git config --add remote.origin.fetch refs/heads/*:refs/remotes/origin/* && - git fetch --prune origin && - git rev-parse origin/main && - git rev-parse origin/pr/42 + git fetch --prune origin && + git rev-parse origin/main && + git rev-parse origin/pr/42 + ) ' test_expect_success 'fetch --prune --tags prunes branches but not tags' ' - cd "$D" && git clone . prune-tags && - cd prune-tags && - git tag sometag main && - # Create what looks like a remote-tracking branch from an earlier - # fetch that has since been deleted from the remote: - git update-ref refs/remotes/origin/fake-remote main && - - git fetch --prune --tags origin && - git rev-parse origin/main && - test_must_fail git rev-parse origin/fake-remote && - git rev-parse sometag + ( + cd prune-tags && + git tag sometag main && + # Create what looks like a remote-tracking branch from an earlier + # fetch that has since been deleted from the remote: + git update-ref refs/remotes/origin/fake-remote main && + + git fetch --prune --tags origin && + git rev-parse origin/main && + test_must_fail git rev-parse origin/fake-remote && + git rev-parse sometag + ) ' test_expect_success 'fetch --prune --tags with branch does not prune other things' ' - cd "$D" && git clone . prune-tags-branch && - cd prune-tags-branch && - git tag sometag main && - git update-ref refs/remotes/origin/extrabranch main && + ( + cd prune-tags-branch && + git tag sometag main && + git update-ref refs/remotes/origin/extrabranch main && - git fetch --prune --tags origin main && - git rev-parse origin/extrabranch && - git rev-parse sometag + git fetch --prune --tags origin main && + git rev-parse origin/extrabranch && + git rev-parse sometag + ) ' test_expect_success 'fetch --prune --tags with refspec prunes based on refspec' ' - cd "$D" && git clone . prune-tags-refspec && - cd prune-tags-refspec && - git tag sometag main && - git update-ref refs/remotes/origin/foo/otherbranch main && - git update-ref refs/remotes/origin/extrabranch main && - - git fetch --prune --tags origin refs/heads/foo/*:refs/remotes/origin/foo/* && - test_must_fail git rev-parse refs/remotes/origin/foo/otherbranch && - git rev-parse origin/extrabranch && - git rev-parse sometag + ( + cd prune-tags-refspec && + git tag sometag main && + git update-ref refs/remotes/origin/foo/otherbranch main && + git update-ref refs/remotes/origin/extrabranch main && + + git fetch --prune --tags origin refs/heads/foo/*:refs/remotes/origin/foo/* && + test_must_fail git rev-parse refs/remotes/origin/foo/otherbranch && + git rev-parse origin/extrabranch && + git rev-parse sometag + ) ' test_expect_success 'fetch --tags gets tags even without a configured remote' ' @@ -381,21 +356,21 @@ test_expect_success 'fetch --tags gets tags even without a configured remote' ' ' test_expect_success REFFILES 'fetch --prune fails to delete branches' ' - cd "$D" && git clone . prune-fail && - cd prune-fail && - git update-ref refs/remotes/origin/extrabranch main && - git pack-refs --all && - : this will prevent --prune from locking packed-refs for deleting refs, but adding loose refs still succeeds && - >.git/packed-refs.new && + ( + cd prune-fail && + git update-ref refs/remotes/origin/extrabranch main && + git pack-refs --all && + : this will prevent --prune from locking packed-refs for deleting refs, but adding loose refs still succeeds && + >.git/packed-refs.new && - test_must_fail git fetch --prune origin + test_must_fail git fetch --prune origin + ) ' test_expect_success 'fetch --atomic works with a single branch' ' - test_when_finished "rm -rf \"$D\"/atomic" && + test_when_finished "rm -rf atomic" && - cd "$D" && git clone . atomic && git branch atomic-branch && oid=$(git rev-parse atomic-branch) && @@ -408,9 +383,8 @@ test_expect_success 'fetch --atomic works with a single branch' ' ' test_expect_success 'fetch --atomic works with multiple branches' ' - test_when_finished "rm -rf \"$D\"/atomic" && + test_when_finished "rm -rf atomic" && - cd "$D" && git clone . atomic && git branch atomic-branch-1 && git branch atomic-branch-2 && @@ -423,9 +397,8 @@ test_expect_success 'fetch --atomic works with multiple branches' ' ' test_expect_success 'fetch --atomic works with mixed branches and tags' ' - test_when_finished "rm -rf \"$D\"/atomic" && + test_when_finished "rm -rf atomic" && - cd "$D" && git clone . atomic && git branch atomic-mixed-branch && git tag atomic-mixed-tag && @@ -437,9 +410,8 @@ test_expect_success 'fetch --atomic works with mixed branches and tags' ' ' test_expect_success 'fetch --atomic prunes references' ' - test_when_finished "rm -rf \"$D\"/atomic" && + test_when_finished "rm -rf atomic" && - cd "$D" && git branch atomic-prune-delete && git clone . atomic && git branch --delete atomic-prune-delete && @@ -453,9 +425,8 @@ test_expect_success 'fetch --atomic prunes references' ' ' test_expect_success 'fetch --atomic aborts with non-fast-forward update' ' - test_when_finished "rm -rf \"$D\"/atomic" && + test_when_finished "rm -rf atomic" && - cd "$D" && git branch atomic-non-ff && git clone . atomic && git rev-parse HEAD >actual && @@ -472,9 +443,8 @@ test_expect_success 'fetch --atomic aborts with non-fast-forward update' ' ' test_expect_success 'fetch --atomic executes a single reference transaction only' ' - test_when_finished "rm -rf \"$D\"/atomic" && + test_when_finished "rm -rf atomic" && - cd "$D" && git clone . atomic && git branch atomic-hooks-1 && git branch atomic-hooks-2 && @@ -499,9 +469,8 @@ test_expect_success 'fetch --atomic executes a single reference transaction only ' test_expect_success 'fetch --atomic aborts all reference updates if hook aborts' ' - test_when_finished "rm -rf \"$D\"/atomic" && + test_when_finished "rm -rf atomic" && - cd "$D" && git clone . atomic && git branch atomic-hooks-abort-1 && git branch atomic-hooks-abort-2 && @@ -536,9 +505,8 @@ test_expect_success 'fetch --atomic aborts all reference updates if hook aborts' ' test_expect_success 'fetch --atomic --append appends to FETCH_HEAD' ' - test_when_finished "rm -rf \"$D\"/atomic" && + test_when_finished "rm -rf atomic" && - cd "$D" && git clone . atomic && oid=$(git rev-parse HEAD) && @@ -574,8 +542,7 @@ test_expect_success REFFILES 'fetch --atomic fails transaction if reference lock ' test_expect_success '--refmap="" ignores configured refspec' ' - cd "$TRASH_DIRECTORY" && - git clone "$D" remote-refs && + git clone . remote-refs && git -C remote-refs rev-parse remotes/origin/main >old && git -C remote-refs update-ref refs/remotes/origin/main main~1 && git -C remote-refs rev-parse remotes/origin/main >new && @@ -599,34 +566,26 @@ test_expect_success '--refmap="" and --prune' ' test_expect_success 'fetch tags when there is no tags' ' - cd "$D" && - - mkdir notags && - cd notags && - git init && - - git fetch -t .. + git init notags && + git -C notags fetch -t .. ' test_expect_success 'fetch following tags' ' - cd "$D" && git tag -a -m "annotated" anno HEAD && git tag light HEAD && - mkdir four && - cd four && - git init && - - git fetch .. :track && - git show-ref --verify refs/tags/anno && - git show-ref --verify refs/tags/light - + git init four && + ( + cd four && + git fetch .. :track && + git show-ref --verify refs/tags/anno && + git show-ref --verify refs/tags/light + ) ' test_expect_success 'fetch uses remote ref names to describe new refs' ' - cd "$D" && git init descriptive && ( cd descriptive && @@ -654,30 +613,20 @@ test_expect_success 'fetch uses remote ref names to describe new refs' ' test_expect_success 'fetch must not resolve short tag name' ' - cd "$D" && - - mkdir five && - cd five && - git init && - - test_must_fail git fetch .. anno:five + git init five && + test_must_fail git -C five fetch .. anno:five ' test_expect_success 'fetch can now resolve short remote name' ' - cd "$D" && git update-ref refs/remotes/six/HEAD HEAD && - mkdir six && - cd six && - git init && - - git fetch .. six:six + git init six && + git -C six fetch .. six:six ' test_expect_success 'create bundle 1' ' - cd "$D" && echo >file updated again by origin && git commit -a -m "tip" && git bundle create --version=3 bundle1 main^..main @@ -691,35 +640,36 @@ test_expect_success 'header of bundle looks right' ' OID refs/heads/main EOF - sed -e "s/$OID_REGEX/OID/g" -e "5q" "$D"/bundle1 >actual && + sed -e "s/$OID_REGEX/OID/g" -e "5q" bundle1 >actual && test_cmp expect actual ' test_expect_success 'create bundle 2' ' - cd "$D" && git bundle create bundle2 main~2..main ' test_expect_success 'unbundle 1' ' - cd "$D/bundle" && - git checkout -b some-branch && - test_must_fail git fetch "$D/bundle1" main:main + ( + cd bundle && + git checkout -b some-branch && + test_must_fail git fetch bundle1 main:main + ) ' test_expect_success 'bundle 1 has only 3 files ' ' - cd "$D" && test_bundle_object_count bundle1 3 ' test_expect_success 'unbundle 2' ' - cd "$D/bundle" && - git fetch ../bundle2 main:main && - test "tip" = "$(git log -1 --pretty=oneline main | cut -d" " -f2)" + ( + cd bundle && + git fetch ../bundle2 main:main && + test "tip" = "$(git log -1 --pretty=oneline main | cut -d" " -f2)" + ) ' test_expect_success 'bundle does not prerequisite objects' ' - cd "$D" && touch file2 && git add file2 && git commit -m add.file2 file2 && @@ -729,7 +679,6 @@ test_expect_success 'bundle does not prerequisite objects' ' test_expect_success 'bundle should be able to create a full history' ' - cd "$D" && git tag -a -m "1.0" v1.0 main && git bundle create bundle4 v1.0 @@ -783,7 +732,6 @@ test_expect_success 'quoting of a strangely named repo' ' test_expect_success 'bundle should record HEAD correctly' ' - cd "$D" && git bundle create bundle5 HEAD main && git bundle list-heads bundle5 >actual && for h in HEAD refs/heads/main @@ -803,7 +751,6 @@ test_expect_success 'mark initial state of origin/main' ' test_expect_success 'explicit fetch should update tracking' ' - cd "$D" && git branch -f side && ( cd three && @@ -818,7 +765,6 @@ test_expect_success 'explicit fetch should update tracking' ' test_expect_success 'explicit pull should update tracking' ' - cd "$D" && git branch -f side && ( cd three && @@ -832,7 +778,6 @@ test_expect_success 'explicit pull should update tracking' ' ' test_expect_success 'explicit --refmap is allowed only with command-line refspec' ' - cd "$D" && ( cd three && test_must_fail git fetch --refmap="*:refs/remotes/none/*" @@ -840,7 +785,6 @@ test_expect_success 'explicit --refmap is allowed only with command-line refspec ' test_expect_success 'explicit --refmap option overrides remote.*.fetch' ' - cd "$D" && git branch -f side && ( cd three && @@ -855,7 +799,6 @@ test_expect_success 'explicit --refmap option overrides remote.*.fetch' ' ' test_expect_success 'explicitly empty --refmap option disables remote.*.fetch' ' - cd "$D" && git branch -f side && ( cd three && @@ -870,7 +813,6 @@ test_expect_success 'explicitly empty --refmap option disables remote.*.fetch' ' test_expect_success 'configured fetch updates tracking' ' - cd "$D" && git branch -f side && ( cd three && @@ -884,7 +826,6 @@ test_expect_success 'configured fetch updates tracking' ' ' test_expect_success 'non-matching refspecs do not confuse tracking update' ' - cd "$D" && git update-ref refs/odd/location HEAD && ( cd three && @@ -901,14 +842,12 @@ test_expect_success 'non-matching refspecs do not confuse tracking update' ' test_expect_success 'pushing nonexistent branch by mistake should not segv' ' - cd "$D" && test_must_fail git push seven no:no ' test_expect_success 'auto tag following fetches minimum' ' - cd "$D" && git clone .git follow && git checkout HEAD^0 && ( @@ -1307,7 +1246,7 @@ test_expect_success 'fetch --prune prints the remotes url' ' cd only-prunes && git fetch --prune origin 2>&1 | head -n1 >../actual ) && - echo "From ${D}/." >expect && + echo "From $(pwd)/." >expect && test_cmp expect actual ' @@ -1357,14 +1296,14 @@ test_expect_success 'fetching with auto-gc does not lock up' ' echo "$*" && false EOF - git clone "file://$D" auto-gc && + git clone "file://$PWD" auto-gc && test_commit test2 && ( cd auto-gc && git config fetch.unpackLimit 1 && git config gc.autoPackLimit 1 && git config gc.autoDetach false && - GIT_ASK_YESNO="$D/askyesno" git fetch --verbose >fetch.out 2>&1 && + GIT_ASK_YESNO="$TRASH_DIRECTORY/askyesno" git fetch --verbose >fetch.out 2>&1 && test_grep "Auto packing the repository" fetch.out && ! grep "Should I try again" fetch.out ) |
