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