Something went wrong. Try again.
A CLI that shows which nixpkgs branches and channels a merged pull request has reached.
Something went wrong. Try again.
6.7 kB · 200 lines
Zig
at zig-0.16
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201// SPDX-FileCopyrightText: © 2025 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
const std = @import("std");const builtin = @import("builtin");const options = @import("options");
const gh = @import("lib/gh.zig");const git = @import("lib/git.zig");const nixpkgs = @import("lib/nixpkgs.zig");
pub fn main(init: std.process.Init) !u8 { const alloc = init.gpa; const io = init.io;
var stdout_buffer: [64]u8 = undefined; var stdout_writer: std.Io.File.Writer = .init(.stdout(), io, &stdout_buffer); const stdout = &stdout_writer.interface;
const pr_numbers = prs: { var l: std.ArrayList(u64) = .empty; errdefer l.deinit(alloc);
var args = try init.minimal.args.iterateAllocator(alloc); errdefer args.deinit();
_ = args.skip(); while (args.next()) |arg| { if (std.mem.eql(u8, arg, "--version")) { std.debug.print("nix-track-pr {f}\n", .{options.version}); return 0; }
const pr = std.fmt.parseUnsigned(u64, std.mem.cutPrefix(u8, arg, "#") orelse arg, 10) catch |err| { std.debug.print("PR numbers must be valid integers. ({t})\n", .{err}); return 1; }; try l.append(alloc, pr); } break :prs try l.toOwnedSlice(alloc); }; defer alloc.free(pr_numbers);
if (pr_numbers.len == 0) { std.debug.print("I need least one PR number!\n", .{}); return 1; }
var git_dir = try git.dir(io, init.environ_map); defer git_dir.close(io);
var git_env = try git.env(alloc, init.environ_map); defer git_env.deinit();
git_dir.access(io, "HEAD", .{ .read = true }) catch |err| switch (err) { error.FileNotFound => { try git.clone(io, git_dir, &git_env); }, else => |e| return e, };
try git_dir.access(io, "HEAD", .{ .read = true });
var prs: std.ArrayList(gh.PR) = .empty; defer { for (prs.items) |pr| pr.deinit(alloc); prs.deinit(alloc); }
for (pr_numbers) |pr_number| { const pr = try gh.PR.view(alloc, io, pr_number, git_dir, &git_env) orelse { std.debug.print("{d} does not appear to be a valid PR number!\n", .{pr_number}); continue; }; try prs.append(alloc, pr); }
var branches: std.ArrayList([]const u8) = .empty; defer { for (branches.items) |branch| alloc.free(branch); branches.deinit(alloc); }
// minimum width of title field needs to be at least 6 var max_title_len: usize = 6;
{ // this will be a map of all of the branches that we need to // check, for all PRs var branch_map: std.StringArrayHashMapUnmanaged(bool) = .empty; errdefer { for (branch_map.keys()) |branch| alloc.free(branch); branch_map.deinit(alloc); }
for (prs.items) |pr| { if (pr.title.len > max_title_len) max_title_len = pr.title.len;
// get a list of all the branches that this PR needs us to check const branches_to_check = try nixpkgs.branchesToCheck(alloc, pr.base_ref); defer { for (branches_to_check) |branch| alloc.free(branch); alloc.free(branches_to_check); } // add any new branch to the map for (branches_to_check) |branch| { if (branch_map.get(branch)) |_| continue; try branch_map.putNoClobber(alloc, try alloc.dupe(u8, branch), true); } }
// add all the map keys to the branches list for (branch_map.keys()) |branch| { try branches.append(alloc, branch); }
branch_map.deinit(alloc); }
// so that we can sort them std.mem.sort([]const u8, branches.items, {}, lessThan);
try git.fetch(alloc, io, branches.items, git_dir, &git_env);
for (prs.items, 0..) |pr, i| { if (i == 0) { try stdout.writeAll("┏"); try stdout.writeAll("━" ** 8); try stdout.writeAll("┯"); try stdout.writeAll("━" ** 8); try stdout.writeAll("┯"); for (0..max_title_len + 2) |_| try stdout.writeAll("━"); try stdout.writeAll("┓\n"); } else { try stdout.writeAll("┣"); try stdout.writeAll("━" ** 8); try stdout.writeAll("┯"); try stdout.writeAll("━" ** 8); try stdout.writeAll("┯"); try stdout.writeAll("━" ** 4); try stdout.writeAll("┷"); for (0..max_title_len - 3) |_| try stdout.writeAll("━"); try stdout.writeAll("┫\n"); }
try stdout.print("┃ {d:>6} │ {t:<6} │ {s}", .{ pr.number, pr.state, pr.title }); for (0..max_title_len - pr.title.len + 1) |_| try stdout.writeAll(" "); try stdout.writeAll("┃\n");
{ try stdout.writeAll("┠"); try stdout.writeAll("─" ** 8); try stdout.writeAll("┴"); try stdout.writeAll("─" ** 8); try stdout.writeAll("┴"); try stdout.writeAll("─" ** 4); try stdout.writeAll("┬"); for (0..max_title_len - 3) |_| try stdout.writeAll("─"); try stdout.writeAll("┨\n"); } try stdout.flush(); // Don't forget to flush!
const branches_to_check = try nixpkgs.branchesToCheck(alloc, pr.base_ref); defer { for (branches_to_check) |branch| alloc.free(branch); alloc.free(branches_to_check); }
for (branches_to_check) |branch| { merged: { if (pr.commit) |commit| { if (try git.isAncestor(io, branch, commit, git_dir, &git_env)) { try stdout.print("┃ {s:<20} │ 🟢", .{branch}); break :merged; } } try stdout.print("┃ {s:<20} │ 🔴", .{branch}); } for (0..max_title_len - 6) |_| try stdout.writeAll(" "); try stdout.writeAll("┃\n");
try stdout.flush(); // Don't forget to flush! } } { try stdout.writeAll("┗"); try stdout.writeAll("━" ** 22); try stdout.writeAll("┷"); for (0..max_title_len - 3) |_| try stdout.writeAll("━"); try stdout.writeAll("┛\n"); }
try stdout.flush(); // Don't forget to flush!
return 0;}
fn lessThan(_: void, lhs: []const u8, rhs: []const u8) bool { return std.mem.lessThan(u8, lhs, rhs);}