authorgravatar for ian@ianjohnson.devIan Johnson <ian@ianjohnson.dev> 2023-11-11 23:15:44-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-11-12 05:11:23-05:00
log7048e93665543005ed3dabd2e7637f01c584006a
treede190dc4347353b472a541e0f9e593251ad2873c
parent547481c31c8a538a7badbdce66d81820177ce87f

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 files changed, 38 insertions(+), 16 deletions(-)

src/Package/Fetch/git.zig+38-16
...@@ -456,6 +456,20 @@ const Packet = union(enum) {...@@ -456,6 +456,20 @@ const Packet = union(enum) {
456 },456 },
457 }457 }
458 }458 }
459
460 /// Returns the normalized form of textual packet data, stripping any
461 /// trailing '\n'.
462 ///
463 /// As documented in
464 /// [protocol-common](https://git-scm.com/docs/protocol-common#_pkt_line_format),
465 /// non-binary (textual) pkt-line data should contain a trailing '\n', but
466 /// is not required to do so (implementations must support both forms).
467 fn normalizeText(data: []const u8) []const u8 {
468 return if (mem.endsWith(u8, data, "\n"))
469 data[0 .. data.len - 1]
470 else
471 data;
472 }
459};473};
460474
461/// A client session for the Git protocol, currently limited to an HTTP(S)475/// A client session for the Git protocol, currently limited to an HTTP(S)
...@@ -554,7 +568,7 @@ pub const Session = struct {...@@ -554,7 +568,7 @@ pub const Session = struct {
554 switch (packet) {568 switch (packet) {
555 .flush => state = .response_start,569 .flush => state = .response_start,
556 .data => |data| switch (state) {570 .data => |data| switch (state) {
557 .response_start => if (mem.eql(u8, data, "version 2\n")) {571 .response_start => if (mem.eql(u8, Packet.normalizeText(data), "version 2")) {
558 return .{ .request = request };572 return .{ .request = request };
559 } else {573 } else {
560 state = .response_content;574 state = .response_content;
...@@ -573,6 +587,13 @@ pub const Session = struct {...@@ -573,6 +587,13 @@ pub const Session = struct {
573 const Capability = struct {587 const Capability = struct {
574 key: []const u8,588 key: []const u8,
575 value: ?[]const u8 = null,589 value: ?[]const u8 = null,
590
591 fn parse(data: []const u8) Capability {
592 return if (mem.indexOfScalar(u8, data, '=')) |separator_pos|
593 .{ .key = data[0..separator_pos], .value = data[separator_pos + 1 ..] }
594 else
595 .{ .key = data };
596 }
576 };597 };
577598
578 fn deinit(iterator: *CapabilityIterator) void {599 fn deinit(iterator: *CapabilityIterator) void {
...@@ -583,13 +604,7 @@ pub const Session = struct {...@@ -583,13 +604,7 @@ pub const Session = struct {
583 fn next(iterator: *CapabilityIterator) !?Capability {604 fn next(iterator: *CapabilityIterator) !?Capability {
584 switch (try Packet.read(iterator.request.reader(), &iterator.buf)) {605 switch (try Packet.read(iterator.request.reader(), &iterator.buf)) {
585 .flush => return null,606 .flush => return null,
586 .data => |data| if (data.len > 0 and data[data.len - 1] == '\n') {607 .data => |data| return Capability.parse(Packet.normalizeText(data)),
587 if (mem.indexOfScalar(u8, data, '=')) |separator_pos| {
588 return .{ .key = data[0..separator_pos], .value = data[separator_pos + 1 .. data.len - 1] };
589 } else {
590 return .{ .key = data[0 .. data.len - 1] };
591 }
592 } else return error.UnexpectedPacket,
593 else => return error.UnexpectedPacket,608 else => return error.UnexpectedPacket,
594 }609 }
595 }610 }
...@@ -676,18 +691,19 @@ pub const Session = struct {...@@ -676,18 +691,19 @@ pub const Session = struct {
676 switch (try Packet.read(iterator.request.reader(), &iterator.buf)) {691 switch (try Packet.read(iterator.request.reader(), &iterator.buf)) {
677 .flush => return null,692 .flush => return null,
678 .data => |data| {693 .data => |data| {
679 const oid_sep_pos = mem.indexOfScalar(u8, data, ' ') orelse return error.InvalidRefPacket;694 const ref_data = Packet.normalizeText(data);
695 const oid_sep_pos = mem.indexOfScalar(u8, ref_data, ' ') orelse return error.InvalidRefPacket;
680 const oid = parseOid(data[0..oid_sep_pos]) catch return error.InvalidRefPacket;696 const oid = parseOid(data[0..oid_sep_pos]) catch return error.InvalidRefPacket;
681697
682 const name_sep_pos = mem.indexOfAnyPos(u8, data, oid_sep_pos + 1, " \n") orelse return error.InvalidRefPacket;698 const name_sep_pos = mem.indexOfScalarPos(u8, ref_data, oid_sep_pos + 1, ' ') orelse ref_data.len;
683 const name = data[oid_sep_pos + 1 .. name_sep_pos];699 const name = ref_data[oid_sep_pos + 1 .. name_sep_pos];
684700
685 var symref_target: ?[]const u8 = null;701 var symref_target: ?[]const u8 = null;
686 var peeled: ?Oid = null;702 var peeled: ?Oid = null;
687 var last_sep_pos = name_sep_pos;703 var last_sep_pos = name_sep_pos;
688 while (data[last_sep_pos] == ' ') {704 while (last_sep_pos < ref_data.len) {
689 const next_sep_pos = mem.indexOfAnyPos(u8, data, last_sep_pos + 1, " \n") orelse return error.InvalidRefPacket;705 const next_sep_pos = mem.indexOfScalarPos(u8, ref_data, last_sep_pos + 1, ' ') orelse ref_data.len;
690 const attribute = data[last_sep_pos + 1 .. next_sep_pos];706 const attribute = ref_data[last_sep_pos + 1 .. next_sep_pos];
691 if (mem.startsWith(u8, attribute, "symref-target:")) {707 if (mem.startsWith(u8, attribute, "symref-target:")) {
692 symref_target = attribute["symref-target:".len..];708 symref_target = attribute["symref-target:".len..];
693 } else if (mem.startsWith(u8, attribute, "peeled:")) {709 } else if (mem.startsWith(u8, attribute, "peeled:")) {
...@@ -762,7 +778,7 @@ pub const Session = struct {...@@ -762,7 +778,7 @@ pub const Session = struct {
762 const packet = try Packet.read(reader, &buf);778 const packet = try Packet.read(reader, &buf);
763 switch (state) {779 switch (state) {
764 .section_start => switch (packet) {780 .section_start => switch (packet) {
765 .data => |data| if (mem.eql(u8, data, "packfile\n")) {781 .data => |data| if (mem.eql(u8, Packet.normalizeText(data), "packfile")) {
766 return .{ .request = request };782 return .{ .request = request };
767 } else {783 } else {
768 state = .section_content;784 state = .section_content;
...@@ -1462,5 +1478,11 @@ pub fn main() !void {...@@ -1462,5 +1478,11 @@ pub fn main() !void {
1462 std.debug.print("Starting checkout...\n", .{});1478 std.debug.print("Starting checkout...\n", .{});
1463 var repository = try Repository.init(allocator, pack_file, index_file);1479 var repository = try Repository.init(allocator, pack_file, index_file);
1464 defer repository.deinit();1480 defer repository.deinit();
1465 try repository.checkout(worktree, commit);1481 var diagnostics: Diagnostics = .{ .allocator = allocator };
1482 defer diagnostics.deinit();
1483 try repository.checkout(worktree, commit, &diagnostics);
1484
1485 for (diagnostics.errors.items) |err| {
1486 std.debug.print("Diagnostic: {}\n", .{err});
1487 }
1466}1488}