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