Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/pwd.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@ function resolveGitDir(dotgit, dir) {
return null;
}

// A linked worktree's git dir (.git/worktrees/<name>) 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.
Expand Down Expand Up @@ -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)),
},
};
}
Expand Down
52 changes: 51 additions & 1 deletion test/pwd.test.mjs
Original file line number Diff line number Diff line change
@@ -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");
Expand All @@ -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/<name>, 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 });
}
});
Loading