Skip to content

Support bare nixpkgs repositories#613

Open
philiptaron wants to merge 6 commits intoMic92:mainfrom
philiptaron:worktree-bare-repo
Open

Support bare nixpkgs repositories#613
philiptaron wants to merge 6 commits intoMic92:mainfrom
philiptaron:worktree-bare-repo

Conversation

@philiptaron
Copy link
Copy Markdown

Supporting bare nixpkgs repositories

The problem

I keep my nixpkgs checkout as a bare repo with worktrees:

~/nixpkgs/
├── .git/              (core.bare = true)
├── master/            (worktree)
├── staging/           (worktree)
├── pr-work/           (worktree)
└── ...

I learned this git workflow for large repos with many workstreams (like nixpkgs)! from @Ericson2314. You clone once, set core.bare = true, and then git worktree add for each branch you're working on. No files are checked out at the root; the worktrees live as sibling
directories alongside .git/. It's pretty awesome.

Today, when I cd ~/nixpkgs and run nixpkgs-review pr 12345, it dies immediately with "Has to be executed from nixpkgs repository". The tool can't tell it's inside nixpkgs because there's no nixos/release.nix on disk at the root level -- it only exists inside the worktrees.

Running from inside a worktree (cd ~/nixpkgs/master && nixpkgs-review pr ...) works fine, but it's annoying to have to pick an arbitrary worktree just to launch a review that creates its own worktree anyway.

What was broken

Two functions assumed a working tree exists:

  1. find_nixpkgs_root() walks up from CWD looking for nixos/release.nix on the filesystem. In a bare repo there are no checked-out files, so it walks all the way to / and gives up.

  2. resolve_git_dir() looks for a .git file or directory. My layout has .git/ as a directory so this one actually works for me, but a "true" bare clone (git clone --bare) has no .git entry at all -- the git database (HEAD, objects, refs) lives directly in the directory. That case would also fail.

What this PR does

find_nixpkgs_root() now falls back to git when the filesystem walk fails. It asks git rev-parse --is-bare-repository whether we're in a bare repo, then checks git cat-file -e HEAD:nixos/release.nix to confirm the repo actually contains nixpkgs (not just any bare repo). If both pass, it returns CWD as the root.

resolve_git_dir() gets a similar fallback: when there's no .git on disk, it tries git rev-parse --git-dir, which works in true bare clones where the git database is the directory itself.

wip is guarded with an early exit in bare repos. The wip command captures the dirty working tree via git diff, but a bare repo has no working tree to diff. Rather than letting it silently produce an empty diff and exit, it now tells you to use pr or rev instead.

The non-bare case is unchanged -- the filesystem walk still runs first, so there's no subprocess overhead for the common case.

Refactoring

Along the way, find_nixpkgs_root, resolve_git_dir, fetch_refs, and friends were extracted from buildenv.py and review.py into a new nixpkgs.py module, since they're all about locating and interacting with the nixpkgs git repo rather than about the build environment or the review workflow.

The test suite also got a session-scoped fixture that sets GIT_CONFIG_GLOBAL and GIT_CONFIG_SYSTEM to /dev/null, so the user's git config (things like commit.gpgsign = true) can't break test commits. Previously -- at least on my system -- this required passing those env vars on the command line every time I ran pytest.

AI-assisted

As you likely can tell, my friend Claude helped with this implementation. I've reviewed it and think it's pretty slop-free, but please let me know your own tastes on the matter.

Detect bare repos (both core.bare=true with .git/ directory and true
bare clones without .git) in find_nixpkgs_root() by falling back to
git cat-file when the filesystem walk finds no nixos/release.nix.

Fix resolve_git_dir() to fall back to git rev-parse --git-dir when
no .git file or directory exists.

Guard the wip command with an early exit in bare repos, since there
is no working tree to diff against.
Move find_nixpkgs_root, is_bare_repository, resolve_git_dir,
fetch_refs, and locked_open out of buildenv.py and review.py into a
dedicated nixpkgs.py module. Rename tests/test_bare.py to
tests/test_nixpkgs.py to match.
Add a session-scoped _isolate_git fixture that sets GIT_CONFIG_GLOBAL
and GIT_CONFIG_SYSTEM to /dev/null, preventing settings like
commit.gpgsign or gpg.format from breaking test commits. Also sets
git identity vars centrally, removing the per-command GIT_ENV dict
from test_nixpkgs.py.
Nothing imports from test modules, so the privacy convention doesn't
apply.  Also hoist the shutil import to the top of the file.
@philiptaron philiptaron reopened this Mar 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant