@@ -5097,6 +5097,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
50975097 .job_queue = & job_queue ,
50985098 .omit_missing_hash_error = true ,
50995099 .allow_missing_paths_field = false ,
5100+ .use_latest_commit = false ,
51005101
51015102 .package_root = undefined ,
51025103 .error_bundle = undefined ,
@@ -5105,6 +5106,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
51055106 .actual_hash = undefined ,
51065107 .has_build_zig = true ,
51075108 .oom_flag = false ,
5109+ .latest_commit = null ,
51085110
51095111 .module = build_mod ,
51105112 };
@@ -6894,6 +6896,8 @@ const usage_fetch =
68946896 \\ --debug-hash Print verbose hash information to stdout
68956897 \\ --save Add the fetched package to build.zig.zon
68966898 \\ --save=[name] Add the fetched package to build.zig.zon as name
6899+ \\ --save-exact Add the fetched package to build.zig.zon, storing the URL verbatim
6900+ \\ --save-exact=[name] Add the fetched package to build.zig.zon as name, storing the URL verbatim
68976901 \\
68986902;
68996903
@@ -6908,7 +6912,11 @@ fn cmdFetch(
69086912 var opt_path_or_url : ? []const u8 = null ;
69096913 var override_global_cache_dir : ? []const u8 = try EnvVar .ZIG_GLOBAL_CACHE_DIR .get (arena );
69106914 var debug_hash : bool = false ;
6911- var save : union (enum ) { no , yes , name : []const u8 } = .no ;
6915+ var save : union (enum ) {
6916+ no ,
6917+ yes : ? []const u8 ,
6918+ exact : ? []const u8 ,
6919+ } = .no ;
69126920
69136921 {
69146922 var i : usize = 0 ;
@@ -6926,9 +6934,13 @@ fn cmdFetch(
69266934 } else if (mem .eql (u8 , arg , "--debug-hash" )) {
69276935 debug_hash = true ;
69286936 } else if (mem .eql (u8 , arg , "--save" )) {
6929- save = .yes ;
6937+ save = .{ . yes = null } ;
69306938 } else if (mem .startsWith (u8 , arg , "--save=" )) {
6931- save = .{ .name = arg ["--save=" .len .. ] };
6939+ save = .{ .yes = arg ["--save=" .len .. ] };
6940+ } else if (mem .eql (u8 , arg , "--save-exact" )) {
6941+ save = .{ .exact = null };
6942+ } else if (mem .startsWith (u8 , arg , "--save-exact=" )) {
6943+ save = .{ .exact = arg ["--save=" .len .. ] };
69326944 } else {
69336945 fatal ("unrecognized parameter: '{s}'" , .{arg });
69346946 }
@@ -6988,6 +7000,7 @@ fn cmdFetch(
69887000 .job_queue = & job_queue ,
69897001 .omit_missing_hash_error = true ,
69907002 .allow_missing_paths_field = false ,
7003+ .use_latest_commit = true ,
69917004
69927005 .package_root = undefined ,
69937006 .error_bundle = undefined ,
@@ -6996,6 +7009,7 @@ fn cmdFetch(
69967009 .actual_hash = undefined ,
69977010 .has_build_zig = false ,
69987011 .oom_flag = false ,
7012+ .latest_commit = null ,
69997013
70007014 .module = null ,
70017015 };
@@ -7022,12 +7036,12 @@ fn cmdFetch(
70227036 try io .getStdOut ().writeAll (hex_digest ++ "\n " );
70237037 return cleanExit ();
70247038 },
7025- .yes = > n : {
7039+ .yes , .exact = > | name | name : {
7040+ if (name ) | n | break :name n ;
70267041 const fetched_manifest = fetch .manifest orelse
70277042 fatal ("unable to determine name; fetched package has no build.zig.zon file" , .{});
7028- break :n fetched_manifest .name ;
7043+ break :name fetched_manifest .name ;
70297044 },
7030- .name = > | n | n ,
70317045 };
70327046
70337047 const cwd_path = try process .getCwdAlloc (arena );
@@ -7052,13 +7066,43 @@ fn cmdFetch(
70527066 var fixups : Ast.Fixups = .{};
70537067 defer fixups .deinit (gpa );
70547068
7069+ var saved_path_or_url = path_or_url ;
7070+
7071+ if (fetch .latest_commit ) | * latest_commit | resolved : {
7072+ const latest_commit_hex = try std .fmt .allocPrint (arena , "{}" , .{std .fmt .fmtSliceHexLower (latest_commit )});
7073+
7074+ var uri = try std .Uri .parse (path_or_url );
7075+
7076+ if (uri .fragment ) | fragment | {
7077+ const target_ref = try fragment .toRawMaybeAlloc (arena );
7078+
7079+ // the refspec may already be fully resolved
7080+ if (std .mem .eql (u8 , target_ref , latest_commit_hex )) break :resolved ;
7081+
7082+ std .log .info ("resolved ref '{s}' to commit {s}" , .{ target_ref , latest_commit_hex });
7083+
7084+ // include the original refspec in a query parameter, could be used to check for updates
7085+ uri .query = .{ .percent_encoded = try std .fmt .allocPrint (arena , "ref={%}" , .{fragment }) };
7086+ } else {
7087+ std .log .info ("resolved to commit {s}" , .{latest_commit_hex });
7088+ }
7089+
7090+ // replace the refspec with the resolved commit SHA
7091+ uri .fragment = .{ .raw = latest_commit_hex };
7092+
7093+ switch (save ) {
7094+ .yes = > saved_path_or_url = try std .fmt .allocPrint (arena , "{}" , .{uri }),
7095+ .no , .exact = > {}, // keep the original URL
7096+ }
7097+ }
7098+
70557099 const new_node_init = try std .fmt .allocPrint (arena ,
70567100 \\.{{
70577101 \\ .url = "{}",
70587102 \\ .hash = "{}",
70597103 \\ }}
70607104 , .{
7061- std .zig .fmtEscapes (path_or_url ),
7105+ std .zig .fmtEscapes (saved_path_or_url ),
70627106 std .zig .fmtEscapes (& hex_digest ),
70637107 });
70647108
@@ -7078,7 +7122,7 @@ fn cmdFetch(
70787122 if (dep .hash ) | h | {
70797123 switch (dep .location ) {
70807124 .url = > | u | {
7081- if (mem .eql (u8 , h , & hex_digest ) and mem .eql (u8 , u , path_or_url )) {
7125+ if (mem .eql (u8 , h , & hex_digest ) and mem .eql (u8 , u , saved_path_or_url )) {
70827126 std .log .info ("existing dependency named '{s}' is up-to-date" , .{name });
70837127 process .exit (0 );
70847128 }
0 commit comments