╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ test.zig ─────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
171 .net_server = net_server, 172 .server_thread = undefined, 173 }, 174 }; 175 }, 176 .raw => unreachable, 177 .ssh => { 178 // create priv host key 179 const host_key_file = try std.Io.Dir.cwd().createFile(io, temp_dir_name ++ "/host_key", .{}); 180 defer host_key_file.close(io); 181 try host_key_file.writeStreamingAll(io, 182 \\-----BEGIN OPENSSH PRIVATE KEY----- 183 \\b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS 184 \\1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQS1ppUfk8n7yvVKEgz3tXjt4q76VGuj 185 \\LcQlRwmogzovV40LLcX0aTObZlQaLWfzJMNpCa/ztMpQlr86nsarE4lEAAAAqLe43zK3uN 186 \\8yAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLWmlR+TyfvK9UoS 187 \\DPe1eO3irvpUa6MtxCVHCaiDOi9XjQstxfRpM5tmVBotZ/Mkw2kJr/O0ylCWvzqexqsTiU 188 \\QAAAAgQ+LCk30ZNJxb2Da5JL+QOFWCMf7bgXCWcEzhEGGvFWYAAAALcmFkYXJAcm9hcmsB 189 \\AgMEBQ== 190 \\-----END OPENSSH PRIVATE KEY----- 191 \\ 192 ); 193 if (.windows != builtin.os.tag) { 194 try host_key_file.setPermissions(io, @enumFromInt(0o600)); 195 } 196 197 // create priv client key 198 const priv_key_file = try std.Io.Dir.cwd().createFile(io, temp_dir_name ++ "/key", .{}); 199 defer priv_key_file.close(io); 200 try priv_key_file.writeStreamingAll(io, 201 \\-----BEGIN OPENSSH PRIVATE KEY----- 202 \\b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 203 \\QyNTUxOQAAACCniLPJiaooAWecvOCeAjoJwCSeWxzysvpTNkpYjF22JgAAAJA+7hikPu4Y 204 \\pAAAAAtzc2gtZWQyNTUxOQAAACCniLPJiaooAWecvOCeAjoJwCSeWxzysvpTNkpYjF22Jg 205 \\AAAEDVlopOMnKt/7by/IA8VZvQXUS/O6VLkixOqnnahUdPCKeIs8mJqigBZ5y84J4COgnA 206 \\JJ5bHPKy+lM2SliMXbYmAAAAC3JhZGFyQHJvYXJrAQI= 207 \\-----END OPENSSH PRIVATE KEY----- 208 \\ 209 ); 210 if (.windows != builtin.os.tag) { 211 try priv_key_file.setPermissions(io, @enumFromInt(0o600)); 212 } 213 214 // create pub key 215 const pub_key_file = try std.Io.Dir.cwd().createFile(io, temp_dir_name ++ "/key.pub", .{}); 216 defer pub_key_file.close(io); 217 try pub_key_file.writeStreamingAll(io, 218 \\ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKeIs8mJqigBZ5y84J4COgnAJJ5bHPKy+lM2SliMXbYm radar@roark 219 \\ 220 ); 221 if (.windows != builtin.os.tag) { 222 try pub_key_file.setPermissions(io, @enumFromInt(0o600)); 223 } 224 225 // create authorized_keys file 226 const auth_keys_file = try std.Io.Dir.cwd().createFile(io, temp_dir_name ++ "/authorized_keys", .{}); 227 defer auth_keys_file.close(io); 228 try auth_keys_file.writeStreamingAll(io, 229 \\ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKeIs8mJqigBZ5y84J4COgnAJJ5bHPKy+lM2SliMXbYm radar@roark 230 \\ 231 ); 232 if (.windows != builtin.os.tag) { 233 try auth_keys_file.setPermissions(io, @enumFromInt(0o600)); 234 } 235 236 // create known_hosts file 237 const known_hosts_file = try std.Io.Dir.cwd().createFile(io, temp_dir_name ++ "/known_hosts", .{}); 238 defer known_hosts_file.close(io); 239 const port_str = std.fmt.comptimePrint("{}", .{port}); 240 try known_hosts_file.writeStreamingAll(io, "[localhost]:" ++ port_str ++ " ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLWmlR+TyfvK9UoSDPe1eO3irvpUa6MtxCVHCaiDOi9XjQstxfRpM5tmVBotZ/Mkw2kJr/O0ylCWvzqexqsTiUQ="); 241 if (.windows != builtin.os.tag) { 242 try known_hosts_file.setPermissions(io, @enumFromInt(0o600)); 243 } 244 245 // create sshd_config file 246 const sshd_config_file = try std.Io.Dir.cwd().createFile(io, temp_dir_name ++ "/sshd_config", .{}); 247 defer sshd_config_file.close(io); 248 try sshd_config_file.writeStreamingAll(io, 249 \\AuthenticationMethods publickey 250 \\PubkeyAuthentication yes 251 \\PasswordAuthentication no 252 \\StrictModes no 253 \\ 254 ); 255 if (.windows != builtin.os.tag) { 256 try sshd_config_file.setPermissions(io, @enumFromInt(0o600)); 257 } 258 259 // create sshd.sh contents 260 const cwd_path = try std.process.currentPathAlloc(io, allocator); 261 defer allocator.free(cwd_path); 262 const host_key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "host_key" }); 263 defer allocator.free(host_key_path); 264 const auth_keys_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "authorized_keys" }); 265 defer allocator.free(auth_keys_path); 266 const sshd_contents = try std.fmt.allocPrint( 267 allocator, 268 "#!/bin/sh\nexec $(which sshd) -p {} -f sshd_config -h \"{s}\" -D -e -o AuthorizedKeysFile=\"{s}\"", 269 .{ port, host_key_path, auth_keys_path }, 270 ); 271 defer allocator.free(sshd_contents); 272 273 // if path has a space char, it fucks up sshd 274 try std.testing.expect(null == std.mem.indexOfScalar(u8, auth_keys_path, ' ')); 275 276 // create sshd.sh 277 { 278 const sshd_file = try std.Io.Dir.cwd().createFile(io, temp_dir_name ++ "/sshd.sh", .{}); 279 defer sshd_file.close(io); 280 try sshd_file.writeStreamingAll(io, sshd_contents); 281 if (.windows != builtin.os.tag) { 282 try sshd_file.setPermissions(io, .executable_file); 283 } 284 } 285 286 if (use_serve_command) { 287 comptime { 288 std.debug.assert(server_repo_kind == .xit); 289 } 290 return .{ 291 .core = .{ 292 .io = io, 293 .allocator = allocator, 294 .sshd_process = null, 295 .serve_process = null, 296 }, 297 }; 298 } else { 299 return .{ 300 .core = .{ .io = io, .process = null }, 301 }; 302 } 303 }, 304 }, 305 } 306 } 307 308 fn start(self: *Server(server_repo_kind, transport_def, temp_dir_name, port, use_serve_command)) !void { 309 switch (transport_def) { 310 .file => {}, 311 .wire => |wire_kind| switch (wire_kind) { 312 .http => { 313 if (use_serve_command) { 314 std.debug.assert(self.core.process == null); 315 316 const cwd_path = try std.process.currentPathAlloc(self.core.io, self.core.allocator); 317 defer self.core.allocator.free(cwd_path); 318 const haxy_path = try std.fs.path.join(self.core.allocator, &.{ cwd_path, "zig-out/bin/haxy" }); 319 defer self.core.allocator.free(haxy_path); 320 const listen_arg = std.fmt.comptimePrint("127.0.0.1:{}", .{port}); 321 322 self.core.process = try std.process.spawn(self.core.io, .{ 323 .argv = &.{ haxy_path, "serve", "--http-listen", listen_arg, "--project-root", temp_dir_name }, 324 .stdin = .ignore, 325 .stdout = .ignore, 326 .stderr = .ignore, 327 }); 328 } else { 329 const ServerHandler = struct { 330 fn run(core: *Core) !void { 331 var send_buffer = [_]u8{0} ** 1024; 332 var recv_buffer = [_]u8{0} ** 1024; 333 334 accept: while (true) { 335 const stream = try core.net_server.accept(core.io); 336 defer stream.close(core.io); 337 338 var conn_br = stream.reader(core.io, &recv_buffer); 339 var conn_bw = stream.writer(core.io, &send_buffer); 340 var http_server = std.http.Server.init(&conn_br.interface, &conn_bw.interface); 341 342 while (http_server.reader.state == .ready) { 343 var request = http_server.receiveHead() catch |err| switch (err) { 344 error.HttpConnectionClosing => continue :accept, 345 else => |e| return e, 346 }; 347 if (std.mem.eql(u8, request.head.target, "/stop-server")) { 348 break :accept; 349 } 350 351 const uri = try std.Uri.parseAfterScheme("", request.head.target); 352 if (uri.path.percent_encoded[0] != '/') return error.PathMustStartWithSlash; 353 const path = if (std.mem.indexOfScalar(u8, uri.path.percent_encoded[1..], '/')) |idx| 354 uri.path.percent_encoded[idx + 1 ..] 355 else 356 return error.SlashNotFound; 357 358 const cwd_path = try std.process.currentPathAlloc(core.io, core.allocator); 359 defer core.allocator.free(cwd_path); 360 const temp_dir_path = try std.fs.path.join(core.allocator, &.{ cwd_path, core.temp_dir_name }); 361 defer core.allocator.free(temp_dir_path); 362 const path_translated = try std.fmt.allocPrint(core.allocator, "{s}{s}", .{ 363 temp_dir_path, 364 uri.path.percent_encoded, 365 }); 366 defer core.allocator.free(path_translated); 367 368 // init env map 369 var env_map = std.process.Environ.Map.init(core.allocator); 370 defer env_map.deinit(); 371 try env_map.put("GATEWAY_INTERFACE", "CGI/1.1"); 372 try env_map.put("REQUEST_METHOD", @tagName(request.head.method)); 373 try env_map.put("PATH_INFO", path); 374 try env_map.put("PATH_TRANSLATED", path_translated); 375 if (uri.query) |query| { 376 try env_map.put("QUERY_STRING", query.percent_encoded); 377 } 378 379 var accept: std.ArrayList([]const u8) = .empty; 380 defer accept.deinit(core.allocator); 381 382 var keep_alive = true; // HTTP 1.1 defaults to keep-alive 383 384 // iterate over headers to fill env map 385 var req_header_it = request.iterateHeaders(); 386 while (req_header_it.next()) |header| { 387 const header_name = header.name; 388 const header_value = header.value; 389 390 if (std.ascii.eqlIgnoreCase(header_name, "content-type")) { 391 try env_map.put("CONTENT_TYPE", header_value); 392 } else if (std.ascii.eqlIgnoreCase(header_name, "content-length")) { 393 try env_map.put("CONTENT_LENGTH", header_value); 394 } else if (std.ascii.eqlIgnoreCase(header_name, "referer")) { 395 try env_map.put("HTTP_REFERER", header_value); 396 } else if (std.ascii.eqlIgnoreCase(header_name, "accept")) { 397 try accept.append(core.allocator, header_value); 398 } else if (std.ascii.eqlIgnoreCase(header_name, "user-agent")) { 399 try env_map.put("HTTP_USER_AGENT", header_value); 400 } else if (std.ascii.eqlIgnoreCase(header_name, "connection")) { 401 if (std.ascii.eqlIgnoreCase(header_value, "close")) { 402 keep_alive = false; 403 } 404 } else if (std.ascii.eqlIgnoreCase(header_name, "git-protocol")) { 405 try env_map.put("GIT_PROTOCOL", header_value); 406 } 407 } 408 409 const accept_str = try std.mem.join(core.allocator, ",", accept.items); 410 defer core.allocator.free(accept_str); 411 if (accept_str.len > 0) { 412 try env_map.put("HTTP_ACCEPT", accept_str); 413 } 414 415 var process = try std.process.spawn(core.io, .{ 416 .argv = switch (server_repo_kind) { 417 .git => &.{ "git", "http-backend" }, 418 .xit => &.{ "../zig-out/bin/haxy", "http-backend" }, 419 }, 420 .cwd = switch (server_repo_kind) { 421 .git => .inherit, 422 .xit => .{ .path = temp_dir_name }, 423 }, 424 .environ_map = &env_map, 425 .stdin = .pipe, 426 .stdout = .pipe, 427 .stderr = .pipe, 428 }); 429 defer process.kill(core.io); 430 431 if (request.head.method == .POST) { 432 const reader = try request.readerExpectContinue(&.{}); 433 const request_body = try reader.allocRemaining(core.allocator, .unlimited); 434 defer core.allocator.free(request_body); 435 try process.stdin.?.writeStreamingAll(core.io, request_body); 436 } 437 process.stdin.?.close(core.io); 438 process.stdin = null; 439 440 var multi_reader_buffer: std.Io.File.MultiReader.Buffer(2) = undefined; 441 var multi_reader: std.Io.File.MultiReader = undefined; 442 multi_reader.init(core.allocator, core.io, multi_reader_buffer.toStreams(), &.{ process.stdout.?, process.stderr.? }); 443 defer multi_reader.deinit(); 444 445 while (multi_reader.fill(64, .none)) |_| {} else |err| switch (err) { 446 error.EndOfStream => {}, 447 else => |e| return e, 448 } 449 450 try multi_reader.checkAnyError(); 451 452 _ = try process.wait(core.io); 453 454 const stdout_slice = try multi_reader.toOwnedSlice(0); 455 defer core.allocator.free(stdout_slice); 456 const stderr_slice = try multi_reader.toOwnedSlice(1); 457 defer core.allocator.free(stderr_slice); 458 459 // transition the http state machine so it can 460 // read the next request on this connection 461 if (http_server.reader.state == .received_head) { 462 http_server.reader.state = .ready; 463 } 464 465 if (stderr_slice.len > 0) { 466 std.debug.print("Error from git-http-backend:\n{s}\n", .{stderr_slice}); 467 try http_server.out.writeAll("HTTP/1.1 500 Internal Server Error\r\n\r\n"); 468 } else { 469 try http_server.out.writeAll("HTTP/1.1 200 OK\r\n"); 470 const double_newline = "\r\n\r\n"; 471 const double_newline_idx = std.mem.indexOf(u8, stdout_slice, double_newline) orelse unreachable; 472 try http_server.out.writeAll(stdout_slice[0..double_newline_idx]); 473 try http_server.out.print("\r\nContent-Length: {}", .{stdout_slice.len - (double_newline_idx + double_newline.len)}); 474 try http_server.out.writeAll(stdout_slice[double_newline_idx..]); 475 } 476 try http_server.out.flush(); 477 478 if (!keep_alive) { 479 continue :accept; 480 } 481 } 482 } 483 } 484 }; 485 self.core.server_thread = try std.Thread.spawn(.{}, ServerHandler.run, .{&self.core}); 486 } 487 }, 488 .raw => unreachable, 489 .ssh => { 490 if (use_serve_command) { 491 std.debug.assert(self.core.sshd_process == null); 492 std.debug.assert(self.core.serve_process == null); 493 494 const cwd_path = try std.process.currentPathAlloc(self.core.io, self.core.allocator); 495 defer self.core.allocator.free(cwd_path); 496 const haxy_path = try std.fs.path.join(self.core.allocator, &.{ cwd_path, "zig-out/bin/haxy" }); 497 defer self.core.allocator.free(haxy_path); 498 const ssh_listen_arg = std.fmt.comptimePrint("127.0.0.1:{}", .{port + 1000}); 499 const http_listen_arg = std.fmt.comptimePrint("127.0.0.1:{}", .{port + 2000}); 500 501 self.core.serve_process = try std.process.spawn(self.core.io, .{ 502 .argv = &.{ haxy_path, "serve", "--http-listen", http_listen_arg, "--ssh-listen", ssh_listen_arg, "--project-root", temp_dir_name }, 503 .stdin = .ignore, 504 .stdout = .ignore, 505 .stderr = .ignore, 506 }); 507 508 const serve_address = try std.Io.net.IpAddress.parseIp4("127.0.0.1", port + 1000); 509 for (0..50) |_| { 510 const stream = serve_address.connect(self.core.io, .{ .mode = .stream }) catch { 511 try std.Io.sleep(self.core.io, .fromMilliseconds(100), .real); 512 continue; 513 }; 514 var probe_buf = [_]u8{0} ** 16; 515 var probe_writer = stream.writer(self.core.io, &probe_buf); 516 try probe_writer.interface.writeAll("probe\n"); 517 try probe_writer.interface.flush(); 518 stream.close(self.core.io); 519 break; 520 } 521 522 self.core.sshd_process = try std.process.spawn(self.core.io, .{ 523 .argv = &.{"./sshd.sh"}, 524 .cwd = .{ .path = temp_dir_name }, 525 .stdin = .pipe, 526 .stdout = .ignore, 527 .stderr = .ignore, 528 }); 529 } else { 530 std.debug.assert(self.core.process == null); 531 self.core.process = try std.process.spawn(self.core.io, .{ 532 .argv = &.{"./sshd.sh"}, 533 .cwd = .{ .path = temp_dir_name }, 534 .stdin = .pipe, 535 .stdout = .ignore, 536 .stderr = .ignore, 537 }); 538 } 539 }, 540 }, 541 } 542 543 // wait for server to be ready by polling the port 544 if (transport_def == .wire) { 545 const address = try std.Io.net.IpAddress.parseIp4("127.0.0.1", port); 546 for (0..50) |_| { 547 const stream = address.connect(self.core.io, .{ .mode = .stream }) catch { 548 try std.Io.sleep(self.core.io, .fromMilliseconds(100), .real); 549 continue; 550 }; 551 stream.close(self.core.io); 552 break; 553 } 554 } 555 } 556 557 fn stop(self: *Server(server_repo_kind, transport_def, temp_dir_name, port, use_serve_command)) void { 558 switch (transport_def) { 559 .file => {}, 560 .wire => |wire_kind| switch (wire_kind) { 561 .http => { 562 if (use_serve_command) { 563 if (self.core.process) |*process| { 564 _ = process.kill(self.core.io); 565 self.core.process = null; 566 } 567 return; 568 } 569 570 var client = std.http.Client{ .io = self.core.io, .allocator = self.core.allocator }; 571 defer client.deinit(); 572 _ = client.fetch(.{ .location = .{ .url = self.core.stop_server_endpoint } }) catch return; 573 self.core.server_thread.join(); 574 }, 575 .raw => unreachable, 576 .ssh => { 577 if (use_serve_command) { 578 if (self.core.sshd_process) |*process| { 579 _ = process.kill(self.core.io); 580 self.core.sshd_process = null; 581 } 582 if (self.core.serve_process) |*process| { 583 _ = process.kill(self.core.io); 584 self.core.serve_process = null; 585 } 586 } else { 587 _ = self.core.process.?.kill(self.core.io); 588 self.core.process = null; 589 } 590 }, 591 }, 592 } 593 } 594 }; 595 } 596 597 fn uploadPackCommand( 598 comptime server_repo_kind: rp.RepoKind, 599 comptime is_ssh: bool, 600 comptime use_serve_command: bool, 601 allocator: std.mem.Allocator, 602 cwd_path: []const u8, 603 comptime port: u16, 604 ) ![]const u8 { 605 return switch (server_repo_kind) { 606 .xit => if (is_ssh and use_serve_command) 607 std.fmt.allocPrint(allocator, "{s}/zig-out/bin/haxy ssh-helper --ssh-connect 127.0.0.1:{} --service upload-pack", .{ cwd_path, port + 1000 }) 608 else 609 std.fmt.allocPrint(allocator, "{s}/zig-out/bin/haxy upload-pack", .{cwd_path}), 610 .git => allocator.dupe(u8, "git-upload-pack"), 611 }; 612 } 613 614 fn receivePackCommand( 615 comptime server_repo_kind: rp.RepoKind, 616 comptime is_ssh: bool, 617 comptime use_serve_command: bool, 618 allocator: std.mem.Allocator, 619 cwd_path: []const u8, 620 comptime port: u16, 621 ) ![]const u8 { 622 return switch (server_repo_kind) { 623 .xit => if (is_ssh and use_serve_command) 624 std.fmt.allocPrint(allocator, "{s}/zig-out/bin/haxy ssh-helper --ssh-connect 127.0.0.1:{} --service receive-pack", .{ cwd_path, port + 1000 }) 625 else 626 std.fmt.allocPrint(allocator, "{s}/zig-out/bin/haxy receive-pack", .{cwd_path}), 627 .git => allocator.dupe(u8, "git-receive-pack"), 628 }; 629 } 630 631 fn testFetch( 632 comptime repo_kind: rp.RepoKind, 633 comptime server_repo_kind: rp.RepoKind, 634 comptime transport_def: net.TransportDefinition, 635 comptime port: u16, 636 comptime use_serve_command: bool, 637 io: std.Io, 638 allocator: std.mem.Allocator, 639 ) !void { 640 const temp_dir_name = "temp-testnet-fetch"; 641 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, &current_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