From 1061875bf96ee5b73a1d4bb0a1a9153d7f5fb6cd Mon Sep 17 00:00:00 2001 From: Jimmi Holst Christensen Date: Fri, 22 May 2026 13:28:07 +0200 Subject: [PATCH] feat: Add --pr flag to `dipm pkgs update` and `dipm pkgs add` The flag creates a PR for every package update, making it easier to review and merge updates. --- src/Packages.zig | 27 +------------ src/git.zig | 98 ++++++++++++++++++++++++++++++++++++++++++------ src/main.zig | 53 ++++++++++++++++++++++---- 3 files changed, 133 insertions(+), 45 deletions(-) diff --git a/src/Packages.zig b/src/Packages.zig index d585550..c1207fe 100644 --- a/src/Packages.zig +++ b/src/Packages.zig @@ -430,7 +430,7 @@ test "parse.fuzz" { try std.testing.fuzz({}, fuzz.fnFromParseAndWrite(parseAndWrite), .{}); } -pub fn sort(pkgs: *Packages) void { +fn sort(pkgs: *Packages) void { pkgs.by_name.sort(struct { keys: []const []const u8, @@ -467,30 +467,6 @@ test sort { }, }, }, .{}) == null); - try expectWrite(&pkgs, - \\[btest.info] - \\version = 0.2.0 - \\ - \\[btest.update] - \\version = https://github.com/test/test - \\ - \\[btest.linux_x86_64] - \\url = test_url - \\hash = test_hash - \\ - \\[atest.info] - \\version = 0.2.0 - \\ - \\[atest.update] - \\version = https://github.com/test/test - \\ - \\[atest.linux_x86_64] - \\url = test_url - \\hash = test_hash - \\ - ); - - pkgs.sort(); try expectWrite(&pkgs, \\[atest.info] \\version = 0.2.0 @@ -571,6 +547,7 @@ pub fn update( } else { entry.key_ptr.* = pkg.name; entry.value_ptr.* = pkg.pkg; + pkgs.sort(); return null; } } diff --git a/src/git.zig b/src/git.zig index 7e6732a..56ae402 100644 --- a/src/git.zig +++ b/src/git.zig @@ -1,21 +1,95 @@ pub fn commitFile(io: std.Io, dir: std.Io.Dir, file: []const u8, msg: []const u8) !void { - var child = try std.process.spawn(io, .{ - .argv = &.{ "git", "commit", "-i", file, "-m", msg }, - .stdin = .ignore, - .stdout = .pipe, - .stderr = .pipe, + return switch (try runCommand(io, dir, &.{ "git", "commit", "-i", file, "-m", msg })) { + 0 => {}, + else => error.GitCommitFailed, + }; +} + +pub fn hasDiffForFile(io: std.Io, dir: std.Io.Dir, file: []const u8) !bool { + const code = try runCommand(io, dir, &.{ "git", "diff", "--quiet", "--", file }); + return switch (code) { + 0 => false, + 1 => true, + else => error.GitDiffFailed, + }; +} + +pub fn currentBranch(gpa: std.mem.Allocator, io: std.Io, dir: std.Io.Dir) ![]u8 { + const res = try std.process.run(gpa, io, .{ + .argv = &.{ "git", "branch", "--show-current" }, .cwd = .{ .dir = dir }, }); - const failed = switch (try child.wait(io)) { + defer gpa.free(res.stdout); + defer gpa.free(res.stderr); + + switch (res.term) { .exited => |code| switch (code) { - 0 => false, // successful commit - 1 => false, // nothing to commit commit - else => true, + 0 => {}, + else => return error.GitCurrentBranchFailed, }, - else => true, + else => return error.ProcessExitedAbnormally, + } + + const trimmed = std.mem.trim(u8, res.stdout, " \r\n\t"); + if (trimmed.len == 0) + return error.GitCurrentBranchFailed; + + return gpa.dupe(u8, trimmed); +} + +pub fn createBranch(io: std.Io, dir: std.Io.Dir, branch: []const u8) !void { + const code = try runCommand(io, dir, &.{ "git", "switch", "-c", branch }); + if (code != 0) + return error.GitCreateBranchFailed; +} + +pub fn switchBranch(io: std.Io, dir: std.Io.Dir, branch: []const u8) !void { + const code = try runCommand(io, dir, &.{ "git", "switch", branch }); + if (code != 0) + return error.GitSwitchBranchFailed; +} + +pub fn push(io: std.Io, dir: std.Io.Dir) !void { + const code = try runCommand(io, dir, &.{ "git", "push" }); + if (code != 0) + return error.GitPushFailed; +} + +pub const PullOptions = struct { + prune: bool = false, +}; + +pub fn pull(io: std.Io, dir: std.Io.Dir, options: PullOptions) !void { + const code = try runCommand(io, dir, &.{ "git", "pull", if (options.prune) "--prune" else "--no-prune" }); + if (code != 0) + return error.GitPullFailed; +} + +pub const PrOptions = struct { + base: []const u8 = "main", +}; + +pub fn createPullRequest(io: std.Io, dir: std.Io.Dir, options: PrOptions) !void { + const code = try runCommand(io, dir, &.{ + "gh", "pr", "create", "--fill", "--base", options.base, + }); + if (code != 0) + return error.GhPrCreateFailed; +} + +fn runCommand(io: std.Io, dir: std.Io.Dir, argv: []const []const u8) !u8 { + var child = try std.process.spawn(io, .{ + .argv = argv, + .stdin = .ignore, + .stdout = .ignore, + .stderr = .ignore, + .cwd = .{ .dir = dir }, + }); + + return switch (try child.wait(io)) { + .exited => |code| code, + else => error.ProcessExitedAbnormally, }; - if (failed) - return error.GitCommitFailed; } pub const MessageOptions = struct { diff --git a/src/main.zig b/src/main.zig index 9ac604c..1001c42 100644 --- a/src/main.zig +++ b/src/main.zig @@ -535,6 +535,9 @@ const pkgs_update_usage = \\ -f, --pkgs-file \\ Path to pkgs.ini (default: ./pkgs.ini) \\ + \\ -p, --pull-request + \\ Create branch, commit, push and open a PR for each updated pkg + \\ \\ -h, --help \\ Display this message \\ @@ -556,6 +559,10 @@ fn pkgsUpdateCommand(prog: *Program) !void { options.commit = true; if (prog.args.flag(&.{ "-d", "--update-description" })) options.update_description = true; + if (prog.args.flag(&.{ "-p", "--pull-request" })) { + options.pr = true; + options.commit = true; + } if (prog.args.flag(&.{ "-h", "--help" })) return prog.stdoutWriteAllLocked(pkgs_update_usage); if (prog.args.positional()) |url| @@ -649,6 +656,7 @@ fn pkgsAddCommand(prog: *Program) !void { const PackagesAddOptions = struct { pkgs_ini_path: []const u8 = "./pkgs.ini", commit: bool = false, + pr: bool = false, update_description: bool, }; @@ -670,6 +678,11 @@ fn pkgsAdd(prog: *Program, add_pkg: AddPackage, options: PackagesAddOptions) !vo fn pkgsAddInner(prog: *Program, add_pkg: AddPackage, options: PackagesAddOptions) !void { const io = prog.init.io; + + var arena_allocator = std.heap.ArenaAllocator.init(prog.init.gpa); + const arena = arena_allocator.allocator(); + defer arena_allocator.deinit(); + var http_client = std.http.Client{ .io = prog.init.io, .allocator = prog.init.gpa, @@ -677,12 +690,16 @@ fn pkgsAddInner(prog: *Program, add_pkg: AddPackage, options: PackagesAddOptions defer http_client.deinit(); const cwd = std.Io.Dir.cwd(); + const pkgs_ini_dir_path = std.fs.path.dirname(options.pkgs_ini_path) orelse "."; const pkgs_ini_base_name = std.fs.path.basename(options.pkgs_ini_path); - var pkgs_ini_dir, const pkgs_ini_file = try fs.openDirAndFile(io, cwd, options.pkgs_ini_path, .{ - .file = .{ .mode = .read_write }, - }); + var pkgs_ini_dir = try cwd.openDir(io, pkgs_ini_dir_path, .{}); defer pkgs_ini_dir.close(io); + + // Ensure repo is up to date + try git.pull(prog.init.io, pkgs_ini_dir, .{ .prune = true }); + + const pkgs_ini_file = try pkgs_ini_dir.openFile(io, pkgs_ini_base_name, .{ .mode = .read_write }); defer pkgs_ini_file.close(io); var pkgs = try Packages.parseFile(prog.init.io, prog.init.gpa, pkgs_ini_file); @@ -696,6 +713,7 @@ fn pkgsAddInner(prog: *Program, add_pkg: AddPackage, options: PackagesAddOptions .gpa = prog.init.gpa, .arena = pkgs.arena(), .http_client = &http_client, + .progress = progress, .name = add_pkg.name, .version_uri = add_pkg.version, .download_uri = add_pkg.download, @@ -703,14 +721,33 @@ fn pkgsAddInner(prog: *Program, add_pkg: AddPackage, options: PackagesAddOptions }); const old_pkg = try pkgs.update(pkg, .{ .description = options.update_description }); - pkgs.sort(); try pkgs.writeToFile(io, pkgs_ini_file); try pkgs_ini_file.sync(io); - if (options.commit) { - const msg = try git.createCommitMessage(prog.init.arena.allocator(), pkg, old_pkg, .{ - .description = options.update_description, + + if (!options.commit and !options.pr) + return; + if (!try git.hasDiffForFile(io, pkgs_ini_dir, pkgs_ini_base_name)) + return; + + const msg = try git.createCommitMessage(arena, pkg, old_pkg, .{ + .description = options.update_description, + }); + + if (options.pr) { + const base_branch = try git.currentBranch(arena, io, pkgs_ini_dir); + const pr_branch = try std.fmt.allocPrint(arena, "{s}-{s}", .{ + pkg.name, pkg.pkg.info.version, }); - try git.commitFile(prog.init.io, pkgs_ini_dir, pkgs_ini_base_name, msg); + + try git.createBranch(io, pkgs_ini_dir, pr_branch); + errdefer git.switchBranch(io, pkgs_ini_dir, base_branch) catch {}; + + try git.commitFile(io, pkgs_ini_dir, pkgs_ini_base_name, msg); + try git.push(io, pkgs_ini_dir); + try git.createPullRequest(io, pkgs_ini_dir, .{ .base = base_branch }); + try git.switchBranch(io, pkgs_ini_dir, base_branch); + } else { + try git.commitFile(io, pkgs_ini_dir, pkgs_ini_base_name, msg); } }