╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ net.zig ──────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
491 allocator, 492 "origin", 493 .{ .refspecs = refspecs, .wire = .{ .ssh = .{ 494 .command = ssh_cmd_maybe, 495 .upload_pack_command = upload_pack_command, 496 } } }, 497 ); 498 499 // update the working dir 500 try client_repo.restore(io, allocator, "."); 501 502 // make sure fetch was successful 503 { 504 const hello_txt = try temp_dir.openFile(io, "client/hello.txt", .{}); 505 defer hello_txt.close(io); 506 507 try std.testing.expect(null != try client_repo.readRef(io, .{ .kind = .tag, .name = "1.0.0" })); 508 try std.testing.expect(null != try client_repo.readRef(io, .{ .kind = .head, .name = "foo" })); 509 510 const oid_master = (try client_repo.readRef(io, .{ .kind = .head, .name = "master" })).?; 511 try std.testing.expectEqualStrings(&commit1, &oid_master); 512 } 513 514 // make another commit 515 const commit2 = blk: { 516 const goodbye_txt = try server_repo.core.work_dir.createFile(io, "goodbye.txt", .{ .truncate = true }); 517 defer goodbye_txt.close(io); 518 try goodbye_txt.writeStreamingAll(io, "goodbye, world!"); 519 try server_repo.add(io, allocator, &.{"goodbye.txt"}); 520 break :blk try server_repo.commit(io, allocator, .{ .message = "goodbye" }); 521 }; 522 523 try client_repo.fetch( 524 io, 525 allocator, 526 "origin", 527 .{ .refspecs = refspecs, .wire = .{ .ssh = .{ 528 .command = ssh_cmd_maybe, 529 .upload_pack_command = upload_pack_command, 530 } } }, 531 ); 532 533 // update the working dir 534 try client_repo.restore(io, allocator, "."); 535 536 // make sure fetch was successful 537 { 538 const goodbye_txt = try temp_dir.openFile(io, "client/goodbye.txt", .{}); 539 defer goodbye_txt.close(io); 540 541 const oid_master = (try client_repo.readRef(io, .{ .kind = .head, .name = "master" })).?; 542 try std.testing.expectEqualStrings(&commit2, &oid_master); 543 } 544 } 545 546 fn testPush( 547 comptime repo_kind: rp.RepoKind, 548 comptime transport_def: net.TransportDefinition, 549 comptime port: u16, 550 io: std.Io, 551 allocator: std.mem.Allocator, 552 ) !void { 553 const temp_dir_name = "temp-testnet-push"; 554 555 // create the temp dir 556 const cwd = std.Io.Dir.cwd(); 557 var temp_dir_or_err = cwd.openDir(io, temp_dir_name, .{}); 558 if (temp_dir_or_err) |*temp_dir| { 559 temp_dir.close(io); 560 try cwd.deleteTree(io, temp_dir_name); 561 } else |_| {} 562 var temp_dir = try cwd.createDirPathOpen(io, temp_dir_name, .{}); 563 defer cwd.deleteTree(io, temp_dir_name) catch {}; 564 defer temp_dir.close(io); 565 566 // init server 567 var server = try Server(transport_def, temp_dir_name, port).init(io, allocator); 568 try server.start(); 569 defer server.stop(); 570 571 const cwd_path = try std.process.currentPathAlloc(io, allocator); 572 defer allocator.free(cwd_path); 573 574 const server_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "repos", "server" }); 575 defer allocator.free(server_path); 576 577 var server_repo = try rp.Repo(.xit, .{ .is_test = true }).init(io, allocator, .{ .path = server_path }); 578 defer server_repo.deinit(io, allocator); 579 580 // add config 581 switch (transport_def) { 582 .file => try server_repo.addConfig(io, allocator, .{ .name = "core.bare", .value = "true" }), 583 .wire => { 584 try server_repo.addConfig(io, allocator, .{ .name = "core.bare", .value = "false" }); 585 try server_repo.addConfig(io, allocator, .{ .name = "receive.denycurrentbranch", .value = "updateinstead" }); 586 }, 587 } 588 try server_repo.addConfig(io, allocator, .{ .name = "http.receivepack", .value = "true" }); 589 590 // export server repo 591 { 592 const export_file = try server_repo.core.repo_dir.createFile(io, "git-daemon-export-ok", .{}); 593 defer export_file.close(io); 594 } 595 596 const client_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "client" }); 597 defer allocator.free(client_path); 598 599 var client_repo = try rp.Repo(repo_kind, .{ .is_test = true }).init(io, allocator, .{ .path = client_path }); 600 defer client_repo.deinit(io, allocator); 601 602 // make a commit 603 const commit1 = blk: { 604 const hello_txt = try client_repo.core.work_dir.createFile(io, "hello.txt", .{ .truncate = true }); 605 defer hello_txt.close(io); 606 try hello_txt.writeStreamingAll(io, "hello, world!"); 607 try client_repo.add(io, allocator, &.{"hello.txt"}); 608 break :blk try client_repo.commit(io, allocator, .{ .message = "let there be light" }); 609 }; 610 611 // add a tag 612 _ = try client_repo.addTag(io, allocator, .{ .name = "1.0.0", .message = "hi" }); 613 614 // add remote 615 { 616 if (.windows == builtin.os.tag) { 617 std.mem.replaceScalar(u8, server_path, '\\', '/'); 618 } 619 const separator = if (server_path[0] == '/') "" else "/"; 620 621 const remote_url = switch (transport_def) { 622 //.file => try std.fmt.allocPrint(allocator, "file://{s}{s}", .{ separator, server_path }), 623 .file => try std.fmt.allocPrint(allocator, "../server", .{}), // relative file paths work too 624 .wire => |wire_kind| switch (wire_kind) { 625 .http => try std.fmt.allocPrint(allocator, "http://localhost:{}/server", .{port}), 626 .raw => try std.fmt.allocPrint(allocator, "git://localhost:{}/server", .{port}), 627 .ssh => try std.fmt.allocPrint(allocator, "ssh://localhost:{}{s}{s}", .{ port, separator, server_path }), 628 }, 629 }; 630 defer allocator.free(remote_url); 631 632 try client_repo.addRemote(io, allocator, .{ .name = "origin", .value = remote_url }); 633 try client_repo.addConfig(io, allocator, .{ .name = "branch.master.remote", .value = "origin" }); 634 } 635 636 const refspecs = &.{ 637 "refs/tags/1.0.0:refs/tags/1.0.0", 638 }; 639 640 const is_ssh = switch (transport_def) { 641 .file => false, 642 .wire => |wire_kind| .ssh == wire_kind, 643 }; 644 const ssh_cmd_maybe: ?[]const u8 = if (is_ssh) blk: { 645 const known_hosts_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "known_hosts" }); 646 defer allocator.free(known_hosts_path); 647 648 const priv_key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "key" }); 649 defer allocator.free(priv_key_path); 650 651 break :blk try std.fmt.allocPrint(allocator, "ssh -o UserKnownHostsFile=\"{s}\" -o IdentityFile=\"{s}\"", .{ known_hosts_path, priv_key_path }); 652 } else null; 653 defer if (ssh_cmd_maybe) |ssh_cmd| allocator.free(ssh_cmd); 654 655 const upload_pack_command = try uploadPackCommand(is_ssh, allocator, cwd_path, port); 656 defer allocator.free(upload_pack_command); 657 const receive_pack_command = try receivePackCommand(is_ssh, allocator, cwd_path, port); 658 defer allocator.free(receive_pack_command); 659 660 try client_repo.push( 661 io, 662 allocator, 663 "origin", 664 "master", 665 false, 666 .{ .refspecs = refspecs, .wire = .{ .ssh = .{ 667 .command = ssh_cmd_maybe, 668 .receive_pack_command = receive_pack_command, 669 } } }, 670 ); 671 672 // make sure push was successful 673 { 674 try std.testing.expect(null != try server_repo.readRef(io, .{ .kind = .tag, .name = "1.0.0" })); 675 676 const oid_master = (try server_repo.readRef(io, .{ .kind = .head, .name = "master" })).?; 677 try std.testing.expectEqualStrings(&commit1, &oid_master); 678 } 679 680 // make a commit on the server 681 { 682 const hello_txt = try server_repo.core.work_dir.createFile(io, "hello.txt", .{ .truncate = true }); 683 defer hello_txt.close(io); 684 try hello_txt.writeStreamingAll(io, "hello, world from the server!"); 685 try server_repo.add(io, allocator, &.{"hello.txt"}); 686 _ = try server_repo.commit(io, allocator, .{ .message = "new commit from the server" }); 687 } 688 689 // make another commit 690 const commit2 = blk: { 691 const goodbye_txt = try client_repo.core.work_dir.createFile(io, "goodbye.txt", .{ .truncate = true }); 692 defer goodbye_txt.close(io); 693 try goodbye_txt.writeStreamingAll(io, "goodbye, world!"); 694 try client_repo.add(io, allocator, &.{"goodbye.txt"}); 695 break :blk try client_repo.commit(io, allocator, .{ .message = "goodbye" }); 696 }; 697 698 // can't push because server has commit not found locally 699 try std.testing.expectError(error.RemoteRefContainsCommitsNotFoundLocally, client_repo.push( 700 io, 701 allocator, 702 "origin", 703 "master", 704 false, 705 .{ .wire = .{ .ssh = .{ 706 .command = ssh_cmd_maybe, 707 .receive_pack_command = receive_pack_command, 708 } } }, 709 )); 710 711 // make a commit on the server with no parents, thus creating an incompatible git history 712 { 713 const hello_txt = try server_repo.core.work_dir.createFile(io, "hello.txt", .{ .truncate = true }); 714 defer hello_txt.close(io); 715 try hello_txt.writeStreamingAll(io, "hello, world from the server again!"); 716 try server_repo.add(io, allocator, &.{"hello.txt"}); 717 _ = try server_repo.commit(io, allocator, .{ .message = "new git history on the server", .parent_oids = &.{} }); 718 } 719 720 // can't push because commit doesn't exist locally 721 try std.testing.expectError(error.RemoteRefContainsCommitsNotFoundLocally, client_repo.push( 722 io, 723 allocator, 724 "origin", 725 "master", 726 false, 727 .{ .wire = .{ .ssh = .{ 728 .command = ssh_cmd_maybe, 729 .receive_pack_command = receive_pack_command, 730 } } }, 731 )); 732 733 // retrieve the commit object 734 try client_repo.fetch( 735 io, 736 allocator, 737 "origin", 738 .{ .wire = .{ .ssh = .{ 739 .command = ssh_cmd_maybe, 740 .upload_pack_command = upload_pack_command, 741 } } }, 742 ); 743 744 // can't push because server's history is incompatible 745 try std.testing.expectError(error.RemoteRefContainsIncompatibleHistory, client_repo.push( 746 io, 747 allocator, 748 "origin", 749 "master", 750 false, 751 .{ .wire = .{ .ssh = .{ 752 .command = ssh_cmd_maybe, 753 .receive_pack_command = receive_pack_command, 754 } } }, 755 )); 756 757 // test denyNonFastForwards (only for wire transports, file transport bypasses receive-pack) 758 switch (transport_def) { 759 .file => {}, 760 .wire => { 761 // set denyNonFastForwards on server 762 try server_repo.addConfig(io, allocator, .{ .name = "receive.denynonfastforwards", .value = "true" }); 763 764 // save the server's current master ref 765 const oid_before_denied_push = (try server_repo.readRef(io, .{ .kind = .head, .name = "master" })).?; 766 767 // force push should be rejected by server due to denyNonFastForwards 768 try client_repo.push( 769 io, 770 allocator, 771 "origin", 772 "master", 773 true, 774 .{ .wire = .{ .ssh = .{ 775 .command = ssh_cmd_maybe, 776 .receive_pack_command = receive_pack_command, 777 } } }, 778 ); 779 780 // verify the server ref was not updated (push was denied) 781 { 782 const oid_master = (try server_repo.readRef(io, .{ .kind = .head, .name = "master" })).?; 783 try std.testing.expectEqualStrings(&oid_before_denied_push, &oid_master); 784 } 785 786 // remove denyNonFastForwards from server 787 try server_repo.removeConfig(io, allocator, .{ .name = "receive.denynonfastforwards" }); 788 }, 789 } 790 791 // force push 792 try client_repo.push( 793 io, 794 allocator, 795 "origin", 796 "master", 797 true, 798 .{ .wire = .{ .ssh = .{ 799 .command = ssh_cmd_maybe, 800 .receive_pack_command = receive_pack_command, 801 } } }, 802 ); 803 804 // make sure push was successful 805 { 806 const oid_master = (try server_repo.readRef(io, .{ .kind = .head, .name = "master" })).?; 807 try std.testing.expectEqualStrings(&commit2, &oid_master); 808 } 809 810 // remove the remote tag 811 try client_repo.push( 812 io, 813 allocator, 814 "origin", 815 ":refs/tags/1.0.0", 816 false, 817 .{ .wire = .{ .ssh = .{ 818 .command = ssh_cmd_maybe, 819 .receive_pack_command = receive_pack_command, 820 } } }, 821 ); 822 823 // make sure push was successful 824 try std.testing.expect(null == try server_repo.readRef(io, .{ .kind = .tag, .name = "1.0.0" })); 825 } 826 827 fn testPushCreatesMissingRepo( 828 comptime repo_kind: rp.RepoKind, 829 comptime transport_def: net.TransportDefinition, 830 comptime port: u16, 831 io: std.Io, 832 allocator: std.mem.Allocator, 833 ) !void { 834 const temp_dir_name = "temp-testnet-push-create"; 835 836 const cwd = std.Io.Dir.cwd(); 837 var temp_dir_or_err = cwd.openDir(io, temp_dir_name, .{}); 838 if (temp_dir_or_err) |*temp_dir| { 839 temp_dir.close(io); 840 try cwd.deleteTree(io, temp_dir_name); 841 } else |_| {} 842 var temp_dir = try cwd.createDirPathOpen(io, temp_dir_name, .{}); 843 defer cwd.deleteTree(io, temp_dir_name) catch {}; 844 defer temp_dir.close(io); 845 846 var server = try Server(transport_def, temp_dir_name, port).init(io, allocator); 847 try server.start(); 848 defer server.stop(); 849 850 const cwd_path = try std.process.currentPathAlloc(io, allocator); 851 defer allocator.free(cwd_path); 852 853 const server_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "repos", "server" }); 854 defer allocator.free(server_path); 855 856 const client_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "client" }); 857 defer allocator.free(client_path); 858 859 var client_repo = try rp.Repo(repo_kind, .{ .is_test = true }).init(io, allocator, .{ .path = client_path }); 860 defer client_repo.deinit(io, allocator); 861 862 const commit1 = blk: { 863 const hello_txt = try client_repo.core.work_dir.createFile(io, "hello.txt", .{ .truncate = true }); 864 defer hello_txt.close(io); 865 try hello_txt.writeStreamingAll(io, "hello, world!"); 866 try client_repo.add(io, allocator, &.{"hello.txt"}); 867 break :blk try client_repo.commit(io, allocator, .{ .message = "let there be light" }); 868 }; 869 870 _ = try client_repo.addTag(io, allocator, .{ .name = "1.0.0", .message = "hi" }); 871 872 { 873 if (.windows == builtin.os.tag) { 874 std.mem.replaceScalar(u8, server_path, '\\', '/'); 875 } 876 const separator = if (server_path[0] == '/') "" else "/"; 877 878 const remote_url = switch (transport_def) { 879 .file => unreachable, 880 .wire => |wire_kind| switch (wire_kind) { 881 .http => try std.fmt.allocPrint(allocator, "http://localhost:{}/server", .{port}), 882 .raw => unreachable, 883 .ssh => try std.fmt.allocPrint(allocator, "ssh://localhost:{}{s}{s}", .{ port, separator, server_path }), 884 }, 885 }; 886 defer allocator.free(remote_url); 887 888 try client_repo.addRemote(io, allocator, .{ .name = "origin", .value = remote_url }); 889 try client_repo.addConfig(io, allocator, .{ .name = "branch.master.remote", .value = "origin" }); 890 } 891 892 const is_ssh = switch (transport_def) { 893 .file => false, 894 .wire => |wire_kind| .ssh == wire_kind, 895 }; 896 const ssh_cmd_maybe: ?[]const u8 = if (is_ssh) blk: { 897 const known_hosts_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "known_hosts" }); 898 defer allocator.free(known_hosts_path); 899 900 const priv_key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "key" }); 901 defer allocator.free(priv_key_path); 902 903 break :blk try std.fmt.allocPrint(allocator, "ssh -o UserKnownHostsFile=\"{s}\" -o IdentityFile=\"{s}\"", .{ known_hosts_path, priv_key_path }); 904 } else null; 905 defer if (ssh_cmd_maybe) |ssh_cmd| allocator.free(ssh_cmd); 906 907 const receive_pack_command = try receivePackCommand(is_ssh, allocator, cwd_path, port); 908 defer allocator.free(receive_pack_command); 909 910 try client_repo.push( 911 io, 912 allocator, 913 "origin", 914 "master", 915 false, 916 .{ .refspecs = &.{"refs/tags/1.0.0:refs/tags/1.0.0"}, .wire = .{ .ssh = .{ 917 .command = ssh_cmd_maybe, 918 .receive_pack_command = receive_pack_command, 919 } } }, 920 ); 921 922 var server_repo = try rp.Repo(.xit, .{ .is_test = true }).open(io, allocator, .{ .path = server_path }); 923 defer server_repo.deinit(io, allocator); 924 925 try std.testing.expect(null != try server_repo.readRef(io, .{ .kind = .tag, .name = "1.0.0" })); 926 927 const oid_master = (try server_repo.readRef(io, .{ .kind = .head, .name = "master" })).?; 928 try std.testing.expectEqualStrings(&commit1, &oid_master); 929 } 930 931 fn testClone( 932 comptime repo_kind: rp.RepoKind, 933 comptime transport_def: net.TransportDefinition, 934 comptime port: u16, 935 comptime shell_out_to_git: bool, 936 io: std.Io, 937 allocator: std.mem.Allocator, 938 ) !void { 939 const temp_dir_name = "temp-testnet-clone"; 940 941 // create the temp dir 942 const cwd = std.Io.Dir.cwd(); 943 var temp_dir_or_err = cwd.openDir(io, temp_dir_name, .{}); 944 if (temp_dir_or_err) |*temp_dir| { 945 temp_dir.close(io); 946 try cwd.deleteTree(io, temp_dir_name); 947 } else |_| {} 948 var temp_dir = try cwd.createDirPathOpen(io, temp_dir_name, .{}); 949 defer cwd.deleteTree(io, temp_dir_name) catch {}; 950 defer temp_dir.close(io); 951 952 // init server 953 var server = try Server(transport_def, temp_dir_name, port).init(io, allocator); 954 try server.start(); 955 defer server.stop(); 956 957 const cwd_path = try std.process.currentPathAlloc(io, allocator); 958 defer allocator.free(cwd_path); 959 960 const temp_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name }); 961 defer allocator.free(temp_path); 962 963 const server_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "repos", "server" }); 964 defer allocator.free(server_path); 965 966 // init server repo with default branch name as main 967 // is_test must be false when shell_out_to_git so commits get real timestamps (needed for --shallow-since) 968 var server_repo = try rp.Repo(.xit, .{ .is_test = !shell_out_to_git }).init(io, allocator, .{ .path = server_path, .create_default_branch = "main" }); 969 defer server_repo.deinit(io, allocator); 970 971 if (shell_out_to_git) { 972 try server_repo.addConfig(io, allocator, .{ .name = "user.name", .value = "test" }); 973 try server_repo.addConfig(io, allocator, .{ .name = "user.email", .value = "test@test" }); 974 try server_repo.addConfig(io, allocator, .{ .name = "uploadpack.allowfilter", .value = "true" }); 975 } 976 977 // make a commit 978 { 979 const hello_txt = try server_repo.core.work_dir.createFile(io, "hello.txt", .{ .truncate = true }); 980 defer hello_txt.close(io); 981 try hello_txt.writeStreamingAll(io, "hello, world!"); 982 try server_repo.add(io, allocator, &.{"hello.txt"}); 983 _ = try server_repo.commit(io, allocator, .{ .message = "let there be light" }); 984 } 985 986 // tag first commit 987 _ = try server_repo.addTag(io, allocator, .{ .name = "v1", .message = "first" }); 988 989 // make a commit 990 { 991 const goodbye_txt = try server_repo.core.work_dir.createFile(io, "goodbye.txt", .{ .truncate = true }); 992 defer goodbye_txt.close(io); 993 try goodbye_txt.writeStreamingAll(io, "goodbye, world!"); 994 try server_repo.add(io, allocator, &.{"goodbye.txt"}); 995 _ = try server_repo.commit(io, allocator, .{ .message = "add goodbye file" }); 996 } 997 998 // export server repo 999 { 1000 const export_file = try server_repo.core.repo_dir.createFile(io, "git-daemon-export-ok", .{}); 1001 defer export_file.close(io); 1002 } 1003 1004 const client_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "client" }); 1005 defer allocator.free(client_path); 1006 1007 // get remote url 1008 const remote_url = blk: { 1009 if (.windows == builtin.os.tag) { 1010 std.mem.replaceScalar(u8, server_path, '\\', '/'); 1011 } 1012 const separator = if (server_path[0] == '/') "" else "/"; 1013 1014 break :blk switch (transport_def) { 1015 //.file => try std.fmt.allocPrint(allocator, "file://{s}{s}", .{ separator, server_path }), 1016 .file => try std.fmt.allocPrint(allocator, "server", .{}), // relative file paths work too 1017 .wire => |wire_kind| switch (wire_kind) { 1018 .http => try std.fmt.allocPrint(allocator, "http://localhost:{}/server", .{port}), 1019 .raw => try std.fmt.allocPrint(allocator, "git://localhost:{}/server", .{port}), 1020 .ssh => try std.fmt.allocPrint(allocator, "ssh://localhost:{}{s}{s}", .{ port, separator, server_path }), 1021 }, 1022 }; 1023 }; 1024 defer allocator.free(remote_url); 1025 1026 const is_ssh = switch (transport_def) { 1027 .file => false, 1028 .wire => |wire_kind| .ssh == wire_kind, 1029 }; 1030 1031 const upload_pack_command = try uploadPackCommand(is_ssh, allocator, cwd_path, port); 1032 defer allocator.free(upload_pack_command); 1033 1034 if (shell_out_to_git) { 1035 const priv_key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "key" }); 1036 defer allocator.free(priv_key_path); 1037 const ssh_config_arg = try std.fmt.allocPrint(allocator, "core.sshCommand=ssh -o StrictHostKeyChecking=no -o IdentityFile={s}", .{priv_key_path}); 1038 defer allocator.free(ssh_config_arg); 1039 1040 { 1041 var process = try std.process.spawn(io, .{ 1042 .argv = if (is_ssh) 1043 &.{ "git", "-c", ssh_config_arg, "clone", "--upload-pack", upload_pack_command, "--depth", "1", remote_url, "client" } 1044 else 1045 &.{ "git", "clone", "--depth", "1", remote_url, "client" }, 1046 .cwd = .{ .path = temp_path }, 1047 .stdin = .ignore, 1048 .stdout = .ignore, 1049 .stderr = .ignore, 1050 }); 1051 const term = try process.wait(io); 1052 if (term != .exited or term.exited != 0) { 1053 return error.GitCommandFailed; 1054 } 1055 } 1056 1057 // make sure shallow clone was successful 1058 { 1059 const hello_txt = try temp_dir.openFile(io, "client/hello.txt", .{}); 1060 hello_txt.close(io); 1061 } 1062 1063 // make a third commit on the server 1064 { 1065 const extra_txt = try server_repo.core.work_dir.createFile(io, "extra.txt", .{ .truncate = true }); 1066 defer extra_txt.close(io); 1067 try extra_txt.writeStreamingAll(io, "extra content"); 1068 try server_repo.add(io, allocator, &.{"extra.txt"}); 1069 _ = try server_repo.commit(io, allocator, .{ .message = "add extra file" }); 1070 } 1071 1072 // pull --unshallow to deepen the clone and get the new commit 1073 { 1074 var process = try std.process.spawn(io, .{ 1075 .argv = if (is_ssh) 1076 &.{ "git", "-c", ssh_config_arg, "pull", "--upload-pack", upload_pack_command, "--unshallow" } 1077 else 1078 &.{ "git", "pull", "--unshallow" }, 1079 .cwd = .{ .path = client_path }, 1080 .stdin = .ignore, 1081 .stdout = .ignore, 1082 .stderr = .ignore, 1083 }); 1084 const term = try process.wait(io); 1085 if (term != .exited or term.exited != 0) { 1086 return error.GitCommandFailed; 1087 } 1088 } 1089 1090 // make sure unshallow pull was successful 1091 { 1092 const extra_txt = try temp_dir.openFile(io, "client/extra.txt", .{}); 1093 extra_txt.close(io); 1094 } 1095 1096 // delete client and clone again with --shallow-since 1097 try cwd.deleteTree(io, temp_dir_name ++ "/client"); 1098 1099 { 1100 var process = try std.process.spawn(io, .{ 1101 .argv = if (is_ssh) 1102 &.{ "git", "-c", ssh_config_arg, "clone", "--upload-pack", upload_pack_command, "--shallow-since=2000-01-01", remote_url, "client" } 1103 else 1104 &.{ "git", "clone", "--shallow-since=2000-01-01", remote_url, "client" }, 1105 .cwd = .{ .path = temp_path }, 1106 .stdin = .ignore, 1107 .stdout = .ignore, 1108 .stderr = .ignore, 1109 }); 1110 const term = try process.wait(io); 1111 if (term != .exited or term.exited != 0) { 1112 return error.GitCommandFailed; 1113 } 1114 } 1115 1116 // make sure shallow clone was successful 1117 { 1118 const hello_txt = try temp_dir.openFile(io, "client/hello.txt", .{}); 1119 hello_txt.close(io); 1120 } 1121 1122 // delete client and clone again with --shallow-exclude 1123 try cwd.deleteTree(io, temp_dir_name ++ "/client"); 1124 1125 { 1126 var process = try std.process.spawn(io, .{ 1127 .argv = if (is_ssh) 1128 &.{ "git", "-c", ssh_config_arg, "clone", "--upload-pack", upload_pack_command, "--shallow-exclude=v1", remote_url, "client" } 1129 else 1130 &.{ "git", "clone", "--shallow-exclude=v1", remote_url, "client" }, 1131 .cwd = .{ .path = temp_path }, 1132 .stdin = .ignore, 1133 .stdout = .ignore, 1134 .stderr = .ignore, 1135 }); 1136 const term = try process.wait(io); 1137 if (term != .exited or term.exited != 0) { 1138 return error.GitCommandFailed; 1139 } 1140 } 1141 1142 // make sure shallow clone was successful 1143 { 1144 const hello_txt = try temp_dir.openFile(io, "client/hello.txt", .{}); 1145 hello_txt.close(io); 1146 } 1147 1148 // delete client and clone again with --filter=blob:none 1149 try cwd.deleteTree(io, temp_dir_name ++ "/client"); 1150 1151 { 1152 var process = try std.process.spawn(io, .{ 1153 .argv = if (is_ssh) 1154 &.{ "git", "-c", ssh_config_arg, "clone", "--upload-pack", upload_pack_command, "--filter=blob:none", remote_url, "client" } 1155 else 1156 &.{ "git", "clone", "--filter=blob:none", remote_url, "client" }, 1157 .cwd = .{ .path = temp_path }, 1158 .stdin = .ignore, 1159 .stdout = .ignore, 1160 .stderr = .ignore, 1161 }); 1162 const term = try process.wait(io); 1163 if (term != .exited or term.exited != 0) { 1164 return error.GitCommandFailed; 1165 } 1166 } 1167 1168 // make sure partial clone was successful 1169 { 1170 const hello_txt = try temp_dir.openFile(io, "client/hello.txt", .{}); 1171 hello_txt.close(io); 1172 } 1173 1174 // delete client and clone again with --filter=tree:0 1175 try cwd.deleteTree(io, temp_dir_name ++ "/client"); 1176 1177 { 1178 var process = try std.process.spawn(io, .{ 1179 .argv = if (is_ssh) 1180 &.{ "git", "-c", ssh_config_arg, "clone", "--upload-pack", upload_pack_command, "--filter=tree:0", remote_url, "client" } 1181 else 1182 &.{ "git", "clone", "--filter=tree:0", remote_url, "client" }, 1183 .cwd = .{ .path = temp_path }, 1184 .stdin = .ignore, 1185 .stdout = .ignore, 1186 .stderr = .ignore, 1187 }); 1188 const term = try process.wait(io); 1189 if (term != .exited or term.exited != 0) { 1190 return error.GitCommandFailed; 1191 } 1192 } 1193 1194 // make sure treeless clone was successful 1195 { 1196 const goodbye_txt = try temp_dir.openFile(io, "client/goodbye.txt", .{}); 1197 goodbye_txt.close(io); 1198 } 1199 } else { 1200 const ssh_cmd_maybe: ?[]const u8 = if (is_ssh) blk: { 1201 const known_hosts_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "known_hosts" }); 1202 defer allocator.free(known_hosts_path); 1203 1204 const priv_key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "key" }); 1205 defer allocator.free(priv_key_path); 1206 1207 break :blk try std.fmt.allocPrint(allocator, "ssh -o UserKnownHostsFile=\"{s}\" -o IdentityFile=\"{s}\"", .{ known_hosts_path, priv_key_path }); 1208 } else null; 1209 defer if (ssh_cmd_maybe) |ssh_cmd| allocator.free(ssh_cmd); 1210 1211 // clone repo 1212 var client_repo = try rp.Repo(repo_kind, .{ .is_test = true }).clone( 1213 io, 1214 allocator, 1215 remote_url, 1216 temp_path, 1217 client_path, 1218 .{ .wire = .{ .ssh = .{ 1219 .command = ssh_cmd_maybe, 1220 .upload_pack_command = upload_pack_command, 1221 } } }, 1222 ); 1223 defer client_repo.deinit(io, allocator); 1224 1225 // make sure HEAD points to the right default branch 1226 var current_branch_buffer = [_]u8{0} ** rf.MAX_REF_CONTENT_SIZE; 1227 const head = try client_repo.head(io, &current_branch_buffer); 1228 try std.testing.expectEqualStrings("main", head.ref.name); 1229 1230 // make sure clone was successful 1231 const hello_txt = try temp_dir.openFile(io, "client/hello.txt", .{}); 1232 defer hello_txt.close(io); 1233 } 1234 } 1235 1236 fn testFetchLarge( 1237 comptime repo_kind: rp.RepoKind, 1238 comptime transport_def: net.TransportDefinition, 1239 comptime port: u16, 1240 comptime shell_out_to_git: bool, 1241 io: std.Io, 1242 allocator: std.mem.Allocator, 1243 ) !void { 1244 const temp_dir_name = "temp-testnet-fetch-large"; 1245 1246 // create the temp dir 1247 const cwd = std.Io.Dir.cwd(); 1248 var temp_dir_or_err = cwd.openDir(io, temp_dir_name, .{}); 1249 if (temp_dir_or_err) |*temp_dir| { 1250 temp_dir.close(io); 1251 try cwd.deleteTree(io, temp_dir_name); 1252 } else |_| {} 1253 var temp_dir = try cwd.createDirPathOpen(io, temp_dir_name, .{}); 1254 defer cwd.deleteTree(io, temp_dir_name) catch {}; 1255 defer temp_dir.close(io); 1256 1257 // init server 1258 var server = try Server(transport_def, temp_dir_name, port).init(io, allocator); 1259 try server.start(); 1260 defer server.stop(); 1261 1262 const cwd_path = try std.process.currentPathAlloc(io, allocator); 1263 defer allocator.free(cwd_path); 1264 1265 const server_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "repos", "server" }); 1266 defer allocator.free(server_path); 1267 1268 var server_repo = try rp.Repo(.xit, .{ .is_test = true }).init(io, allocator, .{ .path = server_path }); 1269 defer server_repo.deinit(io, allocator); 1270 1271 var server_dir = try cwd.openDir(io, server_path, .{}); 1272 defer server_dir.close(io); 1273 1274 // copy files from current repo into server dir 1275 for (&[_][]const u8{"src"}) |dir_name| { 1276 var src_repo_dir = try cwd.openDir(io, dir_name, .{ .iterate = true }); 1277 defer src_repo_dir.close(io); 1278 1279 var dest_repo_dir = try server_dir.createDirPathOpen(io, dir_name, .{}); 1280 defer dest_repo_dir.close(io); 1281 1282 try copyDir(io, src_repo_dir, dest_repo_dir); 1283 1284 try server_repo.add(io, allocator, &.{dir_name}); 1285 } 1286 1287 // make a commit 1288 const commit1 = try server_repo.commit(io, allocator, .{ .message = "let there be light" }); 1289 1290 // export server repo 1291 { 1292 const export_file = try server_repo.core.repo_dir.createFile(io, "git-daemon-export-ok", .{}); 1293 defer export_file.close(io); 1294 } 1295 1296 if (shell_out_to_git) { 1297 try server_repo.addConfig(io, allocator, .{ .name = "uploadpack.allowrefinwant", .value = "true" }); 1298 } 1299 1300 const client_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "client" }); 1301 defer allocator.free(client_path); 1302 1303 var client_repo = try rp.Repo(repo_kind, .{ .is_test = true }).init(io, allocator, .{ .path = client_path }); 1304 defer client_repo.deinit(io, allocator); 1305 1306 // add remote 1307 { 1308 if (.windows == builtin.os.tag) { 1309 std.mem.replaceScalar(u8, server_path, '\\', '/'); 1310 } 1311 const separator = if (server_path[0] == '/') "" else "/"; 1312 1313 const remote_url = switch (transport_def) { 1314 //.file => try std.fmt.allocPrint(allocator, "file://{s}{s}", .{ separator, server_path }), 1315 .file => try std.fmt.allocPrint(allocator, "../server", .{}), // relative file paths work too 1316 .wire => |wire_kind| switch (wire_kind) { 1317 .http => try std.fmt.allocPrint(allocator, "http://localhost:{}/server", .{port}), 1318 .raw => try std.fmt.allocPrint(allocator, "git://localhost:{}/server", .{port}), 1319 .ssh => try std.fmt.allocPrint(allocator, "ssh://localhost:{}{s}{s}", .{ port, separator, server_path }), 1320 }, 1321 }; 1322 defer allocator.free(remote_url); 1323 1324 try client_repo.addRemote(io, allocator, .{ .name = "origin", .value = remote_url }); 1325 try client_repo.addConfig(io, allocator, .{ .name = "branch.master.remote", .value = "origin" }); 1326 } 1327 1328 const is_ssh = switch (transport_def) { 1329 .file => false, 1330 .wire => |wire_kind| .ssh == wire_kind, 1331 }; 1332 1333 const upload_pack_command = try uploadPackCommand(is_ssh, allocator, cwd_path, port); 1334 defer allocator.free(upload_pack_command); 1335 1336 if (shell_out_to_git) { 1337 const priv_key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "key" }); 1338 defer allocator.free(priv_key_path); 1339 const ssh_config_arg = try std.fmt.allocPrint(allocator, "core.sshCommand=ssh -o StrictHostKeyChecking=no -o IdentityFile={s}", .{priv_key_path}); 1340 defer allocator.free(ssh_config_arg); 1341 1342 { 1343 var process = try std.process.spawn(io, .{ 1344 .argv = if (is_ssh) 1345 &.{ "git", "-c", ssh_config_arg, "pull", "--upload-pack", upload_pack_command, "origin", "master" } 1346 else 1347 &.{ "git", "pull", "origin", "master" }, 1348 .cwd = .{ .path = client_path }, 1349 .stdin = .ignore, 1350 .stdout = .ignore, 1351 .stderr = .ignore, 1352 }); 1353 const term = try process.wait(io); 1354 if (term != .exited or term.exited != 0) { 1355 return error.GitCommandFailed; 1356 } 1357 } 1358 1359 // make sure pull was successful 1360 { 1361 const oid_master = (try client_repo.readRef(io, .{ .kind = .head, .name = "master" })).?; 1362 try std.testing.expectEqualStrings(&commit1, &oid_master); 1363 } 1364 1365 // make another commit on the server 1366 const commit2 = blk: { 1367 const extra_txt = try server_repo.core.work_dir.createFile(io, "extra.txt", .{ .truncate = true }); 1368 defer extra_txt.close(io); 1369 try extra_txt.writeStreamingAll(io, "extra content"); 1370 try server_repo.add(io, allocator, &.{"extra.txt"}); 1371 break :blk try server_repo.commit(io, allocator, .{ .message = "add extra file" }); 1372 }; 1373 1374 // fetch with ref-in-want (git uses want-ref in protocol v2 when fetching named refs) 1375 { 1376 var process = try std.process.spawn(io, .{ 1377 .argv = if (is_ssh) 1378 &.{ "git", "-c", ssh_config_arg, "fetch", "--upload-pack", upload_pack_command, "origin", "master" } 1379 else 1380 &.{ "git", "fetch", "origin", "master" }, 1381 .cwd = .{ .path = client_path }, 1382 .stdin = .ignore, 1383 .stdout = .ignore, 1384 .stderr = .ignore, 1385 }); 1386 const term = try process.wait(io); 1387 if (term != .exited or term.exited != 0) { 1388 return error.GitCommandFailed; 1389 } 1390 } 1391 1392 // make sure fetch with want-ref was successful 1393 { 1394 const oid_remote_master = (try client_repo.readRef(io, .{ .kind = .{ .remote = "origin" }, .name = "master" })).?; 1395 try std.testing.expectEqualStrings(&commit2, &oid_remote_master); 1396 } 1397 } else { 1398 const refspecs = &.{ 1399 "+refs/heads/master:refs/heads/master", 1400 }; 1401 1402 const ssh_cmd_maybe: ?[]const u8 = if (is_ssh) blk: { 1403 const known_hosts_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "known_hosts" }); 1404 defer allocator.free(known_hosts_path); 1405 1406 const priv_key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "key" }); 1407 defer allocator.free(priv_key_path); 1408 1409 break :blk try std.fmt.allocPrint(allocator, "ssh -o UserKnownHostsFile=\"{s}\" -o IdentityFile=\"{s}\"", .{ known_hosts_path, priv_key_path }); 1410 } else null; 1411 defer if (ssh_cmd_maybe) |ssh_cmd| allocator.free(ssh_cmd); 1412 1413 try client_repo.fetch( 1414 io, 1415 allocator, 1416 "origin", 1417 .{ .refspecs = refspecs, .wire = .{ .ssh = .{ 1418 .command = ssh_cmd_maybe, 1419 .upload_pack_command = upload_pack_command, 1420 } } }, 1421 ); 1422 1423 // update the working dir 1424 try client_repo.restore(io, allocator, "."); 1425 } 1426 1427 // make sure fetch was successful 1428 { 1429 const oid_master = (try client_repo.readRef(io, .{ .kind = .head, .name = "master" })).?; 1430 try std.testing.expectEqualStrings(&commit1, &oid_master); 1431 } 1432 } 1433 1434 fn testPushLarge( 1435 comptime repo_kind: rp.RepoKind, 1436 comptime transport_def: net.TransportDefinition, 1437 comptime port: u16, 1438 comptime shell_out_to_git: bool, 1439 io: std.Io, 1440 allocator: std.mem.Allocator, 1441 ) !void { 1442 const temp_dir_name = "temp-testnet-push-large"; 1443 1444 // create the temp dir 1445 const cwd = std.Io.Dir.cwd(); 1446 var temp_dir_or_err = cwd.openDir(io, temp_dir_name, .{}); 1447 if (temp_dir_or_err) |*temp_dir| { 1448 temp_dir.close(io); 1449 try cwd.deleteTree(io, temp_dir_name); 1450 } else |_| {} 1451 var temp_dir = try cwd.createDirPathOpen(io, temp_dir_name, .{}); 1452 defer cwd.deleteTree(io, temp_dir_name) catch {}; 1453 defer temp_dir.close(io); 1454 1455 // init server 1456 var server = try Server(transport_def, temp_dir_name, port).init(io, allocator); 1457 try server.start(); 1458 defer server.stop(); 1459 1460 const cwd_path = try std.process.currentPathAlloc(io, allocator); 1461 defer allocator.free(cwd_path); 1462 1463 const server_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "repos", "server" }); 1464 defer allocator.free(server_path); 1465 1466 var server_repo = try rp.Repo(.xit, .{ .is_test = true }).init(io, allocator, .{ .path = server_path }); 1467 defer server_repo.deinit(io, allocator); 1468 1469 // add config 1470 switch (transport_def) { 1471 .file => try server_repo.addConfig(io, allocator, .{ .name = "core.bare", .value = "true" }), 1472 .wire => { 1473 try server_repo.addConfig(io, allocator, .{ .name = "core.bare", .value = "false" }); 1474 try server_repo.addConfig(io, allocator, .{ .name = "receive.denycurrentbranch", .value = "updateinstead" }); 1475 }, 1476 } 1477 try server_repo.addConfig(io, allocator, .{ .name = "http.receivepack", .value = "true" }); 1478 1479 // export server repo 1480 { 1481 const export_file = try server_repo.core.repo_dir.createFile(io, "git-daemon-export-ok", .{}); 1482 defer export_file.close(io); 1483 } 1484 1485 const client_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "client" }); 1486 defer allocator.free(client_path); 1487 1488 var client_repo = try rp.Repo(repo_kind, .{ .is_test = true }).init(io, allocator, .{ .path = client_path }); 1489 defer client_repo.deinit(io, allocator); 1490 1491 var client_dir = try cwd.openDir(io, client_path, .{}); 1492 defer client_dir.close(io); 1493 1494 { 1495 const hello_txt = try client_repo.core.work_dir.createFile(io, "hello.txt", .{ .truncate = true }); 1496 defer hello_txt.close(io); 1497 try hello_txt.writeStreamingAll(io, "hello, world!"); 1498 try client_repo.add(io, allocator, &.{"hello.txt"}); 1499 } 1500 1501 // copy files from current repo into client dir 1502 for (&[_][]const u8{"src"}) |dir_name| { 1503 var src_repo_dir = try cwd.openDir(io, dir_name, .{ .iterate = true }); 1504 defer src_repo_dir.close(io); 1505 1506 var dest_repo_dir = try client_dir.createDirPathOpen(io, dir_name, .{}); 1507 defer dest_repo_dir.close(io); 1508 1509 try copyDir(io, src_repo_dir, dest_repo_dir); 1510 1511 try client_repo.add(io, allocator, &.{dir_name}); 1512 } 1513 1514 _ = try client_repo.commit(io, allocator, .{ .message = "let there be light" }); 1515 1516 // change the files so git will send them as delta objects 1517 for (&[_][]const u8{"src"}) |dir_name| { 1518 var dest_repo_dir = try client_dir.createDirPathOpen(io, dir_name, .{ .open_options = .{ .iterate = true } }); 1519 defer dest_repo_dir.close(io); 1520 1521 { 1522 var iter = dest_repo_dir.iterate(); 1523 while (try iter.next(io)) |entry| { 1524 switch (entry.kind) { 1525 .file => { 1526 const file = try dest_repo_dir.openFile(io, entry.name, .{ .mode = .read_write }); 1527 defer file.close(io); 1528 var writer = file.writer(io, &.{}); 1529 try writer.interface.writeAll("EDIT"); 1530 }, 1531 else => {}, 1532 } 1533 } 1534 } 1535 1536 try client_repo.add(io, allocator, &.{dir_name}); 1537 } 1538 1539 const commit2 = try client_repo.commit(io, allocator, .{ .message = "more stuff" }); 1540 1541 // add remote 1542 { 1543 if (.windows == builtin.os.tag) { 1544 std.mem.replaceScalar(u8, server_path, '\\', '/'); 1545 } 1546 const separator = if (server_path[0] == '/') "" else "/"; 1547 1548 const remote_url = switch (transport_def) { 1549 //.file => try std.fmt.allocPrint(allocator, "file://{s}{s}", .{ separator, server_path }), 1550 .file => try std.fmt.allocPrint(allocator, "../server", .{}), // relative file paths work too 1551 .wire => |wire_kind| switch (wire_kind) { 1552 .http => try std.fmt.allocPrint(allocator, "http://localhost:{}/server", .{port}), 1553 .raw => try std.fmt.allocPrint(allocator, "git://localhost:{}/server", .{port}), 1554 .ssh => try std.fmt.allocPrint(allocator, "ssh://localhost:{}{s}{s}", .{ port, separator, server_path }), 1555 }, 1556 }; 1557 defer allocator.free(remote_url); 1558 1559 try client_repo.addRemote(io, allocator, .{ .name = "origin", .value = remote_url }); 1560 try client_repo.addConfig(io, allocator, .{ .name = "branch.master.remote", .value = "origin" }); 1561 } 1562 1563 const is_ssh = switch (transport_def) { 1564 .file => false, 1565 .wire => |wire_kind| .ssh == wire_kind, 1566 }; 1567 1568 const receive_pack_command = try receivePackCommand(is_ssh, allocator, cwd_path, port); 1569 defer allocator.free(receive_pack_command); 1570 1571 if (shell_out_to_git) { 1572 const priv_key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "key" }); 1573 defer allocator.free(priv_key_path); 1574 const ssh_config_arg = try std.fmt.allocPrint(allocator, "core.sshCommand=ssh -o StrictHostKeyChecking=no -o IdentityFile={s}", .{priv_key_path}); 1575 defer allocator.free(ssh_config_arg); 1576 1577 // shell out to git so it will send delta objects 1578 var process = try std.process.spawn(io, .{ 1579 .argv = if (is_ssh) 1580 &.{ "git", "-c", ssh_config_arg, "push", "--receive-pack", receive_pack_command, "origin", "master" } 1581 else 1582 &.{ "git", "push", "origin", "master" }, 1583 .cwd = .{ .path = client_path }, 1584 .stdin = .ignore, 1585 .stdout = .ignore, 1586 .stderr = .ignore, 1587 }); 1588 const term = try process.wait(io); 1589 if (term != .exited or term.exited != 0) { 1590 return error.GitCommandFailed; 1591 } 1592 } else { 1593 const ssh_cmd_maybe: ?[]const u8 = if (is_ssh) blk: { 1594 const known_hosts_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "known_hosts" }); 1595 defer allocator.free(known_hosts_path); 1596 1597 const priv_key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "key" }); 1598 defer allocator.free(priv_key_path); 1599 1600 break :blk try std.fmt.allocPrint(allocator, "ssh -o UserKnownHostsFile=\"{s}\" -o IdentityFile=\"{s}\"", .{ known_hosts_path, priv_key_path }); 1601 } else null; 1602 defer if (ssh_cmd_maybe) |ssh_cmd| allocator.free(ssh_cmd); 1603 1604 try client_repo.push( 1605 io, 1606 allocator, 1607 "origin", 1608 "master", 1609 false, 1610 .{ .wire = .{ .ssh = .{ 1611 .command = ssh_cmd_maybe, 1612 .receive_pack_command = receive_pack_command, 1613 } } }, 1614 ); 1615 1616 if (transport_def == .file) { 1617 // update the working dir 1618 try server_repo.restore(io, allocator, "."); 1619 } 1620 } 1621 1622 // make sure push was successful 1623 { 1624 const oid_master = (try server_repo.readRef(io, .{ .kind = .head, .name = "master" })).?; 1625 try std.testing.expectEqualStrings(&commit2, &oid_master); 1626 1627 const hello_txt = try temp_dir.openFile(io, "repos/server/hello.txt", .{}); 1628 defer hello_txt.close(io); 1629 } 1630 } 1631 1632 fn copyDir(io: std.Io, src_dir: std.Io.Dir, dest_dir: std.Io.Dir) !void { 1633 var iter = src_dir.iterate(); 1634 while (try iter.next(io)) |entry| { 1635 switch (entry.kind) { 1636 .file => try src_dir.copyFile(entry.name, dest_dir, entry.name, io, .{}), 1637 .directory => { 1638 try dest_dir.createDirPath(io, entry.name); 1639 var dest_entry_dir = try dest_dir.openDir(io, entry.name, .{ .access_sub_paths = true, .iterate = true, .follow_symlinks = false }); 1640 defer dest_entry_dir.close(io); 1641 var src_entry_dir = try src_dir.openDir(io, entry.name, .{ .access_sub_paths = true, .iterate = true, .follow_symlinks = false }); 1642 defer src_entry_dir.close(io); 1643 try copyDir(io, src_entry_dir, dest_entry_dir); 1644 }, 1645 else => {}, 1646 } 1647 } 1648 } 1649