642 // create the temp dir
643 const cwd = std.Io.Dir.cwd();
644 var temp_dir_or_err = cwd.openDir(io, temp_dir_name, .{});
645 if (temp_dir_or_err) |*temp_dir| {
646 temp_dir.close(io);
647 try cwd.deleteTree(io, temp_dir_name);
648 } else |_| {}
649 var temp_dir = try cwd.createDirPathOpen(io, temp_dir_name, .{});
650 defer cwd.deleteTree(io, temp_dir_name) catch {};
651 defer temp_dir.close(io);
652
653 // init server
654 var server = try Server(server_repo_kind, transport_def, temp_dir_name, port, use_serve_command).init(io, allocator);
655 try server.start();
656 defer server.stop();
657
658 const cwd_path = try std.process.currentPathAlloc(io, allocator);
659 defer allocator.free(cwd_path);
660
661 const server_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "server" });
662 defer allocator.free(server_path);
663
664 var server_repo = try rp.Repo(server_repo_kind, .{ .is_test = true }).init(io, allocator, .{ .path = server_path });
665 defer server_repo.deinit(io, allocator);
666
667 // make a commit
668 const commit1 = blk: {
669 const hello_txt = try server_repo.core.work_dir.createFile(io, "hello.txt", .{ .truncate = true });
670 defer hello_txt.close(io);
671 try hello_txt.writeStreamingAll(io, "hello, world!");
672 try server_repo.add(io, allocator, &.{"hello.txt"});
673 break :blk try server_repo.commit(io, allocator, .{ .message = "let there be light" });
674 };
675
676 // export server repo
677 {
678 const export_file = try server_repo.core.repo_dir.createFile(io, "git-daemon-export-ok", .{});
679 defer export_file.close(io);
680
681 try server_repo.addConfig(io, allocator, .{ .name = "uploadpack.allowAnySHA1InWant", .value = "true" });
682 }
683
684 // add a tag
685 _ = try server_repo.addTag(io, allocator, .{ .name = "1.0.0", .message = "hi" });
686
687 const client_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "client" });
688 defer allocator.free(client_path);
689
690 var client_repo = try rp.Repo(repo_kind, .{ .is_test = true }).init(io, allocator, .{ .path = client_path });
691 defer client_repo.deinit(io, allocator);
692
693 // add remote
694 {
695 if (.windows == builtin.os.tag) {
696 std.mem.replaceScalar(u8, server_path, '\\', '/');
697 }
698 const separator = if (server_path[0] == '/') "" else "/";
699
700 const remote_url = switch (transport_def) {
701 //.file => try std.fmt.allocPrint(allocator, "file://{s}{s}", .{ separator, server_path }),
702 .file => try std.fmt.allocPrint(allocator, "../server", .{}), // relative file paths work too
703 .wire => |wire_kind| switch (wire_kind) {
704 .http => try std.fmt.allocPrint(allocator, "http://localhost:{}/server", .{port}),
705 .raw => try std.fmt.allocPrint(allocator, "git://localhost:{}/server", .{port}),
706 .ssh => try std.fmt.allocPrint(allocator, "ssh://localhost:{}{s}{s}", .{ port, separator, server_path }),
707 },
708 };
709 defer allocator.free(remote_url);
710
711 try client_repo.addRemote(io, allocator, .{ .name = "origin", .value = remote_url });
712 try client_repo.addConfig(io, allocator, .{ .name = "branch.master.remote", .value = "origin" });
713 }
714
715 // create refspec with oid as a test
716 const oid_refspec = try std.fmt.allocPrint(allocator, "+{s}:refs/heads/foo", .{&commit1});
717 defer allocator.free(oid_refspec);
718
719 const refspecs = &.{
720 "+refs/heads/master:refs/heads/master",
721 oid_refspec,
722 };
723
724 const is_ssh = switch (transport_def) {
725 .file => false,
726 .wire => |wire_kind| .ssh == wire_kind,
727 };
728 const ssh_cmd_maybe: ?[]const u8 = if (is_ssh) blk: {
729 const known_hosts_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "known_hosts" });
730 defer allocator.free(known_hosts_path);
731
732 const priv_key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "key" });
733 defer allocator.free(priv_key_path);
734
735 break :blk try std.fmt.allocPrint(allocator, "ssh -o UserKnownHostsFile=\"{s}\" -o IdentityFile=\"{s}\"", .{ known_hosts_path, priv_key_path });
736 } else null;
737 defer if (ssh_cmd_maybe) |ssh_cmd| allocator.free(ssh_cmd);
738
739 const upload_pack_command = try uploadPackCommand(server_repo_kind, is_ssh, use_serve_command, allocator, cwd_path, port);
740 defer allocator.free(upload_pack_command);
741
742 try client_repo.fetch(
743 io,
744 allocator,
745 "origin",
746 .{ .refspecs = refspecs, .wire = .{ .ssh = .{
747 .command = ssh_cmd_maybe,
748 .upload_pack_command = upload_pack_command,
749 } } },
750 );
751
752 // update the working dir
753 try client_repo.restore(io, allocator, ".");
754
755 // make sure fetch was successful
756 {
757 const hello_txt = try temp_dir.openFile(io, "client/hello.txt", .{});
758 defer hello_txt.close(io);
759
760 try std.testing.expect(null != try client_repo.readRef(io, .{ .kind = .tag, .name = "1.0.0" }));
761 try std.testing.expect(null != try client_repo.readRef(io, .{ .kind = .head, .name = "foo" }));
762
763 const oid_master = (try client_repo.readRef(io, .{ .kind = .head, .name = "master" })).?;
764 try std.testing.expectEqualStrings(&commit1, &oid_master);
765 }
766
767 // make another commit
768 const commit2 = blk: {
769 const goodbye_txt = try server_repo.core.work_dir.createFile(io, "goodbye.txt", .{ .truncate = true });
770 defer goodbye_txt.close(io);
771 try goodbye_txt.writeStreamingAll(io, "goodbye, world!");
772 try server_repo.add(io, allocator, &.{"goodbye.txt"});
773 break :blk try server_repo.commit(io, allocator, .{ .message = "goodbye" });
774 };
775
776 try client_repo.fetch(
777 io,
778 allocator,
779 "origin",
780 .{ .refspecs = refspecs, .wire = .{ .ssh = .{
781 .command = ssh_cmd_maybe,
782 .upload_pack_command = upload_pack_command,
783 } } },
784 );
785
786 // update the working dir
787 try client_repo.restore(io, allocator, ".");
788
789 // make sure fetch was successful
790 {
791 const goodbye_txt = try temp_dir.openFile(io, "client/goodbye.txt", .{});
792 defer goodbye_txt.close(io);
793
794 const oid_master = (try client_repo.readRef(io, .{ .kind = .head, .name = "master" })).?;
795 try std.testing.expectEqualStrings(&commit2, &oid_master);
796 }
797 }
798
799 fn testPush(
800 comptime repo_kind: rp.RepoKind,
801 comptime server_repo_kind: rp.RepoKind,
802 comptime transport_def: net.TransportDefinition,
803 comptime port: u16,
804 comptime use_serve_command: bool,
805 io: std.Io,
806 allocator: std.mem.Allocator,
807 ) !void {
808 const temp_dir_name = "temp-testnet-push";
809
810 // create the temp dir
811 const cwd = std.Io.Dir.cwd();
812 var temp_dir_or_err = cwd.openDir(io, temp_dir_name, .{});
813 if (temp_dir_or_err) |*temp_dir| {
814 temp_dir.close(io);
815 try cwd.deleteTree(io, temp_dir_name);
816 } else |_| {}
817 var temp_dir = try cwd.createDirPathOpen(io, temp_dir_name, .{});
818 defer cwd.deleteTree(io, temp_dir_name) catch {};
819 defer temp_dir.close(io);
820
821 // init server
822 var server = try Server(server_repo_kind, transport_def, temp_dir_name, port, use_serve_command).init(io, allocator);
823 try server.start();
824 defer server.stop();
825
826 const cwd_path = try std.process.currentPathAlloc(io, allocator);
827 defer allocator.free(cwd_path);
828
829 const server_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "server" });
830 defer allocator.free(server_path);
831
832 var server_repo = try rp.Repo(server_repo_kind, .{ .is_test = true }).init(io, allocator, .{ .path = server_path });
833 defer server_repo.deinit(io, allocator);
834
835 // add config
836 switch (transport_def) {
837 .file => try server_repo.addConfig(io, allocator, .{ .name = "core.bare", .value = "true" }),
838 .wire => {
839 try server_repo.addConfig(io, allocator, .{ .name = "core.bare", .value = "false" });
840 try server_repo.addConfig(io, allocator, .{ .name = "receive.denycurrentbranch", .value = "updateinstead" });
841 },
842 }
843 try server_repo.addConfig(io, allocator, .{ .name = "http.receivepack", .value = "true" });
844
845 // export server repo
846 {
847 const export_file = try server_repo.core.repo_dir.createFile(io, "git-daemon-export-ok", .{});
848 defer export_file.close(io);
849 }
850
851 const client_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "client" });
852 defer allocator.free(client_path);
853
854 var client_repo = try rp.Repo(repo_kind, .{ .is_test = true }).init(io, allocator, .{ .path = client_path });
855 defer client_repo.deinit(io, allocator);
856
857 // make a commit
858 const commit1 = blk: {
859 const hello_txt = try client_repo.core.work_dir.createFile(io, "hello.txt", .{ .truncate = true });
860 defer hello_txt.close(io);
861 try hello_txt.writeStreamingAll(io, "hello, world!");
862 try client_repo.add(io, allocator, &.{"hello.txt"});
863 break :blk try client_repo.commit(io, allocator, .{ .message = "let there be light" });
864 };
865
866 // add a tag
867 _ = try client_repo.addTag(io, allocator, .{ .name = "1.0.0", .message = "hi" });
868
869 // add remote
870 {
871 if (.windows == builtin.os.tag) {
872 std.mem.replaceScalar(u8, server_path, '\\', '/');
873 }
874 const separator = if (server_path[0] == '/') "" else "/";
875
876 const remote_url = switch (transport_def) {
877 //.file => try std.fmt.allocPrint(allocator, "file://{s}{s}", .{ separator, server_path }),
878 .file => try std.fmt.allocPrint(allocator, "../server", .{}), // relative file paths work too
879 .wire => |wire_kind| switch (wire_kind) {
880 .http => try std.fmt.allocPrint(allocator, "http://localhost:{}/server", .{port}),
881 .raw => try std.fmt.allocPrint(allocator, "git://localhost:{}/server", .{port}),
882 .ssh => try std.fmt.allocPrint(allocator, "ssh://localhost:{}{s}{s}", .{ port, separator, server_path }),
883 },
884 };
885 defer allocator.free(remote_url);
886
887 try client_repo.addRemote(io, allocator, .{ .name = "origin", .value = remote_url });
888 try client_repo.addConfig(io, allocator, .{ .name = "branch.master.remote", .value = "origin" });
889 }
890
891 const refspecs = &.{
892 "refs/tags/1.0.0:refs/tags/1.0.0",
893 };
894
895 const is_ssh = switch (transport_def) {
896 .file => false,
897 .wire => |wire_kind| .ssh == wire_kind,
898 };
899 const ssh_cmd_maybe: ?[]const u8 = if (is_ssh) blk: {
900 const known_hosts_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "known_hosts" });
901 defer allocator.free(known_hosts_path);
902
903 const priv_key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "key" });
904 defer allocator.free(priv_key_path);
905
906 break :blk try std.fmt.allocPrint(allocator, "ssh -o UserKnownHostsFile=\"{s}\" -o IdentityFile=\"{s}\"", .{ known_hosts_path, priv_key_path });
907 } else null;
908 defer if (ssh_cmd_maybe) |ssh_cmd| allocator.free(ssh_cmd);
909
910 const upload_pack_command = try uploadPackCommand(server_repo_kind, is_ssh, use_serve_command, allocator, cwd_path, port);
911 defer allocator.free(upload_pack_command);
912 const receive_pack_command = try receivePackCommand(server_repo_kind, is_ssh, use_serve_command, allocator, cwd_path, port);
913 defer allocator.free(receive_pack_command);
914
915 try client_repo.push(
916 io,
917 allocator,
918 "origin",
919 "master",
920 false,
921 .{ .refspecs = refspecs, .wire = .{ .ssh = .{
922 .command = ssh_cmd_maybe,
923 .receive_pack_command = receive_pack_command,
924 } } },
925 );
926
927 // make sure push was successful
928 {
929 try std.testing.expect(null != try server_repo.readRef(io, .{ .kind = .tag, .name = "1.0.0" }));
930
931 const oid_master = (try server_repo.readRef(io, .{ .kind = .head, .name = "master" })).?;
932 try std.testing.expectEqualStrings(&commit1, &oid_master);
933 }
934
935 // make a commit on the server
936 {
937 const hello_txt = try server_repo.core.work_dir.createFile(io, "hello.txt", .{ .truncate = true });
938 defer hello_txt.close(io);
939 try hello_txt.writeStreamingAll(io, "hello, world from the server!");
940 try server_repo.add(io, allocator, &.{"hello.txt"});
941 _ = try server_repo.commit(io, allocator, .{ .message = "new commit from the server" });
942 }
943
944 // make another commit
945 const commit2 = blk: {
946 const goodbye_txt = try client_repo.core.work_dir.createFile(io, "goodbye.txt", .{ .truncate = true });
947 defer goodbye_txt.close(io);
948 try goodbye_txt.writeStreamingAll(io, "goodbye, world!");
949 try client_repo.add(io, allocator, &.{"goodbye.txt"});
950 break :blk try client_repo.commit(io, allocator, .{ .message = "goodbye" });
951 };
952
953 // can't push because server has commit not found locally
954 try std.testing.expectError(error.RemoteRefContainsCommitsNotFoundLocally, client_repo.push(
955 io,
956 allocator,
957 "origin",
958 "master",
959 false,
960 .{ .wire = .{ .ssh = .{
961 .command = ssh_cmd_maybe,
962 .receive_pack_command = receive_pack_command,
963 } } },
964 ));
965
966 // make a commit on the server with no parents, thus creating an incompatible git history
967 {
968 const hello_txt = try server_repo.core.work_dir.createFile(io, "hello.txt", .{ .truncate = true });
969 defer hello_txt.close(io);
970 try hello_txt.writeStreamingAll(io, "hello, world from the server again!");
971 try server_repo.add(io, allocator, &.{"hello.txt"});
972 _ = try server_repo.commit(io, allocator, .{ .message = "new git history on the server", .parent_oids = &.{} });
973 }
974
975 // can't push because commit doesn't exist locally
976 try std.testing.expectError(error.RemoteRefContainsCommitsNotFoundLocally, client_repo.push(
977 io,
978 allocator,
979 "origin",
980 "master",
981 false,
982 .{ .wire = .{ .ssh = .{
983 .command = ssh_cmd_maybe,
984 .receive_pack_command = receive_pack_command,
985 } } },
986 ));
987
988 // retrieve the commit object
989 try client_repo.fetch(
990 io,
991 allocator,
992 "origin",
993 .{ .wire = .{ .ssh = .{
994 .command = ssh_cmd_maybe,
995 .upload_pack_command = upload_pack_command,
996 } } },
997 );
998
999 // can't push because server's history is incompatible
1000 try std.testing.expectError(error.RemoteRefContainsIncompatibleHistory, client_repo.push(
1001 io,
1002 allocator,
1003 "origin",
1004 "master",
1005 false,
1006 .{ .wire = .{ .ssh = .{
1007 .command = ssh_cmd_maybe,
1008 .receive_pack_command = receive_pack_command,
1009 } } },
1010 ));
1011
1012 // test denyNonFastForwards (only for wire transports, file transport bypasses receive-pack)
1013 switch (transport_def) {
1014 .file => {},
1015 .wire => {
1016 // set denyNonFastForwards on server
1017 try server_repo.addConfig(io, allocator, .{ .name = "receive.denynonfastforwards", .value = "true" });
1018
1019 // save the server's current master ref
1020 const oid_before_denied_push = (try server_repo.readRef(io, .{ .kind = .head, .name = "master" })).?;
1021
1022 // force push should be rejected by server due to denyNonFastForwards
1023 try client_repo.push(
1024 io,
1025 allocator,
1026 "origin",
1027 "master",
1028 true,
1029 .{ .wire = .{ .ssh = .{
1030 .command = ssh_cmd_maybe,
1031 .receive_pack_command = receive_pack_command,
1032 } } },
1033 );
1034
1035 // verify the server ref was not updated (push was denied)
1036 {
1037 const oid_master = (try server_repo.readRef(io, .{ .kind = .head, .name = "master" })).?;
1038 try std.testing.expectEqualStrings(&oid_before_denied_push, &oid_master);
1039 }
1040
1041 // remove denyNonFastForwards from server
1042 try server_repo.removeConfig(io, allocator, .{ .name = "receive.denynonfastforwards" });
1043 },
1044 }
1045
1046 // force push
1047 try client_repo.push(
1048 io,
1049 allocator,
1050 "origin",
1051 "master",
1052 true,
1053 .{ .wire = .{ .ssh = .{
1054 .command = ssh_cmd_maybe,
1055 .receive_pack_command = receive_pack_command,
1056 } } },
1057 );
1058
1059 // make sure push was successful
1060 {
1061 const oid_master = (try server_repo.readRef(io, .{ .kind = .head, .name = "master" })).?;
1062 try std.testing.expectEqualStrings(&commit2, &oid_master);
1063 }
1064
1065 // remove the remote tag
1066 try client_repo.push(
1067 io,
1068 allocator,
1069 "origin",
1070 ":refs/tags/1.0.0",
1071 false,
1072 .{ .wire = .{ .ssh = .{
1073 .command = ssh_cmd_maybe,
1074 .receive_pack_command = receive_pack_command,
1075 } } },
1076 );
1077
1078 // make sure push was successful
1079 try std.testing.expect(null == try server_repo.readRef(io, .{ .kind = .tag, .name = "1.0.0" }));
1080 }
1081
1082 fn testPushCreatesMissingRepo(
1083 comptime repo_kind: rp.RepoKind,
1084 comptime server_repo_kind: rp.RepoKind,
1085 comptime transport_def: net.TransportDefinition,
1086 comptime port: u16,
1087 io: std.Io,
1088 allocator: std.mem.Allocator,
1089 ) !void {
1090 const temp_dir_name = "temp-testnet-push-create";
1091
1092 const cwd = std.Io.Dir.cwd();
1093 var temp_dir_or_err = cwd.openDir(io, temp_dir_name, .{});
1094 if (temp_dir_or_err) |*temp_dir| {
1095 temp_dir.close(io);
1096 try cwd.deleteTree(io, temp_dir_name);
1097 } else |_| {}
1098 var temp_dir = try cwd.createDirPathOpen(io, temp_dir_name, .{});
1099 defer cwd.deleteTree(io, temp_dir_name) catch {};
1100 defer temp_dir.close(io);
1101
1102 var server = try Server(server_repo_kind, transport_def, temp_dir_name, port, true).init(io, allocator);
1103 try server.start();
1104 defer server.stop();
1105
1106 const cwd_path = try std.process.currentPathAlloc(io, allocator);
1107 defer allocator.free(cwd_path);
1108
1109 const server_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "server" });
1110 defer allocator.free(server_path);
1111
1112 const client_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "client" });
1113 defer allocator.free(client_path);
1114
1115 var client_repo = try rp.Repo(repo_kind, .{ .is_test = true }).init(io, allocator, .{ .path = client_path });
1116 defer client_repo.deinit(io, allocator);
1117
1118 const commit1 = blk: {
1119 const hello_txt = try client_repo.core.work_dir.createFile(io, "hello.txt", .{ .truncate = true });
1120 defer hello_txt.close(io);
1121 try hello_txt.writeStreamingAll(io, "hello, world!");
1122 try client_repo.add(io, allocator, &.{"hello.txt"});
1123 break :blk try client_repo.commit(io, allocator, .{ .message = "let there be light" });
1124 };
1125
1126 _ = try client_repo.addTag(io, allocator, .{ .name = "1.0.0", .message = "hi" });
1127
1128 {
1129 if (.windows == builtin.os.tag) {
1130 std.mem.replaceScalar(u8, server_path, '\\', '/');
1131 }
1132 const separator = if (server_path[0] == '/') "" else "/";
1133
1134 const remote_url = switch (transport_def) {
1135 .file => unreachable,
1136 .wire => |wire_kind| switch (wire_kind) {
1137 .http => try std.fmt.allocPrint(allocator, "http://localhost:{}/server", .{port}),
1138 .raw => unreachable,
1139 .ssh => try std.fmt.allocPrint(allocator, "ssh://localhost:{}{s}{s}", .{ port, separator, server_path }),
1140 },
1141 };
1142 defer allocator.free(remote_url);
1143
1144 try client_repo.addRemote(io, allocator, .{ .name = "origin", .value = remote_url });
1145 try client_repo.addConfig(io, allocator, .{ .name = "branch.master.remote", .value = "origin" });
1146 }
1147
1148 const is_ssh = switch (transport_def) {
1149 .file => false,
1150 .wire => |wire_kind| .ssh == wire_kind,
1151 };
1152 const ssh_cmd_maybe: ?[]const u8 = if (is_ssh) blk: {
1153 const known_hosts_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "known_hosts" });
1154 defer allocator.free(known_hosts_path);
1155
1156 const priv_key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "key" });
1157 defer allocator.free(priv_key_path);
1158
1159 break :blk try std.fmt.allocPrint(allocator, "ssh -o UserKnownHostsFile=\"{s}\" -o IdentityFile=\"{s}\"", .{ known_hosts_path, priv_key_path });
1160 } else null;
1161 defer if (ssh_cmd_maybe) |ssh_cmd| allocator.free(ssh_cmd);
1162
1163 const receive_pack_command = try receivePackCommand(server_repo_kind, is_ssh, true, allocator, cwd_path, port);
1164 defer allocator.free(receive_pack_command);
1165
1166 try client_repo.push(
1167 io,
1168 allocator,
1169 "origin",
1170 "master",
1171 false,
1172 .{ .refspecs = &.{ "refs/tags/1.0.0:refs/tags/1.0.0" }, .wire = .{ .ssh = .{
1173 .command = ssh_cmd_maybe,
1174 .receive_pack_command = receive_pack_command,
1175 } } },
1176 );
1177
1178 var server_repo = try rp.Repo(server_repo_kind, .{ .is_test = true }).open(io, allocator, .{ .path = server_path });
1179 defer server_repo.deinit(io, allocator);
1180
1181 try std.testing.expect(null != try server_repo.readRef(io, .{ .kind = .tag, .name = "1.0.0" }));
1182
1183 const oid_master = (try server_repo.readRef(io, .{ .kind = .head, .name = "master" })).?;
1184 try std.testing.expectEqualStrings(&commit1, &oid_master);
1185 }
1186
1187 fn testClone(
1188 comptime repo_kind: rp.RepoKind,
1189 comptime server_repo_kind: rp.RepoKind,
1190 comptime transport_def: net.TransportDefinition,
1191 comptime port: u16,
1192 comptime shell_out_to_git: bool,
1193 comptime use_serve_command: bool,
1194 io: std.Io,
1195 allocator: std.mem.Allocator,
1196 ) !void {
1197 const temp_dir_name = "temp-testnet-clone";
1198
1199 // create the temp dir
1200 const cwd = std.Io.Dir.cwd();
1201 var temp_dir_or_err = cwd.openDir(io, temp_dir_name, .{});
1202 if (temp_dir_or_err) |*temp_dir| {
1203 temp_dir.close(io);
1204 try cwd.deleteTree(io, temp_dir_name);
1205 } else |_| {}
1206 var temp_dir = try cwd.createDirPathOpen(io, temp_dir_name, .{});
1207 defer cwd.deleteTree(io, temp_dir_name) catch {};
1208 defer temp_dir.close(io);
1209
1210 // init server
1211 var server = try Server(server_repo_kind, transport_def, temp_dir_name, port, use_serve_command).init(io, allocator);
1212 try server.start();
1213 defer server.stop();
1214
1215 const cwd_path = try std.process.currentPathAlloc(io, allocator);
1216 defer allocator.free(cwd_path);
1217
1218 const temp_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name });
1219 defer allocator.free(temp_path);
1220
1221 const server_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "server" });
1222 defer allocator.free(server_path);
1223
1224 // init server repo with default branch name as main
1225 // is_test must be false when shell_out_to_git so commits get real timestamps (needed for --shallow-since)
1226 var server_repo = try rp.Repo(server_repo_kind, .{ .is_test = !shell_out_to_git }).init(io, allocator, .{ .path = server_path, .create_default_branch = "main" });
1227 defer server_repo.deinit(io, allocator);
1228
1229 if (shell_out_to_git) {
1230 try server_repo.addConfig(io, allocator, .{ .name = "user.name", .value = "test" });
1231 try server_repo.addConfig(io, allocator, .{ .name = "user.email", .value = "test@test" });
1232 try server_repo.addConfig(io, allocator, .{ .name = "uploadpack.allowfilter", .value = "true" });
1233 }
1234
1235 // make a commit
1236 {
1237 const hello_txt = try server_repo.core.work_dir.createFile(io, "hello.txt", .{ .truncate = true });
1238 defer hello_txt.close(io);
1239 try hello_txt.writeStreamingAll(io, "hello, world!");
1240 try server_repo.add(io, allocator, &.{"hello.txt"});
1241 _ = try server_repo.commit(io, allocator, .{ .message = "let there be light" });
1242 }
1243
1244 // tag first commit
1245 _ = try server_repo.addTag(io, allocator, .{ .name = "v1", .message = "first" });
1246
1247 // make a commit
1248 {
1249 const goodbye_txt = try server_repo.core.work_dir.createFile(io, "goodbye.txt", .{ .truncate = true });
1250 defer goodbye_txt.close(io);
1251 try goodbye_txt.writeStreamingAll(io, "goodbye, world!");
1252 try server_repo.add(io, allocator, &.{"goodbye.txt"});
1253 _ = try server_repo.commit(io, allocator, .{ .message = "add goodbye file" });
1254 }
1255
1256 // export server repo
1257 {
1258 const export_file = try server_repo.core.repo_dir.createFile(io, "git-daemon-export-ok", .{});
1259 defer export_file.close(io);
1260 }
1261
1262 const client_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "client" });
1263 defer allocator.free(client_path);
1264
1265 // get remote url
1266 const remote_url = blk: {
1267 if (.windows == builtin.os.tag) {
1268 std.mem.replaceScalar(u8, server_path, '\\', '/');
1269 }
1270 const separator = if (server_path[0] == '/') "" else "/";
1271
1272 break :blk switch (transport_def) {
1273 //.file => try std.fmt.allocPrint(allocator, "file://{s}{s}", .{ separator, server_path }),
1274 .file => try std.fmt.allocPrint(allocator, "server", .{}), // relative file paths work too
1275 .wire => |wire_kind| switch (wire_kind) {
1276 .http => try std.fmt.allocPrint(allocator, "http://localhost:{}/server", .{port}),
1277 .raw => try std.fmt.allocPrint(allocator, "git://localhost:{}/server", .{port}),
1278 .ssh => try std.fmt.allocPrint(allocator, "ssh://localhost:{}{s}{s}", .{ port, separator, server_path }),
1279 },
1280 };
1281 };
1282 defer allocator.free(remote_url);
1283
1284 const is_ssh = switch (transport_def) {
1285 .file => false,
1286 .wire => |wire_kind| .ssh == wire_kind,
1287 };
1288
1289 const upload_pack_command = try uploadPackCommand(server_repo_kind, is_ssh, use_serve_command, allocator, cwd_path, port);
1290 defer allocator.free(upload_pack_command);
1291
1292 if (shell_out_to_git) {
1293 const priv_key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "key" });
1294 defer allocator.free(priv_key_path);
1295 const ssh_config_arg = try std.fmt.allocPrint(allocator, "core.sshCommand=ssh -o StrictHostKeyChecking=no -o IdentityFile={s}", .{priv_key_path});
1296 defer allocator.free(ssh_config_arg);
1297
1298 {
1299 var process = try std.process.spawn(io, .{
1300 .argv = if (is_ssh)
1301 &.{ "git", "-c", ssh_config_arg, "clone", "--upload-pack", upload_pack_command, "--depth", "1", remote_url, "client" }
1302 else
1303 &.{ "git", "clone", "--depth", "1", remote_url, "client" },
1304 .cwd = .{ .path = temp_path },
1305 .stdin = .ignore,
1306 .stdout = .ignore,
1307 .stderr = .ignore,
1308 });
1309 const term = try process.wait(io);
1310 if (term != .exited) {
1311 return error.GitCommandFailed;
1312 }
1313 }
1314
1315 // make sure shallow clone was successful
1316 {
1317 const hello_txt = try temp_dir.openFile(io, "client/hello.txt", .{});
1318 hello_txt.close(io);
1319 }
1320
1321 // make a third commit on the server
1322 {
1323 const extra_txt = try server_repo.core.work_dir.createFile(io, "extra.txt", .{ .truncate = true });
1324 defer extra_txt.close(io);
1325 try extra_txt.writeStreamingAll(io, "extra content");
1326 try server_repo.add(io, allocator, &.{"extra.txt"});
1327 _ = try server_repo.commit(io, allocator, .{ .message = "add extra file" });
1328 }
1329
1330 // pull --unshallow to deepen the clone and get the new commit
1331 {
1332 var process = try std.process.spawn(io, .{
1333 .argv = if (is_ssh)
1334 &.{ "git", "-c", ssh_config_arg, "pull", "--upload-pack", upload_pack_command, "--unshallow" }
1335 else
1336 &.{ "git", "pull", "--unshallow" },
1337 .cwd = .{ .path = client_path },
1338 .stdin = .ignore,
1339 .stdout = .ignore,
1340 .stderr = .ignore,
1341 });
1342 const term = try process.wait(io);
1343 if (term != .exited) {
1344 return error.GitCommandFailed;
1345 }
1346 }
1347
1348 // make sure unshallow pull was successful
1349 {
1350 const extra_txt = try temp_dir.openFile(io, "client/extra.txt", .{});
1351 extra_txt.close(io);
1352 }
1353
1354 // delete client and clone again with --shallow-since
1355 try cwd.deleteTree(io, temp_dir_name ++ "/client");
1356
1357 {
1358 var process = try std.process.spawn(io, .{
1359 .argv = if (is_ssh)
1360 &.{ "git", "-c", ssh_config_arg, "clone", "--upload-pack", upload_pack_command, "--shallow-since=2000-01-01", remote_url, "client" }
1361 else
1362 &.{ "git", "clone", "--shallow-since=2000-01-01", remote_url, "client" },
1363 .cwd = .{ .path = temp_path },
1364 .stdin = .ignore,
1365 .stdout = .ignore,
1366 .stderr = .ignore,
1367 });
1368 const term = try process.wait(io);
1369 if (term != .exited) {
1370 return error.GitCommandFailed;
1371 }
1372 }
1373
1374 // make sure shallow clone was successful
1375 {
1376 const hello_txt = try temp_dir.openFile(io, "client/hello.txt", .{});
1377 hello_txt.close(io);
1378 }
1379
1380 // delete client and clone again with --shallow-exclude
1381 try cwd.deleteTree(io, temp_dir_name ++ "/client");
1382
1383 {
1384 var process = try std.process.spawn(io, .{
1385 .argv = if (is_ssh)
1386 &.{ "git", "-c", ssh_config_arg, "clone", "--upload-pack", upload_pack_command, "--shallow-exclude=v1", remote_url, "client" }
1387 else
1388 &.{ "git", "clone", "--shallow-exclude=v1", remote_url, "client" },
1389 .cwd = .{ .path = temp_path },
1390 .stdin = .ignore,
1391 .stdout = .ignore,
1392 .stderr = .ignore,
1393 });
1394 const term = try process.wait(io);
1395 if (term != .exited) {
1396 return error.GitCommandFailed;
1397 }
1398 }
1399
1400 // make sure shallow clone was successful
1401 {
1402 const hello_txt = try temp_dir.openFile(io, "client/hello.txt", .{});
1403 hello_txt.close(io);
1404 }
1405
1406 // delete client and clone again with --filter=blob:none
1407 try cwd.deleteTree(io, temp_dir_name ++ "/client");
1408
1409 {
1410 var process = try std.process.spawn(io, .{
1411 .argv = if (is_ssh)
1412 &.{ "git", "-c", ssh_config_arg, "clone", "--upload-pack", upload_pack_command, "--filter=blob:none", remote_url, "client" }
1413 else
1414 &.{ "git", "clone", "--filter=blob:none", remote_url, "client" },
1415 .cwd = .{ .path = temp_path },
1416 .stdin = .ignore,
1417 .stdout = .ignore,
1418 .stderr = .ignore,
1419 });
1420 const term = try process.wait(io);
1421 if (term != .exited) {
1422 return error.GitCommandFailed;
1423 }
1424 }
1425
1426 // make sure partial clone was successful
1427 {
1428 const hello_txt = try temp_dir.openFile(io, "client/hello.txt", .{});
1429 hello_txt.close(io);
1430 }
1431
1432 // delete client and clone again with --filter=tree:0
1433 try cwd.deleteTree(io, temp_dir_name ++ "/client");
1434
1435 {
1436 var process = try std.process.spawn(io, .{
1437 .argv = if (is_ssh)
1438 &.{ "git", "-c", ssh_config_arg, "clone", "--upload-pack", upload_pack_command, "--filter=tree:0", remote_url, "client" }
1439 else
1440 &.{ "git", "clone", "--filter=tree:0", remote_url, "client" },
1441 .cwd = .{ .path = temp_path },
1442 .stdin = .ignore,
1443 .stdout = .ignore,
1444 .stderr = .ignore,
1445 });
1446 const term = try process.wait(io);
1447 if (term != .exited) {
1448 return error.GitCommandFailed;
1449 }
1450 }
1451
1452 // make sure treeless clone was successful
1453 {
1454 const goodbye_txt = try temp_dir.openFile(io, "client/goodbye.txt", .{});
1455 goodbye_txt.close(io);
1456 }
1457 } else {
1458 const ssh_cmd_maybe: ?[]const u8 = if (is_ssh) blk: {
1459 const known_hosts_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "known_hosts" });
1460 defer allocator.free(known_hosts_path);
1461
1462 const priv_key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "key" });
1463 defer allocator.free(priv_key_path);
1464
1465 break :blk try std.fmt.allocPrint(allocator, "ssh -o UserKnownHostsFile=\"{s}\" -o IdentityFile=\"{s}\"", .{ known_hosts_path, priv_key_path });
1466 } else null;
1467 defer if (ssh_cmd_maybe) |ssh_cmd| allocator.free(ssh_cmd);
1468
1469 // clone repo
1470 var client_repo = try rp.Repo(repo_kind, .{ .is_test = true }).clone(
1471 io,
1472 allocator,
1473 remote_url,
1474 temp_path,
1475 client_path,
1476 .{ .wire = .{ .ssh = .{
1477 .command = ssh_cmd_maybe,
1478 .upload_pack_command = upload_pack_command,
1479 } } },
1480 );
1481 defer client_repo.deinit(io, allocator);
1482
1483 // make sure HEAD points to the right default branch
1484 var current_branch_buffer = [_]u8{0} ** rf.MAX_REF_CONTENT_SIZE;
1485 const head = try client_repo.head(io, ¤t_branch_buffer);
1486 try std.testing.expectEqualStrings("main", head.ref.name);
1487
1488 // make sure clone was successful
1489 const hello_txt = try temp_dir.openFile(io, "client/hello.txt", .{});
1490 defer hello_txt.close(io);
1491 }
1492 }
1493
1494 fn testFetchLarge(
1495 comptime repo_kind: rp.RepoKind,
1496 comptime server_repo_kind: rp.RepoKind,
1497 comptime transport_def: net.TransportDefinition,
1498 comptime port: u16,
1499 comptime shell_out_to_git: bool,
1500 comptime use_serve_command: bool,
1501 io: std.Io,
1502 allocator: std.mem.Allocator,
1503 ) !void {
1504 const temp_dir_name = "temp-testnet-fetch-large";
1505
1506 // create the temp dir
1507 const cwd = std.Io.Dir.cwd();
1508 var temp_dir_or_err = cwd.openDir(io, temp_dir_name, .{});
1509 if (temp_dir_or_err) |*temp_dir| {
1510 temp_dir.close(io);
1511 try cwd.deleteTree(io, temp_dir_name);
1512 } else |_| {}
1513 var temp_dir = try cwd.createDirPathOpen(io, temp_dir_name, .{});
1514 defer cwd.deleteTree(io, temp_dir_name) catch {};
1515 defer temp_dir.close(io);
1516
1517 // init server
1518 var server = try Server(server_repo_kind, transport_def, temp_dir_name, port, use_serve_command).init(io, allocator);
1519 try server.start();
1520 defer server.stop();
1521
1522 const cwd_path = try std.process.currentPathAlloc(io, allocator);
1523 defer allocator.free(cwd_path);
1524
1525 const server_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "server" });
1526 defer allocator.free(server_path);
1527
1528 var server_repo = try rp.Repo(server_repo_kind, .{ .is_test = true }).init(io, allocator, .{ .path = server_path });
1529 defer server_repo.deinit(io, allocator);
1530
1531 var server_dir = try cwd.openDir(io, server_path, .{});
1532 defer server_dir.close(io);
1533
1534 // copy files from current repo into server dir
1535 for (&[_][]const u8{"src"}) |dir_name| {
1536 var src_repo_dir = try cwd.openDir(io, dir_name, .{ .iterate = true });
1537 defer src_repo_dir.close(io);
1538
1539 var dest_repo_dir = try server_dir.createDirPathOpen(io, dir_name, .{});
1540 defer dest_repo_dir.close(io);
1541
1542 try copyDir(io, src_repo_dir, dest_repo_dir);
1543
1544 try server_repo.add(io, allocator, &.{dir_name});
1545 }
1546
1547 // make a commit
1548 const commit1 = try server_repo.commit(io, allocator, .{ .message = "let there be light" });
1549
1550 // export server repo
1551 {
1552 const export_file = try server_repo.core.repo_dir.createFile(io, "git-daemon-export-ok", .{});
1553 defer export_file.close(io);
1554 }
1555
1556 if (shell_out_to_git) {
1557 try server_repo.addConfig(io, allocator, .{ .name = "uploadpack.allowrefinwant", .value = "true" });
1558 }
1559
1560 const client_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "client" });
1561 defer allocator.free(client_path);
1562
1563 var client_repo = try rp.Repo(repo_kind, .{ .is_test = true }).init(io, allocator, .{ .path = client_path });
1564 defer client_repo.deinit(io, allocator);
1565
1566 // add remote
1567 {
1568 if (.windows == builtin.os.tag) {
1569 std.mem.replaceScalar(u8, server_path, '\\', '/');
1570 }
1571 const separator = if (server_path[0] == '/') "" else "/";
1572
1573 const remote_url = switch (transport_def) {
1574 //.file => try std.fmt.allocPrint(allocator, "file://{s}{s}", .{ separator, server_path }),
1575 .file => try std.fmt.allocPrint(allocator, "../server", .{}), // relative file paths work too
1576 .wire => |wire_kind| switch (wire_kind) {
1577 .http => try std.fmt.allocPrint(allocator, "http://localhost:{}/server", .{port}),
1578 .raw => try std.fmt.allocPrint(allocator, "git://localhost:{}/server", .{port}),
1579 .ssh => try std.fmt.allocPrint(allocator, "ssh://localhost:{}{s}{s}", .{ port, separator, server_path }),
1580 },
1581 };
1582 defer allocator.free(remote_url);
1583
1584 try client_repo.addRemote(io, allocator, .{ .name = "origin", .value = remote_url });
1585 try client_repo.addConfig(io, allocator, .{ .name = "branch.master.remote", .value = "origin" });
1586 }
1587
1588 const is_ssh = switch (transport_def) {
1589 .file => false,
1590 .wire => |wire_kind| .ssh == wire_kind,
1591 };
1592
1593 const upload_pack_command = try uploadPackCommand(server_repo_kind, is_ssh, use_serve_command, allocator, cwd_path, port);
1594 defer allocator.free(upload_pack_command);
1595
1596 if (shell_out_to_git) {
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 const ssh_config_arg = try std.fmt.allocPrint(allocator, "core.sshCommand=ssh -o StrictHostKeyChecking=no -o IdentityFile={s}", .{priv_key_path});
1600 defer allocator.free(ssh_config_arg);
1601
1602 {
1603 var process = try std.process.spawn(io, .{
1604 .argv = if (is_ssh)
1605 &.{ "git", "-c", ssh_config_arg, "pull", "--upload-pack", upload_pack_command, "origin", "master" }
1606 else
1607 &.{ "git", "pull", "origin", "master" },
1608 .cwd = .{ .path = client_path },
1609 .stdin = .ignore,
1610 .stdout = .ignore,
1611 .stderr = .ignore,
1612 });
1613 const term = try process.wait(io);
1614 if (term != .exited) {
1615 return error.GitCommandFailed;
1616 }
1617 }
1618
1619 // make sure pull was successful
1620 {
1621 const oid_master = (try client_repo.readRef(io, .{ .kind = .head, .name = "master" })).?;
1622 try std.testing.expectEqualStrings(&commit1, &oid_master);
1623 }
1624
1625 // make another commit on the server
1626 const commit2 = blk: {
1627 const extra_txt = try server_repo.core.work_dir.createFile(io, "extra.txt", .{ .truncate = true });
1628 defer extra_txt.close(io);
1629 try extra_txt.writeStreamingAll(io, "extra content");
1630 try server_repo.add(io, allocator, &.{"extra.txt"});
1631 break :blk try server_repo.commit(io, allocator, .{ .message = "add extra file" });
1632 };
1633
1634 // fetch with ref-in-want (git uses want-ref in protocol v2 when fetching named refs)
1635 {
1636 var process = try std.process.spawn(io, .{
1637 .argv = if (is_ssh)
1638 &.{ "git", "-c", ssh_config_arg, "fetch", "--upload-pack", upload_pack_command, "origin", "master" }
1639 else
1640 &.{ "git", "fetch", "origin", "master" },
1641 .cwd = .{ .path = client_path },
1642 .stdin = .ignore,
1643 .stdout = .ignore,
1644 .stderr = .ignore,
1645 });
1646 const term = try process.wait(io);
1647 if (term != .exited) {
1648 return error.GitCommandFailed;
1649 }
1650 }
1651
1652 // make sure fetch with want-ref was successful
1653 {
1654 const oid_remote_master = (try client_repo.readRef(io, .{ .kind = .{ .remote = "origin" }, .name = "master" })).?;
1655 try std.testing.expectEqualStrings(&commit2, &oid_remote_master);
1656 }
1657 } else {
1658 const refspecs = &.{
1659 "+refs/heads/master:refs/heads/master",
1660 };
1661
1662 const ssh_cmd_maybe: ?[]const u8 = if (is_ssh) blk: {
1663 const known_hosts_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "known_hosts" });
1664 defer allocator.free(known_hosts_path);
1665
1666 const priv_key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "key" });
1667 defer allocator.free(priv_key_path);
1668
1669 break :blk try std.fmt.allocPrint(allocator, "ssh -o UserKnownHostsFile=\"{s}\" -o IdentityFile=\"{s}\"", .{ known_hosts_path, priv_key_path });
1670 } else null;
1671 defer if (ssh_cmd_maybe) |ssh_cmd| allocator.free(ssh_cmd);
1672
1673 try client_repo.fetch(
1674 io,
1675 allocator,
1676 "origin",
1677 .{ .refspecs = refspecs, .wire = .{ .ssh = .{
1678 .command = ssh_cmd_maybe,
1679 .upload_pack_command = upload_pack_command,
1680 } } },
1681 );
1682
1683 // update the working dir
1684 try client_repo.restore(io, allocator, ".");
1685 }
1686
1687 // make sure fetch was successful
1688 {
1689 const oid_master = (try client_repo.readRef(io, .{ .kind = .head, .name = "master" })).?;
1690 try std.testing.expectEqualStrings(&commit1, &oid_master);
1691 }
1692 }
1693
1694 fn testPushLarge(
1695 comptime repo_kind: rp.RepoKind,
1696 comptime server_repo_kind: rp.RepoKind,
1697 comptime transport_def: net.TransportDefinition,
1698 comptime port: u16,
1699 comptime shell_out_to_git: bool,
1700 comptime use_serve_command: bool,
1701 io: std.Io,
1702 allocator: std.mem.Allocator,
1703 ) !void {
1704 const temp_dir_name = "temp-testnet-push-large";
1705
1706 // create the temp dir
1707 const cwd = std.Io.Dir.cwd();
1708 var temp_dir_or_err = cwd.openDir(io, temp_dir_name, .{});
1709 if (temp_dir_or_err) |*temp_dir| {
1710 temp_dir.close(io);
1711 try cwd.deleteTree(io, temp_dir_name);
1712 } else |_| {}
1713 var temp_dir = try cwd.createDirPathOpen(io, temp_dir_name, .{});
1714 defer cwd.deleteTree(io, temp_dir_name) catch {};
1715 defer temp_dir.close(io);
1716
1717 // init server
1718 var server = try Server(server_repo_kind, transport_def, temp_dir_name, port, use_serve_command).init(io, allocator);
1719 try server.start();
1720 defer server.stop();
1721
1722 const cwd_path = try std.process.currentPathAlloc(io, allocator);
1723 defer allocator.free(cwd_path);
1724
1725 const server_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "server" });
1726 defer allocator.free(server_path);
1727
1728 var server_repo = try rp.Repo(server_repo_kind, .{ .is_test = true }).init(io, allocator, .{ .path = server_path });
1729 defer server_repo.deinit(io, allocator);
1730
1731 // add config
1732 switch (transport_def) {
1733 .file => try server_repo.addConfig(io, allocator, .{ .name = "core.bare", .value = "true" }),
1734 .wire => {
1735 try server_repo.addConfig(io, allocator, .{ .name = "core.bare", .value = "false" });
1736 try server_repo.addConfig(io, allocator, .{ .name = "receive.denycurrentbranch", .value = "updateinstead" });
1737 },
1738 }
1739 try server_repo.addConfig(io, allocator, .{ .name = "http.receivepack", .value = "true" });
1740
1741 // export server repo
1742 {
1743 const export_file = try server_repo.core.repo_dir.createFile(io, "git-daemon-export-ok", .{});
1744 defer export_file.close(io);
1745 }
1746
1747 const client_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "client" });
1748 defer allocator.free(client_path);
1749
1750 var client_repo = try rp.Repo(repo_kind, .{ .is_test = true }).init(io, allocator, .{ .path = client_path });
1751 defer client_repo.deinit(io, allocator);
1752
1753 var client_dir = try cwd.openDir(io, client_path, .{});
1754 defer client_dir.close(io);
1755
1756 {
1757 const hello_txt = try client_repo.core.work_dir.createFile(io, "hello.txt", .{ .truncate = true });
1758 defer hello_txt.close(io);
1759 try hello_txt.writeStreamingAll(io, "hello, world!");
1760 try client_repo.add(io, allocator, &.{"hello.txt"});
1761 }
1762
1763 // copy files from current repo into client dir
1764 for (&[_][]const u8{"src"}) |dir_name| {
1765 var src_repo_dir = try cwd.openDir(io, dir_name, .{ .iterate = true });
1766 defer src_repo_dir.close(io);
1767
1768 var dest_repo_dir = try client_dir.createDirPathOpen(io, dir_name, .{});
1769 defer dest_repo_dir.close(io);
1770
1771 try copyDir(io, src_repo_dir, dest_repo_dir);
1772
1773 try client_repo.add(io, allocator, &.{dir_name});
1774 }
1775
1776 _ = try client_repo.commit(io, allocator, .{ .message = "let there be light" });
1777
1778 // change the files so git will send them as delta objects
1779 for (&[_][]const u8{"src"}) |dir_name| {
1780 var dest_repo_dir = try client_dir.createDirPathOpen(io, dir_name, .{ .open_options = .{ .iterate = true } });
1781 defer dest_repo_dir.close(io);
1782
1783 {
1784 var iter = dest_repo_dir.iterate();
1785 while (try iter.next(io)) |entry| {
1786 switch (entry.kind) {
1787 .file => {
1788 const file = try dest_repo_dir.openFile(io, entry.name, .{ .mode = .read_write });
1789 defer file.close(io);
1790 var writer = file.writer(io, &.{});
1791 try writer.interface.writeAll("EDIT");
1792 },
1793 else => {},
1794 }
1795 }
1796 }
1797
1798 try client_repo.add(io, allocator, &.{dir_name});
1799 }
1800
1801 const commit2 = try client_repo.commit(io, allocator, .{ .message = "more stuff" });
1802
1803 // add remote
1804 {
1805 if (.windows == builtin.os.tag) {
1806 std.mem.replaceScalar(u8, server_path, '\\', '/');
1807 }
1808 const separator = if (server_path[0] == '/') "" else "/";
1809
1810 const remote_url = switch (transport_def) {
1811 //.file => try std.fmt.allocPrint(allocator, "file://{s}{s}", .{ separator, server_path }),
1812 .file => try std.fmt.allocPrint(allocator, "../server", .{}), // relative file paths work too
1813 .wire => |wire_kind| switch (wire_kind) {
1814 .http => try std.fmt.allocPrint(allocator, "http://localhost:{}/server", .{port}),
1815 .raw => try std.fmt.allocPrint(allocator, "git://localhost:{}/server", .{port}),
1816 .ssh => try std.fmt.allocPrint(allocator, "ssh://localhost:{}{s}{s}", .{ port, separator, server_path }),
1817 },
1818 };
1819 defer allocator.free(remote_url);
1820
1821 try client_repo.addRemote(io, allocator, .{ .name = "origin", .value = remote_url });
1822 try client_repo.addConfig(io, allocator, .{ .name = "branch.master.remote", .value = "origin" });
1823 }
1824
1825 const is_ssh = switch (transport_def) {
1826 .file => false,
1827 .wire => |wire_kind| .ssh == wire_kind,
1828 };
1829
1830 const receive_pack_command = try receivePackCommand(server_repo_kind, is_ssh, use_serve_command, allocator, cwd_path, port);
1831 defer allocator.free(receive_pack_command);
1832
1833 if (shell_out_to_git) {
1834 const priv_key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "key" });
1835 defer allocator.free(priv_key_path);
1836 const ssh_config_arg = try std.fmt.allocPrint(allocator, "core.sshCommand=ssh -o StrictHostKeyChecking=no -o IdentityFile={s}", .{priv_key_path});
1837 defer allocator.free(ssh_config_arg);
1838
1839 // shell out to git so it will send delta objects
1840 var process = try std.process.spawn(io, .{
1841 .argv = if (is_ssh)
1842 &.{ "git", "-c", ssh_config_arg, "push", "--receive-pack", receive_pack_command, "origin", "master" }
1843 else
1844 &.{ "git", "push", "origin", "master" },
1845 .cwd = .{ .path = client_path },
1846 .stdin = .ignore,
1847 .stdout = .ignore,
1848 .stderr = .ignore,
1849 });
1850 const term = try process.wait(io);
1851 if (term != .exited) {
1852 return error.GitCommandFailed;
1853 }
1854 } else {
1855 const ssh_cmd_maybe: ?[]const u8 = if (is_ssh) blk: {
1856 const known_hosts_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "known_hosts" });
1857 defer allocator.free(known_hosts_path);
1858
1859 const priv_key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "key" });
1860 defer allocator.free(priv_key_path);
1861
1862 break :blk try std.fmt.allocPrint(allocator, "ssh -o UserKnownHostsFile=\"{s}\" -o IdentityFile=\"{s}\"", .{ known_hosts_path, priv_key_path });
1863 } else null;
1864 defer if (ssh_cmd_maybe) |ssh_cmd| allocator.free(ssh_cmd);
1865
1866 try client_repo.push(
1867 io,
1868 allocator,
1869 "origin",
1870 "master",
1871 false,
1872 .{ .wire = .{ .ssh = .{
1873 .command = ssh_cmd_maybe,
1874 .receive_pack_command = receive_pack_command,
1875 } } },
1876 );
1877
1878 if (transport_def == .file) {
1879 // update the working dir
1880 try server_repo.restore(io, allocator, ".");
1881 }
1882 }
1883
1884 // make sure push was successful
1885 {
1886 const oid_master = (try server_repo.readRef(io, .{ .kind = .head, .name = "master" })).?;
1887 try std.testing.expectEqualStrings(&commit2, &oid_master);
1888
1889 const hello_txt = try temp_dir.openFile(io, "server/hello.txt", .{});
1890 defer hello_txt.close(io);
1891 }
1892 }
1893
1894 fn copyDir(io: std.Io, src_dir: std.Io.Dir, dest_dir: std.Io.Dir) !void {
1895 var iter = src_dir.iterate();
1896 while (try iter.next(io)) |entry| {
1897 switch (entry.kind) {
1898 .file => try src_dir.copyFile(entry.name, dest_dir, entry.name, io, .{}),
1899 .directory => {
1900 try dest_dir.createDirPath(io, entry.name);
1901 var dest_entry_dir = try dest_dir.openDir(io, entry.name, .{ .access_sub_paths = true, .iterate = true, .follow_symlinks = false });
1902 defer dest_entry_dir.close(io);
1903 var src_entry_dir = try src_dir.openDir(io, entry.name, .{ .access_sub_paths = true, .iterate = true, .follow_symlinks = false });
1904 defer src_entry_dir.close(io);
1905 try copyDir(io, src_entry_dir, dest_entry_dir);
1906 },
1907 else => {},
1908 }
1909 }
1910 }
1911