Operator precedence bug in git_repository_foreach_worktree
Affected versions: libgit2 <= 1.9.2 (at least; may be present in other versions)
File: src/libgit2/repository.c, function git_repository_foreach_worktree
The bug
Line 3163 has a missing pair of parentheses:
// BUGGY: '<' has higher precedence than '=', so error is assigned 0 or 1
if ((error = git_worktree_lookup(&worktree, repo, worktrees.strings[i]) < 0) ||
(error = git_repository_open_from_worktree(&worktree_repo, worktree)) < 0) {
if (error != GIT_ENOTFOUND)
goto out;
error = 0;
continue;
}
Because < binds tighter than =, the first operand is parsed as:
(error = (git_worktree_lookup(...) < 0))
instead of the intended:
((error = git_worktree_lookup(...)) < 0)
When git_worktree_lookup fails (returns any negative value), the comparison negative < 0 evaluates to 1, so error is assigned 1 — not the actual error code. The recovery check if (error != GIT_ENOTFOUND) then compares 1 != -3, which is always true, so execution jumps to goto out with error = 1.
Note: the second operand (git_repository_open_from_worktree) is correctly parenthesized and does not have this issue.
There is also a similar bug on line 3148 where:
(error = cb(worktree_repo, payload) != 0)
assigns the boolean result of != 0 rather than the callback’s return value, which has similar consequences when the callback returns a negative error code.
Impact
git_repository_foreach_worktree is used by git_branch_is_checked_out, which returns:
return git_repository_foreach_worktree(..., branch_is_checked_out, ...) == 1;
When the bug causes foreach_worktree to return 1, git_branch_is_checked_out reports the branch as checked out — even if no worktree actually has it checked out. This is a false positive.
This directly breaks git_worktree_add. When called without an explicit ref, git_worktree_add creates a new branch and then calls git_branch_is_checked_out on that just-created branch. The false positive causes it to fail with:
reference refs/heads/<name> is already checked out
git_branch_delete is also affected, as it calls git_branch_is_checked_out and refuses to delete branches it believes are checked out.
Trigger condition
Any stale or corrupted worktree entry in .git/worktrees/ that causes git_worktree_lookup to fail with a non-GIT_ENOTFOUND error. This can happen when:
- a worktree directory was removed but metadata in
.git/worktrees/<name>/ was not pruned
- metadata files (
commondir, gitdir, HEAD) exist but contain invalid/empty content
- any condition where the entry passes
is_worktree_dir checks (used by git_worktree_list) but fails during full parse in open_worktree_dir (used by git_worktree_lookup)
Fix
The correct code should assign return values before comparison:
if (((error = git_worktree_lookup(&worktree, repo, worktrees.strings[i])) < 0) ||
((error = git_repository_open_from_worktree(&worktree_repo, worktree)) < 0)) {
And:
if ((error = git_repository_open(&worktree_repo, repo->commondir)) < 0 ||
((error = cb(worktree_repo, payload)) != 0))
Operator precedence bug in
git_repository_foreach_worktreeAffected versions: libgit2 <= 1.9.2 (at least; may be present in other versions)
File:
src/libgit2/repository.c, functiongit_repository_foreach_worktreeThe bug
Line 3163 has a missing pair of parentheses:
Because
<binds tighter than=, the first operand is parsed as:instead of the intended:
When
git_worktree_lookupfails (returns any negative value), the comparisonnegative < 0evaluates to1, soerroris assigned1— not the actual error code. The recovery checkif (error != GIT_ENOTFOUND)then compares1 != -3, which is always true, so execution jumps togoto outwitherror = 1.Note: the second operand (
git_repository_open_from_worktree) is correctly parenthesized and does not have this issue.There is also a similar bug on line 3148 where:
assigns the boolean result of
!= 0rather than the callback’s return value, which has similar consequences when the callback returns a negative error code.Impact
git_repository_foreach_worktreeis used bygit_branch_is_checked_out, which returns:When the bug causes
foreach_worktreeto return1,git_branch_is_checked_outreports the branch as checked out — even if no worktree actually has it checked out. This is a false positive.This directly breaks
git_worktree_add. When called without an explicit ref,git_worktree_addcreates a new branch and then callsgit_branch_is_checked_outon that just-created branch. The false positive causes it to fail with:reference refs/heads/<name> is already checked outgit_branch_deleteis also affected, as it callsgit_branch_is_checked_outand refuses to delete branches it believes are checked out.Trigger condition
Any stale or corrupted worktree entry in
.git/worktrees/that causesgit_worktree_lookupto fail with a non-GIT_ENOTFOUNDerror. This can happen when:.git/worktrees/<name>/was not prunedcommondir,gitdir,HEAD) exist but contain invalid/empty contentis_worktree_dirchecks (used bygit_worktree_list) but fails during full parse inopen_worktree_dir(used bygit_worktree_lookup)Fix
The correct code should assign return values before comparison:
And: