Compare commits

...

3 commits

Author SHA1 Message Date
Patrick 35b36222d3
chore: move pr logic to extra file 2024-12-01 21:17:33 +01:00
Patrick 6cdabd1b61
feat: remove tracking file 2024-12-01 20:45:49 +01:00
Patrick b6a6a87179
WIP: readme 2024-12-01 20:16:59 +01:00
3 changed files with 182 additions and 146 deletions

48
README.md Normal file
View 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 [ ];
};
};
}
```

View file

@ -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,22 +73,26 @@ 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 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 {
let l = l.trim();
l.parse::<u32>()?
bail!("This shouldn't happen")
};
let pr = get_pr(l, &client).await?;
println!("Fetching diff for PR #{}: {}", l, pr.title);
@ -203,22 +106,9 @@ async fn main() -> Result<()> {
}
}
}
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
View 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())
}