Repository navigation
Description
Version
7.8.0.202609011348-r and 7.7.0.202606012155-r (RefDirectory.logFor is unchanged on master)
Operating System
MacOS
Bug description
When a repository is opened on a linked worktree (created with git worktree add), JGit writes the reflog entries for that worktree's HEAD into the main worktree's <common dir>/logs/HEAD, instead of <common dir>/worktrees/<name>/logs/HEAD. Branch reflogs (logs/refs/heads/*) are shared by all worktrees and are written correctly.
To reproduce (git 2.54, JDK 25):
git init -b main repo
git -C repo commit --allow-empty -m init
git -C repo worktree add -b wt ../wttry (Git git = Git.open(new File("wt"))) { // the same with new File("repo/.git/worktrees/wt")
git.commit().setMessage("commit in wt").setAllowEmpty(true).call();
git.checkout().setCreateBranch(true).setName("topic").call();
System.out.println(git.reflog().call().size()); // prints 2: neither of the new entries
}git -C repo reflog # the main worktree, which never moved
git -C wt reflog # the linked worktreeCause: RefDirectory.fileFor resolves HEAD against the worktree's git dir, and ReflogReaderImpl reads HEAD's log from db.getDirectory(). But RefDirectory.logFor resolves every name outside refs/, HEAD included, against logsDir, which the constructor sets to fs.resolve(gitCommonDir, LOGS). ReflogWriter writes through logFor, so JGit writes HEAD's log to one file and reads it from another. This affects every update of HEAD: commit, checkout, reset, merge, rebase, cherry-pick, revert, and renaming the current branch (RefDirectoryRename).
Actual behavior
- The commit and checkout entries are appended to
repo/.git/logs/HEAD, the main worktree's HEAD reflog. - In the main worktree,
git reflog,HEAD@{1}and@{-1}follow the linked worktree's history.@{-1}iswt, sogit -C repo checkout -switches the main worktree towt, a branch it never had checked out. git -C wt reflogdoesn't show the entries, and neither does JGit's ownReflogCommandin the linked worktree.- With a bare repository and a linked worktree, JGit creates
<bare repo>/logs/HEAD.
Expected behavior
As with the git CLI: the entries for the linked worktree's HEAD go to repo/.git/worktrees/wt/logs/HEAD, where ReflogReaderImpl already reads them, and the main worktree's logs/HEAD is unchanged. Running the same commit and checkout with git -C wt ... does exactly that.
Relevant log output
$ git -C repo reflog
a80a6dd HEAD@{0}: checkout: moving from wt to topic
a80a6dd HEAD@{1}: commit: commit in wt
3a829fd HEAD@{2}: commit (initial): init
$ git -C wt reflog
3a829fd HEAD@{0}: reset: moving to HEAD
3a829fd HEAD@{1}:Other information
-
It isn't specific to macOS: it's path resolution in
RefDirectory. Seen on macOS 26 (arm64) with JDK 25. -
A minimal fix could resolve HEAD's log in
logForthe wayfileForresolves HEAD:if (name.equals(HEAD)) { return new File(gitDir, L_LOGS + HEAD); }
More generally, git keeps the logs of all per-worktree refs (HEAD, pseudo-refs such as ORIG_HEAD,
refs/bisect/,refs/worktree/,refs/rewritten/) in the worktree's own git dir, as was noted in the review of the worktree read support (https://eclipse.gerrithub.io/c/eclipse-jgit/jgit/+/1194900). -
Related, seen with 7.7.0:
RebaseCommand.resetSoftToParent(squash or fixup in an interactive rebase) readsORIG_HEADthroughexactRef, whichfileForresolves against the common dir, and writes it back withwriteOrigHead, which uses the worktree's git dir. After a squash in a linked worktree, that worktree'sORIG_HEADholds the main worktree's value. -
Found in Leaf (https://github.com/zzhelev/Leaf), a desktop git client built on JGit. It works around the bug by overriding
FS.resolvefor(<common dir>, "logs")in theFSit opens linked worktrees with.
Activity
Fork docs: link the JGit issue for the linked worktree HEAD reflog