From d52bc507539af29ff56ff6e900c106d8f3b8475e Mon Sep 17 00:00:00 2001 From: clawedassistant26 <307253840+clawedassistant26@users.noreply.github.com> Date: Mon, 27 Jul 2026 06:19:08 +0000 Subject: [PATCH] fix(pwd): find origin from inside a linked git worktree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A linked worktree's git dir (.git/worktrees/) carries only that worktree's own HEAD and index. The shared config, and therefore every remote, stays in the main repo's git dir, which git names in a `commondir` file. readOrigin() was pointed at the worktree git dir, so it read no config at all and `moshcode pwd` / `/pwd` silently dropped the `origin:` line — the same repo shows it from the main checkout. Resolve commondir (relative or absolute) before reading config. HEAD is still read from the worktree's own git dir, because the branch really is per-worktree. Plain repos and submodules have no commondir file, so they take the unchanged path. --- src/pwd.mjs | 17 +++++++++++++++- test/pwd.test.mjs | 52 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/pwd.mjs b/src/pwd.mjs index 17cc68a..7cbb3b0 100644 --- a/src/pwd.mjs +++ b/src/pwd.mjs @@ -19,6 +19,20 @@ function resolveGitDir(dotgit, dir) { return null; } +// A linked worktree's git dir (.git/worktrees/) holds only that +// worktree's own HEAD and index — the shared config, and therefore the +// remotes, stay in the main repo's git dir. Git names it in a `commondir` +// file, usually as a path relative to the worktree's git dir. Resolve it so +// origin is found from inside a worktree; outside one there is no such file +// and the git dir is already the common one. +function commonGitDir(gitDir) { + try { + const pointer = fs.readFileSync(path.join(gitDir, "commondir"), "utf8").trim(); + if (pointer) return path.resolve(gitDir, pointer); + } catch { /* not a linked worktree */ } + return gitDir; +} + // Walk up from `start` until we find a `.git` with a readable HEAD (a real // repo). A bare `.git` dir with no HEAD — e.g. a stray /tmp/.git — is skipped, // not mistaken for a repo. Returns { root, gitDir } or null at the fs root. @@ -74,8 +88,9 @@ export function locate(cwd = process.cwd()) { git: { root: found.root, name: path.basename(found.root), + // HEAD is per-worktree; config (and its remotes) is shared. branch: readBranch(found.gitDir), - origin: readOrigin(found.gitDir), + origin: readOrigin(commonGitDir(found.gitDir)), }, }; } diff --git a/test/pwd.test.mjs b/test/pwd.test.mjs index 65806db..753a462 100644 --- a/test/pwd.test.mjs +++ b/test/pwd.test.mjs @@ -1,8 +1,10 @@ import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; import path from "node:path"; import test from "node:test"; -import { tilde } from "../src/pwd.mjs"; +import { locate, tilde } from "../src/pwd.mjs"; test("tilde shortens the home directory itself", () => { const home = path.join("C:", "Users", "mosh"); @@ -23,3 +25,51 @@ test("tilde does not shorten sibling paths with the same prefix", () => { assert.equal(tilde(sibling, home), sibling); }); + +// Build a main repo plus one linked worktree the way git lays them out on disk, +// without needing git on PATH: the worktree's `.git` is a file pointing at +// .git/worktrees/, which carries its own HEAD and a `commondir` back to +// the main git dir that holds the shared config. +function fakeWorktree(root, { commondir }) { + const mainGit = path.join(root, "main", ".git"); + const linkedGit = path.join(mainGit, "worktrees", "feature"); + fs.mkdirSync(linkedGit, { recursive: true }); + fs.writeFileSync(path.join(mainGit, "HEAD"), "ref: refs/heads/main\n"); + fs.writeFileSync( + path.join(mainGit, "config"), + '[core]\n\tbare = false\n[remote "origin"]\n\turl = https://github.com/moshcoder/moshcode.git\n\tfetch = +refs/heads/*:refs/remotes/origin/*\n', + ); + fs.writeFileSync(path.join(linkedGit, "HEAD"), "ref: refs/heads/feature\n"); + fs.writeFileSync(path.join(linkedGit, "commondir"), `${commondir}\n`); + + const worktree = path.join(root, "feature"); + fs.mkdirSync(worktree, { recursive: true }); + fs.writeFileSync(path.join(worktree, ".git"), `gitdir: ${linkedGit}\n`); + return worktree; +} + +test("locate reports origin from inside a linked worktree", () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "moshcode-worktree-")); + try { + // git writes commondir relative to the worktree's own git dir. + const worktree = fakeWorktree(root, { commondir: path.join("..", "..") }); + const { git } = locate(worktree); + + assert.equal(git.branch, "feature"); + assert.equal(git.origin, "https://github.com/moshcoder/moshcode.git"); + } finally { + fs.rmSync(root, { recursive: true, force: true }); + } +}); + +test("locate follows an absolute commondir too", () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "moshcode-worktree-")); + try { + const worktree = fakeWorktree(root, { commondir: path.join(root, "main", ".git") }); + const { git } = locate(worktree); + + assert.equal(git.origin, "https://github.com/moshcoder/moshcode.git"); + } finally { + fs.rmSync(root, { recursive: true, force: true }); + } +});