Git fork
at reftables-rust 1046 lines 26 kB view raw
1/* 2 * The Scalar command-line interface. 3 */ 4 5#define USE_THE_REPOSITORY_VARIABLE 6 7#include "git-compat-util.h" 8#include "abspath.h" 9#include "gettext.h" 10#include "parse-options.h" 11#include "config.h" 12#include "run-command.h" 13#include "simple-ipc.h" 14#include "fsmonitor-ipc.h" 15#include "fsmonitor-settings.h" 16#include "refs.h" 17#include "dir.h" 18#include "packfile.h" 19#include "help.h" 20#include "setup.h" 21#include "trace2.h" 22 23static void setup_enlistment_directory(int argc, const char **argv, 24 const char * const *usagestr, 25 const struct option *options, 26 struct strbuf *enlistment_root) 27{ 28 struct strbuf path = STRBUF_INIT; 29 int enlistment_is_repo_parent = 0; 30 size_t len; 31 32 if (startup_info->have_repository) 33 BUG("gitdir already set up?!?"); 34 35 if (argc > 1) 36 usage_with_options(usagestr, options); 37 38 /* find the worktree, determine its corresponding root */ 39 if (argc == 1) { 40 strbuf_add_absolute_path(&path, argv[0]); 41 if (!is_directory(path.buf)) 42 die(_("'%s' does not exist"), path.buf); 43 if (chdir(path.buf) < 0) 44 die_errno(_("could not switch to '%s'"), path.buf); 45 } else if (strbuf_getcwd(&path) < 0) 46 die(_("need a working directory")); 47 48 strbuf_trim_trailing_dir_sep(&path); 49 50 /* check if currently in enlistment root with src/ workdir */ 51 len = path.len; 52 strbuf_addstr(&path, "/src"); 53 if (is_nonbare_repository_dir(&path)) { 54 enlistment_is_repo_parent = 1; 55 if (chdir(path.buf) < 0) 56 die_errno(_("could not switch to '%s'"), path.buf); 57 } 58 strbuf_setlen(&path, len); 59 60 setup_git_directory(); 61 62 if (!the_repository->worktree) 63 die(_("Scalar enlistments require a worktree")); 64 65 if (enlistment_root) { 66 if (enlistment_is_repo_parent) 67 strbuf_addbuf(enlistment_root, &path); 68 else 69 strbuf_addstr(enlistment_root, the_repository->worktree); 70 } 71 72 strbuf_release(&path); 73} 74 75LAST_ARG_MUST_BE_NULL 76static int run_git(const char *arg, ...) 77{ 78 struct child_process cmd = CHILD_PROCESS_INIT; 79 va_list args; 80 const char *p; 81 82 va_start(args, arg); 83 strvec_push(&cmd.args, arg); 84 while ((p = va_arg(args, const char *))) 85 strvec_push(&cmd.args, p); 86 va_end(args); 87 88 cmd.git_cmd = 1; 89 return run_command(&cmd); 90} 91 92struct scalar_config { 93 const char *key; 94 const char *value; 95 int overwrite_on_reconfigure; 96}; 97 98static int set_scalar_config(const struct scalar_config *config, int reconfigure) 99{ 100 char *value = NULL; 101 int res; 102 103 if ((reconfigure && config->overwrite_on_reconfigure) || 104 repo_config_get_string(the_repository, config->key, &value)) { 105 trace2_data_string("scalar", the_repository, config->key, "created"); 106 res = repo_config_set_gently(the_repository, config->key, config->value); 107 } else { 108 trace2_data_string("scalar", the_repository, config->key, "exists"); 109 res = 0; 110 } 111 112 free(value); 113 return res; 114} 115 116static int have_fsmonitor_support(void) 117{ 118 return fsmonitor_ipc__is_supported() && 119 fsm_settings__get_reason(the_repository) == FSMONITOR_REASON_OK; 120} 121 122static int set_recommended_config(int reconfigure) 123{ 124 struct scalar_config config[] = { 125 /* Required */ 126 { "am.keepCR", "true", 1 }, 127 { "core.FSCache", "true", 1 }, 128 { "core.multiPackIndex", "true", 1 }, 129 { "core.preloadIndex", "true", 1 }, 130#ifndef WIN32 131 { "core.untrackedCache", "true", 1 }, 132#else 133 /* 134 * Unfortunately, Scalar's Functional Tests demonstrated 135 * that the untracked cache feature is unreliable on Windows 136 * (which is a bummer because that platform would benefit the 137 * most from it). For some reason, freshly created files seem 138 * not to update the directory's `lastModified` time 139 * immediately, but the untracked cache would need to rely on 140 * that. 141 * 142 * Therefore, with a sad heart, we disable this very useful 143 * feature on Windows. 144 */ 145 { "core.untrackedCache", "false", 1 }, 146#endif 147 { "core.logAllRefUpdates", "true", 1 }, 148 { "credential.https://dev.azure.com.useHttpPath", "true", 1 }, 149 { "credential.validate", "false", 1 }, /* GCM4W-only */ 150 { "gc.auto", "0", 1 }, 151 { "gui.GCWarning", "false", 1 }, 152 { "index.skipHash", "false", 1 }, 153 { "index.threads", "true", 1 }, 154 { "index.version", "4", 1 }, 155 { "merge.stat", "false", 1 }, 156 { "merge.renames", "true", 1 }, 157 { "pack.useBitmaps", "false", 1 }, 158 { "pack.useSparse", "true", 1 }, 159 { "receive.autoGC", "false", 1 }, 160 { "feature.manyFiles", "false", 1 }, 161 { "feature.experimental", "false", 1 }, 162 { "fetch.unpackLimit", "1", 1 }, 163 { "fetch.writeCommitGraph", "false", 1 }, 164#ifdef WIN32 165 { "http.sslBackend", "schannel", 1 }, 166#endif 167 /* Optional */ 168 { "status.aheadBehind", "false" }, 169 { "commitGraph.generationVersion", "1" }, 170 { "core.autoCRLF", "false" }, 171 { "core.safeCRLF", "false" }, 172 { "fetch.showForcedUpdates", "false" }, 173 { "pack.usePathWalk", "true" }, 174 { NULL, NULL }, 175 }; 176 int i; 177 char *value; 178 179 for (i = 0; config[i].key; i++) { 180 if (set_scalar_config(config + i, reconfigure)) 181 return error(_("could not configure %s=%s"), 182 config[i].key, config[i].value); 183 } 184 185 if (have_fsmonitor_support()) { 186 struct scalar_config fsmonitor = { "core.fsmonitor", "true" }; 187 if (set_scalar_config(&fsmonitor, reconfigure)) 188 return error(_("could not configure %s=%s"), 189 fsmonitor.key, fsmonitor.value); 190 } 191 192 /* 193 * The `log.excludeDecoration` setting is special because it allows 194 * for multiple values. 195 */ 196 if (repo_config_get_string(the_repository, "log.excludeDecoration", &value)) { 197 trace2_data_string("scalar", the_repository, 198 "log.excludeDecoration", "created"); 199 if (repo_config_set_multivar_gently(the_repository, "log.excludeDecoration", 200 "refs/prefetch/*", 201 CONFIG_REGEX_NONE, 0)) 202 return error(_("could not configure " 203 "log.excludeDecoration")); 204 } else { 205 trace2_data_string("scalar", the_repository, 206 "log.excludeDecoration", "exists"); 207 free(value); 208 } 209 210 return 0; 211} 212 213/** 214 * Enable or disable the maintenance mode for the current repository: 215 * 216 * * If 'enable' is nonzero, run 'git maintenance start'. 217 * * If 'enable' is zero, run 'git maintenance unregister --force'. 218 */ 219static int toggle_maintenance(int enable) 220{ 221 return run_git("maintenance", 222 enable ? "start" : "unregister", 223 enable ? NULL : "--force", 224 NULL); 225} 226 227static int add_or_remove_enlistment(int add) 228{ 229 int res; 230 231 if (!the_repository->worktree) 232 die(_("Scalar enlistments require a worktree")); 233 234 res = run_git("config", "--global", "--get", "--fixed-value", 235 "scalar.repo", the_repository->worktree, NULL); 236 237 /* 238 * If we want to add and the setting is already there, then do nothing. 239 * If we want to remove and the setting is not there, then do nothing. 240 */ 241 if ((add && !res) || (!add && res)) 242 return 0; 243 244 return run_git("config", "--global", add ? "--add" : "--unset", 245 add ? "--no-fixed-value" : "--fixed-value", 246 "scalar.repo", the_repository->worktree, NULL); 247} 248 249static int start_fsmonitor_daemon(void) 250{ 251 ASSERT(have_fsmonitor_support()); 252 253 if (fsmonitor_ipc__get_state() != IPC_STATE__LISTENING) 254 return run_git("fsmonitor--daemon", "start", NULL); 255 256 return 0; 257} 258 259static int stop_fsmonitor_daemon(void) 260{ 261 ASSERT(have_fsmonitor_support()); 262 263 if (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING) 264 return run_git("fsmonitor--daemon", "stop", NULL); 265 266 return 0; 267} 268 269/** 270 * Register the current directory as a Scalar enlistment, and set the 271 * recommended configuration. 272 * 273 * * If 'maintenance' is non-zero, then enable background maintenance. 274 * * If 'maintenance' is zero, then leave background maintenance as it is 275 * currently configured. 276 */ 277static int register_dir(int maintenance) 278{ 279 if (add_or_remove_enlistment(1)) 280 return error(_("could not add enlistment")); 281 282 if (set_recommended_config(0)) 283 return error(_("could not set recommended config")); 284 285 if (maintenance && 286 toggle_maintenance(maintenance)) 287 warning(_("could not toggle maintenance")); 288 289 if (have_fsmonitor_support() && start_fsmonitor_daemon()) { 290 return error(_("could not start the FSMonitor daemon")); 291 } 292 293 return 0; 294} 295 296static int unregister_dir(void) 297{ 298 int res = 0; 299 300 if (toggle_maintenance(0)) 301 res = error(_("could not turn off maintenance")); 302 303 if (add_or_remove_enlistment(0)) 304 res = error(_("could not remove enlistment")); 305 306 return res; 307} 308 309/* printf-style interface, expects `<key>=<value>` argument */ 310__attribute__((format (printf, 1, 2))) 311static int set_config(const char *fmt, ...) 312{ 313 struct strbuf buf = STRBUF_INIT; 314 char *value; 315 int res; 316 va_list args; 317 318 va_start(args, fmt); 319 strbuf_vaddf(&buf, fmt, args); 320 va_end(args); 321 322 value = strchr(buf.buf, '='); 323 if (value) 324 *(value++) = '\0'; 325 res = repo_config_set_gently(the_repository, buf.buf, value); 326 strbuf_release(&buf); 327 328 return res; 329} 330 331static char *remote_default_branch(const char *url) 332{ 333 struct child_process cp = CHILD_PROCESS_INIT; 334 struct strbuf out = STRBUF_INIT; 335 336 cp.git_cmd = 1; 337 strvec_pushl(&cp.args, "ls-remote", "--symref", url, "HEAD", NULL); 338 if (!pipe_command(&cp, NULL, 0, &out, 0, NULL, 0)) { 339 const char *line = out.buf; 340 341 while (*line) { 342 const char *eol = strchrnul(line, '\n'), *p; 343 size_t len = eol - line; 344 char *branch; 345 346 if (!skip_prefix(line, "ref: ", &p) || 347 !strip_suffix_mem(line, &len, "\tHEAD")) { 348 line = eol + (*eol == '\n'); 349 continue; 350 } 351 352 eol = line + len; 353 if (skip_prefix(p, "refs/heads/", &p)) { 354 branch = xstrndup(p, eol - p); 355 strbuf_release(&out); 356 return branch; 357 } 358 359 error(_("remote HEAD is not a branch: '%.*s'"), 360 (int)(eol - p), p); 361 strbuf_release(&out); 362 return NULL; 363 } 364 } 365 warning(_("failed to get default branch name from remote; " 366 "using local default")); 367 strbuf_reset(&out); 368 369 child_process_init(&cp); 370 cp.git_cmd = 1; 371 strvec_pushl(&cp.args, "symbolic-ref", "--short", "HEAD", NULL); 372 if (!pipe_command(&cp, NULL, 0, &out, 0, NULL, 0)) { 373 strbuf_trim(&out); 374 return strbuf_detach(&out, NULL); 375 } 376 377 strbuf_release(&out); 378 error(_("failed to get default branch name")); 379 return NULL; 380} 381 382static int delete_enlistment(struct strbuf *enlistment) 383{ 384 struct strbuf parent = STRBUF_INIT; 385 size_t offset; 386 char *path_sep; 387 388 if (unregister_dir()) 389 return error(_("failed to unregister repository")); 390 391 /* 392 * Change the current directory to one outside of the enlistment so 393 * that we may delete everything underneath it. 394 */ 395 offset = offset_1st_component(enlistment->buf); 396 path_sep = find_last_dir_sep(enlistment->buf + offset); 397 strbuf_add(&parent, enlistment->buf, 398 path_sep ? (size_t) (path_sep - enlistment->buf) : offset); 399 if (chdir(parent.buf) < 0) { 400 int res = error_errno(_("could not switch to '%s'"), parent.buf); 401 strbuf_release(&parent); 402 return res; 403 } 404 strbuf_release(&parent); 405 406 if (have_fsmonitor_support() && stop_fsmonitor_daemon()) 407 return error(_("failed to stop the FSMonitor daemon")); 408 409 if (remove_dir_recursively(enlistment, 0)) 410 return error(_("failed to delete enlistment directory")); 411 412 return 0; 413} 414 415/* 416 * Dummy implementation; Using `get_version_info()` would cause a link error 417 * without this. 418 */ 419void load_builtin_commands(const char *prefix UNUSED, 420 struct cmdnames *cmds UNUSED) 421{ 422 die("not implemented"); 423} 424 425static int cmd_clone(int argc, const char **argv) 426{ 427 const char *branch = NULL; 428 char *branch_to_free = NULL; 429 int full_clone = 0, single_branch = 0, show_progress = isatty(2); 430 int src = 1, tags = 1, maintenance = 1; 431 struct option clone_options[] = { 432 OPT_STRING('b', "branch", &branch, N_("<branch>"), 433 N_("branch to checkout after clone")), 434 OPT_BOOL(0, "full-clone", &full_clone, 435 N_("when cloning, create full working directory")), 436 OPT_BOOL(0, "single-branch", &single_branch, 437 N_("only download metadata for the branch that will " 438 "be checked out")), 439 OPT_BOOL(0, "src", &src, 440 N_("create repository within 'src' directory")), 441 OPT_BOOL(0, "tags", &tags, 442 N_("specify if tags should be fetched during clone")), 443 OPT_BOOL(0, "maintenance", &maintenance, 444 N_("specify if background maintenance should be enabled")), 445 OPT_END(), 446 }; 447 const char * const clone_usage[] = { 448 N_("scalar clone [--single-branch] [--branch <main-branch>] [--full-clone]\n" 449 "\t[--[no-]src] [--[no-]tags] [--[no-]maintenance] <url> [<enlistment>]"), 450 NULL 451 }; 452 const char *url; 453 char *enlistment = NULL, *dir = NULL; 454 struct strbuf buf = STRBUF_INIT; 455 int res; 456 457 argc = parse_options(argc, argv, NULL, clone_options, clone_usage, 0); 458 459 if (argc == 2) { 460 url = argv[0]; 461 enlistment = xstrdup(argv[1]); 462 } else if (argc == 1) { 463 url = argv[0]; 464 465 strbuf_addstr(&buf, url); 466 /* Strip trailing slashes, if any */ 467 while (buf.len > 0 && is_dir_sep(buf.buf[buf.len - 1])) 468 strbuf_setlen(&buf, buf.len - 1); 469 /* Strip suffix `.git`, if any */ 470 strbuf_strip_suffix(&buf, ".git"); 471 472 enlistment = find_last_dir_sep(buf.buf); 473 if (!enlistment) { 474 die(_("cannot deduce worktree name from '%s'"), url); 475 } 476 enlistment = xstrdup(enlistment + 1); 477 } else { 478 usage_msg_opt(_("You must specify a repository to clone."), 479 clone_usage, clone_options); 480 } 481 482 if (is_directory(enlistment)) 483 die(_("directory '%s' exists already"), enlistment); 484 485 if (src) 486 dir = xstrfmt("%s/src", enlistment); 487 else 488 dir = xstrdup(enlistment); 489 490 strbuf_reset(&buf); 491 if (branch) 492 strbuf_addf(&buf, "init.defaultBranch=%s", branch); 493 else { 494 char *b = repo_default_branch_name(the_repository, 1); 495 strbuf_addf(&buf, "init.defaultBranch=%s", b); 496 free(b); 497 } 498 499 if ((res = run_git("-c", buf.buf, "init", "--", dir, NULL))) 500 goto cleanup; 501 502 if (chdir(dir) < 0) { 503 res = error_errno(_("could not switch to '%s'"), dir); 504 goto cleanup; 505 } 506 507 setup_git_directory(); 508 509 /* common-main already logs `argv` */ 510 trace2_def_repo(the_repository); 511 512 if (!branch && !(branch = branch_to_free = remote_default_branch(url))) { 513 res = error(_("failed to get default branch for '%s'"), url); 514 goto cleanup; 515 } 516 517 if (set_config("remote.origin.url=%s", url) || 518 set_config("remote.origin.fetch=" 519 "+refs/heads/%s:refs/remotes/origin/%s", 520 single_branch ? branch : "*", 521 single_branch ? branch : "*") || 522 set_config("remote.origin.promisor=true") || 523 set_config("remote.origin.partialCloneFilter=blob:none")) { 524 res = error(_("could not configure remote in '%s'"), dir); 525 goto cleanup; 526 } 527 528 if (!tags && set_config("remote.origin.tagOpt=--no-tags")) { 529 res = error(_("could not disable tags in '%s'"), dir); 530 goto cleanup; 531 } 532 533 if (!full_clone && 534 (res = run_git("sparse-checkout", "init", "--cone", NULL))) 535 goto cleanup; 536 537 if (set_recommended_config(0)) 538 return error(_("could not configure '%s'"), dir); 539 540 if ((res = run_git("fetch", "--quiet", 541 show_progress ? "--progress" : "--no-progress", 542 "origin", 543 (tags ? NULL : "--no-tags"), 544 NULL))) { 545 warning(_("partial clone failed; attempting full clone")); 546 547 if (set_config("remote.origin.promisor") || 548 set_config("remote.origin.partialCloneFilter")) { 549 res = error(_("could not configure for full clone")); 550 goto cleanup; 551 } 552 553 if ((res = run_git("fetch", "--quiet", 554 show_progress ? "--progress" : "--no-progress", 555 "origin", NULL))) 556 goto cleanup; 557 } 558 559 if ((res = set_config("branch.%s.remote=origin", branch))) 560 goto cleanup; 561 if ((res = set_config("branch.%s.merge=refs/heads/%s", 562 branch, branch))) 563 goto cleanup; 564 565 strbuf_reset(&buf); 566 strbuf_addf(&buf, "origin/%s", branch); 567 res = run_git("checkout", "-f", "-t", buf.buf, NULL); 568 if (res) 569 goto cleanup; 570 571 /* If --no-maintenance, then skip maintenance command entirely. */ 572 res = register_dir(maintenance); 573 574cleanup: 575 free(branch_to_free); 576 free(enlistment); 577 free(dir); 578 strbuf_release(&buf); 579 return res; 580} 581 582static int cmd_diagnose(int argc, const char **argv) 583{ 584 struct option options[] = { 585 OPT_END(), 586 }; 587 const char * const usage[] = { 588 N_("scalar diagnose [<enlistment>]"), 589 NULL 590 }; 591 struct strbuf diagnostics_root = STRBUF_INIT; 592 int res = 0; 593 594 argc = parse_options(argc, argv, NULL, options, 595 usage, 0); 596 597 setup_enlistment_directory(argc, argv, usage, options, &diagnostics_root); 598 strbuf_addstr(&diagnostics_root, "/.scalarDiagnostics"); 599 600 res = run_git("diagnose", "--mode=all", "-s", "%Y%m%d_%H%M%S", 601 "-o", diagnostics_root.buf, NULL); 602 603 strbuf_release(&diagnostics_root); 604 return res; 605} 606 607static int cmd_list(int argc, const char **argv UNUSED) 608{ 609 if (argc != 1) 610 die(_("`scalar list` does not take arguments")); 611 612 if (run_git("config", "--global", "--get-all", "scalar.repo", NULL) < 0) 613 return -1; 614 return 0; 615} 616 617static int cmd_register(int argc, const char **argv) 618{ 619 int maintenance = 1; 620 struct option options[] = { 621 OPT_BOOL(0, "maintenance", &maintenance, 622 N_("specify if background maintenance should be enabled")), 623 OPT_END(), 624 }; 625 const char * const usage[] = { 626 N_("scalar register [--[no-]maintenance] [<enlistment>]"), 627 NULL 628 }; 629 630 argc = parse_options(argc, argv, NULL, options, 631 usage, 0); 632 633 setup_enlistment_directory(argc, argv, usage, options, NULL); 634 635 /* If --no-maintenance, then leave maintenance as-is. */ 636 return register_dir(maintenance); 637} 638 639static int get_scalar_repos(const char *key, const char *value, 640 const struct config_context *ctx UNUSED, 641 void *data) 642{ 643 struct string_list *list = data; 644 645 if (!strcmp(key, "scalar.repo")) 646 string_list_append(list, value); 647 648 return 0; 649} 650 651static int remove_deleted_enlistment(struct strbuf *path) 652{ 653 int res = 0; 654 strbuf_realpath_forgiving(path, path->buf, 1); 655 656 if (run_git("config", "--global", 657 "--unset", "--fixed-value", 658 "scalar.repo", path->buf, NULL) < 0) 659 res = -1; 660 661 if (run_git("config", "--global", 662 "--unset", "--fixed-value", 663 "maintenance.repo", path->buf, NULL) < 0) 664 res = -1; 665 666 return res; 667} 668 669static int cmd_reconfigure(int argc, const char **argv) 670{ 671 int all = 0; 672 const char *maintenance_str = NULL; 673 int maintenance = 1; /* Enable maintenance by default. */ 674 675 struct option options[] = { 676 OPT_BOOL('a', "all", &all, 677 N_("reconfigure all registered enlistments")), 678 OPT_STRING(0, "maintenance", &maintenance_str, 679 N_("(enable|disable|keep)"), 680 N_("signal how to adjust background maintenance")), 681 OPT_END(), 682 }; 683 const char * const usage[] = { 684 N_("scalar reconfigure [--maintenance=(enable|disable|keep)] [--all | <enlistment>]"), 685 NULL 686 }; 687 struct string_list scalar_repos = STRING_LIST_INIT_DUP; 688 int res = 0; 689 struct strbuf commondir = STRBUF_INIT, gitdir = STRBUF_INIT; 690 691 argc = parse_options(argc, argv, NULL, options, 692 usage, 0); 693 694 if (!all) { 695 setup_enlistment_directory(argc, argv, usage, options, NULL); 696 697 return set_recommended_config(1); 698 } 699 700 if (argc > 0) 701 usage_msg_opt(_("--all or <enlistment>, but not both"), 702 usage, options); 703 704 if (maintenance_str) { 705 if (!strcmp(maintenance_str, "enable")) 706 maintenance = 1; 707 else if (!strcmp(maintenance_str, "disable")) 708 maintenance = 0; 709 else if (!strcmp(maintenance_str, "keep")) 710 maintenance = -1; 711 else 712 die(_("unknown mode for --maintenance option: %s"), 713 maintenance_str); 714 } 715 716 repo_config(the_repository, get_scalar_repos, &scalar_repos); 717 718 for (size_t i = 0; i < scalar_repos.nr; i++) { 719 int succeeded = 0; 720 struct repository *old_repo, r = { NULL }; 721 const char *dir = scalar_repos.items[i].string; 722 723 strbuf_reset(&commondir); 724 strbuf_reset(&gitdir); 725 726 if (chdir(dir) < 0) { 727 struct strbuf buf = STRBUF_INIT; 728 729 if (errno != ENOENT) { 730 warning_errno(_("could not switch to '%s'"), dir); 731 goto loop_end; 732 } 733 734 strbuf_addstr(&buf, dir); 735 if (remove_deleted_enlistment(&buf)) 736 error(_("could not remove stale " 737 "scalar.repo '%s'"), dir); 738 else { 739 warning(_("removed stale scalar.repo '%s'"), 740 dir); 741 succeeded = 1; 742 } 743 strbuf_release(&buf); 744 goto loop_end; 745 } 746 747 switch (discover_git_directory_reason(&commondir, &gitdir)) { 748 case GIT_DIR_INVALID_OWNERSHIP: 749 warning(_("repository at '%s' has different owner"), dir); 750 goto loop_end; 751 752 case GIT_DIR_INVALID_GITFILE: 753 case GIT_DIR_INVALID_FORMAT: 754 warning(_("repository at '%s' has a format issue"), dir); 755 goto loop_end; 756 757 case GIT_DIR_DISCOVERED: 758 succeeded = 1; 759 break; 760 761 default: 762 warning(_("repository not found in '%s'"), dir); 763 break; 764 } 765 766 repo_config_clear(the_repository); 767 768 if (repo_init(&r, gitdir.buf, commondir.buf)) 769 goto loop_end; 770 771 old_repo = the_repository; 772 the_repository = &r; 773 774 if (set_recommended_config(1) >= 0) 775 succeeded = 1; 776 777 the_repository = old_repo; 778 repo_clear(&r); 779 780 if (maintenance >= 0 && 781 toggle_maintenance(maintenance) >= 0) 782 succeeded = 1; 783 784loop_end: 785 if (!succeeded) { 786 res = -1; 787 warning(_("to unregister this repository from Scalar, run\n" 788 "\tgit config --global --unset --fixed-value scalar.repo \"%s\""), 789 dir); 790 } 791 } 792 793 string_list_clear(&scalar_repos, 1); 794 strbuf_release(&commondir); 795 strbuf_release(&gitdir); 796 797 return res; 798} 799 800static int cmd_run(int argc, const char **argv) 801{ 802 struct option options[] = { 803 OPT_END(), 804 }; 805 struct { 806 const char *arg, *task; 807 } tasks[] = { 808 { "config", NULL }, 809 { "commit-graph", "commit-graph" }, 810 { "fetch", "prefetch" }, 811 { "loose-objects", "loose-objects" }, 812 { "pack-files", "incremental-repack" }, 813 { NULL, NULL } 814 }; 815 struct strbuf buf = STRBUF_INIT; 816 const char *usagestr[] = { NULL, NULL }; 817 int i; 818 819 strbuf_addstr(&buf, N_("scalar run <task> [<enlistment>]\nTasks:\n")); 820 for (i = 0; tasks[i].arg; i++) 821 strbuf_addf(&buf, "\t%s\n", tasks[i].arg); 822 usagestr[0] = buf.buf; 823 824 argc = parse_options(argc, argv, NULL, options, 825 usagestr, 0); 826 827 if (!argc) 828 usage_with_options(usagestr, options); 829 830 if (!strcmp("all", argv[0])) { 831 i = -1; 832 } else { 833 for (i = 0; tasks[i].arg && strcmp(tasks[i].arg, argv[0]); i++) 834 ; /* keep looking for the task */ 835 836 if (i > 0 && !tasks[i].arg) { 837 error(_("no such task: '%s'"), argv[0]); 838 usage_with_options(usagestr, options); 839 } 840 } 841 842 argc--; 843 argv++; 844 setup_enlistment_directory(argc, argv, usagestr, options, NULL); 845 strbuf_release(&buf); 846 847 if (i == 0) 848 return register_dir(1); 849 850 if (i > 0) 851 return run_git("maintenance", "run", 852 "--task", tasks[i].task, NULL); 853 854 if (register_dir(1)) 855 return -1; 856 for (i = 1; tasks[i].arg; i++) 857 if (run_git("maintenance", "run", 858 "--task", tasks[i].task, NULL)) 859 return -1; 860 return 0; 861} 862 863static int cmd_unregister(int argc, const char **argv) 864{ 865 struct option options[] = { 866 OPT_END(), 867 }; 868 const char * const usage[] = { 869 N_("scalar unregister [<enlistment>]"), 870 NULL 871 }; 872 873 argc = parse_options(argc, argv, NULL, options, 874 usage, 0); 875 876 /* 877 * Be forgiving when the enlistment or worktree does not even exist any 878 * longer; This can be the case if a user deleted the worktree by 879 * mistake and _still_ wants to unregister the thing. 880 */ 881 if (argc == 1) { 882 struct strbuf src_path = STRBUF_INIT, workdir_path = STRBUF_INIT; 883 884 strbuf_addf(&src_path, "%s/src/.git", argv[0]); 885 strbuf_addf(&workdir_path, "%s/.git", argv[0]); 886 if (!is_directory(src_path.buf) && !is_directory(workdir_path.buf)) { 887 /* remove possible matching registrations */ 888 int res = -1; 889 890 strbuf_strip_suffix(&src_path, "/.git"); 891 res = remove_deleted_enlistment(&src_path) && res; 892 893 strbuf_strip_suffix(&workdir_path, "/.git"); 894 res = remove_deleted_enlistment(&workdir_path) && res; 895 896 strbuf_release(&src_path); 897 strbuf_release(&workdir_path); 898 return res; 899 } 900 strbuf_release(&src_path); 901 strbuf_release(&workdir_path); 902 } 903 904 setup_enlistment_directory(argc, argv, usage, options, NULL); 905 906 return unregister_dir(); 907} 908 909static int cmd_delete(int argc, const char **argv) 910{ 911 char *cwd = xgetcwd(); 912 struct option options[] = { 913 OPT_END(), 914 }; 915 const char * const usage[] = { 916 N_("scalar delete <enlistment>"), 917 NULL 918 }; 919 struct strbuf enlistment = STRBUF_INIT; 920 int res = 0; 921 922 argc = parse_options(argc, argv, NULL, options, 923 usage, 0); 924 925 if (argc != 1) 926 usage_with_options(usage, options); 927 928 setup_enlistment_directory(argc, argv, usage, options, &enlistment); 929 930 if (dir_inside_of(cwd, enlistment.buf) >= 0) 931 res = error(_("refusing to delete current working directory")); 932 else { 933 close_object_store(the_repository->objects); 934 res = delete_enlistment(&enlistment); 935 } 936 strbuf_release(&enlistment); 937 free(cwd); 938 939 return res; 940} 941 942static int cmd_help(int argc, const char **argv) 943{ 944 struct option options[] = { 945 OPT_END(), 946 }; 947 const char * const usage[] = { 948 "scalar help", 949 NULL 950 }; 951 952 argc = parse_options(argc, argv, NULL, options, 953 usage, 0); 954 955 if (argc != 0) 956 usage_with_options(usage, options); 957 958 return run_git("help", "scalar", NULL); 959} 960 961static int cmd_version(int argc, const char **argv) 962{ 963 int verbose = 0, build_options = 0; 964 struct option options[] = { 965 OPT__VERBOSE(&verbose, N_("include Git version")), 966 OPT_BOOL(0, "build-options", &build_options, 967 N_("include Git's build options")), 968 OPT_END(), 969 }; 970 const char * const usage[] = { 971 N_("scalar verbose [-v | --verbose] [--build-options]"), 972 NULL 973 }; 974 struct strbuf buf = STRBUF_INIT; 975 976 argc = parse_options(argc, argv, NULL, options, 977 usage, 0); 978 979 if (argc != 0) 980 usage_with_options(usage, options); 981 982 get_version_info(&buf, build_options); 983 fprintf(stderr, "%s\n", buf.buf); 984 strbuf_release(&buf); 985 986 return 0; 987} 988 989static struct { 990 const char *name; 991 int (*fn)(int, const char **); 992} builtins[] = { 993 { "clone", cmd_clone }, 994 { "list", cmd_list }, 995 { "register", cmd_register }, 996 { "unregister", cmd_unregister }, 997 { "run", cmd_run }, 998 { "reconfigure", cmd_reconfigure }, 999 { "delete", cmd_delete }, 1000 { "help", cmd_help }, 1001 { "version", cmd_version }, 1002 { "diagnose", cmd_diagnose }, 1003 { NULL, NULL}, 1004}; 1005 1006int cmd_main(int argc, const char **argv) 1007{ 1008 struct strbuf scalar_usage = STRBUF_INIT; 1009 int i; 1010 1011 while (argc > 1 && *argv[1] == '-') { 1012 if (!strcmp(argv[1], "-C")) { 1013 if (argc < 3) 1014 die(_("-C requires a <directory>")); 1015 if (chdir(argv[2]) < 0) 1016 die_errno(_("could not change to '%s'"), 1017 argv[2]); 1018 argc -= 2; 1019 argv += 2; 1020 } else if (!strcmp(argv[1], "-c")) { 1021 if (argc < 3) 1022 die(_("-c requires a <key>=<value> argument")); 1023 git_config_push_parameter(argv[2]); 1024 argc -= 2; 1025 argv += 2; 1026 } else 1027 break; 1028 } 1029 1030 if (argc > 1) { 1031 argv++; 1032 argc--; 1033 1034 for (i = 0; builtins[i].name; i++) 1035 if (!strcmp(builtins[i].name, argv[0])) 1036 return !!builtins[i].fn(argc, argv); 1037 } 1038 1039 strbuf_addstr(&scalar_usage, 1040 N_("scalar [-C <directory>] [-c <key>=<value>] " 1041 "<command> [<options>]\n\nCommands:\n")); 1042 for (i = 0; builtins[i].name; i++) 1043 strbuf_addf(&scalar_usage, "\t%s\n", builtins[i].name); 1044 1045 usage(scalar_usage.buf); 1046}