Skip to content

Linked worktree: HEAD reflog entries are written to the main worktree's logs/HEAD #306

Open

Description

@zzhelev

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 ../wt
try (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 worktree

Cause: 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} is wt, so git -C repo checkout - switches the main worktree to wt, a branch it never had checked out.
  • git -C wt reflog doesn't show the entries, and neither does JGit's own ReflogCommand in 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 logFor the way fileFor resolves 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) reads ORIG_HEAD through exactRef, which fileFor resolves against the common dir, and writes it back with writeOrigHead, which uses the worktree's git dir. After a squash in a linked worktree, that worktree's ORIG_HEAD holds 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.resolve for (<common dir>, "logs") in the FS it opens linked worktrees with.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions