* [PATCH v3 1/8] t0602: use subshell to ensure working directory unchanged
2025-02-06 5:56 ` [PATCH v3 0/8] add more ref consistency checks shejialuo
@ 2025-02-06 5:58 ` shejialuo
2025-02-06 5:58 ` [PATCH v3 2/8] builtin/refs: get worktrees without reading head information shejialuo
` (7 subsequent siblings)
8 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-06 5:58 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
For every test, we would execute the command "cd repo" in the first but
we never execute the command "cd .." to restore the working directory.
However, it's either not a good idea use above way. Because if any test
fails between "cd repo" and "cd ..", the "cd .." will never be reached.
And we cannot correctly restore the working directory.
Let's use subshell to ensure that the current working directory could be
restored to the correct path.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
t/t0602-reffiles-fsck.sh | 967 ++++++++++++++++++++-------------------
1 file changed, 494 insertions(+), 473 deletions(-)
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index d4a08b823b..cf7a202d0d 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -14,222 +14,229 @@ test_expect_success 'ref name should be checked' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
-
- git commit --allow-empty -m initial &&
- git checkout -b default-branch &&
- git tag default-tag &&
- git tag multi_hierarchy/default-tag &&
-
- cp $branch_dir_prefix/default-branch $branch_dir_prefix/@ &&
- git refs verify 2>err &&
- test_must_be_empty err &&
- rm $branch_dir_prefix/@ &&
-
- cp $tag_dir_prefix/default-tag $tag_dir_prefix/tag-1.lock &&
- git refs verify 2>err &&
- rm $tag_dir_prefix/tag-1.lock &&
- test_must_be_empty err &&
-
- cp $tag_dir_prefix/default-tag $tag_dir_prefix/.lock &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/tags/.lock: badRefName: invalid refname format
- EOF
- rm $tag_dir_prefix/.lock &&
- test_cmp expect err &&
-
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname" &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/$refname: badRefName: invalid refname format
- EOF
- rm "$branch_dir_prefix/$refname" &&
- test_cmp expect err || return 1
- done &&
+ (
+ cd repo &&
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- cp $tag_dir_prefix/default-tag "$tag_dir_prefix/$refname" &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/tags/$refname: badRefName: invalid refname format
- EOF
- rm "$tag_dir_prefix/$refname" &&
- test_cmp expect err || return 1
- done &&
+ git commit --allow-empty -m initial &&
+ git checkout -b default-branch &&
+ git tag default-tag &&
+ git tag multi_hierarchy/default-tag &&
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- cp $tag_dir_prefix/multi_hierarchy/default-tag "$tag_dir_prefix/multi_hierarchy/$refname" &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/tags/multi_hierarchy/$refname: badRefName: invalid refname format
- EOF
- rm "$tag_dir_prefix/multi_hierarchy/$refname" &&
- test_cmp expect err || return 1
- done &&
-
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- mkdir "$branch_dir_prefix/$refname" &&
- cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname/default-branch" &&
+ cp $branch_dir_prefix/default-branch $branch_dir_prefix/@ &&
+ git refs verify 2>err &&
+ test_must_be_empty err &&
+ rm $branch_dir_prefix/@ &&
+
+ cp $tag_dir_prefix/default-tag $tag_dir_prefix/tag-1.lock &&
+ git refs verify 2>err &&
+ rm $tag_dir_prefix/tag-1.lock &&
+ test_must_be_empty err &&
+
+ cp $tag_dir_prefix/default-tag $tag_dir_prefix/.lock &&
test_must_fail git refs verify 2>err &&
cat >expect <<-EOF &&
- error: refs/heads/$refname/default-branch: badRefName: invalid refname format
+ error: refs/tags/.lock: badRefName: invalid refname format
EOF
- rm -r "$branch_dir_prefix/$refname" &&
- test_cmp expect err || return 1
- done
+ rm $tag_dir_prefix/.lock &&
+ test_cmp expect err &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/$refname: badRefName: invalid refname format
+ EOF
+ rm "$branch_dir_prefix/$refname" &&
+ test_cmp expect err || return 1
+ done &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ cp $tag_dir_prefix/default-tag "$tag_dir_prefix/$refname" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/tags/$refname: badRefName: invalid refname format
+ EOF
+ rm "$tag_dir_prefix/$refname" &&
+ test_cmp expect err || return 1
+ done &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ cp $tag_dir_prefix/multi_hierarchy/default-tag "$tag_dir_prefix/multi_hierarchy/$refname" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/tags/multi_hierarchy/$refname: badRefName: invalid refname format
+ EOF
+ rm "$tag_dir_prefix/multi_hierarchy/$refname" &&
+ test_cmp expect err || return 1
+ done &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ mkdir "$branch_dir_prefix/$refname" &&
+ cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname/default-branch" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/$refname/default-branch: badRefName: invalid refname format
+ EOF
+ rm -r "$branch_dir_prefix/$refname" &&
+ test_cmp expect err || return 1
+ done
+ )
'
test_expect_success 'ref name check should be adapted into fsck messages' '
test_when_finished "rm -rf repo" &&
git init repo &&
branch_dir_prefix=.git/refs/heads &&
- cd repo &&
- git commit --allow-empty -m initial &&
- git checkout -b branch-1 &&
-
- cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
- git -c fsck.badRefName=warn refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/.branch-1: badRefName: invalid refname format
- EOF
- rm $branch_dir_prefix/.branch-1 &&
- test_cmp expect err &&
-
- cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
- git -c fsck.badRefName=ignore refs verify 2>err &&
- test_must_be_empty err
+ (
+ cd repo &&
+ git commit --allow-empty -m initial &&
+ git checkout -b branch-1 &&
+
+ cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
+ git -c fsck.badRefName=warn refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/.branch-1: badRefName: invalid refname format
+ EOF
+ rm $branch_dir_prefix/.branch-1 &&
+ test_cmp expect err &&
+
+ cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
+ git -c fsck.badRefName=ignore refs verify 2>err &&
+ test_must_be_empty err
+ )
'
test_expect_success 'ref name check should work for multiple worktrees' '
test_when_finished "rm -rf repo" &&
git init repo &&
-
- cd repo &&
- test_commit initial &&
- git checkout -b branch-1 &&
- test_commit second &&
- git checkout -b branch-2 &&
- test_commit third &&
- git checkout -b branch-3 &&
- git worktree add ./worktree-1 branch-1 &&
- git worktree add ./worktree-2 branch-2 &&
- worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
- worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
-
- (
- cd worktree-1 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-3
- ) &&
(
- cd worktree-2 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-3
- ) &&
-
- cp $worktree1_refdir_prefix/branch-4 $worktree1_refdir_prefix/'\'' branch-5'\'' &&
- cp $worktree2_refdir_prefix/branch-4 $worktree2_refdir_prefix/'\''~branch-6'\'' &&
-
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
- error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err &&
-
- for worktree in "worktree-1" "worktree-2"
- do
+ cd repo &&
+ test_commit initial &&
+ git checkout -b branch-1 &&
+ test_commit second &&
+ git checkout -b branch-2 &&
+ test_commit third &&
+ git checkout -b branch-3 &&
+ git worktree add ./worktree-1 branch-1 &&
+ git worktree add ./worktree-2 branch-2 &&
+ worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
+ worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
+
(
- cd $worktree &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
- error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err || return 1
- )
- done
+ cd worktree-1 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-3
+ ) &&
+ (
+ cd worktree-2 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-3
+ ) &&
+
+ cp $worktree1_refdir_prefix/branch-4 $worktree1_refdir_prefix/'\'' branch-5'\'' &&
+ cp $worktree2_refdir_prefix/branch-4 $worktree2_refdir_prefix/'\''~branch-6'\'' &&
+
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
+ error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err &&
+
+ for worktree in "worktree-1" "worktree-2"
+ do
+ (
+ cd $worktree &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
+ error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err || return 1
+ )
+ done
+ )
'
test_expect_success 'regular ref content should be checked (individual)' '
test_when_finished "rm -rf repo" &&
git init repo &&
branch_dir_prefix=.git/refs/heads &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
- git refs verify 2>err &&
- test_must_be_empty err &&
+ git refs verify 2>err &&
+ test_must_be_empty err &&
- for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$branch_dir_prefix/branch-bad &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/branch-bad: badRefContent: $bad_content
- EOF
- rm $branch_dir_prefix/branch-bad &&
- test_cmp expect err || return 1
- done &&
+ for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$branch_dir_prefix/branch-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/branch-bad: badRefContent: $bad_content
+ EOF
+ rm $branch_dir_prefix/branch-bad &&
+ test_cmp expect err || return 1
+ done &&
- for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$branch_dir_prefix/a/b/branch-bad &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/a/b/branch-bad: badRefContent: $bad_content
- EOF
- rm $branch_dir_prefix/a/b/branch-bad &&
- test_cmp expect err || return 1
- done &&
-
- printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- rm $branch_dir_prefix/branch-no-newline &&
- test_cmp expect err &&
-
- for trailing_content in " garbage" " more garbage"
- do
- printf "%s" "$(git rev-parse main)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$branch_dir_prefix/a/b/branch-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/a/b/branch-bad: badRefContent: $bad_content
+ EOF
+ rm $branch_dir_prefix/a/b/branch-bad &&
+ test_cmp expect err || return 1
+ done &&
+
+ printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
EOF
- rm $branch_dir_prefix/branch-garbage &&
- test_cmp expect err || return 1
- done &&
+ rm $branch_dir_prefix/branch-no-newline &&
+ test_cmp expect err &&
- printf "%s\n\n\n" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
+ for trailing_content in " garbage" " more garbage"
+ do
+ printf "%s" "$(git rev-parse main)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage &&
+ test_cmp expect err || return 1
+ done &&
+ printf "%s\n\n\n" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
- '\''
- EOF
- rm $branch_dir_prefix/branch-garbage-special &&
- test_cmp expect err &&
- printf "%s\n\n\n garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
+ '\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage-special &&
+ test_cmp expect err &&
+
+ printf "%s\n\n\n garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
- garbage'\''
- EOF
- rm $branch_dir_prefix/branch-garbage-special &&
- test_cmp expect err
+ garbage'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage-special &&
+ test_cmp expect err
+ )
'
test_expect_success 'regular ref content should be checked (aggregate)' '
@@ -237,99 +244,103 @@ test_expect_success 'regular ref content should be checked (aggregate)' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- bad_content_1=$(git rev-parse main)x &&
- bad_content_2=xfsazqfxcadas &&
- bad_content_3=Xfsazqfxcadas &&
- printf "%s" $bad_content_1 >$tag_dir_prefix/tag-bad-1 &&
- printf "%s" $bad_content_2 >$tag_dir_prefix/tag-bad-2 &&
- printf "%s" $bad_content_3 >$branch_dir_prefix/a/b/branch-bad &&
- printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
- printf "%s garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage &&
-
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/a/b/branch-bad: badRefContent: $bad_content_3
- error: refs/tags/tag-bad-1: badRefContent: $bad_content_1
- error: refs/tags/tag-bad-2: badRefContent: $bad_content_2
- warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
- warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ bad_content_1=$(git rev-parse main)x &&
+ bad_content_2=xfsazqfxcadas &&
+ bad_content_3=Xfsazqfxcadas &&
+ printf "%s" $bad_content_1 >$tag_dir_prefix/tag-bad-1 &&
+ printf "%s" $bad_content_2 >$tag_dir_prefix/tag-bad-2 &&
+ printf "%s" $bad_content_3 >$branch_dir_prefix/a/b/branch-bad &&
+ printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
+ printf "%s garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage &&
+
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/a/b/branch-bad: badRefContent: $bad_content_3
+ error: refs/tags/tag-bad-1: badRefContent: $bad_content_1
+ error: refs/tags/tag-bad-2: badRefContent: $bad_content_2
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
+ warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err
+ )
'
test_expect_success 'textual symref content should be checked (individual)' '
test_when_finished "rm -rf repo" &&
git init repo &&
branch_dir_prefix=.git/refs/heads &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ for good_referent in "refs/heads/branch" "HEAD"
+ do
+ printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
+ git refs verify 2>err &&
+ rm $branch_dir_prefix/branch-good &&
+ test_must_be_empty err || return 1
+ done &&
+
+ for bad_referent in "refs/heads/.branch" "refs/heads/~branch" "refs/heads/?branch"
+ do
+ printf "ref: %s\n" $bad_referent >$branch_dir_prefix/branch-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/branch-bad: badReferentName: points to invalid refname '\''$bad_referent'\''
+ EOF
+ rm $branch_dir_prefix/branch-bad &&
+ test_cmp expect err || return 1
+ done &&
- for good_referent in "refs/heads/branch" "HEAD"
- do
- printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
+ printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline &&
git refs verify 2>err &&
- rm $branch_dir_prefix/branch-good &&
- test_must_be_empty err || return 1
- done &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
+ EOF
+ rm $branch_dir_prefix/branch-no-newline &&
+ test_cmp expect err &&
- for bad_referent in "refs/heads/.branch" "refs/heads/~branch" "refs/heads/?branch"
- do
- printf "ref: %s\n" $bad_referent >$branch_dir_prefix/branch-bad &&
- test_must_fail git refs verify 2>err &&
+ printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
+ EOF
+ rm $branch_dir_prefix/a/b/branch-trailing-1 &&
+ test_cmp expect err &&
+
+ printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- error: refs/heads/branch-bad: badReferentName: points to invalid refname '\''$bad_referent'\''
+ warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
EOF
- rm $branch_dir_prefix/branch-bad &&
- test_cmp expect err || return 1
- done &&
-
- printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- rm $branch_dir_prefix/branch-no-newline &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-trailing-1 &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-trailing-2 &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-trailing-3 &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-complicated &&
- test_cmp expect err
+ rm $branch_dir_prefix/a/b/branch-trailing-2 &&
+ test_cmp expect err &&
+
+ printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
+ EOF
+ rm $branch_dir_prefix/a/b/branch-trailing-3 &&
+ test_cmp expect err &&
+
+ printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
+ EOF
+ rm $branch_dir_prefix/a/b/branch-complicated &&
+ test_cmp expect err
+ )
'
test_expect_success 'textual symref content should be checked (aggregate)' '
@@ -337,32 +348,34 @@ test_expect_success 'textual symref content should be checked (aggregate)' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- printf "ref: refs/heads/branch\n" >$branch_dir_prefix/branch-good &&
- printf "ref: HEAD\n" >$branch_dir_prefix/branch-head &&
- printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline-1 &&
- printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
- printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
- printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
- printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
- printf "ref: refs/heads/.branch\n" >$branch_dir_prefix/branch-bad-1 &&
-
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/branch-bad-1: badReferentName: points to invalid refname '\''refs/heads/.branch'\''
- warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/branch-no-newline-1: refMissingNewline: misses LF at the end
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ printf "ref: refs/heads/branch\n" >$branch_dir_prefix/branch-good &&
+ printf "ref: HEAD\n" >$branch_dir_prefix/branch-head &&
+ printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline-1 &&
+ printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
+ printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
+ printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
+ printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
+ printf "ref: refs/heads/.branch\n" >$branch_dir_prefix/branch-bad-1 &&
+
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/branch-bad-1: badReferentName: points to invalid refname '\''refs/heads/.branch'\''
+ warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/branch-no-newline-1: refMissingNewline: misses LF at the end
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err
+ )
'
test_expect_success 'the target of the textual symref should be checked' '
@@ -370,28 +383,30 @@ test_expect_success 'the target of the textual symref should be checked' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- for good_referent in "refs/heads/branch" "HEAD" "refs/tags/tag"
- do
- printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
- git refs verify 2>err &&
- rm $branch_dir_prefix/branch-good &&
- test_must_be_empty err || return 1
- done &&
-
- for nonref_referent in "refs-back/heads/branch" "refs-back/tags/tag" "reflogs/refs/heads/branch"
- do
- printf "ref: %s\n" $nonref_referent >$branch_dir_prefix/branch-bad-1 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-bad-1: symrefTargetIsNotARef: points to non-ref target '\''$nonref_referent'\''
- EOF
- rm $branch_dir_prefix/branch-bad-1 &&
- test_cmp expect err || return 1
- done
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ for good_referent in "refs/heads/branch" "HEAD" "refs/tags/tag"
+ do
+ printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
+ git refs verify 2>err &&
+ rm $branch_dir_prefix/branch-good &&
+ test_must_be_empty err || return 1
+ done &&
+
+ for nonref_referent in "refs-back/heads/branch" "refs-back/tags/tag" "reflogs/refs/heads/branch"
+ do
+ printf "ref: %s\n" $nonref_referent >$branch_dir_prefix/branch-bad-1 &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-bad-1: symrefTargetIsNotARef: points to non-ref target '\''$nonref_referent'\''
+ EOF
+ rm $branch_dir_prefix/branch-bad-1 &&
+ test_cmp expect err || return 1
+ done
+ )
'
test_expect_success SYMLINKS 'symlink symref content should be checked' '
@@ -399,201 +414,207 @@ test_expect_success SYMLINKS 'symlink symref content should be checked' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- ln -sf ./main $branch_dir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $branch_dir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../logs/branch-escape $branch_dir_prefix/branch-symbolic &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
- warning: refs/heads/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
- EOF
- rm $branch_dir_prefix/branch-symbolic &&
- test_cmp expect err &&
-
- ln -sf ./"branch " $branch_dir_prefix/branch-symbolic-bad &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic-bad: symlinkRef: use deprecated symbolic link for symref
- error: refs/heads/branch-symbolic-bad: badReferentName: points to invalid refname '\''refs/heads/branch '\''
- EOF
- rm $branch_dir_prefix/branch-symbolic-bad &&
- test_cmp expect err &&
-
- ln -sf ./".tag" $tag_dir_prefix/tag-symbolic-1 &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/tags/tag-symbolic-1: symlinkRef: use deprecated symbolic link for symref
- error: refs/tags/tag-symbolic-1: badReferentName: points to invalid refname '\''refs/tags/.tag'\''
- EOF
- rm $tag_dir_prefix/tag-symbolic-1 &&
- test_cmp expect err
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ ln -sf ./main $branch_dir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
+ EOF
+ rm $branch_dir_prefix/branch-symbolic-good &&
+ test_cmp expect err &&
+
+ ln -sf ../../logs/branch-escape $branch_dir_prefix/branch-symbolic &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
+ warning: refs/heads/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
+ EOF
+ rm $branch_dir_prefix/branch-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ./"branch " $branch_dir_prefix/branch-symbolic-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-symbolic-bad: symlinkRef: use deprecated symbolic link for symref
+ error: refs/heads/branch-symbolic-bad: badReferentName: points to invalid refname '\''refs/heads/branch '\''
+ EOF
+ rm $branch_dir_prefix/branch-symbolic-bad &&
+ test_cmp expect err &&
+
+ ln -sf ./".tag" $tag_dir_prefix/tag-symbolic-1 &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/tags/tag-symbolic-1: symlinkRef: use deprecated symbolic link for symref
+ error: refs/tags/tag-symbolic-1: badReferentName: points to invalid refname '\''refs/tags/.tag'\''
+ EOF
+ rm $tag_dir_prefix/tag-symbolic-1 &&
+ test_cmp expect err
+ )
'
test_expect_success SYMLINKS 'symlink symref content should be checked (worktree)' '
test_when_finished "rm -rf repo" &&
git init repo &&
- cd repo &&
- test_commit default &&
- git branch branch-1 &&
- git branch branch-2 &&
- git branch branch-3 &&
- git worktree add ./worktree-1 branch-2 &&
- git worktree add ./worktree-2 branch-3 &&
- main_worktree_refdir_prefix=.git/refs/heads &&
- worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
- worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
-
(
- cd worktree-1 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
- (
- cd worktree-2 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
-
- ln -sf ../../../../refs/heads/good-branch $worktree1_refdir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $worktree1_refdir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../../../worktrees/worktree-1/good-branch $worktree2_refdir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-2/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $worktree2_refdir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../worktrees/worktree-2/good-branch $main_worktree_refdir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $main_worktree_refdir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../../../logs/branch-escape $worktree1_refdir_prefix/branch-symbolic &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
- warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
- EOF
- rm $worktree1_refdir_prefix/branch-symbolic &&
- test_cmp expect err &&
-
- for bad_referent_name in ".tag" "branch "
- do
- ln -sf ./"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git branch branch-3 &&
+ git worktree add ./worktree-1 branch-2 &&
+ git worktree add ./worktree-2 branch-3 &&
+ main_worktree_refdir_prefix=.git/refs/heads &&
+ worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
+ worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
+
+ (
+ cd worktree-1 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+ (
+ cd worktree-2 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+
+ ln -sf ../../../../refs/heads/good-branch $worktree1_refdir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-1/refs/worktree/$bad_referent_name'\''
+ warning: worktrees/worktree-1/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
EOF
- rm $worktree1_refdir_prefix/bad-symbolic &&
+ rm $worktree1_refdir_prefix/branch-symbolic-good &&
test_cmp expect err &&
- ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ ln -sf ../../../../worktrees/worktree-1/good-branch $worktree2_refdir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ warning: worktrees/worktree-2/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
EOF
- rm $worktree1_refdir_prefix/bad-symbolic &&
+ rm $worktree2_refdir_prefix/branch-symbolic-good &&
test_cmp expect err &&
- ln -sf ./"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ ln -sf ../../worktrees/worktree-2/good-branch $main_worktree_refdir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-2/refs/worktree/$bad_referent_name'\''
+ warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
EOF
- rm $worktree2_refdir_prefix/bad-symbolic &&
+ rm $main_worktree_refdir_prefix/branch-symbolic-good &&
test_cmp expect err &&
- ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ ln -sf ../../../../logs/branch-escape $worktree1_refdir_prefix/branch-symbolic &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
+ warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
EOF
- rm $worktree2_refdir_prefix/bad-symbolic &&
- test_cmp expect err || return 1
- done
+ rm $worktree1_refdir_prefix/branch-symbolic &&
+ test_cmp expect err &&
+
+ for bad_referent_name in ".tag" "branch "
+ do
+ ln -sf ./"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-1/refs/worktree/$bad_referent_name'\''
+ EOF
+ rm $worktree1_refdir_prefix/bad-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ EOF
+ rm $worktree1_refdir_prefix/bad-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ./"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-2/refs/worktree/$bad_referent_name'\''
+ EOF
+ rm $worktree2_refdir_prefix/bad-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ EOF
+ rm $worktree2_refdir_prefix/bad-symbolic &&
+ test_cmp expect err || return 1
+ done
+ )
'
test_expect_success 'ref content checks should work with worktrees' '
test_when_finished "rm -rf repo" &&
git init repo &&
- cd repo &&
- test_commit default &&
- git branch branch-1 &&
- git branch branch-2 &&
- git branch branch-3 &&
- git worktree add ./worktree-1 branch-2 &&
- git worktree add ./worktree-2 branch-3 &&
- worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
- worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
-
(
- cd worktree-1 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
- (
- cd worktree-2 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git branch branch-3 &&
+ git worktree add ./worktree-1 branch-2 &&
+ git worktree add ./worktree-2 branch-3 &&
+ worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
+ worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
- for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$worktree1_refdir_prefix/bad-branch-1 &&
- test_must_fail git refs verify 2>err &&
+ (
+ cd worktree-1 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+ (
+ cd worktree-2 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+
+ for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$worktree1_refdir_prefix/bad-branch-1 &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-1/refs/worktree/bad-branch-1: badRefContent: $bad_content
+ EOF
+ rm $worktree1_refdir_prefix/bad-branch-1 &&
+ test_cmp expect err || return 1
+ done &&
+
+ for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$worktree2_refdir_prefix/bad-branch-2 &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-2/refs/worktree/bad-branch-2: badRefContent: $bad_content
+ EOF
+ rm $worktree2_refdir_prefix/bad-branch-2 &&
+ test_cmp expect err || return 1
+ done &&
+
+ printf "%s" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-no-newline &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- error: worktrees/worktree-1/refs/worktree/bad-branch-1: badRefContent: $bad_content
+ warning: worktrees/worktree-1/refs/worktree/branch-no-newline: refMissingNewline: misses LF at the end
EOF
- rm $worktree1_refdir_prefix/bad-branch-1 &&
- test_cmp expect err || return 1
- done &&
+ rm $worktree1_refdir_prefix/branch-no-newline &&
+ test_cmp expect err &&
- for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$worktree2_refdir_prefix/bad-branch-2 &&
- test_must_fail git refs verify 2>err &&
+ printf "%s garbage" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-garbage &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- error: worktrees/worktree-2/refs/worktree/bad-branch-2: badRefContent: $bad_content
+ warning: worktrees/worktree-1/refs/worktree/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
EOF
- rm $worktree2_refdir_prefix/bad-branch-2 &&
- test_cmp expect err || return 1
- done &&
-
- printf "%s" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-no-newline &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- rm $worktree1_refdir_prefix/branch-no-newline &&
- test_cmp expect err &&
-
- printf "%s garbage" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-garbage &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
- EOF
- rm $worktree1_refdir_prefix/branch-garbage &&
- test_cmp expect err
+ rm $worktree1_refdir_prefix/branch-garbage &&
+ test_cmp expect err
+ )
'
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v3 2/8] builtin/refs: get worktrees without reading head information
2025-02-06 5:56 ` [PATCH v3 0/8] add more ref consistency checks shejialuo
2025-02-06 5:58 ` [PATCH v3 1/8] t0602: use subshell to ensure working directory unchanged shejialuo
@ 2025-02-06 5:58 ` shejialuo
2025-02-06 5:58 ` [PATCH v3 3/8] packed-backend: check whether the "packed-refs" is regular file shejialuo
` (6 subsequent siblings)
8 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-06 5:58 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
In "packed-backend.c", there are some functions such as "create_snapshot"
and "next_record" which would check the correctness of the content of
the "packed-ref" file. When anything is bad, the program will die.
It may seem that we have nothing relevant to above feature, because we
are going to read and parse the raw "packed-ref" file without creating
the snapshot and using the ref iterator to check the consistency.
However, when using "get_worktrees" in "builtin/refs", we would parse
the "HEAD" information. If the referent of the "HEAD" is inside the
"packed-ref", we will call "create_snapshot" function to parse the
"packed-ref" to get the information. No matter whether the entry of
"HEAD" in "packed-ref" is correct, "create_snapshot" would call
"verify_buffer_safe" to check whether there is a newline in the last
line of the file. If not, the program will die.
Although this behavior has no harm for the program, it will
short-circuit the program. When the users execute "git refs verify" or
"git fsck", we should avoid reading the head information, which may
execute the read operation in packed backend with stricter checks to die
the program. Instead, we should continue to check other parts of the
"packed-refs" file completely.
Fortunately, in 465a22b338 (worktree: skip reading HEAD when repairing
worktrees, 2023-12-29), we have introduced a function
"get_worktrees_internal" which allows us to get worktrees without
reading head information.
Create a new exposed function "get_worktrees_without_reading_head", then
replace the "get_worktrees" in "builtin/refs" with the new created
function.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
builtin/refs.c | 2 +-
worktree.c | 5 +++++
worktree.h | 6 ++++++
3 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/builtin/refs.c b/builtin/refs.c
index a29f195834..55ff5dae11 100644
--- a/builtin/refs.c
+++ b/builtin/refs.c
@@ -88,7 +88,7 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix,
git_config(git_fsck_config, &fsck_refs_options);
prepare_repo_settings(the_repository);
- worktrees = get_worktrees();
+ worktrees = get_worktrees_without_reading_head();
for (size_t i = 0; worktrees[i]; i++)
ret |= refs_fsck(get_worktree_ref_store(worktrees[i]),
&fsck_refs_options, worktrees[i]);
diff --git a/worktree.c b/worktree.c
index 248bbb39d4..89b7d86cef 100644
--- a/worktree.c
+++ b/worktree.c
@@ -175,6 +175,11 @@ struct worktree **get_worktrees(void)
return get_worktrees_internal(0);
}
+struct worktree **get_worktrees_without_reading_head(void)
+{
+ return get_worktrees_internal(1);
+}
+
const char *get_worktree_git_dir(const struct worktree *wt)
{
if (!wt)
diff --git a/worktree.h b/worktree.h
index 38145df80f..1ba4a161a0 100644
--- a/worktree.h
+++ b/worktree.h
@@ -30,6 +30,12 @@ struct worktree {
*/
struct worktree **get_worktrees(void);
+/*
+ * Like `get_worktrees`, but does not read HEAD. This is useful when checking
+ * the consistency, as reading HEAD may not be necessary.
+ */
+struct worktree **get_worktrees_without_reading_head(void);
+
/*
* Returns 1 if linked worktrees exist, 0 otherwise.
*/
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v3 3/8] packed-backend: check whether the "packed-refs" is regular file
2025-02-06 5:56 ` [PATCH v3 0/8] add more ref consistency checks shejialuo
2025-02-06 5:58 ` [PATCH v3 1/8] t0602: use subshell to ensure working directory unchanged shejialuo
2025-02-06 5:58 ` [PATCH v3 2/8] builtin/refs: get worktrees without reading head information shejialuo
@ 2025-02-06 5:58 ` shejialuo
2025-02-06 5:59 ` [PATCH v3 4/8] packed-backend: add "packed-refs" header consistency check shejialuo
` (5 subsequent siblings)
8 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-06 5:58 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
Although "git-fsck(1)" and "packed-backend.c" will check some
consistency and correctness of "packed-refs" file, they never check the
filetype of the "packed-refs". The user should always use "git
pack-refs" command to create the raw regular "packed-refs" file, so we
need to explicitly check this in "git refs verify".
We could use "open_nofollow" wrapper to open the raw "packed-refs" file.
If the returned "fd" value is less than 0, we could check whether the
"errno" is "ELOOP" to report an error to the user.
Reuse "FSCK_MSG_BAD_REF_FILETYPE" fsck message id to report the error to
the user if "packed-refs" is not a regular file.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
refs/packed-backend.c | 39 +++++++++++++++++++++++++++++++++++----
t/t0602-reffiles-fsck.sh | 22 ++++++++++++++++++++++
2 files changed, 57 insertions(+), 4 deletions(-)
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index a7b6f74b6e..6401cecd5f 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -4,6 +4,7 @@
#include "../git-compat-util.h"
#include "../config.h"
#include "../dir.h"
+#include "../fsck.h"
#include "../gettext.h"
#include "../hash.h"
#include "../hex.h"
@@ -1748,15 +1749,45 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
return empty_ref_iterator_begin();
}
-static int packed_fsck(struct ref_store *ref_store UNUSED,
- struct fsck_options *o UNUSED,
+static int packed_fsck(struct ref_store *ref_store,
+ struct fsck_options *o,
struct worktree *wt)
{
+ struct packed_ref_store *refs = packed_downcast(ref_store,
+ REF_STORE_READ, "fsck");
+ int ret = 0;
+ int fd;
if (!is_main_worktree(wt))
- return 0;
+ goto cleanup;
- return 0;
+ if (o->verbose)
+ fprintf_ln(stderr, "Checking packed-refs file %s", refs->path);
+
+ fd = open_nofollow(refs->path, O_RDONLY);
+ if (fd < 0) {
+ /*
+ * If the packed-refs file doesn't exist, there's nothing
+ * to check.
+ */
+ if (errno == ENOENT)
+ goto cleanup;
+
+ if (errno == ELOOP) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs";
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_FILETYPE,
+ "not a regular file");
+ goto cleanup;
+ }
+
+ ret = error_errno(_("unable to open %s"), refs->path);
+ goto cleanup;
+ }
+
+cleanup:
+ return ret;
}
struct ref_storage_be refs_be_packed = {
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index cf7a202d0d..42c8d4ca1e 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -617,4 +617,26 @@ test_expect_success 'ref content checks should work with worktrees' '
)
'
+test_expect_success SYMLINKS 'the filetype of packed-refs should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git branch branch-3 &&
+ git pack-refs --all &&
+
+ mv .git/packed-refs .git/packed-refs-back &&
+ ln -sf packed-refs-bak .git/packed-refs &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs: badRefFiletype: not a regular file
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v3 4/8] packed-backend: add "packed-refs" header consistency check
2025-02-06 5:56 ` [PATCH v3 0/8] add more ref consistency checks shejialuo
` (2 preceding siblings ...)
2025-02-06 5:58 ` [PATCH v3 3/8] packed-backend: check whether the "packed-refs" is regular file shejialuo
@ 2025-02-06 5:59 ` shejialuo
2025-02-12 9:56 ` Patrick Steinhardt
2025-02-06 5:59 ` [PATCH v3 5/8] packed-backend: check whether the refname contains NUL characters shejialuo
` (4 subsequent siblings)
8 siblings, 1 reply; 168+ messages in thread
From: shejialuo @ 2025-02-06 5:59 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
In "packed-backend.c::create_snapshot", if there is a header (the line
which starts with '#'), we will check whether the line starts with "#
pack-refs with:". As we are going to implement the header consistency
check, we should port this check into "packed_fsck".
However, we need to consider other situations and discuss whether we
need to add checks.
1. If the header does not exist, we should not report an error to the
user. This is because in older Git version, we never write header in
the "packed-refs" file. Also, we do allow no header in "packed-refs"
in runtime.
2. If the header content does not start with "# packed-ref with:", we
should report an error just like what "create_snapshot" does. So,
create a new fsck message "badPackedRefHeader(ERROR)" for this.
3. If the header content is not the same as the constant string
"PACKED_REFS_HEADER". This is expected because we make it extensible
intentionally. So, there is no need to report.
As we have analyzed, we only need to check the case 2 in the above. In
order to do this, read the "packed-refs" file via "strbuf_read". Like
what "create_snapshot" and other functions do, we could split the line
by finding the next newline in the buffer. When we cannot find a
newline, we could report an error.
So, create a function "packed_fsck_ref_next_line" to find the next
newline and if there is no such newline, use
"packedRefEntryNotTerminated(ERROR)" to report an error to the user.
Then, parse the first line to apply the checks. Update the test to
exercise the code.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/fsck-msgids.txt | 8 ++++
fsck.h | 2 +
refs/packed-backend.c | 73 +++++++++++++++++++++++++++++++++++
t/t0602-reffiles-fsck.sh | 25 ++++++++++++
4 files changed, 108 insertions(+)
diff --git a/Documentation/fsck-msgids.txt b/Documentation/fsck-msgids.txt
index b14bc44ca4..11906f90fd 100644
--- a/Documentation/fsck-msgids.txt
+++ b/Documentation/fsck-msgids.txt
@@ -16,6 +16,10 @@
`badObjectSha1`::
(ERROR) An object has a bad sha1.
+`badPackedRefHeader`::
+ (ERROR) The "packed-refs" file contains an invalid
+ header.
+
`badParentSha1`::
(ERROR) A commit object has a bad parent sha1.
@@ -176,6 +180,10 @@
`nullSha1`::
(WARN) Tree contains entries pointing to a null sha1.
+`packedRefEntryNotTerminated`::
+ (ERROR) The "packed-refs" file contains an entry that is
+ not terminated by a newline.
+
`refMissingNewline`::
(INFO) A loose ref that does not end with newline(LF). As
valid implementations of Git never created such a loose ref
diff --git a/fsck.h b/fsck.h
index a44c231a5f..67e3c97bc0 100644
--- a/fsck.h
+++ b/fsck.h
@@ -30,6 +30,7 @@ enum fsck_msg_type {
FUNC(BAD_EMAIL, ERROR) \
FUNC(BAD_NAME, ERROR) \
FUNC(BAD_OBJECT_SHA1, ERROR) \
+ FUNC(BAD_PACKED_REF_HEADER, ERROR) \
FUNC(BAD_PARENT_SHA1, ERROR) \
FUNC(BAD_REF_CONTENT, ERROR) \
FUNC(BAD_REF_FILETYPE, ERROR) \
@@ -53,6 +54,7 @@ enum fsck_msg_type {
FUNC(MISSING_TYPE, ERROR) \
FUNC(MISSING_TYPE_ENTRY, ERROR) \
FUNC(MULTIPLE_AUTHORS, ERROR) \
+ FUNC(PACKED_REF_ENTRY_NOT_TERMINATED, ERROR) \
FUNC(TREE_NOT_SORTED, ERROR) \
FUNC(UNKNOWN_TYPE, ERROR) \
FUNC(ZERO_PADDED_DATE, ERROR) \
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 6401cecd5f..683cfe78dc 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1749,12 +1749,76 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
return empty_ref_iterator_begin();
}
+static int packed_fsck_ref_next_line(struct fsck_options *o,
+ struct strbuf *packed_entry, const char *start,
+ const char *eof, const char **eol)
+{
+ int ret = 0;
+
+ *eol = memchr(start, '\n', eof - start);
+ if (!*eol) {
+ struct fsck_ref_report report = { 0 };
+
+ report.path = packed_entry->buf;
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_PACKED_REF_ENTRY_NOT_TERMINATED,
+ "'%.*s' is not terminated with a newline",
+ (int)(eof - start), start);
+
+ /*
+ * There is no newline but we still want to parse it to the end of
+ * the buffer.
+ */
+ *eol = eof;
+ }
+
+ return ret;
+}
+
+static int packed_fsck_ref_header(struct fsck_options *o,
+ const char *start, const char *eol)
+{
+ if (!starts_with(start, "# pack-refs with:")) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs.header";
+
+ return fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_HEADER,
+ "'%.*s' does not start with '# pack-refs with:'",
+ (int)(eol - start), start);
+ }
+
+ return 0;
+}
+
+static int packed_fsck_ref_content(struct fsck_options *o,
+ const char *start, const char *eof)
+{
+ struct strbuf packed_entry = STRBUF_INIT;
+ unsigned long line_number = 1;
+ const char *eol;
+ int ret = 0;
+
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ ret |= packed_fsck_ref_next_line(o, &packed_entry, start, eof, &eol);
+ if (*start == '#') {
+ ret |= packed_fsck_ref_header(o, start, eol);
+
+ start = eol + 1;
+ line_number++;
+ }
+
+ strbuf_release(&packed_entry);
+ return ret;
+}
+
static int packed_fsck(struct ref_store *ref_store,
struct fsck_options *o,
struct worktree *wt)
{
struct packed_ref_store *refs = packed_downcast(ref_store,
REF_STORE_READ, "fsck");
+ struct strbuf packed_ref_content = STRBUF_INIT;
int ret = 0;
int fd;
@@ -1786,7 +1850,16 @@ static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
+ if (strbuf_read(&packed_ref_content, fd, 0) < 0) {
+ ret = error_errno(_("unable to read %s"), refs->path);
+ goto cleanup;
+ }
+
+ ret = packed_fsck_ref_content(o, packed_ref_content.buf,
+ packed_ref_content.buf + packed_ref_content.len);
+
cleanup:
+ strbuf_release(&packed_ref_content);
return ret;
}
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index 42c8d4ca1e..da321f16c6 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -639,4 +639,29 @@ test_expect_success SYMLINKS 'the filetype of packed-refs should be checked' '
)
'
+test_expect_success 'packed-refs header should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+
+ git refs verify 2>err &&
+ test_must_be_empty err &&
+
+ for bad_header in "# pack-refs wit: peeled fully-peeled sorted " \
+ "# pack-refs with traits: peeled fully-peeled sorted " \
+ "# pack-refs with a: peeled fully-peeled"
+ do
+ printf "%s\n" "$bad_header" >.git/packed-refs &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs.header: badPackedRefHeader: '\''$bad_header'\'' does not start with '\''# pack-refs with:'\''
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err || return 1
+ done
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* Re: [PATCH v3 4/8] packed-backend: add "packed-refs" header consistency check
2025-02-06 5:59 ` [PATCH v3 4/8] packed-backend: add "packed-refs" header consistency check shejialuo
@ 2025-02-12 9:56 ` Patrick Steinhardt
2025-02-12 10:12 ` shejialuo
2025-02-12 17:48 ` Junio C Hamano
0 siblings, 2 replies; 168+ messages in thread
From: Patrick Steinhardt @ 2025-02-12 9:56 UTC (permalink / raw)
To: shejialuo; +Cc: git, Karthik Nayak, Junio C Hamano, Michael Haggerty
On Thu, Feb 06, 2025 at 01:59:04PM +0800, shejialuo wrote:
> diff --git a/refs/packed-backend.c b/refs/packed-backend.c
> index 6401cecd5f..683cfe78dc 100644
> --- a/refs/packed-backend.c
> +++ b/refs/packed-backend.c
> @@ -1749,12 +1749,76 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
> +static int packed_fsck_ref_header(struct fsck_options *o,
> + const char *start, const char *eol)
> +{
> + if (!starts_with(start, "# pack-refs with:")) {
> + struct fsck_ref_report report = { 0 };
> + report.path = "packed-refs.header";
> +
> + return fsck_report_ref(o, &report,
> + FSCK_MSG_BAD_PACKED_REF_HEADER,
> + "'%.*s' does not start with '# pack-refs with:'",
> + (int)(eol - start), start);
> + }
> +
> + return 0;
> +}
Okay. We still complain about bad headers, but only if there is a line
starting with "#" and only if the prefix doesn't match. This addresses
Junio's comment that packfiles don't have to have a header, and that
they may contain capabilities that we don't understand.
> diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
> index 42c8d4ca1e..da321f16c6 100755
> --- a/t/t0602-reffiles-fsck.sh
> +++ b/t/t0602-reffiles-fsck.sh
> @@ -639,4 +639,29 @@ test_expect_success SYMLINKS 'the filetype of packed-refs should be checked' '
> )
> '
>
> +test_expect_success 'packed-refs header should be checked' '
> + test_when_finished "rm -rf repo" &&
> + git init repo &&
> + (
> + cd repo &&
> + test_commit default &&
> +
> + git refs verify 2>err &&
> + test_must_be_empty err &&
> +
> + for bad_header in "# pack-refs wit: peeled fully-peeled sorted " \
> + "# pack-refs with traits: peeled fully-peeled sorted " \
> + "# pack-refs with a: peeled fully-peeled"
Instead of verifying thrice that we complain about bad header prefixes,
should we maybe replace two of these with instances where we check a
packed-refs file _without_ a header and one with capabilities that we
don't understand?
Patrick
^ permalink raw reply [flat|nested] 168+ messages in thread
* Re: [PATCH v3 4/8] packed-backend: add "packed-refs" header consistency check
2025-02-12 9:56 ` Patrick Steinhardt
@ 2025-02-12 10:12 ` shejialuo
2025-02-12 17:48 ` Junio C Hamano
1 sibling, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-12 10:12 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git, Karthik Nayak, Junio C Hamano, Michael Haggerty
On Wed, Feb 12, 2025 at 10:56:43AM +0100, Patrick Steinhardt wrote:
[snip]
> > diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
> > index 42c8d4ca1e..da321f16c6 100755
> > --- a/t/t0602-reffiles-fsck.sh
> > +++ b/t/t0602-reffiles-fsck.sh
> > @@ -639,4 +639,29 @@ test_expect_success SYMLINKS 'the filetype of packed-refs should be checked' '
> > )
> > '
> >
> > +test_expect_success 'packed-refs header should be checked' '
> > + test_when_finished "rm -rf repo" &&
> > + git init repo &&
> > + (
> > + cd repo &&
> > + test_commit default &&
> > +
> > + git refs verify 2>err &&
> > + test_must_be_empty err &&
> > +
> > + for bad_header in "# pack-refs wit: peeled fully-peeled sorted " \
> > + "# pack-refs with traits: peeled fully-peeled sorted " \
> > + "# pack-refs with a: peeled fully-peeled"
>
> Instead of verifying thrice that we complain about bad header prefixes,
> should we maybe replace two of these with instances where we check a
> packed-refs file _without_ a header and one with capabilities that we
> don't understand?
>
I think we could add some tests to verify that we won't complain about
above two cases where packed-refs file without a header and one with
capabilities that we don't understand.
> Patrick
^ permalink raw reply [flat|nested] 168+ messages in thread
* Re: [PATCH v3 4/8] packed-backend: add "packed-refs" header consistency check
2025-02-12 9:56 ` Patrick Steinhardt
2025-02-12 10:12 ` shejialuo
@ 2025-02-12 17:48 ` Junio C Hamano
2025-02-14 3:53 ` shejialuo
1 sibling, 1 reply; 168+ messages in thread
From: Junio C Hamano @ 2025-02-12 17:48 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: shejialuo, git, Karthik Nayak, Michael Haggerty
Patrick Steinhardt <ps@pks.im> writes:
> On Thu, Feb 06, 2025 at 01:59:04PM +0800, shejialuo wrote:
>> diff --git a/refs/packed-backend.c b/refs/packed-backend.c
>> index 6401cecd5f..683cfe78dc 100644
>> --- a/refs/packed-backend.c
>> +++ b/refs/packed-backend.c
>> @@ -1749,12 +1749,76 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
>> +static int packed_fsck_ref_header(struct fsck_options *o,
>> + const char *start, const char *eol)
>> +{
>> + if (!starts_with(start, "# pack-refs with:")) {
>> + struct fsck_ref_report report = { 0 };
>> + report.path = "packed-refs.header";
>> +
>> + return fsck_report_ref(o, &report,
>> + FSCK_MSG_BAD_PACKED_REF_HEADER,
>> + "'%.*s' does not start with '# pack-refs with:'",
>> + (int)(eol - start), start);
>> + }
>> +
>> + return 0;
>> +}
>
> Okay. We still complain about bad headers, but only if there is a line
> starting with "#" and only if the prefix doesn't match. This addresses
> Junio's comment that packfiles don't have to have a header, and that
> they may contain capabilities that we don't understand.
We'd want to also ensure that there is a single trailing whitespace
after that colon, which we have always written after "with:", no?
>> diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
>> index 42c8d4ca1e..da321f16c6 100755
>> --- a/t/t0602-reffiles-fsck.sh
>> +++ b/t/t0602-reffiles-fsck.sh
>> @@ -639,4 +639,29 @@ test_expect_success SYMLINKS 'the filetype of packed-refs should be checked' '
>> )
>> '
>>
>> +test_expect_success 'packed-refs header should be checked' '
>> + test_when_finished "rm -rf repo" &&
>> + git init repo &&
>> + (
>> + cd repo &&
>> + test_commit default &&
>> +
>> + git refs verify 2>err &&
>> + test_must_be_empty err &&
>> +
>> + for bad_header in "# pack-refs wit: peeled fully-peeled sorted " \
>> + "# pack-refs with traits: peeled fully-peeled sorted " \
>> + "# pack-refs with a: peeled fully-peeled"
>
> Instead of verifying thrice that we complain about bad header prefixes,
> should we maybe replace two of these with instances where we check a
> packed-refs file _without_ a header and one with capabilities that we
> don't understand?
Yup. I also notice that refs/packed-backend.c:create_snapshot()
would accept "# pack-refs with:peeled" if I am not reading it
correctly, which is an unrelated bug.
Thanks.
^ permalink raw reply [flat|nested] 168+ messages in thread
* Re: [PATCH v3 4/8] packed-backend: add "packed-refs" header consistency check
2025-02-12 17:48 ` Junio C Hamano
@ 2025-02-14 3:53 ` shejialuo
0 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-14 3:53 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Patrick Steinhardt, git, Karthik Nayak, Michael Haggerty
On Wed, Feb 12, 2025 at 09:48:09AM -0800, Junio C Hamano wrote:
> Patrick Steinhardt <ps@pks.im> writes:
>
> > On Thu, Feb 06, 2025 at 01:59:04PM +0800, shejialuo wrote:
> >> diff --git a/refs/packed-backend.c b/refs/packed-backend.c
> >> index 6401cecd5f..683cfe78dc 100644
> >> --- a/refs/packed-backend.c
> >> +++ b/refs/packed-backend.c
> >> @@ -1749,12 +1749,76 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
> >> +static int packed_fsck_ref_header(struct fsck_options *o,
> >> + const char *start, const char *eol)
> >> +{
> >> + if (!starts_with(start, "# pack-refs with:")) {
> >> + struct fsck_ref_report report = { 0 };
> >> + report.path = "packed-refs.header";
> >> +
> >> + return fsck_report_ref(o, &report,
> >> + FSCK_MSG_BAD_PACKED_REF_HEADER,
> >> + "'%.*s' does not start with '# pack-refs with:'",
> >> + (int)(eol - start), start);
> >> + }
> >> +
> >> + return 0;
> >> +}
> >
> > Okay. We still complain about bad headers, but only if there is a line
> > starting with "#" and only if the prefix doesn't match. This addresses
> > Junio's comment that packfiles don't have to have a header, and that
> > they may contain capabilities that we don't understand.
>
> We'd want to also ensure that there is a single trailing whitespace
> after that colon, which we have always written after "with:", no?
>
As you have commented below, I don't add this check due to the reason
that "create_snapshot" method does _not_ check this.
> >> diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
> >> index 42c8d4ca1e..da321f16c6 100755
> >> --- a/t/t0602-reffiles-fsck.sh
> >> +++ b/t/t0602-reffiles-fsck.sh
> >> @@ -639,4 +639,29 @@ test_expect_success SYMLINKS 'the filetype of packed-refs should be checked' '
> >> )
> >> '
> >>
> >> +test_expect_success 'packed-refs header should be checked' '
> >> + test_when_finished "rm -rf repo" &&
> >> + git init repo &&
> >> + (
> >> + cd repo &&
> >> + test_commit default &&
> >> +
> >> + git refs verify 2>err &&
> >> + test_must_be_empty err &&
> >> +
> >> + for bad_header in "# pack-refs wit: peeled fully-peeled sorted " \
> >> + "# pack-refs with traits: peeled fully-peeled sorted " \
> >> + "# pack-refs with a: peeled fully-peeled"
> >
> > Instead of verifying thrice that we complain about bad header prefixes,
> > should we maybe replace two of these with instances where we check a
> > packed-refs file _without_ a header and one with capabilities that we
> > don't understand?
>
> Yup. I also notice that refs/packed-backend.c:create_snapshot()
> would accept "# pack-refs with:peeled" if I am not reading it
> correctly, which is an unrelated bug.
>
Yes, you are correct. Let me fix this in the next version.
Thanks,
Jialuo
^ permalink raw reply [flat|nested] 168+ messages in thread
* [PATCH v3 5/8] packed-backend: check whether the refname contains NUL characters
2025-02-06 5:56 ` [PATCH v3 0/8] add more ref consistency checks shejialuo
` (3 preceding siblings ...)
2025-02-06 5:59 ` [PATCH v3 4/8] packed-backend: add "packed-refs" header consistency check shejialuo
@ 2025-02-06 5:59 ` shejialuo
2025-02-06 5:59 ` [PATCH v3 6/8] packed-backend: add "packed-refs" entry consistency check shejialuo
` (3 subsequent siblings)
8 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-06 5:59 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
"packed-backend.c::next_record" will use "check_refname_format" to check
the consistency of the refname. If it is not OK, the program will die.
However, it is reported in [1], we cannot catch some corruption. But we
already have the code path and we must miss out something.
We use the following code to get the refname:
strbuf_add(&iter->refname_buf, p, eol - p);
iter->base.refname = iter->refname_buf.buf
In the above code, `p` is the start pointer of the refname and `eol` is
the next newline pointer. We calculate the length of the refname by
subtracting the two pointers. Then we add the memory range between `p`
and `eol` to get the refname.
However, if there are some NUL characters in the memory range between `p`
and `eol`, we will see the refname as a valid ref name as long as the
memory range between `p` and first occurred NUL character is valid.
In order to catch above corruption, create a new function
"refname_contains_nul" by searching the first NUL character. If it is
not at the end of the string, there must be some NUL characters in the
refname.
Use this function in "next_record" function to die the program if
"refname_contains_nul" returns true.
[1] https://lore.kernel.org/git/6cfee0e4-3285-4f18-91ff-d097da9de737@rd10.de/
Reported-by: R. Diez <rdiez-temp3@rd10.de>
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
refs/packed-backend.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 683cfe78dc..c8bb93bb18 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -494,6 +494,21 @@ static void verify_buffer_safe(struct snapshot *snapshot)
last_line, eof - last_line);
}
+/*
+ * When parsing the "packed-refs" file, we will parse it line by line.
+ * Because we know the start pointer of the refname and the next
+ * newline pointer, we could calculate the length of the refname by
+ * subtracting the two pointers. However, there is a corner case where
+ * the refname contains corrupted embedded NUL characters. And
+ * `check_refname_format()` will not catch this when the truncated
+ * refname is still a valid refname. To prevent this, we need to check
+ * whether the refname contains the NUL characters.
+ */
+static int refname_contains_nul(struct strbuf *refname)
+{
+ return !!memchr(refname->buf, '\0', refname->len);
+}
+
#define SMALL_FILE_SIZE (32*1024)
/*
@@ -895,6 +910,9 @@ static int next_record(struct packed_ref_iterator *iter)
strbuf_add(&iter->refname_buf, p, eol - p);
iter->base.refname = iter->refname_buf.buf;
+ if (refname_contains_nul(&iter->refname_buf))
+ die("packed refname contains embedded NULL: %s", iter->base.refname);
+
if (check_refname_format(iter->base.refname, REFNAME_ALLOW_ONELEVEL)) {
if (!refname_is_safe(iter->base.refname))
die("packed refname is dangerous: %s",
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v3 6/8] packed-backend: add "packed-refs" entry consistency check
2025-02-06 5:56 ` [PATCH v3 0/8] add more ref consistency checks shejialuo
` (4 preceding siblings ...)
2025-02-06 5:59 ` [PATCH v3 5/8] packed-backend: check whether the refname contains NUL characters shejialuo
@ 2025-02-06 5:59 ` shejialuo
2025-02-12 9:56 ` Patrick Steinhardt
2025-02-06 5:59 ` [PATCH v3 7/8] packed-backend: check whether the "packed-refs" is sorted shejialuo
` (2 subsequent siblings)
8 siblings, 1 reply; 168+ messages in thread
From: shejialuo @ 2025-02-06 5:59 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
"packed-backend.c::next_record" will parse the ref entry to check the
consistency. This function has already checked the following things:
1. Parse the main line of the ref entry to inspect whether the oid is
not correct. Then, check whether the next character is oid. Then
check the refname.
2. If the next line starts with '^', it would continue to parse the
peeled oid and check whether the last character is '\n'.
As we decide to implement the ref consistency check for "packed-refs",
let's port these two checks and update the test to exercise the code.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/fsck-msgids.txt | 3 ++
fsck.h | 1 +
refs/packed-backend.c | 95 ++++++++++++++++++++++++++++++++++-
t/t0602-reffiles-fsck.sh | 42 ++++++++++++++++
4 files changed, 140 insertions(+), 1 deletion(-)
diff --git a/Documentation/fsck-msgids.txt b/Documentation/fsck-msgids.txt
index 11906f90fd..02a7bf0503 100644
--- a/Documentation/fsck-msgids.txt
+++ b/Documentation/fsck-msgids.txt
@@ -16,6 +16,9 @@
`badObjectSha1`::
(ERROR) An object has a bad sha1.
+`badPackedRefEntry`::
+ (ERROR) The "packed-refs" file contains an invalid entry.
+
`badPackedRefHeader`::
(ERROR) The "packed-refs" file contains an invalid
header.
diff --git a/fsck.h b/fsck.h
index 67e3c97bc0..14d70f6653 100644
--- a/fsck.h
+++ b/fsck.h
@@ -30,6 +30,7 @@ enum fsck_msg_type {
FUNC(BAD_EMAIL, ERROR) \
FUNC(BAD_NAME, ERROR) \
FUNC(BAD_OBJECT_SHA1, ERROR) \
+ FUNC(BAD_PACKED_REF_ENTRY, ERROR) \
FUNC(BAD_PACKED_REF_HEADER, ERROR) \
FUNC(BAD_PARENT_SHA1, ERROR) \
FUNC(BAD_REF_CONTENT, ERROR) \
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index c8bb93bb18..658f6bc7da 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1809,10 +1809,83 @@ static int packed_fsck_ref_header(struct fsck_options *o,
return 0;
}
+static int packed_fsck_ref_peeled_line(struct fsck_options *o,
+ struct ref_store *ref_store,
+ struct strbuf *packed_entry,
+ const char *start, const char *eol)
+{
+ struct fsck_ref_report report = { 0 };
+ struct object_id peeled;
+ const char *p;
+
+ report.path = packed_entry->buf;
+
+ /*
+ * Skip the '^' and parse the peeled oid.
+ */
+ start++;
+ if (parse_oid_hex_algop(start, &peeled, &p, ref_store->repo->hash_algo))
+ return fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "'%.*s' has invalid peeled oid",
+ (int)(eol - start), start);
+
+ if (p != eol)
+ return fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "has trailing garbage after peeled oid '%.*s'",
+ (int)(eol - p), p);
+
+ return 0;
+}
+
+static int packed_fsck_ref_main_line(struct fsck_options *o,
+ struct ref_store *ref_store,
+ struct strbuf *packed_entry,
+ struct strbuf *refname,
+ const char *start, const char *eol)
+{
+ struct fsck_ref_report report = { 0 };
+ struct object_id oid;
+ const char *p;
+
+ report.path = packed_entry->buf;
+
+ if (parse_oid_hex_algop(start, &oid, &p, ref_store->repo->hash_algo))
+ return fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "'%.*s' has invalid oid",
+ (int)(eol - start), start);
+
+ if (p == eol || !isspace(*p))
+ return fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "has no space after oid '%s' but with '%.*s'",
+ oid_to_hex(&oid), (int)(eol - p), p);
+
+ p++;
+ strbuf_reset(refname);
+ strbuf_add(refname, p, eol - p);
+ if (refname_contains_nul(refname))
+ return fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "refname '%s' contains NULL binaries",
+ refname->buf);
+
+ if (check_refname_format(refname->buf, 0))
+ return fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_NAME,
+ "has bad refname '%s'", refname->buf);
+
+ return 0;
+}
+
static int packed_fsck_ref_content(struct fsck_options *o,
+ struct ref_store *ref_store,
const char *start, const char *eof)
{
struct strbuf packed_entry = STRBUF_INIT;
+ struct strbuf refname = STRBUF_INIT;
unsigned long line_number = 1;
const char *eol;
int ret = 0;
@@ -1826,6 +1899,26 @@ static int packed_fsck_ref_content(struct fsck_options *o,
line_number++;
}
+ while (start < eof) {
+ strbuf_reset(&packed_entry);
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ ret |= packed_fsck_ref_next_line(o, &packed_entry, start, eof, &eol);
+ ret |= packed_fsck_ref_main_line(o, ref_store, &packed_entry, &refname, start, eol);
+ start = eol + 1;
+ line_number++;
+ if (start < eof && *start == '^') {
+ strbuf_reset(&packed_entry);
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ ret |= packed_fsck_ref_next_line(o, &packed_entry, start, eof, &eol);
+ ret |= packed_fsck_ref_peeled_line(o, ref_store, &packed_entry,
+ start, eol);
+ start = eol + 1;
+ line_number++;
+ }
+ }
+
+ strbuf_release(&packed_entry);
+ strbuf_release(&refname);
strbuf_release(&packed_entry);
return ret;
}
@@ -1873,7 +1966,7 @@ static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
- ret = packed_fsck_ref_content(o, packed_ref_content.buf,
+ ret = packed_fsck_ref_content(o, ref_store, packed_ref_content.buf,
packed_ref_content.buf + packed_ref_content.len);
cleanup:
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index da321f16c6..3ab6b5bba5 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -664,4 +664,46 @@ test_expect_success 'packed-refs header should be checked' '
)
'
+test_expect_success 'packed-refs content should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git tag -a annotated-tag-1 -m tag-1 &&
+ git tag -a annotated-tag-2 -m tag-2 &&
+
+ branch_1_oid=$(git rev-parse branch-1) &&
+ branch_2_oid=$(git rev-parse branch-2) &&
+ tag_1_oid=$(git rev-parse annotated-tag-1) &&
+ tag_2_oid=$(git rev-parse annotated-tag-2) &&
+ tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
+ tag_2_peeled_oid=$(git rev-parse annotated-tag-2^{}) &&
+ short_oid=$(printf "%s" $tag_1_peeled_oid | cut -c 1-4) &&
+
+ printf "# pack-refs with: peeled fully-peeled sorted \n" >.git/packed-refs &&
+ printf "%s\n" "$short_oid refs/heads/branch-1" >>.git/packed-refs &&
+ printf "%sx\n" "$branch_1_oid" >>.git/packed-refs &&
+ printf "%s refs/heads/bad-branch\n" "$branch_2_oid" >>.git/packed-refs &&
+ printf "%s refs/heads/branch.\n" "$branch_2_oid" >>.git/packed-refs &&
+ printf "%s refs/tags/annotated-tag-3\n" "$tag_1_oid" >>.git/packed-refs &&
+ printf "^%s\n" "$short_oid" >>.git/packed-refs &&
+ printf "%s refs/tags/annotated-tag-4.\n" "$tag_2_oid" >>.git/packed-refs &&
+ printf "^%s garbage\n" "$tag_2_peeled_oid" >>.git/packed-refs &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs line 2: badPackedRefEntry: '\''$short_oid refs/heads/branch-1'\'' has invalid oid
+ error: packed-refs line 3: badPackedRefEntry: has no space after oid '\''$branch_1_oid'\'' but with '\''x'\''
+ error: packed-refs line 4: badRefName: has bad refname '\'' refs/heads/bad-branch'\''
+ error: packed-refs line 5: badRefName: has bad refname '\''refs/heads/branch.'\''
+ error: packed-refs line 7: badPackedRefEntry: '\''$short_oid'\'' has invalid peeled oid
+ error: packed-refs line 8: badRefName: has bad refname '\''refs/tags/annotated-tag-4.'\''
+ error: packed-refs line 9: badPackedRefEntry: has trailing garbage after peeled oid '\'' garbage'\''
+ EOF
+ test_cmp expect err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* Re: [PATCH v3 6/8] packed-backend: add "packed-refs" entry consistency check
2025-02-06 5:59 ` [PATCH v3 6/8] packed-backend: add "packed-refs" entry consistency check shejialuo
@ 2025-02-12 9:56 ` Patrick Steinhardt
2025-02-12 10:18 ` shejialuo
0 siblings, 1 reply; 168+ messages in thread
From: Patrick Steinhardt @ 2025-02-12 9:56 UTC (permalink / raw)
To: shejialuo; +Cc: git, Karthik Nayak, Junio C Hamano, Michael Haggerty
On Thu, Feb 06, 2025 at 01:59:40PM +0800, shejialuo wrote:
> diff --git a/refs/packed-backend.c b/refs/packed-backend.c
> index c8bb93bb18..658f6bc7da 100644
> --- a/refs/packed-backend.c
> +++ b/refs/packed-backend.c
> @@ -1826,6 +1899,26 @@ static int packed_fsck_ref_content(struct fsck_options *o,
> line_number++;
> }
>
> + while (start < eof) {
> + strbuf_reset(&packed_entry);
> + strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
Instead of greedily computing the name of the line, can we pass in the
line number? The motivation is that in a well-formatted packed-refs file
we won't ever need this string at all, so it's wasteful to proactively
compute it for every single line.
> diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
> index da321f16c6..3ab6b5bba5 100755
> --- a/t/t0602-reffiles-fsck.sh
> +++ b/t/t0602-reffiles-fsck.sh
> @@ -664,4 +664,46 @@ test_expect_success 'packed-refs header should be checked' '
> )
> '
>
> +test_expect_success 'packed-refs content should be checked' '
> + test_when_finished "rm -rf repo" &&
> + git init repo &&
> + (
> + cd repo &&
> + test_commit default &&
> + git branch branch-1 &&
> + git branch branch-2 &&
> + git tag -a annotated-tag-1 -m tag-1 &&
> + git tag -a annotated-tag-2 -m tag-2 &&
> +
> + branch_1_oid=$(git rev-parse branch-1) &&
> + branch_2_oid=$(git rev-parse branch-2) &&
> + tag_1_oid=$(git rev-parse annotated-tag-1) &&
> + tag_2_oid=$(git rev-parse annotated-tag-2) &&
> + tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
> + tag_2_peeled_oid=$(git rev-parse annotated-tag-2^{}) &&
> + short_oid=$(printf "%s" $tag_1_peeled_oid | cut -c 1-4) &&
> +
> + printf "# pack-refs with: peeled fully-peeled sorted \n" >.git/packed-refs &&
> + printf "%s\n" "$short_oid refs/heads/branch-1" >>.git/packed-refs &&
> + printf "%sx\n" "$branch_1_oid" >>.git/packed-refs &&
> + printf "%s refs/heads/bad-branch\n" "$branch_2_oid" >>.git/packed-refs &&
> + printf "%s refs/heads/branch.\n" "$branch_2_oid" >>.git/packed-refs &&
> + printf "%s refs/tags/annotated-tag-3\n" "$tag_1_oid" >>.git/packed-refs &&
> + printf "^%s\n" "$short_oid" >>.git/packed-refs &&
> + printf "%s refs/tags/annotated-tag-4.\n" "$tag_2_oid" >>.git/packed-refs &&
> + printf "^%s garbage\n" "$tag_2_peeled_oid" >>.git/packed-refs &&
This can be simplified using HERE docs.
cat >.git/packed-refs <<-EOF
# pack-refs with: peeled fully-peeled sorted
$short_oid refs/heads/branch-1
${branch_1_oid}x
$branch_2_oid refs/heads/bad-branch
$branch_2_oid refs/heads/branch.
$tag_1_oid refs/tags/annotated-tag-3
^$short_oid\n
$tag_2_oid refs/tags/annotated-tag-4.
^$tag_2_peeled_oid garbage
EOF
Patrick
^ permalink raw reply [flat|nested] 168+ messages in thread
* Re: [PATCH v3 6/8] packed-backend: add "packed-refs" entry consistency check
2025-02-12 9:56 ` Patrick Steinhardt
@ 2025-02-12 10:18 ` shejialuo
0 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-12 10:18 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git, Karthik Nayak, Junio C Hamano, Michael Haggerty
On Wed, Feb 12, 2025 at 10:56:50AM +0100, Patrick Steinhardt wrote:
> On Thu, Feb 06, 2025 at 01:59:40PM +0800, shejialuo wrote:
> > diff --git a/refs/packed-backend.c b/refs/packed-backend.c
> > index c8bb93bb18..658f6bc7da 100644
> > --- a/refs/packed-backend.c
> > +++ b/refs/packed-backend.c
> > @@ -1826,6 +1899,26 @@ static int packed_fsck_ref_content(struct fsck_options *o,
> > line_number++;
> > }
> >
> > + while (start < eof) {
> > + strbuf_reset(&packed_entry);
> > + strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
>
> Instead of greedily computing the name of the line, can we pass in the
> line number? The motivation is that in a well-formatted packed-refs file
> we won't ever need this string at all, so it's wasteful to proactively
> compute it for every single line.
>
I agree with you here. And I already have idea to do this. Let me
improve this in the next version.
> > diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
> > index da321f16c6..3ab6b5bba5 100755
> > --- a/t/t0602-reffiles-fsck.sh
> > +++ b/t/t0602-reffiles-fsck.sh
> > @@ -664,4 +664,46 @@ test_expect_success 'packed-refs header should be checked' '
> > )
> > '
> >
> > +test_expect_success 'packed-refs content should be checked' '
> > + test_when_finished "rm -rf repo" &&
> > + git init repo &&
> > + (
> > + cd repo &&
> > + test_commit default &&
> > + git branch branch-1 &&
> > + git branch branch-2 &&
> > + git tag -a annotated-tag-1 -m tag-1 &&
> > + git tag -a annotated-tag-2 -m tag-2 &&
> > +
> > + branch_1_oid=$(git rev-parse branch-1) &&
> > + branch_2_oid=$(git rev-parse branch-2) &&
> > + tag_1_oid=$(git rev-parse annotated-tag-1) &&
> > + tag_2_oid=$(git rev-parse annotated-tag-2) &&
> > + tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
> > + tag_2_peeled_oid=$(git rev-parse annotated-tag-2^{}) &&
> > + short_oid=$(printf "%s" $tag_1_peeled_oid | cut -c 1-4) &&
> > +
> > + printf "# pack-refs with: peeled fully-peeled sorted \n" >.git/packed-refs &&
> > + printf "%s\n" "$short_oid refs/heads/branch-1" >>.git/packed-refs &&
> > + printf "%sx\n" "$branch_1_oid" >>.git/packed-refs &&
> > + printf "%s refs/heads/bad-branch\n" "$branch_2_oid" >>.git/packed-refs &&
> > + printf "%s refs/heads/branch.\n" "$branch_2_oid" >>.git/packed-refs &&
> > + printf "%s refs/tags/annotated-tag-3\n" "$tag_1_oid" >>.git/packed-refs &&
> > + printf "^%s\n" "$short_oid" >>.git/packed-refs &&
> > + printf "%s refs/tags/annotated-tag-4.\n" "$tag_2_oid" >>.git/packed-refs &&
> > + printf "^%s garbage\n" "$tag_2_peeled_oid" >>.git/packed-refs &&
>
> This can be simplified using HERE docs.
>
> cat >.git/packed-refs <<-EOF
> # pack-refs with: peeled fully-peeled sorted
> $short_oid refs/heads/branch-1
> ${branch_1_oid}x
> $branch_2_oid refs/heads/bad-branch
> $branch_2_oid refs/heads/branch.
> $tag_1_oid refs/tags/annotated-tag-3
> ^$short_oid\n
> $tag_2_oid refs/tags/annotated-tag-4.
> ^$tag_2_peeled_oid garbage
> EOF
>
Thanks for the suggestion, I will improve this in the next version.
> Patrick
^ permalink raw reply [flat|nested] 168+ messages in thread
* [PATCH v3 7/8] packed-backend: check whether the "packed-refs" is sorted
2025-02-06 5:56 ` [PATCH v3 0/8] add more ref consistency checks shejialuo
` (5 preceding siblings ...)
2025-02-06 5:59 ` [PATCH v3 6/8] packed-backend: add "packed-refs" entry consistency check shejialuo
@ 2025-02-06 5:59 ` shejialuo
2025-02-12 9:56 ` Patrick Steinhardt
2025-02-06 6:00 ` [PATCH v3 8/8] builtin/fsck: add `git refs verify` child process shejialuo
2025-02-14 4:50 ` [PATCH v4 0/8] add more ref consistency checks shejialuo
8 siblings, 1 reply; 168+ messages in thread
From: shejialuo @ 2025-02-06 5:59 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
When there is a "sorted" trait in the header of the "packed-refs" file,
it means that each entry is sorted increasingly by comparing the
refname. We should add checks to verify whether the "packed-refs" is
sorted in this case.
Update the "packed_fsck_ref_header" to know whether there is a "sorted"
trail in the header. Then, create a new structure "fsck_packed_ref_entry"
to store the state during the parsing process for every entry. It may
seem that we could just add a new "struct strbuf refname" into the
"struct fsck_packed_ref_entry" and during the parsing process, we could
store the refname into this structure and thus we could compare later.
However, this is not a good design due to the following reasons:
1. Because we need to store the state across the whole checking
lifetime, we would consume a lot of memory if there are many entries
in the "packed-refs" file.
2. The most important thing is that we cannot reuse the existing compare
functions which cause repetition.
So, instead of storing the "struct strbuf", let's use the existing
structure "struct snaphost_record". And thus we could use the existing
function "cmp_packed_ref_records".
However, this function need an extra parameter for "struct snaphost".
Extract the common part into a new function "cmp_packed_ref_records" to
reuse this function to compare.
Then, create a new function "packed_fsck_ref_sorted" to use the new fsck
message "packedRefUnsorted(ERROR)" to report to the user.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/fsck-msgids.txt | 3 +
fsck.h | 1 +
refs/packed-backend.c | 131 ++++++++++++++++++++++++++++++----
t/t0602-reffiles-fsck.sh | 63 ++++++++++++++++
4 files changed, 183 insertions(+), 15 deletions(-)
diff --git a/Documentation/fsck-msgids.txt b/Documentation/fsck-msgids.txt
index 02a7bf0503..9601fff228 100644
--- a/Documentation/fsck-msgids.txt
+++ b/Documentation/fsck-msgids.txt
@@ -187,6 +187,9 @@
(ERROR) The "packed-refs" file contains an entry that is
not terminated by a newline.
+`packedRefUnsorted`::
+ (ERROR) The "packed-refs" file is not sorted.
+
`refMissingNewline`::
(INFO) A loose ref that does not end with newline(LF). As
valid implementations of Git never created such a loose ref
diff --git a/fsck.h b/fsck.h
index 14d70f6653..19f3cb2773 100644
--- a/fsck.h
+++ b/fsck.h
@@ -56,6 +56,7 @@ enum fsck_msg_type {
FUNC(MISSING_TYPE_ENTRY, ERROR) \
FUNC(MULTIPLE_AUTHORS, ERROR) \
FUNC(PACKED_REF_ENTRY_NOT_TERMINATED, ERROR) \
+ FUNC(PACKED_REF_UNSORTED, ERROR) \
FUNC(TREE_NOT_SORTED, ERROR) \
FUNC(UNKNOWN_TYPE, ERROR) \
FUNC(ZERO_PADDED_DATE, ERROR) \
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 658f6bc7da..0fbdc5c3fa 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -300,14 +300,9 @@ struct snapshot_record {
size_t len;
};
-static int cmp_packed_ref_records(const void *v1, const void *v2,
- void *cb_data)
-{
- const struct snapshot *snapshot = cb_data;
- const struct snapshot_record *e1 = v1, *e2 = v2;
- const char *r1 = e1->start + snapshot_hexsz(snapshot) + 1;
- const char *r2 = e2->start + snapshot_hexsz(snapshot) + 1;
+static int cmp_packed_refname(const char *r1, const char *r2)
+{
while (1) {
if (*r1 == '\n')
return *r2 == '\n' ? 0 : -1;
@@ -322,6 +317,17 @@ static int cmp_packed_ref_records(const void *v1, const void *v2,
}
}
+static int cmp_packed_ref_records(const void *v1, const void *v2,
+ void *cb_data)
+{
+ const struct snapshot *snapshot = cb_data;
+ const struct snapshot_record *e1 = v1, *e2 = v2;
+ const char *r1 = e1->start + snapshot_hexsz(snapshot) + 1;
+ const char *r2 = e2->start + snapshot_hexsz(snapshot) + 1;
+
+ return cmp_packed_refname(r1, r2);
+}
+
/*
* Compare a snapshot record at `rec` to the specified NUL-terminated
* refname.
@@ -1767,6 +1773,28 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
return empty_ref_iterator_begin();
}
+struct fsck_packed_ref_entry {
+ unsigned long line_number;
+
+ struct snapshot_record record;
+};
+
+static struct fsck_packed_ref_entry *create_fsck_packed_ref_entry(unsigned long line_number,
+ const char *start)
+{
+ struct fsck_packed_ref_entry *entry = xcalloc(1, sizeof(*entry));
+ entry->line_number = line_number;
+ entry->record.start = start;
+ return entry;
+}
+
+static void free_fsck_packed_ref_entries(struct fsck_packed_ref_entry **entries, size_t nr)
+{
+ for (size_t i = 0; i < nr; i++)
+ free(entries[i]);
+ free(entries);
+}
+
static int packed_fsck_ref_next_line(struct fsck_options *o,
struct strbuf *packed_entry, const char *start,
const char *eof, const char **eol)
@@ -1794,19 +1822,33 @@ static int packed_fsck_ref_next_line(struct fsck_options *o,
}
static int packed_fsck_ref_header(struct fsck_options *o,
- const char *start, const char *eol)
+ const char *start, const char *eol,
+ unsigned int *sorted)
{
- if (!starts_with(start, "# pack-refs with:")) {
+ struct string_list traits = STRING_LIST_INIT_NODUP;
+ char *tmp_line;
+ int ret = 0;
+ char *p;
+
+ tmp_line = xmemdupz(start, eol - start);
+ if (!skip_prefix(tmp_line, "# pack-refs with:", (const char **)&p)) {
struct fsck_ref_report report = { 0 };
report.path = "packed-refs.header";
- return fsck_report_ref(o, &report,
- FSCK_MSG_BAD_PACKED_REF_HEADER,
- "'%.*s' does not start with '# pack-refs with:'",
- (int)(eol - start), start);
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_HEADER,
+ "'%.*s' does not start with '# pack-refs with:'",
+ (int)(eol - start), start);
+ goto cleanup;
}
- return 0;
+ string_list_split_in_place(&traits, p, " ", -1);
+ *sorted = unsorted_string_list_has_string(&traits, "sorted");
+
+cleanup:
+ free(tmp_line);
+ string_list_clear(&traits, 0);
+ return ret;
}
static int packed_fsck_ref_peeled_line(struct fsck_options *o,
@@ -1880,26 +1922,80 @@ static int packed_fsck_ref_main_line(struct fsck_options *o,
return 0;
}
+static int packed_fsck_ref_sorted(struct fsck_options *o,
+ struct ref_store *ref_store,
+ struct fsck_packed_ref_entry **entries,
+ size_t nr)
+{
+ size_t hexsz = ref_store->repo->hash_algo->hexsz;
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct strbuf refname1 = STRBUF_INIT;
+ struct strbuf refname2 = STRBUF_INIT;
+ int ret = 0;
+
+ for (size_t i = 1; i < nr; i++) {
+ const char *r1 = entries[i - 1]->record.start + hexsz + 1;
+ const char *r2 = entries[i]->record.start + hexsz + 1;
+
+ if (cmp_packed_refname(r1, r2) >= 0) {
+ const char *err_fmt =
+ "refname '%s' is not less than next refname '%s'";
+ const char *eol;
+ eol = memchr(entries[i - 1]->record.start, '\n',
+ entries[i - 1]->record.len);
+ strbuf_add(&refname1, r1, eol - r1);
+ eol = memchr(entries[i]->record.start, '\n',
+ entries[i]->record.len);
+ strbuf_add(&refname2, r2, eol - r2);
+
+ strbuf_addf(&packed_entry, "packed-refs line %lu",
+ entries[i - 1]->line_number);
+ report.path = packed_entry.buf;
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_PACKED_REF_UNSORTED,
+ err_fmt, refname1.buf, refname2.buf);
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ strbuf_release(&packed_entry);
+ strbuf_release(&refname1);
+ strbuf_release(&refname2);
+ return ret;
+}
+
static int packed_fsck_ref_content(struct fsck_options *o,
struct ref_store *ref_store,
const char *start, const char *eof)
{
struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_packed_ref_entry **entries;
struct strbuf refname = STRBUF_INIT;
unsigned long line_number = 1;
+ unsigned int sorted = 0;
+ size_t entry_alloc = 20;
+ size_t entry_nr = 0;
const char *eol;
int ret = 0;
strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
ret |= packed_fsck_ref_next_line(o, &packed_entry, start, eof, &eol);
if (*start == '#') {
- ret |= packed_fsck_ref_header(o, start, eol);
+ ret |= packed_fsck_ref_header(o, start, eol, &sorted);
start = eol + 1;
line_number++;
}
+ ALLOC_ARRAY(entries, entry_alloc);
while (start < eof) {
+ struct fsck_packed_ref_entry *entry
+ = create_fsck_packed_ref_entry(line_number, start);
+
+ ALLOC_GROW(entries, entry_nr + 1, entry_alloc);
+ entries[entry_nr++] = entry;
strbuf_reset(&packed_entry);
strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
ret |= packed_fsck_ref_next_line(o, &packed_entry, start, eof, &eol);
@@ -1915,11 +2011,16 @@ static int packed_fsck_ref_content(struct fsck_options *o,
start = eol + 1;
line_number++;
}
+ entry->record.len = start - entry->record.start;
}
+ if (!ret && sorted)
+ ret |= packed_fsck_ref_sorted(o, ref_store, entries, entry_nr);
+
strbuf_release(&packed_entry);
strbuf_release(&refname);
strbuf_release(&packed_entry);
+ free_fsck_packed_ref_entries(entries, entry_nr);
return ret;
}
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index 3ab6b5bba5..adcb5c1bda 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -706,4 +706,67 @@ test_expect_success 'packed-refs content should be checked' '
)
'
+test_expect_success 'packed-ref with sorted trait should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git tag -a annotated-tag-1 -m tag-1 &&
+ branch_1_oid=$(git rev-parse branch-1) &&
+ branch_2_oid=$(git rev-parse branch-2) &&
+ tag_1_oid=$(git rev-parse annotated-tag-1) &&
+ tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
+ refname1="refs/heads/main" &&
+ refname2="refs/heads/foo" &&
+ refname3="refs/tags/foo" &&
+ printf "# pack-refs with: peeled fully-peeled sorted \n" >.git/packed-refs &&
+ printf "%s %s\n" "$branch_2_oid" "$refname1" >>.git/packed-refs &&
+ printf "%s %s\n" "$branch_1_oid" "$refname2" >>.git/packed-refs &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs line 2: packedRefUnsorted: refname '\''$refname1'\'' is not less than next refname '\''$refname2'\''
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err &&
+
+ printf "# pack-refs with: peeled fully-peeled sorted \n" >.git/packed-refs &&
+ printf "%s %s\n" "$tag_1_oid" "$refname3" >>.git/packed-refs &&
+ printf "^%s\n" "$tag_1_peeled_oid" >>.git/packed-refs &&
+ printf "%s %s\n" "$branch_2_oid" "$refname2" >>.git/packed-refs &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs line 2: packedRefUnsorted: refname '\''$refname3'\'' is not less than next refname '\''$refname2'\''
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err
+ )
+'
+
+test_expect_success 'packed-ref without sorted trait should not be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git tag -a annotated-tag-1 -m tag-1 &&
+ branch_1_oid=$(git rev-parse branch-1) &&
+ branch_2_oid=$(git rev-parse branch-2) &&
+ tag_1_oid=$(git rev-parse annotated-tag-1) &&
+ tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
+ refname1="refs/heads/main" &&
+ refname2="refs/heads/foo" &&
+ refname3="refs/tags/foo" &&
+ printf "# pack-refs with: peeled fully-peeled \n" >.git/packed-refs &&
+ printf "%s %s\n" "$branch_2_oid" "$refname1" >>.git/packed-refs &&
+ printf "%s %s\n" "$branch_1_oid" "$refname2" >>.git/packed-refs &&
+ git refs verify 2>err &&
+ test_must_be_empty err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* Re: [PATCH v3 7/8] packed-backend: check whether the "packed-refs" is sorted
2025-02-06 5:59 ` [PATCH v3 7/8] packed-backend: check whether the "packed-refs" is sorted shejialuo
@ 2025-02-12 9:56 ` Patrick Steinhardt
2025-02-12 10:20 ` shejialuo
2025-02-12 10:56 ` shejialuo
0 siblings, 2 replies; 168+ messages in thread
From: Patrick Steinhardt @ 2025-02-12 9:56 UTC (permalink / raw)
To: shejialuo; +Cc: git, Karthik Nayak, Junio C Hamano, Michael Haggerty
On Thu, Feb 06, 2025 at 01:59:55PM +0800, shejialuo wrote:
> diff --git a/refs/packed-backend.c b/refs/packed-backend.c
> index 658f6bc7da..0fbdc5c3fa 100644
> --- a/refs/packed-backend.c
> +++ b/refs/packed-backend.c
> @@ -1767,6 +1773,28 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
> return empty_ref_iterator_begin();
> }
>
> +struct fsck_packed_ref_entry {
> + unsigned long line_number;
> +
> + struct snapshot_record record;
> +};
> +
> +static struct fsck_packed_ref_entry *create_fsck_packed_ref_entry(unsigned long line_number,
> + const char *start)
> +{
> + struct fsck_packed_ref_entry *entry = xcalloc(1, sizeof(*entry));
> + entry->line_number = line_number;
> + entry->record.start = start;
> + return entry;
> +}
> +
> +static void free_fsck_packed_ref_entries(struct fsck_packed_ref_entry **entries, size_t nr)
> +{
> + for (size_t i = 0; i < nr; i++)
> + free(entries[i]);
> + free(entries);
> +}
> +
> static int packed_fsck_ref_next_line(struct fsck_options *o,
> struct strbuf *packed_entry, const char *start,
> const char *eof, const char **eol)
> @@ -1794,19 +1822,33 @@ static int packed_fsck_ref_next_line(struct fsck_options *o,
> }
>
> static int packed_fsck_ref_header(struct fsck_options *o,
> - const char *start, const char *eol)
> + const char *start, const char *eol,
> + unsigned int *sorted)
> {
> - if (!starts_with(start, "# pack-refs with:")) {
> + struct string_list traits = STRING_LIST_INIT_NODUP;
> + char *tmp_line;
> + int ret = 0;
> + char *p;
> +
> + tmp_line = xmemdupz(start, eol - start);
> + if (!skip_prefix(tmp_line, "# pack-refs with:", (const char **)&p)) {
> struct fsck_ref_report report = { 0 };
> report.path = "packed-refs.header";
>
> - return fsck_report_ref(o, &report,
> - FSCK_MSG_BAD_PACKED_REF_HEADER,
> - "'%.*s' does not start with '# pack-refs with:'",
> - (int)(eol - start), start);
> + ret = fsck_report_ref(o, &report,
> + FSCK_MSG_BAD_PACKED_REF_HEADER,
> + "'%.*s' does not start with '# pack-refs with:'",
> + (int)(eol - start), start);
> + goto cleanup;
> }
>
> - return 0;
> + string_list_split_in_place(&traits, p, " ", -1);
> + *sorted = unsorted_string_list_has_string(&traits, "sorted");
I think we call them capabilities, not traits.
[snip]
> static int packed_fsck_ref_content(struct fsck_options *o,
> struct ref_store *ref_store,
> const char *start, const char *eof)
> {
> struct strbuf packed_entry = STRBUF_INIT;
> + struct fsck_packed_ref_entry **entries;
> struct strbuf refname = STRBUF_INIT;
> unsigned long line_number = 1;
> + unsigned int sorted = 0;
> + size_t entry_alloc = 20;
> + size_t entry_nr = 0;
> const char *eol;
> int ret = 0;
>
> strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
> ret |= packed_fsck_ref_next_line(o, &packed_entry, start, eof, &eol);
> if (*start == '#') {
> - ret |= packed_fsck_ref_header(o, start, eol);
> + ret |= packed_fsck_ref_header(o, start, eol, &sorted);
>
> start = eol + 1;
> line_number++;
> }
>
> + ALLOC_ARRAY(entries, entry_alloc);
> while (start < eof) {
> + struct fsck_packed_ref_entry *entry
> + = create_fsck_packed_ref_entry(line_number, start);
Instead of slurping in all entries and allocating them in an array, can
we instead remember the last one and just compare that the last record
is smaller than the current record?
> @@ -1915,11 +2011,16 @@ static int packed_fsck_ref_content(struct fsck_options *o,
> start = eol + 1;
> line_number++;
> }
> + entry->record.len = start - entry->record.start;
> }
>
> + if (!ret && sorted)
> + ret |= packed_fsck_ref_sorted(o, ref_store, entries, entry_nr);
Okay, we now conditionally check whether the refs are sorted based on
whether or not we found the "sorted" capability.
> diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
> index 3ab6b5bba5..adcb5c1bda 100755
> --- a/t/t0602-reffiles-fsck.sh
> +++ b/t/t0602-reffiles-fsck.sh
> @@ -706,4 +706,67 @@ test_expect_success 'packed-refs content should be checked' '
> )
> '
>
> +test_expect_success 'packed-ref with sorted trait should be checked' '
> + test_when_finished "rm -rf repo" &&
> + git init repo &&
> + (
> + cd repo &&
> + test_commit default &&
> + git branch branch-1 &&
> + git branch branch-2 &&
> + git tag -a annotated-tag-1 -m tag-1 &&
> + branch_1_oid=$(git rev-parse branch-1) &&
> + branch_2_oid=$(git rev-parse branch-2) &&
> + tag_1_oid=$(git rev-parse annotated-tag-1) &&
> + tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
> + refname1="refs/heads/main" &&
> + refname2="refs/heads/foo" &&
> + refname3="refs/tags/foo" &&
> + printf "# pack-refs with: peeled fully-peeled sorted \n" >.git/packed-refs &&
> + printf "%s %s\n" "$branch_2_oid" "$refname1" >>.git/packed-refs &&
> + printf "%s %s\n" "$branch_1_oid" "$refname2" >>.git/packed-refs &&
Same comment here as in the previous patch, this can be simplified with
HERE docs.
> + test_must_fail git refs verify 2>err &&
> + cat >expect <<-EOF &&
> + error: packed-refs line 2: packedRefUnsorted: refname '\''$refname1'\'' is not less than next refname '\''$refname2'\''
> + EOF
> + rm .git/packed-refs &&
> + test_cmp expect err &&
> +
> + printf "# pack-refs with: peeled fully-peeled sorted \n" >.git/packed-refs &&
> + printf "%s %s\n" "$tag_1_oid" "$refname3" >>.git/packed-refs &&
> + printf "^%s\n" "$tag_1_peeled_oid" >>.git/packed-refs &&
> + printf "%s %s\n" "$branch_2_oid" "$refname2" >>.git/packed-refs &&
> + test_must_fail git refs verify 2>err &&
> + cat >expect <<-EOF &&
> + error: packed-refs line 2: packedRefUnsorted: refname '\''$refname3'\'' is not less than next refname '\''$refname2'\''
> + EOF
> + rm .git/packed-refs &&
> + test_cmp expect err
> + )
> +'
> +
> +test_expect_success 'packed-ref without sorted trait should not be checked' '
> + test_when_finished "rm -rf repo" &&
> + git init repo &&
> + (
> + cd repo &&
> + test_commit default &&
> + git branch branch-1 &&
> + git branch branch-2 &&
> + git tag -a annotated-tag-1 -m tag-1 &&
> + branch_1_oid=$(git rev-parse branch-1) &&
> + branch_2_oid=$(git rev-parse branch-2) &&
> + tag_1_oid=$(git rev-parse annotated-tag-1) &&
> + tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
> + refname1="refs/heads/main" &&
> + refname2="refs/heads/foo" &&
> + refname3="refs/tags/foo" &&
> + printf "# pack-refs with: peeled fully-peeled \n" >.git/packed-refs &&
> + printf "%s %s\n" "$branch_2_oid" "$refname1" >>.git/packed-refs &&
> + printf "%s %s\n" "$branch_1_oid" "$refname2" >>.git/packed-refs &&
And here.
Patrick
^ permalink raw reply [flat|nested] 168+ messages in thread
* Re: [PATCH v3 7/8] packed-backend: check whether the "packed-refs" is sorted
2025-02-12 9:56 ` Patrick Steinhardt
@ 2025-02-12 10:20 ` shejialuo
2025-02-12 10:42 ` Patrick Steinhardt
2025-02-12 10:56 ` shejialuo
1 sibling, 1 reply; 168+ messages in thread
From: shejialuo @ 2025-02-12 10:20 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git, Karthik Nayak, Junio C Hamano, Michael Haggerty
On Wed, Feb 12, 2025 at 10:56:56AM +0100, Patrick Steinhardt wrote:
> On Thu, Feb 06, 2025 at 01:59:55PM +0800, shejialuo wrote:
> > diff --git a/refs/packed-backend.c b/refs/packed-backend.c
> > index 658f6bc7da..0fbdc5c3fa 100644
> > --- a/refs/packed-backend.c
> > +++ b/refs/packed-backend.c
> > @@ -1767,6 +1773,28 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
> > return empty_ref_iterator_begin();
> > }
> >
> > +struct fsck_packed_ref_entry {
> > + unsigned long line_number;
> > +
> > + struct snapshot_record record;
> > +};
> > +
> > +static struct fsck_packed_ref_entry *create_fsck_packed_ref_entry(unsigned long line_number,
> > + const char *start)
> > +{
> > + struct fsck_packed_ref_entry *entry = xcalloc(1, sizeof(*entry));
> > + entry->line_number = line_number;
> > + entry->record.start = start;
> > + return entry;
> > +}
> > +
> > +static void free_fsck_packed_ref_entries(struct fsck_packed_ref_entry **entries, size_t nr)
> > +{
> > + for (size_t i = 0; i < nr; i++)
> > + free(entries[i]);
> > + free(entries);
> > +}
> > +
> > static int packed_fsck_ref_next_line(struct fsck_options *o,
> > struct strbuf *packed_entry, const char *start,
> > const char *eof, const char **eol)
> > @@ -1794,19 +1822,33 @@ static int packed_fsck_ref_next_line(struct fsck_options *o,
> > }
> >
> > static int packed_fsck_ref_header(struct fsck_options *o,
> > - const char *start, const char *eol)
> > + const char *start, const char *eol,
> > + unsigned int *sorted)
> > {
> > - if (!starts_with(start, "# pack-refs with:")) {
> > + struct string_list traits = STRING_LIST_INIT_NODUP;
> > + char *tmp_line;
> > + int ret = 0;
> > + char *p;
> > +
> > + tmp_line = xmemdupz(start, eol - start);
> > + if (!skip_prefix(tmp_line, "# pack-refs with:", (const char **)&p)) {
> > struct fsck_ref_report report = { 0 };
> > report.path = "packed-refs.header";
> >
> > - return fsck_report_ref(o, &report,
> > - FSCK_MSG_BAD_PACKED_REF_HEADER,
> > - "'%.*s' does not start with '# pack-refs with:'",
> > - (int)(eol - start), start);
> > + ret = fsck_report_ref(o, &report,
> > + FSCK_MSG_BAD_PACKED_REF_HEADER,
> > + "'%.*s' does not start with '# pack-refs with:'",
> > + (int)(eol - start), start);
> > + goto cleanup;
> > }
> >
> > - return 0;
> > + string_list_split_in_place(&traits, p, " ", -1);
> > + *sorted = unsorted_string_list_has_string(&traits, "sorted");
>
> I think we call them capabilities, not traits.
>
Yes, capabilities will be more semantic. But the original code in
"packed-backend.c" uses "traits". Let us follow the original style to
make sure consistency.
> [snip]
> > static int packed_fsck_ref_content(struct fsck_options *o,
> > struct ref_store *ref_store,
> > const char *start, const char *eof)
> > {
> > struct strbuf packed_entry = STRBUF_INIT;
> > + struct fsck_packed_ref_entry **entries;
> > struct strbuf refname = STRBUF_INIT;
> > unsigned long line_number = 1;
> > + unsigned int sorted = 0;
> > + size_t entry_alloc = 20;
> > + size_t entry_nr = 0;
> > const char *eol;
> > int ret = 0;
> >
> > strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
> > ret |= packed_fsck_ref_next_line(o, &packed_entry, start, eof, &eol);
> > if (*start == '#') {
> > - ret |= packed_fsck_ref_header(o, start, eol);
> > + ret |= packed_fsck_ref_header(o, start, eol, &sorted);
> >
> > start = eol + 1;
> > line_number++;
> > }
> >
> > + ALLOC_ARRAY(entries, entry_alloc);
> > while (start < eof) {
> > + struct fsck_packed_ref_entry *entry
> > + = create_fsck_packed_ref_entry(line_number, start);
>
> Instead of slurping in all entries and allocating them in an array, can
> we instead remember the last one and just compare that the last record
> is smaller than the current record?
>
> > @@ -1915,11 +2011,16 @@ static int packed_fsck_ref_content(struct fsck_options *o,
> > start = eol + 1;
> > line_number++;
> > }
> > + entry->record.len = start - entry->record.start;
> > }
> >
> > + if (!ret && sorted)
> > + ret |= packed_fsck_ref_sorted(o, ref_store, entries, entry_nr);
>
> Okay, we now conditionally check whether the refs are sorted based on
> whether or not we found the "sorted" capability.
>
> > diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
> > index 3ab6b5bba5..adcb5c1bda 100755
> > --- a/t/t0602-reffiles-fsck.sh
> > +++ b/t/t0602-reffiles-fsck.sh
> > @@ -706,4 +706,67 @@ test_expect_success 'packed-refs content should be checked' '
> > )
> > '
> >
> > +test_expect_success 'packed-ref with sorted trait should be checked' '
> > + test_when_finished "rm -rf repo" &&
> > + git init repo &&
> > + (
> > + cd repo &&
> > + test_commit default &&
> > + git branch branch-1 &&
> > + git branch branch-2 &&
> > + git tag -a annotated-tag-1 -m tag-1 &&
> > + branch_1_oid=$(git rev-parse branch-1) &&
> > + branch_2_oid=$(git rev-parse branch-2) &&
> > + tag_1_oid=$(git rev-parse annotated-tag-1) &&
> > + tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
> > + refname1="refs/heads/main" &&
> > + refname2="refs/heads/foo" &&
> > + refname3="refs/tags/foo" &&
> > + printf "# pack-refs with: peeled fully-peeled sorted \n" >.git/packed-refs &&
> > + printf "%s %s\n" "$branch_2_oid" "$refname1" >>.git/packed-refs &&
> > + printf "%s %s\n" "$branch_1_oid" "$refname2" >>.git/packed-refs &&
>
> Same comment here as in the previous patch, this can be simplified with
> HERE docs.
>
> > + test_must_fail git refs verify 2>err &&
> > + cat >expect <<-EOF &&
> > + error: packed-refs line 2: packedRefUnsorted: refname '\''$refname1'\'' is not less than next refname '\''$refname2'\''
> > + EOF
> > + rm .git/packed-refs &&
> > + test_cmp expect err &&
> > +
> > + printf "# pack-refs with: peeled fully-peeled sorted \n" >.git/packed-refs &&
> > + printf "%s %s\n" "$tag_1_oid" "$refname3" >>.git/packed-refs &&
> > + printf "^%s\n" "$tag_1_peeled_oid" >>.git/packed-refs &&
> > + printf "%s %s\n" "$branch_2_oid" "$refname2" >>.git/packed-refs &&
> > + test_must_fail git refs verify 2>err &&
> > + cat >expect <<-EOF &&
> > + error: packed-refs line 2: packedRefUnsorted: refname '\''$refname3'\'' is not less than next refname '\''$refname2'\''
> > + EOF
> > + rm .git/packed-refs &&
> > + test_cmp expect err
> > + )
> > +'
> > +
> > +test_expect_success 'packed-ref without sorted trait should not be checked' '
> > + test_when_finished "rm -rf repo" &&
> > + git init repo &&
> > + (
> > + cd repo &&
> > + test_commit default &&
> > + git branch branch-1 &&
> > + git branch branch-2 &&
> > + git tag -a annotated-tag-1 -m tag-1 &&
> > + branch_1_oid=$(git rev-parse branch-1) &&
> > + branch_2_oid=$(git rev-parse branch-2) &&
> > + tag_1_oid=$(git rev-parse annotated-tag-1) &&
> > + tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
> > + refname1="refs/heads/main" &&
> > + refname2="refs/heads/foo" &&
> > + refname3="refs/tags/foo" &&
> > + printf "# pack-refs with: peeled fully-peeled \n" >.git/packed-refs &&
> > + printf "%s %s\n" "$branch_2_oid" "$refname1" >>.git/packed-refs &&
> > + printf "%s %s\n" "$branch_1_oid" "$refname2" >>.git/packed-refs &&
>
> And here.
>
Thanks, I will improve this in the next version.
> Patrick
^ permalink raw reply [flat|nested] 168+ messages in thread
* Re: [PATCH v3 7/8] packed-backend: check whether the "packed-refs" is sorted
2025-02-12 10:20 ` shejialuo
@ 2025-02-12 10:42 ` Patrick Steinhardt
0 siblings, 0 replies; 168+ messages in thread
From: Patrick Steinhardt @ 2025-02-12 10:42 UTC (permalink / raw)
To: shejialuo; +Cc: git, Karthik Nayak, Junio C Hamano, Michael Haggerty
On Wed, Feb 12, 2025 at 06:20:01PM +0800, shejialuo wrote:
> On Wed, Feb 12, 2025 at 10:56:56AM +0100, Patrick Steinhardt wrote:
> > On Thu, Feb 06, 2025 at 01:59:55PM +0800, shejialuo wrote:
> > > diff --git a/refs/packed-backend.c b/refs/packed-backend.c
> > > index 658f6bc7da..0fbdc5c3fa 100644
> > > --- a/refs/packed-backend.c
> > > +++ b/refs/packed-backend.c
> > > - return 0;
> > > + string_list_split_in_place(&traits, p, " ", -1);
> > > + *sorted = unsorted_string_list_has_string(&traits, "sorted");
> >
> > I think we call them capabilities, not traits.
> >
>
> Yes, capabilities will be more semantic. But the original code in
> "packed-backend.c" uses "traits". Let us follow the original style to
> make sure consistency.
Interesting, TIL. But yeah, in that case we should continue to call them
traits.
Patrick
^ permalink raw reply [flat|nested] 168+ messages in thread
* Re: [PATCH v3 7/8] packed-backend: check whether the "packed-refs" is sorted
2025-02-12 9:56 ` Patrick Steinhardt
2025-02-12 10:20 ` shejialuo
@ 2025-02-12 10:56 ` shejialuo
1 sibling, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-12 10:56 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git, Karthik Nayak, Junio C Hamano, Michael Haggerty
On Wed, Feb 12, 2025 at 10:56:56AM +0100, Patrick Steinhardt wrote:
> > static int packed_fsck_ref_content(struct fsck_options *o,
> > struct ref_store *ref_store,
> > const char *start, const char *eof)
> > {
> > struct strbuf packed_entry = STRBUF_INIT;
> > + struct fsck_packed_ref_entry **entries;
> > struct strbuf refname = STRBUF_INIT;
> > unsigned long line_number = 1;
> > + unsigned int sorted = 0;
> > + size_t entry_alloc = 20;
> > + size_t entry_nr = 0;
> > const char *eol;
> > int ret = 0;
> >
> > strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
> > ret |= packed_fsck_ref_next_line(o, &packed_entry, start, eof, &eol);
> > if (*start == '#') {
> > - ret |= packed_fsck_ref_header(o, start, eol);
> > + ret |= packed_fsck_ref_header(o, start, eol, &sorted);
> >
> > start = eol + 1;
> > line_number++;
> > }
> >
> > + ALLOC_ARRAY(entries, entry_alloc);
> > while (start < eof) {
> > + struct fsck_packed_ref_entry *entry
> > + = create_fsck_packed_ref_entry(line_number, start);
>
> Instead of slurping in all entries and allocating them in an array, can
> we instead remember the last one and just compare that the last record
> is smaller than the current record?
>
Sorry here, I have missed out this. Actually, the way you say is the
most efficient way to check whether the "packed-refs" is sorted.
However, there is a concern. When we check each ref entry, we could
compare the refname with previous refname. But I don't want to do this
due to the reason that I don't want to mix up these two checks. To
conclude, we have the following call sequences which are independent.
1. check ref entry consistency. (oid, refnames, format...)
2. check whether the "packed-refs" is sorted.
But I do agree with your concern. The reason why I record them is that I
think we have already parsed the file, I think there is no need to parse
it again. So, I use a way to record the information needed to check. And
this would definitely introduce memory burden.
So we have two choices:
1. Keep the design unchanged (space overhead).
2. Parse the file again (time overhead). Thus we only have two allocated
memory.
From my writing, I think 2 will be better. If there are many entries, we
would allocate too much memory.
Let me improve this.
Thanks,
Jialuo
^ permalink raw reply [flat|nested] 168+ messages in thread
* [PATCH v3 8/8] builtin/fsck: add `git refs verify` child process
2025-02-06 5:56 ` [PATCH v3 0/8] add more ref consistency checks shejialuo
` (6 preceding siblings ...)
2025-02-06 5:59 ` [PATCH v3 7/8] packed-backend: check whether the "packed-refs" is sorted shejialuo
@ 2025-02-06 6:00 ` shejialuo
2025-02-12 9:56 ` Patrick Steinhardt
2025-02-14 4:50 ` [PATCH v4 0/8] add more ref consistency checks shejialuo
8 siblings, 1 reply; 168+ messages in thread
From: shejialuo @ 2025-02-06 6:00 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
At now, we have already implemented the ref consistency checks for both
"files-backend" and "packed-backend". Although we would check some
redundant things, it won't cause trouble. So, let's integrate it into
the "git-fsck(1)" command to get feedback from the users. And also by
calling "git refs verify" in "git-fsck(1)", we make sure that the new
added checks don't break.
Introduce a new function "fsck_refs" that initializes and runs a child
process to execute the "git refs verify" command. In order to provide
the user interface create a progress which makes the total task be 1.
It's hard to know how many loose refs we will check now. We might
improve this later.
Then, introduce the option to allow the user to disable checking ref
database consistency. Put this function in the very first execution
sequence of "git-fsck(1)" due to that we don't want the existing code of
"git-fsck(1)" which would implicitly check the consistency of refs to
die the program.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/git-fsck.txt | 6 +++++-
builtin/fsck.c | 33 ++++++++++++++++++++++++++++++++-
2 files changed, 37 insertions(+), 2 deletions(-)
diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt
index 5b82e4605c..9bd433028f 100644
--- a/Documentation/git-fsck.txt
+++ b/Documentation/git-fsck.txt
@@ -12,7 +12,7 @@ SYNOPSIS
'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]
[--[no-]full] [--strict] [--verbose] [--lost-found]
[--[no-]dangling] [--[no-]progress] [--connectivity-only]
- [--[no-]name-objects] [<object>...]
+ [--[no-]name-objects] [--[no-]references] [<object>...]
DESCRIPTION
-----------
@@ -104,6 +104,10 @@ care about this output and want to speed it up further.
progress status even if the standard error stream is not
directed to a terminal.
+--[no-]references::
+ Control whether to check the references database consistency
+ via 'git refs verify'. See linkgit:git-refs[1] for details.
+
CONFIGURATION
-------------
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 7a4dcb0716..f4f395cfbd 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -50,6 +50,7 @@ static int verbose;
static int show_progress = -1;
static int show_dangling = 1;
static int name_objects;
+static int check_references = 1;
#define ERROR_OBJECT 01
#define ERROR_REACHABLE 02
#define ERROR_PACK 04
@@ -905,11 +906,37 @@ static int check_pack_rev_indexes(struct repository *r, int show_progress)
return res;
}
+static void fsck_refs(struct repository *r)
+{
+ struct child_process refs_verify = CHILD_PROCESS_INIT;
+ struct progress *progress = NULL;
+
+ if (show_progress)
+ progress = start_progress(r, _("Checking ref database"), 1);
+
+ if (verbose)
+ fprintf_ln(stderr, _("Checking ref database"));
+
+ child_process_init(&refs_verify);
+ refs_verify.git_cmd = 1;
+ strvec_pushl(&refs_verify.args, "refs", "verify", NULL);
+ if (verbose)
+ strvec_push(&refs_verify.args, "--verbose");
+ if (check_strict)
+ strvec_push(&refs_verify.args, "--strict");
+
+ if (run_command(&refs_verify))
+ errors_found |= ERROR_REFS;
+
+ display_progress(progress, 1);
+ stop_progress(&progress);
+}
+
static char const * const fsck_usage[] = {
N_("git fsck [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]\n"
" [--[no-]full] [--strict] [--verbose] [--lost-found]\n"
" [--[no-]dangling] [--[no-]progress] [--connectivity-only]\n"
- " [--[no-]name-objects] [<object>...]"),
+ " [--[no-]name-objects] [--[no-]references] [<object>...]"),
NULL
};
@@ -928,6 +955,7 @@ static struct option fsck_opts[] = {
N_("write dangling objects in .git/lost-found")),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
OPT_BOOL(0, "name-objects", &name_objects, N_("show verbose names for reachable objects")),
+ OPT_BOOL(0, "references", &check_references, N_("check reference database consistency")),
OPT_END(),
};
@@ -970,6 +998,9 @@ int cmd_fsck(int argc,
git_config(git_fsck_config, &fsck_obj_options);
prepare_repo_settings(the_repository);
+ if (check_references)
+ fsck_refs(the_repository);
+
if (connectivity_only) {
for_each_loose_object(mark_loose_for_connectivity, NULL, 0);
for_each_packed_object(the_repository,
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* Re: [PATCH v3 8/8] builtin/fsck: add `git refs verify` child process
2025-02-06 6:00 ` [PATCH v3 8/8] builtin/fsck: add `git refs verify` child process shejialuo
@ 2025-02-12 9:56 ` Patrick Steinhardt
2025-02-12 10:21 ` shejialuo
0 siblings, 1 reply; 168+ messages in thread
From: Patrick Steinhardt @ 2025-02-12 9:56 UTC (permalink / raw)
To: shejialuo; +Cc: git, Karthik Nayak, Junio C Hamano, Michael Haggerty
On Thu, Feb 06, 2025 at 02:00:07PM +0800, shejialuo wrote:
> diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt
> index 5b82e4605c..9bd433028f 100644
> --- a/Documentation/git-fsck.txt
> +++ b/Documentation/git-fsck.txt
> @@ -12,7 +12,7 @@ SYNOPSIS
> 'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]
> [--[no-]full] [--strict] [--verbose] [--lost-found]
> [--[no-]dangling] [--[no-]progress] [--connectivity-only]
> - [--[no-]name-objects] [<object>...]
> + [--[no-]name-objects] [--[no-]references] [<object>...]
>
> DESCRIPTION
> -----------
> @@ -104,6 +104,10 @@ care about this output and want to speed it up further.
> progress status even if the standard error stream is not
> directed to a terminal.
>
> +--[no-]references::
> + Control whether to check the references database consistency
> + via 'git refs verify'. See linkgit:git-refs[1] for details.
I think we should note the default, which is to check them.
It would also be nice to have a couple of tests to verify that the flag
does what it is intended to do.
Patrick
^ permalink raw reply [flat|nested] 168+ messages in thread
* Re: [PATCH v3 8/8] builtin/fsck: add `git refs verify` child process
2025-02-12 9:56 ` Patrick Steinhardt
@ 2025-02-12 10:21 ` shejialuo
0 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-12 10:21 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git, Karthik Nayak, Junio C Hamano, Michael Haggerty
On Wed, Feb 12, 2025 at 10:56:53AM +0100, Patrick Steinhardt wrote:
> On Thu, Feb 06, 2025 at 02:00:07PM +0800, shejialuo wrote:
> > diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt
> > index 5b82e4605c..9bd433028f 100644
> > --- a/Documentation/git-fsck.txt
> > +++ b/Documentation/git-fsck.txt
> > @@ -12,7 +12,7 @@ SYNOPSIS
> > 'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]
> > [--[no-]full] [--strict] [--verbose] [--lost-found]
> > [--[no-]dangling] [--[no-]progress] [--connectivity-only]
> > - [--[no-]name-objects] [<object>...]
> > + [--[no-]name-objects] [--[no-]references] [<object>...]
> >
> > DESCRIPTION
> > -----------
> > @@ -104,6 +104,10 @@ care about this output and want to speed it up further.
> > progress status even if the standard error stream is not
> > directed to a terminal.
> >
> > +--[no-]references::
> > + Control whether to check the references database consistency
> > + via 'git refs verify'. See linkgit:git-refs[1] for details.
>
> I think we should note the default, which is to check them.
>
OK, let me improve the documentation in the next version.
> It would also be nice to have a couple of tests to verify that the flag
> does what it is intended to do.
>
Good idea, we could test via trailing contents to do this. Let me
improve this.
> Patrick
^ permalink raw reply [flat|nested] 168+ messages in thread
* [PATCH v4 0/8] add more ref consistency checks
2025-02-06 5:56 ` [PATCH v3 0/8] add more ref consistency checks shejialuo
` (7 preceding siblings ...)
2025-02-06 6:00 ` [PATCH v3 8/8] builtin/fsck: add `git refs verify` child process shejialuo
@ 2025-02-14 4:50 ` shejialuo
2025-02-14 4:51 ` [PATCH v4 1/8] t0602: use subshell to ensure working directory unchanged shejialuo
` (9 more replies)
8 siblings, 10 replies; 168+ messages in thread
From: shejialuo @ 2025-02-14 4:50 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
Hi All:
This patch enhances the following things:
1. [PATCH v4 4/8]: update the tests to verify that we don't report any
errors to the user in some cases. Also, suggested by Junio, make sure
that we check whether there is a trailing space after "# packed-refs
with:".
2. [PATCH v4 6/8]: instead of greedily calculating the name of the line,
lazily compute when there is any errors. And use the HERE docs to
improve the test script.
3. [PATCH v4 7/8]: instead of storing the states, we parse the file
again to check whether the file is sorted to avoid allocating too
much memory. And use the HERE docs to improve the test script.
4. [PATCH v4 8/8]: update the documentation to emphasis the default. And
add tests to exercise the code.
shejialuo (8):
t0602: use subshell to ensure working directory unchanged
builtin/refs: get worktrees without reading head information
packed-backend: check whether the "packed-refs" is regular file
packed-backend: add "packed-refs" header consistency check
packed-backend: check whether the refname contains NUL characters
packed-backend: add "packed-refs" entry consistency check
packed-backend: check whether the "packed-refs" is sorted
builtin/fsck: add `git refs verify` child process
Documentation/fsck-msgids.txt | 14 +
Documentation/git-fsck.txt | 7 +-
builtin/fsck.c | 33 +-
builtin/refs.c | 2 +-
fsck.h | 4 +
refs/packed-backend.c | 349 +++++++++-
t/t0602-reffiles-fsck.sh | 1205 ++++++++++++++++++++-------------
worktree.c | 5 +
worktree.h | 6 +
9 files changed, 1140 insertions(+), 485 deletions(-)
Range-diff against v3:
1: 20889b7b18 = 1: 20889b7b18 t0602: use subshell to ensure working directory unchanged
2: 9d7780e953 = 2: 9d7780e953 builtin/refs: get worktrees without reading head information
3: 44d26f6440 = 3: 44d26f6440 packed-backend: check whether the "packed-refs" is regular file
4: a9ab7af16a ! 4: 976c5baba0 packed-backend: add "packed-refs" header consistency check
@@ Commit message
In "packed-backend.c::create_snapshot", if there is a header (the line
which starts with '#'), we will check whether the line starts with "#
- pack-refs with:". As we are going to implement the header consistency
- check, we should port this check into "packed_fsck".
+ pack-refs with:". Before we port this check into "packed_fsck", let's
+ fix "create_snapshot" to check the prefix "# packed-ref with: " instead
+ of "# packed-ref with:" due to that we will always write a single
+ trailing space after the colon.
However, we need to consider other situations and discuss whether we
need to add checks.
@@ Commit message
user. This is because in older Git version, we never write header in
the "packed-refs" file. Also, we do allow no header in "packed-refs"
in runtime.
- 2. If the header content does not start with "# packed-ref with:", we
+ 2. If the header content does not start with "# packed-ref with: ", we
should report an error just like what "create_snapshot" does. So,
create a new fsck message "badPackedRefHeader(ERROR)" for this.
3. If the header content is not the same as the constant string
@@ fsck.h: enum fsck_msg_type {
FUNC(ZERO_PADDED_DATE, ERROR) \
## refs/packed-backend.c ##
+@@ refs/packed-backend.c: static struct snapshot *create_snapshot(struct packed_ref_store *refs)
+
+ tmp = xmemdupz(snapshot->buf, eol - snapshot->buf);
+
+- if (!skip_prefix(tmp, "# pack-refs with:", (const char **)&p))
++ if (!skip_prefix(tmp, "# pack-refs with: ", (const char **)&p))
+ die_invalid_line(refs->path,
+ snapshot->buf,
+ snapshot->eof - snapshot->buf);
@@ refs/packed-backend.c: static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
return empty_ref_iterator_begin();
}
+static int packed_fsck_ref_next_line(struct fsck_options *o,
-+ struct strbuf *packed_entry, const char *start,
++ unsigned long line_number, const char *start,
+ const char *eof, const char **eol)
+{
+ int ret = 0;
+
+ *eol = memchr(start, '\n', eof - start);
+ if (!*eol) {
++ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+
-+ report.path = packed_entry->buf;
++ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
++ report.path = packed_entry.buf;
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_PACKED_REF_ENTRY_NOT_TERMINATED,
+ "'%.*s' is not terminated with a newline",
@@ refs/packed-backend.c: static struct ref_iterator *packed_reflog_iterator_begin(
+ * the buffer.
+ */
+ *eol = eof;
++ strbuf_release(&packed_entry);
+ }
+
+ return ret;
@@ refs/packed-backend.c: static struct ref_iterator *packed_reflog_iterator_begin(
+static int packed_fsck_ref_header(struct fsck_options *o,
+ const char *start, const char *eol)
+{
-+ if (!starts_with(start, "# pack-refs with:")) {
++ if (!starts_with(start, "# pack-refs with: ")) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs.header";
+
+ return fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_HEADER,
-+ "'%.*s' does not start with '# pack-refs with:'",
++ "'%.*s' does not start with '# pack-refs with: '",
+ (int)(eol - start), start);
+ }
+
@@ refs/packed-backend.c: static struct ref_iterator *packed_reflog_iterator_begin(
+static int packed_fsck_ref_content(struct fsck_options *o,
+ const char *start, const char *eof)
+{
-+ struct strbuf packed_entry = STRBUF_INIT;
+ unsigned long line_number = 1;
+ const char *eol;
+ int ret = 0;
+
-+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
-+ ret |= packed_fsck_ref_next_line(o, &packed_entry, start, eof, &eol);
++ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ if (*start == '#') {
+ ret |= packed_fsck_ref_header(o, start, eol);
+
@@ refs/packed-backend.c: static struct ref_iterator *packed_reflog_iterator_begin(
+ line_number++;
+ }
+
-+ strbuf_release(&packed_entry);
+ return ret;
+}
+
@@ t/t0602-reffiles-fsck.sh: test_expect_success SYMLINKS 'the filetype of packed-r
+
+ for bad_header in "# pack-refs wit: peeled fully-peeled sorted " \
+ "# pack-refs with traits: peeled fully-peeled sorted " \
-+ "# pack-refs with a: peeled fully-peeled"
++ "# pack-refs with a: peeled fully-peeled" \
++ "# pack-refs with:peeled fully-peeled sorted"
+ do
+ printf "%s\n" "$bad_header" >.git/packed-refs &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
-+ error: packed-refs.header: badPackedRefHeader: '\''$bad_header'\'' does not start with '\''# pack-refs with:'\''
++ error: packed-refs.header: badPackedRefHeader: '\''$bad_header'\'' does not start with '\''# pack-refs with: '\''
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err || return 1
+ done
+ )
+'
++
++test_expect_success 'packed-refs missing header should not be reported' '
++ test_when_finished "rm -rf repo" &&
++ git init repo &&
++ (
++ cd repo &&
++ test_commit default &&
++
++ printf "$(git rev-parse HEAD) refs/heads/main\n" >.git/packed-refs &&
++ git refs verify 2>err &&
++ test_must_be_empty err
++ )
++'
++
++test_expect_success 'packed-refs unknown traits should not be reported' '
++ test_when_finished "rm -rf repo" &&
++ git init repo &&
++ (
++ cd repo &&
++ test_commit default &&
++
++ printf "# pack-refs with: peeled fully-peeled sorted foo\n" >.git/packed-refs &&
++ git refs verify 2>err &&
++ test_must_be_empty err
++ )
++'
+
test_done
5: 9b075434a1 = 5: b66f142d7f packed-backend: check whether the refname contains NUL characters
6: a976508319 ! 6: f68028e171 packed-backend: add "packed-refs" entry consistency check
@@ refs/packed-backend.c: static int packed_fsck_ref_header(struct fsck_options *o,
+static int packed_fsck_ref_peeled_line(struct fsck_options *o,
+ struct ref_store *ref_store,
-+ struct strbuf *packed_entry,
++ unsigned long line_number,
+ const char *start, const char *eol)
+{
++ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct object_id peeled;
+ const char *p;
-+
-+ report.path = packed_entry->buf;
++ int ret = 0;
+
+ /*
+ * Skip the '^' and parse the peeled oid.
+ */
+ start++;
-+ if (parse_oid_hex_algop(start, &peeled, &p, ref_store->repo->hash_algo))
-+ return fsck_report_ref(o, &report,
-+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
-+ "'%.*s' has invalid peeled oid",
-+ (int)(eol - start), start);
-+
-+ if (p != eol)
-+ return fsck_report_ref(o, &report,
-+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
-+ "has trailing garbage after peeled oid '%.*s'",
-+ (int)(eol - p), p);
-+
-+ return 0;
++ if (parse_oid_hex_algop(start, &peeled, &p, ref_store->repo->hash_algo)) {
++ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
++ report.path = packed_entry.buf;
++
++ ret = fsck_report_ref(o, &report,
++ FSCK_MSG_BAD_PACKED_REF_ENTRY,
++ "'%.*s' has invalid peeled oid",
++ (int)(eol - start), start);
++ goto cleanup;
++ }
++
++ if (p != eol) {
++ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
++ report.path = packed_entry.buf;
++
++ ret = fsck_report_ref(o, &report,
++ FSCK_MSG_BAD_PACKED_REF_ENTRY,
++ "has trailing garbage after peeled oid '%.*s'",
++ (int)(eol - p), p);
++ goto cleanup;
++ }
++cleanup:
++ strbuf_release(&packed_entry);
++ return ret;
+}
+
+static int packed_fsck_ref_main_line(struct fsck_options *o,
+ struct ref_store *ref_store,
-+ struct strbuf *packed_entry,
++ unsigned long line_number,
+ struct strbuf *refname,
+ const char *start, const char *eol)
+{
++ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct object_id oid;
+ const char *p;
++ int ret = 0;
+
-+ report.path = packed_entry->buf;
++ if (parse_oid_hex_algop(start, &oid, &p, ref_store->repo->hash_algo)) {
++ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
++ report.path = packed_entry.buf;
+
-+ if (parse_oid_hex_algop(start, &oid, &p, ref_store->repo->hash_algo))
-+ return fsck_report_ref(o, &report,
-+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
-+ "'%.*s' has invalid oid",
-+ (int)(eol - start), start);
++ ret = fsck_report_ref(o, &report,
++ FSCK_MSG_BAD_PACKED_REF_ENTRY,
++ "'%.*s' has invalid oid",
++ (int)(eol - start), start);
++ goto cleanup;
++ }
+
-+ if (p == eol || !isspace(*p))
-+ return fsck_report_ref(o, &report,
-+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
-+ "has no space after oid '%s' but with '%.*s'",
-+ oid_to_hex(&oid), (int)(eol - p), p);
++ if (p == eol || !isspace(*p)) {
++ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
++ report.path = packed_entry.buf;
++
++ ret = fsck_report_ref(o, &report,
++ FSCK_MSG_BAD_PACKED_REF_ENTRY,
++ "has no space after oid '%s' but with '%.*s'",
++ oid_to_hex(&oid), (int)(eol - p), p);
++ goto cleanup;
++ }
+
+ p++;
+ strbuf_reset(refname);
+ strbuf_add(refname, p, eol - p);
-+ if (refname_contains_nul(refname))
-+ return fsck_report_ref(o, &report,
-+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
-+ "refname '%s' contains NULL binaries",
-+ refname->buf);
-+
-+ if (check_refname_format(refname->buf, 0))
-+ return fsck_report_ref(o, &report,
-+ FSCK_MSG_BAD_REF_NAME,
-+ "has bad refname '%s'", refname->buf);
-+
-+ return 0;
++ if (refname_contains_nul(refname)) {
++ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
++ report.path = packed_entry.buf;
++
++ ret = fsck_report_ref(o, &report,
++ FSCK_MSG_BAD_PACKED_REF_ENTRY,
++ "refname '%s' contains NULL binaries",
++ refname->buf);
++ }
++
++ if (check_refname_format(refname->buf, 0)) {
++ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
++ report.path = packed_entry.buf;
++
++ ret = fsck_report_ref(o, &report,
++ FSCK_MSG_BAD_REF_NAME,
++ "has bad refname '%s'", refname->buf);
++ }
++
++cleanup:
++ strbuf_release(&packed_entry);
++ return ret;
+}
+
static int packed_fsck_ref_content(struct fsck_options *o,
+ struct ref_store *ref_store,
const char *start, const char *eof)
{
- struct strbuf packed_entry = STRBUF_INIT;
+ struct strbuf refname = STRBUF_INIT;
unsigned long line_number = 1;
const char *eol;
@@ refs/packed-backend.c: static int packed_fsck_ref_content(struct fsck_options *o
}
+ while (start < eof) {
-+ strbuf_reset(&packed_entry);
-+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
-+ ret |= packed_fsck_ref_next_line(o, &packed_entry, start, eof, &eol);
-+ ret |= packed_fsck_ref_main_line(o, ref_store, &packed_entry, &refname, start, eol);
++ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
++ ret |= packed_fsck_ref_main_line(o, ref_store, line_number, &refname, start, eol);
+ start = eol + 1;
+ line_number++;
+ if (start < eof && *start == '^') {
-+ strbuf_reset(&packed_entry);
-+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
-+ ret |= packed_fsck_ref_next_line(o, &packed_entry, start, eof, &eol);
-+ ret |= packed_fsck_ref_peeled_line(o, ref_store, &packed_entry,
++ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
++ ret |= packed_fsck_ref_peeled_line(o, ref_store, line_number,
+ start, eol);
+ start = eol + 1;
+ line_number++;
+ }
+ }
+
-+ strbuf_release(&packed_entry);
+ strbuf_release(&refname);
- strbuf_release(&packed_entry);
return ret;
}
+
@@ refs/packed-backend.c: static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
@@ refs/packed-backend.c: static int packed_fsck(struct ref_store *ref_store,
cleanup:
## t/t0602-reffiles-fsck.sh ##
-@@ t/t0602-reffiles-fsck.sh: test_expect_success 'packed-refs header should be checked' '
+@@ t/t0602-reffiles-fsck.sh: test_expect_success 'packed-refs unknown traits should not be reported' '
)
'
@@ t/t0602-reffiles-fsck.sh: test_expect_success 'packed-refs header should be chec
+ tag_2_peeled_oid=$(git rev-parse annotated-tag-2^{}) &&
+ short_oid=$(printf "%s" $tag_1_peeled_oid | cut -c 1-4) &&
+
-+ printf "# pack-refs with: peeled fully-peeled sorted \n" >.git/packed-refs &&
-+ printf "%s\n" "$short_oid refs/heads/branch-1" >>.git/packed-refs &&
-+ printf "%sx\n" "$branch_1_oid" >>.git/packed-refs &&
-+ printf "%s refs/heads/bad-branch\n" "$branch_2_oid" >>.git/packed-refs &&
-+ printf "%s refs/heads/branch.\n" "$branch_2_oid" >>.git/packed-refs &&
-+ printf "%s refs/tags/annotated-tag-3\n" "$tag_1_oid" >>.git/packed-refs &&
-+ printf "^%s\n" "$short_oid" >>.git/packed-refs &&
-+ printf "%s refs/tags/annotated-tag-4.\n" "$tag_2_oid" >>.git/packed-refs &&
-+ printf "^%s garbage\n" "$tag_2_peeled_oid" >>.git/packed-refs &&
++ cat >.git/packed-refs <<-EOF &&
++ # pack-refs with: peeled fully-peeled sorted
++ $short_oid refs/heads/branch-1
++ ${branch_1_oid}x
++ $branch_2_oid refs/heads/bad-branch
++ $branch_2_oid refs/heads/branch.
++ $tag_1_oid refs/tags/annotated-tag-3
++ ^$short_oid
++ $tag_2_oid refs/tags/annotated-tag-4.
++ ^$tag_2_peeled_oid garbage
++ EOF
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs line 2: badPackedRefEntry: '\''$short_oid refs/heads/branch-1'\'' has invalid oid
7: 707e3e2151 ! 7: 4a7adf293f packed-backend: check whether the "packed-refs" is sorted
@@ Commit message
sorted in this case.
Update the "packed_fsck_ref_header" to know whether there is a "sorted"
- trail in the header. Then, create a new structure "fsck_packed_ref_entry"
- to store the state during the parsing process for every entry. It may
- seem that we could just add a new "struct strbuf refname" into the
- "struct fsck_packed_ref_entry" and during the parsing process, we could
- store the refname into this structure and thus we could compare later.
- However, this is not a good design due to the following reasons:
+ trail in the header. It may seem that we could record all refnames
+ during the parsing process and then compare later. However, this is not
+ a good design due to the following reasons:
1. Because we need to store the state across the whole checking
lifetime, we would consume a lot of memory if there are many entries
in the "packed-refs" file.
- 2. The most important thing is that we cannot reuse the existing compare
- functions which cause repetition.
+ 2. We cannot reuse the existing compare function "cmp_packed_ref_records"
+ which cause repetition.
- So, instead of storing the "struct strbuf", let's use the existing
- structure "struct snaphost_record". And thus we could use the existing
- function "cmp_packed_ref_records".
+ Because "cmp_packed_ref_records" needs an extra parameter "struct
+ snaphost", extract the common part into a new function
+ "cmp_packed_ref_records" to reuse this function to compare.
- However, this function need an extra parameter for "struct snaphost".
- Extract the common part into a new function "cmp_packed_ref_records" to
- reuse this function to compare.
-
- Then, create a new function "packed_fsck_ref_sorted" to use the new fsck
- message "packedRefUnsorted(ERROR)" to report to the user.
+ Then, create a new function "packed_fsck_ref_sorted" to parse the file
+ again and user the new fsck message "packedRefUnsorted(ERROR)" to report
+ to the user if the file is not sorted.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
@@ refs/packed-backend.c: static int cmp_packed_ref_records(const void *v1, const v
/*
* Compare a snapshot record at `rec` to the specified NUL-terminated
* refname.
-@@ refs/packed-backend.c: static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
- return empty_ref_iterator_begin();
- }
-
-+struct fsck_packed_ref_entry {
-+ unsigned long line_number;
-+
-+ struct snapshot_record record;
-+};
-+
-+static struct fsck_packed_ref_entry *create_fsck_packed_ref_entry(unsigned long line_number,
-+ const char *start)
-+{
-+ struct fsck_packed_ref_entry *entry = xcalloc(1, sizeof(*entry));
-+ entry->line_number = line_number;
-+ entry->record.start = start;
-+ return entry;
-+}
-+
-+static void free_fsck_packed_ref_entries(struct fsck_packed_ref_entry **entries, size_t nr)
-+{
-+ for (size_t i = 0; i < nr; i++)
-+ free(entries[i]);
-+ free(entries);
-+}
-+
- static int packed_fsck_ref_next_line(struct fsck_options *o,
- struct strbuf *packed_entry, const char *start,
- const char *eof, const char **eol)
@@ refs/packed-backend.c: static int packed_fsck_ref_next_line(struct fsck_options *o,
}
@@ refs/packed-backend.c: static int packed_fsck_ref_next_line(struct fsck_options
+ const char *start, const char *eol,
+ unsigned int *sorted)
{
-- if (!starts_with(start, "# pack-refs with:")) {
+- if (!starts_with(start, "# pack-refs with: ")) {
+ struct string_list traits = STRING_LIST_INIT_NODUP;
+ char *tmp_line;
+ int ret = 0;
+ char *p;
+
+ tmp_line = xmemdupz(start, eol - start);
-+ if (!skip_prefix(tmp_line, "# pack-refs with:", (const char **)&p)) {
++ if (!skip_prefix(tmp_line, "# pack-refs with: ", (const char **)&p)) {
struct fsck_ref_report report = { 0 };
report.path = "packed-refs.header";
- return fsck_report_ref(o, &report,
- FSCK_MSG_BAD_PACKED_REF_HEADER,
-- "'%.*s' does not start with '# pack-refs with:'",
+- "'%.*s' does not start with '# pack-refs with: '",
- (int)(eol - start), start);
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_HEADER,
-+ "'%.*s' does not start with '# pack-refs with:'",
++ "'%.*s' does not start with '# pack-refs with: '",
+ (int)(eol - start), start);
+ goto cleanup;
}
@@ refs/packed-backend.c: static int packed_fsck_ref_next_line(struct fsck_options
static int packed_fsck_ref_peeled_line(struct fsck_options *o,
@@ refs/packed-backend.c: static int packed_fsck_ref_main_line(struct fsck_options *o,
- return 0;
+ return ret;
}
+static int packed_fsck_ref_sorted(struct fsck_options *o,
+ struct ref_store *ref_store,
-+ struct fsck_packed_ref_entry **entries,
-+ size_t nr)
++ const char *start, const char *eof)
+{
+ size_t hexsz = ref_store->repo->hash_algo->hexsz;
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct strbuf refname1 = STRBUF_INIT;
+ struct strbuf refname2 = STRBUF_INIT;
++ unsigned long line_number = 1;
++ const char *former = NULL;
++ const char *current;
++ const char *eol;
+ int ret = 0;
+
-+ for (size_t i = 1; i < nr; i++) {
-+ const char *r1 = entries[i - 1]->record.start + hexsz + 1;
-+ const char *r2 = entries[i]->record.start + hexsz + 1;
++ if (*start == '#') {
++ eol = memchr(start, '\n', eof - start);
++ start = eol + 1;
++ line_number++;
++ }
++
++ for (; start < eof; line_number++, start = eol + 1) {
++ eol = memchr(start, '\n', eof - start);
++
++ if (*start == '^')
++ continue;
++
++ if (!former) {
++ former = start + hexsz + 1;
++ continue;
++ }
+
-+ if (cmp_packed_refname(r1, r2) >= 0) {
++ current = start + hexsz + 1;
++ if (cmp_packed_refname(former, current) >= 0) {
+ const char *err_fmt =
-+ "refname '%s' is not less than next refname '%s'";
-+ const char *eol;
-+ eol = memchr(entries[i - 1]->record.start, '\n',
-+ entries[i - 1]->record.len);
-+ strbuf_add(&refname1, r1, eol - r1);
-+ eol = memchr(entries[i]->record.start, '\n',
-+ entries[i]->record.len);
-+ strbuf_add(&refname2, r2, eol - r2);
++ "refname '%s' is less than previous refname '%s'";
++
++ eol = memchr(former, '\n', eof - former);
++ strbuf_add(&refname1, former, eol - former);
++ eol = memchr(current, '\n', eof - current);
++ strbuf_add(&refname2, current, eol - current);
+
-+ strbuf_addf(&packed_entry, "packed-refs line %lu",
-+ entries[i - 1]->line_number);
++ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_PACKED_REF_UNSORTED,
-+ err_fmt, refname1.buf, refname2.buf);
++ err_fmt, refname2.buf, refname1.buf);
+ goto cleanup;
+ }
++ former = current;
+ }
+
+cleanup:
@@ refs/packed-backend.c: static int packed_fsck_ref_main_line(struct fsck_options
+
static int packed_fsck_ref_content(struct fsck_options *o,
struct ref_store *ref_store,
++ unsigned int *sorted,
const char *start, const char *eof)
{
- struct strbuf packed_entry = STRBUF_INIT;
-+ struct fsck_packed_ref_entry **entries;
struct strbuf refname = STRBUF_INIT;
- unsigned long line_number = 1;
-+ unsigned int sorted = 0;
-+ size_t entry_alloc = 20;
-+ size_t entry_nr = 0;
- const char *eol;
- int ret = 0;
+@@ refs/packed-backend.c: static int packed_fsck_ref_content(struct fsck_options *o,
- strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
- ret |= packed_fsck_ref_next_line(o, &packed_entry, start, eof, &eol);
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
if (*start == '#') {
- ret |= packed_fsck_ref_header(o, start, eol);
-+ ret |= packed_fsck_ref_header(o, start, eol, &sorted);
++ ret |= packed_fsck_ref_header(o, start, eol, sorted);
start = eol + 1;
line_number++;
- }
+@@ refs/packed-backend.c: static int packed_fsck(struct ref_store *ref_store,
+ struct packed_ref_store *refs = packed_downcast(ref_store,
+ REF_STORE_READ, "fsck");
+ struct strbuf packed_ref_content = STRBUF_INIT;
++ unsigned int sorted = 0;
+ int ret = 0;
+ int fd;
-+ ALLOC_ARRAY(entries, entry_alloc);
- while (start < eof) {
-+ struct fsck_packed_ref_entry *entry
-+ = create_fsck_packed_ref_entry(line_number, start);
-+
-+ ALLOC_GROW(entries, entry_nr + 1, entry_alloc);
-+ entries[entry_nr++] = entry;
- strbuf_reset(&packed_entry);
- strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
- ret |= packed_fsck_ref_next_line(o, &packed_entry, start, eof, &eol);
-@@ refs/packed-backend.c: static int packed_fsck_ref_content(struct fsck_options *o,
- start = eol + 1;
- line_number++;
- }
-+ entry->record.len = start - entry->record.start;
+@@ refs/packed-backend.c: static int packed_fsck(struct ref_store *ref_store,
+ goto cleanup;
}
+- ret = packed_fsck_ref_content(o, ref_store, packed_ref_content.buf,
++ ret = packed_fsck_ref_content(o, ref_store, &sorted, packed_ref_content.buf,
+ packed_ref_content.buf + packed_ref_content.len);
+ if (!ret && sorted)
-+ ret |= packed_fsck_ref_sorted(o, ref_store, entries, entry_nr);
-+
- strbuf_release(&packed_entry);
- strbuf_release(&refname);
- strbuf_release(&packed_entry);
-+ free_fsck_packed_ref_entries(entries, entry_nr);
- return ret;
- }
++ ret = packed_fsck_ref_sorted(o, ref_store, packed_ref_content.buf,
++ packed_ref_content.buf + packed_ref_content.len);
+ cleanup:
+ strbuf_release(&packed_ref_content);
## t/t0602-reffiles-fsck.sh ##
@@ t/t0602-reffiles-fsck.sh: test_expect_success 'packed-refs content should be checked' '
@@ t/t0602-reffiles-fsck.sh: test_expect_success 'packed-refs content should be che
+ refname1="refs/heads/main" &&
+ refname2="refs/heads/foo" &&
+ refname3="refs/tags/foo" &&
-+ printf "# pack-refs with: peeled fully-peeled sorted \n" >.git/packed-refs &&
-+ printf "%s %s\n" "$branch_2_oid" "$refname1" >>.git/packed-refs &&
-+ printf "%s %s\n" "$branch_1_oid" "$refname2" >>.git/packed-refs &&
++
++ cat >.git/packed-refs <<-EOF &&
++ # pack-refs with: peeled fully-peeled sorted
++ EOF
++ git refs verify 2>err &&
++ rm .git/packed-refs &&
++ test_must_be_empty err &&
++
++ cat >.git/packed-refs <<-EOF &&
++ # pack-refs with: peeled fully-peeled sorted
++ $branch_2_oid $refname1
++ EOF
++ git refs verify 2>err &&
++ rm .git/packed-refs &&
++ test_must_be_empty err &&
++
++ cat >.git/packed-refs <<-EOF &&
++ # pack-refs with: peeled fully-peeled sorted
++ $branch_2_oid $refname1
++ $branch_1_oid $refname2
++ $tag_1_oid $refname3
++ EOF
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
-+ error: packed-refs line 2: packedRefUnsorted: refname '\''$refname1'\'' is not less than next refname '\''$refname2'\''
++ error: packed-refs line 3: packedRefUnsorted: refname '\''$refname2'\'' is less than previous refname '\''$refname1'\''
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err &&
+
-+ printf "# pack-refs with: peeled fully-peeled sorted \n" >.git/packed-refs &&
-+ printf "%s %s\n" "$tag_1_oid" "$refname3" >>.git/packed-refs &&
-+ printf "^%s\n" "$tag_1_peeled_oid" >>.git/packed-refs &&
-+ printf "%s %s\n" "$branch_2_oid" "$refname2" >>.git/packed-refs &&
++ cat >.git/packed-refs <<-EOF &&
++ # pack-refs with: peeled fully-peeled sorted
++ $tag_1_oid $refname3
++ ^$tag_1_peeled_oid
++ $branch_2_oid $refname2
++ EOF
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
-+ error: packed-refs line 2: packedRefUnsorted: refname '\''$refname3'\'' is not less than next refname '\''$refname2'\''
++ error: packed-refs line 4: packedRefUnsorted: refname '\''$refname2'\'' is less than previous refname '\''$refname3'\''
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err
@@ t/t0602-reffiles-fsck.sh: test_expect_success 'packed-refs content should be che
+ refname1="refs/heads/main" &&
+ refname2="refs/heads/foo" &&
+ refname3="refs/tags/foo" &&
-+ printf "# pack-refs with: peeled fully-peeled \n" >.git/packed-refs &&
-+ printf "%s %s\n" "$branch_2_oid" "$refname1" >>.git/packed-refs &&
-+ printf "%s %s\n" "$branch_1_oid" "$refname2" >>.git/packed-refs &&
++
++ cat >.git/packed-refs <<-EOF &&
++ # pack-refs with: peeled fully-peeled
++ $branch_2_oid $refname1
++ $branch_1_oid $refname2
++ EOF
+ git refs verify 2>err &&
+ test_must_be_empty err
+ )
8: 4f2170aa7c ! 8: 2dd3437478 builtin/fsck: add `git refs verify` child process
@@ Commit message
"git-fsck(1)" which would implicitly check the consistency of refs to
die the program.
+ Last, update the test to exercise the code.
+
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
@@ Documentation/git-fsck.txt: care about this output and want to speed it up furth
+--[no-]references::
+ Control whether to check the references database consistency
+ via 'git refs verify'. See linkgit:git-refs[1] for details.
++ The default is to check the references database.
+
CONFIGURATION
-------------
@@ builtin/fsck.c: int cmd_fsck(int argc,
if (connectivity_only) {
for_each_loose_object(mark_loose_for_connectivity, NULL, 0);
for_each_packed_object(the_repository,
+
+ ## t/t0602-reffiles-fsck.sh ##
+@@ t/t0602-reffiles-fsck.sh: test_expect_success 'packed-ref without sorted trait should not be checked' '
+ )
+ '
+
++test_expect_success '--[no-]references option should apply to fsck' '
++ test_when_finished "rm -rf repo" &&
++ git init repo &&
++ branch_dir_prefix=.git/refs/heads &&
++ (
++ cd repo &&
++ test_commit default &&
++ for trailing_content in " garbage" " more garbage"
++ do
++ printf "%s" "$(git rev-parse HEAD)$trailing_content" >$branch_dir_prefix/branch-garbage &&
++ git fsck 2>err &&
++ cat >expect <<-EOF &&
++ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
++ EOF
++ rm $branch_dir_prefix/branch-garbage &&
++ test_cmp expect err || return 1
++ done &&
++
++ for trailing_content in " garbage" " more garbage"
++ do
++ printf "%s" "$(git rev-parse HEAD)$trailing_content" >$branch_dir_prefix/branch-garbage &&
++ git fsck --references 2>err &&
++ cat >expect <<-EOF &&
++ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
++ EOF
++ rm $branch_dir_prefix/branch-garbage &&
++ test_cmp expect err || return 1
++ done &&
++
++ for trailing_content in " garbage" " more garbage"
++ do
++ printf "%s" "$(git rev-parse HEAD)$trailing_content" >$branch_dir_prefix/branch-garbage &&
++ git fsck --no-references 2>err &&
++ rm $branch_dir_prefix/branch-garbage &&
++ test_must_be_empty err || return 1
++ done
++ )
++'
++
+ test_done
--
2.48.1
^ permalink raw reply [flat|nested] 168+ messages in thread
* [PATCH v4 1/8] t0602: use subshell to ensure working directory unchanged
2025-02-14 4:50 ` [PATCH v4 0/8] add more ref consistency checks shejialuo
@ 2025-02-14 4:51 ` shejialuo
2025-02-14 4:52 ` [PATCH v4 2/8] builtin/refs: get worktrees without reading head information shejialuo
` (8 subsequent siblings)
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-14 4:51 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
For every test, we would execute the command "cd repo" in the first but
we never execute the command "cd .." to restore the working directory.
However, it's either not a good idea use above way. Because if any test
fails between "cd repo" and "cd ..", the "cd .." will never be reached.
And we cannot correctly restore the working directory.
Let's use subshell to ensure that the current working directory could be
restored to the correct path.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
t/t0602-reffiles-fsck.sh | 967 ++++++++++++++++++++-------------------
1 file changed, 494 insertions(+), 473 deletions(-)
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index d4a08b823b..cf7a202d0d 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -14,222 +14,229 @@ test_expect_success 'ref name should be checked' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
-
- git commit --allow-empty -m initial &&
- git checkout -b default-branch &&
- git tag default-tag &&
- git tag multi_hierarchy/default-tag &&
-
- cp $branch_dir_prefix/default-branch $branch_dir_prefix/@ &&
- git refs verify 2>err &&
- test_must_be_empty err &&
- rm $branch_dir_prefix/@ &&
-
- cp $tag_dir_prefix/default-tag $tag_dir_prefix/tag-1.lock &&
- git refs verify 2>err &&
- rm $tag_dir_prefix/tag-1.lock &&
- test_must_be_empty err &&
-
- cp $tag_dir_prefix/default-tag $tag_dir_prefix/.lock &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/tags/.lock: badRefName: invalid refname format
- EOF
- rm $tag_dir_prefix/.lock &&
- test_cmp expect err &&
-
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname" &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/$refname: badRefName: invalid refname format
- EOF
- rm "$branch_dir_prefix/$refname" &&
- test_cmp expect err || return 1
- done &&
+ (
+ cd repo &&
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- cp $tag_dir_prefix/default-tag "$tag_dir_prefix/$refname" &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/tags/$refname: badRefName: invalid refname format
- EOF
- rm "$tag_dir_prefix/$refname" &&
- test_cmp expect err || return 1
- done &&
+ git commit --allow-empty -m initial &&
+ git checkout -b default-branch &&
+ git tag default-tag &&
+ git tag multi_hierarchy/default-tag &&
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- cp $tag_dir_prefix/multi_hierarchy/default-tag "$tag_dir_prefix/multi_hierarchy/$refname" &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/tags/multi_hierarchy/$refname: badRefName: invalid refname format
- EOF
- rm "$tag_dir_prefix/multi_hierarchy/$refname" &&
- test_cmp expect err || return 1
- done &&
-
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- mkdir "$branch_dir_prefix/$refname" &&
- cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname/default-branch" &&
+ cp $branch_dir_prefix/default-branch $branch_dir_prefix/@ &&
+ git refs verify 2>err &&
+ test_must_be_empty err &&
+ rm $branch_dir_prefix/@ &&
+
+ cp $tag_dir_prefix/default-tag $tag_dir_prefix/tag-1.lock &&
+ git refs verify 2>err &&
+ rm $tag_dir_prefix/tag-1.lock &&
+ test_must_be_empty err &&
+
+ cp $tag_dir_prefix/default-tag $tag_dir_prefix/.lock &&
test_must_fail git refs verify 2>err &&
cat >expect <<-EOF &&
- error: refs/heads/$refname/default-branch: badRefName: invalid refname format
+ error: refs/tags/.lock: badRefName: invalid refname format
EOF
- rm -r "$branch_dir_prefix/$refname" &&
- test_cmp expect err || return 1
- done
+ rm $tag_dir_prefix/.lock &&
+ test_cmp expect err &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/$refname: badRefName: invalid refname format
+ EOF
+ rm "$branch_dir_prefix/$refname" &&
+ test_cmp expect err || return 1
+ done &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ cp $tag_dir_prefix/default-tag "$tag_dir_prefix/$refname" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/tags/$refname: badRefName: invalid refname format
+ EOF
+ rm "$tag_dir_prefix/$refname" &&
+ test_cmp expect err || return 1
+ done &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ cp $tag_dir_prefix/multi_hierarchy/default-tag "$tag_dir_prefix/multi_hierarchy/$refname" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/tags/multi_hierarchy/$refname: badRefName: invalid refname format
+ EOF
+ rm "$tag_dir_prefix/multi_hierarchy/$refname" &&
+ test_cmp expect err || return 1
+ done &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ mkdir "$branch_dir_prefix/$refname" &&
+ cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname/default-branch" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/$refname/default-branch: badRefName: invalid refname format
+ EOF
+ rm -r "$branch_dir_prefix/$refname" &&
+ test_cmp expect err || return 1
+ done
+ )
'
test_expect_success 'ref name check should be adapted into fsck messages' '
test_when_finished "rm -rf repo" &&
git init repo &&
branch_dir_prefix=.git/refs/heads &&
- cd repo &&
- git commit --allow-empty -m initial &&
- git checkout -b branch-1 &&
-
- cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
- git -c fsck.badRefName=warn refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/.branch-1: badRefName: invalid refname format
- EOF
- rm $branch_dir_prefix/.branch-1 &&
- test_cmp expect err &&
-
- cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
- git -c fsck.badRefName=ignore refs verify 2>err &&
- test_must_be_empty err
+ (
+ cd repo &&
+ git commit --allow-empty -m initial &&
+ git checkout -b branch-1 &&
+
+ cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
+ git -c fsck.badRefName=warn refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/.branch-1: badRefName: invalid refname format
+ EOF
+ rm $branch_dir_prefix/.branch-1 &&
+ test_cmp expect err &&
+
+ cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
+ git -c fsck.badRefName=ignore refs verify 2>err &&
+ test_must_be_empty err
+ )
'
test_expect_success 'ref name check should work for multiple worktrees' '
test_when_finished "rm -rf repo" &&
git init repo &&
-
- cd repo &&
- test_commit initial &&
- git checkout -b branch-1 &&
- test_commit second &&
- git checkout -b branch-2 &&
- test_commit third &&
- git checkout -b branch-3 &&
- git worktree add ./worktree-1 branch-1 &&
- git worktree add ./worktree-2 branch-2 &&
- worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
- worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
-
- (
- cd worktree-1 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-3
- ) &&
(
- cd worktree-2 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-3
- ) &&
-
- cp $worktree1_refdir_prefix/branch-4 $worktree1_refdir_prefix/'\'' branch-5'\'' &&
- cp $worktree2_refdir_prefix/branch-4 $worktree2_refdir_prefix/'\''~branch-6'\'' &&
-
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
- error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err &&
-
- for worktree in "worktree-1" "worktree-2"
- do
+ cd repo &&
+ test_commit initial &&
+ git checkout -b branch-1 &&
+ test_commit second &&
+ git checkout -b branch-2 &&
+ test_commit third &&
+ git checkout -b branch-3 &&
+ git worktree add ./worktree-1 branch-1 &&
+ git worktree add ./worktree-2 branch-2 &&
+ worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
+ worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
+
(
- cd $worktree &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
- error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err || return 1
- )
- done
+ cd worktree-1 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-3
+ ) &&
+ (
+ cd worktree-2 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-3
+ ) &&
+
+ cp $worktree1_refdir_prefix/branch-4 $worktree1_refdir_prefix/'\'' branch-5'\'' &&
+ cp $worktree2_refdir_prefix/branch-4 $worktree2_refdir_prefix/'\''~branch-6'\'' &&
+
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
+ error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err &&
+
+ for worktree in "worktree-1" "worktree-2"
+ do
+ (
+ cd $worktree &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
+ error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err || return 1
+ )
+ done
+ )
'
test_expect_success 'regular ref content should be checked (individual)' '
test_when_finished "rm -rf repo" &&
git init repo &&
branch_dir_prefix=.git/refs/heads &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
- git refs verify 2>err &&
- test_must_be_empty err &&
+ git refs verify 2>err &&
+ test_must_be_empty err &&
- for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$branch_dir_prefix/branch-bad &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/branch-bad: badRefContent: $bad_content
- EOF
- rm $branch_dir_prefix/branch-bad &&
- test_cmp expect err || return 1
- done &&
+ for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$branch_dir_prefix/branch-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/branch-bad: badRefContent: $bad_content
+ EOF
+ rm $branch_dir_prefix/branch-bad &&
+ test_cmp expect err || return 1
+ done &&
- for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$branch_dir_prefix/a/b/branch-bad &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/a/b/branch-bad: badRefContent: $bad_content
- EOF
- rm $branch_dir_prefix/a/b/branch-bad &&
- test_cmp expect err || return 1
- done &&
-
- printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- rm $branch_dir_prefix/branch-no-newline &&
- test_cmp expect err &&
-
- for trailing_content in " garbage" " more garbage"
- do
- printf "%s" "$(git rev-parse main)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$branch_dir_prefix/a/b/branch-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/a/b/branch-bad: badRefContent: $bad_content
+ EOF
+ rm $branch_dir_prefix/a/b/branch-bad &&
+ test_cmp expect err || return 1
+ done &&
+
+ printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
EOF
- rm $branch_dir_prefix/branch-garbage &&
- test_cmp expect err || return 1
- done &&
+ rm $branch_dir_prefix/branch-no-newline &&
+ test_cmp expect err &&
- printf "%s\n\n\n" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
+ for trailing_content in " garbage" " more garbage"
+ do
+ printf "%s" "$(git rev-parse main)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage &&
+ test_cmp expect err || return 1
+ done &&
+ printf "%s\n\n\n" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
- '\''
- EOF
- rm $branch_dir_prefix/branch-garbage-special &&
- test_cmp expect err &&
- printf "%s\n\n\n garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
+ '\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage-special &&
+ test_cmp expect err &&
+
+ printf "%s\n\n\n garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
- garbage'\''
- EOF
- rm $branch_dir_prefix/branch-garbage-special &&
- test_cmp expect err
+ garbage'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage-special &&
+ test_cmp expect err
+ )
'
test_expect_success 'regular ref content should be checked (aggregate)' '
@@ -237,99 +244,103 @@ test_expect_success 'regular ref content should be checked (aggregate)' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- bad_content_1=$(git rev-parse main)x &&
- bad_content_2=xfsazqfxcadas &&
- bad_content_3=Xfsazqfxcadas &&
- printf "%s" $bad_content_1 >$tag_dir_prefix/tag-bad-1 &&
- printf "%s" $bad_content_2 >$tag_dir_prefix/tag-bad-2 &&
- printf "%s" $bad_content_3 >$branch_dir_prefix/a/b/branch-bad &&
- printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
- printf "%s garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage &&
-
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/a/b/branch-bad: badRefContent: $bad_content_3
- error: refs/tags/tag-bad-1: badRefContent: $bad_content_1
- error: refs/tags/tag-bad-2: badRefContent: $bad_content_2
- warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
- warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ bad_content_1=$(git rev-parse main)x &&
+ bad_content_2=xfsazqfxcadas &&
+ bad_content_3=Xfsazqfxcadas &&
+ printf "%s" $bad_content_1 >$tag_dir_prefix/tag-bad-1 &&
+ printf "%s" $bad_content_2 >$tag_dir_prefix/tag-bad-2 &&
+ printf "%s" $bad_content_3 >$branch_dir_prefix/a/b/branch-bad &&
+ printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
+ printf "%s garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage &&
+
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/a/b/branch-bad: badRefContent: $bad_content_3
+ error: refs/tags/tag-bad-1: badRefContent: $bad_content_1
+ error: refs/tags/tag-bad-2: badRefContent: $bad_content_2
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
+ warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err
+ )
'
test_expect_success 'textual symref content should be checked (individual)' '
test_when_finished "rm -rf repo" &&
git init repo &&
branch_dir_prefix=.git/refs/heads &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ for good_referent in "refs/heads/branch" "HEAD"
+ do
+ printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
+ git refs verify 2>err &&
+ rm $branch_dir_prefix/branch-good &&
+ test_must_be_empty err || return 1
+ done &&
+
+ for bad_referent in "refs/heads/.branch" "refs/heads/~branch" "refs/heads/?branch"
+ do
+ printf "ref: %s\n" $bad_referent >$branch_dir_prefix/branch-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/branch-bad: badReferentName: points to invalid refname '\''$bad_referent'\''
+ EOF
+ rm $branch_dir_prefix/branch-bad &&
+ test_cmp expect err || return 1
+ done &&
- for good_referent in "refs/heads/branch" "HEAD"
- do
- printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
+ printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline &&
git refs verify 2>err &&
- rm $branch_dir_prefix/branch-good &&
- test_must_be_empty err || return 1
- done &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
+ EOF
+ rm $branch_dir_prefix/branch-no-newline &&
+ test_cmp expect err &&
- for bad_referent in "refs/heads/.branch" "refs/heads/~branch" "refs/heads/?branch"
- do
- printf "ref: %s\n" $bad_referent >$branch_dir_prefix/branch-bad &&
- test_must_fail git refs verify 2>err &&
+ printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
+ EOF
+ rm $branch_dir_prefix/a/b/branch-trailing-1 &&
+ test_cmp expect err &&
+
+ printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- error: refs/heads/branch-bad: badReferentName: points to invalid refname '\''$bad_referent'\''
+ warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
EOF
- rm $branch_dir_prefix/branch-bad &&
- test_cmp expect err || return 1
- done &&
-
- printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- rm $branch_dir_prefix/branch-no-newline &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-trailing-1 &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-trailing-2 &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-trailing-3 &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-complicated &&
- test_cmp expect err
+ rm $branch_dir_prefix/a/b/branch-trailing-2 &&
+ test_cmp expect err &&
+
+ printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
+ EOF
+ rm $branch_dir_prefix/a/b/branch-trailing-3 &&
+ test_cmp expect err &&
+
+ printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
+ EOF
+ rm $branch_dir_prefix/a/b/branch-complicated &&
+ test_cmp expect err
+ )
'
test_expect_success 'textual symref content should be checked (aggregate)' '
@@ -337,32 +348,34 @@ test_expect_success 'textual symref content should be checked (aggregate)' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- printf "ref: refs/heads/branch\n" >$branch_dir_prefix/branch-good &&
- printf "ref: HEAD\n" >$branch_dir_prefix/branch-head &&
- printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline-1 &&
- printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
- printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
- printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
- printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
- printf "ref: refs/heads/.branch\n" >$branch_dir_prefix/branch-bad-1 &&
-
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/branch-bad-1: badReferentName: points to invalid refname '\''refs/heads/.branch'\''
- warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/branch-no-newline-1: refMissingNewline: misses LF at the end
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ printf "ref: refs/heads/branch\n" >$branch_dir_prefix/branch-good &&
+ printf "ref: HEAD\n" >$branch_dir_prefix/branch-head &&
+ printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline-1 &&
+ printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
+ printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
+ printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
+ printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
+ printf "ref: refs/heads/.branch\n" >$branch_dir_prefix/branch-bad-1 &&
+
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/branch-bad-1: badReferentName: points to invalid refname '\''refs/heads/.branch'\''
+ warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/branch-no-newline-1: refMissingNewline: misses LF at the end
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err
+ )
'
test_expect_success 'the target of the textual symref should be checked' '
@@ -370,28 +383,30 @@ test_expect_success 'the target of the textual symref should be checked' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- for good_referent in "refs/heads/branch" "HEAD" "refs/tags/tag"
- do
- printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
- git refs verify 2>err &&
- rm $branch_dir_prefix/branch-good &&
- test_must_be_empty err || return 1
- done &&
-
- for nonref_referent in "refs-back/heads/branch" "refs-back/tags/tag" "reflogs/refs/heads/branch"
- do
- printf "ref: %s\n" $nonref_referent >$branch_dir_prefix/branch-bad-1 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-bad-1: symrefTargetIsNotARef: points to non-ref target '\''$nonref_referent'\''
- EOF
- rm $branch_dir_prefix/branch-bad-1 &&
- test_cmp expect err || return 1
- done
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ for good_referent in "refs/heads/branch" "HEAD" "refs/tags/tag"
+ do
+ printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
+ git refs verify 2>err &&
+ rm $branch_dir_prefix/branch-good &&
+ test_must_be_empty err || return 1
+ done &&
+
+ for nonref_referent in "refs-back/heads/branch" "refs-back/tags/tag" "reflogs/refs/heads/branch"
+ do
+ printf "ref: %s\n" $nonref_referent >$branch_dir_prefix/branch-bad-1 &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-bad-1: symrefTargetIsNotARef: points to non-ref target '\''$nonref_referent'\''
+ EOF
+ rm $branch_dir_prefix/branch-bad-1 &&
+ test_cmp expect err || return 1
+ done
+ )
'
test_expect_success SYMLINKS 'symlink symref content should be checked' '
@@ -399,201 +414,207 @@ test_expect_success SYMLINKS 'symlink symref content should be checked' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- ln -sf ./main $branch_dir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $branch_dir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../logs/branch-escape $branch_dir_prefix/branch-symbolic &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
- warning: refs/heads/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
- EOF
- rm $branch_dir_prefix/branch-symbolic &&
- test_cmp expect err &&
-
- ln -sf ./"branch " $branch_dir_prefix/branch-symbolic-bad &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic-bad: symlinkRef: use deprecated symbolic link for symref
- error: refs/heads/branch-symbolic-bad: badReferentName: points to invalid refname '\''refs/heads/branch '\''
- EOF
- rm $branch_dir_prefix/branch-symbolic-bad &&
- test_cmp expect err &&
-
- ln -sf ./".tag" $tag_dir_prefix/tag-symbolic-1 &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/tags/tag-symbolic-1: symlinkRef: use deprecated symbolic link for symref
- error: refs/tags/tag-symbolic-1: badReferentName: points to invalid refname '\''refs/tags/.tag'\''
- EOF
- rm $tag_dir_prefix/tag-symbolic-1 &&
- test_cmp expect err
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ ln -sf ./main $branch_dir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
+ EOF
+ rm $branch_dir_prefix/branch-symbolic-good &&
+ test_cmp expect err &&
+
+ ln -sf ../../logs/branch-escape $branch_dir_prefix/branch-symbolic &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
+ warning: refs/heads/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
+ EOF
+ rm $branch_dir_prefix/branch-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ./"branch " $branch_dir_prefix/branch-symbolic-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-symbolic-bad: symlinkRef: use deprecated symbolic link for symref
+ error: refs/heads/branch-symbolic-bad: badReferentName: points to invalid refname '\''refs/heads/branch '\''
+ EOF
+ rm $branch_dir_prefix/branch-symbolic-bad &&
+ test_cmp expect err &&
+
+ ln -sf ./".tag" $tag_dir_prefix/tag-symbolic-1 &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/tags/tag-symbolic-1: symlinkRef: use deprecated symbolic link for symref
+ error: refs/tags/tag-symbolic-1: badReferentName: points to invalid refname '\''refs/tags/.tag'\''
+ EOF
+ rm $tag_dir_prefix/tag-symbolic-1 &&
+ test_cmp expect err
+ )
'
test_expect_success SYMLINKS 'symlink symref content should be checked (worktree)' '
test_when_finished "rm -rf repo" &&
git init repo &&
- cd repo &&
- test_commit default &&
- git branch branch-1 &&
- git branch branch-2 &&
- git branch branch-3 &&
- git worktree add ./worktree-1 branch-2 &&
- git worktree add ./worktree-2 branch-3 &&
- main_worktree_refdir_prefix=.git/refs/heads &&
- worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
- worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
-
(
- cd worktree-1 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
- (
- cd worktree-2 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
-
- ln -sf ../../../../refs/heads/good-branch $worktree1_refdir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $worktree1_refdir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../../../worktrees/worktree-1/good-branch $worktree2_refdir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-2/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $worktree2_refdir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../worktrees/worktree-2/good-branch $main_worktree_refdir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $main_worktree_refdir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../../../logs/branch-escape $worktree1_refdir_prefix/branch-symbolic &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
- warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
- EOF
- rm $worktree1_refdir_prefix/branch-symbolic &&
- test_cmp expect err &&
-
- for bad_referent_name in ".tag" "branch "
- do
- ln -sf ./"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git branch branch-3 &&
+ git worktree add ./worktree-1 branch-2 &&
+ git worktree add ./worktree-2 branch-3 &&
+ main_worktree_refdir_prefix=.git/refs/heads &&
+ worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
+ worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
+
+ (
+ cd worktree-1 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+ (
+ cd worktree-2 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+
+ ln -sf ../../../../refs/heads/good-branch $worktree1_refdir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-1/refs/worktree/$bad_referent_name'\''
+ warning: worktrees/worktree-1/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
EOF
- rm $worktree1_refdir_prefix/bad-symbolic &&
+ rm $worktree1_refdir_prefix/branch-symbolic-good &&
test_cmp expect err &&
- ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ ln -sf ../../../../worktrees/worktree-1/good-branch $worktree2_refdir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ warning: worktrees/worktree-2/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
EOF
- rm $worktree1_refdir_prefix/bad-symbolic &&
+ rm $worktree2_refdir_prefix/branch-symbolic-good &&
test_cmp expect err &&
- ln -sf ./"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ ln -sf ../../worktrees/worktree-2/good-branch $main_worktree_refdir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-2/refs/worktree/$bad_referent_name'\''
+ warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
EOF
- rm $worktree2_refdir_prefix/bad-symbolic &&
+ rm $main_worktree_refdir_prefix/branch-symbolic-good &&
test_cmp expect err &&
- ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ ln -sf ../../../../logs/branch-escape $worktree1_refdir_prefix/branch-symbolic &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
+ warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
EOF
- rm $worktree2_refdir_prefix/bad-symbolic &&
- test_cmp expect err || return 1
- done
+ rm $worktree1_refdir_prefix/branch-symbolic &&
+ test_cmp expect err &&
+
+ for bad_referent_name in ".tag" "branch "
+ do
+ ln -sf ./"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-1/refs/worktree/$bad_referent_name'\''
+ EOF
+ rm $worktree1_refdir_prefix/bad-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ EOF
+ rm $worktree1_refdir_prefix/bad-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ./"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-2/refs/worktree/$bad_referent_name'\''
+ EOF
+ rm $worktree2_refdir_prefix/bad-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ EOF
+ rm $worktree2_refdir_prefix/bad-symbolic &&
+ test_cmp expect err || return 1
+ done
+ )
'
test_expect_success 'ref content checks should work with worktrees' '
test_when_finished "rm -rf repo" &&
git init repo &&
- cd repo &&
- test_commit default &&
- git branch branch-1 &&
- git branch branch-2 &&
- git branch branch-3 &&
- git worktree add ./worktree-1 branch-2 &&
- git worktree add ./worktree-2 branch-3 &&
- worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
- worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
-
(
- cd worktree-1 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
- (
- cd worktree-2 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git branch branch-3 &&
+ git worktree add ./worktree-1 branch-2 &&
+ git worktree add ./worktree-2 branch-3 &&
+ worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
+ worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
- for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$worktree1_refdir_prefix/bad-branch-1 &&
- test_must_fail git refs verify 2>err &&
+ (
+ cd worktree-1 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+ (
+ cd worktree-2 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+
+ for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$worktree1_refdir_prefix/bad-branch-1 &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-1/refs/worktree/bad-branch-1: badRefContent: $bad_content
+ EOF
+ rm $worktree1_refdir_prefix/bad-branch-1 &&
+ test_cmp expect err || return 1
+ done &&
+
+ for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$worktree2_refdir_prefix/bad-branch-2 &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-2/refs/worktree/bad-branch-2: badRefContent: $bad_content
+ EOF
+ rm $worktree2_refdir_prefix/bad-branch-2 &&
+ test_cmp expect err || return 1
+ done &&
+
+ printf "%s" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-no-newline &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- error: worktrees/worktree-1/refs/worktree/bad-branch-1: badRefContent: $bad_content
+ warning: worktrees/worktree-1/refs/worktree/branch-no-newline: refMissingNewline: misses LF at the end
EOF
- rm $worktree1_refdir_prefix/bad-branch-1 &&
- test_cmp expect err || return 1
- done &&
+ rm $worktree1_refdir_prefix/branch-no-newline &&
+ test_cmp expect err &&
- for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$worktree2_refdir_prefix/bad-branch-2 &&
- test_must_fail git refs verify 2>err &&
+ printf "%s garbage" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-garbage &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- error: worktrees/worktree-2/refs/worktree/bad-branch-2: badRefContent: $bad_content
+ warning: worktrees/worktree-1/refs/worktree/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
EOF
- rm $worktree2_refdir_prefix/bad-branch-2 &&
- test_cmp expect err || return 1
- done &&
-
- printf "%s" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-no-newline &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- rm $worktree1_refdir_prefix/branch-no-newline &&
- test_cmp expect err &&
-
- printf "%s garbage" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-garbage &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
- EOF
- rm $worktree1_refdir_prefix/branch-garbage &&
- test_cmp expect err
+ rm $worktree1_refdir_prefix/branch-garbage &&
+ test_cmp expect err
+ )
'
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v4 2/8] builtin/refs: get worktrees without reading head information
2025-02-14 4:50 ` [PATCH v4 0/8] add more ref consistency checks shejialuo
2025-02-14 4:51 ` [PATCH v4 1/8] t0602: use subshell to ensure working directory unchanged shejialuo
@ 2025-02-14 4:52 ` shejialuo
2025-02-14 9:19 ` Karthik Nayak
2025-02-14 4:52 ` [PATCH v4 3/8] packed-backend: check whether the "packed-refs" is regular file shejialuo
` (7 subsequent siblings)
9 siblings, 1 reply; 168+ messages in thread
From: shejialuo @ 2025-02-14 4:52 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
In "packed-backend.c", there are some functions such as "create_snapshot"
and "next_record" which would check the correctness of the content of
the "packed-ref" file. When anything is bad, the program will die.
It may seem that we have nothing relevant to above feature, because we
are going to read and parse the raw "packed-ref" file without creating
the snapshot and using the ref iterator to check the consistency.
However, when using "get_worktrees" in "builtin/refs", we would parse
the "HEAD" information. If the referent of the "HEAD" is inside the
"packed-ref", we will call "create_snapshot" function to parse the
"packed-ref" to get the information. No matter whether the entry of
"HEAD" in "packed-ref" is correct, "create_snapshot" would call
"verify_buffer_safe" to check whether there is a newline in the last
line of the file. If not, the program will die.
Although this behavior has no harm for the program, it will
short-circuit the program. When the users execute "git refs verify" or
"git fsck", we should avoid reading the head information, which may
execute the read operation in packed backend with stricter checks to die
the program. Instead, we should continue to check other parts of the
"packed-refs" file completely.
Fortunately, in 465a22b338 (worktree: skip reading HEAD when repairing
worktrees, 2023-12-29), we have introduced a function
"get_worktrees_internal" which allows us to get worktrees without
reading head information.
Create a new exposed function "get_worktrees_without_reading_head", then
replace the "get_worktrees" in "builtin/refs" with the new created
function.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
builtin/refs.c | 2 +-
worktree.c | 5 +++++
worktree.h | 6 ++++++
3 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/builtin/refs.c b/builtin/refs.c
index a29f195834..55ff5dae11 100644
--- a/builtin/refs.c
+++ b/builtin/refs.c
@@ -88,7 +88,7 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix,
git_config(git_fsck_config, &fsck_refs_options);
prepare_repo_settings(the_repository);
- worktrees = get_worktrees();
+ worktrees = get_worktrees_without_reading_head();
for (size_t i = 0; worktrees[i]; i++)
ret |= refs_fsck(get_worktree_ref_store(worktrees[i]),
&fsck_refs_options, worktrees[i]);
diff --git a/worktree.c b/worktree.c
index 248bbb39d4..89b7d86cef 100644
--- a/worktree.c
+++ b/worktree.c
@@ -175,6 +175,11 @@ struct worktree **get_worktrees(void)
return get_worktrees_internal(0);
}
+struct worktree **get_worktrees_without_reading_head(void)
+{
+ return get_worktrees_internal(1);
+}
+
const char *get_worktree_git_dir(const struct worktree *wt)
{
if (!wt)
diff --git a/worktree.h b/worktree.h
index 38145df80f..1ba4a161a0 100644
--- a/worktree.h
+++ b/worktree.h
@@ -30,6 +30,12 @@ struct worktree {
*/
struct worktree **get_worktrees(void);
+/*
+ * Like `get_worktrees`, but does not read HEAD. This is useful when checking
+ * the consistency, as reading HEAD may not be necessary.
+ */
+struct worktree **get_worktrees_without_reading_head(void);
+
/*
* Returns 1 if linked worktrees exist, 0 otherwise.
*/
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* Re: [PATCH v4 2/8] builtin/refs: get worktrees without reading head information
2025-02-14 4:52 ` [PATCH v4 2/8] builtin/refs: get worktrees without reading head information shejialuo
@ 2025-02-14 9:19 ` Karthik Nayak
2025-02-14 12:20 ` shejialuo
0 siblings, 1 reply; 168+ messages in thread
From: Karthik Nayak @ 2025-02-14 9:19 UTC (permalink / raw)
To: shejialuo, git; +Cc: Patrick Steinhardt, Junio C Hamano, Michael Haggerty
[-- Attachment #1: Type: text/plain, Size: 3999 bytes --]
shejialuo <shejialuo@gmail.com> writes:
> In "packed-backend.c", there are some functions such as "create_snapshot"
> and "next_record" which would check the correctness of the content of
> the "packed-ref" file. When anything is bad, the program will die.
>
> It may seem that we have nothing relevant to above feature, because we
> are going to read and parse the raw "packed-ref" file without creating
> the snapshot and using the ref iterator to check the consistency.
>
> However, when using "get_worktrees" in "builtin/refs", we would parse
> the "HEAD" information. If the referent of the "HEAD" is inside the
> "packed-ref", we will call "create_snapshot" function to parse the
> "packed-ref" to get the information. No matter whether the entry of
> "HEAD" in "packed-ref" is correct, "create_snapshot" would call
> "verify_buffer_safe" to check whether there is a newline in the last
> line of the file. If not, the program will die.
>
Nit: while the second paragraph above makes sense in the context of what
we're trying to achieve in this patch series. It doesn't make much sense
for this patch in isolation. Perhaps we want to give some more context
around what we're trying to solve for in the upcoming patches and hence
how it hinders that.
> Although this behavior has no harm for the program, it will
> short-circuit the program. When the users execute "git refs verify" or
> "git fsck", we should avoid reading the head information, which may
> execute the read operation in packed backend with stricter checks to die
> the program. Instead, we should continue to check other parts of the
> "packed-refs" file completely.
>
> Fortunately, in 465a22b338 (worktree: skip reading HEAD when repairing
> worktrees, 2023-12-29), we have introduced a function
> "get_worktrees_internal" which allows us to get worktrees without
> reading head information.
>
> Create a new exposed function "get_worktrees_without_reading_head", then
> replace the "get_worktrees" in "builtin/refs" with the new created
> function.
>
> Mentored-by: Patrick Steinhardt <ps@pks.im>
> Mentored-by: Karthik Nayak <karthik.188@gmail.com>
> Signed-off-by: shejialuo <shejialuo@gmail.com>
> ---
> builtin/refs.c | 2 +-
> worktree.c | 5 +++++
> worktree.h | 6 ++++++
> 3 files changed, 12 insertions(+), 1 deletion(-)
>
> diff --git a/builtin/refs.c b/builtin/refs.c
> index a29f195834..55ff5dae11 100644
> --- a/builtin/refs.c
> +++ b/builtin/refs.c
> @@ -88,7 +88,7 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix,
> git_config(git_fsck_config, &fsck_refs_options);
> prepare_repo_settings(the_repository);
>
> - worktrees = get_worktrees();
> + worktrees = get_worktrees_without_reading_head();
> for (size_t i = 0; worktrees[i]; i++)
> ret |= refs_fsck(get_worktree_ref_store(worktrees[i]),
> &fsck_refs_options, worktrees[i]);
> diff --git a/worktree.c b/worktree.c
> index 248bbb39d4..89b7d86cef 100644
> --- a/worktree.c
> +++ b/worktree.c
> @@ -175,6 +175,11 @@ struct worktree **get_worktrees(void)
> return get_worktrees_internal(0);
> }
>
> +struct worktree **get_worktrees_without_reading_head(void)
> +{
> + return get_worktrees_internal(1);
> +}
> +
> const char *get_worktree_git_dir(const struct worktree *wt)
> {
> if (!wt)
> diff --git a/worktree.h b/worktree.h
> index 38145df80f..1ba4a161a0 100644
> --- a/worktree.h
> +++ b/worktree.h
> @@ -30,6 +30,12 @@ struct worktree {
> */
> struct worktree **get_worktrees(void);
>
> +/*
> + * Like `get_worktrees`, but does not read HEAD. This is useful when checking
> + * the consistency, as reading HEAD may not be necessary.
Checking what consistency? We should be a bit more verbose here. You can
mention that skipping HEAD allows to get the worktree without worrying
about failures pertaining to parsing the HEAD ref.
> + */
> +struct worktree **get_worktrees_without_reading_head(void);
> +
> /*
> * Returns 1 if linked worktrees exist, 0 otherwise.
> */
> --
> 2.48.1
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 168+ messages in thread
* Re: [PATCH v4 2/8] builtin/refs: get worktrees without reading head information
2025-02-14 9:19 ` Karthik Nayak
@ 2025-02-14 12:20 ` shejialuo
0 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-14 12:20 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git, Patrick Steinhardt, Junio C Hamano, Michael Haggerty
On Fri, Feb 14, 2025 at 01:19:53AM -0800, Karthik Nayak wrote:
> shejialuo <shejialuo@gmail.com> writes:
>
> > In "packed-backend.c", there are some functions such as "create_snapshot"
> > and "next_record" which would check the correctness of the content of
> > the "packed-ref" file. When anything is bad, the program will die.
> >
> > It may seem that we have nothing relevant to above feature, because we
> > are going to read and parse the raw "packed-ref" file without creating
> > the snapshot and using the ref iterator to check the consistency.
> >
> > However, when using "get_worktrees" in "builtin/refs", we would parse
> > the "HEAD" information. If the referent of the "HEAD" is inside the
> > "packed-ref", we will call "create_snapshot" function to parse the
> > "packed-ref" to get the information. No matter whether the entry of
> > "HEAD" in "packed-ref" is correct, "create_snapshot" would call
> > "verify_buffer_safe" to check whether there is a newline in the last
> > line of the file. If not, the program will die.
> >
>
> Nit: while the second paragraph above makes sense in the context of what
> we're trying to achieve in this patch series. It doesn't make much sense
> for this patch in isolation. Perhaps we want to give some more context
> around what we're trying to solve for in the upcoming patches and hence
> how it hinders that.
>
Indeed, I think we should add this paragraph. We need to tell the
context about the motivation.
> > Although this behavior has no harm for the program, it will
> > short-circuit the program. When the users execute "git refs verify" or
> > "git fsck", we should avoid reading the head information, which may
> > execute the read operation in packed backend with stricter checks to die
> > the program. Instead, we should continue to check other parts of the
> > "packed-refs" file completely.
> >
> > Fortunately, in 465a22b338 (worktree: skip reading HEAD when repairing
> > worktrees, 2023-12-29), we have introduced a function
> > "get_worktrees_internal" which allows us to get worktrees without
> > reading head information.
> >
> > Create a new exposed function "get_worktrees_without_reading_head", then
> > replace the "get_worktrees" in "builtin/refs" with the new created
> > function.
> >
> > Mentored-by: Patrick Steinhardt <ps@pks.im>
> > Mentored-by: Karthik Nayak <karthik.188@gmail.com>
> > Signed-off-by: shejialuo <shejialuo@gmail.com>
> > ---
> > builtin/refs.c | 2 +-
> > worktree.c | 5 +++++
> > worktree.h | 6 ++++++
> > 3 files changed, 12 insertions(+), 1 deletion(-)
> >
> > diff --git a/builtin/refs.c b/builtin/refs.c
> > index a29f195834..55ff5dae11 100644
> > --- a/builtin/refs.c
> > +++ b/builtin/refs.c
> > @@ -88,7 +88,7 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix,
> > git_config(git_fsck_config, &fsck_refs_options);
> > prepare_repo_settings(the_repository);
> >
> > - worktrees = get_worktrees();
> > + worktrees = get_worktrees_without_reading_head();
> > for (size_t i = 0; worktrees[i]; i++)
> > ret |= refs_fsck(get_worktree_ref_store(worktrees[i]),
> > &fsck_refs_options, worktrees[i]);
> > diff --git a/worktree.c b/worktree.c
> > index 248bbb39d4..89b7d86cef 100644
> > --- a/worktree.c
> > +++ b/worktree.c
> > @@ -175,6 +175,11 @@ struct worktree **get_worktrees(void)
> > return get_worktrees_internal(0);
> > }
> >
> > +struct worktree **get_worktrees_without_reading_head(void)
> > +{
> > + return get_worktrees_internal(1);
> > +}
> > +
> > const char *get_worktree_git_dir(const struct worktree *wt)
> > {
> > if (!wt)
> > diff --git a/worktree.h b/worktree.h
> > index 38145df80f..1ba4a161a0 100644
> > --- a/worktree.h
> > +++ b/worktree.h
> > @@ -30,6 +30,12 @@ struct worktree {
> > */
> > struct worktree **get_worktrees(void);
> >
> > +/*
> > + * Like `get_worktrees`, but does not read HEAD. This is useful when checking
> > + * the consistency, as reading HEAD may not be necessary.
>
> Checking what consistency? We should be a bit more verbose here. You can
> mention that skipping HEAD allows to get the worktree without worrying
> about failures pertaining to parsing the HEAD ref.
>
Good idea, I will improve this in the next version.
> > + */
> > +struct worktree **get_worktrees_without_reading_head(void);
> > +
> > /*
> > * Returns 1 if linked worktrees exist, 0 otherwise.
> > */
> > --
> > 2.48.1
^ permalink raw reply [flat|nested] 168+ messages in thread
* [PATCH v4 3/8] packed-backend: check whether the "packed-refs" is regular file
2025-02-14 4:50 ` [PATCH v4 0/8] add more ref consistency checks shejialuo
2025-02-14 4:51 ` [PATCH v4 1/8] t0602: use subshell to ensure working directory unchanged shejialuo
2025-02-14 4:52 ` [PATCH v4 2/8] builtin/refs: get worktrees without reading head information shejialuo
@ 2025-02-14 4:52 ` shejialuo
2025-02-14 9:50 ` Karthik Nayak
2025-02-14 4:52 ` [PATCH v4 4/8] packed-backend: add "packed-refs" header consistency check shejialuo
` (6 subsequent siblings)
9 siblings, 1 reply; 168+ messages in thread
From: shejialuo @ 2025-02-14 4:52 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
Although "git-fsck(1)" and "packed-backend.c" will check some
consistency and correctness of "packed-refs" file, they never check the
filetype of the "packed-refs". The user should always use "git
pack-refs" command to create the raw regular "packed-refs" file, so we
need to explicitly check this in "git refs verify".
We could use "open_nofollow" wrapper to open the raw "packed-refs" file.
If the returned "fd" value is less than 0, we could check whether the
"errno" is "ELOOP" to report an error to the user.
Reuse "FSCK_MSG_BAD_REF_FILETYPE" fsck message id to report the error to
the user if "packed-refs" is not a regular file.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
refs/packed-backend.c | 39 +++++++++++++++++++++++++++++++++++----
t/t0602-reffiles-fsck.sh | 22 ++++++++++++++++++++++
2 files changed, 57 insertions(+), 4 deletions(-)
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index a7b6f74b6e..6401cecd5f 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -4,6 +4,7 @@
#include "../git-compat-util.h"
#include "../config.h"
#include "../dir.h"
+#include "../fsck.h"
#include "../gettext.h"
#include "../hash.h"
#include "../hex.h"
@@ -1748,15 +1749,45 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
return empty_ref_iterator_begin();
}
-static int packed_fsck(struct ref_store *ref_store UNUSED,
- struct fsck_options *o UNUSED,
+static int packed_fsck(struct ref_store *ref_store,
+ struct fsck_options *o,
struct worktree *wt)
{
+ struct packed_ref_store *refs = packed_downcast(ref_store,
+ REF_STORE_READ, "fsck");
+ int ret = 0;
+ int fd;
if (!is_main_worktree(wt))
- return 0;
+ goto cleanup;
- return 0;
+ if (o->verbose)
+ fprintf_ln(stderr, "Checking packed-refs file %s", refs->path);
+
+ fd = open_nofollow(refs->path, O_RDONLY);
+ if (fd < 0) {
+ /*
+ * If the packed-refs file doesn't exist, there's nothing
+ * to check.
+ */
+ if (errno == ENOENT)
+ goto cleanup;
+
+ if (errno == ELOOP) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs";
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_FILETYPE,
+ "not a regular file");
+ goto cleanup;
+ }
+
+ ret = error_errno(_("unable to open %s"), refs->path);
+ goto cleanup;
+ }
+
+cleanup:
+ return ret;
}
struct ref_storage_be refs_be_packed = {
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index cf7a202d0d..42c8d4ca1e 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -617,4 +617,26 @@ test_expect_success 'ref content checks should work with worktrees' '
)
'
+test_expect_success SYMLINKS 'the filetype of packed-refs should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git branch branch-3 &&
+ git pack-refs --all &&
+
+ mv .git/packed-refs .git/packed-refs-back &&
+ ln -sf packed-refs-bak .git/packed-refs &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs: badRefFiletype: not a regular file
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* Re: [PATCH v4 3/8] packed-backend: check whether the "packed-refs" is regular file
2025-02-14 4:52 ` [PATCH v4 3/8] packed-backend: check whether the "packed-refs" is regular file shejialuo
@ 2025-02-14 9:50 ` Karthik Nayak
2025-02-14 12:37 ` shejialuo
0 siblings, 1 reply; 168+ messages in thread
From: Karthik Nayak @ 2025-02-14 9:50 UTC (permalink / raw)
To: shejialuo, git; +Cc: Patrick Steinhardt, Junio C Hamano, Michael Haggerty
[-- Attachment #1: Type: text/plain, Size: 4916 bytes --]
shejialuo <shejialuo@gmail.com> writes:
> Although "git-fsck(1)" and "packed-backend.c" will check some
> consistency and correctness of "packed-refs" file, they never check the
Because you say 'some' here, it made me more curious. Could you state
exactly what checks are being done here?
> filetype of the "packed-refs". The user should always use "git
> pack-refs" command to create the raw regular "packed-refs" file, so we
> need to explicitly check this in "git refs verify".
>
Not sure I understand how the start of this last sentence correlates to
the end of it. Is the intention to say that we want to explicitly check
the filetype to ensure that the 'packed-refs' file was only created via
'git pack-refs'? If so, perhaps:
Verify that the 'packed-refs' file has the expected filetype,
confirming it was created by 'git pack-refs'.
> We could use "open_nofollow" wrapper to open the raw "packed-refs" file.
> If the returned "fd" value is less than 0, we could check whether the
> "errno" is "ELOOP" to report an error to the user.
>
> Reuse "FSCK_MSG_BAD_REF_FILETYPE" fsck message id to report the error to
> the user if "packed-refs" is not a regular file.
>
> Mentored-by: Patrick Steinhardt <ps@pks.im>
> Mentored-by: Karthik Nayak <karthik.188@gmail.com>
> Signed-off-by: shejialuo <shejialuo@gmail.com>
> ---
> refs/packed-backend.c | 39 +++++++++++++++++++++++++++++++++++----
> t/t0602-reffiles-fsck.sh | 22 ++++++++++++++++++++++
> 2 files changed, 57 insertions(+), 4 deletions(-)
>
> diff --git a/refs/packed-backend.c b/refs/packed-backend.c
> index a7b6f74b6e..6401cecd5f 100644
> --- a/refs/packed-backend.c
> +++ b/refs/packed-backend.c
> @@ -4,6 +4,7 @@
> #include "../git-compat-util.h"
> #include "../config.h"
> #include "../dir.h"
> +#include "../fsck.h"
> #include "../gettext.h"
> #include "../hash.h"
> #include "../hex.h"
> @@ -1748,15 +1749,45 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
> return empty_ref_iterator_begin();
> }
>
> -static int packed_fsck(struct ref_store *ref_store UNUSED,
> - struct fsck_options *o UNUSED,
> +static int packed_fsck(struct ref_store *ref_store,
> + struct fsck_options *o,
> struct worktree *wt)
> {
> + struct packed_ref_store *refs = packed_downcast(ref_store,
> + REF_STORE_READ, "fsck");
> + int ret = 0;
> + int fd;
>
> if (!is_main_worktree(wt))
> - return 0;
> + goto cleanup;
>
> - return 0;
> + if (o->verbose)
> + fprintf_ln(stderr, "Checking packed-refs file %s", refs->path);
> +
> + fd = open_nofollow(refs->path, O_RDONLY);
> + if (fd < 0) {
> + /*
> + * If the packed-refs file doesn't exist, there's nothing
> + * to check.
> + */
> + if (errno == ENOENT)
> + goto cleanup;
> +
> + if (errno == ELOOP) {
> + struct fsck_ref_report report = { 0 };
> + report.path = "packed-refs";
> + ret = fsck_report_ref(o, &report,
> + FSCK_MSG_BAD_REF_FILETYPE,
> + "not a regular file");
> + goto cleanup;
> + }
> +
> + ret = error_errno(_("unable to open %s"), refs->path);
> + goto cleanup;
The paragraph in the commit message:
Reuse "FSCK_MSG_BAD_REF_FILETYPE" fsck message id to report the error to
the user if "packed-refs" is not a regular file.
Gave me the indication that any error would be reported via
'fsck_report_ref()', but it seems like we are only reporting for
symbolic links. Why is that being singled out?
> + }
> +
> +cleanup:
> + return ret;
> }
>
> struct ref_storage_be refs_be_packed = {
> diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
> index cf7a202d0d..42c8d4ca1e 100755
> --- a/t/t0602-reffiles-fsck.sh
> +++ b/t/t0602-reffiles-fsck.sh
> @@ -617,4 +617,26 @@ test_expect_success 'ref content checks should work with worktrees' '
> )
> '
>
> +test_expect_success SYMLINKS 'the filetype of packed-refs should be checked' '
> + test_when_finished "rm -rf repo" &&
> + git init repo &&
> + (
> + cd repo &&
> + test_commit default &&
> + git branch branch-1 &&
> + git branch branch-2 &&
> + git branch branch-3 &&
> + git pack-refs --all &&
> +
> + mv .git/packed-refs .git/packed-refs-back &&
> + ln -sf packed-refs-bak .git/packed-refs &&
This still doesn't make sense to me. 'packed-refs-bak' doesn't exist, is
the intention to symlink '.git/packed-refs' -> something which doesn't
exist?
In that case why even make the effort to build a packed-refs file, could
we simply do 'ln -sf packed-refs-bak .git/packed-refs' in an empty repo?
If not, then 'packed-refs-bak' is definitely a typo and needs to be made
'packed-refs-back' which would go in hand with how we setup the test...
> + test_must_fail git refs verify 2>err &&
> + cat >expect <<-EOF &&
> + error: packed-refs: badRefFiletype: not a regular file
> + EOF
> + rm .git/packed-refs &&
> + test_cmp expect err
> + )
> +'
> +
> test_done
> --
> 2.48.1
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 168+ messages in thread
* Re: [PATCH v4 3/8] packed-backend: check whether the "packed-refs" is regular file
2025-02-14 9:50 ` Karthik Nayak
@ 2025-02-14 12:37 ` shejialuo
0 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-14 12:37 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git, Patrick Steinhardt, Junio C Hamano, Michael Haggerty
On Fri, Feb 14, 2025 at 01:50:26AM -0800, Karthik Nayak wrote:
> shejialuo <shejialuo@gmail.com> writes:
>
> > Although "git-fsck(1)" and "packed-backend.c" will check some
> > consistency and correctness of "packed-refs" file, they never check the
>
> Because you say 'some' here, it made me more curious. Could you state
> exactly what checks are being done here?
>
Well, I don't think we need to elaborate on this at now for the
following two reasons:
1. We will explain this in the later patches.
2. Here I just want to emphasis that it does not check the filetype.
> > filetype of the "packed-refs". The user should always use "git
> > pack-refs" command to create the raw regular "packed-refs" file, so we
> > need to explicitly check this in "git refs verify".
> >
>
> Not sure I understand how the start of this last sentence correlates to
> the end of it. Is the intention to say that we want to explicitly check
> the filetype to ensure that the 'packed-refs' file was only created via
> 'git pack-refs'? If so, perhaps:
>
> Verify that the 'packed-refs' file has the expected filetype,
> confirming it was created by 'git pack-refs'.
>
Thanks for the suggestion, I will improve this in the next version.
> > We could use "open_nofollow" wrapper to open the raw "packed-refs" file.
> > If the returned "fd" value is less than 0, we could check whether the
> > "errno" is "ELOOP" to report an error to the user.
> >
> > Reuse "FSCK_MSG_BAD_REF_FILETYPE" fsck message id to report the error to
> > the user if "packed-refs" is not a regular file.
> >
> > Mentored-by: Patrick Steinhardt <ps@pks.im>
> > Mentored-by: Karthik Nayak <karthik.188@gmail.com>
> > Signed-off-by: shejialuo <shejialuo@gmail.com>
> > ---
> > refs/packed-backend.c | 39 +++++++++++++++++++++++++++++++++++----
> > t/t0602-reffiles-fsck.sh | 22 ++++++++++++++++++++++
> > 2 files changed, 57 insertions(+), 4 deletions(-)
> >
> > diff --git a/refs/packed-backend.c b/refs/packed-backend.c
> > index a7b6f74b6e..6401cecd5f 100644
> > --- a/refs/packed-backend.c
> > +++ b/refs/packed-backend.c
> > @@ -4,6 +4,7 @@
> > #include "../git-compat-util.h"
> > #include "../config.h"
> > #include "../dir.h"
> > +#include "../fsck.h"
> > #include "../gettext.h"
> > #include "../hash.h"
> > #include "../hex.h"
> > @@ -1748,15 +1749,45 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
> > return empty_ref_iterator_begin();
> > }
> >
> > -static int packed_fsck(struct ref_store *ref_store UNUSED,
> > - struct fsck_options *o UNUSED,
> > +static int packed_fsck(struct ref_store *ref_store,
> > + struct fsck_options *o,
> > struct worktree *wt)
> > {
> > + struct packed_ref_store *refs = packed_downcast(ref_store,
> > + REF_STORE_READ, "fsck");
> > + int ret = 0;
> > + int fd;
> >
> > if (!is_main_worktree(wt))
> > - return 0;
> > + goto cleanup;
> >
> > - return 0;
> > + if (o->verbose)
> > + fprintf_ln(stderr, "Checking packed-refs file %s", refs->path);
> > +
> > + fd = open_nofollow(refs->path, O_RDONLY);
> > + if (fd < 0) {
> > + /*
> > + * If the packed-refs file doesn't exist, there's nothing
> > + * to check.
> > + */
> > + if (errno == ENOENT)
> > + goto cleanup;
> > +
> > + if (errno == ELOOP) {
> > + struct fsck_ref_report report = { 0 };
> > + report.path = "packed-refs";
> > + ret = fsck_report_ref(o, &report,
> > + FSCK_MSG_BAD_REF_FILETYPE,
> > + "not a regular file");
> > + goto cleanup;
> > + }
> > +
> > + ret = error_errno(_("unable to open %s"), refs->path);
> > + goto cleanup;
>
> The paragraph in the commit message:
>
> Reuse "FSCK_MSG_BAD_REF_FILETYPE" fsck message id to report the error to
> the user if "packed-refs" is not a regular file.
>
> Gave me the indication that any error would be reported via
> 'fsck_report_ref()', but it seems like we are only reporting for
> symbolic links. Why is that being singled out?
>
IIRC, when Patrick told me in first version that if I first stat the
file type and then use the `strbuf_read_file` to read the content, there
is a corner case that the file could be converted into symlink between
the `stat` and read.
So, I use `open_nofollow` to avoid this situation. (Actually, this could
not be avoided because in Windows, we would first stat the file and
then open the file due to that there is no "O_NOFOLLOW" flag for Windows).
I will find a solution to do this in the next version.
> > + }
> > +
> > +cleanup:
> > + return ret;
> > }
> >
> > struct ref_storage_be refs_be_packed = {
> > diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
> > index cf7a202d0d..42c8d4ca1e 100755
> > --- a/t/t0602-reffiles-fsck.sh
> > +++ b/t/t0602-reffiles-fsck.sh
> > @@ -617,4 +617,26 @@ test_expect_success 'ref content checks should work with worktrees' '
> > )
> > '
> >
> > +test_expect_success SYMLINKS 'the filetype of packed-refs should be checked' '
> > + test_when_finished "rm -rf repo" &&
> > + git init repo &&
> > + (
> > + cd repo &&
> > + test_commit default &&
> > + git branch branch-1 &&
> > + git branch branch-2 &&
> > + git branch branch-3 &&
> > + git pack-refs --all &&
> > +
> > + mv .git/packed-refs .git/packed-refs-back &&
> > + ln -sf packed-refs-bak .git/packed-refs &&
>
> This still doesn't make sense to me. 'packed-refs-bak' doesn't exist, is
> the intention to symlink '.git/packed-refs' -> something which doesn't
> exist?
>
> In that case why even make the effort to build a packed-refs file, could
> we simply do 'ln -sf packed-refs-bak .git/packed-refs' in an empty repo?
>
You are correct. My intention is not this. If the "packed-refs" is a
symlink and points to file which we can successfully parse. Current Git
won't complain. So my motivation here is to imitate this situation.
> If not, then 'packed-refs-bak' is definitely a typo and needs to be made
> 'packed-refs-back' which would go in hand with how we setup the test...
>
Thanks for noticing this problem. I definitely made a mistake to type the
"packed-refs-back" to "packed-refs-bak".
Jialuo
^ permalink raw reply [flat|nested] 168+ messages in thread
* [PATCH v4 4/8] packed-backend: add "packed-refs" header consistency check
2025-02-14 4:50 ` [PATCH v4 0/8] add more ref consistency checks shejialuo
` (2 preceding siblings ...)
2025-02-14 4:52 ` [PATCH v4 3/8] packed-backend: check whether the "packed-refs" is regular file shejialuo
@ 2025-02-14 4:52 ` shejialuo
2025-02-14 10:30 ` Karthik Nayak
2025-02-14 14:01 ` Junio C Hamano
2025-02-14 4:52 ` [PATCH v4 5/8] packed-backend: check whether the refname contains NUL characters shejialuo
` (5 subsequent siblings)
9 siblings, 2 replies; 168+ messages in thread
From: shejialuo @ 2025-02-14 4:52 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
In "packed-backend.c::create_snapshot", if there is a header (the line
which starts with '#'), we will check whether the line starts with "#
pack-refs with:". Before we port this check into "packed_fsck", let's
fix "create_snapshot" to check the prefix "# packed-ref with: " instead
of "# packed-ref with:" due to that we will always write a single
trailing space after the colon.
However, we need to consider other situations and discuss whether we
need to add checks.
1. If the header does not exist, we should not report an error to the
user. This is because in older Git version, we never write header in
the "packed-refs" file. Also, we do allow no header in "packed-refs"
in runtime.
2. If the header content does not start with "# packed-ref with: ", we
should report an error just like what "create_snapshot" does. So,
create a new fsck message "badPackedRefHeader(ERROR)" for this.
3. If the header content is not the same as the constant string
"PACKED_REFS_HEADER". This is expected because we make it extensible
intentionally. So, there is no need to report.
As we have analyzed, we only need to check the case 2 in the above. In
order to do this, read the "packed-refs" file via "strbuf_read". Like
what "create_snapshot" and other functions do, we could split the line
by finding the next newline in the buffer. When we cannot find a
newline, we could report an error.
So, create a function "packed_fsck_ref_next_line" to find the next
newline and if there is no such newline, use
"packedRefEntryNotTerminated(ERROR)" to report an error to the user.
Then, parse the first line to apply the checks. Update the test to
exercise the code.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/fsck-msgids.txt | 8 ++++
fsck.h | 2 +
refs/packed-backend.c | 75 ++++++++++++++++++++++++++++++++++-
t/t0602-reffiles-fsck.sh | 52 ++++++++++++++++++++++++
4 files changed, 136 insertions(+), 1 deletion(-)
diff --git a/Documentation/fsck-msgids.txt b/Documentation/fsck-msgids.txt
index b14bc44ca4..11906f90fd 100644
--- a/Documentation/fsck-msgids.txt
+++ b/Documentation/fsck-msgids.txt
@@ -16,6 +16,10 @@
`badObjectSha1`::
(ERROR) An object has a bad sha1.
+`badPackedRefHeader`::
+ (ERROR) The "packed-refs" file contains an invalid
+ header.
+
`badParentSha1`::
(ERROR) A commit object has a bad parent sha1.
@@ -176,6 +180,10 @@
`nullSha1`::
(WARN) Tree contains entries pointing to a null sha1.
+`packedRefEntryNotTerminated`::
+ (ERROR) The "packed-refs" file contains an entry that is
+ not terminated by a newline.
+
`refMissingNewline`::
(INFO) A loose ref that does not end with newline(LF). As
valid implementations of Git never created such a loose ref
diff --git a/fsck.h b/fsck.h
index a44c231a5f..67e3c97bc0 100644
--- a/fsck.h
+++ b/fsck.h
@@ -30,6 +30,7 @@ enum fsck_msg_type {
FUNC(BAD_EMAIL, ERROR) \
FUNC(BAD_NAME, ERROR) \
FUNC(BAD_OBJECT_SHA1, ERROR) \
+ FUNC(BAD_PACKED_REF_HEADER, ERROR) \
FUNC(BAD_PARENT_SHA1, ERROR) \
FUNC(BAD_REF_CONTENT, ERROR) \
FUNC(BAD_REF_FILETYPE, ERROR) \
@@ -53,6 +54,7 @@ enum fsck_msg_type {
FUNC(MISSING_TYPE, ERROR) \
FUNC(MISSING_TYPE_ENTRY, ERROR) \
FUNC(MULTIPLE_AUTHORS, ERROR) \
+ FUNC(PACKED_REF_ENTRY_NOT_TERMINATED, ERROR) \
FUNC(TREE_NOT_SORTED, ERROR) \
FUNC(UNKNOWN_TYPE, ERROR) \
FUNC(ZERO_PADDED_DATE, ERROR) \
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 6401cecd5f..ff74ab915e 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -694,7 +694,7 @@ static struct snapshot *create_snapshot(struct packed_ref_store *refs)
tmp = xmemdupz(snapshot->buf, eol - snapshot->buf);
- if (!skip_prefix(tmp, "# pack-refs with:", (const char **)&p))
+ if (!skip_prefix(tmp, "# pack-refs with: ", (const char **)&p))
die_invalid_line(refs->path,
snapshot->buf,
snapshot->eof - snapshot->buf);
@@ -1749,12 +1749,76 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
return empty_ref_iterator_begin();
}
+static int packed_fsck_ref_next_line(struct fsck_options *o,
+ unsigned long line_number, const char *start,
+ const char *eof, const char **eol)
+{
+ int ret = 0;
+
+ *eol = memchr(start, '\n', eof - start);
+ if (!*eol) {
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_PACKED_REF_ENTRY_NOT_TERMINATED,
+ "'%.*s' is not terminated with a newline",
+ (int)(eof - start), start);
+
+ /*
+ * There is no newline but we still want to parse it to the end of
+ * the buffer.
+ */
+ *eol = eof;
+ strbuf_release(&packed_entry);
+ }
+
+ return ret;
+}
+
+static int packed_fsck_ref_header(struct fsck_options *o,
+ const char *start, const char *eol)
+{
+ if (!starts_with(start, "# pack-refs with: ")) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs.header";
+
+ return fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_HEADER,
+ "'%.*s' does not start with '# pack-refs with: '",
+ (int)(eol - start), start);
+ }
+
+ return 0;
+}
+
+static int packed_fsck_ref_content(struct fsck_options *o,
+ const char *start, const char *eof)
+{
+ unsigned long line_number = 1;
+ const char *eol;
+ int ret = 0;
+
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ if (*start == '#') {
+ ret |= packed_fsck_ref_header(o, start, eol);
+
+ start = eol + 1;
+ line_number++;
+ }
+
+ return ret;
+}
+
static int packed_fsck(struct ref_store *ref_store,
struct fsck_options *o,
struct worktree *wt)
{
struct packed_ref_store *refs = packed_downcast(ref_store,
REF_STORE_READ, "fsck");
+ struct strbuf packed_ref_content = STRBUF_INIT;
int ret = 0;
int fd;
@@ -1786,7 +1850,16 @@ static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
+ if (strbuf_read(&packed_ref_content, fd, 0) < 0) {
+ ret = error_errno(_("unable to read %s"), refs->path);
+ goto cleanup;
+ }
+
+ ret = packed_fsck_ref_content(o, packed_ref_content.buf,
+ packed_ref_content.buf + packed_ref_content.len);
+
cleanup:
+ strbuf_release(&packed_ref_content);
return ret;
}
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index 42c8d4ca1e..30be1982df 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -639,4 +639,56 @@ test_expect_success SYMLINKS 'the filetype of packed-refs should be checked' '
)
'
+test_expect_success 'packed-refs header should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+
+ git refs verify 2>err &&
+ test_must_be_empty err &&
+
+ for bad_header in "# pack-refs wit: peeled fully-peeled sorted " \
+ "# pack-refs with traits: peeled fully-peeled sorted " \
+ "# pack-refs with a: peeled fully-peeled" \
+ "# pack-refs with:peeled fully-peeled sorted"
+ do
+ printf "%s\n" "$bad_header" >.git/packed-refs &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs.header: badPackedRefHeader: '\''$bad_header'\'' does not start with '\''# pack-refs with: '\''
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err || return 1
+ done
+ )
+'
+
+test_expect_success 'packed-refs missing header should not be reported' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+
+ printf "$(git rev-parse HEAD) refs/heads/main\n" >.git/packed-refs &&
+ git refs verify 2>err &&
+ test_must_be_empty err
+ )
+'
+
+test_expect_success 'packed-refs unknown traits should not be reported' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+
+ printf "# pack-refs with: peeled fully-peeled sorted foo\n" >.git/packed-refs &&
+ git refs verify 2>err &&
+ test_must_be_empty err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* Re: [PATCH v4 4/8] packed-backend: add "packed-refs" header consistency check
2025-02-14 4:52 ` [PATCH v4 4/8] packed-backend: add "packed-refs" header consistency check shejialuo
@ 2025-02-14 10:30 ` Karthik Nayak
2025-02-14 12:43 ` shejialuo
2025-02-14 14:01 ` Junio C Hamano
1 sibling, 1 reply; 168+ messages in thread
From: Karthik Nayak @ 2025-02-14 10:30 UTC (permalink / raw)
To: shejialuo, git; +Cc: Patrick Steinhardt, Junio C Hamano, Michael Haggerty
[-- Attachment #1: Type: text/plain, Size: 9330 bytes --]
shejialuo <shejialuo@gmail.com> writes:
> In "packed-backend.c::create_snapshot", if there is a header (the line
> which starts with '#'), we will check whether the line starts with "#
> pack-refs with:". Before we port this check into "packed_fsck", let's
> fix "create_snapshot" to check the prefix "# packed-ref with: " instead
> of "# packed-ref with:" due to that we will always write a single
> trailing space after the colon.
>
Okay. So we're extending the check to also include the trailing space.
>
> However, we need to consider other situations and discuss whether we
> need to add checks.
>
> 1. If the header does not exist, we should not report an error to the
> user. This is because in older Git version, we never write header in
> the "packed-refs" file. Also, we do allow no header in "packed-refs"
> in runtime.
Makes sense.
> 2. If the header content does not start with "# packed-ref with: ", we
> should report an error just like what "create_snapshot" does. So,
> create a new fsck message "badPackedRefHeader(ERROR)" for this.
> 3. If the header content is not the same as the constant string
> "PACKED_REFS_HEADER". This is expected because we make it extensible
> intentionally. So, there is no need to report.
Do you think it's worthwhile adding a warning/info here? This would
allow users to re-run 'git pack-refs' to ensure that they have a more
up-to date version of 'packed-refs'.
>
> As we have analyzed, we only need to check the case 2 in the above. In
> order to do this, read the "packed-refs" file via "strbuf_read". Like
> what "create_snapshot" and other functions do, we could split the line
> by finding the next newline in the buffer. When we cannot find a
> newline, we could report an error.
>
> So, create a function "packed_fsck_ref_next_line" to find the next
> newline and if there is no such newline, use
> "packedRefEntryNotTerminated(ERROR)" to report an error to the user.
>
> Then, parse the first line to apply the checks. Update the test to
> exercise the code.
>
> Mentored-by: Patrick Steinhardt <ps@pks.im>
> Mentored-by: Karthik Nayak <karthik.188@gmail.com>
> Signed-off-by: shejialuo <shejialuo@gmail.com>
> ---
> Documentation/fsck-msgids.txt | 8 ++++
> fsck.h | 2 +
> refs/packed-backend.c | 75 ++++++++++++++++++++++++++++++++++-
> t/t0602-reffiles-fsck.sh | 52 ++++++++++++++++++++++++
> 4 files changed, 136 insertions(+), 1 deletion(-)
>
> diff --git a/Documentation/fsck-msgids.txt b/Documentation/fsck-msgids.txt
> index b14bc44ca4..11906f90fd 100644
> --- a/Documentation/fsck-msgids.txt
> +++ b/Documentation/fsck-msgids.txt
> @@ -16,6 +16,10 @@
> `badObjectSha1`::
> (ERROR) An object has a bad sha1.
>
> +`badPackedRefHeader`::
> + (ERROR) The "packed-refs" file contains an invalid
> + header.
> +
> `badParentSha1`::
> (ERROR) A commit object has a bad parent sha1.
>
> @@ -176,6 +180,10 @@
> `nullSha1`::
> (WARN) Tree contains entries pointing to a null sha1.
>
> +`packedRefEntryNotTerminated`::
> + (ERROR) The "packed-refs" file contains an entry that is
> + not terminated by a newline.
> +
> `refMissingNewline`::
> (INFO) A loose ref that does not end with newline(LF). As
> valid implementations of Git never created such a loose ref
> diff --git a/fsck.h b/fsck.h
> index a44c231a5f..67e3c97bc0 100644
> --- a/fsck.h
> +++ b/fsck.h
> @@ -30,6 +30,7 @@ enum fsck_msg_type {
> FUNC(BAD_EMAIL, ERROR) \
> FUNC(BAD_NAME, ERROR) \
> FUNC(BAD_OBJECT_SHA1, ERROR) \
> + FUNC(BAD_PACKED_REF_HEADER, ERROR) \
> FUNC(BAD_PARENT_SHA1, ERROR) \
> FUNC(BAD_REF_CONTENT, ERROR) \
> FUNC(BAD_REF_FILETYPE, ERROR) \
> @@ -53,6 +54,7 @@ enum fsck_msg_type {
> FUNC(MISSING_TYPE, ERROR) \
> FUNC(MISSING_TYPE_ENTRY, ERROR) \
> FUNC(MULTIPLE_AUTHORS, ERROR) \
> + FUNC(PACKED_REF_ENTRY_NOT_TERMINATED, ERROR) \
> FUNC(TREE_NOT_SORTED, ERROR) \
> FUNC(UNKNOWN_TYPE, ERROR) \
> FUNC(ZERO_PADDED_DATE, ERROR) \
> diff --git a/refs/packed-backend.c b/refs/packed-backend.c
> index 6401cecd5f..ff74ab915e 100644
> --- a/refs/packed-backend.c
> +++ b/refs/packed-backend.c
> @@ -694,7 +694,7 @@ static struct snapshot *create_snapshot(struct packed_ref_store *refs)
>
> tmp = xmemdupz(snapshot->buf, eol - snapshot->buf);
>
> - if (!skip_prefix(tmp, "# pack-refs with:", (const char **)&p))
> + if (!skip_prefix(tmp, "# pack-refs with: ", (const char **)&p))
> die_invalid_line(refs->path,
> snapshot->buf,
> snapshot->eof - snapshot->buf);
> @@ -1749,12 +1749,76 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
> return empty_ref_iterator_begin();
> }
>
> +static int packed_fsck_ref_next_line(struct fsck_options *o,
> + unsigned long line_number, const char *start,
> + const char *eof, const char **eol)
> +{
> + int ret = 0;
> +
> + *eol = memchr(start, '\n', eof - start);
> + if (!*eol) {
> + struct strbuf packed_entry = STRBUF_INIT;
> + struct fsck_ref_report report = { 0 };
> +
> + strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
> + report.path = packed_entry.buf;
> + ret = fsck_report_ref(o, &report,
> + FSCK_MSG_PACKED_REF_ENTRY_NOT_TERMINATED,
> + "'%.*s' is not terminated with a newline",
> + (int)(eof - start), start);
> +
> + /*
> + * There is no newline but we still want to parse it to the end of
> + * the buffer.
> + */
> + *eol = eof;
> + strbuf_release(&packed_entry);
> + }
> +
> + return ret;
> +}
> +
> +static int packed_fsck_ref_header(struct fsck_options *o,
> + const char *start, const char *eol)
> +{
> + if (!starts_with(start, "# pack-refs with: ")) {
> + struct fsck_ref_report report = { 0 };
> + report.path = "packed-refs.header";
> +
> + return fsck_report_ref(o, &report,
> + FSCK_MSG_BAD_PACKED_REF_HEADER,
> + "'%.*s' does not start with '# pack-refs with: '",
> + (int)(eol - start), start);
> + }
> +
> + return 0;
> +}
> +
> +static int packed_fsck_ref_content(struct fsck_options *o,
> + const char *start, const char *eof)
> +{
> + unsigned long line_number = 1;
> + const char *eol;
> + int ret = 0;
> +
> + ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
> + if (*start == '#') {
> + ret |= packed_fsck_ref_header(o, start, eol);
> +
> + start = eol + 1;
> + line_number++;
Why do we increment `line_number` here? There is no usage beyond this.
> + }
> +
> + return ret;
> +}
> +
> static int packed_fsck(struct ref_store *ref_store,
> struct fsck_options *o,
> struct worktree *wt)
> {
> struct packed_ref_store *refs = packed_downcast(ref_store,
> REF_STORE_READ, "fsck");
> + struct strbuf packed_ref_content = STRBUF_INIT;
> int ret = 0;
> int fd;
>
> @@ -1786,7 +1850,16 @@ static int packed_fsck(struct ref_store *ref_store,
> goto cleanup;
> }
>
> + if (strbuf_read(&packed_ref_content, fd, 0) < 0) {
> + ret = error_errno(_("unable to read %s"), refs->path);
> + goto cleanup;
> + }
> +
So we want to parse the whole ref content to a buffer, wonder if it
makes more sense to use `strbuf_read_line()` here instead. But let's
carry on.
> + ret = packed_fsck_ref_content(o, packed_ref_content.buf,
> + packed_ref_content.buf + packed_ref_content.len);
> +
We pass the entire content and the EOF to the function.
> cleanup:
> + strbuf_release(&packed_ref_content);
> return ret;
> }
>
> diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
> index 42c8d4ca1e..30be1982df 100755
> --- a/t/t0602-reffiles-fsck.sh
> +++ b/t/t0602-reffiles-fsck.sh
> @@ -639,4 +639,56 @@ test_expect_success SYMLINKS 'the filetype of packed-refs should be checked' '
> )
> '
>
> +test_expect_success 'packed-refs header should be checked' '
> + test_when_finished "rm -rf repo" &&
> + git init repo &&
> + (
> + cd repo &&
> + test_commit default &&
> +
> + git refs verify 2>err &&
> + test_must_be_empty err &&
> +
> + for bad_header in "# pack-refs wit: peeled fully-peeled sorted " \
> + "# pack-refs with traits: peeled fully-peeled sorted " \
> + "# pack-refs with a: peeled fully-peeled" \
> + "# pack-refs with:peeled fully-peeled sorted"
> + do
> + printf "%s\n" "$bad_header" >.git/packed-refs &&
> + test_must_fail git refs verify 2>err &&
> + cat >expect <<-EOF &&
> + error: packed-refs.header: badPackedRefHeader: '\''$bad_header'\'' does not start with '\''# pack-refs with: '\''
> + EOF
> + rm .git/packed-refs &&
> + test_cmp expect err || return 1
> + done
> + )
> +'
> +
> +test_expect_success 'packed-refs missing header should not be reported' '
> + test_when_finished "rm -rf repo" &&
> + git init repo &&
> + (
> + cd repo &&
> + test_commit default &&
> +
> + printf "$(git rev-parse HEAD) refs/heads/main\n" >.git/packed-refs &&
> + git refs verify 2>err &&
> + test_must_be_empty err
> + )
> +'
> +
> +test_expect_success 'packed-refs unknown traits should not be reported' '
> + test_when_finished "rm -rf repo" &&
> + git init repo &&
> + (
> + cd repo &&
> + test_commit default &&
> +
> + printf "# pack-refs with: peeled fully-peeled sorted foo\n" >.git/packed-refs &&
> + git refs verify 2>err &&
> + test_must_be_empty err
> + )
> +'
> +
> test_done
> --
> 2.48.1
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 168+ messages in thread
* Re: [PATCH v4 4/8] packed-backend: add "packed-refs" header consistency check
2025-02-14 10:30 ` Karthik Nayak
@ 2025-02-14 12:43 ` shejialuo
0 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-14 12:43 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git, Patrick Steinhardt, Junio C Hamano, Michael Haggerty
On Fri, Feb 14, 2025 at 02:30:45AM -0800, Karthik Nayak wrote:
> shejialuo <shejialuo@gmail.com> writes:
[snip]
> > 2. If the header content does not start with "# packed-ref with: ", we
> > should report an error just like what "create_snapshot" does. So,
> > create a new fsck message "badPackedRefHeader(ERROR)" for this.
> > 3. If the header content is not the same as the constant string
> > "PACKED_REFS_HEADER". This is expected because we make it extensible
> > intentionally. So, there is no need to report.
>
> Do you think it's worthwhile adding a warning/info here? This would
> allow users to re-run 'git pack-refs' to ensure that they have a more
> up-to date version of 'packed-refs'.
>
I somehow agree with you here. But Junio worries about the
compatibility. You could see [1] about this discussion:
[1] https://lore.kernel.org/git/xmqq1pwkdt7r.fsf@gitster.g/
[snip]
> > +static int packed_fsck_ref_content(struct fsck_options *o,
> > + const char *start, const char *eof)
> > +{
> > + unsigned long line_number = 1;
> > + const char *eol;
> > + int ret = 0;
> > +
> > + ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
> > + if (*start == '#') {
> > + ret |= packed_fsck_ref_header(o, start, eol);
> > +
> > + start = eol + 1;
> > + line_number++;
>
> Why do we increment `line_number` here? There is no usage beyond this.
>
We will use this variable when iterating the next line (ref entries). It
will be used in next patch.
> > + }
> > +
> > + return ret;
> > +}
> > +
> > static int packed_fsck(struct ref_store *ref_store,
> > struct fsck_options *o,
> > struct worktree *wt)
> > {
> > struct packed_ref_store *refs = packed_downcast(ref_store,
> > REF_STORE_READ, "fsck");
> > + struct strbuf packed_ref_content = STRBUF_INIT;
> > int ret = 0;
> > int fd;
> >
> > @@ -1786,7 +1850,16 @@ static int packed_fsck(struct ref_store *ref_store,
> > goto cleanup;
> > }
> >
> > + if (strbuf_read(&packed_ref_content, fd, 0) < 0) {
> > + ret = error_errno(_("unable to read %s"), refs->path);
> > + goto cleanup;
> > + }
> > +
>
> So we want to parse the whole ref content to a buffer, wonder if it
> makes more sense to use `strbuf_read_line()` here instead. But let's
> carry on.
>
We may use `strbuf_read_line`. But I don't want to do this. My check
logic is the same as the parse logic ("create_snapshot" and "next_record").
I want to keep the logic nearly the same. So maybe one day, we may
refactor the code to make the parse and check use the same code. But at
now, this is difficult.
Thanks,
Jialuo
^ permalink raw reply [flat|nested] 168+ messages in thread
* Re: [PATCH v4 4/8] packed-backend: add "packed-refs" header consistency check
2025-02-14 4:52 ` [PATCH v4 4/8] packed-backend: add "packed-refs" header consistency check shejialuo
2025-02-14 10:30 ` Karthik Nayak
@ 2025-02-14 14:01 ` Junio C Hamano
1 sibling, 0 replies; 168+ messages in thread
From: Junio C Hamano @ 2025-02-14 14:01 UTC (permalink / raw)
To: shejialuo; +Cc: git, Patrick Steinhardt, Karthik Nayak, Michael Haggerty
shejialuo <shejialuo@gmail.com> writes:
> In "packed-backend.c::create_snapshot", if there is a header (the line
> which starts with '#'), we will check whether the line starts with "#
> pack-refs with:". Before we port this check into "packed_fsck", let's
> fix "create_snapshot" to check the prefix "# packed-ref with: " instead
> of "# packed-ref with:" due to that we will always write a single
> trailing space after the colon.
A more important reason to be more strict is not "we will always
write", but "we HAVE ALWAYS written", I think.
> However, we need to consider other situations and discuss whether we
> need to add checks.
>
> 1. If the header does not exist, we should not report an error to the
> user. This is because in older Git version, we never write header in
> the "packed-refs" file. Also, we do allow no header in "packed-refs"
> in runtime.
Yes.
> 2. If the header content does not start with "# packed-ref with: ", we
> should report an error just like what "create_snapshot" does. So,
> create a new fsck message "badPackedRefHeader(ERROR)" for this.
OK.
> 3. If the header content is not the same as the constant string
> "PACKED_REFS_HEADER". This is expected because we make it extensible
> intentionally. So, there is no need to report.
Nor there is any need to check for literal equality with the
constant string. We may want to split the traits that are recorded
on the "with:" line and see if there are ones that we do not
recognise if only for curiosity, but because create_snapshot(), which
is the only run-time consumer of this information, only uses the
ones it recognises while ignoring everything else, presence of an
unknown trait is not an error- or even warning-worthy event. Unless
we are curious and want to emit "info" level message, there is not
much point in checking the remainder of the header.
^ permalink raw reply [flat|nested] 168+ messages in thread
* [PATCH v4 5/8] packed-backend: check whether the refname contains NUL characters
2025-02-14 4:50 ` [PATCH v4 0/8] add more ref consistency checks shejialuo
` (3 preceding siblings ...)
2025-02-14 4:52 ` [PATCH v4 4/8] packed-backend: add "packed-refs" header consistency check shejialuo
@ 2025-02-14 4:52 ` shejialuo
2025-02-14 4:53 ` [PATCH v4 6/8] packed-backend: add "packed-refs" entry consistency check shejialuo
` (4 subsequent siblings)
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-14 4:52 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
"packed-backend.c::next_record" will use "check_refname_format" to check
the consistency of the refname. If it is not OK, the program will die.
However, it is reported in [1], we cannot catch some corruption. But we
already have the code path and we must miss out something.
We use the following code to get the refname:
strbuf_add(&iter->refname_buf, p, eol - p);
iter->base.refname = iter->refname_buf.buf
In the above code, `p` is the start pointer of the refname and `eol` is
the next newline pointer. We calculate the length of the refname by
subtracting the two pointers. Then we add the memory range between `p`
and `eol` to get the refname.
However, if there are some NUL characters in the memory range between `p`
and `eol`, we will see the refname as a valid ref name as long as the
memory range between `p` and first occurred NUL character is valid.
In order to catch above corruption, create a new function
"refname_contains_nul" by searching the first NUL character. If it is
not at the end of the string, there must be some NUL characters in the
refname.
Use this function in "next_record" function to die the program if
"refname_contains_nul" returns true.
[1] https://lore.kernel.org/git/6cfee0e4-3285-4f18-91ff-d097da9de737@rd10.de/
Reported-by: R. Diez <rdiez-temp3@rd10.de>
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
refs/packed-backend.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index ff74ab915e..692e315e41 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -494,6 +494,21 @@ static void verify_buffer_safe(struct snapshot *snapshot)
last_line, eof - last_line);
}
+/*
+ * When parsing the "packed-refs" file, we will parse it line by line.
+ * Because we know the start pointer of the refname and the next
+ * newline pointer, we could calculate the length of the refname by
+ * subtracting the two pointers. However, there is a corner case where
+ * the refname contains corrupted embedded NUL characters. And
+ * `check_refname_format()` will not catch this when the truncated
+ * refname is still a valid refname. To prevent this, we need to check
+ * whether the refname contains the NUL characters.
+ */
+static int refname_contains_nul(struct strbuf *refname)
+{
+ return !!memchr(refname->buf, '\0', refname->len);
+}
+
#define SMALL_FILE_SIZE (32*1024)
/*
@@ -895,6 +910,9 @@ static int next_record(struct packed_ref_iterator *iter)
strbuf_add(&iter->refname_buf, p, eol - p);
iter->base.refname = iter->refname_buf.buf;
+ if (refname_contains_nul(&iter->refname_buf))
+ die("packed refname contains embedded NULL: %s", iter->base.refname);
+
if (check_refname_format(iter->base.refname, REFNAME_ALLOW_ONELEVEL)) {
if (!refname_is_safe(iter->base.refname))
die("packed refname is dangerous: %s",
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v4 6/8] packed-backend: add "packed-refs" entry consistency check
2025-02-14 4:50 ` [PATCH v4 0/8] add more ref consistency checks shejialuo
` (4 preceding siblings ...)
2025-02-14 4:52 ` [PATCH v4 5/8] packed-backend: check whether the refname contains NUL characters shejialuo
@ 2025-02-14 4:53 ` shejialuo
2025-02-14 4:59 ` [PATCH v4 7/8] packed-backend: check whether the "packed-refs" is sorted shejialuo
` (3 subsequent siblings)
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-14 4:53 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
"packed-backend.c::next_record" will parse the ref entry to check the
consistency. This function has already checked the following things:
1. Parse the main line of the ref entry to inspect whether the oid is
not correct. Then, check whether the next character is oid. Then
check the refname.
2. If the next line starts with '^', it would continue to parse the
peeled oid and check whether the last character is '\n'.
As we decide to implement the ref consistency check for "packed-refs",
let's port these two checks and update the test to exercise the code.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/fsck-msgids.txt | 3 +
fsck.h | 1 +
refs/packed-backend.c | 121 +++++++++++++++++++++++++++++++++-
t/t0602-reffiles-fsck.sh | 44 +++++++++++++
4 files changed, 168 insertions(+), 1 deletion(-)
diff --git a/Documentation/fsck-msgids.txt b/Documentation/fsck-msgids.txt
index 11906f90fd..02a7bf0503 100644
--- a/Documentation/fsck-msgids.txt
+++ b/Documentation/fsck-msgids.txt
@@ -16,6 +16,9 @@
`badObjectSha1`::
(ERROR) An object has a bad sha1.
+`badPackedRefEntry`::
+ (ERROR) The "packed-refs" file contains an invalid entry.
+
`badPackedRefHeader`::
(ERROR) The "packed-refs" file contains an invalid
header.
diff --git a/fsck.h b/fsck.h
index 67e3c97bc0..14d70f6653 100644
--- a/fsck.h
+++ b/fsck.h
@@ -30,6 +30,7 @@ enum fsck_msg_type {
FUNC(BAD_EMAIL, ERROR) \
FUNC(BAD_NAME, ERROR) \
FUNC(BAD_OBJECT_SHA1, ERROR) \
+ FUNC(BAD_PACKED_REF_ENTRY, ERROR) \
FUNC(BAD_PACKED_REF_HEADER, ERROR) \
FUNC(BAD_PARENT_SHA1, ERROR) \
FUNC(BAD_REF_CONTENT, ERROR) \
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 692e315e41..5d1dcfec6f 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1812,9 +1812,113 @@ static int packed_fsck_ref_header(struct fsck_options *o,
return 0;
}
+static int packed_fsck_ref_peeled_line(struct fsck_options *o,
+ struct ref_store *ref_store,
+ unsigned long line_number,
+ const char *start, const char *eol)
+{
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct object_id peeled;
+ const char *p;
+ int ret = 0;
+
+ /*
+ * Skip the '^' and parse the peeled oid.
+ */
+ start++;
+ if (parse_oid_hex_algop(start, &peeled, &p, ref_store->repo->hash_algo)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "'%.*s' has invalid peeled oid",
+ (int)(eol - start), start);
+ goto cleanup;
+ }
+
+ if (p != eol) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "has trailing garbage after peeled oid '%.*s'",
+ (int)(eol - p), p);
+ goto cleanup;
+ }
+cleanup:
+ strbuf_release(&packed_entry);
+ return ret;
+}
+
+static int packed_fsck_ref_main_line(struct fsck_options *o,
+ struct ref_store *ref_store,
+ unsigned long line_number,
+ struct strbuf *refname,
+ const char *start, const char *eol)
+{
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct object_id oid;
+ const char *p;
+ int ret = 0;
+
+ if (parse_oid_hex_algop(start, &oid, &p, ref_store->repo->hash_algo)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "'%.*s' has invalid oid",
+ (int)(eol - start), start);
+ goto cleanup;
+ }
+
+ if (p == eol || !isspace(*p)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "has no space after oid '%s' but with '%.*s'",
+ oid_to_hex(&oid), (int)(eol - p), p);
+ goto cleanup;
+ }
+
+ p++;
+ strbuf_reset(refname);
+ strbuf_add(refname, p, eol - p);
+ if (refname_contains_nul(refname)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "refname '%s' contains NULL binaries",
+ refname->buf);
+ }
+
+ if (check_refname_format(refname->buf, 0)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_NAME,
+ "has bad refname '%s'", refname->buf);
+ }
+
+cleanup:
+ strbuf_release(&packed_entry);
+ return ret;
+}
+
static int packed_fsck_ref_content(struct fsck_options *o,
+ struct ref_store *ref_store,
const char *start, const char *eof)
{
+ struct strbuf refname = STRBUF_INIT;
unsigned long line_number = 1;
const char *eol;
int ret = 0;
@@ -1827,6 +1931,21 @@ static int packed_fsck_ref_content(struct fsck_options *o,
line_number++;
}
+ while (start < eof) {
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ ret |= packed_fsck_ref_main_line(o, ref_store, line_number, &refname, start, eol);
+ start = eol + 1;
+ line_number++;
+ if (start < eof && *start == '^') {
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ ret |= packed_fsck_ref_peeled_line(o, ref_store, line_number,
+ start, eol);
+ start = eol + 1;
+ line_number++;
+ }
+ }
+
+ strbuf_release(&refname);
return ret;
}
@@ -1873,7 +1992,7 @@ static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
- ret = packed_fsck_ref_content(o, packed_ref_content.buf,
+ ret = packed_fsck_ref_content(o, ref_store, packed_ref_content.buf,
packed_ref_content.buf + packed_ref_content.len);
cleanup:
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index 30be1982df..058a783cb7 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -691,4 +691,48 @@ test_expect_success 'packed-refs unknown traits should not be reported' '
)
'
+test_expect_success 'packed-refs content should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git tag -a annotated-tag-1 -m tag-1 &&
+ git tag -a annotated-tag-2 -m tag-2 &&
+
+ branch_1_oid=$(git rev-parse branch-1) &&
+ branch_2_oid=$(git rev-parse branch-2) &&
+ tag_1_oid=$(git rev-parse annotated-tag-1) &&
+ tag_2_oid=$(git rev-parse annotated-tag-2) &&
+ tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
+ tag_2_peeled_oid=$(git rev-parse annotated-tag-2^{}) &&
+ short_oid=$(printf "%s" $tag_1_peeled_oid | cut -c 1-4) &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ $short_oid refs/heads/branch-1
+ ${branch_1_oid}x
+ $branch_2_oid refs/heads/bad-branch
+ $branch_2_oid refs/heads/branch.
+ $tag_1_oid refs/tags/annotated-tag-3
+ ^$short_oid
+ $tag_2_oid refs/tags/annotated-tag-4.
+ ^$tag_2_peeled_oid garbage
+ EOF
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs line 2: badPackedRefEntry: '\''$short_oid refs/heads/branch-1'\'' has invalid oid
+ error: packed-refs line 3: badPackedRefEntry: has no space after oid '\''$branch_1_oid'\'' but with '\''x'\''
+ error: packed-refs line 4: badRefName: has bad refname '\'' refs/heads/bad-branch'\''
+ error: packed-refs line 5: badRefName: has bad refname '\''refs/heads/branch.'\''
+ error: packed-refs line 7: badPackedRefEntry: '\''$short_oid'\'' has invalid peeled oid
+ error: packed-refs line 8: badRefName: has bad refname '\''refs/tags/annotated-tag-4.'\''
+ error: packed-refs line 9: badPackedRefEntry: has trailing garbage after peeled oid '\'' garbage'\''
+ EOF
+ test_cmp expect err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v4 7/8] packed-backend: check whether the "packed-refs" is sorted
2025-02-14 4:50 ` [PATCH v4 0/8] add more ref consistency checks shejialuo
` (5 preceding siblings ...)
2025-02-14 4:53 ` [PATCH v4 6/8] packed-backend: add "packed-refs" entry consistency check shejialuo
@ 2025-02-14 4:59 ` shejialuo
2025-02-14 4:59 ` [PATCH v4 8/8] builtin/fsck: add `git refs verify` child process shejialuo
` (2 subsequent siblings)
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-14 4:59 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
When there is a "sorted" trait in the header of the "packed-refs" file,
it means that each entry is sorted increasingly by comparing the
refname. We should add checks to verify whether the "packed-refs" is
sorted in this case.
Update the "packed_fsck_ref_header" to know whether there is a "sorted"
trail in the header. It may seem that we could record all refnames
during the parsing process and then compare later. However, this is not
a good design due to the following reasons:
1. Because we need to store the state across the whole checking
lifetime, we would consume a lot of memory if there are many entries
in the "packed-refs" file.
2. We cannot reuse the existing compare function "cmp_packed_ref_records"
which cause repetition.
Because "cmp_packed_ref_records" needs an extra parameter "struct
snaphost", extract the common part into a new function
"cmp_packed_ref_records" to reuse this function to compare.
Then, create a new function "packed_fsck_ref_sorted" to parse the file
again and user the new fsck message "packedRefUnsorted(ERROR)" to report
to the user if the file is not sorted.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/fsck-msgids.txt | 3 +
fsck.h | 1 +
refs/packed-backend.c | 116 +++++++++++++++++++++++++++++-----
t/t0602-reffiles-fsck.sh | 87 +++++++++++++++++++++++++
4 files changed, 191 insertions(+), 16 deletions(-)
diff --git a/Documentation/fsck-msgids.txt b/Documentation/fsck-msgids.txt
index 02a7bf0503..9601fff228 100644
--- a/Documentation/fsck-msgids.txt
+++ b/Documentation/fsck-msgids.txt
@@ -187,6 +187,9 @@
(ERROR) The "packed-refs" file contains an entry that is
not terminated by a newline.
+`packedRefUnsorted`::
+ (ERROR) The "packed-refs" file is not sorted.
+
`refMissingNewline`::
(INFO) A loose ref that does not end with newline(LF). As
valid implementations of Git never created such a loose ref
diff --git a/fsck.h b/fsck.h
index 14d70f6653..19f3cb2773 100644
--- a/fsck.h
+++ b/fsck.h
@@ -56,6 +56,7 @@ enum fsck_msg_type {
FUNC(MISSING_TYPE_ENTRY, ERROR) \
FUNC(MULTIPLE_AUTHORS, ERROR) \
FUNC(PACKED_REF_ENTRY_NOT_TERMINATED, ERROR) \
+ FUNC(PACKED_REF_UNSORTED, ERROR) \
FUNC(TREE_NOT_SORTED, ERROR) \
FUNC(UNKNOWN_TYPE, ERROR) \
FUNC(ZERO_PADDED_DATE, ERROR) \
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 5d1dcfec6f..391efced54 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -300,14 +300,9 @@ struct snapshot_record {
size_t len;
};
-static int cmp_packed_ref_records(const void *v1, const void *v2,
- void *cb_data)
-{
- const struct snapshot *snapshot = cb_data;
- const struct snapshot_record *e1 = v1, *e2 = v2;
- const char *r1 = e1->start + snapshot_hexsz(snapshot) + 1;
- const char *r2 = e2->start + snapshot_hexsz(snapshot) + 1;
+static int cmp_packed_refname(const char *r1, const char *r2)
+{
while (1) {
if (*r1 == '\n')
return *r2 == '\n' ? 0 : -1;
@@ -322,6 +317,17 @@ static int cmp_packed_ref_records(const void *v1, const void *v2,
}
}
+static int cmp_packed_ref_records(const void *v1, const void *v2,
+ void *cb_data)
+{
+ const struct snapshot *snapshot = cb_data;
+ const struct snapshot_record *e1 = v1, *e2 = v2;
+ const char *r1 = e1->start + snapshot_hexsz(snapshot) + 1;
+ const char *r2 = e2->start + snapshot_hexsz(snapshot) + 1;
+
+ return cmp_packed_refname(r1, r2);
+}
+
/*
* Compare a snapshot record at `rec` to the specified NUL-terminated
* refname.
@@ -1797,19 +1803,33 @@ static int packed_fsck_ref_next_line(struct fsck_options *o,
}
static int packed_fsck_ref_header(struct fsck_options *o,
- const char *start, const char *eol)
+ const char *start, const char *eol,
+ unsigned int *sorted)
{
- if (!starts_with(start, "# pack-refs with: ")) {
+ struct string_list traits = STRING_LIST_INIT_NODUP;
+ char *tmp_line;
+ int ret = 0;
+ char *p;
+
+ tmp_line = xmemdupz(start, eol - start);
+ if (!skip_prefix(tmp_line, "# pack-refs with: ", (const char **)&p)) {
struct fsck_ref_report report = { 0 };
report.path = "packed-refs.header";
- return fsck_report_ref(o, &report,
- FSCK_MSG_BAD_PACKED_REF_HEADER,
- "'%.*s' does not start with '# pack-refs with: '",
- (int)(eol - start), start);
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_HEADER,
+ "'%.*s' does not start with '# pack-refs with: '",
+ (int)(eol - start), start);
+ goto cleanup;
}
- return 0;
+ string_list_split_in_place(&traits, p, " ", -1);
+ *sorted = unsorted_string_list_has_string(&traits, "sorted");
+
+cleanup:
+ free(tmp_line);
+ string_list_clear(&traits, 0);
+ return ret;
}
static int packed_fsck_ref_peeled_line(struct fsck_options *o,
@@ -1914,8 +1934,68 @@ static int packed_fsck_ref_main_line(struct fsck_options *o,
return ret;
}
+static int packed_fsck_ref_sorted(struct fsck_options *o,
+ struct ref_store *ref_store,
+ const char *start, const char *eof)
+{
+ size_t hexsz = ref_store->repo->hash_algo->hexsz;
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct strbuf refname1 = STRBUF_INIT;
+ struct strbuf refname2 = STRBUF_INIT;
+ unsigned long line_number = 1;
+ const char *former = NULL;
+ const char *current;
+ const char *eol;
+ int ret = 0;
+
+ if (*start == '#') {
+ eol = memchr(start, '\n', eof - start);
+ start = eol + 1;
+ line_number++;
+ }
+
+ for (; start < eof; line_number++, start = eol + 1) {
+ eol = memchr(start, '\n', eof - start);
+
+ if (*start == '^')
+ continue;
+
+ if (!former) {
+ former = start + hexsz + 1;
+ continue;
+ }
+
+ current = start + hexsz + 1;
+ if (cmp_packed_refname(former, current) >= 0) {
+ const char *err_fmt =
+ "refname '%s' is less than previous refname '%s'";
+
+ eol = memchr(former, '\n', eof - former);
+ strbuf_add(&refname1, former, eol - former);
+ eol = memchr(current, '\n', eof - current);
+ strbuf_add(&refname2, current, eol - current);
+
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_PACKED_REF_UNSORTED,
+ err_fmt, refname2.buf, refname1.buf);
+ goto cleanup;
+ }
+ former = current;
+ }
+
+cleanup:
+ strbuf_release(&packed_entry);
+ strbuf_release(&refname1);
+ strbuf_release(&refname2);
+ return ret;
+}
+
static int packed_fsck_ref_content(struct fsck_options *o,
struct ref_store *ref_store,
+ unsigned int *sorted,
const char *start, const char *eof)
{
struct strbuf refname = STRBUF_INIT;
@@ -1925,7 +2005,7 @@ static int packed_fsck_ref_content(struct fsck_options *o,
ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
if (*start == '#') {
- ret |= packed_fsck_ref_header(o, start, eol);
+ ret |= packed_fsck_ref_header(o, start, eol, sorted);
start = eol + 1;
line_number++;
@@ -1956,6 +2036,7 @@ static int packed_fsck(struct ref_store *ref_store,
struct packed_ref_store *refs = packed_downcast(ref_store,
REF_STORE_READ, "fsck");
struct strbuf packed_ref_content = STRBUF_INIT;
+ unsigned int sorted = 0;
int ret = 0;
int fd;
@@ -1992,8 +2073,11 @@ static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
- ret = packed_fsck_ref_content(o, ref_store, packed_ref_content.buf,
+ ret = packed_fsck_ref_content(o, ref_store, &sorted, packed_ref_content.buf,
packed_ref_content.buf + packed_ref_content.len);
+ if (!ret && sorted)
+ ret = packed_fsck_ref_sorted(o, ref_store, packed_ref_content.buf,
+ packed_ref_content.buf + packed_ref_content.len);
cleanup:
strbuf_release(&packed_ref_content);
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index 058a783cb7..f305428f12 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -735,4 +735,91 @@ test_expect_success 'packed-refs content should be checked' '
)
'
+test_expect_success 'packed-ref with sorted trait should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git tag -a annotated-tag-1 -m tag-1 &&
+ branch_1_oid=$(git rev-parse branch-1) &&
+ branch_2_oid=$(git rev-parse branch-2) &&
+ tag_1_oid=$(git rev-parse annotated-tag-1) &&
+ tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
+ refname1="refs/heads/main" &&
+ refname2="refs/heads/foo" &&
+ refname3="refs/tags/foo" &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ EOF
+ git refs verify 2>err &&
+ rm .git/packed-refs &&
+ test_must_be_empty err &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ $branch_2_oid $refname1
+ EOF
+ git refs verify 2>err &&
+ rm .git/packed-refs &&
+ test_must_be_empty err &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ $branch_2_oid $refname1
+ $branch_1_oid $refname2
+ $tag_1_oid $refname3
+ EOF
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs line 3: packedRefUnsorted: refname '\''$refname2'\'' is less than previous refname '\''$refname1'\''
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ $tag_1_oid $refname3
+ ^$tag_1_peeled_oid
+ $branch_2_oid $refname2
+ EOF
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs line 4: packedRefUnsorted: refname '\''$refname2'\'' is less than previous refname '\''$refname3'\''
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err
+ )
+'
+
+test_expect_success 'packed-ref without sorted trait should not be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git tag -a annotated-tag-1 -m tag-1 &&
+ branch_1_oid=$(git rev-parse branch-1) &&
+ branch_2_oid=$(git rev-parse branch-2) &&
+ tag_1_oid=$(git rev-parse annotated-tag-1) &&
+ tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
+ refname1="refs/heads/main" &&
+ refname2="refs/heads/foo" &&
+ refname3="refs/tags/foo" &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled
+ $branch_2_oid $refname1
+ $branch_1_oid $refname2
+ EOF
+ git refs verify 2>err &&
+ test_must_be_empty err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v4 8/8] builtin/fsck: add `git refs verify` child process
2025-02-14 4:50 ` [PATCH v4 0/8] add more ref consistency checks shejialuo
` (6 preceding siblings ...)
2025-02-14 4:59 ` [PATCH v4 7/8] packed-backend: check whether the "packed-refs" is sorted shejialuo
@ 2025-02-14 4:59 ` shejialuo
2025-02-14 9:04 ` [PATCH v4 0/8] add more ref consistency checks Karthik Nayak
2025-02-17 15:25 ` [PATCH v5 " shejialuo
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-14 4:59 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
At now, we have already implemented the ref consistency checks for both
"files-backend" and "packed-backend". Although we would check some
redundant things, it won't cause trouble. So, let's integrate it into
the "git-fsck(1)" command to get feedback from the users. And also by
calling "git refs verify" in "git-fsck(1)", we make sure that the new
added checks don't break.
Introduce a new function "fsck_refs" that initializes and runs a child
process to execute the "git refs verify" command. In order to provide
the user interface create a progress which makes the total task be 1.
It's hard to know how many loose refs we will check now. We might
improve this later.
Then, introduce the option to allow the user to disable checking ref
database consistency. Put this function in the very first execution
sequence of "git-fsck(1)" due to that we don't want the existing code of
"git-fsck(1)" which would implicitly check the consistency of refs to
die the program.
Last, update the test to exercise the code.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/git-fsck.txt | 7 ++++++-
builtin/fsck.c | 33 +++++++++++++++++++++++++++++++-
t/t0602-reffiles-fsck.sh | 39 ++++++++++++++++++++++++++++++++++++++
3 files changed, 77 insertions(+), 2 deletions(-)
diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt
index 5b82e4605c..5e71a29c3b 100644
--- a/Documentation/git-fsck.txt
+++ b/Documentation/git-fsck.txt
@@ -12,7 +12,7 @@ SYNOPSIS
'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]
[--[no-]full] [--strict] [--verbose] [--lost-found]
[--[no-]dangling] [--[no-]progress] [--connectivity-only]
- [--[no-]name-objects] [<object>...]
+ [--[no-]name-objects] [--[no-]references] [<object>...]
DESCRIPTION
-----------
@@ -104,6 +104,11 @@ care about this output and want to speed it up further.
progress status even if the standard error stream is not
directed to a terminal.
+--[no-]references::
+ Control whether to check the references database consistency
+ via 'git refs verify'. See linkgit:git-refs[1] for details.
+ The default is to check the references database.
+
CONFIGURATION
-------------
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 7a4dcb0716..f4f395cfbd 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -50,6 +50,7 @@ static int verbose;
static int show_progress = -1;
static int show_dangling = 1;
static int name_objects;
+static int check_references = 1;
#define ERROR_OBJECT 01
#define ERROR_REACHABLE 02
#define ERROR_PACK 04
@@ -905,11 +906,37 @@ static int check_pack_rev_indexes(struct repository *r, int show_progress)
return res;
}
+static void fsck_refs(struct repository *r)
+{
+ struct child_process refs_verify = CHILD_PROCESS_INIT;
+ struct progress *progress = NULL;
+
+ if (show_progress)
+ progress = start_progress(r, _("Checking ref database"), 1);
+
+ if (verbose)
+ fprintf_ln(stderr, _("Checking ref database"));
+
+ child_process_init(&refs_verify);
+ refs_verify.git_cmd = 1;
+ strvec_pushl(&refs_verify.args, "refs", "verify", NULL);
+ if (verbose)
+ strvec_push(&refs_verify.args, "--verbose");
+ if (check_strict)
+ strvec_push(&refs_verify.args, "--strict");
+
+ if (run_command(&refs_verify))
+ errors_found |= ERROR_REFS;
+
+ display_progress(progress, 1);
+ stop_progress(&progress);
+}
+
static char const * const fsck_usage[] = {
N_("git fsck [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]\n"
" [--[no-]full] [--strict] [--verbose] [--lost-found]\n"
" [--[no-]dangling] [--[no-]progress] [--connectivity-only]\n"
- " [--[no-]name-objects] [<object>...]"),
+ " [--[no-]name-objects] [--[no-]references] [<object>...]"),
NULL
};
@@ -928,6 +955,7 @@ static struct option fsck_opts[] = {
N_("write dangling objects in .git/lost-found")),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
OPT_BOOL(0, "name-objects", &name_objects, N_("show verbose names for reachable objects")),
+ OPT_BOOL(0, "references", &check_references, N_("check reference database consistency")),
OPT_END(),
};
@@ -970,6 +998,9 @@ int cmd_fsck(int argc,
git_config(git_fsck_config, &fsck_obj_options);
prepare_repo_settings(the_repository);
+ if (check_references)
+ fsck_refs(the_repository);
+
if (connectivity_only) {
for_each_loose_object(mark_loose_for_connectivity, NULL, 0);
for_each_packed_object(the_repository,
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index f305428f12..22bd847782 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -822,4 +822,43 @@ test_expect_success 'packed-ref without sorted trait should not be checked' '
)
'
+test_expect_success '--[no-]references option should apply to fsck' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ branch_dir_prefix=.git/refs/heads &&
+ (
+ cd repo &&
+ test_commit default &&
+ for trailing_content in " garbage" " more garbage"
+ do
+ printf "%s" "$(git rev-parse HEAD)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ git fsck 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage &&
+ test_cmp expect err || return 1
+ done &&
+
+ for trailing_content in " garbage" " more garbage"
+ do
+ printf "%s" "$(git rev-parse HEAD)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ git fsck --references 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage &&
+ test_cmp expect err || return 1
+ done &&
+
+ for trailing_content in " garbage" " more garbage"
+ do
+ printf "%s" "$(git rev-parse HEAD)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ git fsck --no-references 2>err &&
+ rm $branch_dir_prefix/branch-garbage &&
+ test_must_be_empty err || return 1
+ done
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* Re: [PATCH v4 0/8] add more ref consistency checks
2025-02-14 4:50 ` [PATCH v4 0/8] add more ref consistency checks shejialuo
` (7 preceding siblings ...)
2025-02-14 4:59 ` [PATCH v4 8/8] builtin/fsck: add `git refs verify` child process shejialuo
@ 2025-02-14 9:04 ` Karthik Nayak
2025-02-14 12:16 ` shejialuo
2025-02-17 15:25 ` [PATCH v5 " shejialuo
9 siblings, 1 reply; 168+ messages in thread
From: Karthik Nayak @ 2025-02-14 9:04 UTC (permalink / raw)
To: shejialuo, git; +Cc: Patrick Steinhardt, Junio C Hamano, Michael Haggerty
[-- Attachment #1: Type: text/plain, Size: 2169 bytes --]
shejialuo <shejialuo@gmail.com> writes:
> Hi All:
>
> This patch enhances the following things:
>
> 1. [PATCH v4 4/8]: update the tests to verify that we don't report any
> errors to the user in some cases. Also, suggested by Junio, make sure
> that we check whether there is a trailing space after "# packed-refs
> with:".
> 2. [PATCH v4 6/8]: instead of greedily calculating the name of the line,
> lazily compute when there is any errors. And use the HERE docs to
> improve the test script.
> 3. [PATCH v4 7/8]: instead of storing the states, we parse the file
> again to check whether the file is sorted to avoid allocating too
> much memory. And use the HERE docs to improve the test script.
> 4. [PATCH v4 8/8]: update the documentation to emphasis the default. And
> add tests to exercise the code.
>
Nit: For someone coming in to review the 4th version directly it would
be really nice to see:
1. Summary of what the patch series is about.
2. Changes built over the last versions.
I know all this information is already spread out over the previous
versions, but would be nice to have it here (in every version rather).
> shejialuo (8):
> t0602: use subshell to ensure working directory unchanged
> builtin/refs: get worktrees without reading head information
> packed-backend: check whether the "packed-refs" is regular file
> packed-backend: add "packed-refs" header consistency check
> packed-backend: check whether the refname contains NUL characters
> packed-backend: add "packed-refs" entry consistency check
> packed-backend: check whether the "packed-refs" is sorted
> builtin/fsck: add `git refs verify` child process
>
> Documentation/fsck-msgids.txt | 14 +
> Documentation/git-fsck.txt | 7 +-
> builtin/fsck.c | 33 +-
> builtin/refs.c | 2 +-
> fsck.h | 4 +
> refs/packed-backend.c | 349 +++++++++-
> t/t0602-reffiles-fsck.sh | 1205 ++++++++++++++++++++-------------
> worktree.c | 5 +
> worktree.h | 6 +
> 9 files changed, 1140 insertions(+), 485 deletions(-)
[snip]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 168+ messages in thread
* Re: [PATCH v4 0/8] add more ref consistency checks
2025-02-14 9:04 ` [PATCH v4 0/8] add more ref consistency checks Karthik Nayak
@ 2025-02-14 12:16 ` shejialuo
0 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-14 12:16 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git, Patrick Steinhardt, Junio C Hamano, Michael Haggerty
On Fri, Feb 14, 2025 at 01:04:09AM -0800, Karthik Nayak wrote:
> shejialuo <shejialuo@gmail.com> writes:
>
> > Hi All:
> >
> > This patch enhances the following things:
> >
> > 1. [PATCH v4 4/8]: update the tests to verify that we don't report any
> > errors to the user in some cases. Also, suggested by Junio, make sure
> > that we check whether there is a trailing space after "# packed-refs
> > with:".
> > 2. [PATCH v4 6/8]: instead of greedily calculating the name of the line,
> > lazily compute when there is any errors. And use the HERE docs to
> > improve the test script.
> > 3. [PATCH v4 7/8]: instead of storing the states, we parse the file
> > again to check whether the file is sorted to avoid allocating too
> > much memory. And use the HERE docs to improve the test script.
> > 4. [PATCH v4 8/8]: update the documentation to emphasis the default. And
> > add tests to exercise the code.
> >
>
> Nit: For someone coming in to review the 4th version directly it would
> be really nice to see:
>
> 1. Summary of what the patch series is about.
> 2. Changes built over the last versions.
>
> I know all this information is already spread out over the previous
> versions, but would be nice to have it here (in every version rather).
>
Thanks for your suggestion, I will do this in my later patch.
^ permalink raw reply [flat|nested] 168+ messages in thread
* [PATCH v5 0/8] add more ref consistency checks
2025-02-14 4:50 ` [PATCH v4 0/8] add more ref consistency checks shejialuo
` (8 preceding siblings ...)
2025-02-14 9:04 ` [PATCH v4 0/8] add more ref consistency checks Karthik Nayak
@ 2025-02-17 15:25 ` shejialuo
2025-02-17 15:27 ` [PATCH v5 1/8] t0602: use subshell to ensure working directory unchanged shejialuo
` (9 more replies)
9 siblings, 10 replies; 168+ messages in thread
From: shejialuo @ 2025-02-17 15:25 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
Hi All:
This changes enhances the following things:
1. [PATCH v5 2/8]: enhance the comment suggested by Karthik.
2. [PATCH v5 3/8]: use lstat to check whether the filetype of
"packed-ref" is a regular file instead of using `open_nofollow`
to check. And also enhance the commit message suggested by Karthik.
3. [PATCH v5 4/8]: move "open_nofollow" in original [PATCH v4 3/8] to
this.
Also, I rebase due to the conflict that all *.txt files have been
renamed to *.adoc. However, I don't know whether this is a real
conflict. But I decide to rebase to make the life of Junio easy.
Thanks,
Jialuo
---
This series mainly does the following things:
1. Fix subshell issues
2. Add ref checks for packed-backend.
1. Check whether the filetype of "packed-refs" is correct.
2. Check whether the syntax of "packed-refs" is correct by using the
rules from "packed-backend.c::create_snapshot" and
"packed-backend.c::next_record".
3. Check whether the pointed object exists and whether the
"packed-refs" file is sorted.
3. Call "git refs verify" for "git-fsck(1)".
shejialuo (8):
t0602: use subshell to ensure working directory unchanged
builtin/refs: get worktrees without reading head information
packed-backend: check whether the "packed-refs" is regular file
packed-backend: add "packed-refs" header consistency check
packed-backend: check whether the refname contains NUL characters
packed-backend: add "packed-refs" entry consistency check
packed-backend: check whether the "packed-refs" is sorted
builtin/fsck: add `git refs verify` child process
Documentation/fsck-msgids.adoc | 14 +
Documentation/git-fsck.adoc | 7 +-
builtin/fsck.c | 33 +-
builtin/refs.c | 2 +-
fsck.h | 4 +
refs/packed-backend.c | 369 +++++++++-
t/t0602-reffiles-fsck.sh | 1205 +++++++++++++++++++-------------
worktree.c | 5 +
worktree.h | 7 +
9 files changed, 1161 insertions(+), 485 deletions(-)
Range-diff against v4:
1: 20889b7b18 = 1: b3952d80a2 t0602: use subshell to ensure working directory unchanged
2: 9d7780e953 ! 2: 3695586f58 builtin/refs: get worktrees without reading head information
@@ worktree.h: struct worktree {
struct worktree **get_worktrees(void);
+/*
-+ * Like `get_worktrees`, but does not read HEAD. This is useful when checking
-+ * the consistency, as reading HEAD may not be necessary.
++ * Like `get_worktrees`, but does not read HEAD. Skip reading HEAD allows to
++ * get the worktree without worrying about failures pertaining to parsing
++ * the HEAD ref. This is useful when we want to check the ref db consistency.
+ */
+struct worktree **get_worktrees_without_reading_head(void);
+
3: 44d26f6440 ! 3: cbaae00e8b packed-backend: check whether the "packed-refs" is regular file
@@ Commit message
Although "git-fsck(1)" and "packed-backend.c" will check some
consistency and correctness of "packed-refs" file, they never check the
- filetype of the "packed-refs". The user should always use "git
- pack-refs" command to create the raw regular "packed-refs" file, so we
- need to explicitly check this in "git refs verify".
+ filetype of the "packed-refs". Let's verify that the "packed-refs" has
+ the expected filetype, confirming it is created by "git pack-refs"
+ command.
- We could use "open_nofollow" wrapper to open the raw "packed-refs" file.
- If the returned "fd" value is less than 0, we could check whether the
- "errno" is "ELOOP" to report an error to the user.
+ Use "lstat" to check the file mode. If we cannot check the file status
+ due to there is no such file this is OK because there is a possibility
+ that there is no "packed-refs" in the repo.
Reuse "FSCK_MSG_BAD_REF_FILETYPE" fsck message id to report the error to
the user if "packed-refs" is not a regular file.
@@ refs/packed-backend.c: static struct ref_iterator *packed_reflog_iterator_begin(
{
+ struct packed_ref_store *refs = packed_downcast(ref_store,
+ REF_STORE_READ, "fsck");
++ struct stat st;
+ int ret = 0;
-+ int fd;
if (!is_main_worktree(wt))
- return 0;
@@ refs/packed-backend.c: static struct ref_iterator *packed_reflog_iterator_begin(
+ if (o->verbose)
+ fprintf_ln(stderr, "Checking packed-refs file %s", refs->path);
+
-+ fd = open_nofollow(refs->path, O_RDONLY);
-+ if (fd < 0) {
++ if (lstat(refs->path, &st) < 0) {
+ /*
+ * If the packed-refs file doesn't exist, there's nothing
+ * to check.
+ */
+ if (errno == ENOENT)
+ goto cleanup;
++ ret = error_errno(_("unable to stat %s"), refs->path);
++ goto cleanup;
++ }
+
-+ if (errno == ELOOP) {
-+ struct fsck_ref_report report = { 0 };
-+ report.path = "packed-refs";
-+ ret = fsck_report_ref(o, &report,
-+ FSCK_MSG_BAD_REF_FILETYPE,
-+ "not a regular file");
-+ goto cleanup;
-+ }
-+
-+ ret = error_errno(_("unable to open %s"), refs->path);
++ if (!S_ISREG(st.st_mode)) {
++ struct fsck_ref_report report = { 0 };
++ report.path = "packed-refs";
++ ret = fsck_report_ref(o, &report,
++ FSCK_MSG_BAD_REF_FILETYPE,
++ "not a regular file");
+ goto cleanup;
+ }
+
@@ t/t0602-reffiles-fsck.sh: test_expect_success 'ref content checks should work wi
+ git pack-refs --all &&
+
+ mv .git/packed-refs .git/packed-refs-back &&
-+ ln -sf packed-refs-bak .git/packed-refs &&
++ ln -sf packed-refs-back .git/packed-refs &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs: badRefFiletype: not a regular file
4: 976c5baba0 ! 4: b9ce8734ac packed-backend: add "packed-refs" header consistency check
@@ Commit message
create a new fsck message "badPackedRefHeader(ERROR)" for this.
3. If the header content is not the same as the constant string
"PACKED_REFS_HEADER". This is expected because we make it extensible
- intentionally. So, there is no need to report.
+ intentionally and runtime "create_snapshot" won't complain about
+ unknown traits. In order to align with the runtime behavior. There is
+ no need to report.
As we have analyzed, we only need to check the case 2 in the above. In
- order to do this, read the "packed-refs" file via "strbuf_read". Like
+ order to do this, use "open_nofollow" function to get the file
+ descriptor and then read the "packed-refs" file via "strbuf_read". Like
what "create_snapshot" and other functions do, we could split the line
by finding the next newline in the buffer. When we cannot find a
newline, we could report an error.
@@ Commit message
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
- ## Documentation/fsck-msgids.txt ##
+ ## Documentation/fsck-msgids.adoc ##
@@
`badObjectSha1`::
(ERROR) An object has a bad sha1.
@@ refs/packed-backend.c: static struct ref_iterator *packed_reflog_iterator_begin(
struct packed_ref_store *refs = packed_downcast(ref_store,
REF_STORE_READ, "fsck");
+ struct strbuf packed_ref_content = STRBUF_INIT;
+ struct stat st;
++ int fd;
int ret = 0;
- int fd;
+ if (!is_main_worktree(wt))
@@ refs/packed-backend.c: static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
++ /*
++ * There is a chance that "packed-refs" file is removed or converted to
++ * a symlink after filetype check and before open. So we need to avoid
++ * this race condition by opening the file.
++ */
++ fd = open_nofollow(refs->path, O_RDONLY);
++ if (fd < 0) {
++ if (errno == ENOENT)
++ goto cleanup;
++
++ if (errno == ELOOP) {
++ struct fsck_ref_report report = { 0 };
++ report.path = "packed-refs";
++ ret = fsck_report_ref(o, &report,
++ FSCK_MSG_BAD_REF_FILETYPE,
++ "not a regular file");
++ goto cleanup;
++ }
++ }
++
+ if (strbuf_read(&packed_ref_content, fd, 0) < 0) {
+ ret = error_errno(_("unable to read %s"), refs->path);
+ goto cleanup;
5: b66f142d7f = 5: 9f638b3adf packed-backend: check whether the refname contains NUL characters
6: f68028e171 ! 6: 2c5395bdd0 packed-backend: add "packed-refs" entry consistency check
@@ Commit message
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
- ## Documentation/fsck-msgids.txt ##
+ ## Documentation/fsck-msgids.adoc ##
@@
`badObjectSha1`::
(ERROR) An object has a bad sha1.
@@ refs/packed-backend.c: static int packed_fsck_ref_header(struct fsck_options *o,
+ (int)(eol - p), p);
+ goto cleanup;
+ }
++
+cleanup:
+ strbuf_release(&packed_entry);
+ return ret;
7: 4a7adf293f ! 7: 648404c60d packed-backend: check whether the "packed-refs" is sorted
@@ Commit message
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
- ## Documentation/fsck-msgids.txt ##
+ ## Documentation/fsck-msgids.adoc ##
@@
(ERROR) The "packed-refs" file contains an entry that is
not terminated by a newline.
@@ refs/packed-backend.c: static int packed_fsck(struct ref_store *ref_store,
REF_STORE_READ, "fsck");
struct strbuf packed_ref_content = STRBUF_INIT;
+ unsigned int sorted = 0;
+ struct stat st;
+- int fd;
int ret = 0;
- int fd;
++ int fd;
+ if (!is_main_worktree(wt))
+ goto cleanup;
@@ refs/packed-backend.c: static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
8: 2dd3437478 ! 8: 4dbbacf44b builtin/fsck: add `git refs verify` child process
@@ Commit message
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
- ## Documentation/git-fsck.txt ##
-@@ Documentation/git-fsck.txt: SYNOPSIS
+ ## Documentation/git-fsck.adoc ##
+@@ Documentation/git-fsck.adoc: SYNOPSIS
'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]
[--[no-]full] [--strict] [--verbose] [--lost-found]
[--[no-]dangling] [--[no-]progress] [--connectivity-only]
@@ Documentation/git-fsck.txt: SYNOPSIS
DESCRIPTION
-----------
-@@ Documentation/git-fsck.txt: care about this output and want to speed it up further.
+@@ Documentation/git-fsck.adoc: care about this output and want to speed it up further.
progress status even if the standard error stream is not
directed to a terminal.
--
2.48.1
^ permalink raw reply [flat|nested] 168+ messages in thread
* [PATCH v5 1/8] t0602: use subshell to ensure working directory unchanged
2025-02-17 15:25 ` [PATCH v5 " shejialuo
@ 2025-02-17 15:27 ` shejialuo
2025-02-17 15:27 ` [PATCH v5 2/8] builtin/refs: get worktrees without reading head information shejialuo
` (8 subsequent siblings)
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-17 15:27 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
For every test, we would execute the command "cd repo" in the first but
we never execute the command "cd .." to restore the working directory.
However, it's either not a good idea use above way. Because if any test
fails between "cd repo" and "cd ..", the "cd .." will never be reached.
And we cannot correctly restore the working directory.
Let's use subshell to ensure that the current working directory could be
restored to the correct path.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
t/t0602-reffiles-fsck.sh | 967 ++++++++++++++++++++-------------------
1 file changed, 494 insertions(+), 473 deletions(-)
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index d4a08b823b..cf7a202d0d 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -14,222 +14,229 @@ test_expect_success 'ref name should be checked' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
-
- git commit --allow-empty -m initial &&
- git checkout -b default-branch &&
- git tag default-tag &&
- git tag multi_hierarchy/default-tag &&
-
- cp $branch_dir_prefix/default-branch $branch_dir_prefix/@ &&
- git refs verify 2>err &&
- test_must_be_empty err &&
- rm $branch_dir_prefix/@ &&
-
- cp $tag_dir_prefix/default-tag $tag_dir_prefix/tag-1.lock &&
- git refs verify 2>err &&
- rm $tag_dir_prefix/tag-1.lock &&
- test_must_be_empty err &&
-
- cp $tag_dir_prefix/default-tag $tag_dir_prefix/.lock &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/tags/.lock: badRefName: invalid refname format
- EOF
- rm $tag_dir_prefix/.lock &&
- test_cmp expect err &&
-
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname" &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/$refname: badRefName: invalid refname format
- EOF
- rm "$branch_dir_prefix/$refname" &&
- test_cmp expect err || return 1
- done &&
+ (
+ cd repo &&
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- cp $tag_dir_prefix/default-tag "$tag_dir_prefix/$refname" &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/tags/$refname: badRefName: invalid refname format
- EOF
- rm "$tag_dir_prefix/$refname" &&
- test_cmp expect err || return 1
- done &&
+ git commit --allow-empty -m initial &&
+ git checkout -b default-branch &&
+ git tag default-tag &&
+ git tag multi_hierarchy/default-tag &&
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- cp $tag_dir_prefix/multi_hierarchy/default-tag "$tag_dir_prefix/multi_hierarchy/$refname" &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/tags/multi_hierarchy/$refname: badRefName: invalid refname format
- EOF
- rm "$tag_dir_prefix/multi_hierarchy/$refname" &&
- test_cmp expect err || return 1
- done &&
-
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- mkdir "$branch_dir_prefix/$refname" &&
- cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname/default-branch" &&
+ cp $branch_dir_prefix/default-branch $branch_dir_prefix/@ &&
+ git refs verify 2>err &&
+ test_must_be_empty err &&
+ rm $branch_dir_prefix/@ &&
+
+ cp $tag_dir_prefix/default-tag $tag_dir_prefix/tag-1.lock &&
+ git refs verify 2>err &&
+ rm $tag_dir_prefix/tag-1.lock &&
+ test_must_be_empty err &&
+
+ cp $tag_dir_prefix/default-tag $tag_dir_prefix/.lock &&
test_must_fail git refs verify 2>err &&
cat >expect <<-EOF &&
- error: refs/heads/$refname/default-branch: badRefName: invalid refname format
+ error: refs/tags/.lock: badRefName: invalid refname format
EOF
- rm -r "$branch_dir_prefix/$refname" &&
- test_cmp expect err || return 1
- done
+ rm $tag_dir_prefix/.lock &&
+ test_cmp expect err &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/$refname: badRefName: invalid refname format
+ EOF
+ rm "$branch_dir_prefix/$refname" &&
+ test_cmp expect err || return 1
+ done &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ cp $tag_dir_prefix/default-tag "$tag_dir_prefix/$refname" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/tags/$refname: badRefName: invalid refname format
+ EOF
+ rm "$tag_dir_prefix/$refname" &&
+ test_cmp expect err || return 1
+ done &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ cp $tag_dir_prefix/multi_hierarchy/default-tag "$tag_dir_prefix/multi_hierarchy/$refname" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/tags/multi_hierarchy/$refname: badRefName: invalid refname format
+ EOF
+ rm "$tag_dir_prefix/multi_hierarchy/$refname" &&
+ test_cmp expect err || return 1
+ done &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ mkdir "$branch_dir_prefix/$refname" &&
+ cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname/default-branch" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/$refname/default-branch: badRefName: invalid refname format
+ EOF
+ rm -r "$branch_dir_prefix/$refname" &&
+ test_cmp expect err || return 1
+ done
+ )
'
test_expect_success 'ref name check should be adapted into fsck messages' '
test_when_finished "rm -rf repo" &&
git init repo &&
branch_dir_prefix=.git/refs/heads &&
- cd repo &&
- git commit --allow-empty -m initial &&
- git checkout -b branch-1 &&
-
- cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
- git -c fsck.badRefName=warn refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/.branch-1: badRefName: invalid refname format
- EOF
- rm $branch_dir_prefix/.branch-1 &&
- test_cmp expect err &&
-
- cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
- git -c fsck.badRefName=ignore refs verify 2>err &&
- test_must_be_empty err
+ (
+ cd repo &&
+ git commit --allow-empty -m initial &&
+ git checkout -b branch-1 &&
+
+ cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
+ git -c fsck.badRefName=warn refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/.branch-1: badRefName: invalid refname format
+ EOF
+ rm $branch_dir_prefix/.branch-1 &&
+ test_cmp expect err &&
+
+ cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
+ git -c fsck.badRefName=ignore refs verify 2>err &&
+ test_must_be_empty err
+ )
'
test_expect_success 'ref name check should work for multiple worktrees' '
test_when_finished "rm -rf repo" &&
git init repo &&
-
- cd repo &&
- test_commit initial &&
- git checkout -b branch-1 &&
- test_commit second &&
- git checkout -b branch-2 &&
- test_commit third &&
- git checkout -b branch-3 &&
- git worktree add ./worktree-1 branch-1 &&
- git worktree add ./worktree-2 branch-2 &&
- worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
- worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
-
- (
- cd worktree-1 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-3
- ) &&
(
- cd worktree-2 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-3
- ) &&
-
- cp $worktree1_refdir_prefix/branch-4 $worktree1_refdir_prefix/'\'' branch-5'\'' &&
- cp $worktree2_refdir_prefix/branch-4 $worktree2_refdir_prefix/'\''~branch-6'\'' &&
-
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
- error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err &&
-
- for worktree in "worktree-1" "worktree-2"
- do
+ cd repo &&
+ test_commit initial &&
+ git checkout -b branch-1 &&
+ test_commit second &&
+ git checkout -b branch-2 &&
+ test_commit third &&
+ git checkout -b branch-3 &&
+ git worktree add ./worktree-1 branch-1 &&
+ git worktree add ./worktree-2 branch-2 &&
+ worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
+ worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
+
(
- cd $worktree &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
- error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err || return 1
- )
- done
+ cd worktree-1 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-3
+ ) &&
+ (
+ cd worktree-2 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-3
+ ) &&
+
+ cp $worktree1_refdir_prefix/branch-4 $worktree1_refdir_prefix/'\'' branch-5'\'' &&
+ cp $worktree2_refdir_prefix/branch-4 $worktree2_refdir_prefix/'\''~branch-6'\'' &&
+
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
+ error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err &&
+
+ for worktree in "worktree-1" "worktree-2"
+ do
+ (
+ cd $worktree &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
+ error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err || return 1
+ )
+ done
+ )
'
test_expect_success 'regular ref content should be checked (individual)' '
test_when_finished "rm -rf repo" &&
git init repo &&
branch_dir_prefix=.git/refs/heads &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
- git refs verify 2>err &&
- test_must_be_empty err &&
+ git refs verify 2>err &&
+ test_must_be_empty err &&
- for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$branch_dir_prefix/branch-bad &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/branch-bad: badRefContent: $bad_content
- EOF
- rm $branch_dir_prefix/branch-bad &&
- test_cmp expect err || return 1
- done &&
+ for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$branch_dir_prefix/branch-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/branch-bad: badRefContent: $bad_content
+ EOF
+ rm $branch_dir_prefix/branch-bad &&
+ test_cmp expect err || return 1
+ done &&
- for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$branch_dir_prefix/a/b/branch-bad &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/a/b/branch-bad: badRefContent: $bad_content
- EOF
- rm $branch_dir_prefix/a/b/branch-bad &&
- test_cmp expect err || return 1
- done &&
-
- printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- rm $branch_dir_prefix/branch-no-newline &&
- test_cmp expect err &&
-
- for trailing_content in " garbage" " more garbage"
- do
- printf "%s" "$(git rev-parse main)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$branch_dir_prefix/a/b/branch-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/a/b/branch-bad: badRefContent: $bad_content
+ EOF
+ rm $branch_dir_prefix/a/b/branch-bad &&
+ test_cmp expect err || return 1
+ done &&
+
+ printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
EOF
- rm $branch_dir_prefix/branch-garbage &&
- test_cmp expect err || return 1
- done &&
+ rm $branch_dir_prefix/branch-no-newline &&
+ test_cmp expect err &&
- printf "%s\n\n\n" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
+ for trailing_content in " garbage" " more garbage"
+ do
+ printf "%s" "$(git rev-parse main)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage &&
+ test_cmp expect err || return 1
+ done &&
+ printf "%s\n\n\n" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
- '\''
- EOF
- rm $branch_dir_prefix/branch-garbage-special &&
- test_cmp expect err &&
- printf "%s\n\n\n garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
+ '\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage-special &&
+ test_cmp expect err &&
+
+ printf "%s\n\n\n garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
- garbage'\''
- EOF
- rm $branch_dir_prefix/branch-garbage-special &&
- test_cmp expect err
+ garbage'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage-special &&
+ test_cmp expect err
+ )
'
test_expect_success 'regular ref content should be checked (aggregate)' '
@@ -237,99 +244,103 @@ test_expect_success 'regular ref content should be checked (aggregate)' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- bad_content_1=$(git rev-parse main)x &&
- bad_content_2=xfsazqfxcadas &&
- bad_content_3=Xfsazqfxcadas &&
- printf "%s" $bad_content_1 >$tag_dir_prefix/tag-bad-1 &&
- printf "%s" $bad_content_2 >$tag_dir_prefix/tag-bad-2 &&
- printf "%s" $bad_content_3 >$branch_dir_prefix/a/b/branch-bad &&
- printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
- printf "%s garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage &&
-
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/a/b/branch-bad: badRefContent: $bad_content_3
- error: refs/tags/tag-bad-1: badRefContent: $bad_content_1
- error: refs/tags/tag-bad-2: badRefContent: $bad_content_2
- warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
- warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ bad_content_1=$(git rev-parse main)x &&
+ bad_content_2=xfsazqfxcadas &&
+ bad_content_3=Xfsazqfxcadas &&
+ printf "%s" $bad_content_1 >$tag_dir_prefix/tag-bad-1 &&
+ printf "%s" $bad_content_2 >$tag_dir_prefix/tag-bad-2 &&
+ printf "%s" $bad_content_3 >$branch_dir_prefix/a/b/branch-bad &&
+ printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
+ printf "%s garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage &&
+
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/a/b/branch-bad: badRefContent: $bad_content_3
+ error: refs/tags/tag-bad-1: badRefContent: $bad_content_1
+ error: refs/tags/tag-bad-2: badRefContent: $bad_content_2
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
+ warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err
+ )
'
test_expect_success 'textual symref content should be checked (individual)' '
test_when_finished "rm -rf repo" &&
git init repo &&
branch_dir_prefix=.git/refs/heads &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ for good_referent in "refs/heads/branch" "HEAD"
+ do
+ printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
+ git refs verify 2>err &&
+ rm $branch_dir_prefix/branch-good &&
+ test_must_be_empty err || return 1
+ done &&
+
+ for bad_referent in "refs/heads/.branch" "refs/heads/~branch" "refs/heads/?branch"
+ do
+ printf "ref: %s\n" $bad_referent >$branch_dir_prefix/branch-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/branch-bad: badReferentName: points to invalid refname '\''$bad_referent'\''
+ EOF
+ rm $branch_dir_prefix/branch-bad &&
+ test_cmp expect err || return 1
+ done &&
- for good_referent in "refs/heads/branch" "HEAD"
- do
- printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
+ printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline &&
git refs verify 2>err &&
- rm $branch_dir_prefix/branch-good &&
- test_must_be_empty err || return 1
- done &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
+ EOF
+ rm $branch_dir_prefix/branch-no-newline &&
+ test_cmp expect err &&
- for bad_referent in "refs/heads/.branch" "refs/heads/~branch" "refs/heads/?branch"
- do
- printf "ref: %s\n" $bad_referent >$branch_dir_prefix/branch-bad &&
- test_must_fail git refs verify 2>err &&
+ printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
+ EOF
+ rm $branch_dir_prefix/a/b/branch-trailing-1 &&
+ test_cmp expect err &&
+
+ printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- error: refs/heads/branch-bad: badReferentName: points to invalid refname '\''$bad_referent'\''
+ warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
EOF
- rm $branch_dir_prefix/branch-bad &&
- test_cmp expect err || return 1
- done &&
-
- printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- rm $branch_dir_prefix/branch-no-newline &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-trailing-1 &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-trailing-2 &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-trailing-3 &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-complicated &&
- test_cmp expect err
+ rm $branch_dir_prefix/a/b/branch-trailing-2 &&
+ test_cmp expect err &&
+
+ printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
+ EOF
+ rm $branch_dir_prefix/a/b/branch-trailing-3 &&
+ test_cmp expect err &&
+
+ printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
+ EOF
+ rm $branch_dir_prefix/a/b/branch-complicated &&
+ test_cmp expect err
+ )
'
test_expect_success 'textual symref content should be checked (aggregate)' '
@@ -337,32 +348,34 @@ test_expect_success 'textual symref content should be checked (aggregate)' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- printf "ref: refs/heads/branch\n" >$branch_dir_prefix/branch-good &&
- printf "ref: HEAD\n" >$branch_dir_prefix/branch-head &&
- printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline-1 &&
- printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
- printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
- printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
- printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
- printf "ref: refs/heads/.branch\n" >$branch_dir_prefix/branch-bad-1 &&
-
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/branch-bad-1: badReferentName: points to invalid refname '\''refs/heads/.branch'\''
- warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/branch-no-newline-1: refMissingNewline: misses LF at the end
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ printf "ref: refs/heads/branch\n" >$branch_dir_prefix/branch-good &&
+ printf "ref: HEAD\n" >$branch_dir_prefix/branch-head &&
+ printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline-1 &&
+ printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
+ printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
+ printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
+ printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
+ printf "ref: refs/heads/.branch\n" >$branch_dir_prefix/branch-bad-1 &&
+
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/branch-bad-1: badReferentName: points to invalid refname '\''refs/heads/.branch'\''
+ warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/branch-no-newline-1: refMissingNewline: misses LF at the end
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err
+ )
'
test_expect_success 'the target of the textual symref should be checked' '
@@ -370,28 +383,30 @@ test_expect_success 'the target of the textual symref should be checked' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- for good_referent in "refs/heads/branch" "HEAD" "refs/tags/tag"
- do
- printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
- git refs verify 2>err &&
- rm $branch_dir_prefix/branch-good &&
- test_must_be_empty err || return 1
- done &&
-
- for nonref_referent in "refs-back/heads/branch" "refs-back/tags/tag" "reflogs/refs/heads/branch"
- do
- printf "ref: %s\n" $nonref_referent >$branch_dir_prefix/branch-bad-1 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-bad-1: symrefTargetIsNotARef: points to non-ref target '\''$nonref_referent'\''
- EOF
- rm $branch_dir_prefix/branch-bad-1 &&
- test_cmp expect err || return 1
- done
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ for good_referent in "refs/heads/branch" "HEAD" "refs/tags/tag"
+ do
+ printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
+ git refs verify 2>err &&
+ rm $branch_dir_prefix/branch-good &&
+ test_must_be_empty err || return 1
+ done &&
+
+ for nonref_referent in "refs-back/heads/branch" "refs-back/tags/tag" "reflogs/refs/heads/branch"
+ do
+ printf "ref: %s\n" $nonref_referent >$branch_dir_prefix/branch-bad-1 &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-bad-1: symrefTargetIsNotARef: points to non-ref target '\''$nonref_referent'\''
+ EOF
+ rm $branch_dir_prefix/branch-bad-1 &&
+ test_cmp expect err || return 1
+ done
+ )
'
test_expect_success SYMLINKS 'symlink symref content should be checked' '
@@ -399,201 +414,207 @@ test_expect_success SYMLINKS 'symlink symref content should be checked' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- ln -sf ./main $branch_dir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $branch_dir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../logs/branch-escape $branch_dir_prefix/branch-symbolic &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
- warning: refs/heads/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
- EOF
- rm $branch_dir_prefix/branch-symbolic &&
- test_cmp expect err &&
-
- ln -sf ./"branch " $branch_dir_prefix/branch-symbolic-bad &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic-bad: symlinkRef: use deprecated symbolic link for symref
- error: refs/heads/branch-symbolic-bad: badReferentName: points to invalid refname '\''refs/heads/branch '\''
- EOF
- rm $branch_dir_prefix/branch-symbolic-bad &&
- test_cmp expect err &&
-
- ln -sf ./".tag" $tag_dir_prefix/tag-symbolic-1 &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/tags/tag-symbolic-1: symlinkRef: use deprecated symbolic link for symref
- error: refs/tags/tag-symbolic-1: badReferentName: points to invalid refname '\''refs/tags/.tag'\''
- EOF
- rm $tag_dir_prefix/tag-symbolic-1 &&
- test_cmp expect err
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ ln -sf ./main $branch_dir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
+ EOF
+ rm $branch_dir_prefix/branch-symbolic-good &&
+ test_cmp expect err &&
+
+ ln -sf ../../logs/branch-escape $branch_dir_prefix/branch-symbolic &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
+ warning: refs/heads/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
+ EOF
+ rm $branch_dir_prefix/branch-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ./"branch " $branch_dir_prefix/branch-symbolic-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-symbolic-bad: symlinkRef: use deprecated symbolic link for symref
+ error: refs/heads/branch-symbolic-bad: badReferentName: points to invalid refname '\''refs/heads/branch '\''
+ EOF
+ rm $branch_dir_prefix/branch-symbolic-bad &&
+ test_cmp expect err &&
+
+ ln -sf ./".tag" $tag_dir_prefix/tag-symbolic-1 &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/tags/tag-symbolic-1: symlinkRef: use deprecated symbolic link for symref
+ error: refs/tags/tag-symbolic-1: badReferentName: points to invalid refname '\''refs/tags/.tag'\''
+ EOF
+ rm $tag_dir_prefix/tag-symbolic-1 &&
+ test_cmp expect err
+ )
'
test_expect_success SYMLINKS 'symlink symref content should be checked (worktree)' '
test_when_finished "rm -rf repo" &&
git init repo &&
- cd repo &&
- test_commit default &&
- git branch branch-1 &&
- git branch branch-2 &&
- git branch branch-3 &&
- git worktree add ./worktree-1 branch-2 &&
- git worktree add ./worktree-2 branch-3 &&
- main_worktree_refdir_prefix=.git/refs/heads &&
- worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
- worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
-
(
- cd worktree-1 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
- (
- cd worktree-2 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
-
- ln -sf ../../../../refs/heads/good-branch $worktree1_refdir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $worktree1_refdir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../../../worktrees/worktree-1/good-branch $worktree2_refdir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-2/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $worktree2_refdir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../worktrees/worktree-2/good-branch $main_worktree_refdir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $main_worktree_refdir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../../../logs/branch-escape $worktree1_refdir_prefix/branch-symbolic &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
- warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
- EOF
- rm $worktree1_refdir_prefix/branch-symbolic &&
- test_cmp expect err &&
-
- for bad_referent_name in ".tag" "branch "
- do
- ln -sf ./"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git branch branch-3 &&
+ git worktree add ./worktree-1 branch-2 &&
+ git worktree add ./worktree-2 branch-3 &&
+ main_worktree_refdir_prefix=.git/refs/heads &&
+ worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
+ worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
+
+ (
+ cd worktree-1 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+ (
+ cd worktree-2 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+
+ ln -sf ../../../../refs/heads/good-branch $worktree1_refdir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-1/refs/worktree/$bad_referent_name'\''
+ warning: worktrees/worktree-1/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
EOF
- rm $worktree1_refdir_prefix/bad-symbolic &&
+ rm $worktree1_refdir_prefix/branch-symbolic-good &&
test_cmp expect err &&
- ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ ln -sf ../../../../worktrees/worktree-1/good-branch $worktree2_refdir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ warning: worktrees/worktree-2/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
EOF
- rm $worktree1_refdir_prefix/bad-symbolic &&
+ rm $worktree2_refdir_prefix/branch-symbolic-good &&
test_cmp expect err &&
- ln -sf ./"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ ln -sf ../../worktrees/worktree-2/good-branch $main_worktree_refdir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-2/refs/worktree/$bad_referent_name'\''
+ warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
EOF
- rm $worktree2_refdir_prefix/bad-symbolic &&
+ rm $main_worktree_refdir_prefix/branch-symbolic-good &&
test_cmp expect err &&
- ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ ln -sf ../../../../logs/branch-escape $worktree1_refdir_prefix/branch-symbolic &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
+ warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
EOF
- rm $worktree2_refdir_prefix/bad-symbolic &&
- test_cmp expect err || return 1
- done
+ rm $worktree1_refdir_prefix/branch-symbolic &&
+ test_cmp expect err &&
+
+ for bad_referent_name in ".tag" "branch "
+ do
+ ln -sf ./"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-1/refs/worktree/$bad_referent_name'\''
+ EOF
+ rm $worktree1_refdir_prefix/bad-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ EOF
+ rm $worktree1_refdir_prefix/bad-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ./"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-2/refs/worktree/$bad_referent_name'\''
+ EOF
+ rm $worktree2_refdir_prefix/bad-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ EOF
+ rm $worktree2_refdir_prefix/bad-symbolic &&
+ test_cmp expect err || return 1
+ done
+ )
'
test_expect_success 'ref content checks should work with worktrees' '
test_when_finished "rm -rf repo" &&
git init repo &&
- cd repo &&
- test_commit default &&
- git branch branch-1 &&
- git branch branch-2 &&
- git branch branch-3 &&
- git worktree add ./worktree-1 branch-2 &&
- git worktree add ./worktree-2 branch-3 &&
- worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
- worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
-
(
- cd worktree-1 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
- (
- cd worktree-2 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git branch branch-3 &&
+ git worktree add ./worktree-1 branch-2 &&
+ git worktree add ./worktree-2 branch-3 &&
+ worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
+ worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
- for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$worktree1_refdir_prefix/bad-branch-1 &&
- test_must_fail git refs verify 2>err &&
+ (
+ cd worktree-1 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+ (
+ cd worktree-2 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+
+ for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$worktree1_refdir_prefix/bad-branch-1 &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-1/refs/worktree/bad-branch-1: badRefContent: $bad_content
+ EOF
+ rm $worktree1_refdir_prefix/bad-branch-1 &&
+ test_cmp expect err || return 1
+ done &&
+
+ for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$worktree2_refdir_prefix/bad-branch-2 &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-2/refs/worktree/bad-branch-2: badRefContent: $bad_content
+ EOF
+ rm $worktree2_refdir_prefix/bad-branch-2 &&
+ test_cmp expect err || return 1
+ done &&
+
+ printf "%s" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-no-newline &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- error: worktrees/worktree-1/refs/worktree/bad-branch-1: badRefContent: $bad_content
+ warning: worktrees/worktree-1/refs/worktree/branch-no-newline: refMissingNewline: misses LF at the end
EOF
- rm $worktree1_refdir_prefix/bad-branch-1 &&
- test_cmp expect err || return 1
- done &&
+ rm $worktree1_refdir_prefix/branch-no-newline &&
+ test_cmp expect err &&
- for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$worktree2_refdir_prefix/bad-branch-2 &&
- test_must_fail git refs verify 2>err &&
+ printf "%s garbage" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-garbage &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- error: worktrees/worktree-2/refs/worktree/bad-branch-2: badRefContent: $bad_content
+ warning: worktrees/worktree-1/refs/worktree/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
EOF
- rm $worktree2_refdir_prefix/bad-branch-2 &&
- test_cmp expect err || return 1
- done &&
-
- printf "%s" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-no-newline &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- rm $worktree1_refdir_prefix/branch-no-newline &&
- test_cmp expect err &&
-
- printf "%s garbage" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-garbage &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
- EOF
- rm $worktree1_refdir_prefix/branch-garbage &&
- test_cmp expect err
+ rm $worktree1_refdir_prefix/branch-garbage &&
+ test_cmp expect err
+ )
'
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v5 2/8] builtin/refs: get worktrees without reading head information
2025-02-17 15:25 ` [PATCH v5 " shejialuo
2025-02-17 15:27 ` [PATCH v5 1/8] t0602: use subshell to ensure working directory unchanged shejialuo
@ 2025-02-17 15:27 ` shejialuo
2025-02-25 8:26 ` Patrick Steinhardt
2025-02-17 15:27 ` [PATCH v5 3/8] packed-backend: check whether the "packed-refs" is regular file shejialuo
` (7 subsequent siblings)
9 siblings, 1 reply; 168+ messages in thread
From: shejialuo @ 2025-02-17 15:27 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
In "packed-backend.c", there are some functions such as "create_snapshot"
and "next_record" which would check the correctness of the content of
the "packed-ref" file. When anything is bad, the program will die.
It may seem that we have nothing relevant to above feature, because we
are going to read and parse the raw "packed-ref" file without creating
the snapshot and using the ref iterator to check the consistency.
However, when using "get_worktrees" in "builtin/refs", we would parse
the "HEAD" information. If the referent of the "HEAD" is inside the
"packed-ref", we will call "create_snapshot" function to parse the
"packed-ref" to get the information. No matter whether the entry of
"HEAD" in "packed-ref" is correct, "create_snapshot" would call
"verify_buffer_safe" to check whether there is a newline in the last
line of the file. If not, the program will die.
Although this behavior has no harm for the program, it will
short-circuit the program. When the users execute "git refs verify" or
"git fsck", we should avoid reading the head information, which may
execute the read operation in packed backend with stricter checks to die
the program. Instead, we should continue to check other parts of the
"packed-refs" file completely.
Fortunately, in 465a22b338 (worktree: skip reading HEAD when repairing
worktrees, 2023-12-29), we have introduced a function
"get_worktrees_internal" which allows us to get worktrees without
reading head information.
Create a new exposed function "get_worktrees_without_reading_head", then
replace the "get_worktrees" in "builtin/refs" with the new created
function.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
builtin/refs.c | 2 +-
worktree.c | 5 +++++
worktree.h | 7 +++++++
3 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/builtin/refs.c b/builtin/refs.c
index a29f195834..55ff5dae11 100644
--- a/builtin/refs.c
+++ b/builtin/refs.c
@@ -88,7 +88,7 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix,
git_config(git_fsck_config, &fsck_refs_options);
prepare_repo_settings(the_repository);
- worktrees = get_worktrees();
+ worktrees = get_worktrees_without_reading_head();
for (size_t i = 0; worktrees[i]; i++)
ret |= refs_fsck(get_worktree_ref_store(worktrees[i]),
&fsck_refs_options, worktrees[i]);
diff --git a/worktree.c b/worktree.c
index d4a68c9c23..d23482a746 100644
--- a/worktree.c
+++ b/worktree.c
@@ -198,6 +198,11 @@ struct worktree **get_worktrees(void)
return get_worktrees_internal(0);
}
+struct worktree **get_worktrees_without_reading_head(void)
+{
+ return get_worktrees_internal(1);
+}
+
const char *get_worktree_git_dir(const struct worktree *wt)
{
if (!wt)
diff --git a/worktree.h b/worktree.h
index 38145df80f..f7003a9c12 100644
--- a/worktree.h
+++ b/worktree.h
@@ -30,6 +30,13 @@ struct worktree {
*/
struct worktree **get_worktrees(void);
+/*
+ * Like `get_worktrees`, but does not read HEAD. Skip reading HEAD allows to
+ * get the worktree without worrying about failures pertaining to parsing
+ * the HEAD ref. This is useful when we want to check the ref db consistency.
+ */
+struct worktree **get_worktrees_without_reading_head(void);
+
/*
* Returns 1 if linked worktrees exist, 0 otherwise.
*/
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* Re: [PATCH v5 2/8] builtin/refs: get worktrees without reading head information
2025-02-17 15:27 ` [PATCH v5 2/8] builtin/refs: get worktrees without reading head information shejialuo
@ 2025-02-25 8:26 ` Patrick Steinhardt
0 siblings, 0 replies; 168+ messages in thread
From: Patrick Steinhardt @ 2025-02-25 8:26 UTC (permalink / raw)
To: shejialuo; +Cc: git, Karthik Nayak, Junio C Hamano, Michael Haggerty
On Mon, Feb 17, 2025 at 11:27:34PM +0800, shejialuo wrote:
> diff --git a/worktree.h b/worktree.h
> index 38145df80f..f7003a9c12 100644
> --- a/worktree.h
> +++ b/worktree.h
> @@ -30,6 +30,13 @@ struct worktree {
> */
> struct worktree **get_worktrees(void);
>
> +/*
> + * Like `get_worktrees`, but does not read HEAD. Skip reading HEAD allows to
> + * get the worktree without worrying about failures pertaining to parsing
> + * the HEAD ref. This is useful when we want to check the ref db consistency.
Nit, not worth a reroll: this is highly specific to what you're doing.
How about: "This is useful in contexts where it is assumed that the
refdb may not be in a consistent state." That would also include cases
like e.g. `repair_worktrees()`.
Patrick
^ permalink raw reply [flat|nested] 168+ messages in thread
* [PATCH v5 3/8] packed-backend: check whether the "packed-refs" is regular file
2025-02-17 15:25 ` [PATCH v5 " shejialuo
2025-02-17 15:27 ` [PATCH v5 1/8] t0602: use subshell to ensure working directory unchanged shejialuo
2025-02-17 15:27 ` [PATCH v5 2/8] builtin/refs: get worktrees without reading head information shejialuo
@ 2025-02-17 15:27 ` shejialuo
2025-02-25 8:27 ` Patrick Steinhardt
2025-02-17 15:27 ` [PATCH v5 4/8] packed-backend: add "packed-refs" header consistency check shejialuo
` (6 subsequent siblings)
9 siblings, 1 reply; 168+ messages in thread
From: shejialuo @ 2025-02-17 15:27 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
Although "git-fsck(1)" and "packed-backend.c" will check some
consistency and correctness of "packed-refs" file, they never check the
filetype of the "packed-refs". Let's verify that the "packed-refs" has
the expected filetype, confirming it is created by "git pack-refs"
command.
Use "lstat" to check the file mode. If we cannot check the file status
due to there is no such file this is OK because there is a possibility
that there is no "packed-refs" in the repo.
Reuse "FSCK_MSG_BAD_REF_FILETYPE" fsck message id to report the error to
the user if "packed-refs" is not a regular file.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
refs/packed-backend.c | 37 +++++++++++++++++++++++++++++++++----
t/t0602-reffiles-fsck.sh | 22 ++++++++++++++++++++++
2 files changed, 55 insertions(+), 4 deletions(-)
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index a7b6f74b6e..8140a31d07 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -4,6 +4,7 @@
#include "../git-compat-util.h"
#include "../config.h"
#include "../dir.h"
+#include "../fsck.h"
#include "../gettext.h"
#include "../hash.h"
#include "../hex.h"
@@ -1748,15 +1749,43 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
return empty_ref_iterator_begin();
}
-static int packed_fsck(struct ref_store *ref_store UNUSED,
- struct fsck_options *o UNUSED,
+static int packed_fsck(struct ref_store *ref_store,
+ struct fsck_options *o,
struct worktree *wt)
{
+ struct packed_ref_store *refs = packed_downcast(ref_store,
+ REF_STORE_READ, "fsck");
+ struct stat st;
+ int ret = 0;
if (!is_main_worktree(wt))
- return 0;
+ goto cleanup;
- return 0;
+ if (o->verbose)
+ fprintf_ln(stderr, "Checking packed-refs file %s", refs->path);
+
+ if (lstat(refs->path, &st) < 0) {
+ /*
+ * If the packed-refs file doesn't exist, there's nothing
+ * to check.
+ */
+ if (errno == ENOENT)
+ goto cleanup;
+ ret = error_errno(_("unable to stat %s"), refs->path);
+ goto cleanup;
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs";
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_FILETYPE,
+ "not a regular file");
+ goto cleanup;
+ }
+
+cleanup:
+ return ret;
}
struct ref_storage_be refs_be_packed = {
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index cf7a202d0d..e65ca341cd 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -617,4 +617,26 @@ test_expect_success 'ref content checks should work with worktrees' '
)
'
+test_expect_success SYMLINKS 'the filetype of packed-refs should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git branch branch-3 &&
+ git pack-refs --all &&
+
+ mv .git/packed-refs .git/packed-refs-back &&
+ ln -sf packed-refs-back .git/packed-refs &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs: badRefFiletype: not a regular file
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* Re: [PATCH v5 3/8] packed-backend: check whether the "packed-refs" is regular file
2025-02-17 15:27 ` [PATCH v5 3/8] packed-backend: check whether the "packed-refs" is regular file shejialuo
@ 2025-02-25 8:27 ` Patrick Steinhardt
0 siblings, 0 replies; 168+ messages in thread
From: Patrick Steinhardt @ 2025-02-25 8:27 UTC (permalink / raw)
To: shejialuo; +Cc: git, Karthik Nayak, Junio C Hamano, Michael Haggerty
On Mon, Feb 17, 2025 at 11:27:42PM +0800, shejialuo wrote:
> diff --git a/refs/packed-backend.c b/refs/packed-backend.c
> index a7b6f74b6e..8140a31d07 100644
> --- a/refs/packed-backend.c
> +++ b/refs/packed-backend.c
> @@ -1748,15 +1749,43 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
> return empty_ref_iterator_begin();
> }
>
> -static int packed_fsck(struct ref_store *ref_store UNUSED,
> - struct fsck_options *o UNUSED,
> +static int packed_fsck(struct ref_store *ref_store,
> + struct fsck_options *o,
> struct worktree *wt)
> {
> + struct packed_ref_store *refs = packed_downcast(ref_store,
> + REF_STORE_READ, "fsck");
> + struct stat st;
> + int ret = 0;
>
> if (!is_main_worktree(wt))
> - return 0;
> + goto cleanup;
>
> - return 0;
> + if (o->verbose)
> + fprintf_ln(stderr, "Checking packed-refs file %s", refs->path);
> +
> + if (lstat(refs->path, &st) < 0) {
> + /*
> + * If the packed-refs file doesn't exist, there's nothing
> + * to check.
> + */
> + if (errno == ENOENT)
> + goto cleanup;
> + ret = error_errno(_("unable to stat %s"), refs->path);
Nit: We should quote the file name: "unable to stat '%s'".
Patrick
^ permalink raw reply [flat|nested] 168+ messages in thread
* [PATCH v5 4/8] packed-backend: add "packed-refs" header consistency check
2025-02-17 15:25 ` [PATCH v5 " shejialuo
` (2 preceding siblings ...)
2025-02-17 15:27 ` [PATCH v5 3/8] packed-backend: check whether the "packed-refs" is regular file shejialuo
@ 2025-02-17 15:27 ` shejialuo
2025-02-25 8:27 ` Patrick Steinhardt
2025-02-17 15:27 ` [PATCH v5 5/8] packed-backend: check whether the refname contains NUL characters shejialuo
` (5 subsequent siblings)
9 siblings, 1 reply; 168+ messages in thread
From: shejialuo @ 2025-02-17 15:27 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
In "packed-backend.c::create_snapshot", if there is a header (the line
which starts with '#'), we will check whether the line starts with "#
pack-refs with:". Before we port this check into "packed_fsck", let's
fix "create_snapshot" to check the prefix "# packed-ref with: " instead
of "# packed-ref with:" due to that we will always write a single
trailing space after the colon.
However, we need to consider other situations and discuss whether we
need to add checks.
1. If the header does not exist, we should not report an error to the
user. This is because in older Git version, we never write header in
the "packed-refs" file. Also, we do allow no header in "packed-refs"
in runtime.
2. If the header content does not start with "# packed-ref with: ", we
should report an error just like what "create_snapshot" does. So,
create a new fsck message "badPackedRefHeader(ERROR)" for this.
3. If the header content is not the same as the constant string
"PACKED_REFS_HEADER". This is expected because we make it extensible
intentionally and runtime "create_snapshot" won't complain about
unknown traits. In order to align with the runtime behavior. There is
no need to report.
As we have analyzed, we only need to check the case 2 in the above. In
order to do this, use "open_nofollow" function to get the file
descriptor and then read the "packed-refs" file via "strbuf_read". Like
what "create_snapshot" and other functions do, we could split the line
by finding the next newline in the buffer. When we cannot find a
newline, we could report an error.
So, create a function "packed_fsck_ref_next_line" to find the next
newline and if there is no such newline, use
"packedRefEntryNotTerminated(ERROR)" to report an error to the user.
Then, parse the first line to apply the checks. Update the test to
exercise the code.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/fsck-msgids.adoc | 8 +++
fsck.h | 2 +
refs/packed-backend.c | 96 +++++++++++++++++++++++++++++++++-
t/t0602-reffiles-fsck.sh | 52 ++++++++++++++++++
4 files changed, 157 insertions(+), 1 deletion(-)
diff --git a/Documentation/fsck-msgids.adoc b/Documentation/fsck-msgids.adoc
index b14bc44ca4..11906f90fd 100644
--- a/Documentation/fsck-msgids.adoc
+++ b/Documentation/fsck-msgids.adoc
@@ -16,6 +16,10 @@
`badObjectSha1`::
(ERROR) An object has a bad sha1.
+`badPackedRefHeader`::
+ (ERROR) The "packed-refs" file contains an invalid
+ header.
+
`badParentSha1`::
(ERROR) A commit object has a bad parent sha1.
@@ -176,6 +180,10 @@
`nullSha1`::
(WARN) Tree contains entries pointing to a null sha1.
+`packedRefEntryNotTerminated`::
+ (ERROR) The "packed-refs" file contains an entry that is
+ not terminated by a newline.
+
`refMissingNewline`::
(INFO) A loose ref that does not end with newline(LF). As
valid implementations of Git never created such a loose ref
diff --git a/fsck.h b/fsck.h
index a44c231a5f..67e3c97bc0 100644
--- a/fsck.h
+++ b/fsck.h
@@ -30,6 +30,7 @@ enum fsck_msg_type {
FUNC(BAD_EMAIL, ERROR) \
FUNC(BAD_NAME, ERROR) \
FUNC(BAD_OBJECT_SHA1, ERROR) \
+ FUNC(BAD_PACKED_REF_HEADER, ERROR) \
FUNC(BAD_PARENT_SHA1, ERROR) \
FUNC(BAD_REF_CONTENT, ERROR) \
FUNC(BAD_REF_FILETYPE, ERROR) \
@@ -53,6 +54,7 @@ enum fsck_msg_type {
FUNC(MISSING_TYPE, ERROR) \
FUNC(MISSING_TYPE_ENTRY, ERROR) \
FUNC(MULTIPLE_AUTHORS, ERROR) \
+ FUNC(PACKED_REF_ENTRY_NOT_TERMINATED, ERROR) \
FUNC(TREE_NOT_SORTED, ERROR) \
FUNC(UNKNOWN_TYPE, ERROR) \
FUNC(ZERO_PADDED_DATE, ERROR) \
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 8140a31d07..09eb3886c3 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -694,7 +694,7 @@ static struct snapshot *create_snapshot(struct packed_ref_store *refs)
tmp = xmemdupz(snapshot->buf, eol - snapshot->buf);
- if (!skip_prefix(tmp, "# pack-refs with:", (const char **)&p))
+ if (!skip_prefix(tmp, "# pack-refs with: ", (const char **)&p))
die_invalid_line(refs->path,
snapshot->buf,
snapshot->eof - snapshot->buf);
@@ -1749,13 +1749,78 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
return empty_ref_iterator_begin();
}
+static int packed_fsck_ref_next_line(struct fsck_options *o,
+ unsigned long line_number, const char *start,
+ const char *eof, const char **eol)
+{
+ int ret = 0;
+
+ *eol = memchr(start, '\n', eof - start);
+ if (!*eol) {
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_PACKED_REF_ENTRY_NOT_TERMINATED,
+ "'%.*s' is not terminated with a newline",
+ (int)(eof - start), start);
+
+ /*
+ * There is no newline but we still want to parse it to the end of
+ * the buffer.
+ */
+ *eol = eof;
+ strbuf_release(&packed_entry);
+ }
+
+ return ret;
+}
+
+static int packed_fsck_ref_header(struct fsck_options *o,
+ const char *start, const char *eol)
+{
+ if (!starts_with(start, "# pack-refs with: ")) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs.header";
+
+ return fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_HEADER,
+ "'%.*s' does not start with '# pack-refs with: '",
+ (int)(eol - start), start);
+ }
+
+ return 0;
+}
+
+static int packed_fsck_ref_content(struct fsck_options *o,
+ const char *start, const char *eof)
+{
+ unsigned long line_number = 1;
+ const char *eol;
+ int ret = 0;
+
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ if (*start == '#') {
+ ret |= packed_fsck_ref_header(o, start, eol);
+
+ start = eol + 1;
+ line_number++;
+ }
+
+ return ret;
+}
+
static int packed_fsck(struct ref_store *ref_store,
struct fsck_options *o,
struct worktree *wt)
{
struct packed_ref_store *refs = packed_downcast(ref_store,
REF_STORE_READ, "fsck");
+ struct strbuf packed_ref_content = STRBUF_INIT;
struct stat st;
+ int fd;
int ret = 0;
if (!is_main_worktree(wt))
@@ -1784,7 +1849,36 @@ static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
+ /*
+ * There is a chance that "packed-refs" file is removed or converted to
+ * a symlink after filetype check and before open. So we need to avoid
+ * this race condition by opening the file.
+ */
+ fd = open_nofollow(refs->path, O_RDONLY);
+ if (fd < 0) {
+ if (errno == ENOENT)
+ goto cleanup;
+
+ if (errno == ELOOP) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs";
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_FILETYPE,
+ "not a regular file");
+ goto cleanup;
+ }
+ }
+
+ if (strbuf_read(&packed_ref_content, fd, 0) < 0) {
+ ret = error_errno(_("unable to read %s"), refs->path);
+ goto cleanup;
+ }
+
+ ret = packed_fsck_ref_content(o, packed_ref_content.buf,
+ packed_ref_content.buf + packed_ref_content.len);
+
cleanup:
+ strbuf_release(&packed_ref_content);
return ret;
}
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index e65ca341cd..e055c36e74 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -639,4 +639,56 @@ test_expect_success SYMLINKS 'the filetype of packed-refs should be checked' '
)
'
+test_expect_success 'packed-refs header should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+
+ git refs verify 2>err &&
+ test_must_be_empty err &&
+
+ for bad_header in "# pack-refs wit: peeled fully-peeled sorted " \
+ "# pack-refs with traits: peeled fully-peeled sorted " \
+ "# pack-refs with a: peeled fully-peeled" \
+ "# pack-refs with:peeled fully-peeled sorted"
+ do
+ printf "%s\n" "$bad_header" >.git/packed-refs &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs.header: badPackedRefHeader: '\''$bad_header'\'' does not start with '\''# pack-refs with: '\''
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err || return 1
+ done
+ )
+'
+
+test_expect_success 'packed-refs missing header should not be reported' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+
+ printf "$(git rev-parse HEAD) refs/heads/main\n" >.git/packed-refs &&
+ git refs verify 2>err &&
+ test_must_be_empty err
+ )
+'
+
+test_expect_success 'packed-refs unknown traits should not be reported' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+
+ printf "# pack-refs with: peeled fully-peeled sorted foo\n" >.git/packed-refs &&
+ git refs verify 2>err &&
+ test_must_be_empty err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* Re: [PATCH v5 4/8] packed-backend: add "packed-refs" header consistency check
2025-02-17 15:27 ` [PATCH v5 4/8] packed-backend: add "packed-refs" header consistency check shejialuo
@ 2025-02-25 8:27 ` Patrick Steinhardt
2025-02-25 12:34 ` shejialuo
0 siblings, 1 reply; 168+ messages in thread
From: Patrick Steinhardt @ 2025-02-25 8:27 UTC (permalink / raw)
To: shejialuo; +Cc: git, Karthik Nayak, Junio C Hamano, Michael Haggerty
On Mon, Feb 17, 2025 at 11:27:50PM +0800, shejialuo wrote:
> diff --git a/refs/packed-backend.c b/refs/packed-backend.c
> index 8140a31d07..09eb3886c3 100644
> --- a/refs/packed-backend.c
> +++ b/refs/packed-backend.c
> @@ -694,7 +694,7 @@ static struct snapshot *create_snapshot(struct packed_ref_store *refs)
>
> tmp = xmemdupz(snapshot->buf, eol - snapshot->buf);
>
> - if (!skip_prefix(tmp, "# pack-refs with:", (const char **)&p))
> + if (!skip_prefix(tmp, "# pack-refs with: ", (const char **)&p))
> die_invalid_line(refs->path,
> snapshot->buf,
> snapshot->eof - snapshot->buf);
I know that Junio pointed out that we should check for a trailing space
after the colon. But do we really feel comfortable to tighten the check
like this now? If there was any broken writer of the format that does
not include the whitespace we'd now be unable to parse their output.
I scanned through a couple of third-party clients:
- libgit2 is fine and always writes the space. It also expects the
whitespace to exist.
- JGit does not expect the header to have a trailing space, but
expects the "peeled" capability to have a leading space, which is
mostly equivalent because that capability is typically the first one
we write. It always writes the space.
- gitoxide expects the space to exist and writes it.
- go-git doesn't even seem to care about the header? Dunno, maybe I
was just not able to locate the relevant code.
So yes, we should be fine, and the fact that other implementations
expect the space to exist indicates that being more thorough here is a
good thing. It might be a good idea though to split out this change into
a separate commit and then provide more reasoning _why_ it is fine,
including the above info about alternate implementations.
Patrick
^ permalink raw reply [flat|nested] 168+ messages in thread
* Re: [PATCH v5 4/8] packed-backend: add "packed-refs" header consistency check
2025-02-25 8:27 ` Patrick Steinhardt
@ 2025-02-25 12:34 ` shejialuo
0 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-25 12:34 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git, Karthik Nayak, Junio C Hamano, Michael Haggerty
On Tue, Feb 25, 2025 at 09:27:03AM +0100, Patrick Steinhardt wrote:
> On Mon, Feb 17, 2025 at 11:27:50PM +0800, shejialuo wrote:
> > diff --git a/refs/packed-backend.c b/refs/packed-backend.c
> > index 8140a31d07..09eb3886c3 100644
> > --- a/refs/packed-backend.c
> > +++ b/refs/packed-backend.c
> > @@ -694,7 +694,7 @@ static struct snapshot *create_snapshot(struct packed_ref_store *refs)
> >
> > tmp = xmemdupz(snapshot->buf, eol - snapshot->buf);
> >
> > - if (!skip_prefix(tmp, "# pack-refs with:", (const char **)&p))
> > + if (!skip_prefix(tmp, "# pack-refs with: ", (const char **)&p))
> > die_invalid_line(refs->path,
> > snapshot->buf,
> > snapshot->eof - snapshot->buf);
>
> I know that Junio pointed out that we should check for a trailing space
> after the colon. But do we really feel comfortable to tighten the check
> like this now? If there was any broken writer of the format that does
> not include the whitespace we'd now be unable to parse their output.
>
> I scanned through a couple of third-party clients:
>
> - libgit2 is fine and always writes the space. It also expects the
> whitespace to exist.
>
> - JGit does not expect the header to have a trailing space, but
> expects the "peeled" capability to have a leading space, which is
> mostly equivalent because that capability is typically the first one
> we write. It always writes the space.
>
> - gitoxide expects the space to exist and writes it.
>
> - go-git doesn't even seem to care about the header? Dunno, maybe I
> was just not able to locate the relevant code.
I have searched the code. The go-git implement "git pack-refs" in
`PackRefs`. go-git never writes header for "packed-refs" file.
Thanks for this wonderful suggestion.
>
> So yes, we should be fine, and the fact that other implementations
> expect the space to exist indicates that being more thorough here is a
> good thing. It might be a good idea though to split out this change into
> a separate commit and then provide more reasoning _why_ it is fine,
> including the above info about alternate implementations.
>
Yes, I agree that we should split out this change. Let me do this.
> Patrick
^ permalink raw reply [flat|nested] 168+ messages in thread
* [PATCH v5 5/8] packed-backend: check whether the refname contains NUL characters
2025-02-17 15:25 ` [PATCH v5 " shejialuo
` (3 preceding siblings ...)
2025-02-17 15:27 ` [PATCH v5 4/8] packed-backend: add "packed-refs" header consistency check shejialuo
@ 2025-02-17 15:27 ` shejialuo
2025-02-17 15:28 ` [PATCH v5 6/8] packed-backend: add "packed-refs" entry consistency check shejialuo
` (4 subsequent siblings)
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-17 15:27 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
"packed-backend.c::next_record" will use "check_refname_format" to check
the consistency of the refname. If it is not OK, the program will die.
However, it is reported in [1], we cannot catch some corruption. But we
already have the code path and we must miss out something.
We use the following code to get the refname:
strbuf_add(&iter->refname_buf, p, eol - p);
iter->base.refname = iter->refname_buf.buf
In the above code, `p` is the start pointer of the refname and `eol` is
the next newline pointer. We calculate the length of the refname by
subtracting the two pointers. Then we add the memory range between `p`
and `eol` to get the refname.
However, if there are some NUL characters in the memory range between `p`
and `eol`, we will see the refname as a valid ref name as long as the
memory range between `p` and first occurred NUL character is valid.
In order to catch above corruption, create a new function
"refname_contains_nul" by searching the first NUL character. If it is
not at the end of the string, there must be some NUL characters in the
refname.
Use this function in "next_record" function to die the program if
"refname_contains_nul" returns true.
[1] https://lore.kernel.org/git/6cfee0e4-3285-4f18-91ff-d097da9de737@rd10.de/
Reported-by: R. Diez <rdiez-temp3@rd10.de>
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
refs/packed-backend.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 09eb3886c3..5edd2136bb 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -494,6 +494,21 @@ static void verify_buffer_safe(struct snapshot *snapshot)
last_line, eof - last_line);
}
+/*
+ * When parsing the "packed-refs" file, we will parse it line by line.
+ * Because we know the start pointer of the refname and the next
+ * newline pointer, we could calculate the length of the refname by
+ * subtracting the two pointers. However, there is a corner case where
+ * the refname contains corrupted embedded NUL characters. And
+ * `check_refname_format()` will not catch this when the truncated
+ * refname is still a valid refname. To prevent this, we need to check
+ * whether the refname contains the NUL characters.
+ */
+static int refname_contains_nul(struct strbuf *refname)
+{
+ return !!memchr(refname->buf, '\0', refname->len);
+}
+
#define SMALL_FILE_SIZE (32*1024)
/*
@@ -895,6 +910,9 @@ static int next_record(struct packed_ref_iterator *iter)
strbuf_add(&iter->refname_buf, p, eol - p);
iter->base.refname = iter->refname_buf.buf;
+ if (refname_contains_nul(&iter->refname_buf))
+ die("packed refname contains embedded NULL: %s", iter->base.refname);
+
if (check_refname_format(iter->base.refname, REFNAME_ALLOW_ONELEVEL)) {
if (!refname_is_safe(iter->base.refname))
die("packed refname is dangerous: %s",
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v5 6/8] packed-backend: add "packed-refs" entry consistency check
2025-02-17 15:25 ` [PATCH v5 " shejialuo
` (4 preceding siblings ...)
2025-02-17 15:27 ` [PATCH v5 5/8] packed-backend: check whether the refname contains NUL characters shejialuo
@ 2025-02-17 15:28 ` shejialuo
2025-02-17 15:28 ` [PATCH v5 7/8] packed-backend: check whether the "packed-refs" is sorted shejialuo
` (3 subsequent siblings)
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-17 15:28 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
"packed-backend.c::next_record" will parse the ref entry to check the
consistency. This function has already checked the following things:
1. Parse the main line of the ref entry to inspect whether the oid is
not correct. Then, check whether the next character is oid. Then
check the refname.
2. If the next line starts with '^', it would continue to parse the
peeled oid and check whether the last character is '\n'.
As we decide to implement the ref consistency check for "packed-refs",
let's port these two checks and update the test to exercise the code.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/fsck-msgids.adoc | 3 +
fsck.h | 1 +
refs/packed-backend.c | 122 ++++++++++++++++++++++++++++++++-
t/t0602-reffiles-fsck.sh | 44 ++++++++++++
4 files changed, 169 insertions(+), 1 deletion(-)
diff --git a/Documentation/fsck-msgids.adoc b/Documentation/fsck-msgids.adoc
index 11906f90fd..02a7bf0503 100644
--- a/Documentation/fsck-msgids.adoc
+++ b/Documentation/fsck-msgids.adoc
@@ -16,6 +16,9 @@
`badObjectSha1`::
(ERROR) An object has a bad sha1.
+`badPackedRefEntry`::
+ (ERROR) The "packed-refs" file contains an invalid entry.
+
`badPackedRefHeader`::
(ERROR) The "packed-refs" file contains an invalid
header.
diff --git a/fsck.h b/fsck.h
index 67e3c97bc0..14d70f6653 100644
--- a/fsck.h
+++ b/fsck.h
@@ -30,6 +30,7 @@ enum fsck_msg_type {
FUNC(BAD_EMAIL, ERROR) \
FUNC(BAD_NAME, ERROR) \
FUNC(BAD_OBJECT_SHA1, ERROR) \
+ FUNC(BAD_PACKED_REF_ENTRY, ERROR) \
FUNC(BAD_PACKED_REF_HEADER, ERROR) \
FUNC(BAD_PARENT_SHA1, ERROR) \
FUNC(BAD_REF_CONTENT, ERROR) \
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 5edd2136bb..c7138aefff 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1812,9 +1812,114 @@ static int packed_fsck_ref_header(struct fsck_options *o,
return 0;
}
+static int packed_fsck_ref_peeled_line(struct fsck_options *o,
+ struct ref_store *ref_store,
+ unsigned long line_number,
+ const char *start, const char *eol)
+{
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct object_id peeled;
+ const char *p;
+ int ret = 0;
+
+ /*
+ * Skip the '^' and parse the peeled oid.
+ */
+ start++;
+ if (parse_oid_hex_algop(start, &peeled, &p, ref_store->repo->hash_algo)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "'%.*s' has invalid peeled oid",
+ (int)(eol - start), start);
+ goto cleanup;
+ }
+
+ if (p != eol) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "has trailing garbage after peeled oid '%.*s'",
+ (int)(eol - p), p);
+ goto cleanup;
+ }
+
+cleanup:
+ strbuf_release(&packed_entry);
+ return ret;
+}
+
+static int packed_fsck_ref_main_line(struct fsck_options *o,
+ struct ref_store *ref_store,
+ unsigned long line_number,
+ struct strbuf *refname,
+ const char *start, const char *eol)
+{
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct object_id oid;
+ const char *p;
+ int ret = 0;
+
+ if (parse_oid_hex_algop(start, &oid, &p, ref_store->repo->hash_algo)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "'%.*s' has invalid oid",
+ (int)(eol - start), start);
+ goto cleanup;
+ }
+
+ if (p == eol || !isspace(*p)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "has no space after oid '%s' but with '%.*s'",
+ oid_to_hex(&oid), (int)(eol - p), p);
+ goto cleanup;
+ }
+
+ p++;
+ strbuf_reset(refname);
+ strbuf_add(refname, p, eol - p);
+ if (refname_contains_nul(refname)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "refname '%s' contains NULL binaries",
+ refname->buf);
+ }
+
+ if (check_refname_format(refname->buf, 0)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_NAME,
+ "has bad refname '%s'", refname->buf);
+ }
+
+cleanup:
+ strbuf_release(&packed_entry);
+ return ret;
+}
+
static int packed_fsck_ref_content(struct fsck_options *o,
+ struct ref_store *ref_store,
const char *start, const char *eof)
{
+ struct strbuf refname = STRBUF_INIT;
unsigned long line_number = 1;
const char *eol;
int ret = 0;
@@ -1827,6 +1932,21 @@ static int packed_fsck_ref_content(struct fsck_options *o,
line_number++;
}
+ while (start < eof) {
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ ret |= packed_fsck_ref_main_line(o, ref_store, line_number, &refname, start, eol);
+ start = eol + 1;
+ line_number++;
+ if (start < eof && *start == '^') {
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ ret |= packed_fsck_ref_peeled_line(o, ref_store, line_number,
+ start, eol);
+ start = eol + 1;
+ line_number++;
+ }
+ }
+
+ strbuf_release(&refname);
return ret;
}
@@ -1892,7 +2012,7 @@ static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
- ret = packed_fsck_ref_content(o, packed_ref_content.buf,
+ ret = packed_fsck_ref_content(o, ref_store, packed_ref_content.buf,
packed_ref_content.buf + packed_ref_content.len);
cleanup:
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index e055c36e74..7421cc1e7f 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -691,4 +691,48 @@ test_expect_success 'packed-refs unknown traits should not be reported' '
)
'
+test_expect_success 'packed-refs content should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git tag -a annotated-tag-1 -m tag-1 &&
+ git tag -a annotated-tag-2 -m tag-2 &&
+
+ branch_1_oid=$(git rev-parse branch-1) &&
+ branch_2_oid=$(git rev-parse branch-2) &&
+ tag_1_oid=$(git rev-parse annotated-tag-1) &&
+ tag_2_oid=$(git rev-parse annotated-tag-2) &&
+ tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
+ tag_2_peeled_oid=$(git rev-parse annotated-tag-2^{}) &&
+ short_oid=$(printf "%s" $tag_1_peeled_oid | cut -c 1-4) &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ $short_oid refs/heads/branch-1
+ ${branch_1_oid}x
+ $branch_2_oid refs/heads/bad-branch
+ $branch_2_oid refs/heads/branch.
+ $tag_1_oid refs/tags/annotated-tag-3
+ ^$short_oid
+ $tag_2_oid refs/tags/annotated-tag-4.
+ ^$tag_2_peeled_oid garbage
+ EOF
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs line 2: badPackedRefEntry: '\''$short_oid refs/heads/branch-1'\'' has invalid oid
+ error: packed-refs line 3: badPackedRefEntry: has no space after oid '\''$branch_1_oid'\'' but with '\''x'\''
+ error: packed-refs line 4: badRefName: has bad refname '\'' refs/heads/bad-branch'\''
+ error: packed-refs line 5: badRefName: has bad refname '\''refs/heads/branch.'\''
+ error: packed-refs line 7: badPackedRefEntry: '\''$short_oid'\'' has invalid peeled oid
+ error: packed-refs line 8: badRefName: has bad refname '\''refs/tags/annotated-tag-4.'\''
+ error: packed-refs line 9: badPackedRefEntry: has trailing garbage after peeled oid '\'' garbage'\''
+ EOF
+ test_cmp expect err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v5 7/8] packed-backend: check whether the "packed-refs" is sorted
2025-02-17 15:25 ` [PATCH v5 " shejialuo
` (5 preceding siblings ...)
2025-02-17 15:28 ` [PATCH v5 6/8] packed-backend: add "packed-refs" entry consistency check shejialuo
@ 2025-02-17 15:28 ` shejialuo
2025-02-17 15:28 ` [PATCH v5 8/8] builtin/fsck: add `git refs verify` child process shejialuo
` (2 subsequent siblings)
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-17 15:28 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
When there is a "sorted" trait in the header of the "packed-refs" file,
it means that each entry is sorted increasingly by comparing the
refname. We should add checks to verify whether the "packed-refs" is
sorted in this case.
Update the "packed_fsck_ref_header" to know whether there is a "sorted"
trail in the header. It may seem that we could record all refnames
during the parsing process and then compare later. However, this is not
a good design due to the following reasons:
1. Because we need to store the state across the whole checking
lifetime, we would consume a lot of memory if there are many entries
in the "packed-refs" file.
2. We cannot reuse the existing compare function "cmp_packed_ref_records"
which cause repetition.
Because "cmp_packed_ref_records" needs an extra parameter "struct
snaphost", extract the common part into a new function
"cmp_packed_ref_records" to reuse this function to compare.
Then, create a new function "packed_fsck_ref_sorted" to parse the file
again and user the new fsck message "packedRefUnsorted(ERROR)" to report
to the user if the file is not sorted.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/fsck-msgids.adoc | 3 +
fsck.h | 1 +
refs/packed-backend.c | 118 ++++++++++++++++++++++++++++-----
t/t0602-reffiles-fsck.sh | 87 ++++++++++++++++++++++++
4 files changed, 192 insertions(+), 17 deletions(-)
diff --git a/Documentation/fsck-msgids.adoc b/Documentation/fsck-msgids.adoc
index 02a7bf0503..9601fff228 100644
--- a/Documentation/fsck-msgids.adoc
+++ b/Documentation/fsck-msgids.adoc
@@ -187,6 +187,9 @@
(ERROR) The "packed-refs" file contains an entry that is
not terminated by a newline.
+`packedRefUnsorted`::
+ (ERROR) The "packed-refs" file is not sorted.
+
`refMissingNewline`::
(INFO) A loose ref that does not end with newline(LF). As
valid implementations of Git never created such a loose ref
diff --git a/fsck.h b/fsck.h
index 14d70f6653..19f3cb2773 100644
--- a/fsck.h
+++ b/fsck.h
@@ -56,6 +56,7 @@ enum fsck_msg_type {
FUNC(MISSING_TYPE_ENTRY, ERROR) \
FUNC(MULTIPLE_AUTHORS, ERROR) \
FUNC(PACKED_REF_ENTRY_NOT_TERMINATED, ERROR) \
+ FUNC(PACKED_REF_UNSORTED, ERROR) \
FUNC(TREE_NOT_SORTED, ERROR) \
FUNC(UNKNOWN_TYPE, ERROR) \
FUNC(ZERO_PADDED_DATE, ERROR) \
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index c7138aefff..ae04d8ae80 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -300,14 +300,9 @@ struct snapshot_record {
size_t len;
};
-static int cmp_packed_ref_records(const void *v1, const void *v2,
- void *cb_data)
-{
- const struct snapshot *snapshot = cb_data;
- const struct snapshot_record *e1 = v1, *e2 = v2;
- const char *r1 = e1->start + snapshot_hexsz(snapshot) + 1;
- const char *r2 = e2->start + snapshot_hexsz(snapshot) + 1;
+static int cmp_packed_refname(const char *r1, const char *r2)
+{
while (1) {
if (*r1 == '\n')
return *r2 == '\n' ? 0 : -1;
@@ -322,6 +317,17 @@ static int cmp_packed_ref_records(const void *v1, const void *v2,
}
}
+static int cmp_packed_ref_records(const void *v1, const void *v2,
+ void *cb_data)
+{
+ const struct snapshot *snapshot = cb_data;
+ const struct snapshot_record *e1 = v1, *e2 = v2;
+ const char *r1 = e1->start + snapshot_hexsz(snapshot) + 1;
+ const char *r2 = e2->start + snapshot_hexsz(snapshot) + 1;
+
+ return cmp_packed_refname(r1, r2);
+}
+
/*
* Compare a snapshot record at `rec` to the specified NUL-terminated
* refname.
@@ -1797,19 +1803,33 @@ static int packed_fsck_ref_next_line(struct fsck_options *o,
}
static int packed_fsck_ref_header(struct fsck_options *o,
- const char *start, const char *eol)
+ const char *start, const char *eol,
+ unsigned int *sorted)
{
- if (!starts_with(start, "# pack-refs with: ")) {
+ struct string_list traits = STRING_LIST_INIT_NODUP;
+ char *tmp_line;
+ int ret = 0;
+ char *p;
+
+ tmp_line = xmemdupz(start, eol - start);
+ if (!skip_prefix(tmp_line, "# pack-refs with: ", (const char **)&p)) {
struct fsck_ref_report report = { 0 };
report.path = "packed-refs.header";
- return fsck_report_ref(o, &report,
- FSCK_MSG_BAD_PACKED_REF_HEADER,
- "'%.*s' does not start with '# pack-refs with: '",
- (int)(eol - start), start);
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_HEADER,
+ "'%.*s' does not start with '# pack-refs with: '",
+ (int)(eol - start), start);
+ goto cleanup;
}
- return 0;
+ string_list_split_in_place(&traits, p, " ", -1);
+ *sorted = unsorted_string_list_has_string(&traits, "sorted");
+
+cleanup:
+ free(tmp_line);
+ string_list_clear(&traits, 0);
+ return ret;
}
static int packed_fsck_ref_peeled_line(struct fsck_options *o,
@@ -1915,8 +1935,68 @@ static int packed_fsck_ref_main_line(struct fsck_options *o,
return ret;
}
+static int packed_fsck_ref_sorted(struct fsck_options *o,
+ struct ref_store *ref_store,
+ const char *start, const char *eof)
+{
+ size_t hexsz = ref_store->repo->hash_algo->hexsz;
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct strbuf refname1 = STRBUF_INIT;
+ struct strbuf refname2 = STRBUF_INIT;
+ unsigned long line_number = 1;
+ const char *former = NULL;
+ const char *current;
+ const char *eol;
+ int ret = 0;
+
+ if (*start == '#') {
+ eol = memchr(start, '\n', eof - start);
+ start = eol + 1;
+ line_number++;
+ }
+
+ for (; start < eof; line_number++, start = eol + 1) {
+ eol = memchr(start, '\n', eof - start);
+
+ if (*start == '^')
+ continue;
+
+ if (!former) {
+ former = start + hexsz + 1;
+ continue;
+ }
+
+ current = start + hexsz + 1;
+ if (cmp_packed_refname(former, current) >= 0) {
+ const char *err_fmt =
+ "refname '%s' is less than previous refname '%s'";
+
+ eol = memchr(former, '\n', eof - former);
+ strbuf_add(&refname1, former, eol - former);
+ eol = memchr(current, '\n', eof - current);
+ strbuf_add(&refname2, current, eol - current);
+
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_PACKED_REF_UNSORTED,
+ err_fmt, refname2.buf, refname1.buf);
+ goto cleanup;
+ }
+ former = current;
+ }
+
+cleanup:
+ strbuf_release(&packed_entry);
+ strbuf_release(&refname1);
+ strbuf_release(&refname2);
+ return ret;
+}
+
static int packed_fsck_ref_content(struct fsck_options *o,
struct ref_store *ref_store,
+ unsigned int *sorted,
const char *start, const char *eof)
{
struct strbuf refname = STRBUF_INIT;
@@ -1926,7 +2006,7 @@ static int packed_fsck_ref_content(struct fsck_options *o,
ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
if (*start == '#') {
- ret |= packed_fsck_ref_header(o, start, eol);
+ ret |= packed_fsck_ref_header(o, start, eol, sorted);
start = eol + 1;
line_number++;
@@ -1957,9 +2037,10 @@ static int packed_fsck(struct ref_store *ref_store,
struct packed_ref_store *refs = packed_downcast(ref_store,
REF_STORE_READ, "fsck");
struct strbuf packed_ref_content = STRBUF_INIT;
+ unsigned int sorted = 0;
struct stat st;
- int fd;
int ret = 0;
+ int fd;
if (!is_main_worktree(wt))
goto cleanup;
@@ -2012,8 +2093,11 @@ static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
- ret = packed_fsck_ref_content(o, ref_store, packed_ref_content.buf,
+ ret = packed_fsck_ref_content(o, ref_store, &sorted, packed_ref_content.buf,
packed_ref_content.buf + packed_ref_content.len);
+ if (!ret && sorted)
+ ret = packed_fsck_ref_sorted(o, ref_store, packed_ref_content.buf,
+ packed_ref_content.buf + packed_ref_content.len);
cleanup:
strbuf_release(&packed_ref_content);
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index 7421cc1e7f..28dc8dcddc 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -735,4 +735,91 @@ test_expect_success 'packed-refs content should be checked' '
)
'
+test_expect_success 'packed-ref with sorted trait should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git tag -a annotated-tag-1 -m tag-1 &&
+ branch_1_oid=$(git rev-parse branch-1) &&
+ branch_2_oid=$(git rev-parse branch-2) &&
+ tag_1_oid=$(git rev-parse annotated-tag-1) &&
+ tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
+ refname1="refs/heads/main" &&
+ refname2="refs/heads/foo" &&
+ refname3="refs/tags/foo" &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ EOF
+ git refs verify 2>err &&
+ rm .git/packed-refs &&
+ test_must_be_empty err &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ $branch_2_oid $refname1
+ EOF
+ git refs verify 2>err &&
+ rm .git/packed-refs &&
+ test_must_be_empty err &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ $branch_2_oid $refname1
+ $branch_1_oid $refname2
+ $tag_1_oid $refname3
+ EOF
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs line 3: packedRefUnsorted: refname '\''$refname2'\'' is less than previous refname '\''$refname1'\''
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ $tag_1_oid $refname3
+ ^$tag_1_peeled_oid
+ $branch_2_oid $refname2
+ EOF
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs line 4: packedRefUnsorted: refname '\''$refname2'\'' is less than previous refname '\''$refname3'\''
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err
+ )
+'
+
+test_expect_success 'packed-ref without sorted trait should not be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git tag -a annotated-tag-1 -m tag-1 &&
+ branch_1_oid=$(git rev-parse branch-1) &&
+ branch_2_oid=$(git rev-parse branch-2) &&
+ tag_1_oid=$(git rev-parse annotated-tag-1) &&
+ tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
+ refname1="refs/heads/main" &&
+ refname2="refs/heads/foo" &&
+ refname3="refs/tags/foo" &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled
+ $branch_2_oid $refname1
+ $branch_1_oid $refname2
+ EOF
+ git refs verify 2>err &&
+ test_must_be_empty err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v5 8/8] builtin/fsck: add `git refs verify` child process
2025-02-17 15:25 ` [PATCH v5 " shejialuo
` (6 preceding siblings ...)
2025-02-17 15:28 ` [PATCH v5 7/8] packed-backend: check whether the "packed-refs" is sorted shejialuo
@ 2025-02-17 15:28 ` shejialuo
2025-02-25 8:27 ` [PATCH v5 0/8] add more ref consistency checks Patrick Steinhardt
2025-02-25 13:19 ` [PATCH v6 0/9] " shejialuo
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-17 15:28 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
At now, we have already implemented the ref consistency checks for both
"files-backend" and "packed-backend". Although we would check some
redundant things, it won't cause trouble. So, let's integrate it into
the "git-fsck(1)" command to get feedback from the users. And also by
calling "git refs verify" in "git-fsck(1)", we make sure that the new
added checks don't break.
Introduce a new function "fsck_refs" that initializes and runs a child
process to execute the "git refs verify" command. In order to provide
the user interface create a progress which makes the total task be 1.
It's hard to know how many loose refs we will check now. We might
improve this later.
Then, introduce the option to allow the user to disable checking ref
database consistency. Put this function in the very first execution
sequence of "git-fsck(1)" due to that we don't want the existing code of
"git-fsck(1)" which would implicitly check the consistency of refs to
die the program.
Last, update the test to exercise the code.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/git-fsck.adoc | 7 ++++++-
builtin/fsck.c | 33 ++++++++++++++++++++++++++++++-
t/t0602-reffiles-fsck.sh | 39 +++++++++++++++++++++++++++++++++++++
3 files changed, 77 insertions(+), 2 deletions(-)
diff --git a/Documentation/git-fsck.adoc b/Documentation/git-fsck.adoc
index 8f32800a83..11203ba925 100644
--- a/Documentation/git-fsck.adoc
+++ b/Documentation/git-fsck.adoc
@@ -12,7 +12,7 @@ SYNOPSIS
'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]
[--[no-]full] [--strict] [--verbose] [--lost-found]
[--[no-]dangling] [--[no-]progress] [--connectivity-only]
- [--[no-]name-objects] [<object>...]
+ [--[no-]name-objects] [--[no-]references] [<object>...]
DESCRIPTION
-----------
@@ -104,6 +104,11 @@ care about this output and want to speed it up further.
progress status even if the standard error stream is not
directed to a terminal.
+--[no-]references::
+ Control whether to check the references database consistency
+ via 'git refs verify'. See linkgit:git-refs[1] for details.
+ The default is to check the references database.
+
CONFIGURATION
-------------
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 7a4dcb0716..f4f395cfbd 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -50,6 +50,7 @@ static int verbose;
static int show_progress = -1;
static int show_dangling = 1;
static int name_objects;
+static int check_references = 1;
#define ERROR_OBJECT 01
#define ERROR_REACHABLE 02
#define ERROR_PACK 04
@@ -905,11 +906,37 @@ static int check_pack_rev_indexes(struct repository *r, int show_progress)
return res;
}
+static void fsck_refs(struct repository *r)
+{
+ struct child_process refs_verify = CHILD_PROCESS_INIT;
+ struct progress *progress = NULL;
+
+ if (show_progress)
+ progress = start_progress(r, _("Checking ref database"), 1);
+
+ if (verbose)
+ fprintf_ln(stderr, _("Checking ref database"));
+
+ child_process_init(&refs_verify);
+ refs_verify.git_cmd = 1;
+ strvec_pushl(&refs_verify.args, "refs", "verify", NULL);
+ if (verbose)
+ strvec_push(&refs_verify.args, "--verbose");
+ if (check_strict)
+ strvec_push(&refs_verify.args, "--strict");
+
+ if (run_command(&refs_verify))
+ errors_found |= ERROR_REFS;
+
+ display_progress(progress, 1);
+ stop_progress(&progress);
+}
+
static char const * const fsck_usage[] = {
N_("git fsck [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]\n"
" [--[no-]full] [--strict] [--verbose] [--lost-found]\n"
" [--[no-]dangling] [--[no-]progress] [--connectivity-only]\n"
- " [--[no-]name-objects] [<object>...]"),
+ " [--[no-]name-objects] [--[no-]references] [<object>...]"),
NULL
};
@@ -928,6 +955,7 @@ static struct option fsck_opts[] = {
N_("write dangling objects in .git/lost-found")),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
OPT_BOOL(0, "name-objects", &name_objects, N_("show verbose names for reachable objects")),
+ OPT_BOOL(0, "references", &check_references, N_("check reference database consistency")),
OPT_END(),
};
@@ -970,6 +998,9 @@ int cmd_fsck(int argc,
git_config(git_fsck_config, &fsck_obj_options);
prepare_repo_settings(the_repository);
+ if (check_references)
+ fsck_refs(the_repository);
+
if (connectivity_only) {
for_each_loose_object(mark_loose_for_connectivity, NULL, 0);
for_each_packed_object(the_repository,
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index 28dc8dcddc..42e8a84739 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -822,4 +822,43 @@ test_expect_success 'packed-ref without sorted trait should not be checked' '
)
'
+test_expect_success '--[no-]references option should apply to fsck' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ branch_dir_prefix=.git/refs/heads &&
+ (
+ cd repo &&
+ test_commit default &&
+ for trailing_content in " garbage" " more garbage"
+ do
+ printf "%s" "$(git rev-parse HEAD)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ git fsck 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage &&
+ test_cmp expect err || return 1
+ done &&
+
+ for trailing_content in " garbage" " more garbage"
+ do
+ printf "%s" "$(git rev-parse HEAD)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ git fsck --references 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage &&
+ test_cmp expect err || return 1
+ done &&
+
+ for trailing_content in " garbage" " more garbage"
+ do
+ printf "%s" "$(git rev-parse HEAD)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ git fsck --no-references 2>err &&
+ rm $branch_dir_prefix/branch-garbage &&
+ test_must_be_empty err || return 1
+ done
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* Re: [PATCH v5 0/8] add more ref consistency checks
2025-02-17 15:25 ` [PATCH v5 " shejialuo
` (7 preceding siblings ...)
2025-02-17 15:28 ` [PATCH v5 8/8] builtin/fsck: add `git refs verify` child process shejialuo
@ 2025-02-25 8:27 ` Patrick Steinhardt
2025-02-25 13:19 ` [PATCH v6 0/9] " shejialuo
9 siblings, 0 replies; 168+ messages in thread
From: Patrick Steinhardt @ 2025-02-25 8:27 UTC (permalink / raw)
To: shejialuo; +Cc: git, Karthik Nayak, Junio C Hamano, Michael Haggerty
On Mon, Feb 17, 2025 at 11:25:25PM +0800, shejialuo wrote:
> Hi All:
>
> This changes enhances the following things:
>
> 1. [PATCH v5 2/8]: enhance the comment suggested by Karthik.
> 2. [PATCH v5 3/8]: use lstat to check whether the filetype of
> "packed-ref" is a regular file instead of using `open_nofollow`
> to check. And also enhance the commit message suggested by Karthik.
> 3. [PATCH v5 4/8]: move "open_nofollow" in original [PATCH v4 3/8] to
> this.
>
> Also, I rebase due to the conflict that all *.txt files have been
> renamed to *.adoc. However, I don't know whether this is a real
> conflict. But I decide to rebase to make the life of Junio easy.
I've got a couple of small nits, but overall I think this series should
be almost ready. Thanks!
Patrick
^ permalink raw reply [flat|nested] 168+ messages in thread
* [PATCH v6 0/9] add more ref consistency checks
2025-02-17 15:25 ` [PATCH v5 " shejialuo
` (8 preceding siblings ...)
2025-02-25 8:27 ` [PATCH v5 0/8] add more ref consistency checks Patrick Steinhardt
@ 2025-02-25 13:19 ` shejialuo
2025-02-25 13:21 ` [PATCH v6 1/9] t0602: use subshell to ensure working directory unchanged shejialuo
` (9 more replies)
9 siblings, 10 replies; 168+ messages in thread
From: shejialuo @ 2025-02-25 13:19 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
Hi All:
This changes enhances the following things (v6-changed):
1. [PATCH v6 2/9]: enhance the comment.
2. [PATCH v6 3/9]: use '' to quote the file in the print message.
2. [PATCH v6 4/9]: a new commit message to explain why we can tighten
the rule.
Thanks,
Jialuo
---
This series mainly does the following things:
1. Fix subshell issues
2. Add ref checks for packed-backend.
1. Check whether the filetype of "packed-refs" is correct.
2. Check whether the syntax of "packed-refs" is correct by using the
rules from "packed-backend.c::create_snapshot" and
"packed-backend.c::next_record".
3. Check whether the pointed object exists and whether the
"packed-refs" file is sorted.
3. Call "git refs verify" for "git-fsck(1)".
shejialuo (9):
t0602: use subshell to ensure working directory unchanged
builtin/refs: get worktrees without reading head information
packed-backend: check whether the "packed-refs" is regular file
packed-backend: check if header starts with "# pack-refs with: "
packed-backend: add "packed-refs" header consistency check
packed-backend: check whether the refname contains NUL characters
packed-backend: add "packed-refs" entry consistency check
packed-backend: check whether the "packed-refs" is sorted
builtin/fsck: add `git refs verify` child process
Documentation/fsck-msgids.adoc | 14 +
Documentation/git-fsck.adoc | 7 +-
builtin/fsck.c | 33 +-
builtin/refs.c | 2 +-
fsck.h | 4 +
refs/packed-backend.c | 369 +++++++++-
t/t0602-reffiles-fsck.sh | 1205 +++++++++++++++++++-------------
worktree.c | 5 +
worktree.h | 8 +
9 files changed, 1162 insertions(+), 485 deletions(-)
Range-diff against v5:
1: b3952d80a2 = 1: b3952d80a2 t0602: use subshell to ensure working directory unchanged
2: 3695586f58 ! 2: fa5ce20bb7 builtin/refs: get worktrees without reading head information
@@ worktree.h: struct worktree {
+/*
+ * Like `get_worktrees`, but does not read HEAD. Skip reading HEAD allows to
+ * get the worktree without worrying about failures pertaining to parsing
-+ * the HEAD ref. This is useful when we want to check the ref db consistency.
++ * the HEAD ref. This is useful in contexts where it is assumed that the
++ * refdb may not be in a consistent state.
+ */
+struct worktree **get_worktrees_without_reading_head(void);
+
3: cbaae00e8b ! 3: 787645a700 packed-backend: check whether the "packed-refs" is regular file
@@ refs/packed-backend.c: static struct ref_iterator *packed_reflog_iterator_begin(
+ */
+ if (errno == ENOENT)
+ goto cleanup;
-+ ret = error_errno(_("unable to stat %s"), refs->path);
++ ret = error_errno(_("unable to stat '%s'"), refs->path);
+ goto cleanup;
+ }
+
-: ---------- > 4: f097e0f093 packed-backend: check if header starts with "# pack-refs with: "
4: b9ce8734ac ! 5: a589a38b68 packed-backend: add "packed-refs" header consistency check
@@ Commit message
In "packed-backend.c::create_snapshot", if there is a header (the line
which starts with '#'), we will check whether the line starts with "#
- pack-refs with:". Before we port this check into "packed_fsck", let's
- fix "create_snapshot" to check the prefix "# packed-ref with: " instead
- of "# packed-ref with:" due to that we will always write a single
- trailing space after the colon.
-
- However, we need to consider other situations and discuss whether we
- need to add checks.
+ pack-refs with: ". However, we need to consider other situations and
+ discuss whether we need to add checks.
1. If the header does not exist, we should not report an error to the
user. This is because in older Git version, we never write header in
@@ fsck.h: enum fsck_msg_type {
FUNC(ZERO_PADDED_DATE, ERROR) \
## refs/packed-backend.c ##
-@@ refs/packed-backend.c: static struct snapshot *create_snapshot(struct packed_ref_store *refs)
-
- tmp = xmemdupz(snapshot->buf, eol - snapshot->buf);
-
-- if (!skip_prefix(tmp, "# pack-refs with:", (const char **)&p))
-+ if (!skip_prefix(tmp, "# pack-refs with: ", (const char **)&p))
- die_invalid_line(refs->path,
- snapshot->buf,
- snapshot->eof - snapshot->buf);
@@ refs/packed-backend.c: static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
return empty_ref_iterator_begin();
}
5: 9f638b3adf = 6: 7255c2b597 packed-backend: check whether the refname contains NUL characters
6: 2c5395bdd0 = 7: 7794a2ebfd packed-backend: add "packed-refs" entry consistency check
7: 648404c60d = 8: 2a9138b14d packed-backend: check whether the "packed-refs" is sorted
8: 4dbbacf44b = 9: ccde32491f builtin/fsck: add `git refs verify` child process
--
2.48.1
^ permalink raw reply [flat|nested] 168+ messages in thread
* [PATCH v6 1/9] t0602: use subshell to ensure working directory unchanged
2025-02-25 13:19 ` [PATCH v6 0/9] " shejialuo
@ 2025-02-25 13:21 ` shejialuo
2025-02-25 13:21 ` [PATCH v6 2/9] builtin/refs: get worktrees without reading head information shejialuo
` (8 subsequent siblings)
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-25 13:21 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
For every test, we would execute the command "cd repo" in the first but
we never execute the command "cd .." to restore the working directory.
However, it's either not a good idea use above way. Because if any test
fails between "cd repo" and "cd ..", the "cd .." will never be reached.
And we cannot correctly restore the working directory.
Let's use subshell to ensure that the current working directory could be
restored to the correct path.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
t/t0602-reffiles-fsck.sh | 967 ++++++++++++++++++++-------------------
1 file changed, 494 insertions(+), 473 deletions(-)
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index d4a08b823b..cf7a202d0d 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -14,222 +14,229 @@ test_expect_success 'ref name should be checked' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
-
- git commit --allow-empty -m initial &&
- git checkout -b default-branch &&
- git tag default-tag &&
- git tag multi_hierarchy/default-tag &&
-
- cp $branch_dir_prefix/default-branch $branch_dir_prefix/@ &&
- git refs verify 2>err &&
- test_must_be_empty err &&
- rm $branch_dir_prefix/@ &&
-
- cp $tag_dir_prefix/default-tag $tag_dir_prefix/tag-1.lock &&
- git refs verify 2>err &&
- rm $tag_dir_prefix/tag-1.lock &&
- test_must_be_empty err &&
-
- cp $tag_dir_prefix/default-tag $tag_dir_prefix/.lock &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/tags/.lock: badRefName: invalid refname format
- EOF
- rm $tag_dir_prefix/.lock &&
- test_cmp expect err &&
-
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname" &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/$refname: badRefName: invalid refname format
- EOF
- rm "$branch_dir_prefix/$refname" &&
- test_cmp expect err || return 1
- done &&
+ (
+ cd repo &&
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- cp $tag_dir_prefix/default-tag "$tag_dir_prefix/$refname" &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/tags/$refname: badRefName: invalid refname format
- EOF
- rm "$tag_dir_prefix/$refname" &&
- test_cmp expect err || return 1
- done &&
+ git commit --allow-empty -m initial &&
+ git checkout -b default-branch &&
+ git tag default-tag &&
+ git tag multi_hierarchy/default-tag &&
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- cp $tag_dir_prefix/multi_hierarchy/default-tag "$tag_dir_prefix/multi_hierarchy/$refname" &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/tags/multi_hierarchy/$refname: badRefName: invalid refname format
- EOF
- rm "$tag_dir_prefix/multi_hierarchy/$refname" &&
- test_cmp expect err || return 1
- done &&
-
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- mkdir "$branch_dir_prefix/$refname" &&
- cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname/default-branch" &&
+ cp $branch_dir_prefix/default-branch $branch_dir_prefix/@ &&
+ git refs verify 2>err &&
+ test_must_be_empty err &&
+ rm $branch_dir_prefix/@ &&
+
+ cp $tag_dir_prefix/default-tag $tag_dir_prefix/tag-1.lock &&
+ git refs verify 2>err &&
+ rm $tag_dir_prefix/tag-1.lock &&
+ test_must_be_empty err &&
+
+ cp $tag_dir_prefix/default-tag $tag_dir_prefix/.lock &&
test_must_fail git refs verify 2>err &&
cat >expect <<-EOF &&
- error: refs/heads/$refname/default-branch: badRefName: invalid refname format
+ error: refs/tags/.lock: badRefName: invalid refname format
EOF
- rm -r "$branch_dir_prefix/$refname" &&
- test_cmp expect err || return 1
- done
+ rm $tag_dir_prefix/.lock &&
+ test_cmp expect err &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/$refname: badRefName: invalid refname format
+ EOF
+ rm "$branch_dir_prefix/$refname" &&
+ test_cmp expect err || return 1
+ done &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ cp $tag_dir_prefix/default-tag "$tag_dir_prefix/$refname" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/tags/$refname: badRefName: invalid refname format
+ EOF
+ rm "$tag_dir_prefix/$refname" &&
+ test_cmp expect err || return 1
+ done &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ cp $tag_dir_prefix/multi_hierarchy/default-tag "$tag_dir_prefix/multi_hierarchy/$refname" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/tags/multi_hierarchy/$refname: badRefName: invalid refname format
+ EOF
+ rm "$tag_dir_prefix/multi_hierarchy/$refname" &&
+ test_cmp expect err || return 1
+ done &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ mkdir "$branch_dir_prefix/$refname" &&
+ cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname/default-branch" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/$refname/default-branch: badRefName: invalid refname format
+ EOF
+ rm -r "$branch_dir_prefix/$refname" &&
+ test_cmp expect err || return 1
+ done
+ )
'
test_expect_success 'ref name check should be adapted into fsck messages' '
test_when_finished "rm -rf repo" &&
git init repo &&
branch_dir_prefix=.git/refs/heads &&
- cd repo &&
- git commit --allow-empty -m initial &&
- git checkout -b branch-1 &&
-
- cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
- git -c fsck.badRefName=warn refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/.branch-1: badRefName: invalid refname format
- EOF
- rm $branch_dir_prefix/.branch-1 &&
- test_cmp expect err &&
-
- cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
- git -c fsck.badRefName=ignore refs verify 2>err &&
- test_must_be_empty err
+ (
+ cd repo &&
+ git commit --allow-empty -m initial &&
+ git checkout -b branch-1 &&
+
+ cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
+ git -c fsck.badRefName=warn refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/.branch-1: badRefName: invalid refname format
+ EOF
+ rm $branch_dir_prefix/.branch-1 &&
+ test_cmp expect err &&
+
+ cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
+ git -c fsck.badRefName=ignore refs verify 2>err &&
+ test_must_be_empty err
+ )
'
test_expect_success 'ref name check should work for multiple worktrees' '
test_when_finished "rm -rf repo" &&
git init repo &&
-
- cd repo &&
- test_commit initial &&
- git checkout -b branch-1 &&
- test_commit second &&
- git checkout -b branch-2 &&
- test_commit third &&
- git checkout -b branch-3 &&
- git worktree add ./worktree-1 branch-1 &&
- git worktree add ./worktree-2 branch-2 &&
- worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
- worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
-
- (
- cd worktree-1 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-3
- ) &&
(
- cd worktree-2 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-3
- ) &&
-
- cp $worktree1_refdir_prefix/branch-4 $worktree1_refdir_prefix/'\'' branch-5'\'' &&
- cp $worktree2_refdir_prefix/branch-4 $worktree2_refdir_prefix/'\''~branch-6'\'' &&
-
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
- error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err &&
-
- for worktree in "worktree-1" "worktree-2"
- do
+ cd repo &&
+ test_commit initial &&
+ git checkout -b branch-1 &&
+ test_commit second &&
+ git checkout -b branch-2 &&
+ test_commit third &&
+ git checkout -b branch-3 &&
+ git worktree add ./worktree-1 branch-1 &&
+ git worktree add ./worktree-2 branch-2 &&
+ worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
+ worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
+
(
- cd $worktree &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
- error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err || return 1
- )
- done
+ cd worktree-1 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-3
+ ) &&
+ (
+ cd worktree-2 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-3
+ ) &&
+
+ cp $worktree1_refdir_prefix/branch-4 $worktree1_refdir_prefix/'\'' branch-5'\'' &&
+ cp $worktree2_refdir_prefix/branch-4 $worktree2_refdir_prefix/'\''~branch-6'\'' &&
+
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
+ error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err &&
+
+ for worktree in "worktree-1" "worktree-2"
+ do
+ (
+ cd $worktree &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
+ error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err || return 1
+ )
+ done
+ )
'
test_expect_success 'regular ref content should be checked (individual)' '
test_when_finished "rm -rf repo" &&
git init repo &&
branch_dir_prefix=.git/refs/heads &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
- git refs verify 2>err &&
- test_must_be_empty err &&
+ git refs verify 2>err &&
+ test_must_be_empty err &&
- for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$branch_dir_prefix/branch-bad &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/branch-bad: badRefContent: $bad_content
- EOF
- rm $branch_dir_prefix/branch-bad &&
- test_cmp expect err || return 1
- done &&
+ for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$branch_dir_prefix/branch-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/branch-bad: badRefContent: $bad_content
+ EOF
+ rm $branch_dir_prefix/branch-bad &&
+ test_cmp expect err || return 1
+ done &&
- for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$branch_dir_prefix/a/b/branch-bad &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/a/b/branch-bad: badRefContent: $bad_content
- EOF
- rm $branch_dir_prefix/a/b/branch-bad &&
- test_cmp expect err || return 1
- done &&
-
- printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- rm $branch_dir_prefix/branch-no-newline &&
- test_cmp expect err &&
-
- for trailing_content in " garbage" " more garbage"
- do
- printf "%s" "$(git rev-parse main)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$branch_dir_prefix/a/b/branch-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/a/b/branch-bad: badRefContent: $bad_content
+ EOF
+ rm $branch_dir_prefix/a/b/branch-bad &&
+ test_cmp expect err || return 1
+ done &&
+
+ printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
EOF
- rm $branch_dir_prefix/branch-garbage &&
- test_cmp expect err || return 1
- done &&
+ rm $branch_dir_prefix/branch-no-newline &&
+ test_cmp expect err &&
- printf "%s\n\n\n" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
+ for trailing_content in " garbage" " more garbage"
+ do
+ printf "%s" "$(git rev-parse main)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage &&
+ test_cmp expect err || return 1
+ done &&
+ printf "%s\n\n\n" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
- '\''
- EOF
- rm $branch_dir_prefix/branch-garbage-special &&
- test_cmp expect err &&
- printf "%s\n\n\n garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
+ '\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage-special &&
+ test_cmp expect err &&
+
+ printf "%s\n\n\n garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
- garbage'\''
- EOF
- rm $branch_dir_prefix/branch-garbage-special &&
- test_cmp expect err
+ garbage'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage-special &&
+ test_cmp expect err
+ )
'
test_expect_success 'regular ref content should be checked (aggregate)' '
@@ -237,99 +244,103 @@ test_expect_success 'regular ref content should be checked (aggregate)' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- bad_content_1=$(git rev-parse main)x &&
- bad_content_2=xfsazqfxcadas &&
- bad_content_3=Xfsazqfxcadas &&
- printf "%s" $bad_content_1 >$tag_dir_prefix/tag-bad-1 &&
- printf "%s" $bad_content_2 >$tag_dir_prefix/tag-bad-2 &&
- printf "%s" $bad_content_3 >$branch_dir_prefix/a/b/branch-bad &&
- printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
- printf "%s garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage &&
-
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/a/b/branch-bad: badRefContent: $bad_content_3
- error: refs/tags/tag-bad-1: badRefContent: $bad_content_1
- error: refs/tags/tag-bad-2: badRefContent: $bad_content_2
- warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
- warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ bad_content_1=$(git rev-parse main)x &&
+ bad_content_2=xfsazqfxcadas &&
+ bad_content_3=Xfsazqfxcadas &&
+ printf "%s" $bad_content_1 >$tag_dir_prefix/tag-bad-1 &&
+ printf "%s" $bad_content_2 >$tag_dir_prefix/tag-bad-2 &&
+ printf "%s" $bad_content_3 >$branch_dir_prefix/a/b/branch-bad &&
+ printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
+ printf "%s garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage &&
+
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/a/b/branch-bad: badRefContent: $bad_content_3
+ error: refs/tags/tag-bad-1: badRefContent: $bad_content_1
+ error: refs/tags/tag-bad-2: badRefContent: $bad_content_2
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
+ warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err
+ )
'
test_expect_success 'textual symref content should be checked (individual)' '
test_when_finished "rm -rf repo" &&
git init repo &&
branch_dir_prefix=.git/refs/heads &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ for good_referent in "refs/heads/branch" "HEAD"
+ do
+ printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
+ git refs verify 2>err &&
+ rm $branch_dir_prefix/branch-good &&
+ test_must_be_empty err || return 1
+ done &&
+
+ for bad_referent in "refs/heads/.branch" "refs/heads/~branch" "refs/heads/?branch"
+ do
+ printf "ref: %s\n" $bad_referent >$branch_dir_prefix/branch-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/branch-bad: badReferentName: points to invalid refname '\''$bad_referent'\''
+ EOF
+ rm $branch_dir_prefix/branch-bad &&
+ test_cmp expect err || return 1
+ done &&
- for good_referent in "refs/heads/branch" "HEAD"
- do
- printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
+ printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline &&
git refs verify 2>err &&
- rm $branch_dir_prefix/branch-good &&
- test_must_be_empty err || return 1
- done &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
+ EOF
+ rm $branch_dir_prefix/branch-no-newline &&
+ test_cmp expect err &&
- for bad_referent in "refs/heads/.branch" "refs/heads/~branch" "refs/heads/?branch"
- do
- printf "ref: %s\n" $bad_referent >$branch_dir_prefix/branch-bad &&
- test_must_fail git refs verify 2>err &&
+ printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
+ EOF
+ rm $branch_dir_prefix/a/b/branch-trailing-1 &&
+ test_cmp expect err &&
+
+ printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- error: refs/heads/branch-bad: badReferentName: points to invalid refname '\''$bad_referent'\''
+ warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
EOF
- rm $branch_dir_prefix/branch-bad &&
- test_cmp expect err || return 1
- done &&
-
- printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- rm $branch_dir_prefix/branch-no-newline &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-trailing-1 &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-trailing-2 &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-trailing-3 &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-complicated &&
- test_cmp expect err
+ rm $branch_dir_prefix/a/b/branch-trailing-2 &&
+ test_cmp expect err &&
+
+ printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
+ EOF
+ rm $branch_dir_prefix/a/b/branch-trailing-3 &&
+ test_cmp expect err &&
+
+ printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
+ EOF
+ rm $branch_dir_prefix/a/b/branch-complicated &&
+ test_cmp expect err
+ )
'
test_expect_success 'textual symref content should be checked (aggregate)' '
@@ -337,32 +348,34 @@ test_expect_success 'textual symref content should be checked (aggregate)' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- printf "ref: refs/heads/branch\n" >$branch_dir_prefix/branch-good &&
- printf "ref: HEAD\n" >$branch_dir_prefix/branch-head &&
- printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline-1 &&
- printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
- printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
- printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
- printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
- printf "ref: refs/heads/.branch\n" >$branch_dir_prefix/branch-bad-1 &&
-
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/branch-bad-1: badReferentName: points to invalid refname '\''refs/heads/.branch'\''
- warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/branch-no-newline-1: refMissingNewline: misses LF at the end
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ printf "ref: refs/heads/branch\n" >$branch_dir_prefix/branch-good &&
+ printf "ref: HEAD\n" >$branch_dir_prefix/branch-head &&
+ printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline-1 &&
+ printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
+ printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
+ printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
+ printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
+ printf "ref: refs/heads/.branch\n" >$branch_dir_prefix/branch-bad-1 &&
+
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/branch-bad-1: badReferentName: points to invalid refname '\''refs/heads/.branch'\''
+ warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/branch-no-newline-1: refMissingNewline: misses LF at the end
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err
+ )
'
test_expect_success 'the target of the textual symref should be checked' '
@@ -370,28 +383,30 @@ test_expect_success 'the target of the textual symref should be checked' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- for good_referent in "refs/heads/branch" "HEAD" "refs/tags/tag"
- do
- printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
- git refs verify 2>err &&
- rm $branch_dir_prefix/branch-good &&
- test_must_be_empty err || return 1
- done &&
-
- for nonref_referent in "refs-back/heads/branch" "refs-back/tags/tag" "reflogs/refs/heads/branch"
- do
- printf "ref: %s\n" $nonref_referent >$branch_dir_prefix/branch-bad-1 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-bad-1: symrefTargetIsNotARef: points to non-ref target '\''$nonref_referent'\''
- EOF
- rm $branch_dir_prefix/branch-bad-1 &&
- test_cmp expect err || return 1
- done
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ for good_referent in "refs/heads/branch" "HEAD" "refs/tags/tag"
+ do
+ printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
+ git refs verify 2>err &&
+ rm $branch_dir_prefix/branch-good &&
+ test_must_be_empty err || return 1
+ done &&
+
+ for nonref_referent in "refs-back/heads/branch" "refs-back/tags/tag" "reflogs/refs/heads/branch"
+ do
+ printf "ref: %s\n" $nonref_referent >$branch_dir_prefix/branch-bad-1 &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-bad-1: symrefTargetIsNotARef: points to non-ref target '\''$nonref_referent'\''
+ EOF
+ rm $branch_dir_prefix/branch-bad-1 &&
+ test_cmp expect err || return 1
+ done
+ )
'
test_expect_success SYMLINKS 'symlink symref content should be checked' '
@@ -399,201 +414,207 @@ test_expect_success SYMLINKS 'symlink symref content should be checked' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- ln -sf ./main $branch_dir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $branch_dir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../logs/branch-escape $branch_dir_prefix/branch-symbolic &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
- warning: refs/heads/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
- EOF
- rm $branch_dir_prefix/branch-symbolic &&
- test_cmp expect err &&
-
- ln -sf ./"branch " $branch_dir_prefix/branch-symbolic-bad &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic-bad: symlinkRef: use deprecated symbolic link for symref
- error: refs/heads/branch-symbolic-bad: badReferentName: points to invalid refname '\''refs/heads/branch '\''
- EOF
- rm $branch_dir_prefix/branch-symbolic-bad &&
- test_cmp expect err &&
-
- ln -sf ./".tag" $tag_dir_prefix/tag-symbolic-1 &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/tags/tag-symbolic-1: symlinkRef: use deprecated symbolic link for symref
- error: refs/tags/tag-symbolic-1: badReferentName: points to invalid refname '\''refs/tags/.tag'\''
- EOF
- rm $tag_dir_prefix/tag-symbolic-1 &&
- test_cmp expect err
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ ln -sf ./main $branch_dir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
+ EOF
+ rm $branch_dir_prefix/branch-symbolic-good &&
+ test_cmp expect err &&
+
+ ln -sf ../../logs/branch-escape $branch_dir_prefix/branch-symbolic &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
+ warning: refs/heads/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
+ EOF
+ rm $branch_dir_prefix/branch-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ./"branch " $branch_dir_prefix/branch-symbolic-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-symbolic-bad: symlinkRef: use deprecated symbolic link for symref
+ error: refs/heads/branch-symbolic-bad: badReferentName: points to invalid refname '\''refs/heads/branch '\''
+ EOF
+ rm $branch_dir_prefix/branch-symbolic-bad &&
+ test_cmp expect err &&
+
+ ln -sf ./".tag" $tag_dir_prefix/tag-symbolic-1 &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/tags/tag-symbolic-1: symlinkRef: use deprecated symbolic link for symref
+ error: refs/tags/tag-symbolic-1: badReferentName: points to invalid refname '\''refs/tags/.tag'\''
+ EOF
+ rm $tag_dir_prefix/tag-symbolic-1 &&
+ test_cmp expect err
+ )
'
test_expect_success SYMLINKS 'symlink symref content should be checked (worktree)' '
test_when_finished "rm -rf repo" &&
git init repo &&
- cd repo &&
- test_commit default &&
- git branch branch-1 &&
- git branch branch-2 &&
- git branch branch-3 &&
- git worktree add ./worktree-1 branch-2 &&
- git worktree add ./worktree-2 branch-3 &&
- main_worktree_refdir_prefix=.git/refs/heads &&
- worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
- worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
-
(
- cd worktree-1 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
- (
- cd worktree-2 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
-
- ln -sf ../../../../refs/heads/good-branch $worktree1_refdir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $worktree1_refdir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../../../worktrees/worktree-1/good-branch $worktree2_refdir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-2/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $worktree2_refdir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../worktrees/worktree-2/good-branch $main_worktree_refdir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $main_worktree_refdir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../../../logs/branch-escape $worktree1_refdir_prefix/branch-symbolic &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
- warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
- EOF
- rm $worktree1_refdir_prefix/branch-symbolic &&
- test_cmp expect err &&
-
- for bad_referent_name in ".tag" "branch "
- do
- ln -sf ./"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git branch branch-3 &&
+ git worktree add ./worktree-1 branch-2 &&
+ git worktree add ./worktree-2 branch-3 &&
+ main_worktree_refdir_prefix=.git/refs/heads &&
+ worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
+ worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
+
+ (
+ cd worktree-1 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+ (
+ cd worktree-2 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+
+ ln -sf ../../../../refs/heads/good-branch $worktree1_refdir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-1/refs/worktree/$bad_referent_name'\''
+ warning: worktrees/worktree-1/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
EOF
- rm $worktree1_refdir_prefix/bad-symbolic &&
+ rm $worktree1_refdir_prefix/branch-symbolic-good &&
test_cmp expect err &&
- ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ ln -sf ../../../../worktrees/worktree-1/good-branch $worktree2_refdir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ warning: worktrees/worktree-2/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
EOF
- rm $worktree1_refdir_prefix/bad-symbolic &&
+ rm $worktree2_refdir_prefix/branch-symbolic-good &&
test_cmp expect err &&
- ln -sf ./"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ ln -sf ../../worktrees/worktree-2/good-branch $main_worktree_refdir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-2/refs/worktree/$bad_referent_name'\''
+ warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
EOF
- rm $worktree2_refdir_prefix/bad-symbolic &&
+ rm $main_worktree_refdir_prefix/branch-symbolic-good &&
test_cmp expect err &&
- ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ ln -sf ../../../../logs/branch-escape $worktree1_refdir_prefix/branch-symbolic &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
+ warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
EOF
- rm $worktree2_refdir_prefix/bad-symbolic &&
- test_cmp expect err || return 1
- done
+ rm $worktree1_refdir_prefix/branch-symbolic &&
+ test_cmp expect err &&
+
+ for bad_referent_name in ".tag" "branch "
+ do
+ ln -sf ./"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-1/refs/worktree/$bad_referent_name'\''
+ EOF
+ rm $worktree1_refdir_prefix/bad-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ EOF
+ rm $worktree1_refdir_prefix/bad-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ./"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-2/refs/worktree/$bad_referent_name'\''
+ EOF
+ rm $worktree2_refdir_prefix/bad-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ EOF
+ rm $worktree2_refdir_prefix/bad-symbolic &&
+ test_cmp expect err || return 1
+ done
+ )
'
test_expect_success 'ref content checks should work with worktrees' '
test_when_finished "rm -rf repo" &&
git init repo &&
- cd repo &&
- test_commit default &&
- git branch branch-1 &&
- git branch branch-2 &&
- git branch branch-3 &&
- git worktree add ./worktree-1 branch-2 &&
- git worktree add ./worktree-2 branch-3 &&
- worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
- worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
-
(
- cd worktree-1 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
- (
- cd worktree-2 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git branch branch-3 &&
+ git worktree add ./worktree-1 branch-2 &&
+ git worktree add ./worktree-2 branch-3 &&
+ worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
+ worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
- for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$worktree1_refdir_prefix/bad-branch-1 &&
- test_must_fail git refs verify 2>err &&
+ (
+ cd worktree-1 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+ (
+ cd worktree-2 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+
+ for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$worktree1_refdir_prefix/bad-branch-1 &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-1/refs/worktree/bad-branch-1: badRefContent: $bad_content
+ EOF
+ rm $worktree1_refdir_prefix/bad-branch-1 &&
+ test_cmp expect err || return 1
+ done &&
+
+ for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$worktree2_refdir_prefix/bad-branch-2 &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-2/refs/worktree/bad-branch-2: badRefContent: $bad_content
+ EOF
+ rm $worktree2_refdir_prefix/bad-branch-2 &&
+ test_cmp expect err || return 1
+ done &&
+
+ printf "%s" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-no-newline &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- error: worktrees/worktree-1/refs/worktree/bad-branch-1: badRefContent: $bad_content
+ warning: worktrees/worktree-1/refs/worktree/branch-no-newline: refMissingNewline: misses LF at the end
EOF
- rm $worktree1_refdir_prefix/bad-branch-1 &&
- test_cmp expect err || return 1
- done &&
+ rm $worktree1_refdir_prefix/branch-no-newline &&
+ test_cmp expect err &&
- for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$worktree2_refdir_prefix/bad-branch-2 &&
- test_must_fail git refs verify 2>err &&
+ printf "%s garbage" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-garbage &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- error: worktrees/worktree-2/refs/worktree/bad-branch-2: badRefContent: $bad_content
+ warning: worktrees/worktree-1/refs/worktree/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
EOF
- rm $worktree2_refdir_prefix/bad-branch-2 &&
- test_cmp expect err || return 1
- done &&
-
- printf "%s" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-no-newline &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- rm $worktree1_refdir_prefix/branch-no-newline &&
- test_cmp expect err &&
-
- printf "%s garbage" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-garbage &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
- EOF
- rm $worktree1_refdir_prefix/branch-garbage &&
- test_cmp expect err
+ rm $worktree1_refdir_prefix/branch-garbage &&
+ test_cmp expect err
+ )
'
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v6 2/9] builtin/refs: get worktrees without reading head information
2025-02-25 13:19 ` [PATCH v6 0/9] " shejialuo
2025-02-25 13:21 ` [PATCH v6 1/9] t0602: use subshell to ensure working directory unchanged shejialuo
@ 2025-02-25 13:21 ` shejialuo
2025-02-25 13:21 ` [PATCH v6 3/9] packed-backend: check whether the "packed-refs" is regular file shejialuo
` (7 subsequent siblings)
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-25 13:21 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
In "packed-backend.c", there are some functions such as "create_snapshot"
and "next_record" which would check the correctness of the content of
the "packed-ref" file. When anything is bad, the program will die.
It may seem that we have nothing relevant to above feature, because we
are going to read and parse the raw "packed-ref" file without creating
the snapshot and using the ref iterator to check the consistency.
However, when using "get_worktrees" in "builtin/refs", we would parse
the "HEAD" information. If the referent of the "HEAD" is inside the
"packed-ref", we will call "create_snapshot" function to parse the
"packed-ref" to get the information. No matter whether the entry of
"HEAD" in "packed-ref" is correct, "create_snapshot" would call
"verify_buffer_safe" to check whether there is a newline in the last
line of the file. If not, the program will die.
Although this behavior has no harm for the program, it will
short-circuit the program. When the users execute "git refs verify" or
"git fsck", we should avoid reading the head information, which may
execute the read operation in packed backend with stricter checks to die
the program. Instead, we should continue to check other parts of the
"packed-refs" file completely.
Fortunately, in 465a22b338 (worktree: skip reading HEAD when repairing
worktrees, 2023-12-29), we have introduced a function
"get_worktrees_internal" which allows us to get worktrees without
reading head information.
Create a new exposed function "get_worktrees_without_reading_head", then
replace the "get_worktrees" in "builtin/refs" with the new created
function.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
builtin/refs.c | 2 +-
worktree.c | 5 +++++
worktree.h | 8 ++++++++
3 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/builtin/refs.c b/builtin/refs.c
index a29f195834..55ff5dae11 100644
--- a/builtin/refs.c
+++ b/builtin/refs.c
@@ -88,7 +88,7 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix,
git_config(git_fsck_config, &fsck_refs_options);
prepare_repo_settings(the_repository);
- worktrees = get_worktrees();
+ worktrees = get_worktrees_without_reading_head();
for (size_t i = 0; worktrees[i]; i++)
ret |= refs_fsck(get_worktree_ref_store(worktrees[i]),
&fsck_refs_options, worktrees[i]);
diff --git a/worktree.c b/worktree.c
index d4a68c9c23..d23482a746 100644
--- a/worktree.c
+++ b/worktree.c
@@ -198,6 +198,11 @@ struct worktree **get_worktrees(void)
return get_worktrees_internal(0);
}
+struct worktree **get_worktrees_without_reading_head(void)
+{
+ return get_worktrees_internal(1);
+}
+
const char *get_worktree_git_dir(const struct worktree *wt)
{
if (!wt)
diff --git a/worktree.h b/worktree.h
index 38145df80f..a305c7e2c7 100644
--- a/worktree.h
+++ b/worktree.h
@@ -30,6 +30,14 @@ struct worktree {
*/
struct worktree **get_worktrees(void);
+/*
+ * Like `get_worktrees`, but does not read HEAD. Skip reading HEAD allows to
+ * get the worktree without worrying about failures pertaining to parsing
+ * the HEAD ref. This is useful in contexts where it is assumed that the
+ * refdb may not be in a consistent state.
+ */
+struct worktree **get_worktrees_without_reading_head(void);
+
/*
* Returns 1 if linked worktrees exist, 0 otherwise.
*/
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v6 3/9] packed-backend: check whether the "packed-refs" is regular file
2025-02-25 13:19 ` [PATCH v6 0/9] " shejialuo
2025-02-25 13:21 ` [PATCH v6 1/9] t0602: use subshell to ensure working directory unchanged shejialuo
2025-02-25 13:21 ` [PATCH v6 2/9] builtin/refs: get worktrees without reading head information shejialuo
@ 2025-02-25 13:21 ` shejialuo
2025-02-25 17:44 ` Junio C Hamano
2025-02-25 13:21 ` [PATCH v6 4/9] packed-backend: check if header starts with "# pack-refs with: " shejialuo
` (6 subsequent siblings)
9 siblings, 1 reply; 168+ messages in thread
From: shejialuo @ 2025-02-25 13:21 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
Although "git-fsck(1)" and "packed-backend.c" will check some
consistency and correctness of "packed-refs" file, they never check the
filetype of the "packed-refs". Let's verify that the "packed-refs" has
the expected filetype, confirming it is created by "git pack-refs"
command.
Use "lstat" to check the file mode. If we cannot check the file status
due to there is no such file this is OK because there is a possibility
that there is no "packed-refs" in the repo.
Reuse "FSCK_MSG_BAD_REF_FILETYPE" fsck message id to report the error to
the user if "packed-refs" is not a regular file.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
refs/packed-backend.c | 37 +++++++++++++++++++++++++++++++++----
t/t0602-reffiles-fsck.sh | 22 ++++++++++++++++++++++
2 files changed, 55 insertions(+), 4 deletions(-)
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index a7b6f74b6e..6c118119a0 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -4,6 +4,7 @@
#include "../git-compat-util.h"
#include "../config.h"
#include "../dir.h"
+#include "../fsck.h"
#include "../gettext.h"
#include "../hash.h"
#include "../hex.h"
@@ -1748,15 +1749,43 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
return empty_ref_iterator_begin();
}
-static int packed_fsck(struct ref_store *ref_store UNUSED,
- struct fsck_options *o UNUSED,
+static int packed_fsck(struct ref_store *ref_store,
+ struct fsck_options *o,
struct worktree *wt)
{
+ struct packed_ref_store *refs = packed_downcast(ref_store,
+ REF_STORE_READ, "fsck");
+ struct stat st;
+ int ret = 0;
if (!is_main_worktree(wt))
- return 0;
+ goto cleanup;
- return 0;
+ if (o->verbose)
+ fprintf_ln(stderr, "Checking packed-refs file %s", refs->path);
+
+ if (lstat(refs->path, &st) < 0) {
+ /*
+ * If the packed-refs file doesn't exist, there's nothing
+ * to check.
+ */
+ if (errno == ENOENT)
+ goto cleanup;
+ ret = error_errno(_("unable to stat '%s'"), refs->path);
+ goto cleanup;
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs";
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_FILETYPE,
+ "not a regular file");
+ goto cleanup;
+ }
+
+cleanup:
+ return ret;
}
struct ref_storage_be refs_be_packed = {
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index cf7a202d0d..e65ca341cd 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -617,4 +617,26 @@ test_expect_success 'ref content checks should work with worktrees' '
)
'
+test_expect_success SYMLINKS 'the filetype of packed-refs should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git branch branch-3 &&
+ git pack-refs --all &&
+
+ mv .git/packed-refs .git/packed-refs-back &&
+ ln -sf packed-refs-back .git/packed-refs &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs: badRefFiletype: not a regular file
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* Re: [PATCH v6 3/9] packed-backend: check whether the "packed-refs" is regular file
2025-02-25 13:21 ` [PATCH v6 3/9] packed-backend: check whether the "packed-refs" is regular file shejialuo
@ 2025-02-25 17:44 ` Junio C Hamano
2025-02-26 12:05 ` shejialuo
0 siblings, 1 reply; 168+ messages in thread
From: Junio C Hamano @ 2025-02-25 17:44 UTC (permalink / raw)
To: shejialuo; +Cc: git, Patrick Steinhardt, Karthik Nayak, Michael Haggerty
shejialuo <shejialuo@gmail.com> writes:
> Although "git-fsck(1)" and "packed-backend.c" will check some
> consistency and correctness of "packed-refs" file, they never check the
> filetype of the "packed-refs". Let's verify that the "packed-refs" has
> the expected filetype, confirming it is created by "git pack-refs"
> command.
>
> Use "lstat" to check the file mode. If we cannot check the file status
> due to there is no such file this is OK because there is a possibility
> that there is no "packed-refs" in the repo.
Can this be done _after_ the open_nofollow() check you had in the
previous round noticed a problem? Even though we are trying to
notice and find problems in the given repository, it is generally
a good idea to optimize for the more common case (i.e. the file is a
regular one and not a symbolic link or directory or anything funny).
Something along the lines of
fd = open_nofollow(...);
if (fd < 0) {
lstat() to inspect the details
} else if (fstat(fd, &st) < 0) {
... cannot tell what we opened ...
} else if (!S_ISREG(st.st_mode)) {
... we opened something funny ...
} else {
... the thing is a regular file as expected ...
}
perhaps?
^ permalink raw reply [flat|nested] 168+ messages in thread
* Re: [PATCH v6 3/9] packed-backend: check whether the "packed-refs" is regular file
2025-02-25 17:44 ` Junio C Hamano
@ 2025-02-26 12:05 ` shejialuo
0 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-26 12:05 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Patrick Steinhardt, Karthik Nayak, Michael Haggerty
On Tue, Feb 25, 2025 at 09:44:12AM -0800, Junio C Hamano wrote:
> shejialuo <shejialuo@gmail.com> writes:
>
> > Although "git-fsck(1)" and "packed-backend.c" will check some
> > consistency and correctness of "packed-refs" file, they never check the
> > filetype of the "packed-refs". Let's verify that the "packed-refs" has
> > the expected filetype, confirming it is created by "git pack-refs"
> > command.
> >
> > Use "lstat" to check the file mode. If we cannot check the file status
> > due to there is no such file this is OK because there is a possibility
> > that there is no "packed-refs" in the repo.
>
> Can this be done _after_ the open_nofollow() check you had in the
> previous round noticed a problem? Even though we are trying to
> notice and find problems in the given repository, it is generally
> a good idea to optimize for the more common case (i.e. the file is a
> regular one and not a symbolic link or directory or anything funny).
> Something along the lines of
>
> fd = open_nofollow(...);
> if (fd < 0) {
> lstat() to inspect the details
> } else if (fstat(fd, &st) < 0) {
> ... cannot tell what we opened ...
> } else if (!S_ISREG(st.st_mode)) {
> ... we opened something funny ...
> } else {
> ... the thing is a regular file as expected ...
> }
>
Good idea, by using this way, the code would be more clean. I will
improve this in the next version.
> perhaps?
^ permalink raw reply [flat|nested] 168+ messages in thread
* [PATCH v6 4/9] packed-backend: check if header starts with "# pack-refs with: "
2025-02-25 13:19 ` [PATCH v6 0/9] " shejialuo
` (2 preceding siblings ...)
2025-02-25 13:21 ` [PATCH v6 3/9] packed-backend: check whether the "packed-refs" is regular file shejialuo
@ 2025-02-25 13:21 ` shejialuo
2025-02-26 8:08 ` Patrick Steinhardt
2025-02-25 13:21 ` [PATCH v6 5/9] packed-backend: add "packed-refs" header consistency check shejialuo
` (5 subsequent siblings)
9 siblings, 1 reply; 168+ messages in thread
From: shejialuo @ 2025-02-25 13:21 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
We always write a space after "# pack-refs with:". However, when
creating the packed-ref snapshot, we only check whether the header
starts with "# pack-refs with:". However, we need to make sure that we
would not break compatibility by tightening the rule. The following is
how some third-party libraries handle the header of "packed-ref" file.
1. libgit2 is fine and always writes the space. It also expects the
whitespace to exist.
2. JGit does not expect th header to have a trailing space, but expects
the "peeled" capability to have a leading space, which is mostly
equivalent because that capability is typically the first one we
write. It always writes the space.
3. gitoxide expects the space t exist and writes it.
4. go-git doesn't create the header by default.
So, we are safe to tighten the rule by checking whether the header
starts with "# pack-refs with: ".
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
refs/packed-backend.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 6c118119a0..9dabb5e556 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -694,7 +694,7 @@ static struct snapshot *create_snapshot(struct packed_ref_store *refs)
tmp = xmemdupz(snapshot->buf, eol - snapshot->buf);
- if (!skip_prefix(tmp, "# pack-refs with:", (const char **)&p))
+ if (!skip_prefix(tmp, "# pack-refs with: ", (const char **)&p))
die_invalid_line(refs->path,
snapshot->buf,
snapshot->eof - snapshot->buf);
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* Re: [PATCH v6 4/9] packed-backend: check if header starts with "# pack-refs with: "
2025-02-25 13:21 ` [PATCH v6 4/9] packed-backend: check if header starts with "# pack-refs with: " shejialuo
@ 2025-02-26 8:08 ` Patrick Steinhardt
2025-02-26 12:28 ` shejialuo
0 siblings, 1 reply; 168+ messages in thread
From: Patrick Steinhardt @ 2025-02-26 8:08 UTC (permalink / raw)
To: shejialuo; +Cc: git, Karthik Nayak, Junio C Hamano, Michael Haggerty
On Tue, Feb 25, 2025 at 09:21:41PM +0800, shejialuo wrote:
> We always write a space after "# pack-refs with:". However, when
> creating the packed-ref snapshot, we only check whether the header
> starts with "# pack-refs with:". However, we need to make sure that we
> would not break compatibility by tightening the rule. The following is
> how some third-party libraries handle the header of "packed-ref" file.
>
> 1. libgit2 is fine and always writes the space. It also expects the
> whitespace to exist.
> 2. JGit does not expect th header to have a trailing space, but expects
> the "peeled" capability to have a leading space, which is mostly
> equivalent because that capability is typically the first one we
> write. It always writes the space.
> 3. gitoxide expects the space t exist and writes it.
> 4. go-git doesn't create the header by default.
>
> So, we are safe to tighten the rule by checking whether the header
> starts with "# pack-refs with: ".
The commit message nicely describes why it's safe to do the change, but
it doesn't describe why it's something we _want_ to do.
Ideally, we'd be able to argue with a technical spec of the format, but
unless I'm mistaken such a document does not exist. The next-best thing
is to do what everyone can agree on, and that seems to be to both write
and expect a space after the colon. By not following consensus that
exists in other libraries we're being more loose.
So if we for example started to stop writing the space due to a bug,
we'd still continue to parse the header alright and thus not notice the
problem, but now we have broken other implementations. That may be a
good enough justification for the change itself.
Patrick
^ permalink raw reply [flat|nested] 168+ messages in thread
* Re: [PATCH v6 4/9] packed-backend: check if header starts with "# pack-refs with: "
2025-02-26 8:08 ` Patrick Steinhardt
@ 2025-02-26 12:28 ` shejialuo
0 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-26 12:28 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git, Karthik Nayak, Junio C Hamano, Michael Haggerty
On Wed, Feb 26, 2025 at 09:08:32AM +0100, Patrick Steinhardt wrote:
> On Tue, Feb 25, 2025 at 09:21:41PM +0800, shejialuo wrote:
> > We always write a space after "# pack-refs with:". However, when
> > creating the packed-ref snapshot, we only check whether the header
> > starts with "# pack-refs with:". However, we need to make sure that we
> > would not break compatibility by tightening the rule. The following is
> > how some third-party libraries handle the header of "packed-ref" file.
> >
> > 1. libgit2 is fine and always writes the space. It also expects the
> > whitespace to exist.
> > 2. JGit does not expect th header to have a trailing space, but expects
> > the "peeled" capability to have a leading space, which is mostly
> > equivalent because that capability is typically the first one we
> > write. It always writes the space.
> > 3. gitoxide expects the space t exist and writes it.
> > 4. go-git doesn't create the header by default.
> >
> > So, we are safe to tighten the rule by checking whether the header
> > starts with "# pack-refs with: ".
>
> The commit message nicely describes why it's safe to do the change, but
> it doesn't describe why it's something we _want_ to do.
>
Yes, as you have said below. We don't have document about the header
format. It's an internal implementation of Git.
> Ideally, we'd be able to argue with a technical spec of the format, but
> unless I'm mistaken such a document does not exist. The next-best thing
> is to do what everyone can agree on, and that seems to be to both write
> and expect a space after the colon. By not following consensus that
> exists in other libraries we're being more loose.
>
> So if we for example started to stop writing the space due to a bug,
> we'd still continue to parse the header alright and thus not notice the
> problem, but now we have broken other implementations. That may be a
> good enough justification for the change itself.
>
Thanks, I will improve the commit message in the next version.
> Patrick
^ permalink raw reply [flat|nested] 168+ messages in thread
* [PATCH v6 5/9] packed-backend: add "packed-refs" header consistency check
2025-02-25 13:19 ` [PATCH v6 0/9] " shejialuo
` (3 preceding siblings ...)
2025-02-25 13:21 ` [PATCH v6 4/9] packed-backend: check if header starts with "# pack-refs with: " shejialuo
@ 2025-02-25 13:21 ` shejialuo
2025-02-25 13:21 ` [PATCH v6 6/9] packed-backend: check whether the refname contains NUL characters shejialuo
` (4 subsequent siblings)
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-25 13:21 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
In "packed-backend.c::create_snapshot", if there is a header (the line
which starts with '#'), we will check whether the line starts with "#
pack-refs with: ". However, we need to consider other situations and
discuss whether we need to add checks.
1. If the header does not exist, we should not report an error to the
user. This is because in older Git version, we never write header in
the "packed-refs" file. Also, we do allow no header in "packed-refs"
in runtime.
2. If the header content does not start with "# packed-ref with: ", we
should report an error just like what "create_snapshot" does. So,
create a new fsck message "badPackedRefHeader(ERROR)" for this.
3. If the header content is not the same as the constant string
"PACKED_REFS_HEADER". This is expected because we make it extensible
intentionally and runtime "create_snapshot" won't complain about
unknown traits. In order to align with the runtime behavior. There is
no need to report.
As we have analyzed, we only need to check the case 2 in the above. In
order to do this, use "open_nofollow" function to get the file
descriptor and then read the "packed-refs" file via "strbuf_read". Like
what "create_snapshot" and other functions do, we could split the line
by finding the next newline in the buffer. When we cannot find a
newline, we could report an error.
So, create a function "packed_fsck_ref_next_line" to find the next
newline and if there is no such newline, use
"packedRefEntryNotTerminated(ERROR)" to report an error to the user.
Then, parse the first line to apply the checks. Update the test to
exercise the code.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/fsck-msgids.adoc | 8 +++
fsck.h | 2 +
refs/packed-backend.c | 94 ++++++++++++++++++++++++++++++++++
t/t0602-reffiles-fsck.sh | 52 +++++++++++++++++++
4 files changed, 156 insertions(+)
diff --git a/Documentation/fsck-msgids.adoc b/Documentation/fsck-msgids.adoc
index b14bc44ca4..11906f90fd 100644
--- a/Documentation/fsck-msgids.adoc
+++ b/Documentation/fsck-msgids.adoc
@@ -16,6 +16,10 @@
`badObjectSha1`::
(ERROR) An object has a bad sha1.
+`badPackedRefHeader`::
+ (ERROR) The "packed-refs" file contains an invalid
+ header.
+
`badParentSha1`::
(ERROR) A commit object has a bad parent sha1.
@@ -176,6 +180,10 @@
`nullSha1`::
(WARN) Tree contains entries pointing to a null sha1.
+`packedRefEntryNotTerminated`::
+ (ERROR) The "packed-refs" file contains an entry that is
+ not terminated by a newline.
+
`refMissingNewline`::
(INFO) A loose ref that does not end with newline(LF). As
valid implementations of Git never created such a loose ref
diff --git a/fsck.h b/fsck.h
index a44c231a5f..67e3c97bc0 100644
--- a/fsck.h
+++ b/fsck.h
@@ -30,6 +30,7 @@ enum fsck_msg_type {
FUNC(BAD_EMAIL, ERROR) \
FUNC(BAD_NAME, ERROR) \
FUNC(BAD_OBJECT_SHA1, ERROR) \
+ FUNC(BAD_PACKED_REF_HEADER, ERROR) \
FUNC(BAD_PARENT_SHA1, ERROR) \
FUNC(BAD_REF_CONTENT, ERROR) \
FUNC(BAD_REF_FILETYPE, ERROR) \
@@ -53,6 +54,7 @@ enum fsck_msg_type {
FUNC(MISSING_TYPE, ERROR) \
FUNC(MISSING_TYPE_ENTRY, ERROR) \
FUNC(MULTIPLE_AUTHORS, ERROR) \
+ FUNC(PACKED_REF_ENTRY_NOT_TERMINATED, ERROR) \
FUNC(TREE_NOT_SORTED, ERROR) \
FUNC(UNKNOWN_TYPE, ERROR) \
FUNC(ZERO_PADDED_DATE, ERROR) \
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 9dabb5e556..4891c86a5a 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1749,13 +1749,78 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
return empty_ref_iterator_begin();
}
+static int packed_fsck_ref_next_line(struct fsck_options *o,
+ unsigned long line_number, const char *start,
+ const char *eof, const char **eol)
+{
+ int ret = 0;
+
+ *eol = memchr(start, '\n', eof - start);
+ if (!*eol) {
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_PACKED_REF_ENTRY_NOT_TERMINATED,
+ "'%.*s' is not terminated with a newline",
+ (int)(eof - start), start);
+
+ /*
+ * There is no newline but we still want to parse it to the end of
+ * the buffer.
+ */
+ *eol = eof;
+ strbuf_release(&packed_entry);
+ }
+
+ return ret;
+}
+
+static int packed_fsck_ref_header(struct fsck_options *o,
+ const char *start, const char *eol)
+{
+ if (!starts_with(start, "# pack-refs with: ")) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs.header";
+
+ return fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_HEADER,
+ "'%.*s' does not start with '# pack-refs with: '",
+ (int)(eol - start), start);
+ }
+
+ return 0;
+}
+
+static int packed_fsck_ref_content(struct fsck_options *o,
+ const char *start, const char *eof)
+{
+ unsigned long line_number = 1;
+ const char *eol;
+ int ret = 0;
+
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ if (*start == '#') {
+ ret |= packed_fsck_ref_header(o, start, eol);
+
+ start = eol + 1;
+ line_number++;
+ }
+
+ return ret;
+}
+
static int packed_fsck(struct ref_store *ref_store,
struct fsck_options *o,
struct worktree *wt)
{
struct packed_ref_store *refs = packed_downcast(ref_store,
REF_STORE_READ, "fsck");
+ struct strbuf packed_ref_content = STRBUF_INIT;
struct stat st;
+ int fd;
int ret = 0;
if (!is_main_worktree(wt))
@@ -1784,7 +1849,36 @@ static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
+ /*
+ * There is a chance that "packed-refs" file is removed or converted to
+ * a symlink after filetype check and before open. So we need to avoid
+ * this race condition by opening the file.
+ */
+ fd = open_nofollow(refs->path, O_RDONLY);
+ if (fd < 0) {
+ if (errno == ENOENT)
+ goto cleanup;
+
+ if (errno == ELOOP) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs";
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_FILETYPE,
+ "not a regular file");
+ goto cleanup;
+ }
+ }
+
+ if (strbuf_read(&packed_ref_content, fd, 0) < 0) {
+ ret = error_errno(_("unable to read %s"), refs->path);
+ goto cleanup;
+ }
+
+ ret = packed_fsck_ref_content(o, packed_ref_content.buf,
+ packed_ref_content.buf + packed_ref_content.len);
+
cleanup:
+ strbuf_release(&packed_ref_content);
return ret;
}
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index e65ca341cd..e055c36e74 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -639,4 +639,56 @@ test_expect_success SYMLINKS 'the filetype of packed-refs should be checked' '
)
'
+test_expect_success 'packed-refs header should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+
+ git refs verify 2>err &&
+ test_must_be_empty err &&
+
+ for bad_header in "# pack-refs wit: peeled fully-peeled sorted " \
+ "# pack-refs with traits: peeled fully-peeled sorted " \
+ "# pack-refs with a: peeled fully-peeled" \
+ "# pack-refs with:peeled fully-peeled sorted"
+ do
+ printf "%s\n" "$bad_header" >.git/packed-refs &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs.header: badPackedRefHeader: '\''$bad_header'\'' does not start with '\''# pack-refs with: '\''
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err || return 1
+ done
+ )
+'
+
+test_expect_success 'packed-refs missing header should not be reported' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+
+ printf "$(git rev-parse HEAD) refs/heads/main\n" >.git/packed-refs &&
+ git refs verify 2>err &&
+ test_must_be_empty err
+ )
+'
+
+test_expect_success 'packed-refs unknown traits should not be reported' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+
+ printf "# pack-refs with: peeled fully-peeled sorted foo\n" >.git/packed-refs &&
+ git refs verify 2>err &&
+ test_must_be_empty err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v6 6/9] packed-backend: check whether the refname contains NUL characters
2025-02-25 13:19 ` [PATCH v6 0/9] " shejialuo
` (4 preceding siblings ...)
2025-02-25 13:21 ` [PATCH v6 5/9] packed-backend: add "packed-refs" header consistency check shejialuo
@ 2025-02-25 13:21 ` shejialuo
2025-02-25 13:22 ` [PATCH v6 7/9] packed-backend: add "packed-refs" entry consistency check shejialuo
` (3 subsequent siblings)
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-25 13:21 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
"packed-backend.c::next_record" will use "check_refname_format" to check
the consistency of the refname. If it is not OK, the program will die.
However, it is reported in [1], we cannot catch some corruption. But we
already have the code path and we must miss out something.
We use the following code to get the refname:
strbuf_add(&iter->refname_buf, p, eol - p);
iter->base.refname = iter->refname_buf.buf
In the above code, `p` is the start pointer of the refname and `eol` is
the next newline pointer. We calculate the length of the refname by
subtracting the two pointers. Then we add the memory range between `p`
and `eol` to get the refname.
However, if there are some NUL characters in the memory range between `p`
and `eol`, we will see the refname as a valid ref name as long as the
memory range between `p` and first occurred NUL character is valid.
In order to catch above corruption, create a new function
"refname_contains_nul" by searching the first NUL character. If it is
not at the end of the string, there must be some NUL characters in the
refname.
Use this function in "next_record" function to die the program if
"refname_contains_nul" returns true.
[1] https://lore.kernel.org/git/6cfee0e4-3285-4f18-91ff-d097da9de737@rd10.de/
Reported-by: R. Diez <rdiez-temp3@rd10.de>
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
refs/packed-backend.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 4891c86a5a..a74ee57776 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -494,6 +494,21 @@ static void verify_buffer_safe(struct snapshot *snapshot)
last_line, eof - last_line);
}
+/*
+ * When parsing the "packed-refs" file, we will parse it line by line.
+ * Because we know the start pointer of the refname and the next
+ * newline pointer, we could calculate the length of the refname by
+ * subtracting the two pointers. However, there is a corner case where
+ * the refname contains corrupted embedded NUL characters. And
+ * `check_refname_format()` will not catch this when the truncated
+ * refname is still a valid refname. To prevent this, we need to check
+ * whether the refname contains the NUL characters.
+ */
+static int refname_contains_nul(struct strbuf *refname)
+{
+ return !!memchr(refname->buf, '\0', refname->len);
+}
+
#define SMALL_FILE_SIZE (32*1024)
/*
@@ -895,6 +910,9 @@ static int next_record(struct packed_ref_iterator *iter)
strbuf_add(&iter->refname_buf, p, eol - p);
iter->base.refname = iter->refname_buf.buf;
+ if (refname_contains_nul(&iter->refname_buf))
+ die("packed refname contains embedded NULL: %s", iter->base.refname);
+
if (check_refname_format(iter->base.refname, REFNAME_ALLOW_ONELEVEL)) {
if (!refname_is_safe(iter->base.refname))
die("packed refname is dangerous: %s",
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v6 7/9] packed-backend: add "packed-refs" entry consistency check
2025-02-25 13:19 ` [PATCH v6 0/9] " shejialuo
` (5 preceding siblings ...)
2025-02-25 13:21 ` [PATCH v6 6/9] packed-backend: check whether the refname contains NUL characters shejialuo
@ 2025-02-25 13:22 ` shejialuo
2025-02-25 13:22 ` [PATCH v6 8/9] packed-backend: check whether the "packed-refs" is sorted shejialuo
` (2 subsequent siblings)
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-25 13:22 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
"packed-backend.c::next_record" will parse the ref entry to check the
consistency. This function has already checked the following things:
1. Parse the main line of the ref entry to inspect whether the oid is
not correct. Then, check whether the next character is oid. Then
check the refname.
2. If the next line starts with '^', it would continue to parse the
peeled oid and check whether the last character is '\n'.
As we decide to implement the ref consistency check for "packed-refs",
let's port these two checks and update the test to exercise the code.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/fsck-msgids.adoc | 3 +
fsck.h | 1 +
refs/packed-backend.c | 122 ++++++++++++++++++++++++++++++++-
t/t0602-reffiles-fsck.sh | 44 ++++++++++++
4 files changed, 169 insertions(+), 1 deletion(-)
diff --git a/Documentation/fsck-msgids.adoc b/Documentation/fsck-msgids.adoc
index 11906f90fd..02a7bf0503 100644
--- a/Documentation/fsck-msgids.adoc
+++ b/Documentation/fsck-msgids.adoc
@@ -16,6 +16,9 @@
`badObjectSha1`::
(ERROR) An object has a bad sha1.
+`badPackedRefEntry`::
+ (ERROR) The "packed-refs" file contains an invalid entry.
+
`badPackedRefHeader`::
(ERROR) The "packed-refs" file contains an invalid
header.
diff --git a/fsck.h b/fsck.h
index 67e3c97bc0..14d70f6653 100644
--- a/fsck.h
+++ b/fsck.h
@@ -30,6 +30,7 @@ enum fsck_msg_type {
FUNC(BAD_EMAIL, ERROR) \
FUNC(BAD_NAME, ERROR) \
FUNC(BAD_OBJECT_SHA1, ERROR) \
+ FUNC(BAD_PACKED_REF_ENTRY, ERROR) \
FUNC(BAD_PACKED_REF_HEADER, ERROR) \
FUNC(BAD_PARENT_SHA1, ERROR) \
FUNC(BAD_REF_CONTENT, ERROR) \
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index a74ee57776..dd3f7ab255 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1812,9 +1812,114 @@ static int packed_fsck_ref_header(struct fsck_options *o,
return 0;
}
+static int packed_fsck_ref_peeled_line(struct fsck_options *o,
+ struct ref_store *ref_store,
+ unsigned long line_number,
+ const char *start, const char *eol)
+{
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct object_id peeled;
+ const char *p;
+ int ret = 0;
+
+ /*
+ * Skip the '^' and parse the peeled oid.
+ */
+ start++;
+ if (parse_oid_hex_algop(start, &peeled, &p, ref_store->repo->hash_algo)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "'%.*s' has invalid peeled oid",
+ (int)(eol - start), start);
+ goto cleanup;
+ }
+
+ if (p != eol) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "has trailing garbage after peeled oid '%.*s'",
+ (int)(eol - p), p);
+ goto cleanup;
+ }
+
+cleanup:
+ strbuf_release(&packed_entry);
+ return ret;
+}
+
+static int packed_fsck_ref_main_line(struct fsck_options *o,
+ struct ref_store *ref_store,
+ unsigned long line_number,
+ struct strbuf *refname,
+ const char *start, const char *eol)
+{
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct object_id oid;
+ const char *p;
+ int ret = 0;
+
+ if (parse_oid_hex_algop(start, &oid, &p, ref_store->repo->hash_algo)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "'%.*s' has invalid oid",
+ (int)(eol - start), start);
+ goto cleanup;
+ }
+
+ if (p == eol || !isspace(*p)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "has no space after oid '%s' but with '%.*s'",
+ oid_to_hex(&oid), (int)(eol - p), p);
+ goto cleanup;
+ }
+
+ p++;
+ strbuf_reset(refname);
+ strbuf_add(refname, p, eol - p);
+ if (refname_contains_nul(refname)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "refname '%s' contains NULL binaries",
+ refname->buf);
+ }
+
+ if (check_refname_format(refname->buf, 0)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_NAME,
+ "has bad refname '%s'", refname->buf);
+ }
+
+cleanup:
+ strbuf_release(&packed_entry);
+ return ret;
+}
+
static int packed_fsck_ref_content(struct fsck_options *o,
+ struct ref_store *ref_store,
const char *start, const char *eof)
{
+ struct strbuf refname = STRBUF_INIT;
unsigned long line_number = 1;
const char *eol;
int ret = 0;
@@ -1827,6 +1932,21 @@ static int packed_fsck_ref_content(struct fsck_options *o,
line_number++;
}
+ while (start < eof) {
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ ret |= packed_fsck_ref_main_line(o, ref_store, line_number, &refname, start, eol);
+ start = eol + 1;
+ line_number++;
+ if (start < eof && *start == '^') {
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ ret |= packed_fsck_ref_peeled_line(o, ref_store, line_number,
+ start, eol);
+ start = eol + 1;
+ line_number++;
+ }
+ }
+
+ strbuf_release(&refname);
return ret;
}
@@ -1892,7 +2012,7 @@ static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
- ret = packed_fsck_ref_content(o, packed_ref_content.buf,
+ ret = packed_fsck_ref_content(o, ref_store, packed_ref_content.buf,
packed_ref_content.buf + packed_ref_content.len);
cleanup:
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index e055c36e74..7421cc1e7f 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -691,4 +691,48 @@ test_expect_success 'packed-refs unknown traits should not be reported' '
)
'
+test_expect_success 'packed-refs content should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git tag -a annotated-tag-1 -m tag-1 &&
+ git tag -a annotated-tag-2 -m tag-2 &&
+
+ branch_1_oid=$(git rev-parse branch-1) &&
+ branch_2_oid=$(git rev-parse branch-2) &&
+ tag_1_oid=$(git rev-parse annotated-tag-1) &&
+ tag_2_oid=$(git rev-parse annotated-tag-2) &&
+ tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
+ tag_2_peeled_oid=$(git rev-parse annotated-tag-2^{}) &&
+ short_oid=$(printf "%s" $tag_1_peeled_oid | cut -c 1-4) &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ $short_oid refs/heads/branch-1
+ ${branch_1_oid}x
+ $branch_2_oid refs/heads/bad-branch
+ $branch_2_oid refs/heads/branch.
+ $tag_1_oid refs/tags/annotated-tag-3
+ ^$short_oid
+ $tag_2_oid refs/tags/annotated-tag-4.
+ ^$tag_2_peeled_oid garbage
+ EOF
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs line 2: badPackedRefEntry: '\''$short_oid refs/heads/branch-1'\'' has invalid oid
+ error: packed-refs line 3: badPackedRefEntry: has no space after oid '\''$branch_1_oid'\'' but with '\''x'\''
+ error: packed-refs line 4: badRefName: has bad refname '\'' refs/heads/bad-branch'\''
+ error: packed-refs line 5: badRefName: has bad refname '\''refs/heads/branch.'\''
+ error: packed-refs line 7: badPackedRefEntry: '\''$short_oid'\'' has invalid peeled oid
+ error: packed-refs line 8: badRefName: has bad refname '\''refs/tags/annotated-tag-4.'\''
+ error: packed-refs line 9: badPackedRefEntry: has trailing garbage after peeled oid '\'' garbage'\''
+ EOF
+ test_cmp expect err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v6 8/9] packed-backend: check whether the "packed-refs" is sorted
2025-02-25 13:19 ` [PATCH v6 0/9] " shejialuo
` (6 preceding siblings ...)
2025-02-25 13:22 ` [PATCH v6 7/9] packed-backend: add "packed-refs" entry consistency check shejialuo
@ 2025-02-25 13:22 ` shejialuo
2025-02-25 13:22 ` [PATCH v6 9/9] builtin/fsck: add `git refs verify` child process shejialuo
2025-02-26 13:48 ` [PATCH v7 0/9] add more ref consistency checks shejialuo
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-25 13:22 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
When there is a "sorted" trait in the header of the "packed-refs" file,
it means that each entry is sorted increasingly by comparing the
refname. We should add checks to verify whether the "packed-refs" is
sorted in this case.
Update the "packed_fsck_ref_header" to know whether there is a "sorted"
trail in the header. It may seem that we could record all refnames
during the parsing process and then compare later. However, this is not
a good design due to the following reasons:
1. Because we need to store the state across the whole checking
lifetime, we would consume a lot of memory if there are many entries
in the "packed-refs" file.
2. We cannot reuse the existing compare function "cmp_packed_ref_records"
which cause repetition.
Because "cmp_packed_ref_records" needs an extra parameter "struct
snaphost", extract the common part into a new function
"cmp_packed_ref_records" to reuse this function to compare.
Then, create a new function "packed_fsck_ref_sorted" to parse the file
again and user the new fsck message "packedRefUnsorted(ERROR)" to report
to the user if the file is not sorted.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/fsck-msgids.adoc | 3 +
fsck.h | 1 +
refs/packed-backend.c | 118 ++++++++++++++++++++++++++++-----
t/t0602-reffiles-fsck.sh | 87 ++++++++++++++++++++++++
4 files changed, 192 insertions(+), 17 deletions(-)
diff --git a/Documentation/fsck-msgids.adoc b/Documentation/fsck-msgids.adoc
index 02a7bf0503..9601fff228 100644
--- a/Documentation/fsck-msgids.adoc
+++ b/Documentation/fsck-msgids.adoc
@@ -187,6 +187,9 @@
(ERROR) The "packed-refs" file contains an entry that is
not terminated by a newline.
+`packedRefUnsorted`::
+ (ERROR) The "packed-refs" file is not sorted.
+
`refMissingNewline`::
(INFO) A loose ref that does not end with newline(LF). As
valid implementations of Git never created such a loose ref
diff --git a/fsck.h b/fsck.h
index 14d70f6653..19f3cb2773 100644
--- a/fsck.h
+++ b/fsck.h
@@ -56,6 +56,7 @@ enum fsck_msg_type {
FUNC(MISSING_TYPE_ENTRY, ERROR) \
FUNC(MULTIPLE_AUTHORS, ERROR) \
FUNC(PACKED_REF_ENTRY_NOT_TERMINATED, ERROR) \
+ FUNC(PACKED_REF_UNSORTED, ERROR) \
FUNC(TREE_NOT_SORTED, ERROR) \
FUNC(UNKNOWN_TYPE, ERROR) \
FUNC(ZERO_PADDED_DATE, ERROR) \
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index dd3f7ab255..75f28e283a 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -300,14 +300,9 @@ struct snapshot_record {
size_t len;
};
-static int cmp_packed_ref_records(const void *v1, const void *v2,
- void *cb_data)
-{
- const struct snapshot *snapshot = cb_data;
- const struct snapshot_record *e1 = v1, *e2 = v2;
- const char *r1 = e1->start + snapshot_hexsz(snapshot) + 1;
- const char *r2 = e2->start + snapshot_hexsz(snapshot) + 1;
+static int cmp_packed_refname(const char *r1, const char *r2)
+{
while (1) {
if (*r1 == '\n')
return *r2 == '\n' ? 0 : -1;
@@ -322,6 +317,17 @@ static int cmp_packed_ref_records(const void *v1, const void *v2,
}
}
+static int cmp_packed_ref_records(const void *v1, const void *v2,
+ void *cb_data)
+{
+ const struct snapshot *snapshot = cb_data;
+ const struct snapshot_record *e1 = v1, *e2 = v2;
+ const char *r1 = e1->start + snapshot_hexsz(snapshot) + 1;
+ const char *r2 = e2->start + snapshot_hexsz(snapshot) + 1;
+
+ return cmp_packed_refname(r1, r2);
+}
+
/*
* Compare a snapshot record at `rec` to the specified NUL-terminated
* refname.
@@ -1797,19 +1803,33 @@ static int packed_fsck_ref_next_line(struct fsck_options *o,
}
static int packed_fsck_ref_header(struct fsck_options *o,
- const char *start, const char *eol)
+ const char *start, const char *eol,
+ unsigned int *sorted)
{
- if (!starts_with(start, "# pack-refs with: ")) {
+ struct string_list traits = STRING_LIST_INIT_NODUP;
+ char *tmp_line;
+ int ret = 0;
+ char *p;
+
+ tmp_line = xmemdupz(start, eol - start);
+ if (!skip_prefix(tmp_line, "# pack-refs with: ", (const char **)&p)) {
struct fsck_ref_report report = { 0 };
report.path = "packed-refs.header";
- return fsck_report_ref(o, &report,
- FSCK_MSG_BAD_PACKED_REF_HEADER,
- "'%.*s' does not start with '# pack-refs with: '",
- (int)(eol - start), start);
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_HEADER,
+ "'%.*s' does not start with '# pack-refs with: '",
+ (int)(eol - start), start);
+ goto cleanup;
}
- return 0;
+ string_list_split_in_place(&traits, p, " ", -1);
+ *sorted = unsorted_string_list_has_string(&traits, "sorted");
+
+cleanup:
+ free(tmp_line);
+ string_list_clear(&traits, 0);
+ return ret;
}
static int packed_fsck_ref_peeled_line(struct fsck_options *o,
@@ -1915,8 +1935,68 @@ static int packed_fsck_ref_main_line(struct fsck_options *o,
return ret;
}
+static int packed_fsck_ref_sorted(struct fsck_options *o,
+ struct ref_store *ref_store,
+ const char *start, const char *eof)
+{
+ size_t hexsz = ref_store->repo->hash_algo->hexsz;
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct strbuf refname1 = STRBUF_INIT;
+ struct strbuf refname2 = STRBUF_INIT;
+ unsigned long line_number = 1;
+ const char *former = NULL;
+ const char *current;
+ const char *eol;
+ int ret = 0;
+
+ if (*start == '#') {
+ eol = memchr(start, '\n', eof - start);
+ start = eol + 1;
+ line_number++;
+ }
+
+ for (; start < eof; line_number++, start = eol + 1) {
+ eol = memchr(start, '\n', eof - start);
+
+ if (*start == '^')
+ continue;
+
+ if (!former) {
+ former = start + hexsz + 1;
+ continue;
+ }
+
+ current = start + hexsz + 1;
+ if (cmp_packed_refname(former, current) >= 0) {
+ const char *err_fmt =
+ "refname '%s' is less than previous refname '%s'";
+
+ eol = memchr(former, '\n', eof - former);
+ strbuf_add(&refname1, former, eol - former);
+ eol = memchr(current, '\n', eof - current);
+ strbuf_add(&refname2, current, eol - current);
+
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_PACKED_REF_UNSORTED,
+ err_fmt, refname2.buf, refname1.buf);
+ goto cleanup;
+ }
+ former = current;
+ }
+
+cleanup:
+ strbuf_release(&packed_entry);
+ strbuf_release(&refname1);
+ strbuf_release(&refname2);
+ return ret;
+}
+
static int packed_fsck_ref_content(struct fsck_options *o,
struct ref_store *ref_store,
+ unsigned int *sorted,
const char *start, const char *eof)
{
struct strbuf refname = STRBUF_INIT;
@@ -1926,7 +2006,7 @@ static int packed_fsck_ref_content(struct fsck_options *o,
ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
if (*start == '#') {
- ret |= packed_fsck_ref_header(o, start, eol);
+ ret |= packed_fsck_ref_header(o, start, eol, sorted);
start = eol + 1;
line_number++;
@@ -1957,9 +2037,10 @@ static int packed_fsck(struct ref_store *ref_store,
struct packed_ref_store *refs = packed_downcast(ref_store,
REF_STORE_READ, "fsck");
struct strbuf packed_ref_content = STRBUF_INIT;
+ unsigned int sorted = 0;
struct stat st;
- int fd;
int ret = 0;
+ int fd;
if (!is_main_worktree(wt))
goto cleanup;
@@ -2012,8 +2093,11 @@ static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
- ret = packed_fsck_ref_content(o, ref_store, packed_ref_content.buf,
+ ret = packed_fsck_ref_content(o, ref_store, &sorted, packed_ref_content.buf,
packed_ref_content.buf + packed_ref_content.len);
+ if (!ret && sorted)
+ ret = packed_fsck_ref_sorted(o, ref_store, packed_ref_content.buf,
+ packed_ref_content.buf + packed_ref_content.len);
cleanup:
strbuf_release(&packed_ref_content);
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index 7421cc1e7f..28dc8dcddc 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -735,4 +735,91 @@ test_expect_success 'packed-refs content should be checked' '
)
'
+test_expect_success 'packed-ref with sorted trait should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git tag -a annotated-tag-1 -m tag-1 &&
+ branch_1_oid=$(git rev-parse branch-1) &&
+ branch_2_oid=$(git rev-parse branch-2) &&
+ tag_1_oid=$(git rev-parse annotated-tag-1) &&
+ tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
+ refname1="refs/heads/main" &&
+ refname2="refs/heads/foo" &&
+ refname3="refs/tags/foo" &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ EOF
+ git refs verify 2>err &&
+ rm .git/packed-refs &&
+ test_must_be_empty err &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ $branch_2_oid $refname1
+ EOF
+ git refs verify 2>err &&
+ rm .git/packed-refs &&
+ test_must_be_empty err &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ $branch_2_oid $refname1
+ $branch_1_oid $refname2
+ $tag_1_oid $refname3
+ EOF
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs line 3: packedRefUnsorted: refname '\''$refname2'\'' is less than previous refname '\''$refname1'\''
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ $tag_1_oid $refname3
+ ^$tag_1_peeled_oid
+ $branch_2_oid $refname2
+ EOF
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs line 4: packedRefUnsorted: refname '\''$refname2'\'' is less than previous refname '\''$refname3'\''
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err
+ )
+'
+
+test_expect_success 'packed-ref without sorted trait should not be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git tag -a annotated-tag-1 -m tag-1 &&
+ branch_1_oid=$(git rev-parse branch-1) &&
+ branch_2_oid=$(git rev-parse branch-2) &&
+ tag_1_oid=$(git rev-parse annotated-tag-1) &&
+ tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
+ refname1="refs/heads/main" &&
+ refname2="refs/heads/foo" &&
+ refname3="refs/tags/foo" &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled
+ $branch_2_oid $refname1
+ $branch_1_oid $refname2
+ EOF
+ git refs verify 2>err &&
+ test_must_be_empty err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v6 9/9] builtin/fsck: add `git refs verify` child process
2025-02-25 13:19 ` [PATCH v6 0/9] " shejialuo
` (7 preceding siblings ...)
2025-02-25 13:22 ` [PATCH v6 8/9] packed-backend: check whether the "packed-refs" is sorted shejialuo
@ 2025-02-25 13:22 ` shejialuo
2025-02-26 13:48 ` [PATCH v7 0/9] add more ref consistency checks shejialuo
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-25 13:22 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
At now, we have already implemented the ref consistency checks for both
"files-backend" and "packed-backend". Although we would check some
redundant things, it won't cause trouble. So, let's integrate it into
the "git-fsck(1)" command to get feedback from the users. And also by
calling "git refs verify" in "git-fsck(1)", we make sure that the new
added checks don't break.
Introduce a new function "fsck_refs" that initializes and runs a child
process to execute the "git refs verify" command. In order to provide
the user interface create a progress which makes the total task be 1.
It's hard to know how many loose refs we will check now. We might
improve this later.
Then, introduce the option to allow the user to disable checking ref
database consistency. Put this function in the very first execution
sequence of "git-fsck(1)" due to that we don't want the existing code of
"git-fsck(1)" which would implicitly check the consistency of refs to
die the program.
Last, update the test to exercise the code.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/git-fsck.adoc | 7 ++++++-
builtin/fsck.c | 33 ++++++++++++++++++++++++++++++-
t/t0602-reffiles-fsck.sh | 39 +++++++++++++++++++++++++++++++++++++
3 files changed, 77 insertions(+), 2 deletions(-)
diff --git a/Documentation/git-fsck.adoc b/Documentation/git-fsck.adoc
index 8f32800a83..11203ba925 100644
--- a/Documentation/git-fsck.adoc
+++ b/Documentation/git-fsck.adoc
@@ -12,7 +12,7 @@ SYNOPSIS
'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]
[--[no-]full] [--strict] [--verbose] [--lost-found]
[--[no-]dangling] [--[no-]progress] [--connectivity-only]
- [--[no-]name-objects] [<object>...]
+ [--[no-]name-objects] [--[no-]references] [<object>...]
DESCRIPTION
-----------
@@ -104,6 +104,11 @@ care about this output and want to speed it up further.
progress status even if the standard error stream is not
directed to a terminal.
+--[no-]references::
+ Control whether to check the references database consistency
+ via 'git refs verify'. See linkgit:git-refs[1] for details.
+ The default is to check the references database.
+
CONFIGURATION
-------------
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 7a4dcb0716..f4f395cfbd 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -50,6 +50,7 @@ static int verbose;
static int show_progress = -1;
static int show_dangling = 1;
static int name_objects;
+static int check_references = 1;
#define ERROR_OBJECT 01
#define ERROR_REACHABLE 02
#define ERROR_PACK 04
@@ -905,11 +906,37 @@ static int check_pack_rev_indexes(struct repository *r, int show_progress)
return res;
}
+static void fsck_refs(struct repository *r)
+{
+ struct child_process refs_verify = CHILD_PROCESS_INIT;
+ struct progress *progress = NULL;
+
+ if (show_progress)
+ progress = start_progress(r, _("Checking ref database"), 1);
+
+ if (verbose)
+ fprintf_ln(stderr, _("Checking ref database"));
+
+ child_process_init(&refs_verify);
+ refs_verify.git_cmd = 1;
+ strvec_pushl(&refs_verify.args, "refs", "verify", NULL);
+ if (verbose)
+ strvec_push(&refs_verify.args, "--verbose");
+ if (check_strict)
+ strvec_push(&refs_verify.args, "--strict");
+
+ if (run_command(&refs_verify))
+ errors_found |= ERROR_REFS;
+
+ display_progress(progress, 1);
+ stop_progress(&progress);
+}
+
static char const * const fsck_usage[] = {
N_("git fsck [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]\n"
" [--[no-]full] [--strict] [--verbose] [--lost-found]\n"
" [--[no-]dangling] [--[no-]progress] [--connectivity-only]\n"
- " [--[no-]name-objects] [<object>...]"),
+ " [--[no-]name-objects] [--[no-]references] [<object>...]"),
NULL
};
@@ -928,6 +955,7 @@ static struct option fsck_opts[] = {
N_("write dangling objects in .git/lost-found")),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
OPT_BOOL(0, "name-objects", &name_objects, N_("show verbose names for reachable objects")),
+ OPT_BOOL(0, "references", &check_references, N_("check reference database consistency")),
OPT_END(),
};
@@ -970,6 +998,9 @@ int cmd_fsck(int argc,
git_config(git_fsck_config, &fsck_obj_options);
prepare_repo_settings(the_repository);
+ if (check_references)
+ fsck_refs(the_repository);
+
if (connectivity_only) {
for_each_loose_object(mark_loose_for_connectivity, NULL, 0);
for_each_packed_object(the_repository,
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index 28dc8dcddc..42e8a84739 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -822,4 +822,43 @@ test_expect_success 'packed-ref without sorted trait should not be checked' '
)
'
+test_expect_success '--[no-]references option should apply to fsck' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ branch_dir_prefix=.git/refs/heads &&
+ (
+ cd repo &&
+ test_commit default &&
+ for trailing_content in " garbage" " more garbage"
+ do
+ printf "%s" "$(git rev-parse HEAD)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ git fsck 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage &&
+ test_cmp expect err || return 1
+ done &&
+
+ for trailing_content in " garbage" " more garbage"
+ do
+ printf "%s" "$(git rev-parse HEAD)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ git fsck --references 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage &&
+ test_cmp expect err || return 1
+ done &&
+
+ for trailing_content in " garbage" " more garbage"
+ do
+ printf "%s" "$(git rev-parse HEAD)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ git fsck --no-references 2>err &&
+ rm $branch_dir_prefix/branch-garbage &&
+ test_must_be_empty err || return 1
+ done
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v7 0/9] add more ref consistency checks
2025-02-25 13:19 ` [PATCH v6 0/9] " shejialuo
` (8 preceding siblings ...)
2025-02-25 13:22 ` [PATCH v6 9/9] builtin/fsck: add `git refs verify` child process shejialuo
@ 2025-02-26 13:48 ` shejialuo
2025-02-26 13:49 ` [PATCH v7 1/9] t0602: use subshell to ensure working directory unchanged shejialuo
` (9 more replies)
9 siblings, 10 replies; 168+ messages in thread
From: shejialuo @ 2025-02-26 13:48 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
Hi All:
This changes enhances the following things:
1. [PATCH v7 3/9]: use "open_nofollow" with "fstat" to check whether the
file is regular. And update the test to improve coverage.
2. [PACTH v7 4/9]: improve the commit message suggested by Patrick.
Thanks,
Jialuo
---
This series mainly does the following things:
1. Fix subshell issues
2. Add ref checks for packed-backend.
1. Check whether the filetype of "packed-refs" is correct.
2. Check whether the syntax of "packed-refs" is correct by using the
rules from "packed-backend.c::create_snapshot" and
"packed-backend.c::next_record".
3. Check whether the pointed object exists and whether the
"packed-refs" file is sorted.
3. Call "git refs verify" for "git-fsck(1)".
shejialuo (9):
t0602: use subshell to ensure working directory unchanged
builtin/refs: get worktrees without reading head information
packed-backend: check whether the "packed-refs" is regular file
packed-backend: check if header starts with "# pack-refs with: "
packed-backend: add "packed-refs" header consistency check
packed-backend: check whether the refname contains NUL characters
packed-backend: add "packed-refs" entry consistency check
packed-backend: check whether the "packed-refs" is sorted
builtin/fsck: add `git refs verify` child process
Documentation/fsck-msgids.adoc | 14 +
Documentation/git-fsck.adoc | 7 +-
builtin/fsck.c | 33 +-
builtin/refs.c | 2 +-
fsck.h | 4 +
refs/packed-backend.c | 361 +++++++++-
t/t0602-reffiles-fsck.sh | 1209 +++++++++++++++++++-------------
worktree.c | 5 +
worktree.h | 8 +
9 files changed, 1161 insertions(+), 482 deletions(-)
Range-diff against v6:
1: b3952d80a2 = 1: b3952d80a2 t0602: use subshell to ensure working directory unchanged
2: fa5ce20bb7 = 2: fa5ce20bb7 builtin/refs: get worktrees without reading head information
3: 787645a700 ! 3: 861583f417 packed-backend: check whether the "packed-refs" is regular file
@@ Commit message
the expected filetype, confirming it is created by "git pack-refs"
command.
- Use "lstat" to check the file mode. If we cannot check the file status
- due to there is no such file this is OK because there is a possibility
- that there is no "packed-refs" in the repo.
+ We could use "open_nofollow" wrapper to open the raw "packed-refs" file.
+ If the returned "fd" value is less than 0, we could check whether the
+ "errno" is "ELOOP" to report an error to the user. And then we use
+ "fstat" to check whether the "packed-refs" file is a regular file.
Reuse "FSCK_MSG_BAD_REF_FILETYPE" fsck message id to report the error to
the user if "packed-refs" is not a regular file.
@@ refs/packed-backend.c: static struct ref_iterator *packed_reflog_iterator_begin(
+ REF_STORE_READ, "fsck");
+ struct stat st;
+ int ret = 0;
++ int fd;
if (!is_main_worktree(wt))
-- return 0;
-+ goto cleanup;
+ return 0;
- return 0;
+ if (o->verbose)
+ fprintf_ln(stderr, "Checking packed-refs file %s", refs->path);
+
-+ if (lstat(refs->path, &st) < 0) {
++ fd = open_nofollow(refs->path, O_RDONLY);
++ if (fd < 0) {
+ /*
+ * If the packed-refs file doesn't exist, there's nothing
+ * to check.
+ */
+ if (errno == ENOENT)
+ goto cleanup;
++
++ if (errno == ELOOP) {
++ struct fsck_ref_report report = { 0 };
++ report.path = "packed-refs";
++ ret = fsck_report_ref(o, &report,
++ FSCK_MSG_BAD_REF_FILETYPE,
++ "not a regular file but a symlink");
++ goto cleanup;
++ }
++
++ ret = error_errno(_("unable to open '%s'"), refs->path);
++ goto cleanup;
++ } else if (fstat(fd, &st) < 0) {
+ ret = error_errno(_("unable to stat '%s'"), refs->path);
+ goto cleanup;
-+ }
-+
-+ if (!S_ISREG(st.st_mode)) {
++ } else if (!S_ISREG(st.st_mode)) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs";
+ ret = fsck_report_ref(o, &report,
@@ refs/packed-backend.c: static struct ref_iterator *packed_reflog_iterator_begin(
+ }
+
+cleanup:
++ if (fd >= 0)
++ close(fd);
+ return ret;
}
@@ t/t0602-reffiles-fsck.sh: test_expect_success 'ref content checks should work wi
+ ln -sf packed-refs-back .git/packed-refs &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
-+ error: packed-refs: badRefFiletype: not a regular file
++ error: packed-refs: badRefFiletype: not a regular file but a symlink
+ EOF
+ rm .git/packed-refs &&
++ test_cmp expect err &&
++
++ mkdir .git/packed-refs &&
++ test_must_fail git refs verify 2>err &&
++ cat >expect <<-EOF &&
++ error: packed-refs: badRefFiletype: not a regular file
++ EOF
++ rm -r .git/packed-refs &&
+ test_cmp expect err
+ )
+'
4: f097e0f093 ! 4: 5f54cb05c3 packed-backend: check if header starts with "# pack-refs with: "
@@ Metadata
## Commit message ##
packed-backend: check if header starts with "# pack-refs with: "
- We always write a space after "# pack-refs with:". However, when
- creating the packed-ref snapshot, we only check whether the header
- starts with "# pack-refs with:". However, we need to make sure that we
- would not break compatibility by tightening the rule. The following is
- how some third-party libraries handle the header of "packed-ref" file.
+ We always write a space after "# pack-refs with:" but we don't align
+ with this rule in the "create_snapshot" method where we would check
+ whether header starts with "# pack-refs with:". It might seem that we
+ should undoubtedly tighten this rule, however, we don't have any
+ technical documentation about this and there is a possibility that we
+ would break the compatibility for other third-party libraries.
+
+ By investigating influential third-party libraries, we could conclude
+ how these libraries handle the header of "packed-refs" file:
1. libgit2 is fine and always writes the space. It also expects the
whitespace to exist.
@@ Commit message
3. gitoxide expects the space t exist and writes it.
4. go-git doesn't create the header by default.
- So, we are safe to tighten the rule by checking whether the header
- starts with "# pack-refs with: ".
+ As many third-party libraries expect a single space after "# pack-refs
+ with:", if we forget to write the space after the colon,
+ "create_snapshot" won't catch this. And we would break other
+ re-implementations. So, we'd better tighten the rule by checking whether
+ the header starts with "# pack-refs with: ".
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
5: a589a38b68 ! 5: 7d7dc899ad packed-backend: add "packed-refs" header consistency check
@@ refs/packed-backend.c: static struct ref_iterator *packed_reflog_iterator_begin(
REF_STORE_READ, "fsck");
+ struct strbuf packed_ref_content = STRBUF_INIT;
struct stat st;
-+ int fd;
int ret = 0;
-
- if (!is_main_worktree(wt))
+ int fd;
@@ refs/packed-backend.c: static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
-+ /*
-+ * There is a chance that "packed-refs" file is removed or converted to
-+ * a symlink after filetype check and before open. So we need to avoid
-+ * this race condition by opening the file.
-+ */
-+ fd = open_nofollow(refs->path, O_RDONLY);
-+ if (fd < 0) {
-+ if (errno == ENOENT)
-+ goto cleanup;
-+
-+ if (errno == ELOOP) {
-+ struct fsck_ref_report report = { 0 };
-+ report.path = "packed-refs";
-+ ret = fsck_report_ref(o, &report,
-+ FSCK_MSG_BAD_REF_FILETYPE,
-+ "not a regular file");
-+ goto cleanup;
-+ }
-+ }
-+
+ if (strbuf_read(&packed_ref_content, fd, 0) < 0) {
-+ ret = error_errno(_("unable to read %s"), refs->path);
++ ret = error_errno(_("unable to read '%s'"), refs->path);
+ goto cleanup;
+ }
+
@@ refs/packed-backend.c: static int packed_fsck(struct ref_store *ref_store,
+ packed_ref_content.buf + packed_ref_content.len);
+
cleanup:
+ if (fd >= 0)
+ close(fd);
+ strbuf_release(&packed_ref_content);
return ret;
}
6: 7255c2b597 = 6: 571479d3e7 packed-backend: check whether the refname contains NUL characters
7: 7794a2ebfd = 7: e498a57286 packed-backend: add "packed-refs" entry consistency check
8: 2a9138b14d ! 8: 3638cb118d packed-backend: check whether the "packed-refs" is sorted
@@ refs/packed-backend.c: static int packed_fsck(struct ref_store *ref_store,
struct strbuf packed_ref_content = STRBUF_INIT;
+ unsigned int sorted = 0;
struct stat st;
-- int fd;
int ret = 0;
-+ int fd;
-
- if (!is_main_worktree(wt))
- goto cleanup;
+ int fd;
@@ refs/packed-backend.c: static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
@@ refs/packed-backend.c: static int packed_fsck(struct ref_store *ref_store,
+ packed_ref_content.buf + packed_ref_content.len);
cleanup:
- strbuf_release(&packed_ref_content);
+ if (fd >= 0)
## t/t0602-reffiles-fsck.sh ##
@@ t/t0602-reffiles-fsck.sh: test_expect_success 'packed-refs content should be checked' '
9: ccde32491f = 9: 5d87e76d28 builtin/fsck: add `git refs verify` child process
--
2.48.1
^ permalink raw reply [flat|nested] 168+ messages in thread
* [PATCH v7 1/9] t0602: use subshell to ensure working directory unchanged
2025-02-26 13:48 ` [PATCH v7 0/9] add more ref consistency checks shejialuo
@ 2025-02-26 13:49 ` shejialuo
2025-02-26 13:49 ` [PATCH v7 2/9] builtin/refs: get worktrees without reading head information shejialuo
` (8 subsequent siblings)
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-26 13:49 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
For every test, we would execute the command "cd repo" in the first but
we never execute the command "cd .." to restore the working directory.
However, it's either not a good idea use above way. Because if any test
fails between "cd repo" and "cd ..", the "cd .." will never be reached.
And we cannot correctly restore the working directory.
Let's use subshell to ensure that the current working directory could be
restored to the correct path.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
t/t0602-reffiles-fsck.sh | 967 ++++++++++++++++++++-------------------
1 file changed, 494 insertions(+), 473 deletions(-)
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index d4a08b823b..cf7a202d0d 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -14,222 +14,229 @@ test_expect_success 'ref name should be checked' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
-
- git commit --allow-empty -m initial &&
- git checkout -b default-branch &&
- git tag default-tag &&
- git tag multi_hierarchy/default-tag &&
-
- cp $branch_dir_prefix/default-branch $branch_dir_prefix/@ &&
- git refs verify 2>err &&
- test_must_be_empty err &&
- rm $branch_dir_prefix/@ &&
-
- cp $tag_dir_prefix/default-tag $tag_dir_prefix/tag-1.lock &&
- git refs verify 2>err &&
- rm $tag_dir_prefix/tag-1.lock &&
- test_must_be_empty err &&
-
- cp $tag_dir_prefix/default-tag $tag_dir_prefix/.lock &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/tags/.lock: badRefName: invalid refname format
- EOF
- rm $tag_dir_prefix/.lock &&
- test_cmp expect err &&
-
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname" &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/$refname: badRefName: invalid refname format
- EOF
- rm "$branch_dir_prefix/$refname" &&
- test_cmp expect err || return 1
- done &&
+ (
+ cd repo &&
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- cp $tag_dir_prefix/default-tag "$tag_dir_prefix/$refname" &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/tags/$refname: badRefName: invalid refname format
- EOF
- rm "$tag_dir_prefix/$refname" &&
- test_cmp expect err || return 1
- done &&
+ git commit --allow-empty -m initial &&
+ git checkout -b default-branch &&
+ git tag default-tag &&
+ git tag multi_hierarchy/default-tag &&
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- cp $tag_dir_prefix/multi_hierarchy/default-tag "$tag_dir_prefix/multi_hierarchy/$refname" &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/tags/multi_hierarchy/$refname: badRefName: invalid refname format
- EOF
- rm "$tag_dir_prefix/multi_hierarchy/$refname" &&
- test_cmp expect err || return 1
- done &&
-
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- mkdir "$branch_dir_prefix/$refname" &&
- cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname/default-branch" &&
+ cp $branch_dir_prefix/default-branch $branch_dir_prefix/@ &&
+ git refs verify 2>err &&
+ test_must_be_empty err &&
+ rm $branch_dir_prefix/@ &&
+
+ cp $tag_dir_prefix/default-tag $tag_dir_prefix/tag-1.lock &&
+ git refs verify 2>err &&
+ rm $tag_dir_prefix/tag-1.lock &&
+ test_must_be_empty err &&
+
+ cp $tag_dir_prefix/default-tag $tag_dir_prefix/.lock &&
test_must_fail git refs verify 2>err &&
cat >expect <<-EOF &&
- error: refs/heads/$refname/default-branch: badRefName: invalid refname format
+ error: refs/tags/.lock: badRefName: invalid refname format
EOF
- rm -r "$branch_dir_prefix/$refname" &&
- test_cmp expect err || return 1
- done
+ rm $tag_dir_prefix/.lock &&
+ test_cmp expect err &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/$refname: badRefName: invalid refname format
+ EOF
+ rm "$branch_dir_prefix/$refname" &&
+ test_cmp expect err || return 1
+ done &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ cp $tag_dir_prefix/default-tag "$tag_dir_prefix/$refname" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/tags/$refname: badRefName: invalid refname format
+ EOF
+ rm "$tag_dir_prefix/$refname" &&
+ test_cmp expect err || return 1
+ done &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ cp $tag_dir_prefix/multi_hierarchy/default-tag "$tag_dir_prefix/multi_hierarchy/$refname" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/tags/multi_hierarchy/$refname: badRefName: invalid refname format
+ EOF
+ rm "$tag_dir_prefix/multi_hierarchy/$refname" &&
+ test_cmp expect err || return 1
+ done &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ mkdir "$branch_dir_prefix/$refname" &&
+ cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname/default-branch" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/$refname/default-branch: badRefName: invalid refname format
+ EOF
+ rm -r "$branch_dir_prefix/$refname" &&
+ test_cmp expect err || return 1
+ done
+ )
'
test_expect_success 'ref name check should be adapted into fsck messages' '
test_when_finished "rm -rf repo" &&
git init repo &&
branch_dir_prefix=.git/refs/heads &&
- cd repo &&
- git commit --allow-empty -m initial &&
- git checkout -b branch-1 &&
-
- cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
- git -c fsck.badRefName=warn refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/.branch-1: badRefName: invalid refname format
- EOF
- rm $branch_dir_prefix/.branch-1 &&
- test_cmp expect err &&
-
- cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
- git -c fsck.badRefName=ignore refs verify 2>err &&
- test_must_be_empty err
+ (
+ cd repo &&
+ git commit --allow-empty -m initial &&
+ git checkout -b branch-1 &&
+
+ cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
+ git -c fsck.badRefName=warn refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/.branch-1: badRefName: invalid refname format
+ EOF
+ rm $branch_dir_prefix/.branch-1 &&
+ test_cmp expect err &&
+
+ cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
+ git -c fsck.badRefName=ignore refs verify 2>err &&
+ test_must_be_empty err
+ )
'
test_expect_success 'ref name check should work for multiple worktrees' '
test_when_finished "rm -rf repo" &&
git init repo &&
-
- cd repo &&
- test_commit initial &&
- git checkout -b branch-1 &&
- test_commit second &&
- git checkout -b branch-2 &&
- test_commit third &&
- git checkout -b branch-3 &&
- git worktree add ./worktree-1 branch-1 &&
- git worktree add ./worktree-2 branch-2 &&
- worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
- worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
-
- (
- cd worktree-1 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-3
- ) &&
(
- cd worktree-2 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-3
- ) &&
-
- cp $worktree1_refdir_prefix/branch-4 $worktree1_refdir_prefix/'\'' branch-5'\'' &&
- cp $worktree2_refdir_prefix/branch-4 $worktree2_refdir_prefix/'\''~branch-6'\'' &&
-
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
- error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err &&
-
- for worktree in "worktree-1" "worktree-2"
- do
+ cd repo &&
+ test_commit initial &&
+ git checkout -b branch-1 &&
+ test_commit second &&
+ git checkout -b branch-2 &&
+ test_commit third &&
+ git checkout -b branch-3 &&
+ git worktree add ./worktree-1 branch-1 &&
+ git worktree add ./worktree-2 branch-2 &&
+ worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
+ worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
+
(
- cd $worktree &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
- error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err || return 1
- )
- done
+ cd worktree-1 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-3
+ ) &&
+ (
+ cd worktree-2 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-3
+ ) &&
+
+ cp $worktree1_refdir_prefix/branch-4 $worktree1_refdir_prefix/'\'' branch-5'\'' &&
+ cp $worktree2_refdir_prefix/branch-4 $worktree2_refdir_prefix/'\''~branch-6'\'' &&
+
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
+ error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err &&
+
+ for worktree in "worktree-1" "worktree-2"
+ do
+ (
+ cd $worktree &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
+ error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err || return 1
+ )
+ done
+ )
'
test_expect_success 'regular ref content should be checked (individual)' '
test_when_finished "rm -rf repo" &&
git init repo &&
branch_dir_prefix=.git/refs/heads &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
- git refs verify 2>err &&
- test_must_be_empty err &&
+ git refs verify 2>err &&
+ test_must_be_empty err &&
- for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$branch_dir_prefix/branch-bad &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/branch-bad: badRefContent: $bad_content
- EOF
- rm $branch_dir_prefix/branch-bad &&
- test_cmp expect err || return 1
- done &&
+ for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$branch_dir_prefix/branch-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/branch-bad: badRefContent: $bad_content
+ EOF
+ rm $branch_dir_prefix/branch-bad &&
+ test_cmp expect err || return 1
+ done &&
- for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$branch_dir_prefix/a/b/branch-bad &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/a/b/branch-bad: badRefContent: $bad_content
- EOF
- rm $branch_dir_prefix/a/b/branch-bad &&
- test_cmp expect err || return 1
- done &&
-
- printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- rm $branch_dir_prefix/branch-no-newline &&
- test_cmp expect err &&
-
- for trailing_content in " garbage" " more garbage"
- do
- printf "%s" "$(git rev-parse main)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$branch_dir_prefix/a/b/branch-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/a/b/branch-bad: badRefContent: $bad_content
+ EOF
+ rm $branch_dir_prefix/a/b/branch-bad &&
+ test_cmp expect err || return 1
+ done &&
+
+ printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
EOF
- rm $branch_dir_prefix/branch-garbage &&
- test_cmp expect err || return 1
- done &&
+ rm $branch_dir_prefix/branch-no-newline &&
+ test_cmp expect err &&
- printf "%s\n\n\n" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
+ for trailing_content in " garbage" " more garbage"
+ do
+ printf "%s" "$(git rev-parse main)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage &&
+ test_cmp expect err || return 1
+ done &&
+ printf "%s\n\n\n" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
- '\''
- EOF
- rm $branch_dir_prefix/branch-garbage-special &&
- test_cmp expect err &&
- printf "%s\n\n\n garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
+ '\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage-special &&
+ test_cmp expect err &&
+
+ printf "%s\n\n\n garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
- garbage'\''
- EOF
- rm $branch_dir_prefix/branch-garbage-special &&
- test_cmp expect err
+ garbage'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage-special &&
+ test_cmp expect err
+ )
'
test_expect_success 'regular ref content should be checked (aggregate)' '
@@ -237,99 +244,103 @@ test_expect_success 'regular ref content should be checked (aggregate)' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- bad_content_1=$(git rev-parse main)x &&
- bad_content_2=xfsazqfxcadas &&
- bad_content_3=Xfsazqfxcadas &&
- printf "%s" $bad_content_1 >$tag_dir_prefix/tag-bad-1 &&
- printf "%s" $bad_content_2 >$tag_dir_prefix/tag-bad-2 &&
- printf "%s" $bad_content_3 >$branch_dir_prefix/a/b/branch-bad &&
- printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
- printf "%s garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage &&
-
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/a/b/branch-bad: badRefContent: $bad_content_3
- error: refs/tags/tag-bad-1: badRefContent: $bad_content_1
- error: refs/tags/tag-bad-2: badRefContent: $bad_content_2
- warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
- warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ bad_content_1=$(git rev-parse main)x &&
+ bad_content_2=xfsazqfxcadas &&
+ bad_content_3=Xfsazqfxcadas &&
+ printf "%s" $bad_content_1 >$tag_dir_prefix/tag-bad-1 &&
+ printf "%s" $bad_content_2 >$tag_dir_prefix/tag-bad-2 &&
+ printf "%s" $bad_content_3 >$branch_dir_prefix/a/b/branch-bad &&
+ printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
+ printf "%s garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage &&
+
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/a/b/branch-bad: badRefContent: $bad_content_3
+ error: refs/tags/tag-bad-1: badRefContent: $bad_content_1
+ error: refs/tags/tag-bad-2: badRefContent: $bad_content_2
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
+ warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err
+ )
'
test_expect_success 'textual symref content should be checked (individual)' '
test_when_finished "rm -rf repo" &&
git init repo &&
branch_dir_prefix=.git/refs/heads &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ for good_referent in "refs/heads/branch" "HEAD"
+ do
+ printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
+ git refs verify 2>err &&
+ rm $branch_dir_prefix/branch-good &&
+ test_must_be_empty err || return 1
+ done &&
+
+ for bad_referent in "refs/heads/.branch" "refs/heads/~branch" "refs/heads/?branch"
+ do
+ printf "ref: %s\n" $bad_referent >$branch_dir_prefix/branch-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/branch-bad: badReferentName: points to invalid refname '\''$bad_referent'\''
+ EOF
+ rm $branch_dir_prefix/branch-bad &&
+ test_cmp expect err || return 1
+ done &&
- for good_referent in "refs/heads/branch" "HEAD"
- do
- printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
+ printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline &&
git refs verify 2>err &&
- rm $branch_dir_prefix/branch-good &&
- test_must_be_empty err || return 1
- done &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
+ EOF
+ rm $branch_dir_prefix/branch-no-newline &&
+ test_cmp expect err &&
- for bad_referent in "refs/heads/.branch" "refs/heads/~branch" "refs/heads/?branch"
- do
- printf "ref: %s\n" $bad_referent >$branch_dir_prefix/branch-bad &&
- test_must_fail git refs verify 2>err &&
+ printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
+ EOF
+ rm $branch_dir_prefix/a/b/branch-trailing-1 &&
+ test_cmp expect err &&
+
+ printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- error: refs/heads/branch-bad: badReferentName: points to invalid refname '\''$bad_referent'\''
+ warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
EOF
- rm $branch_dir_prefix/branch-bad &&
- test_cmp expect err || return 1
- done &&
-
- printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- rm $branch_dir_prefix/branch-no-newline &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-trailing-1 &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-trailing-2 &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-trailing-3 &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-complicated &&
- test_cmp expect err
+ rm $branch_dir_prefix/a/b/branch-trailing-2 &&
+ test_cmp expect err &&
+
+ printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
+ EOF
+ rm $branch_dir_prefix/a/b/branch-trailing-3 &&
+ test_cmp expect err &&
+
+ printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
+ EOF
+ rm $branch_dir_prefix/a/b/branch-complicated &&
+ test_cmp expect err
+ )
'
test_expect_success 'textual symref content should be checked (aggregate)' '
@@ -337,32 +348,34 @@ test_expect_success 'textual symref content should be checked (aggregate)' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- printf "ref: refs/heads/branch\n" >$branch_dir_prefix/branch-good &&
- printf "ref: HEAD\n" >$branch_dir_prefix/branch-head &&
- printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline-1 &&
- printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
- printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
- printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
- printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
- printf "ref: refs/heads/.branch\n" >$branch_dir_prefix/branch-bad-1 &&
-
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/branch-bad-1: badReferentName: points to invalid refname '\''refs/heads/.branch'\''
- warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/branch-no-newline-1: refMissingNewline: misses LF at the end
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ printf "ref: refs/heads/branch\n" >$branch_dir_prefix/branch-good &&
+ printf "ref: HEAD\n" >$branch_dir_prefix/branch-head &&
+ printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline-1 &&
+ printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
+ printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
+ printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
+ printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
+ printf "ref: refs/heads/.branch\n" >$branch_dir_prefix/branch-bad-1 &&
+
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/branch-bad-1: badReferentName: points to invalid refname '\''refs/heads/.branch'\''
+ warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/branch-no-newline-1: refMissingNewline: misses LF at the end
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err
+ )
'
test_expect_success 'the target of the textual symref should be checked' '
@@ -370,28 +383,30 @@ test_expect_success 'the target of the textual symref should be checked' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- for good_referent in "refs/heads/branch" "HEAD" "refs/tags/tag"
- do
- printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
- git refs verify 2>err &&
- rm $branch_dir_prefix/branch-good &&
- test_must_be_empty err || return 1
- done &&
-
- for nonref_referent in "refs-back/heads/branch" "refs-back/tags/tag" "reflogs/refs/heads/branch"
- do
- printf "ref: %s\n" $nonref_referent >$branch_dir_prefix/branch-bad-1 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-bad-1: symrefTargetIsNotARef: points to non-ref target '\''$nonref_referent'\''
- EOF
- rm $branch_dir_prefix/branch-bad-1 &&
- test_cmp expect err || return 1
- done
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ for good_referent in "refs/heads/branch" "HEAD" "refs/tags/tag"
+ do
+ printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
+ git refs verify 2>err &&
+ rm $branch_dir_prefix/branch-good &&
+ test_must_be_empty err || return 1
+ done &&
+
+ for nonref_referent in "refs-back/heads/branch" "refs-back/tags/tag" "reflogs/refs/heads/branch"
+ do
+ printf "ref: %s\n" $nonref_referent >$branch_dir_prefix/branch-bad-1 &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-bad-1: symrefTargetIsNotARef: points to non-ref target '\''$nonref_referent'\''
+ EOF
+ rm $branch_dir_prefix/branch-bad-1 &&
+ test_cmp expect err || return 1
+ done
+ )
'
test_expect_success SYMLINKS 'symlink symref content should be checked' '
@@ -399,201 +414,207 @@ test_expect_success SYMLINKS 'symlink symref content should be checked' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- ln -sf ./main $branch_dir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $branch_dir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../logs/branch-escape $branch_dir_prefix/branch-symbolic &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
- warning: refs/heads/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
- EOF
- rm $branch_dir_prefix/branch-symbolic &&
- test_cmp expect err &&
-
- ln -sf ./"branch " $branch_dir_prefix/branch-symbolic-bad &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic-bad: symlinkRef: use deprecated symbolic link for symref
- error: refs/heads/branch-symbolic-bad: badReferentName: points to invalid refname '\''refs/heads/branch '\''
- EOF
- rm $branch_dir_prefix/branch-symbolic-bad &&
- test_cmp expect err &&
-
- ln -sf ./".tag" $tag_dir_prefix/tag-symbolic-1 &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/tags/tag-symbolic-1: symlinkRef: use deprecated symbolic link for symref
- error: refs/tags/tag-symbolic-1: badReferentName: points to invalid refname '\''refs/tags/.tag'\''
- EOF
- rm $tag_dir_prefix/tag-symbolic-1 &&
- test_cmp expect err
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ ln -sf ./main $branch_dir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
+ EOF
+ rm $branch_dir_prefix/branch-symbolic-good &&
+ test_cmp expect err &&
+
+ ln -sf ../../logs/branch-escape $branch_dir_prefix/branch-symbolic &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
+ warning: refs/heads/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
+ EOF
+ rm $branch_dir_prefix/branch-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ./"branch " $branch_dir_prefix/branch-symbolic-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-symbolic-bad: symlinkRef: use deprecated symbolic link for symref
+ error: refs/heads/branch-symbolic-bad: badReferentName: points to invalid refname '\''refs/heads/branch '\''
+ EOF
+ rm $branch_dir_prefix/branch-symbolic-bad &&
+ test_cmp expect err &&
+
+ ln -sf ./".tag" $tag_dir_prefix/tag-symbolic-1 &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/tags/tag-symbolic-1: symlinkRef: use deprecated symbolic link for symref
+ error: refs/tags/tag-symbolic-1: badReferentName: points to invalid refname '\''refs/tags/.tag'\''
+ EOF
+ rm $tag_dir_prefix/tag-symbolic-1 &&
+ test_cmp expect err
+ )
'
test_expect_success SYMLINKS 'symlink symref content should be checked (worktree)' '
test_when_finished "rm -rf repo" &&
git init repo &&
- cd repo &&
- test_commit default &&
- git branch branch-1 &&
- git branch branch-2 &&
- git branch branch-3 &&
- git worktree add ./worktree-1 branch-2 &&
- git worktree add ./worktree-2 branch-3 &&
- main_worktree_refdir_prefix=.git/refs/heads &&
- worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
- worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
-
(
- cd worktree-1 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
- (
- cd worktree-2 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
-
- ln -sf ../../../../refs/heads/good-branch $worktree1_refdir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $worktree1_refdir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../../../worktrees/worktree-1/good-branch $worktree2_refdir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-2/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $worktree2_refdir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../worktrees/worktree-2/good-branch $main_worktree_refdir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $main_worktree_refdir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../../../logs/branch-escape $worktree1_refdir_prefix/branch-symbolic &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
- warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
- EOF
- rm $worktree1_refdir_prefix/branch-symbolic &&
- test_cmp expect err &&
-
- for bad_referent_name in ".tag" "branch "
- do
- ln -sf ./"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git branch branch-3 &&
+ git worktree add ./worktree-1 branch-2 &&
+ git worktree add ./worktree-2 branch-3 &&
+ main_worktree_refdir_prefix=.git/refs/heads &&
+ worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
+ worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
+
+ (
+ cd worktree-1 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+ (
+ cd worktree-2 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+
+ ln -sf ../../../../refs/heads/good-branch $worktree1_refdir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-1/refs/worktree/$bad_referent_name'\''
+ warning: worktrees/worktree-1/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
EOF
- rm $worktree1_refdir_prefix/bad-symbolic &&
+ rm $worktree1_refdir_prefix/branch-symbolic-good &&
test_cmp expect err &&
- ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ ln -sf ../../../../worktrees/worktree-1/good-branch $worktree2_refdir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ warning: worktrees/worktree-2/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
EOF
- rm $worktree1_refdir_prefix/bad-symbolic &&
+ rm $worktree2_refdir_prefix/branch-symbolic-good &&
test_cmp expect err &&
- ln -sf ./"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ ln -sf ../../worktrees/worktree-2/good-branch $main_worktree_refdir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-2/refs/worktree/$bad_referent_name'\''
+ warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
EOF
- rm $worktree2_refdir_prefix/bad-symbolic &&
+ rm $main_worktree_refdir_prefix/branch-symbolic-good &&
test_cmp expect err &&
- ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ ln -sf ../../../../logs/branch-escape $worktree1_refdir_prefix/branch-symbolic &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
+ warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
EOF
- rm $worktree2_refdir_prefix/bad-symbolic &&
- test_cmp expect err || return 1
- done
+ rm $worktree1_refdir_prefix/branch-symbolic &&
+ test_cmp expect err &&
+
+ for bad_referent_name in ".tag" "branch "
+ do
+ ln -sf ./"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-1/refs/worktree/$bad_referent_name'\''
+ EOF
+ rm $worktree1_refdir_prefix/bad-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ EOF
+ rm $worktree1_refdir_prefix/bad-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ./"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-2/refs/worktree/$bad_referent_name'\''
+ EOF
+ rm $worktree2_refdir_prefix/bad-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ EOF
+ rm $worktree2_refdir_prefix/bad-symbolic &&
+ test_cmp expect err || return 1
+ done
+ )
'
test_expect_success 'ref content checks should work with worktrees' '
test_when_finished "rm -rf repo" &&
git init repo &&
- cd repo &&
- test_commit default &&
- git branch branch-1 &&
- git branch branch-2 &&
- git branch branch-3 &&
- git worktree add ./worktree-1 branch-2 &&
- git worktree add ./worktree-2 branch-3 &&
- worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
- worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
-
(
- cd worktree-1 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
- (
- cd worktree-2 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git branch branch-3 &&
+ git worktree add ./worktree-1 branch-2 &&
+ git worktree add ./worktree-2 branch-3 &&
+ worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
+ worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
- for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$worktree1_refdir_prefix/bad-branch-1 &&
- test_must_fail git refs verify 2>err &&
+ (
+ cd worktree-1 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+ (
+ cd worktree-2 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+
+ for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$worktree1_refdir_prefix/bad-branch-1 &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-1/refs/worktree/bad-branch-1: badRefContent: $bad_content
+ EOF
+ rm $worktree1_refdir_prefix/bad-branch-1 &&
+ test_cmp expect err || return 1
+ done &&
+
+ for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$worktree2_refdir_prefix/bad-branch-2 &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-2/refs/worktree/bad-branch-2: badRefContent: $bad_content
+ EOF
+ rm $worktree2_refdir_prefix/bad-branch-2 &&
+ test_cmp expect err || return 1
+ done &&
+
+ printf "%s" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-no-newline &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- error: worktrees/worktree-1/refs/worktree/bad-branch-1: badRefContent: $bad_content
+ warning: worktrees/worktree-1/refs/worktree/branch-no-newline: refMissingNewline: misses LF at the end
EOF
- rm $worktree1_refdir_prefix/bad-branch-1 &&
- test_cmp expect err || return 1
- done &&
+ rm $worktree1_refdir_prefix/branch-no-newline &&
+ test_cmp expect err &&
- for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$worktree2_refdir_prefix/bad-branch-2 &&
- test_must_fail git refs verify 2>err &&
+ printf "%s garbage" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-garbage &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- error: worktrees/worktree-2/refs/worktree/bad-branch-2: badRefContent: $bad_content
+ warning: worktrees/worktree-1/refs/worktree/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
EOF
- rm $worktree2_refdir_prefix/bad-branch-2 &&
- test_cmp expect err || return 1
- done &&
-
- printf "%s" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-no-newline &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- rm $worktree1_refdir_prefix/branch-no-newline &&
- test_cmp expect err &&
-
- printf "%s garbage" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-garbage &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
- EOF
- rm $worktree1_refdir_prefix/branch-garbage &&
- test_cmp expect err
+ rm $worktree1_refdir_prefix/branch-garbage &&
+ test_cmp expect err
+ )
'
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v7 2/9] builtin/refs: get worktrees without reading head information
2025-02-26 13:48 ` [PATCH v7 0/9] add more ref consistency checks shejialuo
2025-02-26 13:49 ` [PATCH v7 1/9] t0602: use subshell to ensure working directory unchanged shejialuo
@ 2025-02-26 13:49 ` shejialuo
2025-02-26 13:49 ` [PATCH v7 3/9] packed-backend: check whether the "packed-refs" is regular file shejialuo
` (7 subsequent siblings)
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-26 13:49 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
In "packed-backend.c", there are some functions such as "create_snapshot"
and "next_record" which would check the correctness of the content of
the "packed-ref" file. When anything is bad, the program will die.
It may seem that we have nothing relevant to above feature, because we
are going to read and parse the raw "packed-ref" file without creating
the snapshot and using the ref iterator to check the consistency.
However, when using "get_worktrees" in "builtin/refs", we would parse
the "HEAD" information. If the referent of the "HEAD" is inside the
"packed-ref", we will call "create_snapshot" function to parse the
"packed-ref" to get the information. No matter whether the entry of
"HEAD" in "packed-ref" is correct, "create_snapshot" would call
"verify_buffer_safe" to check whether there is a newline in the last
line of the file. If not, the program will die.
Although this behavior has no harm for the program, it will
short-circuit the program. When the users execute "git refs verify" or
"git fsck", we should avoid reading the head information, which may
execute the read operation in packed backend with stricter checks to die
the program. Instead, we should continue to check other parts of the
"packed-refs" file completely.
Fortunately, in 465a22b338 (worktree: skip reading HEAD when repairing
worktrees, 2023-12-29), we have introduced a function
"get_worktrees_internal" which allows us to get worktrees without
reading head information.
Create a new exposed function "get_worktrees_without_reading_head", then
replace the "get_worktrees" in "builtin/refs" with the new created
function.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
builtin/refs.c | 2 +-
worktree.c | 5 +++++
worktree.h | 8 ++++++++
3 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/builtin/refs.c b/builtin/refs.c
index a29f195834..55ff5dae11 100644
--- a/builtin/refs.c
+++ b/builtin/refs.c
@@ -88,7 +88,7 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix,
git_config(git_fsck_config, &fsck_refs_options);
prepare_repo_settings(the_repository);
- worktrees = get_worktrees();
+ worktrees = get_worktrees_without_reading_head();
for (size_t i = 0; worktrees[i]; i++)
ret |= refs_fsck(get_worktree_ref_store(worktrees[i]),
&fsck_refs_options, worktrees[i]);
diff --git a/worktree.c b/worktree.c
index d4a68c9c23..d23482a746 100644
--- a/worktree.c
+++ b/worktree.c
@@ -198,6 +198,11 @@ struct worktree **get_worktrees(void)
return get_worktrees_internal(0);
}
+struct worktree **get_worktrees_without_reading_head(void)
+{
+ return get_worktrees_internal(1);
+}
+
const char *get_worktree_git_dir(const struct worktree *wt)
{
if (!wt)
diff --git a/worktree.h b/worktree.h
index 38145df80f..a305c7e2c7 100644
--- a/worktree.h
+++ b/worktree.h
@@ -30,6 +30,14 @@ struct worktree {
*/
struct worktree **get_worktrees(void);
+/*
+ * Like `get_worktrees`, but does not read HEAD. Skip reading HEAD allows to
+ * get the worktree without worrying about failures pertaining to parsing
+ * the HEAD ref. This is useful in contexts where it is assumed that the
+ * refdb may not be in a consistent state.
+ */
+struct worktree **get_worktrees_without_reading_head(void);
+
/*
* Returns 1 if linked worktrees exist, 0 otherwise.
*/
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v7 3/9] packed-backend: check whether the "packed-refs" is regular file
2025-02-26 13:48 ` [PATCH v7 0/9] add more ref consistency checks shejialuo
2025-02-26 13:49 ` [PATCH v7 1/9] t0602: use subshell to ensure working directory unchanged shejialuo
2025-02-26 13:49 ` [PATCH v7 2/9] builtin/refs: get worktrees without reading head information shejialuo
@ 2025-02-26 13:49 ` shejialuo
2025-02-26 18:36 ` Junio C Hamano
2025-02-26 13:50 ` [PATCH v7 4/9] packed-backend: check if header starts with "# pack-refs with: " shejialuo
` (6 subsequent siblings)
9 siblings, 1 reply; 168+ messages in thread
From: shejialuo @ 2025-02-26 13:49 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
Although "git-fsck(1)" and "packed-backend.c" will check some
consistency and correctness of "packed-refs" file, they never check the
filetype of the "packed-refs". Let's verify that the "packed-refs" has
the expected filetype, confirming it is created by "git pack-refs"
command.
We could use "open_nofollow" wrapper to open the raw "packed-refs" file.
If the returned "fd" value is less than 0, we could check whether the
"errno" is "ELOOP" to report an error to the user. And then we use
"fstat" to check whether the "packed-refs" file is a regular file.
Reuse "FSCK_MSG_BAD_REF_FILETYPE" fsck message id to report the error to
the user if "packed-refs" is not a regular file.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
refs/packed-backend.c | 50 +++++++++++++++++++++++++++++++++++++---
t/t0602-reffiles-fsck.sh | 30 ++++++++++++++++++++++++
2 files changed, 77 insertions(+), 3 deletions(-)
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index a7b6f74b6e..f69a0598c7 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -4,6 +4,7 @@
#include "../git-compat-util.h"
#include "../config.h"
#include "../dir.h"
+#include "../fsck.h"
#include "../gettext.h"
#include "../hash.h"
#include "../hex.h"
@@ -1748,15 +1749,58 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
return empty_ref_iterator_begin();
}
-static int packed_fsck(struct ref_store *ref_store UNUSED,
- struct fsck_options *o UNUSED,
+static int packed_fsck(struct ref_store *ref_store,
+ struct fsck_options *o,
struct worktree *wt)
{
+ struct packed_ref_store *refs = packed_downcast(ref_store,
+ REF_STORE_READ, "fsck");
+ struct stat st;
+ int ret = 0;
+ int fd;
if (!is_main_worktree(wt))
return 0;
- return 0;
+ if (o->verbose)
+ fprintf_ln(stderr, "Checking packed-refs file %s", refs->path);
+
+ fd = open_nofollow(refs->path, O_RDONLY);
+ if (fd < 0) {
+ /*
+ * If the packed-refs file doesn't exist, there's nothing
+ * to check.
+ */
+ if (errno == ENOENT)
+ goto cleanup;
+
+ if (errno == ELOOP) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs";
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_FILETYPE,
+ "not a regular file but a symlink");
+ goto cleanup;
+ }
+
+ ret = error_errno(_("unable to open '%s'"), refs->path);
+ goto cleanup;
+ } else if (fstat(fd, &st) < 0) {
+ ret = error_errno(_("unable to stat '%s'"), refs->path);
+ goto cleanup;
+ } else if (!S_ISREG(st.st_mode)) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs";
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_FILETYPE,
+ "not a regular file");
+ goto cleanup;
+ }
+
+cleanup:
+ if (fd >= 0)
+ close(fd);
+ return ret;
}
struct ref_storage_be refs_be_packed = {
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index cf7a202d0d..68b7d4999e 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -617,4 +617,34 @@ test_expect_success 'ref content checks should work with worktrees' '
)
'
+test_expect_success SYMLINKS 'the filetype of packed-refs should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git branch branch-3 &&
+ git pack-refs --all &&
+
+ mv .git/packed-refs .git/packed-refs-back &&
+ ln -sf packed-refs-back .git/packed-refs &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs: badRefFiletype: not a regular file but a symlink
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err &&
+
+ mkdir .git/packed-refs &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs: badRefFiletype: not a regular file
+ EOF
+ rm -r .git/packed-refs &&
+ test_cmp expect err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* Re: [PATCH v7 3/9] packed-backend: check whether the "packed-refs" is regular file
2025-02-26 13:49 ` [PATCH v7 3/9] packed-backend: check whether the "packed-refs" is regular file shejialuo
@ 2025-02-26 18:36 ` Junio C Hamano
2025-02-27 0:57 ` shejialuo
0 siblings, 1 reply; 168+ messages in thread
From: Junio C Hamano @ 2025-02-26 18:36 UTC (permalink / raw)
To: shejialuo; +Cc: git, Patrick Steinhardt, Karthik Nayak, Michael Haggerty
shejialuo <shejialuo@gmail.com> writes:
> +static int packed_fsck(struct ref_store *ref_store,
> + struct fsck_options *o,
> struct worktree *wt)
> {
> + struct packed_ref_store *refs = packed_downcast(ref_store,
> + REF_STORE_READ, "fsck");
> + struct stat st;
> + int ret = 0;
> + int fd;
>
> if (!is_main_worktree(wt))
> return 0;
I do not think it is worth a reroll only to improve this one, but
for future reference, initializing "fd = -1" and jumping to cleanup
here instead of "return 0" would future-proof the code better. This
is especially so, given that in a few patches later, we would add a
strbuf that is initialized before this "we do not do anything
outside the primary worktree" short-cut, and many "goto cleanup"s we
see in this patch below would jump to cleanup to strbuf_release() on
that initialized but unused strbuf. Jumping there with negative fd
to cleanup that already avoids close(fd) for negative fd would be
like jumping there with initialized but unused strbuf. Having a
single exit point ("cleanup:" label) would help future evolution of
the code, by making it easier to add more resource-acquriing code to
this function in the future.
> - return 0;
> + if (o->verbose)
> + fprintf_ln(stderr, "Checking packed-refs file %s", refs->path);
> +
> + fd = open_nofollow(refs->path, O_RDONLY);
> + if (fd < 0) {
> + /*
> + * If the packed-refs file doesn't exist, there's nothing
> + * to check.
> + */
> + if (errno == ENOENT)
> + goto cleanup;
> +
> + if (errno == ELOOP) {
> + struct fsck_ref_report report = { 0 };
> + report.path = "packed-refs";
> + ret = fsck_report_ref(o, &report,
> + FSCK_MSG_BAD_REF_FILETYPE,
> + "not a regular file but a symlink");
> + goto cleanup;
> + }
> +
> + ret = error_errno(_("unable to open '%s'"), refs->path);
> + goto cleanup;
> + } else if (fstat(fd, &st) < 0) {
> + ret = error_errno(_("unable to stat '%s'"), refs->path);
> + goto cleanup;
> + } else if (!S_ISREG(st.st_mode)) {
> + struct fsck_ref_report report = { 0 };
> + report.path = "packed-refs";
> + ret = fsck_report_ref(o, &report,
> + FSCK_MSG_BAD_REF_FILETYPE,
> + "not a regular file");
> + goto cleanup;
> + }
> +
> +cleanup:
> + if (fd >= 0)
> + close(fd);
> + return ret;
> }
^ permalink raw reply [flat|nested] 168+ messages in thread
* Re: [PATCH v7 3/9] packed-backend: check whether the "packed-refs" is regular file
2025-02-26 18:36 ` Junio C Hamano
@ 2025-02-27 0:57 ` shejialuo
2025-02-27 14:10 ` Patrick Steinhardt
2025-02-27 16:57 ` Junio C Hamano
0 siblings, 2 replies; 168+ messages in thread
From: shejialuo @ 2025-02-27 0:57 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Patrick Steinhardt, Karthik Nayak, Michael Haggerty
On Wed, Feb 26, 2025 at 10:36:29AM -0800, Junio C Hamano wrote:
> shejialuo <shejialuo@gmail.com> writes:
>
> > +static int packed_fsck(struct ref_store *ref_store,
> > + struct fsck_options *o,
> > struct worktree *wt)
> > {
> > + struct packed_ref_store *refs = packed_downcast(ref_store,
> > + REF_STORE_READ, "fsck");
> > + struct stat st;
> > + int ret = 0;
> > + int fd;
> >
> > if (!is_main_worktree(wt))
> > return 0;
>
> I do not think it is worth a reroll only to improve this one, but
> for future reference, initializing "fd = -1" and jumping to cleanup
> here instead of "return 0" would future-proof the code better. This
> is especially so, given that in a few patches later, we would add a
> strbuf that is initialized before this "we do not do anything
> outside the primary worktree" short-cut, and many "goto cleanup"s we
> see in this patch below would jump to cleanup to strbuf_release() on
> that initialized but unused strbuf. Jumping there with negative fd
> to cleanup that already avoids close(fd) for negative fd would be
> like jumping there with initialized but unused strbuf. Having a
> single exit point ("cleanup:" label) would help future evolution of
> the code, by making it easier to add more resource-acquriing code to
> this function in the future.
>
You are right. Actually, I just want to avoid assigning the `fd` to -1.
However, I didn't realize that I would initialize the strbuf later.
After waking up, I have suddenly realized this problem.
If other reviewers don't have any comments for this new version, I will
send out a reroll. We have already iterated many times, if we could make
it better, why not?
Thanks,
Jialuo
^ permalink raw reply [flat|nested] 168+ messages in thread
* Re: [PATCH v7 3/9] packed-backend: check whether the "packed-refs" is regular file
2025-02-27 0:57 ` shejialuo
@ 2025-02-27 14:10 ` Patrick Steinhardt
2025-02-27 16:57 ` Junio C Hamano
1 sibling, 0 replies; 168+ messages in thread
From: Patrick Steinhardt @ 2025-02-27 14:10 UTC (permalink / raw)
To: shejialuo; +Cc: Junio C Hamano, git, Karthik Nayak, Michael Haggerty
On Thu, Feb 27, 2025 at 08:57:01AM +0800, shejialuo wrote:
> On Wed, Feb 26, 2025 at 10:36:29AM -0800, Junio C Hamano wrote:
> > shejialuo <shejialuo@gmail.com> writes:
> >
> > > +static int packed_fsck(struct ref_store *ref_store,
> > > + struct fsck_options *o,
> > > struct worktree *wt)
> > > {
> > > + struct packed_ref_store *refs = packed_downcast(ref_store,
> > > + REF_STORE_READ, "fsck");
> > > + struct stat st;
> > > + int ret = 0;
> > > + int fd;
> > >
> > > if (!is_main_worktree(wt))
> > > return 0;
> >
> > I do not think it is worth a reroll only to improve this one, but
> > for future reference, initializing "fd = -1" and jumping to cleanup
> > here instead of "return 0" would future-proof the code better. This
> > is especially so, given that in a few patches later, we would add a
> > strbuf that is initialized before this "we do not do anything
> > outside the primary worktree" short-cut, and many "goto cleanup"s we
> > see in this patch below would jump to cleanup to strbuf_release() on
> > that initialized but unused strbuf. Jumping there with negative fd
> > to cleanup that already avoids close(fd) for negative fd would be
> > like jumping there with initialized but unused strbuf. Having a
> > single exit point ("cleanup:" label) would help future evolution of
> > the code, by making it easier to add more resource-acquriing code to
> > this function in the future.
> >
>
> You are right. Actually, I just want to avoid assigning the `fd` to -1.
> However, I didn't realize that I would initialize the strbuf later.
> After waking up, I have suddenly realized this problem.
>
> If other reviewers don't have any comments for this new version, I will
> send out a reroll. We have already iterated many times, if we could make
> it better, why not?
I don't have anything else to add to this version, thanks!
Patrick
^ permalink raw reply [flat|nested] 168+ messages in thread
* Re: [PATCH v7 3/9] packed-backend: check whether the "packed-refs" is regular file
2025-02-27 0:57 ` shejialuo
2025-02-27 14:10 ` Patrick Steinhardt
@ 2025-02-27 16:57 ` Junio C Hamano
2025-02-28 5:02 ` shejialuo
1 sibling, 1 reply; 168+ messages in thread
From: Junio C Hamano @ 2025-02-27 16:57 UTC (permalink / raw)
To: shejialuo; +Cc: git, Patrick Steinhardt, Karthik Nayak, Michael Haggerty
shejialuo <shejialuo@gmail.com> writes:
> You are right. Actually, I just want to avoid assigning the `fd` to -1.
Why not?
Between leaving it uninitialized and explicitly initializing it to
signal that it is invalid, the only difference is that you can
programmatically check if fd is invalid and refrain from calling
close(fd), for example, with the latter, while with the former you
cannot.
> However, I didn't realize that I would initialize the strbuf later.
> After waking up, I have suddenly realized this problem.
Given that initialized-but-never-used strbuf does not hold any
acquired resources, the current code at the end of the series is
still OK. So there is technically nothing to fix. I'll take a
reroll if you later send one, but as I said, I do not think it is
necessary to reroll only to add fd=-1 initialization.
^ permalink raw reply [flat|nested] 168+ messages in thread
* Re: [PATCH v7 3/9] packed-backend: check whether the "packed-refs" is regular file
2025-02-27 16:57 ` Junio C Hamano
@ 2025-02-28 5:02 ` shejialuo
0 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-28 5:02 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Patrick Steinhardt, Karthik Nayak, Michael Haggerty
On Thu, Feb 27, 2025 at 08:57:11AM -0800, Junio C Hamano wrote:
> shejialuo <shejialuo@gmail.com> writes:
>
> > You are right. Actually, I just want to avoid assigning the `fd` to -1.
>
> Why not?
>
> Between leaving it uninitialized and explicitly initializing it to
> signal that it is invalid, the only difference is that you can
> programmatically check if fd is invalid and refrain from calling
> close(fd), for example, with the latter, while with the former you
> cannot.
>
Yes, that's correct.
> > However, I didn't realize that I would initialize the strbuf later.
> > After waking up, I have suddenly realized this problem.
>
> Given that initialized-but-never-used strbuf does not hold any
> acquired resources, the current code at the end of the series is
> still OK. So there is technically nothing to fix. I'll take a
> reroll if you later send one, but as I said, I do not think it is
> necessary to reroll only to add fd=-1 initialization.
Yes, as you have said, there is nothing wrong at now. And as Patrick has
nothing comment. I have sent out a reroll to make code better.
Thanks,
Jialuo
^ permalink raw reply [flat|nested] 168+ messages in thread
* [PATCH v7 4/9] packed-backend: check if header starts with "# pack-refs with: "
2025-02-26 13:48 ` [PATCH v7 0/9] add more ref consistency checks shejialuo
` (2 preceding siblings ...)
2025-02-26 13:49 ` [PATCH v7 3/9] packed-backend: check whether the "packed-refs" is regular file shejialuo
@ 2025-02-26 13:50 ` shejialuo
2025-02-26 13:50 ` [PATCH v7 5/9] packed-backend: add "packed-refs" header consistency check shejialuo
` (5 subsequent siblings)
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-26 13:50 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
We always write a space after "# pack-refs with:" but we don't align
with this rule in the "create_snapshot" method where we would check
whether header starts with "# pack-refs with:". It might seem that we
should undoubtedly tighten this rule, however, we don't have any
technical documentation about this and there is a possibility that we
would break the compatibility for other third-party libraries.
By investigating influential third-party libraries, we could conclude
how these libraries handle the header of "packed-refs" file:
1. libgit2 is fine and always writes the space. It also expects the
whitespace to exist.
2. JGit does not expect th header to have a trailing space, but expects
the "peeled" capability to have a leading space, which is mostly
equivalent because that capability is typically the first one we
write. It always writes the space.
3. gitoxide expects the space t exist and writes it.
4. go-git doesn't create the header by default.
As many third-party libraries expect a single space after "# pack-refs
with:", if we forget to write the space after the colon,
"create_snapshot" won't catch this. And we would break other
re-implementations. So, we'd better tighten the rule by checking whether
the header starts with "# pack-refs with: ".
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
refs/packed-backend.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index f69a0598c7..3dd3fec459 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -694,7 +694,7 @@ static struct snapshot *create_snapshot(struct packed_ref_store *refs)
tmp = xmemdupz(snapshot->buf, eol - snapshot->buf);
- if (!skip_prefix(tmp, "# pack-refs with:", (const char **)&p))
+ if (!skip_prefix(tmp, "# pack-refs with: ", (const char **)&p))
die_invalid_line(refs->path,
snapshot->buf,
snapshot->eof - snapshot->buf);
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v7 5/9] packed-backend: add "packed-refs" header consistency check
2025-02-26 13:48 ` [PATCH v7 0/9] add more ref consistency checks shejialuo
` (3 preceding siblings ...)
2025-02-26 13:50 ` [PATCH v7 4/9] packed-backend: check if header starts with "# pack-refs with: " shejialuo
@ 2025-02-26 13:50 ` shejialuo
2025-02-26 13:50 ` [PATCH v7 6/9] packed-backend: check whether the refname contains NUL characters shejialuo
` (4 subsequent siblings)
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-26 13:50 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
In "packed-backend.c::create_snapshot", if there is a header (the line
which starts with '#'), we will check whether the line starts with "#
pack-refs with: ". However, we need to consider other situations and
discuss whether we need to add checks.
1. If the header does not exist, we should not report an error to the
user. This is because in older Git version, we never write header in
the "packed-refs" file. Also, we do allow no header in "packed-refs"
in runtime.
2. If the header content does not start with "# packed-ref with: ", we
should report an error just like what "create_snapshot" does. So,
create a new fsck message "badPackedRefHeader(ERROR)" for this.
3. If the header content is not the same as the constant string
"PACKED_REFS_HEADER". This is expected because we make it extensible
intentionally and runtime "create_snapshot" won't complain about
unknown traits. In order to align with the runtime behavior. There is
no need to report.
As we have analyzed, we only need to check the case 2 in the above. In
order to do this, use "open_nofollow" function to get the file
descriptor and then read the "packed-refs" file via "strbuf_read". Like
what "create_snapshot" and other functions do, we could split the line
by finding the next newline in the buffer. When we cannot find a
newline, we could report an error.
So, create a function "packed_fsck_ref_next_line" to find the next
newline and if there is no such newline, use
"packedRefEntryNotTerminated(ERROR)" to report an error to the user.
Then, parse the first line to apply the checks. Update the test to
exercise the code.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/fsck-msgids.adoc | 8 ++++
fsck.h | 2 +
refs/packed-backend.c | 73 ++++++++++++++++++++++++++++++++++
t/t0602-reffiles-fsck.sh | 52 ++++++++++++++++++++++++
4 files changed, 135 insertions(+)
diff --git a/Documentation/fsck-msgids.adoc b/Documentation/fsck-msgids.adoc
index b14bc44ca4..11906f90fd 100644
--- a/Documentation/fsck-msgids.adoc
+++ b/Documentation/fsck-msgids.adoc
@@ -16,6 +16,10 @@
`badObjectSha1`::
(ERROR) An object has a bad sha1.
+`badPackedRefHeader`::
+ (ERROR) The "packed-refs" file contains an invalid
+ header.
+
`badParentSha1`::
(ERROR) A commit object has a bad parent sha1.
@@ -176,6 +180,10 @@
`nullSha1`::
(WARN) Tree contains entries pointing to a null sha1.
+`packedRefEntryNotTerminated`::
+ (ERROR) The "packed-refs" file contains an entry that is
+ not terminated by a newline.
+
`refMissingNewline`::
(INFO) A loose ref that does not end with newline(LF). As
valid implementations of Git never created such a loose ref
diff --git a/fsck.h b/fsck.h
index a44c231a5f..67e3c97bc0 100644
--- a/fsck.h
+++ b/fsck.h
@@ -30,6 +30,7 @@ enum fsck_msg_type {
FUNC(BAD_EMAIL, ERROR) \
FUNC(BAD_NAME, ERROR) \
FUNC(BAD_OBJECT_SHA1, ERROR) \
+ FUNC(BAD_PACKED_REF_HEADER, ERROR) \
FUNC(BAD_PARENT_SHA1, ERROR) \
FUNC(BAD_REF_CONTENT, ERROR) \
FUNC(BAD_REF_FILETYPE, ERROR) \
@@ -53,6 +54,7 @@ enum fsck_msg_type {
FUNC(MISSING_TYPE, ERROR) \
FUNC(MISSING_TYPE_ENTRY, ERROR) \
FUNC(MULTIPLE_AUTHORS, ERROR) \
+ FUNC(PACKED_REF_ENTRY_NOT_TERMINATED, ERROR) \
FUNC(TREE_NOT_SORTED, ERROR) \
FUNC(UNKNOWN_TYPE, ERROR) \
FUNC(ZERO_PADDED_DATE, ERROR) \
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 3dd3fec459..b00fca6501 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1749,12 +1749,76 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
return empty_ref_iterator_begin();
}
+static int packed_fsck_ref_next_line(struct fsck_options *o,
+ unsigned long line_number, const char *start,
+ const char *eof, const char **eol)
+{
+ int ret = 0;
+
+ *eol = memchr(start, '\n', eof - start);
+ if (!*eol) {
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_PACKED_REF_ENTRY_NOT_TERMINATED,
+ "'%.*s' is not terminated with a newline",
+ (int)(eof - start), start);
+
+ /*
+ * There is no newline but we still want to parse it to the end of
+ * the buffer.
+ */
+ *eol = eof;
+ strbuf_release(&packed_entry);
+ }
+
+ return ret;
+}
+
+static int packed_fsck_ref_header(struct fsck_options *o,
+ const char *start, const char *eol)
+{
+ if (!starts_with(start, "# pack-refs with: ")) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs.header";
+
+ return fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_HEADER,
+ "'%.*s' does not start with '# pack-refs with: '",
+ (int)(eol - start), start);
+ }
+
+ return 0;
+}
+
+static int packed_fsck_ref_content(struct fsck_options *o,
+ const char *start, const char *eof)
+{
+ unsigned long line_number = 1;
+ const char *eol;
+ int ret = 0;
+
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ if (*start == '#') {
+ ret |= packed_fsck_ref_header(o, start, eol);
+
+ start = eol + 1;
+ line_number++;
+ }
+
+ return ret;
+}
+
static int packed_fsck(struct ref_store *ref_store,
struct fsck_options *o,
struct worktree *wt)
{
struct packed_ref_store *refs = packed_downcast(ref_store,
REF_STORE_READ, "fsck");
+ struct strbuf packed_ref_content = STRBUF_INIT;
struct stat st;
int ret = 0;
int fd;
@@ -1797,9 +1861,18 @@ static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
+ if (strbuf_read(&packed_ref_content, fd, 0) < 0) {
+ ret = error_errno(_("unable to read '%s'"), refs->path);
+ goto cleanup;
+ }
+
+ ret = packed_fsck_ref_content(o, packed_ref_content.buf,
+ packed_ref_content.buf + packed_ref_content.len);
+
cleanup:
if (fd >= 0)
close(fd);
+ strbuf_release(&packed_ref_content);
return ret;
}
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index 68b7d4999e..74d876984d 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -647,4 +647,56 @@ test_expect_success SYMLINKS 'the filetype of packed-refs should be checked' '
)
'
+test_expect_success 'packed-refs header should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+
+ git refs verify 2>err &&
+ test_must_be_empty err &&
+
+ for bad_header in "# pack-refs wit: peeled fully-peeled sorted " \
+ "# pack-refs with traits: peeled fully-peeled sorted " \
+ "# pack-refs with a: peeled fully-peeled" \
+ "# pack-refs with:peeled fully-peeled sorted"
+ do
+ printf "%s\n" "$bad_header" >.git/packed-refs &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs.header: badPackedRefHeader: '\''$bad_header'\'' does not start with '\''# pack-refs with: '\''
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err || return 1
+ done
+ )
+'
+
+test_expect_success 'packed-refs missing header should not be reported' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+
+ printf "$(git rev-parse HEAD) refs/heads/main\n" >.git/packed-refs &&
+ git refs verify 2>err &&
+ test_must_be_empty err
+ )
+'
+
+test_expect_success 'packed-refs unknown traits should not be reported' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+
+ printf "# pack-refs with: peeled fully-peeled sorted foo\n" >.git/packed-refs &&
+ git refs verify 2>err &&
+ test_must_be_empty err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v7 6/9] packed-backend: check whether the refname contains NUL characters
2025-02-26 13:48 ` [PATCH v7 0/9] add more ref consistency checks shejialuo
` (4 preceding siblings ...)
2025-02-26 13:50 ` [PATCH v7 5/9] packed-backend: add "packed-refs" header consistency check shejialuo
@ 2025-02-26 13:50 ` shejialuo
2025-02-26 13:50 ` [PATCH v7 7/9] packed-backend: add "packed-refs" entry consistency check shejialuo
` (3 subsequent siblings)
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-26 13:50 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
"packed-backend.c::next_record" will use "check_refname_format" to check
the consistency of the refname. If it is not OK, the program will die.
However, it is reported in [1], we cannot catch some corruption. But we
already have the code path and we must miss out something.
We use the following code to get the refname:
strbuf_add(&iter->refname_buf, p, eol - p);
iter->base.refname = iter->refname_buf.buf
In the above code, `p` is the start pointer of the refname and `eol` is
the next newline pointer. We calculate the length of the refname by
subtracting the two pointers. Then we add the memory range between `p`
and `eol` to get the refname.
However, if there are some NUL characters in the memory range between `p`
and `eol`, we will see the refname as a valid ref name as long as the
memory range between `p` and first occurred NUL character is valid.
In order to catch above corruption, create a new function
"refname_contains_nul" by searching the first NUL character. If it is
not at the end of the string, there must be some NUL characters in the
refname.
Use this function in "next_record" function to die the program if
"refname_contains_nul" returns true.
[1] https://lore.kernel.org/git/6cfee0e4-3285-4f18-91ff-d097da9de737@rd10.de/
Reported-by: R. Diez <rdiez-temp3@rd10.de>
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
refs/packed-backend.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index b00fca6501..6e7d08c565 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -494,6 +494,21 @@ static void verify_buffer_safe(struct snapshot *snapshot)
last_line, eof - last_line);
}
+/*
+ * When parsing the "packed-refs" file, we will parse it line by line.
+ * Because we know the start pointer of the refname and the next
+ * newline pointer, we could calculate the length of the refname by
+ * subtracting the two pointers. However, there is a corner case where
+ * the refname contains corrupted embedded NUL characters. And
+ * `check_refname_format()` will not catch this when the truncated
+ * refname is still a valid refname. To prevent this, we need to check
+ * whether the refname contains the NUL characters.
+ */
+static int refname_contains_nul(struct strbuf *refname)
+{
+ return !!memchr(refname->buf, '\0', refname->len);
+}
+
#define SMALL_FILE_SIZE (32*1024)
/*
@@ -895,6 +910,9 @@ static int next_record(struct packed_ref_iterator *iter)
strbuf_add(&iter->refname_buf, p, eol - p);
iter->base.refname = iter->refname_buf.buf;
+ if (refname_contains_nul(&iter->refname_buf))
+ die("packed refname contains embedded NULL: %s", iter->base.refname);
+
if (check_refname_format(iter->base.refname, REFNAME_ALLOW_ONELEVEL)) {
if (!refname_is_safe(iter->base.refname))
die("packed refname is dangerous: %s",
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v7 7/9] packed-backend: add "packed-refs" entry consistency check
2025-02-26 13:48 ` [PATCH v7 0/9] add more ref consistency checks shejialuo
` (5 preceding siblings ...)
2025-02-26 13:50 ` [PATCH v7 6/9] packed-backend: check whether the refname contains NUL characters shejialuo
@ 2025-02-26 13:50 ` shejialuo
2025-02-26 13:50 ` [PATCH v7 8/9] packed-backend: check whether the "packed-refs" is sorted shejialuo
` (2 subsequent siblings)
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-26 13:50 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
"packed-backend.c::next_record" will parse the ref entry to check the
consistency. This function has already checked the following things:
1. Parse the main line of the ref entry to inspect whether the oid is
not correct. Then, check whether the next character is oid. Then
check the refname.
2. If the next line starts with '^', it would continue to parse the
peeled oid and check whether the last character is '\n'.
As we decide to implement the ref consistency check for "packed-refs",
let's port these two checks and update the test to exercise the code.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/fsck-msgids.adoc | 3 +
fsck.h | 1 +
refs/packed-backend.c | 122 ++++++++++++++++++++++++++++++++-
t/t0602-reffiles-fsck.sh | 44 ++++++++++++
4 files changed, 169 insertions(+), 1 deletion(-)
diff --git a/Documentation/fsck-msgids.adoc b/Documentation/fsck-msgids.adoc
index 11906f90fd..02a7bf0503 100644
--- a/Documentation/fsck-msgids.adoc
+++ b/Documentation/fsck-msgids.adoc
@@ -16,6 +16,9 @@
`badObjectSha1`::
(ERROR) An object has a bad sha1.
+`badPackedRefEntry`::
+ (ERROR) The "packed-refs" file contains an invalid entry.
+
`badPackedRefHeader`::
(ERROR) The "packed-refs" file contains an invalid
header.
diff --git a/fsck.h b/fsck.h
index 67e3c97bc0..14d70f6653 100644
--- a/fsck.h
+++ b/fsck.h
@@ -30,6 +30,7 @@ enum fsck_msg_type {
FUNC(BAD_EMAIL, ERROR) \
FUNC(BAD_NAME, ERROR) \
FUNC(BAD_OBJECT_SHA1, ERROR) \
+ FUNC(BAD_PACKED_REF_ENTRY, ERROR) \
FUNC(BAD_PACKED_REF_HEADER, ERROR) \
FUNC(BAD_PARENT_SHA1, ERROR) \
FUNC(BAD_REF_CONTENT, ERROR) \
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 6e7d08c565..8c410fca77 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1812,9 +1812,114 @@ static int packed_fsck_ref_header(struct fsck_options *o,
return 0;
}
+static int packed_fsck_ref_peeled_line(struct fsck_options *o,
+ struct ref_store *ref_store,
+ unsigned long line_number,
+ const char *start, const char *eol)
+{
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct object_id peeled;
+ const char *p;
+ int ret = 0;
+
+ /*
+ * Skip the '^' and parse the peeled oid.
+ */
+ start++;
+ if (parse_oid_hex_algop(start, &peeled, &p, ref_store->repo->hash_algo)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "'%.*s' has invalid peeled oid",
+ (int)(eol - start), start);
+ goto cleanup;
+ }
+
+ if (p != eol) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "has trailing garbage after peeled oid '%.*s'",
+ (int)(eol - p), p);
+ goto cleanup;
+ }
+
+cleanup:
+ strbuf_release(&packed_entry);
+ return ret;
+}
+
+static int packed_fsck_ref_main_line(struct fsck_options *o,
+ struct ref_store *ref_store,
+ unsigned long line_number,
+ struct strbuf *refname,
+ const char *start, const char *eol)
+{
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct object_id oid;
+ const char *p;
+ int ret = 0;
+
+ if (parse_oid_hex_algop(start, &oid, &p, ref_store->repo->hash_algo)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "'%.*s' has invalid oid",
+ (int)(eol - start), start);
+ goto cleanup;
+ }
+
+ if (p == eol || !isspace(*p)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "has no space after oid '%s' but with '%.*s'",
+ oid_to_hex(&oid), (int)(eol - p), p);
+ goto cleanup;
+ }
+
+ p++;
+ strbuf_reset(refname);
+ strbuf_add(refname, p, eol - p);
+ if (refname_contains_nul(refname)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "refname '%s' contains NULL binaries",
+ refname->buf);
+ }
+
+ if (check_refname_format(refname->buf, 0)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_NAME,
+ "has bad refname '%s'", refname->buf);
+ }
+
+cleanup:
+ strbuf_release(&packed_entry);
+ return ret;
+}
+
static int packed_fsck_ref_content(struct fsck_options *o,
+ struct ref_store *ref_store,
const char *start, const char *eof)
{
+ struct strbuf refname = STRBUF_INIT;
unsigned long line_number = 1;
const char *eol;
int ret = 0;
@@ -1827,6 +1932,21 @@ static int packed_fsck_ref_content(struct fsck_options *o,
line_number++;
}
+ while (start < eof) {
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ ret |= packed_fsck_ref_main_line(o, ref_store, line_number, &refname, start, eol);
+ start = eol + 1;
+ line_number++;
+ if (start < eof && *start == '^') {
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ ret |= packed_fsck_ref_peeled_line(o, ref_store, line_number,
+ start, eol);
+ start = eol + 1;
+ line_number++;
+ }
+ }
+
+ strbuf_release(&refname);
return ret;
}
@@ -1884,7 +2004,7 @@ static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
- ret = packed_fsck_ref_content(o, packed_ref_content.buf,
+ ret = packed_fsck_ref_content(o, ref_store, packed_ref_content.buf,
packed_ref_content.buf + packed_ref_content.len);
cleanup:
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index 74d876984d..a88c792ce1 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -699,4 +699,48 @@ test_expect_success 'packed-refs unknown traits should not be reported' '
)
'
+test_expect_success 'packed-refs content should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git tag -a annotated-tag-1 -m tag-1 &&
+ git tag -a annotated-tag-2 -m tag-2 &&
+
+ branch_1_oid=$(git rev-parse branch-1) &&
+ branch_2_oid=$(git rev-parse branch-2) &&
+ tag_1_oid=$(git rev-parse annotated-tag-1) &&
+ tag_2_oid=$(git rev-parse annotated-tag-2) &&
+ tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
+ tag_2_peeled_oid=$(git rev-parse annotated-tag-2^{}) &&
+ short_oid=$(printf "%s" $tag_1_peeled_oid | cut -c 1-4) &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ $short_oid refs/heads/branch-1
+ ${branch_1_oid}x
+ $branch_2_oid refs/heads/bad-branch
+ $branch_2_oid refs/heads/branch.
+ $tag_1_oid refs/tags/annotated-tag-3
+ ^$short_oid
+ $tag_2_oid refs/tags/annotated-tag-4.
+ ^$tag_2_peeled_oid garbage
+ EOF
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs line 2: badPackedRefEntry: '\''$short_oid refs/heads/branch-1'\'' has invalid oid
+ error: packed-refs line 3: badPackedRefEntry: has no space after oid '\''$branch_1_oid'\'' but with '\''x'\''
+ error: packed-refs line 4: badRefName: has bad refname '\'' refs/heads/bad-branch'\''
+ error: packed-refs line 5: badRefName: has bad refname '\''refs/heads/branch.'\''
+ error: packed-refs line 7: badPackedRefEntry: '\''$short_oid'\'' has invalid peeled oid
+ error: packed-refs line 8: badRefName: has bad refname '\''refs/tags/annotated-tag-4.'\''
+ error: packed-refs line 9: badPackedRefEntry: has trailing garbage after peeled oid '\'' garbage'\''
+ EOF
+ test_cmp expect err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v7 8/9] packed-backend: check whether the "packed-refs" is sorted
2025-02-26 13:48 ` [PATCH v7 0/9] add more ref consistency checks shejialuo
` (6 preceding siblings ...)
2025-02-26 13:50 ` [PATCH v7 7/9] packed-backend: add "packed-refs" entry consistency check shejialuo
@ 2025-02-26 13:50 ` shejialuo
2025-02-26 13:51 ` [PATCH v7 9/9] builtin/fsck: add `git refs verify` child process shejialuo
2025-02-27 16:03 ` [PATCH v8 0/9] add more ref consistency checks shejialuo
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-26 13:50 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
When there is a "sorted" trait in the header of the "packed-refs" file,
it means that each entry is sorted increasingly by comparing the
refname. We should add checks to verify whether the "packed-refs" is
sorted in this case.
Update the "packed_fsck_ref_header" to know whether there is a "sorted"
trail in the header. It may seem that we could record all refnames
during the parsing process and then compare later. However, this is not
a good design due to the following reasons:
1. Because we need to store the state across the whole checking
lifetime, we would consume a lot of memory if there are many entries
in the "packed-refs" file.
2. We cannot reuse the existing compare function "cmp_packed_ref_records"
which cause repetition.
Because "cmp_packed_ref_records" needs an extra parameter "struct
snaphost", extract the common part into a new function
"cmp_packed_ref_records" to reuse this function to compare.
Then, create a new function "packed_fsck_ref_sorted" to parse the file
again and user the new fsck message "packedRefUnsorted(ERROR)" to report
to the user if the file is not sorted.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/fsck-msgids.adoc | 3 +
fsck.h | 1 +
refs/packed-backend.c | 116 ++++++++++++++++++++++++++++-----
t/t0602-reffiles-fsck.sh | 87 +++++++++++++++++++++++++
4 files changed, 191 insertions(+), 16 deletions(-)
diff --git a/Documentation/fsck-msgids.adoc b/Documentation/fsck-msgids.adoc
index 02a7bf0503..9601fff228 100644
--- a/Documentation/fsck-msgids.adoc
+++ b/Documentation/fsck-msgids.adoc
@@ -187,6 +187,9 @@
(ERROR) The "packed-refs" file contains an entry that is
not terminated by a newline.
+`packedRefUnsorted`::
+ (ERROR) The "packed-refs" file is not sorted.
+
`refMissingNewline`::
(INFO) A loose ref that does not end with newline(LF). As
valid implementations of Git never created such a loose ref
diff --git a/fsck.h b/fsck.h
index 14d70f6653..19f3cb2773 100644
--- a/fsck.h
+++ b/fsck.h
@@ -56,6 +56,7 @@ enum fsck_msg_type {
FUNC(MISSING_TYPE_ENTRY, ERROR) \
FUNC(MULTIPLE_AUTHORS, ERROR) \
FUNC(PACKED_REF_ENTRY_NOT_TERMINATED, ERROR) \
+ FUNC(PACKED_REF_UNSORTED, ERROR) \
FUNC(TREE_NOT_SORTED, ERROR) \
FUNC(UNKNOWN_TYPE, ERROR) \
FUNC(ZERO_PADDED_DATE, ERROR) \
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 8c410fca77..a1710d7c2a 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -300,14 +300,9 @@ struct snapshot_record {
size_t len;
};
-static int cmp_packed_ref_records(const void *v1, const void *v2,
- void *cb_data)
-{
- const struct snapshot *snapshot = cb_data;
- const struct snapshot_record *e1 = v1, *e2 = v2;
- const char *r1 = e1->start + snapshot_hexsz(snapshot) + 1;
- const char *r2 = e2->start + snapshot_hexsz(snapshot) + 1;
+static int cmp_packed_refname(const char *r1, const char *r2)
+{
while (1) {
if (*r1 == '\n')
return *r2 == '\n' ? 0 : -1;
@@ -322,6 +317,17 @@ static int cmp_packed_ref_records(const void *v1, const void *v2,
}
}
+static int cmp_packed_ref_records(const void *v1, const void *v2,
+ void *cb_data)
+{
+ const struct snapshot *snapshot = cb_data;
+ const struct snapshot_record *e1 = v1, *e2 = v2;
+ const char *r1 = e1->start + snapshot_hexsz(snapshot) + 1;
+ const char *r2 = e2->start + snapshot_hexsz(snapshot) + 1;
+
+ return cmp_packed_refname(r1, r2);
+}
+
/*
* Compare a snapshot record at `rec` to the specified NUL-terminated
* refname.
@@ -1797,19 +1803,33 @@ static int packed_fsck_ref_next_line(struct fsck_options *o,
}
static int packed_fsck_ref_header(struct fsck_options *o,
- const char *start, const char *eol)
+ const char *start, const char *eol,
+ unsigned int *sorted)
{
- if (!starts_with(start, "# pack-refs with: ")) {
+ struct string_list traits = STRING_LIST_INIT_NODUP;
+ char *tmp_line;
+ int ret = 0;
+ char *p;
+
+ tmp_line = xmemdupz(start, eol - start);
+ if (!skip_prefix(tmp_line, "# pack-refs with: ", (const char **)&p)) {
struct fsck_ref_report report = { 0 };
report.path = "packed-refs.header";
- return fsck_report_ref(o, &report,
- FSCK_MSG_BAD_PACKED_REF_HEADER,
- "'%.*s' does not start with '# pack-refs with: '",
- (int)(eol - start), start);
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_HEADER,
+ "'%.*s' does not start with '# pack-refs with: '",
+ (int)(eol - start), start);
+ goto cleanup;
}
- return 0;
+ string_list_split_in_place(&traits, p, " ", -1);
+ *sorted = unsorted_string_list_has_string(&traits, "sorted");
+
+cleanup:
+ free(tmp_line);
+ string_list_clear(&traits, 0);
+ return ret;
}
static int packed_fsck_ref_peeled_line(struct fsck_options *o,
@@ -1915,8 +1935,68 @@ static int packed_fsck_ref_main_line(struct fsck_options *o,
return ret;
}
+static int packed_fsck_ref_sorted(struct fsck_options *o,
+ struct ref_store *ref_store,
+ const char *start, const char *eof)
+{
+ size_t hexsz = ref_store->repo->hash_algo->hexsz;
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct strbuf refname1 = STRBUF_INIT;
+ struct strbuf refname2 = STRBUF_INIT;
+ unsigned long line_number = 1;
+ const char *former = NULL;
+ const char *current;
+ const char *eol;
+ int ret = 0;
+
+ if (*start == '#') {
+ eol = memchr(start, '\n', eof - start);
+ start = eol + 1;
+ line_number++;
+ }
+
+ for (; start < eof; line_number++, start = eol + 1) {
+ eol = memchr(start, '\n', eof - start);
+
+ if (*start == '^')
+ continue;
+
+ if (!former) {
+ former = start + hexsz + 1;
+ continue;
+ }
+
+ current = start + hexsz + 1;
+ if (cmp_packed_refname(former, current) >= 0) {
+ const char *err_fmt =
+ "refname '%s' is less than previous refname '%s'";
+
+ eol = memchr(former, '\n', eof - former);
+ strbuf_add(&refname1, former, eol - former);
+ eol = memchr(current, '\n', eof - current);
+ strbuf_add(&refname2, current, eol - current);
+
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_PACKED_REF_UNSORTED,
+ err_fmt, refname2.buf, refname1.buf);
+ goto cleanup;
+ }
+ former = current;
+ }
+
+cleanup:
+ strbuf_release(&packed_entry);
+ strbuf_release(&refname1);
+ strbuf_release(&refname2);
+ return ret;
+}
+
static int packed_fsck_ref_content(struct fsck_options *o,
struct ref_store *ref_store,
+ unsigned int *sorted,
const char *start, const char *eof)
{
struct strbuf refname = STRBUF_INIT;
@@ -1926,7 +2006,7 @@ static int packed_fsck_ref_content(struct fsck_options *o,
ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
if (*start == '#') {
- ret |= packed_fsck_ref_header(o, start, eol);
+ ret |= packed_fsck_ref_header(o, start, eol, sorted);
start = eol + 1;
line_number++;
@@ -1957,6 +2037,7 @@ static int packed_fsck(struct ref_store *ref_store,
struct packed_ref_store *refs = packed_downcast(ref_store,
REF_STORE_READ, "fsck");
struct strbuf packed_ref_content = STRBUF_INIT;
+ unsigned int sorted = 0;
struct stat st;
int ret = 0;
int fd;
@@ -2004,8 +2085,11 @@ static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
- ret = packed_fsck_ref_content(o, ref_store, packed_ref_content.buf,
+ ret = packed_fsck_ref_content(o, ref_store, &sorted, packed_ref_content.buf,
packed_ref_content.buf + packed_ref_content.len);
+ if (!ret && sorted)
+ ret = packed_fsck_ref_sorted(o, ref_store, packed_ref_content.buf,
+ packed_ref_content.buf + packed_ref_content.len);
cleanup:
if (fd >= 0)
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index a88c792ce1..767e2bd4a0 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -743,4 +743,91 @@ test_expect_success 'packed-refs content should be checked' '
)
'
+test_expect_success 'packed-ref with sorted trait should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git tag -a annotated-tag-1 -m tag-1 &&
+ branch_1_oid=$(git rev-parse branch-1) &&
+ branch_2_oid=$(git rev-parse branch-2) &&
+ tag_1_oid=$(git rev-parse annotated-tag-1) &&
+ tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
+ refname1="refs/heads/main" &&
+ refname2="refs/heads/foo" &&
+ refname3="refs/tags/foo" &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ EOF
+ git refs verify 2>err &&
+ rm .git/packed-refs &&
+ test_must_be_empty err &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ $branch_2_oid $refname1
+ EOF
+ git refs verify 2>err &&
+ rm .git/packed-refs &&
+ test_must_be_empty err &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ $branch_2_oid $refname1
+ $branch_1_oid $refname2
+ $tag_1_oid $refname3
+ EOF
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs line 3: packedRefUnsorted: refname '\''$refname2'\'' is less than previous refname '\''$refname1'\''
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ $tag_1_oid $refname3
+ ^$tag_1_peeled_oid
+ $branch_2_oid $refname2
+ EOF
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs line 4: packedRefUnsorted: refname '\''$refname2'\'' is less than previous refname '\''$refname3'\''
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err
+ )
+'
+
+test_expect_success 'packed-ref without sorted trait should not be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git tag -a annotated-tag-1 -m tag-1 &&
+ branch_1_oid=$(git rev-parse branch-1) &&
+ branch_2_oid=$(git rev-parse branch-2) &&
+ tag_1_oid=$(git rev-parse annotated-tag-1) &&
+ tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
+ refname1="refs/heads/main" &&
+ refname2="refs/heads/foo" &&
+ refname3="refs/tags/foo" &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled
+ $branch_2_oid $refname1
+ $branch_1_oid $refname2
+ EOF
+ git refs verify 2>err &&
+ test_must_be_empty err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v7 9/9] builtin/fsck: add `git refs verify` child process
2025-02-26 13:48 ` [PATCH v7 0/9] add more ref consistency checks shejialuo
` (7 preceding siblings ...)
2025-02-26 13:50 ` [PATCH v7 8/9] packed-backend: check whether the "packed-refs" is sorted shejialuo
@ 2025-02-26 13:51 ` shejialuo
2025-02-27 16:03 ` [PATCH v8 0/9] add more ref consistency checks shejialuo
9 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-26 13:51 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
At now, we have already implemented the ref consistency checks for both
"files-backend" and "packed-backend". Although we would check some
redundant things, it won't cause trouble. So, let's integrate it into
the "git-fsck(1)" command to get feedback from the users. And also by
calling "git refs verify" in "git-fsck(1)", we make sure that the new
added checks don't break.
Introduce a new function "fsck_refs" that initializes and runs a child
process to execute the "git refs verify" command. In order to provide
the user interface create a progress which makes the total task be 1.
It's hard to know how many loose refs we will check now. We might
improve this later.
Then, introduce the option to allow the user to disable checking ref
database consistency. Put this function in the very first execution
sequence of "git-fsck(1)" due to that we don't want the existing code of
"git-fsck(1)" which would implicitly check the consistency of refs to
die the program.
Last, update the test to exercise the code.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/git-fsck.adoc | 7 ++++++-
builtin/fsck.c | 33 ++++++++++++++++++++++++++++++-
t/t0602-reffiles-fsck.sh | 39 +++++++++++++++++++++++++++++++++++++
3 files changed, 77 insertions(+), 2 deletions(-)
diff --git a/Documentation/git-fsck.adoc b/Documentation/git-fsck.adoc
index 8f32800a83..11203ba925 100644
--- a/Documentation/git-fsck.adoc
+++ b/Documentation/git-fsck.adoc
@@ -12,7 +12,7 @@ SYNOPSIS
'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]
[--[no-]full] [--strict] [--verbose] [--lost-found]
[--[no-]dangling] [--[no-]progress] [--connectivity-only]
- [--[no-]name-objects] [<object>...]
+ [--[no-]name-objects] [--[no-]references] [<object>...]
DESCRIPTION
-----------
@@ -104,6 +104,11 @@ care about this output and want to speed it up further.
progress status even if the standard error stream is not
directed to a terminal.
+--[no-]references::
+ Control whether to check the references database consistency
+ via 'git refs verify'. See linkgit:git-refs[1] for details.
+ The default is to check the references database.
+
CONFIGURATION
-------------
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 7a4dcb0716..f4f395cfbd 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -50,6 +50,7 @@ static int verbose;
static int show_progress = -1;
static int show_dangling = 1;
static int name_objects;
+static int check_references = 1;
#define ERROR_OBJECT 01
#define ERROR_REACHABLE 02
#define ERROR_PACK 04
@@ -905,11 +906,37 @@ static int check_pack_rev_indexes(struct repository *r, int show_progress)
return res;
}
+static void fsck_refs(struct repository *r)
+{
+ struct child_process refs_verify = CHILD_PROCESS_INIT;
+ struct progress *progress = NULL;
+
+ if (show_progress)
+ progress = start_progress(r, _("Checking ref database"), 1);
+
+ if (verbose)
+ fprintf_ln(stderr, _("Checking ref database"));
+
+ child_process_init(&refs_verify);
+ refs_verify.git_cmd = 1;
+ strvec_pushl(&refs_verify.args, "refs", "verify", NULL);
+ if (verbose)
+ strvec_push(&refs_verify.args, "--verbose");
+ if (check_strict)
+ strvec_push(&refs_verify.args, "--strict");
+
+ if (run_command(&refs_verify))
+ errors_found |= ERROR_REFS;
+
+ display_progress(progress, 1);
+ stop_progress(&progress);
+}
+
static char const * const fsck_usage[] = {
N_("git fsck [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]\n"
" [--[no-]full] [--strict] [--verbose] [--lost-found]\n"
" [--[no-]dangling] [--[no-]progress] [--connectivity-only]\n"
- " [--[no-]name-objects] [<object>...]"),
+ " [--[no-]name-objects] [--[no-]references] [<object>...]"),
NULL
};
@@ -928,6 +955,7 @@ static struct option fsck_opts[] = {
N_("write dangling objects in .git/lost-found")),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
OPT_BOOL(0, "name-objects", &name_objects, N_("show verbose names for reachable objects")),
+ OPT_BOOL(0, "references", &check_references, N_("check reference database consistency")),
OPT_END(),
};
@@ -970,6 +998,9 @@ int cmd_fsck(int argc,
git_config(git_fsck_config, &fsck_obj_options);
prepare_repo_settings(the_repository);
+ if (check_references)
+ fsck_refs(the_repository);
+
if (connectivity_only) {
for_each_loose_object(mark_loose_for_connectivity, NULL, 0);
for_each_packed_object(the_repository,
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index 767e2bd4a0..9d1dc2144c 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -830,4 +830,43 @@ test_expect_success 'packed-ref without sorted trait should not be checked' '
)
'
+test_expect_success '--[no-]references option should apply to fsck' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ branch_dir_prefix=.git/refs/heads &&
+ (
+ cd repo &&
+ test_commit default &&
+ for trailing_content in " garbage" " more garbage"
+ do
+ printf "%s" "$(git rev-parse HEAD)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ git fsck 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage &&
+ test_cmp expect err || return 1
+ done &&
+
+ for trailing_content in " garbage" " more garbage"
+ do
+ printf "%s" "$(git rev-parse HEAD)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ git fsck --references 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage &&
+ test_cmp expect err || return 1
+ done &&
+
+ for trailing_content in " garbage" " more garbage"
+ do
+ printf "%s" "$(git rev-parse HEAD)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ git fsck --no-references 2>err &&
+ rm $branch_dir_prefix/branch-garbage &&
+ test_must_be_empty err || return 1
+ done
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v8 0/9] add more ref consistency checks
2025-02-26 13:48 ` [PATCH v7 0/9] add more ref consistency checks shejialuo
` (8 preceding siblings ...)
2025-02-26 13:51 ` [PATCH v7 9/9] builtin/fsck: add `git refs verify` child process shejialuo
@ 2025-02-27 16:03 ` shejialuo
2025-02-27 16:05 ` [PATCH v8 1/9] t0602: use subshell to ensure working directory unchanged shejialuo
` (8 more replies)
9 siblings, 9 replies; 168+ messages in thread
From: shejialuo @ 2025-02-27 16:03 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
Hi All:
This changes enhances the following things:
1. [PATCH v8 3/9]: initialize fd = -1 for two purposes:
1. We could use unified `goto cleanup` where we have one only
control.
2. We should not close the fd when we cannot open the file.
Hope that this version would be final. Thank for the effort of every
reviewer.
Thanks,
Jialuo
---
This series mainly does the following things:
1. Fix subshell issues
2. Add ref checks for packed-backend.
1. Check whether the filetype of "packed-refs" is correct.
2. Check whether the syntax of "packed-refs" is correct by using the
rules from "packed-backend.c::create_snapshot" and
"packed-backend.c::next_record".
3. Check whether the pointed object exists and whether the
"packed-refs" file is sorted.
3. Call "git refs verify" for "git-fsck(1)".
shejialuo (9):
t0602: use subshell to ensure working directory unchanged
builtin/refs: get worktrees without reading head information
packed-backend: check whether the "packed-refs" is regular file
packed-backend: check if header starts with "# pack-refs with: "
packed-backend: add "packed-refs" header consistency check
packed-backend: check whether the refname contains NUL characters
packed-backend: add "packed-refs" entry consistency check
packed-backend: check whether the "packed-refs" is sorted
builtin/fsck: add `git refs verify` child process
Documentation/fsck-msgids.adoc | 14 +
Documentation/git-fsck.adoc | 7 +-
builtin/fsck.c | 33 +-
builtin/refs.c | 2 +-
fsck.h | 4 +
refs/packed-backend.c | 363 +++++++++-
t/t0602-reffiles-fsck.sh | 1209 +++++++++++++++++++-------------
worktree.c | 5 +
worktree.h | 8 +
9 files changed, 1162 insertions(+), 483 deletions(-)
Range-diff against v7:
1: b3952d80a2 = 1: b3952d80a2 t0602: use subshell to ensure working directory unchanged
2: fa5ce20bb7 = 2: fa5ce20bb7 builtin/refs: get worktrees without reading head information
3: 861583f417 ! 3: b3686a9695 packed-backend: check whether the "packed-refs" is regular file
@@ refs/packed-backend.c: static struct ref_iterator *packed_reflog_iterator_begin(
+ REF_STORE_READ, "fsck");
+ struct stat st;
+ int ret = 0;
-+ int fd;
++ int fd = -1;
if (!is_main_worktree(wt))
- return 0;
+- return 0;
++ goto cleanup;
- return 0;
+ if (o->verbose)
4: 5f54cb05c3 = 4: 2638d5043f packed-backend: check if header starts with "# pack-refs with: "
5: 7d7dc899ad ! 5: 13e34de350 packed-backend: add "packed-refs" header consistency check
@@ refs/packed-backend.c: static struct ref_iterator *packed_reflog_iterator_begin(
+ struct strbuf packed_ref_content = STRBUF_INIT;
struct stat st;
int ret = 0;
- int fd;
+ int fd = -1;
@@ refs/packed-backend.c: static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
6: 571479d3e7 = 6: 0632a1d5e2 packed-backend: check whether the refname contains NUL characters
7: e498a57286 = 7: 4618da3199 packed-backend: add "packed-refs" entry consistency check
8: 3638cb118d ! 8: 355e43d251 packed-backend: check whether the "packed-refs" is sorted
@@ refs/packed-backend.c: static int packed_fsck(struct ref_store *ref_store,
+ unsigned int sorted = 0;
struct stat st;
int ret = 0;
- int fd;
+ int fd = -1;
@@ refs/packed-backend.c: static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
9: 5d87e76d28 = 9: 57dac06151 builtin/fsck: add `git refs verify` child process
--
2.48.1
^ permalink raw reply [flat|nested] 168+ messages in thread
* [PATCH v8 1/9] t0602: use subshell to ensure working directory unchanged
2025-02-27 16:03 ` [PATCH v8 0/9] add more ref consistency checks shejialuo
@ 2025-02-27 16:05 ` shejialuo
2025-02-27 16:06 ` [PATCH v8 2/9] builtin/refs: get worktrees without reading head information shejialuo
` (7 subsequent siblings)
8 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-27 16:05 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
For every test, we would execute the command "cd repo" in the first but
we never execute the command "cd .." to restore the working directory.
However, it's either not a good idea use above way. Because if any test
fails between "cd repo" and "cd ..", the "cd .." will never be reached.
And we cannot correctly restore the working directory.
Let's use subshell to ensure that the current working directory could be
restored to the correct path.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
t/t0602-reffiles-fsck.sh | 967 ++++++++++++++++++++-------------------
1 file changed, 494 insertions(+), 473 deletions(-)
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index d4a08b823b..cf7a202d0d 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -14,222 +14,229 @@ test_expect_success 'ref name should be checked' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
-
- git commit --allow-empty -m initial &&
- git checkout -b default-branch &&
- git tag default-tag &&
- git tag multi_hierarchy/default-tag &&
-
- cp $branch_dir_prefix/default-branch $branch_dir_prefix/@ &&
- git refs verify 2>err &&
- test_must_be_empty err &&
- rm $branch_dir_prefix/@ &&
-
- cp $tag_dir_prefix/default-tag $tag_dir_prefix/tag-1.lock &&
- git refs verify 2>err &&
- rm $tag_dir_prefix/tag-1.lock &&
- test_must_be_empty err &&
-
- cp $tag_dir_prefix/default-tag $tag_dir_prefix/.lock &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/tags/.lock: badRefName: invalid refname format
- EOF
- rm $tag_dir_prefix/.lock &&
- test_cmp expect err &&
-
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname" &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/$refname: badRefName: invalid refname format
- EOF
- rm "$branch_dir_prefix/$refname" &&
- test_cmp expect err || return 1
- done &&
+ (
+ cd repo &&
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- cp $tag_dir_prefix/default-tag "$tag_dir_prefix/$refname" &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/tags/$refname: badRefName: invalid refname format
- EOF
- rm "$tag_dir_prefix/$refname" &&
- test_cmp expect err || return 1
- done &&
+ git commit --allow-empty -m initial &&
+ git checkout -b default-branch &&
+ git tag default-tag &&
+ git tag multi_hierarchy/default-tag &&
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- cp $tag_dir_prefix/multi_hierarchy/default-tag "$tag_dir_prefix/multi_hierarchy/$refname" &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/tags/multi_hierarchy/$refname: badRefName: invalid refname format
- EOF
- rm "$tag_dir_prefix/multi_hierarchy/$refname" &&
- test_cmp expect err || return 1
- done &&
-
- for refname in ".refname-starts-with-dot" "~refname-has-stride"
- do
- mkdir "$branch_dir_prefix/$refname" &&
- cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname/default-branch" &&
+ cp $branch_dir_prefix/default-branch $branch_dir_prefix/@ &&
+ git refs verify 2>err &&
+ test_must_be_empty err &&
+ rm $branch_dir_prefix/@ &&
+
+ cp $tag_dir_prefix/default-tag $tag_dir_prefix/tag-1.lock &&
+ git refs verify 2>err &&
+ rm $tag_dir_prefix/tag-1.lock &&
+ test_must_be_empty err &&
+
+ cp $tag_dir_prefix/default-tag $tag_dir_prefix/.lock &&
test_must_fail git refs verify 2>err &&
cat >expect <<-EOF &&
- error: refs/heads/$refname/default-branch: badRefName: invalid refname format
+ error: refs/tags/.lock: badRefName: invalid refname format
EOF
- rm -r "$branch_dir_prefix/$refname" &&
- test_cmp expect err || return 1
- done
+ rm $tag_dir_prefix/.lock &&
+ test_cmp expect err &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/$refname: badRefName: invalid refname format
+ EOF
+ rm "$branch_dir_prefix/$refname" &&
+ test_cmp expect err || return 1
+ done &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ cp $tag_dir_prefix/default-tag "$tag_dir_prefix/$refname" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/tags/$refname: badRefName: invalid refname format
+ EOF
+ rm "$tag_dir_prefix/$refname" &&
+ test_cmp expect err || return 1
+ done &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ cp $tag_dir_prefix/multi_hierarchy/default-tag "$tag_dir_prefix/multi_hierarchy/$refname" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/tags/multi_hierarchy/$refname: badRefName: invalid refname format
+ EOF
+ rm "$tag_dir_prefix/multi_hierarchy/$refname" &&
+ test_cmp expect err || return 1
+ done &&
+
+ for refname in ".refname-starts-with-dot" "~refname-has-stride"
+ do
+ mkdir "$branch_dir_prefix/$refname" &&
+ cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname/default-branch" &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/$refname/default-branch: badRefName: invalid refname format
+ EOF
+ rm -r "$branch_dir_prefix/$refname" &&
+ test_cmp expect err || return 1
+ done
+ )
'
test_expect_success 'ref name check should be adapted into fsck messages' '
test_when_finished "rm -rf repo" &&
git init repo &&
branch_dir_prefix=.git/refs/heads &&
- cd repo &&
- git commit --allow-empty -m initial &&
- git checkout -b branch-1 &&
-
- cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
- git -c fsck.badRefName=warn refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/.branch-1: badRefName: invalid refname format
- EOF
- rm $branch_dir_prefix/.branch-1 &&
- test_cmp expect err &&
-
- cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
- git -c fsck.badRefName=ignore refs verify 2>err &&
- test_must_be_empty err
+ (
+ cd repo &&
+ git commit --allow-empty -m initial &&
+ git checkout -b branch-1 &&
+
+ cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
+ git -c fsck.badRefName=warn refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/.branch-1: badRefName: invalid refname format
+ EOF
+ rm $branch_dir_prefix/.branch-1 &&
+ test_cmp expect err &&
+
+ cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
+ git -c fsck.badRefName=ignore refs verify 2>err &&
+ test_must_be_empty err
+ )
'
test_expect_success 'ref name check should work for multiple worktrees' '
test_when_finished "rm -rf repo" &&
git init repo &&
-
- cd repo &&
- test_commit initial &&
- git checkout -b branch-1 &&
- test_commit second &&
- git checkout -b branch-2 &&
- test_commit third &&
- git checkout -b branch-3 &&
- git worktree add ./worktree-1 branch-1 &&
- git worktree add ./worktree-2 branch-2 &&
- worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
- worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
-
- (
- cd worktree-1 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-3
- ) &&
(
- cd worktree-2 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-3
- ) &&
-
- cp $worktree1_refdir_prefix/branch-4 $worktree1_refdir_prefix/'\'' branch-5'\'' &&
- cp $worktree2_refdir_prefix/branch-4 $worktree2_refdir_prefix/'\''~branch-6'\'' &&
-
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
- error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err &&
-
- for worktree in "worktree-1" "worktree-2"
- do
+ cd repo &&
+ test_commit initial &&
+ git checkout -b branch-1 &&
+ test_commit second &&
+ git checkout -b branch-2 &&
+ test_commit third &&
+ git checkout -b branch-3 &&
+ git worktree add ./worktree-1 branch-1 &&
+ git worktree add ./worktree-2 branch-2 &&
+ worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
+ worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
+
(
- cd $worktree &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
- error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err || return 1
- )
- done
+ cd worktree-1 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-3
+ ) &&
+ (
+ cd worktree-2 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-3
+ ) &&
+
+ cp $worktree1_refdir_prefix/branch-4 $worktree1_refdir_prefix/'\'' branch-5'\'' &&
+ cp $worktree2_refdir_prefix/branch-4 $worktree2_refdir_prefix/'\''~branch-6'\'' &&
+
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
+ error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err &&
+
+ for worktree in "worktree-1" "worktree-2"
+ do
+ (
+ cd $worktree &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
+ error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err || return 1
+ )
+ done
+ )
'
test_expect_success 'regular ref content should be checked (individual)' '
test_when_finished "rm -rf repo" &&
git init repo &&
branch_dir_prefix=.git/refs/heads &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
- git refs verify 2>err &&
- test_must_be_empty err &&
+ git refs verify 2>err &&
+ test_must_be_empty err &&
- for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$branch_dir_prefix/branch-bad &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/branch-bad: badRefContent: $bad_content
- EOF
- rm $branch_dir_prefix/branch-bad &&
- test_cmp expect err || return 1
- done &&
+ for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$branch_dir_prefix/branch-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/branch-bad: badRefContent: $bad_content
+ EOF
+ rm $branch_dir_prefix/branch-bad &&
+ test_cmp expect err || return 1
+ done &&
- for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$branch_dir_prefix/a/b/branch-bad &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/a/b/branch-bad: badRefContent: $bad_content
- EOF
- rm $branch_dir_prefix/a/b/branch-bad &&
- test_cmp expect err || return 1
- done &&
-
- printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- rm $branch_dir_prefix/branch-no-newline &&
- test_cmp expect err &&
-
- for trailing_content in " garbage" " more garbage"
- do
- printf "%s" "$(git rev-parse main)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$branch_dir_prefix/a/b/branch-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/a/b/branch-bad: badRefContent: $bad_content
+ EOF
+ rm $branch_dir_prefix/a/b/branch-bad &&
+ test_cmp expect err || return 1
+ done &&
+
+ printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
EOF
- rm $branch_dir_prefix/branch-garbage &&
- test_cmp expect err || return 1
- done &&
+ rm $branch_dir_prefix/branch-no-newline &&
+ test_cmp expect err &&
- printf "%s\n\n\n" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
+ for trailing_content in " garbage" " more garbage"
+ do
+ printf "%s" "$(git rev-parse main)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage &&
+ test_cmp expect err || return 1
+ done &&
+ printf "%s\n\n\n" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
- '\''
- EOF
- rm $branch_dir_prefix/branch-garbage-special &&
- test_cmp expect err &&
- printf "%s\n\n\n garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
+ '\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage-special &&
+ test_cmp expect err &&
+
+ printf "%s\n\n\n garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\''
- garbage'\''
- EOF
- rm $branch_dir_prefix/branch-garbage-special &&
- test_cmp expect err
+ garbage'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage-special &&
+ test_cmp expect err
+ )
'
test_expect_success 'regular ref content should be checked (aggregate)' '
@@ -237,99 +244,103 @@ test_expect_success 'regular ref content should be checked (aggregate)' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- bad_content_1=$(git rev-parse main)x &&
- bad_content_2=xfsazqfxcadas &&
- bad_content_3=Xfsazqfxcadas &&
- printf "%s" $bad_content_1 >$tag_dir_prefix/tag-bad-1 &&
- printf "%s" $bad_content_2 >$tag_dir_prefix/tag-bad-2 &&
- printf "%s" $bad_content_3 >$branch_dir_prefix/a/b/branch-bad &&
- printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
- printf "%s garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage &&
-
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/a/b/branch-bad: badRefContent: $bad_content_3
- error: refs/tags/tag-bad-1: badRefContent: $bad_content_1
- error: refs/tags/tag-bad-2: badRefContent: $bad_content_2
- warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
- warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ bad_content_1=$(git rev-parse main)x &&
+ bad_content_2=xfsazqfxcadas &&
+ bad_content_3=Xfsazqfxcadas &&
+ printf "%s" $bad_content_1 >$tag_dir_prefix/tag-bad-1 &&
+ printf "%s" $bad_content_2 >$tag_dir_prefix/tag-bad-2 &&
+ printf "%s" $bad_content_3 >$branch_dir_prefix/a/b/branch-bad &&
+ printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
+ printf "%s garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage &&
+
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/a/b/branch-bad: badRefContent: $bad_content_3
+ error: refs/tags/tag-bad-1: badRefContent: $bad_content_1
+ error: refs/tags/tag-bad-2: badRefContent: $bad_content_2
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
+ warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err
+ )
'
test_expect_success 'textual symref content should be checked (individual)' '
test_when_finished "rm -rf repo" &&
git init repo &&
branch_dir_prefix=.git/refs/heads &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ for good_referent in "refs/heads/branch" "HEAD"
+ do
+ printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
+ git refs verify 2>err &&
+ rm $branch_dir_prefix/branch-good &&
+ test_must_be_empty err || return 1
+ done &&
+
+ for bad_referent in "refs/heads/.branch" "refs/heads/~branch" "refs/heads/?branch"
+ do
+ printf "ref: %s\n" $bad_referent >$branch_dir_prefix/branch-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/branch-bad: badReferentName: points to invalid refname '\''$bad_referent'\''
+ EOF
+ rm $branch_dir_prefix/branch-bad &&
+ test_cmp expect err || return 1
+ done &&
- for good_referent in "refs/heads/branch" "HEAD"
- do
- printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
+ printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline &&
git refs verify 2>err &&
- rm $branch_dir_prefix/branch-good &&
- test_must_be_empty err || return 1
- done &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
+ EOF
+ rm $branch_dir_prefix/branch-no-newline &&
+ test_cmp expect err &&
- for bad_referent in "refs/heads/.branch" "refs/heads/~branch" "refs/heads/?branch"
- do
- printf "ref: %s\n" $bad_referent >$branch_dir_prefix/branch-bad &&
- test_must_fail git refs verify 2>err &&
+ printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
+ EOF
+ rm $branch_dir_prefix/a/b/branch-trailing-1 &&
+ test_cmp expect err &&
+
+ printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- error: refs/heads/branch-bad: badReferentName: points to invalid refname '\''$bad_referent'\''
+ warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
EOF
- rm $branch_dir_prefix/branch-bad &&
- test_cmp expect err || return 1
- done &&
-
- printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- rm $branch_dir_prefix/branch-no-newline &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-trailing-1 &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-trailing-2 &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-trailing-3 &&
- test_cmp expect err &&
-
- printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
- EOF
- rm $branch_dir_prefix/a/b/branch-complicated &&
- test_cmp expect err
+ rm $branch_dir_prefix/a/b/branch-trailing-2 &&
+ test_cmp expect err &&
+
+ printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
+ EOF
+ rm $branch_dir_prefix/a/b/branch-trailing-3 &&
+ test_cmp expect err &&
+
+ printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
+ EOF
+ rm $branch_dir_prefix/a/b/branch-complicated &&
+ test_cmp expect err
+ )
'
test_expect_success 'textual symref content should be checked (aggregate)' '
@@ -337,32 +348,34 @@ test_expect_success 'textual symref content should be checked (aggregate)' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- printf "ref: refs/heads/branch\n" >$branch_dir_prefix/branch-good &&
- printf "ref: HEAD\n" >$branch_dir_prefix/branch-head &&
- printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline-1 &&
- printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
- printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
- printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
- printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
- printf "ref: refs/heads/.branch\n" >$branch_dir_prefix/branch-bad-1 &&
-
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- error: refs/heads/branch-bad-1: badReferentName: points to invalid refname '\''refs/heads/.branch'\''
- warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
- warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
- warning: refs/heads/branch-no-newline-1: refMissingNewline: misses LF at the end
- EOF
- sort err >sorted_err &&
- test_cmp expect sorted_err
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ printf "ref: refs/heads/branch\n" >$branch_dir_prefix/branch-good &&
+ printf "ref: HEAD\n" >$branch_dir_prefix/branch-head &&
+ printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline-1 &&
+ printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
+ printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
+ printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
+ printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
+ printf "ref: refs/heads/.branch\n" >$branch_dir_prefix/branch-bad-1 &&
+
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/branch-bad-1: badReferentName: points to invalid refname '\''refs/heads/.branch'\''
+ warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end
+ warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines
+ warning: refs/heads/branch-no-newline-1: refMissingNewline: misses LF at the end
+ EOF
+ sort err >sorted_err &&
+ test_cmp expect sorted_err
+ )
'
test_expect_success 'the target of the textual symref should be checked' '
@@ -370,28 +383,30 @@ test_expect_success 'the target of the textual symref should be checked' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- for good_referent in "refs/heads/branch" "HEAD" "refs/tags/tag"
- do
- printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
- git refs verify 2>err &&
- rm $branch_dir_prefix/branch-good &&
- test_must_be_empty err || return 1
- done &&
-
- for nonref_referent in "refs-back/heads/branch" "refs-back/tags/tag" "reflogs/refs/heads/branch"
- do
- printf "ref: %s\n" $nonref_referent >$branch_dir_prefix/branch-bad-1 &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-bad-1: symrefTargetIsNotARef: points to non-ref target '\''$nonref_referent'\''
- EOF
- rm $branch_dir_prefix/branch-bad-1 &&
- test_cmp expect err || return 1
- done
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ for good_referent in "refs/heads/branch" "HEAD" "refs/tags/tag"
+ do
+ printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good &&
+ git refs verify 2>err &&
+ rm $branch_dir_prefix/branch-good &&
+ test_must_be_empty err || return 1
+ done &&
+
+ for nonref_referent in "refs-back/heads/branch" "refs-back/tags/tag" "reflogs/refs/heads/branch"
+ do
+ printf "ref: %s\n" $nonref_referent >$branch_dir_prefix/branch-bad-1 &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-bad-1: symrefTargetIsNotARef: points to non-ref target '\''$nonref_referent'\''
+ EOF
+ rm $branch_dir_prefix/branch-bad-1 &&
+ test_cmp expect err || return 1
+ done
+ )
'
test_expect_success SYMLINKS 'symlink symref content should be checked' '
@@ -399,201 +414,207 @@ test_expect_success SYMLINKS 'symlink symref content should be checked' '
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
- cd repo &&
- test_commit default &&
- mkdir -p "$branch_dir_prefix/a/b" &&
-
- ln -sf ./main $branch_dir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $branch_dir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../logs/branch-escape $branch_dir_prefix/branch-symbolic &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
- warning: refs/heads/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
- EOF
- rm $branch_dir_prefix/branch-symbolic &&
- test_cmp expect err &&
-
- ln -sf ./"branch " $branch_dir_prefix/branch-symbolic-bad &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic-bad: symlinkRef: use deprecated symbolic link for symref
- error: refs/heads/branch-symbolic-bad: badReferentName: points to invalid refname '\''refs/heads/branch '\''
- EOF
- rm $branch_dir_prefix/branch-symbolic-bad &&
- test_cmp expect err &&
-
- ln -sf ./".tag" $tag_dir_prefix/tag-symbolic-1 &&
- test_must_fail git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/tags/tag-symbolic-1: symlinkRef: use deprecated symbolic link for symref
- error: refs/tags/tag-symbolic-1: badReferentName: points to invalid refname '\''refs/tags/.tag'\''
- EOF
- rm $tag_dir_prefix/tag-symbolic-1 &&
- test_cmp expect err
+ (
+ cd repo &&
+ test_commit default &&
+ mkdir -p "$branch_dir_prefix/a/b" &&
+
+ ln -sf ./main $branch_dir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
+ EOF
+ rm $branch_dir_prefix/branch-symbolic-good &&
+ test_cmp expect err &&
+
+ ln -sf ../../logs/branch-escape $branch_dir_prefix/branch-symbolic &&
+ git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
+ warning: refs/heads/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
+ EOF
+ rm $branch_dir_prefix/branch-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ./"branch " $branch_dir_prefix/branch-symbolic-bad &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-symbolic-bad: symlinkRef: use deprecated symbolic link for symref
+ error: refs/heads/branch-symbolic-bad: badReferentName: points to invalid refname '\''refs/heads/branch '\''
+ EOF
+ rm $branch_dir_prefix/branch-symbolic-bad &&
+ test_cmp expect err &&
+
+ ln -sf ./".tag" $tag_dir_prefix/tag-symbolic-1 &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/tags/tag-symbolic-1: symlinkRef: use deprecated symbolic link for symref
+ error: refs/tags/tag-symbolic-1: badReferentName: points to invalid refname '\''refs/tags/.tag'\''
+ EOF
+ rm $tag_dir_prefix/tag-symbolic-1 &&
+ test_cmp expect err
+ )
'
test_expect_success SYMLINKS 'symlink symref content should be checked (worktree)' '
test_when_finished "rm -rf repo" &&
git init repo &&
- cd repo &&
- test_commit default &&
- git branch branch-1 &&
- git branch branch-2 &&
- git branch branch-3 &&
- git worktree add ./worktree-1 branch-2 &&
- git worktree add ./worktree-2 branch-3 &&
- main_worktree_refdir_prefix=.git/refs/heads &&
- worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
- worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
-
(
- cd worktree-1 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
- (
- cd worktree-2 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
-
- ln -sf ../../../../refs/heads/good-branch $worktree1_refdir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $worktree1_refdir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../../../worktrees/worktree-1/good-branch $worktree2_refdir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-2/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $worktree2_refdir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../worktrees/worktree-2/good-branch $main_worktree_refdir_prefix/branch-symbolic-good &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
- EOF
- rm $main_worktree_refdir_prefix/branch-symbolic-good &&
- test_cmp expect err &&
-
- ln -sf ../../../../logs/branch-escape $worktree1_refdir_prefix/branch-symbolic &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
- warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
- EOF
- rm $worktree1_refdir_prefix/branch-symbolic &&
- test_cmp expect err &&
-
- for bad_referent_name in ".tag" "branch "
- do
- ln -sf ./"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git branch branch-3 &&
+ git worktree add ./worktree-1 branch-2 &&
+ git worktree add ./worktree-2 branch-3 &&
+ main_worktree_refdir_prefix=.git/refs/heads &&
+ worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
+ worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
+
+ (
+ cd worktree-1 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+ (
+ cd worktree-2 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+
+ ln -sf ../../../../refs/heads/good-branch $worktree1_refdir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-1/refs/worktree/$bad_referent_name'\''
+ warning: worktrees/worktree-1/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
EOF
- rm $worktree1_refdir_prefix/bad-symbolic &&
+ rm $worktree1_refdir_prefix/branch-symbolic-good &&
test_cmp expect err &&
- ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ ln -sf ../../../../worktrees/worktree-1/good-branch $worktree2_refdir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ warning: worktrees/worktree-2/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
EOF
- rm $worktree1_refdir_prefix/bad-symbolic &&
+ rm $worktree2_refdir_prefix/branch-symbolic-good &&
test_cmp expect err &&
- ln -sf ./"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ ln -sf ../../worktrees/worktree-2/good-branch $main_worktree_refdir_prefix/branch-symbolic-good &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-2/refs/worktree/$bad_referent_name'\''
+ warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
EOF
- rm $worktree2_refdir_prefix/bad-symbolic &&
+ rm $main_worktree_refdir_prefix/branch-symbolic-good &&
test_cmp expect err &&
- ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
- test_must_fail git refs verify 2>err &&
+ ln -sf ../../../../logs/branch-escape $worktree1_refdir_prefix/branch-symbolic &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
- error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symlinkRef: use deprecated symbolic link for symref
+ warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\''
EOF
- rm $worktree2_refdir_prefix/bad-symbolic &&
- test_cmp expect err || return 1
- done
+ rm $worktree1_refdir_prefix/branch-symbolic &&
+ test_cmp expect err &&
+
+ for bad_referent_name in ".tag" "branch "
+ do
+ ln -sf ./"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-1/refs/worktree/$bad_referent_name'\''
+ EOF
+ rm $worktree1_refdir_prefix/bad-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ EOF
+ rm $worktree1_refdir_prefix/bad-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ./"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-2/refs/worktree/$bad_referent_name'\''
+ EOF
+ rm $worktree2_refdir_prefix/bad-symbolic &&
+ test_cmp expect err &&
+
+ ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref
+ error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\''
+ EOF
+ rm $worktree2_refdir_prefix/bad-symbolic &&
+ test_cmp expect err || return 1
+ done
+ )
'
test_expect_success 'ref content checks should work with worktrees' '
test_when_finished "rm -rf repo" &&
git init repo &&
- cd repo &&
- test_commit default &&
- git branch branch-1 &&
- git branch branch-2 &&
- git branch branch-3 &&
- git worktree add ./worktree-1 branch-2 &&
- git worktree add ./worktree-2 branch-3 &&
- worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
- worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
-
(
- cd worktree-1 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
- (
- cd worktree-2 &&
- git update-ref refs/worktree/branch-4 refs/heads/branch-1
- ) &&
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git branch branch-3 &&
+ git worktree add ./worktree-1 branch-2 &&
+ git worktree add ./worktree-2 branch-3 &&
+ worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
+ worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
- for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$worktree1_refdir_prefix/bad-branch-1 &&
- test_must_fail git refs verify 2>err &&
+ (
+ cd worktree-1 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+ (
+ cd worktree-2 &&
+ git update-ref refs/worktree/branch-4 refs/heads/branch-1
+ ) &&
+
+ for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$worktree1_refdir_prefix/bad-branch-1 &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-1/refs/worktree/bad-branch-1: badRefContent: $bad_content
+ EOF
+ rm $worktree1_refdir_prefix/bad-branch-1 &&
+ test_cmp expect err || return 1
+ done &&
+
+ for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
+ do
+ printf "%s" $bad_content >$worktree2_refdir_prefix/bad-branch-2 &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: worktrees/worktree-2/refs/worktree/bad-branch-2: badRefContent: $bad_content
+ EOF
+ rm $worktree2_refdir_prefix/bad-branch-2 &&
+ test_cmp expect err || return 1
+ done &&
+
+ printf "%s" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-no-newline &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- error: worktrees/worktree-1/refs/worktree/bad-branch-1: badRefContent: $bad_content
+ warning: worktrees/worktree-1/refs/worktree/branch-no-newline: refMissingNewline: misses LF at the end
EOF
- rm $worktree1_refdir_prefix/bad-branch-1 &&
- test_cmp expect err || return 1
- done &&
+ rm $worktree1_refdir_prefix/branch-no-newline &&
+ test_cmp expect err &&
- for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas"
- do
- printf "%s" $bad_content >$worktree2_refdir_prefix/bad-branch-2 &&
- test_must_fail git refs verify 2>err &&
+ printf "%s garbage" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-garbage &&
+ git refs verify 2>err &&
cat >expect <<-EOF &&
- error: worktrees/worktree-2/refs/worktree/bad-branch-2: badRefContent: $bad_content
+ warning: worktrees/worktree-1/refs/worktree/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
EOF
- rm $worktree2_refdir_prefix/bad-branch-2 &&
- test_cmp expect err || return 1
- done &&
-
- printf "%s" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-no-newline &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-no-newline: refMissingNewline: misses LF at the end
- EOF
- rm $worktree1_refdir_prefix/branch-no-newline &&
- test_cmp expect err &&
-
- printf "%s garbage" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-garbage &&
- git refs verify 2>err &&
- cat >expect <<-EOF &&
- warning: worktrees/worktree-1/refs/worktree/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\''
- EOF
- rm $worktree1_refdir_prefix/branch-garbage &&
- test_cmp expect err
+ rm $worktree1_refdir_prefix/branch-garbage &&
+ test_cmp expect err
+ )
'
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v8 2/9] builtin/refs: get worktrees without reading head information
2025-02-27 16:03 ` [PATCH v8 0/9] add more ref consistency checks shejialuo
2025-02-27 16:05 ` [PATCH v8 1/9] t0602: use subshell to ensure working directory unchanged shejialuo
@ 2025-02-27 16:06 ` shejialuo
2025-02-27 16:06 ` [PATCH v8 3/9] packed-backend: check whether the "packed-refs" is regular file shejialuo
` (6 subsequent siblings)
8 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-27 16:06 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
In "packed-backend.c", there are some functions such as "create_snapshot"
and "next_record" which would check the correctness of the content of
the "packed-ref" file. When anything is bad, the program will die.
It may seem that we have nothing relevant to above feature, because we
are going to read and parse the raw "packed-ref" file without creating
the snapshot and using the ref iterator to check the consistency.
However, when using "get_worktrees" in "builtin/refs", we would parse
the "HEAD" information. If the referent of the "HEAD" is inside the
"packed-ref", we will call "create_snapshot" function to parse the
"packed-ref" to get the information. No matter whether the entry of
"HEAD" in "packed-ref" is correct, "create_snapshot" would call
"verify_buffer_safe" to check whether there is a newline in the last
line of the file. If not, the program will die.
Although this behavior has no harm for the program, it will
short-circuit the program. When the users execute "git refs verify" or
"git fsck", we should avoid reading the head information, which may
execute the read operation in packed backend with stricter checks to die
the program. Instead, we should continue to check other parts of the
"packed-refs" file completely.
Fortunately, in 465a22b338 (worktree: skip reading HEAD when repairing
worktrees, 2023-12-29), we have introduced a function
"get_worktrees_internal" which allows us to get worktrees without
reading head information.
Create a new exposed function "get_worktrees_without_reading_head", then
replace the "get_worktrees" in "builtin/refs" with the new created
function.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
builtin/refs.c | 2 +-
worktree.c | 5 +++++
worktree.h | 8 ++++++++
3 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/builtin/refs.c b/builtin/refs.c
index a29f195834..55ff5dae11 100644
--- a/builtin/refs.c
+++ b/builtin/refs.c
@@ -88,7 +88,7 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix,
git_config(git_fsck_config, &fsck_refs_options);
prepare_repo_settings(the_repository);
- worktrees = get_worktrees();
+ worktrees = get_worktrees_without_reading_head();
for (size_t i = 0; worktrees[i]; i++)
ret |= refs_fsck(get_worktree_ref_store(worktrees[i]),
&fsck_refs_options, worktrees[i]);
diff --git a/worktree.c b/worktree.c
index d4a68c9c23..d23482a746 100644
--- a/worktree.c
+++ b/worktree.c
@@ -198,6 +198,11 @@ struct worktree **get_worktrees(void)
return get_worktrees_internal(0);
}
+struct worktree **get_worktrees_without_reading_head(void)
+{
+ return get_worktrees_internal(1);
+}
+
const char *get_worktree_git_dir(const struct worktree *wt)
{
if (!wt)
diff --git a/worktree.h b/worktree.h
index 38145df80f..a305c7e2c7 100644
--- a/worktree.h
+++ b/worktree.h
@@ -30,6 +30,14 @@ struct worktree {
*/
struct worktree **get_worktrees(void);
+/*
+ * Like `get_worktrees`, but does not read HEAD. Skip reading HEAD allows to
+ * get the worktree without worrying about failures pertaining to parsing
+ * the HEAD ref. This is useful in contexts where it is assumed that the
+ * refdb may not be in a consistent state.
+ */
+struct worktree **get_worktrees_without_reading_head(void);
+
/*
* Returns 1 if linked worktrees exist, 0 otherwise.
*/
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v8 3/9] packed-backend: check whether the "packed-refs" is regular file
2025-02-27 16:03 ` [PATCH v8 0/9] add more ref consistency checks shejialuo
2025-02-27 16:05 ` [PATCH v8 1/9] t0602: use subshell to ensure working directory unchanged shejialuo
2025-02-27 16:06 ` [PATCH v8 2/9] builtin/refs: get worktrees without reading head information shejialuo
@ 2025-02-27 16:06 ` shejialuo
2025-02-27 16:06 ` [PATCH v8 4/9] packed-backend: check if header starts with "# pack-refs with: " shejialuo
` (5 subsequent siblings)
8 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-27 16:06 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
Although "git-fsck(1)" and "packed-backend.c" will check some
consistency and correctness of "packed-refs" file, they never check the
filetype of the "packed-refs". Let's verify that the "packed-refs" has
the expected filetype, confirming it is created by "git pack-refs"
command.
We could use "open_nofollow" wrapper to open the raw "packed-refs" file.
If the returned "fd" value is less than 0, we could check whether the
"errno" is "ELOOP" to report an error to the user. And then we use
"fstat" to check whether the "packed-refs" file is a regular file.
Reuse "FSCK_MSG_BAD_REF_FILETYPE" fsck message id to report the error to
the user if "packed-refs" is not a regular file.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
refs/packed-backend.c | 52 ++++++++++++++++++++++++++++++++++++----
t/t0602-reffiles-fsck.sh | 30 +++++++++++++++++++++++
2 files changed, 78 insertions(+), 4 deletions(-)
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index a7b6f74b6e..1fba804a2a 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -4,6 +4,7 @@
#include "../git-compat-util.h"
#include "../config.h"
#include "../dir.h"
+#include "../fsck.h"
#include "../gettext.h"
#include "../hash.h"
#include "../hex.h"
@@ -1748,15 +1749,58 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
return empty_ref_iterator_begin();
}
-static int packed_fsck(struct ref_store *ref_store UNUSED,
- struct fsck_options *o UNUSED,
+static int packed_fsck(struct ref_store *ref_store,
+ struct fsck_options *o,
struct worktree *wt)
{
+ struct packed_ref_store *refs = packed_downcast(ref_store,
+ REF_STORE_READ, "fsck");
+ struct stat st;
+ int ret = 0;
+ int fd = -1;
if (!is_main_worktree(wt))
- return 0;
+ goto cleanup;
- return 0;
+ if (o->verbose)
+ fprintf_ln(stderr, "Checking packed-refs file %s", refs->path);
+
+ fd = open_nofollow(refs->path, O_RDONLY);
+ if (fd < 0) {
+ /*
+ * If the packed-refs file doesn't exist, there's nothing
+ * to check.
+ */
+ if (errno == ENOENT)
+ goto cleanup;
+
+ if (errno == ELOOP) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs";
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_FILETYPE,
+ "not a regular file but a symlink");
+ goto cleanup;
+ }
+
+ ret = error_errno(_("unable to open '%s'"), refs->path);
+ goto cleanup;
+ } else if (fstat(fd, &st) < 0) {
+ ret = error_errno(_("unable to stat '%s'"), refs->path);
+ goto cleanup;
+ } else if (!S_ISREG(st.st_mode)) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs";
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_FILETYPE,
+ "not a regular file");
+ goto cleanup;
+ }
+
+cleanup:
+ if (fd >= 0)
+ close(fd);
+ return ret;
}
struct ref_storage_be refs_be_packed = {
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index cf7a202d0d..68b7d4999e 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -617,4 +617,34 @@ test_expect_success 'ref content checks should work with worktrees' '
)
'
+test_expect_success SYMLINKS 'the filetype of packed-refs should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git branch branch-3 &&
+ git pack-refs --all &&
+
+ mv .git/packed-refs .git/packed-refs-back &&
+ ln -sf packed-refs-back .git/packed-refs &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs: badRefFiletype: not a regular file but a symlink
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err &&
+
+ mkdir .git/packed-refs &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs: badRefFiletype: not a regular file
+ EOF
+ rm -r .git/packed-refs &&
+ test_cmp expect err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v8 4/9] packed-backend: check if header starts with "# pack-refs with: "
2025-02-27 16:03 ` [PATCH v8 0/9] add more ref consistency checks shejialuo
` (2 preceding siblings ...)
2025-02-27 16:06 ` [PATCH v8 3/9] packed-backend: check whether the "packed-refs" is regular file shejialuo
@ 2025-02-27 16:06 ` shejialuo
2025-02-27 16:06 ` [PATCH v8 5/9] packed-backend: add "packed-refs" header consistency check shejialuo
` (4 subsequent siblings)
8 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-27 16:06 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
We always write a space after "# pack-refs with:" but we don't align
with this rule in the "create_snapshot" method where we would check
whether header starts with "# pack-refs with:". It might seem that we
should undoubtedly tighten this rule, however, we don't have any
technical documentation about this and there is a possibility that we
would break the compatibility for other third-party libraries.
By investigating influential third-party libraries, we could conclude
how these libraries handle the header of "packed-refs" file:
1. libgit2 is fine and always writes the space. It also expects the
whitespace to exist.
2. JGit does not expect th header to have a trailing space, but expects
the "peeled" capability to have a leading space, which is mostly
equivalent because that capability is typically the first one we
write. It always writes the space.
3. gitoxide expects the space t exist and writes it.
4. go-git doesn't create the header by default.
As many third-party libraries expect a single space after "# pack-refs
with:", if we forget to write the space after the colon,
"create_snapshot" won't catch this. And we would break other
re-implementations. So, we'd better tighten the rule by checking whether
the header starts with "# pack-refs with: ".
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
refs/packed-backend.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 1fba804a2a..eaa8746f3e 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -694,7 +694,7 @@ static struct snapshot *create_snapshot(struct packed_ref_store *refs)
tmp = xmemdupz(snapshot->buf, eol - snapshot->buf);
- if (!skip_prefix(tmp, "# pack-refs with:", (const char **)&p))
+ if (!skip_prefix(tmp, "# pack-refs with: ", (const char **)&p))
die_invalid_line(refs->path,
snapshot->buf,
snapshot->eof - snapshot->buf);
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v8 5/9] packed-backend: add "packed-refs" header consistency check
2025-02-27 16:03 ` [PATCH v8 0/9] add more ref consistency checks shejialuo
` (3 preceding siblings ...)
2025-02-27 16:06 ` [PATCH v8 4/9] packed-backend: check if header starts with "# pack-refs with: " shejialuo
@ 2025-02-27 16:06 ` shejialuo
2025-02-27 16:07 ` [PATCH v8 6/9] packed-backend: check whether the refname contains NUL characters shejialuo
` (3 subsequent siblings)
8 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-27 16:06 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
In "packed-backend.c::create_snapshot", if there is a header (the line
which starts with '#'), we will check whether the line starts with "#
pack-refs with: ". However, we need to consider other situations and
discuss whether we need to add checks.
1. If the header does not exist, we should not report an error to the
user. This is because in older Git version, we never write header in
the "packed-refs" file. Also, we do allow no header in "packed-refs"
in runtime.
2. If the header content does not start with "# packed-ref with: ", we
should report an error just like what "create_snapshot" does. So,
create a new fsck message "badPackedRefHeader(ERROR)" for this.
3. If the header content is not the same as the constant string
"PACKED_REFS_HEADER". This is expected because we make it extensible
intentionally and runtime "create_snapshot" won't complain about
unknown traits. In order to align with the runtime behavior. There is
no need to report.
As we have analyzed, we only need to check the case 2 in the above. In
order to do this, use "open_nofollow" function to get the file
descriptor and then read the "packed-refs" file via "strbuf_read". Like
what "create_snapshot" and other functions do, we could split the line
by finding the next newline in the buffer. When we cannot find a
newline, we could report an error.
So, create a function "packed_fsck_ref_next_line" to find the next
newline and if there is no such newline, use
"packedRefEntryNotTerminated(ERROR)" to report an error to the user.
Then, parse the first line to apply the checks. Update the test to
exercise the code.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/fsck-msgids.adoc | 8 ++++
fsck.h | 2 +
refs/packed-backend.c | 73 ++++++++++++++++++++++++++++++++++
t/t0602-reffiles-fsck.sh | 52 ++++++++++++++++++++++++
4 files changed, 135 insertions(+)
diff --git a/Documentation/fsck-msgids.adoc b/Documentation/fsck-msgids.adoc
index b14bc44ca4..11906f90fd 100644
--- a/Documentation/fsck-msgids.adoc
+++ b/Documentation/fsck-msgids.adoc
@@ -16,6 +16,10 @@
`badObjectSha1`::
(ERROR) An object has a bad sha1.
+`badPackedRefHeader`::
+ (ERROR) The "packed-refs" file contains an invalid
+ header.
+
`badParentSha1`::
(ERROR) A commit object has a bad parent sha1.
@@ -176,6 +180,10 @@
`nullSha1`::
(WARN) Tree contains entries pointing to a null sha1.
+`packedRefEntryNotTerminated`::
+ (ERROR) The "packed-refs" file contains an entry that is
+ not terminated by a newline.
+
`refMissingNewline`::
(INFO) A loose ref that does not end with newline(LF). As
valid implementations of Git never created such a loose ref
diff --git a/fsck.h b/fsck.h
index a44c231a5f..67e3c97bc0 100644
--- a/fsck.h
+++ b/fsck.h
@@ -30,6 +30,7 @@ enum fsck_msg_type {
FUNC(BAD_EMAIL, ERROR) \
FUNC(BAD_NAME, ERROR) \
FUNC(BAD_OBJECT_SHA1, ERROR) \
+ FUNC(BAD_PACKED_REF_HEADER, ERROR) \
FUNC(BAD_PARENT_SHA1, ERROR) \
FUNC(BAD_REF_CONTENT, ERROR) \
FUNC(BAD_REF_FILETYPE, ERROR) \
@@ -53,6 +54,7 @@ enum fsck_msg_type {
FUNC(MISSING_TYPE, ERROR) \
FUNC(MISSING_TYPE_ENTRY, ERROR) \
FUNC(MULTIPLE_AUTHORS, ERROR) \
+ FUNC(PACKED_REF_ENTRY_NOT_TERMINATED, ERROR) \
FUNC(TREE_NOT_SORTED, ERROR) \
FUNC(UNKNOWN_TYPE, ERROR) \
FUNC(ZERO_PADDED_DATE, ERROR) \
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index eaa8746f3e..07154bccae 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1749,12 +1749,76 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
return empty_ref_iterator_begin();
}
+static int packed_fsck_ref_next_line(struct fsck_options *o,
+ unsigned long line_number, const char *start,
+ const char *eof, const char **eol)
+{
+ int ret = 0;
+
+ *eol = memchr(start, '\n', eof - start);
+ if (!*eol) {
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_PACKED_REF_ENTRY_NOT_TERMINATED,
+ "'%.*s' is not terminated with a newline",
+ (int)(eof - start), start);
+
+ /*
+ * There is no newline but we still want to parse it to the end of
+ * the buffer.
+ */
+ *eol = eof;
+ strbuf_release(&packed_entry);
+ }
+
+ return ret;
+}
+
+static int packed_fsck_ref_header(struct fsck_options *o,
+ const char *start, const char *eol)
+{
+ if (!starts_with(start, "# pack-refs with: ")) {
+ struct fsck_ref_report report = { 0 };
+ report.path = "packed-refs.header";
+
+ return fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_HEADER,
+ "'%.*s' does not start with '# pack-refs with: '",
+ (int)(eol - start), start);
+ }
+
+ return 0;
+}
+
+static int packed_fsck_ref_content(struct fsck_options *o,
+ const char *start, const char *eof)
+{
+ unsigned long line_number = 1;
+ const char *eol;
+ int ret = 0;
+
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ if (*start == '#') {
+ ret |= packed_fsck_ref_header(o, start, eol);
+
+ start = eol + 1;
+ line_number++;
+ }
+
+ return ret;
+}
+
static int packed_fsck(struct ref_store *ref_store,
struct fsck_options *o,
struct worktree *wt)
{
struct packed_ref_store *refs = packed_downcast(ref_store,
REF_STORE_READ, "fsck");
+ struct strbuf packed_ref_content = STRBUF_INIT;
struct stat st;
int ret = 0;
int fd = -1;
@@ -1797,9 +1861,18 @@ static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
+ if (strbuf_read(&packed_ref_content, fd, 0) < 0) {
+ ret = error_errno(_("unable to read '%s'"), refs->path);
+ goto cleanup;
+ }
+
+ ret = packed_fsck_ref_content(o, packed_ref_content.buf,
+ packed_ref_content.buf + packed_ref_content.len);
+
cleanup:
if (fd >= 0)
close(fd);
+ strbuf_release(&packed_ref_content);
return ret;
}
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index 68b7d4999e..74d876984d 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -647,4 +647,56 @@ test_expect_success SYMLINKS 'the filetype of packed-refs should be checked' '
)
'
+test_expect_success 'packed-refs header should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+
+ git refs verify 2>err &&
+ test_must_be_empty err &&
+
+ for bad_header in "# pack-refs wit: peeled fully-peeled sorted " \
+ "# pack-refs with traits: peeled fully-peeled sorted " \
+ "# pack-refs with a: peeled fully-peeled" \
+ "# pack-refs with:peeled fully-peeled sorted"
+ do
+ printf "%s\n" "$bad_header" >.git/packed-refs &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs.header: badPackedRefHeader: '\''$bad_header'\'' does not start with '\''# pack-refs with: '\''
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err || return 1
+ done
+ )
+'
+
+test_expect_success 'packed-refs missing header should not be reported' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+
+ printf "$(git rev-parse HEAD) refs/heads/main\n" >.git/packed-refs &&
+ git refs verify 2>err &&
+ test_must_be_empty err
+ )
+'
+
+test_expect_success 'packed-refs unknown traits should not be reported' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+
+ printf "# pack-refs with: peeled fully-peeled sorted foo\n" >.git/packed-refs &&
+ git refs verify 2>err &&
+ test_must_be_empty err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v8 6/9] packed-backend: check whether the refname contains NUL characters
2025-02-27 16:03 ` [PATCH v8 0/9] add more ref consistency checks shejialuo
` (4 preceding siblings ...)
2025-02-27 16:06 ` [PATCH v8 5/9] packed-backend: add "packed-refs" header consistency check shejialuo
@ 2025-02-27 16:07 ` shejialuo
2025-02-27 16:07 ` [PATCH v8 7/9] packed-backend: add "packed-refs" entry consistency check shejialuo
` (2 subsequent siblings)
8 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-27 16:07 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
"packed-backend.c::next_record" will use "check_refname_format" to check
the consistency of the refname. If it is not OK, the program will die.
However, it is reported in [1], we cannot catch some corruption. But we
already have the code path and we must miss out something.
We use the following code to get the refname:
strbuf_add(&iter->refname_buf, p, eol - p);
iter->base.refname = iter->refname_buf.buf
In the above code, `p` is the start pointer of the refname and `eol` is
the next newline pointer. We calculate the length of the refname by
subtracting the two pointers. Then we add the memory range between `p`
and `eol` to get the refname.
However, if there are some NUL characters in the memory range between `p`
and `eol`, we will see the refname as a valid ref name as long as the
memory range between `p` and first occurred NUL character is valid.
In order to catch above corruption, create a new function
"refname_contains_nul" by searching the first NUL character. If it is
not at the end of the string, there must be some NUL characters in the
refname.
Use this function in "next_record" function to die the program if
"refname_contains_nul" returns true.
[1] https://lore.kernel.org/git/6cfee0e4-3285-4f18-91ff-d097da9de737@rd10.de/
Reported-by: R. Diez <rdiez-temp3@rd10.de>
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
refs/packed-backend.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 07154bccae..9a90c52f70 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -494,6 +494,21 @@ static void verify_buffer_safe(struct snapshot *snapshot)
last_line, eof - last_line);
}
+/*
+ * When parsing the "packed-refs" file, we will parse it line by line.
+ * Because we know the start pointer of the refname and the next
+ * newline pointer, we could calculate the length of the refname by
+ * subtracting the two pointers. However, there is a corner case where
+ * the refname contains corrupted embedded NUL characters. And
+ * `check_refname_format()` will not catch this when the truncated
+ * refname is still a valid refname. To prevent this, we need to check
+ * whether the refname contains the NUL characters.
+ */
+static int refname_contains_nul(struct strbuf *refname)
+{
+ return !!memchr(refname->buf, '\0', refname->len);
+}
+
#define SMALL_FILE_SIZE (32*1024)
/*
@@ -895,6 +910,9 @@ static int next_record(struct packed_ref_iterator *iter)
strbuf_add(&iter->refname_buf, p, eol - p);
iter->base.refname = iter->refname_buf.buf;
+ if (refname_contains_nul(&iter->refname_buf))
+ die("packed refname contains embedded NULL: %s", iter->base.refname);
+
if (check_refname_format(iter->base.refname, REFNAME_ALLOW_ONELEVEL)) {
if (!refname_is_safe(iter->base.refname))
die("packed refname is dangerous: %s",
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v8 7/9] packed-backend: add "packed-refs" entry consistency check
2025-02-27 16:03 ` [PATCH v8 0/9] add more ref consistency checks shejialuo
` (5 preceding siblings ...)
2025-02-27 16:07 ` [PATCH v8 6/9] packed-backend: check whether the refname contains NUL characters shejialuo
@ 2025-02-27 16:07 ` shejialuo
2025-02-27 16:07 ` [PATCH v8 8/9] packed-backend: check whether the "packed-refs" is sorted shejialuo
2025-02-27 16:07 ` [PATCH v8 9/9] builtin/fsck: add `git refs verify` child process shejialuo
8 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-27 16:07 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
"packed-backend.c::next_record" will parse the ref entry to check the
consistency. This function has already checked the following things:
1. Parse the main line of the ref entry to inspect whether the oid is
not correct. Then, check whether the next character is oid. Then
check the refname.
2. If the next line starts with '^', it would continue to parse the
peeled oid and check whether the last character is '\n'.
As we decide to implement the ref consistency check for "packed-refs",
let's port these two checks and update the test to exercise the code.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/fsck-msgids.adoc | 3 +
fsck.h | 1 +
refs/packed-backend.c | 122 ++++++++++++++++++++++++++++++++-
t/t0602-reffiles-fsck.sh | 44 ++++++++++++
4 files changed, 169 insertions(+), 1 deletion(-)
diff --git a/Documentation/fsck-msgids.adoc b/Documentation/fsck-msgids.adoc
index 11906f90fd..02a7bf0503 100644
--- a/Documentation/fsck-msgids.adoc
+++ b/Documentation/fsck-msgids.adoc
@@ -16,6 +16,9 @@
`badObjectSha1`::
(ERROR) An object has a bad sha1.
+`badPackedRefEntry`::
+ (ERROR) The "packed-refs" file contains an invalid entry.
+
`badPackedRefHeader`::
(ERROR) The "packed-refs" file contains an invalid
header.
diff --git a/fsck.h b/fsck.h
index 67e3c97bc0..14d70f6653 100644
--- a/fsck.h
+++ b/fsck.h
@@ -30,6 +30,7 @@ enum fsck_msg_type {
FUNC(BAD_EMAIL, ERROR) \
FUNC(BAD_NAME, ERROR) \
FUNC(BAD_OBJECT_SHA1, ERROR) \
+ FUNC(BAD_PACKED_REF_ENTRY, ERROR) \
FUNC(BAD_PACKED_REF_HEADER, ERROR) \
FUNC(BAD_PARENT_SHA1, ERROR) \
FUNC(BAD_REF_CONTENT, ERROR) \
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 9a90c52f70..ef20300fd3 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1812,9 +1812,114 @@ static int packed_fsck_ref_header(struct fsck_options *o,
return 0;
}
+static int packed_fsck_ref_peeled_line(struct fsck_options *o,
+ struct ref_store *ref_store,
+ unsigned long line_number,
+ const char *start, const char *eol)
+{
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct object_id peeled;
+ const char *p;
+ int ret = 0;
+
+ /*
+ * Skip the '^' and parse the peeled oid.
+ */
+ start++;
+ if (parse_oid_hex_algop(start, &peeled, &p, ref_store->repo->hash_algo)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "'%.*s' has invalid peeled oid",
+ (int)(eol - start), start);
+ goto cleanup;
+ }
+
+ if (p != eol) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "has trailing garbage after peeled oid '%.*s'",
+ (int)(eol - p), p);
+ goto cleanup;
+ }
+
+cleanup:
+ strbuf_release(&packed_entry);
+ return ret;
+}
+
+static int packed_fsck_ref_main_line(struct fsck_options *o,
+ struct ref_store *ref_store,
+ unsigned long line_number,
+ struct strbuf *refname,
+ const char *start, const char *eol)
+{
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct object_id oid;
+ const char *p;
+ int ret = 0;
+
+ if (parse_oid_hex_algop(start, &oid, &p, ref_store->repo->hash_algo)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "'%.*s' has invalid oid",
+ (int)(eol - start), start);
+ goto cleanup;
+ }
+
+ if (p == eol || !isspace(*p)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "has no space after oid '%s' but with '%.*s'",
+ oid_to_hex(&oid), (int)(eol - p), p);
+ goto cleanup;
+ }
+
+ p++;
+ strbuf_reset(refname);
+ strbuf_add(refname, p, eol - p);
+ if (refname_contains_nul(refname)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_ENTRY,
+ "refname '%s' contains NULL binaries",
+ refname->buf);
+ }
+
+ if (check_refname_format(refname->buf, 0)) {
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_NAME,
+ "has bad refname '%s'", refname->buf);
+ }
+
+cleanup:
+ strbuf_release(&packed_entry);
+ return ret;
+}
+
static int packed_fsck_ref_content(struct fsck_options *o,
+ struct ref_store *ref_store,
const char *start, const char *eof)
{
+ struct strbuf refname = STRBUF_INIT;
unsigned long line_number = 1;
const char *eol;
int ret = 0;
@@ -1827,6 +1932,21 @@ static int packed_fsck_ref_content(struct fsck_options *o,
line_number++;
}
+ while (start < eof) {
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ ret |= packed_fsck_ref_main_line(o, ref_store, line_number, &refname, start, eol);
+ start = eol + 1;
+ line_number++;
+ if (start < eof && *start == '^') {
+ ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
+ ret |= packed_fsck_ref_peeled_line(o, ref_store, line_number,
+ start, eol);
+ start = eol + 1;
+ line_number++;
+ }
+ }
+
+ strbuf_release(&refname);
return ret;
}
@@ -1884,7 +2004,7 @@ static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
- ret = packed_fsck_ref_content(o, packed_ref_content.buf,
+ ret = packed_fsck_ref_content(o, ref_store, packed_ref_content.buf,
packed_ref_content.buf + packed_ref_content.len);
cleanup:
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index 74d876984d..a88c792ce1 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -699,4 +699,48 @@ test_expect_success 'packed-refs unknown traits should not be reported' '
)
'
+test_expect_success 'packed-refs content should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git tag -a annotated-tag-1 -m tag-1 &&
+ git tag -a annotated-tag-2 -m tag-2 &&
+
+ branch_1_oid=$(git rev-parse branch-1) &&
+ branch_2_oid=$(git rev-parse branch-2) &&
+ tag_1_oid=$(git rev-parse annotated-tag-1) &&
+ tag_2_oid=$(git rev-parse annotated-tag-2) &&
+ tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
+ tag_2_peeled_oid=$(git rev-parse annotated-tag-2^{}) &&
+ short_oid=$(printf "%s" $tag_1_peeled_oid | cut -c 1-4) &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ $short_oid refs/heads/branch-1
+ ${branch_1_oid}x
+ $branch_2_oid refs/heads/bad-branch
+ $branch_2_oid refs/heads/branch.
+ $tag_1_oid refs/tags/annotated-tag-3
+ ^$short_oid
+ $tag_2_oid refs/tags/annotated-tag-4.
+ ^$tag_2_peeled_oid garbage
+ EOF
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs line 2: badPackedRefEntry: '\''$short_oid refs/heads/branch-1'\'' has invalid oid
+ error: packed-refs line 3: badPackedRefEntry: has no space after oid '\''$branch_1_oid'\'' but with '\''x'\''
+ error: packed-refs line 4: badRefName: has bad refname '\'' refs/heads/bad-branch'\''
+ error: packed-refs line 5: badRefName: has bad refname '\''refs/heads/branch.'\''
+ error: packed-refs line 7: badPackedRefEntry: '\''$short_oid'\'' has invalid peeled oid
+ error: packed-refs line 8: badRefName: has bad refname '\''refs/tags/annotated-tag-4.'\''
+ error: packed-refs line 9: badPackedRefEntry: has trailing garbage after peeled oid '\'' garbage'\''
+ EOF
+ test_cmp expect err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v8 8/9] packed-backend: check whether the "packed-refs" is sorted
2025-02-27 16:03 ` [PATCH v8 0/9] add more ref consistency checks shejialuo
` (6 preceding siblings ...)
2025-02-27 16:07 ` [PATCH v8 7/9] packed-backend: add "packed-refs" entry consistency check shejialuo
@ 2025-02-27 16:07 ` shejialuo
2025-02-27 16:07 ` [PATCH v8 9/9] builtin/fsck: add `git refs verify` child process shejialuo
8 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-27 16:07 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
When there is a "sorted" trait in the header of the "packed-refs" file,
it means that each entry is sorted increasingly by comparing the
refname. We should add checks to verify whether the "packed-refs" is
sorted in this case.
Update the "packed_fsck_ref_header" to know whether there is a "sorted"
trail in the header. It may seem that we could record all refnames
during the parsing process and then compare later. However, this is not
a good design due to the following reasons:
1. Because we need to store the state across the whole checking
lifetime, we would consume a lot of memory if there are many entries
in the "packed-refs" file.
2. We cannot reuse the existing compare function "cmp_packed_ref_records"
which cause repetition.
Because "cmp_packed_ref_records" needs an extra parameter "struct
snaphost", extract the common part into a new function
"cmp_packed_ref_records" to reuse this function to compare.
Then, create a new function "packed_fsck_ref_sorted" to parse the file
again and user the new fsck message "packedRefUnsorted(ERROR)" to report
to the user if the file is not sorted.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/fsck-msgids.adoc | 3 +
fsck.h | 1 +
refs/packed-backend.c | 116 ++++++++++++++++++++++++++++-----
t/t0602-reffiles-fsck.sh | 87 +++++++++++++++++++++++++
4 files changed, 191 insertions(+), 16 deletions(-)
diff --git a/Documentation/fsck-msgids.adoc b/Documentation/fsck-msgids.adoc
index 02a7bf0503..9601fff228 100644
--- a/Documentation/fsck-msgids.adoc
+++ b/Documentation/fsck-msgids.adoc
@@ -187,6 +187,9 @@
(ERROR) The "packed-refs" file contains an entry that is
not terminated by a newline.
+`packedRefUnsorted`::
+ (ERROR) The "packed-refs" file is not sorted.
+
`refMissingNewline`::
(INFO) A loose ref that does not end with newline(LF). As
valid implementations of Git never created such a loose ref
diff --git a/fsck.h b/fsck.h
index 14d70f6653..19f3cb2773 100644
--- a/fsck.h
+++ b/fsck.h
@@ -56,6 +56,7 @@ enum fsck_msg_type {
FUNC(MISSING_TYPE_ENTRY, ERROR) \
FUNC(MULTIPLE_AUTHORS, ERROR) \
FUNC(PACKED_REF_ENTRY_NOT_TERMINATED, ERROR) \
+ FUNC(PACKED_REF_UNSORTED, ERROR) \
FUNC(TREE_NOT_SORTED, ERROR) \
FUNC(UNKNOWN_TYPE, ERROR) \
FUNC(ZERO_PADDED_DATE, ERROR) \
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index ef20300fd3..813e5020e4 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -300,14 +300,9 @@ struct snapshot_record {
size_t len;
};
-static int cmp_packed_ref_records(const void *v1, const void *v2,
- void *cb_data)
-{
- const struct snapshot *snapshot = cb_data;
- const struct snapshot_record *e1 = v1, *e2 = v2;
- const char *r1 = e1->start + snapshot_hexsz(snapshot) + 1;
- const char *r2 = e2->start + snapshot_hexsz(snapshot) + 1;
+static int cmp_packed_refname(const char *r1, const char *r2)
+{
while (1) {
if (*r1 == '\n')
return *r2 == '\n' ? 0 : -1;
@@ -322,6 +317,17 @@ static int cmp_packed_ref_records(const void *v1, const void *v2,
}
}
+static int cmp_packed_ref_records(const void *v1, const void *v2,
+ void *cb_data)
+{
+ const struct snapshot *snapshot = cb_data;
+ const struct snapshot_record *e1 = v1, *e2 = v2;
+ const char *r1 = e1->start + snapshot_hexsz(snapshot) + 1;
+ const char *r2 = e2->start + snapshot_hexsz(snapshot) + 1;
+
+ return cmp_packed_refname(r1, r2);
+}
+
/*
* Compare a snapshot record at `rec` to the specified NUL-terminated
* refname.
@@ -1797,19 +1803,33 @@ static int packed_fsck_ref_next_line(struct fsck_options *o,
}
static int packed_fsck_ref_header(struct fsck_options *o,
- const char *start, const char *eol)
+ const char *start, const char *eol,
+ unsigned int *sorted)
{
- if (!starts_with(start, "# pack-refs with: ")) {
+ struct string_list traits = STRING_LIST_INIT_NODUP;
+ char *tmp_line;
+ int ret = 0;
+ char *p;
+
+ tmp_line = xmemdupz(start, eol - start);
+ if (!skip_prefix(tmp_line, "# pack-refs with: ", (const char **)&p)) {
struct fsck_ref_report report = { 0 };
report.path = "packed-refs.header";
- return fsck_report_ref(o, &report,
- FSCK_MSG_BAD_PACKED_REF_HEADER,
- "'%.*s' does not start with '# pack-refs with: '",
- (int)(eol - start), start);
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_PACKED_REF_HEADER,
+ "'%.*s' does not start with '# pack-refs with: '",
+ (int)(eol - start), start);
+ goto cleanup;
}
- return 0;
+ string_list_split_in_place(&traits, p, " ", -1);
+ *sorted = unsorted_string_list_has_string(&traits, "sorted");
+
+cleanup:
+ free(tmp_line);
+ string_list_clear(&traits, 0);
+ return ret;
}
static int packed_fsck_ref_peeled_line(struct fsck_options *o,
@@ -1915,8 +1935,68 @@ static int packed_fsck_ref_main_line(struct fsck_options *o,
return ret;
}
+static int packed_fsck_ref_sorted(struct fsck_options *o,
+ struct ref_store *ref_store,
+ const char *start, const char *eof)
+{
+ size_t hexsz = ref_store->repo->hash_algo->hexsz;
+ struct strbuf packed_entry = STRBUF_INIT;
+ struct fsck_ref_report report = { 0 };
+ struct strbuf refname1 = STRBUF_INIT;
+ struct strbuf refname2 = STRBUF_INIT;
+ unsigned long line_number = 1;
+ const char *former = NULL;
+ const char *current;
+ const char *eol;
+ int ret = 0;
+
+ if (*start == '#') {
+ eol = memchr(start, '\n', eof - start);
+ start = eol + 1;
+ line_number++;
+ }
+
+ for (; start < eof; line_number++, start = eol + 1) {
+ eol = memchr(start, '\n', eof - start);
+
+ if (*start == '^')
+ continue;
+
+ if (!former) {
+ former = start + hexsz + 1;
+ continue;
+ }
+
+ current = start + hexsz + 1;
+ if (cmp_packed_refname(former, current) >= 0) {
+ const char *err_fmt =
+ "refname '%s' is less than previous refname '%s'";
+
+ eol = memchr(former, '\n', eof - former);
+ strbuf_add(&refname1, former, eol - former);
+ eol = memchr(current, '\n', eof - current);
+ strbuf_add(&refname2, current, eol - current);
+
+ strbuf_addf(&packed_entry, "packed-refs line %lu", line_number);
+ report.path = packed_entry.buf;
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_PACKED_REF_UNSORTED,
+ err_fmt, refname2.buf, refname1.buf);
+ goto cleanup;
+ }
+ former = current;
+ }
+
+cleanup:
+ strbuf_release(&packed_entry);
+ strbuf_release(&refname1);
+ strbuf_release(&refname2);
+ return ret;
+}
+
static int packed_fsck_ref_content(struct fsck_options *o,
struct ref_store *ref_store,
+ unsigned int *sorted,
const char *start, const char *eof)
{
struct strbuf refname = STRBUF_INIT;
@@ -1926,7 +2006,7 @@ static int packed_fsck_ref_content(struct fsck_options *o,
ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol);
if (*start == '#') {
- ret |= packed_fsck_ref_header(o, start, eol);
+ ret |= packed_fsck_ref_header(o, start, eol, sorted);
start = eol + 1;
line_number++;
@@ -1957,6 +2037,7 @@ static int packed_fsck(struct ref_store *ref_store,
struct packed_ref_store *refs = packed_downcast(ref_store,
REF_STORE_READ, "fsck");
struct strbuf packed_ref_content = STRBUF_INIT;
+ unsigned int sorted = 0;
struct stat st;
int ret = 0;
int fd = -1;
@@ -2004,8 +2085,11 @@ static int packed_fsck(struct ref_store *ref_store,
goto cleanup;
}
- ret = packed_fsck_ref_content(o, ref_store, packed_ref_content.buf,
+ ret = packed_fsck_ref_content(o, ref_store, &sorted, packed_ref_content.buf,
packed_ref_content.buf + packed_ref_content.len);
+ if (!ret && sorted)
+ ret = packed_fsck_ref_sorted(o, ref_store, packed_ref_content.buf,
+ packed_ref_content.buf + packed_ref_content.len);
cleanup:
if (fd >= 0)
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index a88c792ce1..767e2bd4a0 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -743,4 +743,91 @@ test_expect_success 'packed-refs content should be checked' '
)
'
+test_expect_success 'packed-ref with sorted trait should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git tag -a annotated-tag-1 -m tag-1 &&
+ branch_1_oid=$(git rev-parse branch-1) &&
+ branch_2_oid=$(git rev-parse branch-2) &&
+ tag_1_oid=$(git rev-parse annotated-tag-1) &&
+ tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
+ refname1="refs/heads/main" &&
+ refname2="refs/heads/foo" &&
+ refname3="refs/tags/foo" &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ EOF
+ git refs verify 2>err &&
+ rm .git/packed-refs &&
+ test_must_be_empty err &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ $branch_2_oid $refname1
+ EOF
+ git refs verify 2>err &&
+ rm .git/packed-refs &&
+ test_must_be_empty err &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ $branch_2_oid $refname1
+ $branch_1_oid $refname2
+ $tag_1_oid $refname3
+ EOF
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs line 3: packedRefUnsorted: refname '\''$refname2'\'' is less than previous refname '\''$refname1'\''
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled sorted
+ $tag_1_oid $refname3
+ ^$tag_1_peeled_oid
+ $branch_2_oid $refname2
+ EOF
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: packed-refs line 4: packedRefUnsorted: refname '\''$refname2'\'' is less than previous refname '\''$refname3'\''
+ EOF
+ rm .git/packed-refs &&
+ test_cmp expect err
+ )
+'
+
+test_expect_success 'packed-ref without sorted trait should not be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit default &&
+ git branch branch-1 &&
+ git branch branch-2 &&
+ git tag -a annotated-tag-1 -m tag-1 &&
+ branch_1_oid=$(git rev-parse branch-1) &&
+ branch_2_oid=$(git rev-parse branch-2) &&
+ tag_1_oid=$(git rev-parse annotated-tag-1) &&
+ tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) &&
+ refname1="refs/heads/main" &&
+ refname2="refs/heads/foo" &&
+ refname3="refs/tags/foo" &&
+
+ cat >.git/packed-refs <<-EOF &&
+ # pack-refs with: peeled fully-peeled
+ $branch_2_oid $refname1
+ $branch_1_oid $refname2
+ EOF
+ git refs verify 2>err &&
+ test_must_be_empty err
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread
* [PATCH v8 9/9] builtin/fsck: add `git refs verify` child process
2025-02-27 16:03 ` [PATCH v8 0/9] add more ref consistency checks shejialuo
` (7 preceding siblings ...)
2025-02-27 16:07 ` [PATCH v8 8/9] packed-backend: check whether the "packed-refs" is sorted shejialuo
@ 2025-02-27 16:07 ` shejialuo
8 siblings, 0 replies; 168+ messages in thread
From: shejialuo @ 2025-02-27 16:07 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Karthik Nayak, Junio C Hamano,
Michael Haggerty
At now, we have already implemented the ref consistency checks for both
"files-backend" and "packed-backend". Although we would check some
redundant things, it won't cause trouble. So, let's integrate it into
the "git-fsck(1)" command to get feedback from the users. And also by
calling "git refs verify" in "git-fsck(1)", we make sure that the new
added checks don't break.
Introduce a new function "fsck_refs" that initializes and runs a child
process to execute the "git refs verify" command. In order to provide
the user interface create a progress which makes the total task be 1.
It's hard to know how many loose refs we will check now. We might
improve this later.
Then, introduce the option to allow the user to disable checking ref
database consistency. Put this function in the very first execution
sequence of "git-fsck(1)" due to that we don't want the existing code of
"git-fsck(1)" which would implicitly check the consistency of refs to
die the program.
Last, update the test to exercise the code.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
Documentation/git-fsck.adoc | 7 ++++++-
builtin/fsck.c | 33 ++++++++++++++++++++++++++++++-
t/t0602-reffiles-fsck.sh | 39 +++++++++++++++++++++++++++++++++++++
3 files changed, 77 insertions(+), 2 deletions(-)
diff --git a/Documentation/git-fsck.adoc b/Documentation/git-fsck.adoc
index 8f32800a83..11203ba925 100644
--- a/Documentation/git-fsck.adoc
+++ b/Documentation/git-fsck.adoc
@@ -12,7 +12,7 @@ SYNOPSIS
'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]
[--[no-]full] [--strict] [--verbose] [--lost-found]
[--[no-]dangling] [--[no-]progress] [--connectivity-only]
- [--[no-]name-objects] [<object>...]
+ [--[no-]name-objects] [--[no-]references] [<object>...]
DESCRIPTION
-----------
@@ -104,6 +104,11 @@ care about this output and want to speed it up further.
progress status even if the standard error stream is not
directed to a terminal.
+--[no-]references::
+ Control whether to check the references database consistency
+ via 'git refs verify'. See linkgit:git-refs[1] for details.
+ The default is to check the references database.
+
CONFIGURATION
-------------
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 7a4dcb0716..f4f395cfbd 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -50,6 +50,7 @@ static int verbose;
static int show_progress = -1;
static int show_dangling = 1;
static int name_objects;
+static int check_references = 1;
#define ERROR_OBJECT 01
#define ERROR_REACHABLE 02
#define ERROR_PACK 04
@@ -905,11 +906,37 @@ static int check_pack_rev_indexes(struct repository *r, int show_progress)
return res;
}
+static void fsck_refs(struct repository *r)
+{
+ struct child_process refs_verify = CHILD_PROCESS_INIT;
+ struct progress *progress = NULL;
+
+ if (show_progress)
+ progress = start_progress(r, _("Checking ref database"), 1);
+
+ if (verbose)
+ fprintf_ln(stderr, _("Checking ref database"));
+
+ child_process_init(&refs_verify);
+ refs_verify.git_cmd = 1;
+ strvec_pushl(&refs_verify.args, "refs", "verify", NULL);
+ if (verbose)
+ strvec_push(&refs_verify.args, "--verbose");
+ if (check_strict)
+ strvec_push(&refs_verify.args, "--strict");
+
+ if (run_command(&refs_verify))
+ errors_found |= ERROR_REFS;
+
+ display_progress(progress, 1);
+ stop_progress(&progress);
+}
+
static char const * const fsck_usage[] = {
N_("git fsck [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]\n"
" [--[no-]full] [--strict] [--verbose] [--lost-found]\n"
" [--[no-]dangling] [--[no-]progress] [--connectivity-only]\n"
- " [--[no-]name-objects] [<object>...]"),
+ " [--[no-]name-objects] [--[no-]references] [<object>...]"),
NULL
};
@@ -928,6 +955,7 @@ static struct option fsck_opts[] = {
N_("write dangling objects in .git/lost-found")),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
OPT_BOOL(0, "name-objects", &name_objects, N_("show verbose names for reachable objects")),
+ OPT_BOOL(0, "references", &check_references, N_("check reference database consistency")),
OPT_END(),
};
@@ -970,6 +998,9 @@ int cmd_fsck(int argc,
git_config(git_fsck_config, &fsck_obj_options);
prepare_repo_settings(the_repository);
+ if (check_references)
+ fsck_refs(the_repository);
+
if (connectivity_only) {
for_each_loose_object(mark_loose_for_connectivity, NULL, 0);
for_each_packed_object(the_repository,
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index 767e2bd4a0..9d1dc2144c 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -830,4 +830,43 @@ test_expect_success 'packed-ref without sorted trait should not be checked' '
)
'
+test_expect_success '--[no-]references option should apply to fsck' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ branch_dir_prefix=.git/refs/heads &&
+ (
+ cd repo &&
+ test_commit default &&
+ for trailing_content in " garbage" " more garbage"
+ do
+ printf "%s" "$(git rev-parse HEAD)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ git fsck 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage &&
+ test_cmp expect err || return 1
+ done &&
+
+ for trailing_content in " garbage" " more garbage"
+ do
+ printf "%s" "$(git rev-parse HEAD)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ git fsck --references 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\''
+ EOF
+ rm $branch_dir_prefix/branch-garbage &&
+ test_cmp expect err || return 1
+ done &&
+
+ for trailing_content in " garbage" " more garbage"
+ do
+ printf "%s" "$(git rev-parse HEAD)$trailing_content" >$branch_dir_prefix/branch-garbage &&
+ git fsck --no-references 2>err &&
+ rm $branch_dir_prefix/branch-garbage &&
+ test_must_be_empty err || return 1
+ done
+ )
+'
+
test_done
--
2.48.1
^ permalink raw reply related [flat|nested] 168+ messages in thread