[!Warning]
This usesgh(but can theoretically be tweaked to use the github api instead withcurlandjq). Also usessedandtr(ghinternally callsjqtoo).
Background
Checking if a pull request is merged into nixos-unstable branch is a common occurrence in the nix(os) world.
There are lots of online trackers (like https://nixpkgs-tracker.ocfox.me/ and https://nixpk.gs/pr-tracker.html and others which are currently under construction) and cli-/ocal polished trackers with lots of cool features (like https://copeberg.org/faukah/npr which I hope the author of it makes a separate post about it, because that tool they made is wild).
I wanted to make something small to fit in a bash script, so I made a function and an alias to do that.
In the future I could maybe plug it to a unifiedpush server and have my server notify me for merges or something.
Function
The function that does the checking is nix-prs-check() and it takes an array of pull request numbers:
- iterates each pull request number
- omits any non-digit numbers to sanitize the input
- fetches and prints title of the pull request
- if the pull request is merged into the main branch of the repo
- fetches the timestamp of when the pull request was merged and prints it
- fetches the commit hash of the merge commit
- compares with various branches and prints if the commit is merged in these branches, by calling a another function
mergechecks()- takes two arguments, the nix branch and the mergecommit of the pull request to compare them
- prints the merge status (“Merged:)” / “Not merged yet:/” / “Not merged, status is diverged..”)
Merged:): commit is either behind or identical to the head of the branchNot merged yet:/: commit is ahead of the head of the branchNot merged, status is diverged..: history of the commit is different to the commit history of branch
- else if the the pull request is either still open or it has been closed (and not merged)
- prints the status of the pull request
The logic of the script may not be the best, for diverged pull requests at least, but I think it works good enough.
The nix-prs-check() function is this (the comments explain a lot):
nix-prs-check() - click to expand
nix-prs-check() {
# Accepts two arguments, a branch and a mergecommit hash, and prints whether the commit is in the branch
mergechecks() {
nixbranch=$1
mergecommit=$2
# This can be done with curl and jq instead of gh (with or without user token)
# The -H parameters are what github recommends
# The three-dot comparison shows the difference between the latest common commit of both branches (merge base) and the most recent version of the topic branch.
# --jq .status returns only the status field which is behind/ahead/diverged/identical
# only the behind and identical statuses mean that this commit is merged into the the branch
# ahead means that it just recently merged into main branch and hasn't been merged yet into the other channel
# diverged means that something went wrong, the branch has a different history from the commit (probably won't happen here)
mergestatus=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /repos/NixOS/nixpkgs/compare/"$nixbranch"..."$mergecommit" --jq .status)
# Print merge status per channel
# echo -e means "enable interpretation of backslash escapes" in echo command
if [ "$mergestatus" == "behind" ] || [ "$mergestatus" == "identical" ] ; then
echo -e "Channel $nixbranch: \tMerged:)"
elif [ "$mergestatus" == "ahead" ] ; then
echo -e "Channel $nixbranch: \tNot merged yet:/"
else
echo -e "Channel $nixbranch: \tNot merged, status is diverged.."
fi
}
# We can use "@" variable to access every parameter passed to the script via the command line. It is a special variable that holds the array of variables in BASH.
for i in "$@"
do
# Change new line per pull request
echo ""
# Assigns the pull request number to the variable
prnumber="$i"
# "s/[^0-9]//g" - removes any non-digit characters
# https://www.shellcheck.net/wiki/SC2001 - we can ignore it
prnumber="$(echo "$prnumber" | sed "s/[^0-9]//g")"
# Fetches the state of the pull request and assigns it to the variable
# The states are: MERGED/OPEN/CLOSED
# --json fields - Output JSON with the specified fields
# --jq expression - Filter JSON output using a jq expression (returns json parts that the args specify)
mergestate="$(gh pull request view "$prnumber" --repo=https://github.com/NixOS/nixpkgs --json state --jq .state)"
# Fetch and print title of pull request
echo "Pull Request No. $prnumber - Title: $(gh pull request view "$prnumber" --repo=https://github.com/NixOS/nixpkgs --json title --jq .title)"
# We only proceed if the state is MERGED
if [ "$mergestate" == "MERGED" ]; then
# Fetches the timestamp of when the pull request was merged and prints it
echo "The Pull Request No. $prnumber was Merged at $(gh pull request view "$prnumber" --repo=https://github.com/NixOS/nixpkgs --json mergedAt --jq .mergedAt)"
# We fetch the commit hash of the merge commit
mergecommit=$(gh pull request view "$prnumber" --repo=https://github.com/NixOS/nixpkgs --json mergeCommit --jq .mergeCommit.oid)
# We compare with various branches and print if the commit is merged in these branches
mergechecks staging-next "$mergecommit"
mergechecks master "$mergecommit"
mergechecks nixos-unstable-small "$mergecommit"
mergechecks nixpkgs-unstable "$mergecommit"
mergechecks nixos-unstable "$mergecommit"
else
# If the pull request isn't merged we print the status
echo "The Pull Request No. $prnumber is: $mergestate"
fi
done
# exit program
exit 0
# Resources:
# https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#compare-two-commits
# https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-comparing-branches-in-pull-requests#three-dot-and-two-dot-git-diff-comparisons
# https://www.geeksforgeeks.org/linux-unix/how-to-pass-and-parse-linux-bash-script-arguments-and-parameters/
# https://stackoverflow.com/questions/62098519/what-is-the-application-vnd-githubjson-media-type
# https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#check-if-a-pull-request-has-been-merged
# https://stackoverflow.com/questions/59002495/github-gitlab-rest-api-get-diff-between-two-branches
# https://docs.github.com/en/pull-requests/committing-changes-to-your-project/viewing-and-comparing-commits/comparing-commits#comparing-branches
# https://stackoverflow.com/questions/525872/echo-tab-characters-in-bash-script
# https://api.github.com/repos/NixOS/nixpkgs/pulls/436671
# https://api.github.com/repos/NixOS/nixpkgs/compare/nixos-unstable...46aa320299c3066886159ec4ecfff8ecf0eea7e6
# https://api.github.com/repos/NixOS/nixpkgs/pulls/436671
# "created_at": "2025-08-25T04:17:55Z",
# "updated_at": "2026-02-01T12:40:39Z",
# "closed_at": "2026-02-01T10:50:25Z",
# "merged_at": "2026-02-01T10:50:25Z",
# "merge_commit_sha": "f689e223dbbca4936e7cf05d56700cfef81232fd",
#
# https://api.github.com/repos/NixOS/nixpkgs/compare/nixos-unstable...8638315ffe30152fc90a296e245c072e19f010b8
# "status": "diverged", OR
# "status": "ahead", OR
# "status": "behind",
# "ahead_by": 1,
# "behind_by": 80335,
# "total_commits": 1,
}
Bash script
This and other functions are then packaged in a bash script bash-scripts.sh which starts with:
bash-scripts.sh - BEGIN - click to expand
#!/usr/bin/env bash
# https://gist.github.com/akrasic/380bda362e0420be08709152c91ca1f9
# set -e option instructs bash to immediately exit if any command [1] has a non-zero exit status
# set -x enables a mode of the shell where all executed commands are printed to the terminal.
# set -u affects variables. When set, a reference to any variable you haven't previously defined - with the exceptions of * and @ - is an error, and causes the program to immediately exit
# set -o pipefail this setting prevents errors in a pipeline from being masked
set -euo pipefail
And ends with:
bash-scripts.sh - END - click to expand
# The script contains f1, f2 and f3 functions. The variable f_call is assigned the value of the first command-line argument ($1), which should be the name of the function to be called.
# The shift command is used to shift the command-line arguments to the left, effectively discarding the first argument (the function name).
# Finally, the specified function ($f_call) is called with any remaining command-line arguments (“$@”). This allows users to pass additional arguments to the chosen function.
# https://linuxsimply.com/bash-scripting-tutorial/functions/call-function/
f_call=$1; shift; $f_call "$@"
# If the above line doesn't call any command, the following text will be displayed
echo "Wrong input!"
This script is called by different aliases which call each appropriate function stated in an aliases.nix module of mine.
Aliases
In my aliases.nix I have the following related aliases (the 2nd is a bonus, simply fetches details about a specific pull request on the nixpkgs repo):
aliases - click to expand
#gets an array of pull request numbers from the nixpkgs repo and shows whether they've been merged or not in mulitple channels
#uses gh
nix-prs-check = "read -r -p \"Type the number(s) of pull request(s) (if many seperate them by space): \" prnumbers && ~/dotfiles/.config/nixos-config/modules/non-nix-scripts/bash-scripts.sh nix-prs-check $prnumbers && unset prnumbers";
#shows details of a pull request from the nixpkgs repo
# "s/[^0-9]//g" - removes any non-digit characters
#uses gh
nix-prs-view = "read -r -p \"Type the number of pull request: \" prnumber && prnumber=\"$(echo \"$prnumber\" | sed \"s/[^0-9]//g\")\" && gh pull request view \"$prnumber\" --repo https://github.com/NixOS/nixpkgs --comments && unset prnumber";
Links
All these exist in my nixos git repo which has the dotfiles of my systems. You can them (and more) on my codeberg account:
- dotfiles/nixos-config: https://codeberg.org/BlastboomStrice/dotfiles
aliases.nix: https://codeberg.org/BlastboomStrice/dotfiles/src/branch/main/.config/nixos-config/modules/nixos/aliases.nixbash-scripts.sh: https://codeberg.org/BlastboomStrice/dotfiles/src/branch/main/.config/nixos-config/modules/non-nix-scripts/bash-scripts.sh
Demo
When running for example
$ nix-prs-check
With arguments: 436671 dssdfa488266 #488280 48fs8sdf30fsd9
I get this:
These pull requests currently on github:
436671- merged488266- freshly merged488280- closed488309- open
