Compare commits
3 commits
e033b3e678
...
35b36222d3
Author | SHA1 | Date | |
---|---|---|---|
Patrick | 35b36222d3 | ||
Patrick | 6cdabd1b61 | ||
Patrick | b6a6a87179 |
48
README.md
Normal file
48
README.md
Normal file
|
@ -0,0 +1,48 @@
|
|||
# nixp-meta
|
||||
|
||||
General purpose meta tool for all things nix.
|
||||
|
||||
## Currently implemented modules
|
||||
|
||||
|
||||
### PR tracking
|
||||
|
||||
Using `nixp-meta track <pr>` on can see which nixpkgs branches a pull requests has reached, to gauge if updating is worth it yet.
|
||||
|
||||
### Live PR diffs
|
||||
|
||||
If you cannot wait for a PR to be merged, or just want to test it while it's still being worked on, this module is for you.
|
||||
|
||||
`nixp-meta add-pr <pr>` Downloads the diff for the specified PR to a folder (default `./patches/PR`)
|
||||
`nixp-meta update-prs` updates all prs.
|
||||
|
||||
Then using below module one can alter nixpkgs to use these patches
|
||||
|
||||
```nix
|
||||
{
|
||||
inputs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
flake = {
|
||||
nixpkgs-patched =
|
||||
let
|
||||
system = "x86_64-linux";
|
||||
pkgs = import inputs.nixpkgs { inherit system; };
|
||||
in
|
||||
pkgs.stdenvNoCC.mkDerivation {
|
||||
name = "Nixpkgs with patches from open PRs";
|
||||
src = inputs.nixpkgs;
|
||||
dontConfigure = true;
|
||||
dontBuild = true;
|
||||
doCheck = false;
|
||||
dontFixup = true;
|
||||
installPhase = ''
|
||||
cp -r ./ $out
|
||||
'';
|
||||
patches =
|
||||
if builtins.pathExists ../patches then pkgs.lib.filesystem.listFilesRecursive ../patches else [ ];
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
182
src/main.rs
182
src/main.rs
|
@ -1,15 +1,14 @@
|
|||
use std::{
|
||||
env,
|
||||
fmt::Write,
|
||||
fs::{self, File, OpenOptions},
|
||||
io::{self, BufRead, BufReader},
|
||||
fs::{self, read_dir},
|
||||
};
|
||||
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use color_eyre::eyre::{bail, ContextCompat, Result};
|
||||
use color_eyre::eyre::{bail, Result};
|
||||
use pr::*;
|
||||
use regex::Regex;
|
||||
use reqwest::{header, Client};
|
||||
use serde::Deserialize;
|
||||
mod pr;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(version, about)]
|
||||
|
@ -20,33 +19,23 @@ struct Cli {
|
|||
|
||||
#[derive(Subcommand)]
|
||||
enum CliCommands {
|
||||
/// Track how far a PR has made it in the nix pipeline
|
||||
Track(Track),
|
||||
GetDiff(GetDiff),
|
||||
// Update all PR, removing them if they are contained in your local nixpkgs
|
||||
/// Update all PR, removing them if they are contained in your local nixpkgs
|
||||
UpdatePrs(UpdatePRs),
|
||||
// Unconditionally add a new PR
|
||||
/// Unconditionally add a new PR
|
||||
AddPr(AddPR),
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Default)]
|
||||
struct UpdatePRs {
|
||||
#[arg(long, default_value_t = ("./pr.txt".to_string()))]
|
||||
pr_file: String,
|
||||
pr: Option<String>,
|
||||
#[arg(long, default_value_t = ("./patches/PR".to_string()))]
|
||||
path: String,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Default)]
|
||||
struct AddPR {
|
||||
pr: String,
|
||||
#[arg(long, default_value_t = ("./pr.txt".to_string()))]
|
||||
pr_file: String,
|
||||
#[arg(long, default_value_t = ("./patches/PR".to_string()))]
|
||||
path: String,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Default)]
|
||||
struct GetDiff {
|
||||
pr: String,
|
||||
#[arg(long, default_value_t = ("./patches/PR".to_string()))]
|
||||
path: String,
|
||||
|
@ -57,96 +46,6 @@ struct Track {
|
|||
pr: String,
|
||||
}
|
||||
|
||||
const BRANCHES: &[&str] = &[
|
||||
"master",
|
||||
"staging-next",
|
||||
"nixos-unstable-small",
|
||||
"nixos-unstable",
|
||||
];
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct PR {
|
||||
title: String,
|
||||
state: String,
|
||||
number: u32,
|
||||
merged: Option<bool>,
|
||||
merged_at: Option<String>,
|
||||
// merge conflicts don't have a merge_commit_sha
|
||||
merge_commit_sha: Option<String>,
|
||||
mergeable: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Compare {
|
||||
status: String,
|
||||
}
|
||||
|
||||
fn parse_pr(pr: &str) -> Result<u32> {
|
||||
let re = Regex::new(r"^[0-9]*$")?;
|
||||
let re2 = Regex::new(r"^https://github.com/NixOS/nixpkgs/pull/([0-9]*)$")?;
|
||||
if let Some(caps) = re.captures(pr) {
|
||||
Ok(caps[0].parse::<u32>()?)
|
||||
} else if let Some(caps) = re2.captures(pr) {
|
||||
Ok(caps[1].parse::<u32>()?)
|
||||
} else {
|
||||
bail!("Could not parse pr number!")
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_pr(pr: u32, client: &Client) -> Result<PR> {
|
||||
let request = client
|
||||
.get(format!(
|
||||
"https://api.github.com/repos/nixos/nixpkgs/pulls/{}",
|
||||
pr
|
||||
))
|
||||
.send();
|
||||
let text = request.await?.text().await?;
|
||||
let v: PR = serde_json::from_str(&text)?;
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
async fn contains(branch: &str, pr: &PR, client: &Client) -> Result<bool> {
|
||||
if let Some(sha) = &pr.merge_commit_sha {
|
||||
let req = client
|
||||
.get(format!(
|
||||
"https://api.github.com/repos/nixos/nixpkgs/compare/{}...{}",
|
||||
branch, sha
|
||||
))
|
||||
.send();
|
||||
let text = &req.await?.text().await?;
|
||||
let v: Compare = serde_json::from_str(text)?;
|
||||
if v.status == "identical" || v.status == "behind" {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
async fn get_diff(pr: u32, path: &str, client: &Client) -> Result<()> {
|
||||
let req = client
|
||||
.get(format!("https://github.com/nixos/nixpkgs/pull/{}.diff", pr))
|
||||
.send();
|
||||
let text = &req.await?.text().await?;
|
||||
fs::write(format!("{}/{}.diff", path, pr), text)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_local_nixp_rev() -> Result<String> {
|
||||
let file = File::open("./flake.lock")?;
|
||||
let reader = BufReader::new(file);
|
||||
let val: serde_json::Value = serde_json::from_reader(reader)?;
|
||||
let s = val
|
||||
.pointer("/nodes/root/inputs/nixpkgs")
|
||||
.wrap_err("Could not find inputs.nixpkgs for local flake")?
|
||||
.as_str()
|
||||
.wrap_err("nixpkgs link link not a string")?;
|
||||
let rev = val
|
||||
.pointer(&format!("/nodes/{}/locked/rev", s))
|
||||
.wrap_err("Could not get rev of nixpkgs")?;
|
||||
rev.as_str()
|
||||
.wrap_err("rev not a string")
|
||||
.map(|x| x.to_string())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
@ -174,51 +73,42 @@ async fn main() -> Result<()> {
|
|||
println!("{}: {}", i, contains(i, &pr, &client).await?);
|
||||
}
|
||||
}
|
||||
CliCommands::GetDiff(opts) => {
|
||||
let pr = parse_pr(&opts.pr)?;
|
||||
get_diff(pr, &opts.path, &client).await?;
|
||||
}
|
||||
CliCommands::UpdatePrs(opts) => {
|
||||
let mut trackable: Vec<(u32, String)> = Vec::new();
|
||||
{
|
||||
let file = File::open(opts.pr_file.clone())?;
|
||||
for l in io::BufReader::new(file).lines() {
|
||||
let l = l?;
|
||||
let l = if let Some((pr, _)) = l.split_once("#") {
|
||||
let pr = pr.trim();
|
||||
pr.parse::<u32>()?
|
||||
} else {
|
||||
let l = l.trim();
|
||||
l.parse::<u32>()?
|
||||
};
|
||||
let pr = get_pr(l, &client).await?;
|
||||
println!("Fetching diff for PR #{}: {}", l, pr.title);
|
||||
let branch = get_local_nixp_rev()?;
|
||||
if contains(&branch, &pr, &client).await? {
|
||||
println!("PR is contained in your local nixpkgs, removing diff");
|
||||
fs::remove_file(format!("{}/{}.diff", opts.path, l))?;
|
||||
} else {
|
||||
get_diff(l, &opts.path, &client).await?;
|
||||
trackable.push((l, pr.title));
|
||||
}
|
||||
let re = Regex::new(r"^[0-9]*.diff$")?;
|
||||
let prs = read_dir(&opts.path)?.filter(|x| {
|
||||
x.as_ref().is_ok_and(|x| {
|
||||
x.metadata().is_ok_and(|x| x.file_type().is_file())
|
||||
&& x.file_name().to_str().is_some_and(|x| re.is_match(x))
|
||||
})
|
||||
});
|
||||
for l in prs {
|
||||
let l = l?.file_name();
|
||||
let l = l
|
||||
.to_str()
|
||||
.to_owned()
|
||||
.and_then(|x| x.strip_suffix(".diff").to_owned());
|
||||
let l = if let Some(pr) = l {
|
||||
let pr = pr.trim();
|
||||
pr.parse::<u32>()?
|
||||
} else {
|
||||
bail!("This shouldn't happen")
|
||||
};
|
||||
let pr = get_pr(l, &client).await?;
|
||||
println!("Fetching diff for PR #{}: {}", l, pr.title);
|
||||
let branch = get_local_nixp_rev()?;
|
||||
if contains(&branch, &pr, &client).await? {
|
||||
println!("PR is contained in your local nixpkgs, removing diff");
|
||||
fs::remove_file(format!("{}/{}.diff", opts.path, l))?;
|
||||
} else {
|
||||
get_diff(l, &opts.path, &client).await?;
|
||||
trackable.push((l, pr.title));
|
||||
}
|
||||
}
|
||||
let mut str = String::new();
|
||||
for (pr, title) in trackable {
|
||||
let title = title.replace("\n", "//");
|
||||
writeln!(&mut str, "{} # {}", pr, title)?;
|
||||
}
|
||||
|
||||
fs::write(&opts.pr_file, str)?;
|
||||
}
|
||||
CliCommands::AddPr(opts) => {
|
||||
let pr = parse_pr(&opts.pr)?;
|
||||
get_diff(pr, &opts.path, &client).await?;
|
||||
let pr = get_pr(pr, &client).await?;
|
||||
let title = pr.title.replace("\n", "//");
|
||||
let mut file = OpenOptions::new().append(true).open(&opts.pr_file)?;
|
||||
use std::io::Write;
|
||||
writeln!(file, "{} # {}", pr.number, title)?;
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
|
|
98
src/pr.rs
Normal file
98
src/pr.rs
Normal file
|
@ -0,0 +1,98 @@
|
|||
use color_eyre::eyre::{bail, ContextCompat, Result};
|
||||
use regex::Regex;
|
||||
use reqwest::Client;
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::BufReader,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Compare {
|
||||
status: String,
|
||||
}
|
||||
|
||||
pub const BRANCHES: &[&str] = &[
|
||||
"master",
|
||||
"staging-next",
|
||||
"nixos-unstable-small",
|
||||
"nixos-unstable",
|
||||
];
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct PR {
|
||||
pub title: String,
|
||||
state: String,
|
||||
number: u32,
|
||||
merged: Option<bool>,
|
||||
merged_at: Option<String>,
|
||||
// merge conflicts don't have a merge_commit_sha
|
||||
merge_commit_sha: Option<String>,
|
||||
mergeable: Option<bool>,
|
||||
}
|
||||
|
||||
pub fn parse_pr(pr: &str) -> Result<u32> {
|
||||
let re = Regex::new(r"^[0-9]*$")?;
|
||||
let re2 = Regex::new(r"^https://github.com/NixOS/nixpkgs/pull/([0-9]*)$")?;
|
||||
if let Some(caps) = re.captures(pr) {
|
||||
Ok(caps[0].parse::<u32>()?)
|
||||
} else if let Some(caps) = re2.captures(pr) {
|
||||
Ok(caps[1].parse::<u32>()?)
|
||||
} else {
|
||||
bail!("Could not parse pr number!")
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_pr(pr: u32, client: &Client) -> Result<PR> {
|
||||
let request = client
|
||||
.get(format!(
|
||||
"https://api.github.com/repos/nixos/nixpkgs/pulls/{}",
|
||||
pr
|
||||
))
|
||||
.send();
|
||||
let text = request.await?.text().await?;
|
||||
let v: PR = serde_json::from_str(&text)?;
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
pub async fn contains(branch: &str, pr: &PR, client: &Client) -> Result<bool> {
|
||||
if let Some(sha) = &pr.merge_commit_sha {
|
||||
let req = client
|
||||
.get(format!(
|
||||
"https://api.github.com/repos/nixos/nixpkgs/compare/{}...{}",
|
||||
branch, sha
|
||||
))
|
||||
.send();
|
||||
let text = &req.await?.text().await?;
|
||||
let v: Compare = serde_json::from_str(text)?;
|
||||
if v.status == "identical" || v.status == "behind" {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
pub async fn get_diff(pr: u32, path: &str, client: &Client) -> Result<()> {
|
||||
let req = client
|
||||
.get(format!("https://github.com/nixos/nixpkgs/pull/{}.diff", pr))
|
||||
.send();
|
||||
let text = &req.await?.text().await?;
|
||||
fs::write(format!("{}/{}.diff", path, pr), text)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_local_nixp_rev() -> Result<String> {
|
||||
let file = File::open("./flake.lock")?;
|
||||
let reader = BufReader::new(file);
|
||||
let val: serde_json::Value = serde_json::from_reader(reader)?;
|
||||
let s = val
|
||||
.pointer("/nodes/root/inputs/nixpkgs")
|
||||
.wrap_err("Could not find inputs.nixpkgs for local flake")?
|
||||
.as_str()
|
||||
.wrap_err("nixpkgs link link not a string")?;
|
||||
let rev = val
|
||||
.pointer(&format!("/nodes/{}/locked/rev", s))
|
||||
.wrap_err("Could not get rev of nixpkgs")?;
|
||||
rev.as_str()
|
||||
.wrap_err("rev not a string")
|
||||
.map(|x| x.to_string())
|
||||
}
|
Loading…
Reference in a new issue