A CLI tool to access the history of Nixpkgs channels.
The unstable Nixpkgs channels are an amazing way to get reproducible up-to-date versions of most software. However, things do break sometimes. Let's say you open a project you haven't touched in a couple months that uses the nixpkgs-unstable channel, and you run nix flake update. Something breaks. What do you do?
Without npc, you could either
- come up with a simple proxy for what broke in your actual project, manually git bisect the Nixpkgs repo using the --first-parent flag, query the Nix releases S3 bucket to see what commits nixpkgs-unstable has actually pointed to in the past, bisect again to find the pair between which that earlier commit from git bisect fell, try that in your project, and repeat if your proxy check was insufficient; or,
- revert your channel bump and keep using the months-old commit from before.
(Usually the second one.)
With npc, you can simply npc bisect directly in your own project to find what was the last time nixpkgs-unstable pointed to a commit that works for you, trying only commits to which that branch has pointed at some point in the past, and skipping everything else. Then commit the change to flake.lock, and continue on your way.
This repository provides a Nix flake, so if you have flakes enabled, the quickest way to use it is via the nix shell command:
This starts a shell within your shell which has npc available on the PATH.
If you use Home Manager and would like to do a more global installation, you can add npc to your home.nix like this:
This is assuming you configure Home Manager via a flake, in which case you'd pass npc to home.nix via extraSpecialArgs:
flake.nixnpc provides several subcommands. To view them all, use the --help flag:
You can also use --help on a specific subcommand to view more details about it:
The subcommand names are all borrowed from similar subcommands in Git, so hopefully this CLI as a whole should feel familiar.
This is the first command you need to run before you can do anything with npc. If you haven't run it before, it first clones the entire Nixpkgs repository, which can take a while and will use several gigabytes of disk space.
All the other subcommands use this cache and don't try to update it themselves; for instance, if they see a commit that's not in the cache, they'll just give an error. You can always run npc fetch again to update the cache, which takes less time once you've already done it at least once: in my experience, usually it takes about a minute.
The cache shouldn't end up in a broken state, but if it ever does, you can use this command to delete the whole thing. It was probably a bug in npc, so please let me know!
Alternatively, if you've decided you don't want to use npc anymore and want to free up those several gigabytes, this command works for that too.
This command tells you when each Nixpkgs branch was last updated, according to whenever you last ran npc fetch:
This command prints the history of a Nixpkgs branch:
If you don't pass -n/--max-count then there will be too many commits to fit on one screen, so the Git pager will be used to let you scroll through the list of commits, search for specific commits or dates, etc.
If you are in a directory that has a flake.lock file, you don't need to specify the branch name explicitly; npc will determine it automatically:
There are a couple caveats to this, though:
-
Currently npc only looks for flake inputs that look like github:NixOS/nixpkgs optionally followed by some branch name, and ignores other possible ways of specifying Nixpkgs. If your flake refers to the Nixpkgs repo in a different way that you'd like npc to support, please let me know!
-
If your flake.lock file has multiple independent versions of Nixpkgs, even if they happen to currently point to the same commit, npc will not automatically choose one; you'll need to explicitly choose one yourself via the --input flag:
-
The log subcommand always just prints all the commits that a Nixpkgs branch has ever pointed to according to the npc cache, modulo the -n/--max-count argument. That is, it may even show commits newer than the commit you currently have in your flake.lock.
This command runs nix flake update with --override-input to modify your flake.lock file:
Just like the log subcommand, checkout attempts to use your flake.lock to automatically infer the name of the flake input to modify. Similarly, if there are multiple instances of Nixpkgs in flake.lock then it will ask you to explicitly specify one.
Other than saving you some typing, the primary difference between this and just running nix flake update yourself is that it checks whether the commit you give it is actually a commit that has been a tip of your Nixpkgs branch at some point in the past. If not, that's an error. The goal of this is to maintain consistency and reduce surprises: if your flake.nix says you're using the nixos-unstable branch, it'd be weird for your flake.lock to list a commit that has never been the tip of that branch.
This is like git bisect, except instead finding the commit in your repository that introduced a bug, it finds the most recent Nixpkgs commit that doesn't have the bug but was at some point the tip of a given branch.
The easiest way to use this is in a flake where it can automatically do the equivalent of npc checkout while narrowing in on a specific commit. But you can also do it without a flake.lock if you specify a branch; the difference is just that you'll need to explicity specify each commit, rather than npc automatically reading the current commit from flake.lock at each step.
Note that if you are currently bisecting in a given directory, running npc status will also print the current bisection status.
Just like checkout, if you have exactly one Nixpkgs input in flake.lock then you don't need to specify any further information:
If you have multiple Nixpkgs inputs then you need to specify one via --input on this command. Since bisection status is stored per directory, you only need to specify that when you start bisecting, and not each time you mark a commit as bad or good.
Either way, that will start bisecting in "flake mode"; if you instead want to bisect outside the context of any flake, simply specify a branch:
Note that if you specify a branch, the bisection will not use "flake mode" even if you also have a unique Nixpkgs input with that branch in flake.lock. Specifying a branch here will cause the other npc bisect commands to ignore flake.lock entirely.
These subcommands are aliases of each other, just like with Git. They mark a Nixpkgs commit as being broken, or more generally, as being after the change that we are trying to pinpoint.
If you are bisecting in "flake mode" then you can run this with no argument, and npc will read the current commit from your flake.lock according to whatever flake input was determined when you first started bisecting:
However, you can always instead provide a commit explicitly.
Similarly, these two subcommands let you mark a commit as not broken, or more generally, as being before the change happened.
Once you have specified at least one "bad" commit and at least one "good" commit, npc will determine the midpoint commit in the history of the branch being bisected, according to the last good and first bad commits it has seen so far. If you are in "flake mode" then it will also do the equivalent of npc checkout with that midpoint commit, every time you mark any further commits as "bad" or "good".
Once you finish bisecting, all the state is still stored. This is to allow you to check back on the end result via the status subcommand if you'd like. Then whenever you're ready, you can go ahead and delete the state for the current directory:
The main difference from git bisect reset is that this only deletes the bisection state, and does not make any further modifications to your flake.lock. That is, it does not bring you back to the original Nixpkgs commit you were on before you started bisecting; you'd need to do that manually if you didn't want to end up on the commit that npc bisect found.
See CONTRIBUTING.md.
npc is licensed under the MIT License.
.png)


