| ... | ... | @@ -492,26 +492,31 @@ const Packet = union(enum) { |
| 492 | 492 | /// [protocol-v2](https://git-scm.com/docs/protocol-v2). |
| 493 | 493 | pub const Session = struct { |
| 494 | 494 | transport: *std.http.Client, |
| 495 | | uri: std.Uri, |
| 496 | | supports_agent: bool = false, |
| 497 | | supports_shallow: bool = false, |
| 495 | location: Location, |
| 496 | supports_agent: bool, |
| 497 | supports_shallow: bool, |
| 498 | allocator: Allocator, |
| 498 | 499 | |
| 499 | 500 | const agent = "zig/" ++ @import("builtin").zig_version_string; |
| 500 | 501 | const agent_capability = std.fmt.comptimePrint("agent={s}\n", .{agent}); |
| 501 | 502 | |
| 502 | | /// Discovers server capabilities. This should be called before using any |
| 503 | | /// other client functionality, or the client will be forced to default to |
| 504 | | /// the bare minimum server requirements, which may be considerably less |
| 505 | | /// efficient (e.g. no shallow fetches). |
| 506 | | /// |
| 507 | | /// See the note on `getCapabilities` regarding `redirect_uri`. |
| 508 | | pub fn discoverCapabilities( |
| 509 | | session: *Session, |
| 503 | /// Initializes a client session and discovers the capabilities of the |
| 504 | /// server for optimal transport. |
| 505 | pub fn init( |
| 510 | 506 | allocator: Allocator, |
| 511 | | redirect_uri: *[]u8, |
| 507 | transport: *std.http.Client, |
| 508 | uri: std.Uri, |
| 512 | 509 | http_headers_buffer: []u8, |
| 513 | | ) !void { |
| 514 | | var capability_iterator = try session.getCapabilities(allocator, redirect_uri, http_headers_buffer); |
| 510 | ) !Session { |
| 511 | var session: Session = .{ |
| 512 | .transport = transport, |
| 513 | .location = try .init(allocator, uri), |
| 514 | .supports_agent = false, |
| 515 | .supports_shallow = false, |
| 516 | .allocator = allocator, |
| 517 | }; |
| 518 | errdefer session.deinit(); |
| 519 | var capability_iterator = try session.getCapabilities(http_headers_buffer); |
| 515 | 520 | defer capability_iterator.deinit(); |
| 516 | 521 | while (try capability_iterator.next()) |capability| { |
| 517 | 522 | if (mem.eql(u8, capability.key, "agent")) { |
| ... | ... | @@ -525,27 +530,63 @@ pub const Session = struct { |
| 525 | 530 | } |
| 526 | 531 | } |
| 527 | 532 | } |
| 533 | return session; |
| 528 | 534 | } |
| 529 | 535 | |
| 536 | pub fn deinit(session: *Session) void { |
| 537 | session.location.deinit(session.allocator); |
| 538 | session.* = undefined; |
| 539 | } |
| 540 | |
| 541 | /// An owned `std.Uri` representing the location of the server (base URI). |
| 542 | const Location = struct { |
| 543 | uri: std.Uri, |
| 544 | |
| 545 | fn init(allocator: Allocator, uri: std.Uri) !Location { |
| 546 | const scheme = try allocator.dupe(u8, uri.scheme); |
| 547 | errdefer allocator.free(scheme); |
| 548 | const user = if (uri.user) |user| try std.fmt.allocPrint(allocator, "{user}", .{user}) else null; |
| 549 | errdefer if (user) |s| allocator.free(s); |
| 550 | const password = if (uri.password) |password| try std.fmt.allocPrint(allocator, "{password}", .{password}) else null; |
| 551 | errdefer if (password) |s| allocator.free(s); |
| 552 | const host = if (uri.host) |host| try std.fmt.allocPrint(allocator, "{host}", .{host}) else null; |
| 553 | errdefer if (host) |s| allocator.free(s); |
| 554 | const path = try std.fmt.allocPrint(allocator, "{path}", .{uri.path}); |
| 555 | errdefer allocator.free(path); |
| 556 | // The query and fragment are not used as part of the base server URI. |
| 557 | return .{ |
| 558 | .uri = .{ |
| 559 | .scheme = scheme, |
| 560 | .user = if (user) |s| .{ .percent_encoded = s } else null, |
| 561 | .password = if (password) |s| .{ .percent_encoded = s } else null, |
| 562 | .host = if (host) |s| .{ .percent_encoded = s } else null, |
| 563 | .port = uri.port, |
| 564 | .path = .{ .percent_encoded = path }, |
| 565 | }, |
| 566 | }; |
| 567 | } |
| 568 | |
| 569 | fn deinit(loc: *Location, allocator: Allocator) void { |
| 570 | allocator.free(loc.uri.scheme); |
| 571 | if (loc.uri.user) |user| allocator.free(user.percent_encoded); |
| 572 | if (loc.uri.password) |password| allocator.free(password.percent_encoded); |
| 573 | if (loc.uri.host) |host| allocator.free(host.percent_encoded); |
| 574 | allocator.free(loc.uri.path.percent_encoded); |
| 575 | } |
| 576 | }; |
| 577 | |
| 530 | 578 | /// Returns an iterator over capabilities supported by the server. |
| 531 | 579 | /// |
| 532 | | /// If the server redirects the request, `error.Redirected` is returned and |
| 533 | | /// `redirect_uri` is populated with the URI resulting from the redirects. |
| 534 | | /// When this occurs, the value of `redirect_uri` must be freed with |
| 535 | | /// `allocator` when the caller is done with it. |
| 536 | | fn getCapabilities( |
| 537 | | session: Session, |
| 538 | | allocator: Allocator, |
| 539 | | redirect_uri: *[]u8, |
| 540 | | http_headers_buffer: []u8, |
| 541 | | ) !CapabilityIterator { |
| 542 | | var info_refs_uri = session.uri; |
| 580 | /// The `session.location` is updated if the server returns a redirect, so |
| 581 | /// that subsequent session functions do not need to handle redirects. |
| 582 | fn getCapabilities(session: *Session, http_headers_buffer: []u8) !CapabilityIterator { |
| 583 | var info_refs_uri = session.location.uri; |
| 543 | 584 | { |
| 544 | | const session_uri_path = try std.fmt.allocPrint(allocator, "{path}", .{session.uri.path}); |
| 545 | | defer allocator.free(session_uri_path); |
| 546 | | info_refs_uri.path = .{ .percent_encoded = try std.fs.path.resolvePosix(allocator, &.{ "/", session_uri_path, "info/refs" }) }; |
| 585 | const session_uri_path = try std.fmt.allocPrint(session.allocator, "{path}", .{session.location.uri.path}); |
| 586 | defer session.allocator.free(session_uri_path); |
| 587 | info_refs_uri.path = .{ .percent_encoded = try std.fs.path.resolvePosix(session.allocator, &.{ "/", session_uri_path, "info/refs" }) }; |
| 547 | 588 | } |
| 548 | | defer allocator.free(info_refs_uri.path.percent_encoded); |
| 589 | defer session.allocator.free(info_refs_uri.path.percent_encoded); |
| 549 | 590 | info_refs_uri.query = .{ .percent_encoded = "service=git-upload-pack" }; |
| 550 | 591 | info_refs_uri.fragment = null; |
| 551 | 592 | |
| ... | ... | @@ -565,14 +606,14 @@ pub const Session = struct { |
| 565 | 606 | if (request.response.status != .ok) return error.ProtocolError; |
| 566 | 607 | const any_redirects_occurred = request.redirect_behavior.remaining() < max_redirects; |
| 567 | 608 | if (any_redirects_occurred) { |
| 568 | | const request_uri_path = try std.fmt.allocPrint(allocator, "{path}", .{request.uri.path}); |
| 569 | | defer allocator.free(request_uri_path); |
| 609 | const request_uri_path = try std.fmt.allocPrint(session.allocator, "{path}", .{request.uri.path}); |
| 610 | defer session.allocator.free(request_uri_path); |
| 570 | 611 | if (!mem.endsWith(u8, request_uri_path, "/info/refs")) return error.UnparseableRedirect; |
| 571 | 612 | var new_uri = request.uri; |
| 572 | 613 | new_uri.path = .{ .percent_encoded = request_uri_path[0 .. request_uri_path.len - "/info/refs".len] }; |
| 573 | | new_uri.query = null; |
| 574 | | redirect_uri.* = try std.fmt.allocPrint(allocator, "{+/}", .{new_uri}); |
| 575 | | return error.Redirected; |
| 614 | const new_location: Location = try .init(session.allocator, new_uri); |
| 615 | session.location.deinit(session.allocator); |
| 616 | session.location = new_location; |
| 576 | 617 | } |
| 577 | 618 | |
| 578 | 619 | const reader = request.reader(); |
| ... | ... | @@ -649,28 +690,28 @@ pub const Session = struct { |
| 649 | 690 | }; |
| 650 | 691 | |
| 651 | 692 | /// Returns an iterator over refs known to the server. |
| 652 | | pub fn listRefs(session: Session, allocator: Allocator, options: ListRefsOptions) !RefIterator { |
| 653 | | var upload_pack_uri = session.uri; |
| 693 | pub fn listRefs(session: Session, options: ListRefsOptions) !RefIterator { |
| 694 | var upload_pack_uri = session.location.uri; |
| 654 | 695 | { |
| 655 | | const session_uri_path = try std.fmt.allocPrint(allocator, "{path}", .{session.uri.path}); |
| 656 | | defer allocator.free(session_uri_path); |
| 657 | | upload_pack_uri.path = .{ .percent_encoded = try std.fs.path.resolvePosix(allocator, &.{ "/", session_uri_path, "git-upload-pack" }) }; |
| 696 | const session_uri_path = try std.fmt.allocPrint(session.allocator, "{path}", .{session.location.uri.path}); |
| 697 | defer session.allocator.free(session_uri_path); |
| 698 | upload_pack_uri.path = .{ .percent_encoded = try std.fs.path.resolvePosix(session.allocator, &.{ "/", session_uri_path, "git-upload-pack" }) }; |
| 658 | 699 | } |
| 659 | | defer allocator.free(upload_pack_uri.path.percent_encoded); |
| 700 | defer session.allocator.free(upload_pack_uri.path.percent_encoded); |
| 660 | 701 | upload_pack_uri.query = null; |
| 661 | 702 | upload_pack_uri.fragment = null; |
| 662 | 703 | |
| 663 | 704 | var body: std.ArrayListUnmanaged(u8) = .empty; |
| 664 | | defer body.deinit(allocator); |
| 665 | | const body_writer = body.writer(allocator); |
| 705 | defer body.deinit(session.allocator); |
| 706 | const body_writer = body.writer(session.allocator); |
| 666 | 707 | try Packet.write(.{ .data = "command=ls-refs\n" }, body_writer); |
| 667 | 708 | if (session.supports_agent) { |
| 668 | 709 | try Packet.write(.{ .data = agent_capability }, body_writer); |
| 669 | 710 | } |
| 670 | 711 | try Packet.write(.delimiter, body_writer); |
| 671 | 712 | for (options.ref_prefixes) |ref_prefix| { |
| 672 | | const ref_prefix_packet = try std.fmt.allocPrint(allocator, "ref-prefix {s}\n", .{ref_prefix}); |
| 673 | | defer allocator.free(ref_prefix_packet); |
| 713 | const ref_prefix_packet = try std.fmt.allocPrint(session.allocator, "ref-prefix {s}\n", .{ref_prefix}); |
| 714 | defer session.allocator.free(ref_prefix_packet); |
| 674 | 715 | try Packet.write(.{ .data = ref_prefix_packet }, body_writer); |
| 675 | 716 | } |
| 676 | 717 | if (options.include_symrefs) { |
| ... | ... | @@ -753,23 +794,22 @@ pub const Session = struct { |
| 753 | 794 | /// performed if the server supports it. |
| 754 | 795 | pub fn fetch( |
| 755 | 796 | session: Session, |
| 756 | | allocator: Allocator, |
| 757 | 797 | wants: []const []const u8, |
| 758 | 798 | http_headers_buffer: []u8, |
| 759 | 799 | ) !FetchStream { |
| 760 | | var upload_pack_uri = session.uri; |
| 800 | var upload_pack_uri = session.location.uri; |
| 761 | 801 | { |
| 762 | | const session_uri_path = try std.fmt.allocPrint(allocator, "{path}", .{session.uri.path}); |
| 763 | | defer allocator.free(session_uri_path); |
| 764 | | upload_pack_uri.path = .{ .percent_encoded = try std.fs.path.resolvePosix(allocator, &.{ "/", session_uri_path, "git-upload-pack" }) }; |
| 802 | const session_uri_path = try std.fmt.allocPrint(session.allocator, "{path}", .{session.location.uri.path}); |
| 803 | defer session.allocator.free(session_uri_path); |
| 804 | upload_pack_uri.path = .{ .percent_encoded = try std.fs.path.resolvePosix(session.allocator, &.{ "/", session_uri_path, "git-upload-pack" }) }; |
| 765 | 805 | } |
| 766 | | defer allocator.free(upload_pack_uri.path.percent_encoded); |
| 806 | defer session.allocator.free(upload_pack_uri.path.percent_encoded); |
| 767 | 807 | upload_pack_uri.query = null; |
| 768 | 808 | upload_pack_uri.fragment = null; |
| 769 | 809 | |
| 770 | 810 | var body: std.ArrayListUnmanaged(u8) = .empty; |
| 771 | | defer body.deinit(allocator); |
| 772 | | const body_writer = body.writer(allocator); |
| 811 | defer body.deinit(session.allocator); |
| 812 | const body_writer = body.writer(session.allocator); |
| 773 | 813 | try Packet.write(.{ .data = "command=fetch\n" }, body_writer); |
| 774 | 814 | if (session.supports_agent) { |
| 775 | 815 | try Packet.write(.{ .data = agent_capability }, body_writer); |