| ... | ... | @@ -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 | }; |
| 460 | 474 | |
| 461 | 475 | /// A client session for the Git protocol, currently limited to an HTTP(S) |
| ... | ... | @@ -554,7 +568,7 @@ pub const Session = struct { |
| 554 | 568 | switch (packet) { |
| 555 | 569 | .flush => state = .response_start, |
| 556 | 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 | 572 | return .{ .request = request }; |
| 559 | 573 | } else { |
| 560 | 574 | state = .response_content; |
| ... | ... | @@ -573,6 +587,13 @@ pub const Session = struct { |
| 573 | 587 | const Capability = struct { |
| 574 | 588 | key: []const u8, |
| 575 | 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 | }; |
| 577 | 598 | |
| 578 | 599 | fn deinit(iterator: *CapabilityIterator) void { |
| ... | ... | @@ -583,13 +604,7 @@ pub const Session = struct { |
| 583 | 604 | fn next(iterator: *CapabilityIterator) !?Capability { |
| 584 | 605 | switch (try Packet.read(iterator.request.reader(), &iterator.buf)) { |
| 585 | 606 | .flush => return null, |
| 586 | | .data => |data| if (data.len > 0 and data[data.len - 1] == '\n') { |
| 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, |
| 607 | .data => |data| return Capability.parse(Packet.normalizeText(data)), |
| 593 | 608 | else => return error.UnexpectedPacket, |
| 594 | 609 | } |
| 595 | 610 | } |
| ... | ... | @@ -676,18 +691,19 @@ pub const Session = struct { |
| 676 | 691 | switch (try Packet.read(iterator.request.reader(), &iterator.buf)) { |
| 677 | 692 | .flush => return null, |
| 678 | 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 | 696 | const oid = parseOid(data[0..oid_sep_pos]) catch return error.InvalidRefPacket; |
| 681 | 697 | |
| 682 | | const name_sep_pos = mem.indexOfAnyPos(u8, data, oid_sep_pos + 1, " \n") orelse return error.InvalidRefPacket; |
| 683 | | const name = data[oid_sep_pos + 1 .. name_sep_pos]; |
| 698 | const name_sep_pos = mem.indexOfScalarPos(u8, ref_data, oid_sep_pos + 1, ' ') orelse ref_data.len; |
| 699 | const name = ref_data[oid_sep_pos + 1 .. name_sep_pos]; |
| 684 | 700 | |
| 685 | 701 | var symref_target: ?[]const u8 = null; |
| 686 | 702 | var peeled: ?Oid = null; |
| 687 | 703 | var last_sep_pos = name_sep_pos; |
| 688 | | while (data[last_sep_pos] == ' ') { |
| 689 | | const next_sep_pos = mem.indexOfAnyPos(u8, data, last_sep_pos + 1, " \n") orelse return error.InvalidRefPacket; |
| 690 | | const attribute = data[last_sep_pos + 1 .. next_sep_pos]; |
| 704 | while (last_sep_pos < ref_data.len) { |
| 705 | const next_sep_pos = mem.indexOfScalarPos(u8, ref_data, last_sep_pos + 1, ' ') orelse ref_data.len; |
| 706 | const attribute = ref_data[last_sep_pos + 1 .. next_sep_pos]; |
| 691 | 707 | if (mem.startsWith(u8, attribute, "symref-target:")) { |
| 692 | 708 | symref_target = attribute["symref-target:".len..]; |
| 693 | 709 | } else if (mem.startsWith(u8, attribute, "peeled:")) { |
| ... | ... | @@ -762,7 +778,7 @@ pub const Session = struct { |
| 762 | 778 | const packet = try Packet.read(reader, &buf); |
| 763 | 779 | switch (state) { |
| 764 | 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 | 782 | return .{ .request = request }; |
| 767 | 783 | } else { |
| 768 | 784 | state = .section_content; |
| ... | ... | @@ -1462,5 +1478,11 @@ pub fn main() !void { |
| 1462 | 1478 | std.debug.print("Starting checkout...\n", .{}); |
| 1463 | 1479 | var repository = try Repository.init(allocator, pack_file, index_file); |
| 1464 | 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 | } |