|
7 | 7 | #include "parse-options.h"
|
8 | 8 | #include "config.h"
|
9 | 9 | #include "run-command.h"
|
| 10 | +#include "refs.h" |
10 | 11 |
|
11 | 12 | /*
|
12 | 13 | * Remove the deepest subdirectory in the provided path string. Path must not
|
@@ -251,6 +252,205 @@ static int unregister_dir(void)
|
251 | 252 | return res;
|
252 | 253 | }
|
253 | 254 |
|
| 255 | +/* printf-style interface, expects `<key>=<value>` argument */ |
| 256 | +static int set_config(const char *fmt, ...) |
| 257 | +{ |
| 258 | + struct strbuf buf = STRBUF_INIT; |
| 259 | + char *value; |
| 260 | + int res; |
| 261 | + va_list args; |
| 262 | + |
| 263 | + va_start(args, fmt); |
| 264 | + strbuf_vaddf(&buf, fmt, args); |
| 265 | + va_end(args); |
| 266 | + |
| 267 | + value = strchr(buf.buf, '='); |
| 268 | + if (value) |
| 269 | + *(value++) = '\0'; |
| 270 | + res = git_config_set_gently(buf.buf, value); |
| 271 | + strbuf_release(&buf); |
| 272 | + |
| 273 | + return res; |
| 274 | +} |
| 275 | + |
| 276 | +static char *remote_default_branch(const char *url) |
| 277 | +{ |
| 278 | + struct child_process cp = CHILD_PROCESS_INIT; |
| 279 | + struct strbuf out = STRBUF_INIT; |
| 280 | + |
| 281 | + cp.git_cmd = 1; |
| 282 | + strvec_pushl(&cp.args, "ls-remote", "--symref", url, "HEAD", NULL); |
| 283 | + if (!pipe_command(&cp, NULL, 0, &out, 0, NULL, 0)) { |
| 284 | + const char *line = out.buf; |
| 285 | + |
| 286 | + while (*line) { |
| 287 | + const char *eol = strchrnul(line, '\n'), *p; |
| 288 | + size_t len = eol - line; |
| 289 | + char *branch; |
| 290 | + |
| 291 | + if (!skip_prefix(line, "ref: ", &p) || |
| 292 | + !strip_suffix_mem(line, &len, "\tHEAD")) { |
| 293 | + line = eol + (*eol == '\n'); |
| 294 | + continue; |
| 295 | + } |
| 296 | + |
| 297 | + eol = line + len; |
| 298 | + if (skip_prefix(p, "refs/heads/", &p)) { |
| 299 | + branch = xstrndup(p, eol - p); |
| 300 | + strbuf_release(&out); |
| 301 | + return branch; |
| 302 | + } |
| 303 | + |
| 304 | + error(_("remote HEAD is not a branch: '%.*s'"), |
| 305 | + (int)(eol - p), p); |
| 306 | + strbuf_release(&out); |
| 307 | + return NULL; |
| 308 | + } |
| 309 | + } |
| 310 | + warning(_("failed to get default branch name from remote; " |
| 311 | + "using local default")); |
| 312 | + strbuf_reset(&out); |
| 313 | + |
| 314 | + child_process_init(&cp); |
| 315 | + cp.git_cmd = 1; |
| 316 | + strvec_pushl(&cp.args, "symbolic-ref", "--short", "HEAD", NULL); |
| 317 | + if (!pipe_command(&cp, NULL, 0, &out, 0, NULL, 0)) { |
| 318 | + strbuf_trim(&out); |
| 319 | + return strbuf_detach(&out, NULL); |
| 320 | + } |
| 321 | + |
| 322 | + strbuf_release(&out); |
| 323 | + error(_("failed to get default branch name")); |
| 324 | + return NULL; |
| 325 | +} |
| 326 | + |
| 327 | +static int cmd_clone(int argc, const char **argv) |
| 328 | +{ |
| 329 | + const char *branch = NULL; |
| 330 | + int full_clone = 0; |
| 331 | + struct option clone_options[] = { |
| 332 | + OPT_STRING('b', "branch", &branch, N_("<branch>"), |
| 333 | + N_("branch to checkout after clone")), |
| 334 | + OPT_BOOL(0, "full-clone", &full_clone, |
| 335 | + N_("when cloning, create full working directory")), |
| 336 | + OPT_END(), |
| 337 | + }; |
| 338 | + const char * const clone_usage[] = { |
| 339 | + N_("scalar clone [<options>] [--] <repo> [<dir>]"), |
| 340 | + NULL |
| 341 | + }; |
| 342 | + const char *url; |
| 343 | + char *enlistment = NULL, *dir = NULL; |
| 344 | + struct strbuf buf = STRBUF_INIT; |
| 345 | + int res; |
| 346 | + |
| 347 | + argc = parse_options(argc, argv, NULL, clone_options, clone_usage, 0); |
| 348 | + |
| 349 | + if (argc == 2) { |
| 350 | + url = argv[0]; |
| 351 | + enlistment = xstrdup(argv[1]); |
| 352 | + } else if (argc == 1) { |
| 353 | + url = argv[0]; |
| 354 | + |
| 355 | + strbuf_addstr(&buf, url); |
| 356 | + /* Strip trailing slashes, if any */ |
| 357 | + while (buf.len > 0 && is_dir_sep(buf.buf[buf.len - 1])) |
| 358 | + strbuf_setlen(&buf, buf.len - 1); |
| 359 | + /* Strip suffix `.git`, if any */ |
| 360 | + strbuf_strip_suffix(&buf, ".git"); |
| 361 | + |
| 362 | + enlistment = find_last_dir_sep(buf.buf); |
| 363 | + if (!enlistment) { |
| 364 | + die(_("cannot deduce worktree name from '%s'"), url); |
| 365 | + } |
| 366 | + enlistment = xstrdup(enlistment + 1); |
| 367 | + } else { |
| 368 | + usage_msg_opt(_("You must specify a repository to clone."), |
| 369 | + clone_usage, clone_options); |
| 370 | + } |
| 371 | + |
| 372 | + if (is_directory(enlistment)) |
| 373 | + die(_("directory '%s' exists already"), enlistment); |
| 374 | + |
| 375 | + dir = xstrfmt("%s/src", enlistment); |
| 376 | + |
| 377 | + strbuf_reset(&buf); |
| 378 | + if (branch) |
| 379 | + strbuf_addf(&buf, "init.defaultBranch=%s", branch); |
| 380 | + else { |
| 381 | + char *b = repo_default_branch_name(the_repository, 1); |
| 382 | + strbuf_addf(&buf, "init.defaultBranch=%s", b); |
| 383 | + free(b); |
| 384 | + } |
| 385 | + |
| 386 | + if ((res = run_git("-c", buf.buf, "init", "--", dir, NULL))) |
| 387 | + goto cleanup; |
| 388 | + |
| 389 | + if (chdir(dir) < 0) { |
| 390 | + res = error_errno(_("could not switch to '%s'"), dir); |
| 391 | + goto cleanup; |
| 392 | + } |
| 393 | + |
| 394 | + setup_git_directory(); |
| 395 | + |
| 396 | + /* common-main already logs `argv` */ |
| 397 | + trace2_def_repo(the_repository); |
| 398 | + |
| 399 | + if (!branch && !(branch = remote_default_branch(url))) { |
| 400 | + res = error(_("failed to get default branch for '%s'"), url); |
| 401 | + goto cleanup; |
| 402 | + } |
| 403 | + |
| 404 | + if (set_config("remote.origin.url=%s", url) || |
| 405 | + set_config("remote.origin.fetch=" |
| 406 | + "+refs/heads/*:refs/remotes/origin/*") || |
| 407 | + set_config("remote.origin.promisor=true") || |
| 408 | + set_config("remote.origin.partialCloneFilter=blob:none")) { |
| 409 | + res = error(_("could not configure remote in '%s'"), dir); |
| 410 | + goto cleanup; |
| 411 | + } |
| 412 | + |
| 413 | + if (!full_clone && |
| 414 | + (res = run_git("sparse-checkout", "init", "--cone", NULL))) |
| 415 | + goto cleanup; |
| 416 | + |
| 417 | + if (set_recommended_config()) |
| 418 | + return error(_("could not configure '%s'"), dir); |
| 419 | + |
| 420 | + if ((res = run_git("fetch", "--quiet", "origin", NULL))) { |
| 421 | + warning(_("partial clone failed; attempting full clone")); |
| 422 | + |
| 423 | + if (set_config("remote.origin.promisor") || |
| 424 | + set_config("remote.origin.partialCloneFilter")) { |
| 425 | + res = error(_("could not configure for full clone")); |
| 426 | + goto cleanup; |
| 427 | + } |
| 428 | + |
| 429 | + if ((res = run_git("fetch", "--quiet", "origin", NULL))) |
| 430 | + goto cleanup; |
| 431 | + } |
| 432 | + |
| 433 | + if ((res = set_config("branch.%s.remote=origin", branch))) |
| 434 | + goto cleanup; |
| 435 | + if ((res = set_config("branch.%s.merge=refs/heads/%s", |
| 436 | + branch, branch))) |
| 437 | + goto cleanup; |
| 438 | + |
| 439 | + strbuf_reset(&buf); |
| 440 | + strbuf_addf(&buf, "origin/%s", branch); |
| 441 | + res = run_git("checkout", "-f", "-t", buf.buf, NULL); |
| 442 | + if (res) |
| 443 | + goto cleanup; |
| 444 | + |
| 445 | + res = register_dir(); |
| 446 | + |
| 447 | +cleanup: |
| 448 | + free(enlistment); |
| 449 | + free(dir); |
| 450 | + strbuf_release(&buf); |
| 451 | + return res; |
| 452 | +} |
| 453 | + |
254 | 454 | static int cmd_list(int argc, const char **argv)
|
255 | 455 | {
|
256 | 456 | if (argc != 1)
|
@@ -347,6 +547,7 @@ static struct {
|
347 | 547 | const char *name;
|
348 | 548 | int (*fn)(int, const char **);
|
349 | 549 | } builtins[] = {
|
| 550 | + { "clone", cmd_clone }, |
350 | 551 | { "list", cmd_list },
|
351 | 552 | { "register", cmd_register },
|
352 | 553 | { "unregister", cmd_unregister },
|
|
0 commit comments