Commit 7048e93665

Ian Johnson <ian@ianjohnson.dev>
2023-11-12 05:15:44
Package.Fetch.git: handle optional pkt-line LF
Addresses a comment in #17779 pointing out the inability to fetch the upstream BoringSSL sources over Git. The reason for this is because the Git server used in this case did not include the optional (but recommended) LF terminator for textual pkt-line data. This commit adjusts handling of textual pkt-line data so that it works both with and without the optional trailing LF.
1 parent 547481c
Changed files (1)
src
Package
Fetch
src/Package/Fetch/git.zig
@@ -456,6 +456,20 @@ const Packet = union(enum) {
             },
         }
     }
+
+    /// Returns the normalized form of textual packet data, stripping any
+    /// trailing '\n'.
+    ///
+    /// As documented in
+    /// [protocol-common](https://git-scm.com/docs/protocol-common#_pkt_line_format),
+    /// non-binary (textual) pkt-line data should contain a trailing '\n', but
+    /// is not required to do so (implementations must support both forms).
+    fn normalizeText(data: []const u8) []const u8 {
+        return if (mem.endsWith(u8, data, "\n"))
+            data[0 .. data.len - 1]
+        else
+            data;
+    }
 };
 
 /// A client session for the Git protocol, currently limited to an HTTP(S)
@@ -554,7 +568,7 @@ pub const Session = struct {
             switch (packet) {
                 .flush => state = .response_start,
                 .data => |data| switch (state) {
-                    .response_start => if (mem.eql(u8, data, "version 2\n")) {
+                    .response_start => if (mem.eql(u8, Packet.normalizeText(data), "version 2")) {
                         return .{ .request = request };
                     } else {
                         state = .response_content;
@@ -573,6 +587,13 @@ pub const Session = struct {
         const Capability = struct {
             key: []const u8,
             value: ?[]const u8 = null,
+
+            fn parse(data: []const u8) Capability {
+                return if (mem.indexOfScalar(u8, data, '=')) |separator_pos|
+                    .{ .key = data[0..separator_pos], .value = data[separator_pos + 1 ..] }
+                else
+                    .{ .key = data };
+            }
         };
 
         fn deinit(iterator: *CapabilityIterator) void {
@@ -583,13 +604,7 @@ pub const Session = struct {
         fn next(iterator: *CapabilityIterator) !?Capability {
             switch (try Packet.read(iterator.request.reader(), &iterator.buf)) {
                 .flush => return null,
-                .data => |data| if (data.len > 0 and data[data.len - 1] == '\n') {
-                    if (mem.indexOfScalar(u8, data, '=')) |separator_pos| {
-                        return .{ .key = data[0..separator_pos], .value = data[separator_pos + 1 .. data.len - 1] };
-                    } else {
-                        return .{ .key = data[0 .. data.len - 1] };
-                    }
-                } else return error.UnexpectedPacket,
+                .data => |data| return Capability.parse(Packet.normalizeText(data)),
                 else => return error.UnexpectedPacket,
             }
         }
@@ -676,18 +691,19 @@ pub const Session = struct {
             switch (try Packet.read(iterator.request.reader(), &iterator.buf)) {
                 .flush => return null,
                 .data => |data| {
-                    const oid_sep_pos = mem.indexOfScalar(u8, data, ' ') orelse return error.InvalidRefPacket;
+                    const ref_data = Packet.normalizeText(data);
+                    const oid_sep_pos = mem.indexOfScalar(u8, ref_data, ' ') orelse return error.InvalidRefPacket;
                     const oid = parseOid(data[0..oid_sep_pos]) catch return error.InvalidRefPacket;
 
-                    const name_sep_pos = mem.indexOfAnyPos(u8, data, oid_sep_pos + 1, " \n") orelse return error.InvalidRefPacket;
-                    const name = data[oid_sep_pos + 1 .. name_sep_pos];
+                    const name_sep_pos = mem.indexOfScalarPos(u8, ref_data, oid_sep_pos + 1, ' ') orelse ref_data.len;
+                    const name = ref_data[oid_sep_pos + 1 .. name_sep_pos];
 
                     var symref_target: ?[]const u8 = null;
                     var peeled: ?Oid = null;
                     var last_sep_pos = name_sep_pos;
-                    while (data[last_sep_pos] == ' ') {
-                        const next_sep_pos = mem.indexOfAnyPos(u8, data, last_sep_pos + 1, " \n") orelse return error.InvalidRefPacket;
-                        const attribute = data[last_sep_pos + 1 .. next_sep_pos];
+                    while (last_sep_pos < ref_data.len) {
+                        const next_sep_pos = mem.indexOfScalarPos(u8, ref_data, last_sep_pos + 1, ' ') orelse ref_data.len;
+                        const attribute = ref_data[last_sep_pos + 1 .. next_sep_pos];
                         if (mem.startsWith(u8, attribute, "symref-target:")) {
                             symref_target = attribute["symref-target:".len..];
                         } else if (mem.startsWith(u8, attribute, "peeled:")) {
@@ -762,7 +778,7 @@ pub const Session = struct {
             const packet = try Packet.read(reader, &buf);
             switch (state) {
                 .section_start => switch (packet) {
-                    .data => |data| if (mem.eql(u8, data, "packfile\n")) {
+                    .data => |data| if (mem.eql(u8, Packet.normalizeText(data), "packfile")) {
                         return .{ .request = request };
                     } else {
                         state = .section_content;
@@ -1462,5 +1478,11 @@ pub fn main() !void {
     std.debug.print("Starting checkout...\n", .{});
     var repository = try Repository.init(allocator, pack_file, index_file);
     defer repository.deinit();
-    try repository.checkout(worktree, commit);
+    var diagnostics: Diagnostics = .{ .allocator = allocator };
+    defer diagnostics.deinit();
+    try repository.checkout(worktree, commit, &diagnostics);
+
+    for (diagnostics.errors.items) |err| {
+        std.debug.print("Diagnostic: {}\n", .{err});
+    }
 }