The fifteenth batch
[alt-git.git] / scalar.c
bloba8318078c972b8a46813907f9259d17b4d7ec8d7
1 /*
2 * The Scalar command-line interface.
3 */
5 #include "git-compat-util.h"
6 #include "abspath.h"
7 #include "gettext.h"
8 #include "parse-options.h"
9 #include "config.h"
10 #include "run-command.h"
11 #include "simple-ipc.h"
12 #include "fsmonitor-ipc.h"
13 #include "fsmonitor-settings.h"
14 #include "refs.h"
15 #include "dir.h"
16 #include "packfile.h"
17 #include "help.h"
18 #include "setup.h"
19 #include "trace2.h"
21 static void setup_enlistment_directory(int argc, const char **argv,
22 const char * const *usagestr,
23 const struct option *options,
24 struct strbuf *enlistment_root)
26 struct strbuf path = STRBUF_INIT;
27 int enlistment_is_repo_parent = 0;
28 size_t len;
30 if (startup_info->have_repository)
31 BUG("gitdir already set up?!?");
33 if (argc > 1)
34 usage_with_options(usagestr, options);
36 /* find the worktree, determine its corresponding root */
37 if (argc == 1) {
38 strbuf_add_absolute_path(&path, argv[0]);
39 if (!is_directory(path.buf))
40 die(_("'%s' does not exist"), path.buf);
41 if (chdir(path.buf) < 0)
42 die_errno(_("could not switch to '%s'"), path.buf);
43 } else if (strbuf_getcwd(&path) < 0)
44 die(_("need a working directory"));
46 strbuf_trim_trailing_dir_sep(&path);
48 /* check if currently in enlistment root with src/ workdir */
49 len = path.len;
50 strbuf_addstr(&path, "/src");
51 if (is_nonbare_repository_dir(&path)) {
52 enlistment_is_repo_parent = 1;
53 if (chdir(path.buf) < 0)
54 die_errno(_("could not switch to '%s'"), path.buf);
56 strbuf_setlen(&path, len);
58 setup_git_directory();
60 if (!the_repository->worktree)
61 die(_("Scalar enlistments require a worktree"));
63 if (enlistment_root) {
64 if (enlistment_is_repo_parent)
65 strbuf_addbuf(enlistment_root, &path);
66 else
67 strbuf_addstr(enlistment_root, the_repository->worktree);
70 strbuf_release(&path);
73 LAST_ARG_MUST_BE_NULL
74 static int run_git(const char *arg, ...)
76 struct child_process cmd = CHILD_PROCESS_INIT;
77 va_list args;
78 const char *p;
80 va_start(args, arg);
81 strvec_push(&cmd.args, arg);
82 while ((p = va_arg(args, const char *)))
83 strvec_push(&cmd.args, p);
84 va_end(args);
86 cmd.git_cmd = 1;
87 return run_command(&cmd);
90 struct scalar_config {
91 const char *key;
92 const char *value;
93 int overwrite_on_reconfigure;
96 static int set_scalar_config(const struct scalar_config *config, int reconfigure)
98 char *value = NULL;
99 int res;
101 if ((reconfigure && config->overwrite_on_reconfigure) ||
102 git_config_get_string(config->key, &value)) {
103 trace2_data_string("scalar", the_repository, config->key, "created");
104 res = git_config_set_gently(config->key, config->value);
105 } else {
106 trace2_data_string("scalar", the_repository, config->key, "exists");
107 res = 0;
110 free(value);
111 return res;
114 static int have_fsmonitor_support(void)
116 return fsmonitor_ipc__is_supported() &&
117 fsm_settings__get_reason(the_repository) == FSMONITOR_REASON_OK;
120 static int set_recommended_config(int reconfigure)
122 struct scalar_config config[] = {
123 /* Required */
124 { "am.keepCR", "true", 1 },
125 { "core.FSCache", "true", 1 },
126 { "core.multiPackIndex", "true", 1 },
127 { "core.preloadIndex", "true", 1 },
128 #ifndef WIN32
129 { "core.untrackedCache", "true", 1 },
130 #else
132 * Unfortunately, Scalar's Functional Tests demonstrated
133 * that the untracked cache feature is unreliable on Windows
134 * (which is a bummer because that platform would benefit the
135 * most from it). For some reason, freshly created files seem
136 * not to update the directory's `lastModified` time
137 * immediately, but the untracked cache would need to rely on
138 * that.
140 * Therefore, with a sad heart, we disable this very useful
141 * feature on Windows.
143 { "core.untrackedCache", "false", 1 },
144 #endif
145 { "core.logAllRefUpdates", "true", 1 },
146 { "credential.https://dev.azure.com.useHttpPath", "true", 1 },
147 { "credential.validate", "false", 1 }, /* GCM4W-only */
148 { "gc.auto", "0", 1 },
149 { "gui.GCWarning", "false", 1 },
150 { "index.skipHash", "false", 1 },
151 { "index.threads", "true", 1 },
152 { "index.version", "4", 1 },
153 { "merge.stat", "false", 1 },
154 { "merge.renames", "true", 1 },
155 { "pack.useBitmaps", "false", 1 },
156 { "pack.useSparse", "true", 1 },
157 { "receive.autoGC", "false", 1 },
158 { "feature.manyFiles", "false", 1 },
159 { "feature.experimental", "false", 1 },
160 { "fetch.unpackLimit", "1", 1 },
161 { "fetch.writeCommitGraph", "false", 1 },
162 #ifdef WIN32
163 { "http.sslBackend", "schannel", 1 },
164 #endif
165 /* Optional */
166 { "status.aheadBehind", "false" },
167 { "commitGraph.generationVersion", "1" },
168 { "core.autoCRLF", "false" },
169 { "core.safeCRLF", "false" },
170 { "fetch.showForcedUpdates", "false" },
171 { NULL, NULL },
173 int i;
174 char *value;
176 for (i = 0; config[i].key; i++) {
177 if (set_scalar_config(config + i, reconfigure))
178 return error(_("could not configure %s=%s"),
179 config[i].key, config[i].value);
182 if (have_fsmonitor_support()) {
183 struct scalar_config fsmonitor = { "core.fsmonitor", "true" };
184 if (set_scalar_config(&fsmonitor, reconfigure))
185 return error(_("could not configure %s=%s"),
186 fsmonitor.key, fsmonitor.value);
190 * The `log.excludeDecoration` setting is special because it allows
191 * for multiple values.
193 if (git_config_get_string("log.excludeDecoration", &value)) {
194 trace2_data_string("scalar", the_repository,
195 "log.excludeDecoration", "created");
196 if (git_config_set_multivar_gently("log.excludeDecoration",
197 "refs/prefetch/*",
198 CONFIG_REGEX_NONE, 0))
199 return error(_("could not configure "
200 "log.excludeDecoration"));
201 } else {
202 trace2_data_string("scalar", the_repository,
203 "log.excludeDecoration", "exists");
204 free(value);
207 return 0;
210 static int toggle_maintenance(int enable)
212 return run_git("maintenance",
213 enable ? "start" : "unregister",
214 enable ? NULL : "--force",
215 NULL);
218 static int add_or_remove_enlistment(int add)
220 int res;
222 if (!the_repository->worktree)
223 die(_("Scalar enlistments require a worktree"));
225 res = run_git("config", "--global", "--get", "--fixed-value",
226 "scalar.repo", the_repository->worktree, NULL);
229 * If we want to add and the setting is already there, then do nothing.
230 * If we want to remove and the setting is not there, then do nothing.
232 if ((add && !res) || (!add && res))
233 return 0;
235 return run_git("config", "--global", add ? "--add" : "--unset",
236 add ? "--no-fixed-value" : "--fixed-value",
237 "scalar.repo", the_repository->worktree, NULL);
240 static int start_fsmonitor_daemon(void)
242 assert(have_fsmonitor_support());
244 if (fsmonitor_ipc__get_state() != IPC_STATE__LISTENING)
245 return run_git("fsmonitor--daemon", "start", NULL);
247 return 0;
250 static int stop_fsmonitor_daemon(void)
252 assert(have_fsmonitor_support());
254 if (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING)
255 return run_git("fsmonitor--daemon", "stop", NULL);
257 return 0;
260 static int register_dir(void)
262 if (add_or_remove_enlistment(1))
263 return error(_("could not add enlistment"));
265 if (set_recommended_config(0))
266 return error(_("could not set recommended config"));
268 if (toggle_maintenance(1))
269 warning(_("could not turn on maintenance"));
271 if (have_fsmonitor_support() && start_fsmonitor_daemon()) {
272 return error(_("could not start the FSMonitor daemon"));
275 return 0;
278 static int unregister_dir(void)
280 int res = 0;
282 if (toggle_maintenance(0))
283 res = error(_("could not turn off maintenance"));
285 if (add_or_remove_enlistment(0))
286 res = error(_("could not remove enlistment"));
288 return res;
291 /* printf-style interface, expects `<key>=<value>` argument */
292 __attribute__((format (printf, 1, 2)))
293 static int set_config(const char *fmt, ...)
295 struct strbuf buf = STRBUF_INIT;
296 char *value;
297 int res;
298 va_list args;
300 va_start(args, fmt);
301 strbuf_vaddf(&buf, fmt, args);
302 va_end(args);
304 value = strchr(buf.buf, '=');
305 if (value)
306 *(value++) = '\0';
307 res = git_config_set_gently(buf.buf, value);
308 strbuf_release(&buf);
310 return res;
313 static char *remote_default_branch(const char *url)
315 struct child_process cp = CHILD_PROCESS_INIT;
316 struct strbuf out = STRBUF_INIT;
318 cp.git_cmd = 1;
319 strvec_pushl(&cp.args, "ls-remote", "--symref", url, "HEAD", NULL);
320 if (!pipe_command(&cp, NULL, 0, &out, 0, NULL, 0)) {
321 const char *line = out.buf;
323 while (*line) {
324 const char *eol = strchrnul(line, '\n'), *p;
325 size_t len = eol - line;
326 char *branch;
328 if (!skip_prefix(line, "ref: ", &p) ||
329 !strip_suffix_mem(line, &len, "\tHEAD")) {
330 line = eol + (*eol == '\n');
331 continue;
334 eol = line + len;
335 if (skip_prefix(p, "refs/heads/", &p)) {
336 branch = xstrndup(p, eol - p);
337 strbuf_release(&out);
338 return branch;
341 error(_("remote HEAD is not a branch: '%.*s'"),
342 (int)(eol - p), p);
343 strbuf_release(&out);
344 return NULL;
347 warning(_("failed to get default branch name from remote; "
348 "using local default"));
349 strbuf_reset(&out);
351 child_process_init(&cp);
352 cp.git_cmd = 1;
353 strvec_pushl(&cp.args, "symbolic-ref", "--short", "HEAD", NULL);
354 if (!pipe_command(&cp, NULL, 0, &out, 0, NULL, 0)) {
355 strbuf_trim(&out);
356 return strbuf_detach(&out, NULL);
359 strbuf_release(&out);
360 error(_("failed to get default branch name"));
361 return NULL;
364 static int delete_enlistment(struct strbuf *enlistment)
366 struct strbuf parent = STRBUF_INIT;
367 size_t offset;
368 char *path_sep;
370 if (unregister_dir())
371 return error(_("failed to unregister repository"));
374 * Change the current directory to one outside of the enlistment so
375 * that we may delete everything underneath it.
377 offset = offset_1st_component(enlistment->buf);
378 path_sep = find_last_dir_sep(enlistment->buf + offset);
379 strbuf_add(&parent, enlistment->buf,
380 path_sep ? path_sep - enlistment->buf : offset);
381 if (chdir(parent.buf) < 0) {
382 int res = error_errno(_("could not switch to '%s'"), parent.buf);
383 strbuf_release(&parent);
384 return res;
386 strbuf_release(&parent);
388 if (have_fsmonitor_support() && stop_fsmonitor_daemon())
389 return error(_("failed to stop the FSMonitor daemon"));
391 if (remove_dir_recursively(enlistment, 0))
392 return error(_("failed to delete enlistment directory"));
394 return 0;
398 * Dummy implementation; Using `get_version_info()` would cause a link error
399 * without this.
401 void load_builtin_commands(const char *prefix, struct cmdnames *cmds)
403 die("not implemented");
406 static int cmd_clone(int argc, const char **argv)
408 const char *branch = NULL;
409 int full_clone = 0, single_branch = 0, show_progress = isatty(2);
410 int src = 1;
411 struct option clone_options[] = {
412 OPT_STRING('b', "branch", &branch, N_("<branch>"),
413 N_("branch to checkout after clone")),
414 OPT_BOOL(0, "full-clone", &full_clone,
415 N_("when cloning, create full working directory")),
416 OPT_BOOL(0, "single-branch", &single_branch,
417 N_("only download metadata for the branch that will "
418 "be checked out")),
419 OPT_BOOL(0, "src", &src,
420 N_("create repository within 'src' directory")),
421 OPT_END(),
423 const char * const clone_usage[] = {
424 N_("scalar clone [--single-branch] [--branch <main-branch>] [--full-clone]\n"
425 "\t[--[no-]src] <url> [<enlistment>]"),
426 NULL
428 const char *url;
429 char *enlistment = NULL, *dir = NULL;
430 struct strbuf buf = STRBUF_INIT;
431 int res;
433 argc = parse_options(argc, argv, NULL, clone_options, clone_usage, 0);
435 if (argc == 2) {
436 url = argv[0];
437 enlistment = xstrdup(argv[1]);
438 } else if (argc == 1) {
439 url = argv[0];
441 strbuf_addstr(&buf, url);
442 /* Strip trailing slashes, if any */
443 while (buf.len > 0 && is_dir_sep(buf.buf[buf.len - 1]))
444 strbuf_setlen(&buf, buf.len - 1);
445 /* Strip suffix `.git`, if any */
446 strbuf_strip_suffix(&buf, ".git");
448 enlistment = find_last_dir_sep(buf.buf);
449 if (!enlistment) {
450 die(_("cannot deduce worktree name from '%s'"), url);
452 enlistment = xstrdup(enlistment + 1);
453 } else {
454 usage_msg_opt(_("You must specify a repository to clone."),
455 clone_usage, clone_options);
458 if (is_directory(enlistment))
459 die(_("directory '%s' exists already"), enlistment);
461 if (src)
462 dir = xstrfmt("%s/src", enlistment);
463 else
464 dir = xstrdup(enlistment);
466 strbuf_reset(&buf);
467 if (branch)
468 strbuf_addf(&buf, "init.defaultBranch=%s", branch);
469 else {
470 char *b = repo_default_branch_name(the_repository, 1);
471 strbuf_addf(&buf, "init.defaultBranch=%s", b);
472 free(b);
475 if ((res = run_git("-c", buf.buf, "init", "--", dir, NULL)))
476 goto cleanup;
478 if (chdir(dir) < 0) {
479 res = error_errno(_("could not switch to '%s'"), dir);
480 goto cleanup;
483 setup_git_directory();
485 /* common-main already logs `argv` */
486 trace2_def_repo(the_repository);
488 if (!branch && !(branch = remote_default_branch(url))) {
489 res = error(_("failed to get default branch for '%s'"), url);
490 goto cleanup;
493 if (set_config("remote.origin.url=%s", url) ||
494 set_config("remote.origin.fetch="
495 "+refs/heads/%s:refs/remotes/origin/%s",
496 single_branch ? branch : "*",
497 single_branch ? branch : "*") ||
498 set_config("remote.origin.promisor=true") ||
499 set_config("remote.origin.partialCloneFilter=blob:none")) {
500 res = error(_("could not configure remote in '%s'"), dir);
501 goto cleanup;
504 if (!full_clone &&
505 (res = run_git("sparse-checkout", "init", "--cone", NULL)))
506 goto cleanup;
508 if (set_recommended_config(0))
509 return error(_("could not configure '%s'"), dir);
511 if ((res = run_git("fetch", "--quiet",
512 show_progress ? "--progress" : "--no-progress",
513 "origin", NULL))) {
514 warning(_("partial clone failed; attempting full clone"));
516 if (set_config("remote.origin.promisor") ||
517 set_config("remote.origin.partialCloneFilter")) {
518 res = error(_("could not configure for full clone"));
519 goto cleanup;
522 if ((res = run_git("fetch", "--quiet",
523 show_progress ? "--progress" : "--no-progress",
524 "origin", NULL)))
525 goto cleanup;
528 if ((res = set_config("branch.%s.remote=origin", branch)))
529 goto cleanup;
530 if ((res = set_config("branch.%s.merge=refs/heads/%s",
531 branch, branch)))
532 goto cleanup;
534 strbuf_reset(&buf);
535 strbuf_addf(&buf, "origin/%s", branch);
536 res = run_git("checkout", "-f", "-t", buf.buf, NULL);
537 if (res)
538 goto cleanup;
540 res = register_dir();
542 cleanup:
543 free(enlistment);
544 free(dir);
545 strbuf_release(&buf);
546 return res;
549 static int cmd_diagnose(int argc, const char **argv)
551 struct option options[] = {
552 OPT_END(),
554 const char * const usage[] = {
555 N_("scalar diagnose [<enlistment>]"),
556 NULL
558 struct strbuf diagnostics_root = STRBUF_INIT;
559 int res = 0;
561 argc = parse_options(argc, argv, NULL, options,
562 usage, 0);
564 setup_enlistment_directory(argc, argv, usage, options, &diagnostics_root);
565 strbuf_addstr(&diagnostics_root, "/.scalarDiagnostics");
567 res = run_git("diagnose", "--mode=all", "-s", "%Y%m%d_%H%M%S",
568 "-o", diagnostics_root.buf, NULL);
570 strbuf_release(&diagnostics_root);
571 return res;
574 static int cmd_list(int argc, const char **argv UNUSED)
576 if (argc != 1)
577 die(_("`scalar list` does not take arguments"));
579 if (run_git("config", "--global", "--get-all", "scalar.repo", NULL) < 0)
580 return -1;
581 return 0;
584 static int cmd_register(int argc, const char **argv)
586 struct option options[] = {
587 OPT_END(),
589 const char * const usage[] = {
590 N_("scalar register [<enlistment>]"),
591 NULL
594 argc = parse_options(argc, argv, NULL, options,
595 usage, 0);
597 setup_enlistment_directory(argc, argv, usage, options, NULL);
599 return register_dir();
602 static int get_scalar_repos(const char *key, const char *value,
603 const struct config_context *ctx UNUSED,
604 void *data)
606 struct string_list *list = data;
608 if (!strcmp(key, "scalar.repo"))
609 string_list_append(list, value);
611 return 0;
614 static int remove_deleted_enlistment(struct strbuf *path)
616 int res = 0;
617 strbuf_realpath_forgiving(path, path->buf, 1);
619 if (run_git("config", "--global",
620 "--unset", "--fixed-value",
621 "scalar.repo", path->buf, NULL) < 0)
622 res = -1;
624 if (run_git("config", "--global",
625 "--unset", "--fixed-value",
626 "maintenance.repo", path->buf, NULL) < 0)
627 res = -1;
629 return res;
632 static int cmd_reconfigure(int argc, const char **argv)
634 int all = 0;
635 struct option options[] = {
636 OPT_BOOL('a', "all", &all,
637 N_("reconfigure all registered enlistments")),
638 OPT_END(),
640 const char * const usage[] = {
641 N_("scalar reconfigure [--all | <enlistment>]"),
642 NULL
644 struct string_list scalar_repos = STRING_LIST_INIT_DUP;
645 int i, res = 0;
646 struct strbuf commondir = STRBUF_INIT, gitdir = STRBUF_INIT;
648 argc = parse_options(argc, argv, NULL, options,
649 usage, 0);
651 if (!all) {
652 setup_enlistment_directory(argc, argv, usage, options, NULL);
654 return set_recommended_config(1);
657 if (argc > 0)
658 usage_msg_opt(_("--all or <enlistment>, but not both"),
659 usage, options);
661 git_config(get_scalar_repos, &scalar_repos);
663 for (i = 0; i < scalar_repos.nr; i++) {
664 int succeeded = 0;
665 struct repository *old_repo, r = { NULL };
666 const char *dir = scalar_repos.items[i].string;
668 strbuf_reset(&commondir);
669 strbuf_reset(&gitdir);
671 if (chdir(dir) < 0) {
672 struct strbuf buf = STRBUF_INIT;
674 if (errno != ENOENT) {
675 warning_errno(_("could not switch to '%s'"), dir);
676 goto loop_end;
679 strbuf_addstr(&buf, dir);
680 if (remove_deleted_enlistment(&buf))
681 error(_("could not remove stale "
682 "scalar.repo '%s'"), dir);
683 else {
684 warning(_("removed stale scalar.repo '%s'"),
685 dir);
686 succeeded = 1;
688 strbuf_release(&buf);
689 goto loop_end;
692 switch (discover_git_directory_reason(&commondir, &gitdir)) {
693 case GIT_DIR_INVALID_OWNERSHIP:
694 warning(_("repository at '%s' has different owner"), dir);
695 goto loop_end;
697 case GIT_DIR_INVALID_GITFILE:
698 case GIT_DIR_INVALID_FORMAT:
699 warning(_("repository at '%s' has a format issue"), dir);
700 goto loop_end;
702 case GIT_DIR_DISCOVERED:
703 succeeded = 1;
704 break;
706 default:
707 warning(_("repository not found in '%s'"), dir);
708 break;
711 git_config_clear();
713 if (repo_init(&r, gitdir.buf, commondir.buf))
714 goto loop_end;
716 old_repo = the_repository;
717 the_repository = &r;
719 if (set_recommended_config(1) >= 0)
720 succeeded = 1;
722 the_repository = old_repo;
724 loop_end:
725 if (!succeeded) {
726 res = -1;
727 warning(_("to unregister this repository from Scalar, run\n"
728 "\tgit config --global --unset --fixed-value scalar.repo \"%s\""),
729 dir);
733 string_list_clear(&scalar_repos, 1);
734 strbuf_release(&commondir);
735 strbuf_release(&gitdir);
737 return res;
740 static int cmd_run(int argc, const char **argv)
742 struct option options[] = {
743 OPT_END(),
745 struct {
746 const char *arg, *task;
747 } tasks[] = {
748 { "config", NULL },
749 { "commit-graph", "commit-graph" },
750 { "fetch", "prefetch" },
751 { "loose-objects", "loose-objects" },
752 { "pack-files", "incremental-repack" },
753 { NULL, NULL }
755 struct strbuf buf = STRBUF_INIT;
756 const char *usagestr[] = { NULL, NULL };
757 int i;
759 strbuf_addstr(&buf, N_("scalar run <task> [<enlistment>]\nTasks:\n"));
760 for (i = 0; tasks[i].arg; i++)
761 strbuf_addf(&buf, "\t%s\n", tasks[i].arg);
762 usagestr[0] = buf.buf;
764 argc = parse_options(argc, argv, NULL, options,
765 usagestr, 0);
767 if (!argc)
768 usage_with_options(usagestr, options);
770 if (!strcmp("all", argv[0])) {
771 i = -1;
772 } else {
773 for (i = 0; tasks[i].arg && strcmp(tasks[i].arg, argv[0]); i++)
774 ; /* keep looking for the task */
776 if (i > 0 && !tasks[i].arg) {
777 error(_("no such task: '%s'"), argv[0]);
778 usage_with_options(usagestr, options);
782 argc--;
783 argv++;
784 setup_enlistment_directory(argc, argv, usagestr, options, NULL);
785 strbuf_release(&buf);
787 if (i == 0)
788 return register_dir();
790 if (i > 0)
791 return run_git("maintenance", "run",
792 "--task", tasks[i].task, NULL);
794 if (register_dir())
795 return -1;
796 for (i = 1; tasks[i].arg; i++)
797 if (run_git("maintenance", "run",
798 "--task", tasks[i].task, NULL))
799 return -1;
800 return 0;
803 static int cmd_unregister(int argc, const char **argv)
805 struct option options[] = {
806 OPT_END(),
808 const char * const usage[] = {
809 N_("scalar unregister [<enlistment>]"),
810 NULL
813 argc = parse_options(argc, argv, NULL, options,
814 usage, 0);
817 * Be forgiving when the enlistment or worktree does not even exist any
818 * longer; This can be the case if a user deleted the worktree by
819 * mistake and _still_ wants to unregister the thing.
821 if (argc == 1) {
822 struct strbuf src_path = STRBUF_INIT, workdir_path = STRBUF_INIT;
824 strbuf_addf(&src_path, "%s/src/.git", argv[0]);
825 strbuf_addf(&workdir_path, "%s/.git", argv[0]);
826 if (!is_directory(src_path.buf) && !is_directory(workdir_path.buf)) {
827 /* remove possible matching registrations */
828 int res = -1;
830 strbuf_strip_suffix(&src_path, "/.git");
831 res = remove_deleted_enlistment(&src_path) && res;
833 strbuf_strip_suffix(&workdir_path, "/.git");
834 res = remove_deleted_enlistment(&workdir_path) && res;
836 strbuf_release(&src_path);
837 strbuf_release(&workdir_path);
838 return res;
840 strbuf_release(&src_path);
841 strbuf_release(&workdir_path);
844 setup_enlistment_directory(argc, argv, usage, options, NULL);
846 return unregister_dir();
849 static int cmd_delete(int argc, const char **argv)
851 char *cwd = xgetcwd();
852 struct option options[] = {
853 OPT_END(),
855 const char * const usage[] = {
856 N_("scalar delete <enlistment>"),
857 NULL
859 struct strbuf enlistment = STRBUF_INIT;
860 int res = 0;
862 argc = parse_options(argc, argv, NULL, options,
863 usage, 0);
865 if (argc != 1)
866 usage_with_options(usage, options);
868 setup_enlistment_directory(argc, argv, usage, options, &enlistment);
870 if (dir_inside_of(cwd, enlistment.buf) >= 0)
871 res = error(_("refusing to delete current working directory"));
872 else {
873 close_object_store(the_repository->objects);
874 res = delete_enlistment(&enlistment);
876 strbuf_release(&enlistment);
877 free(cwd);
879 return res;
882 static int cmd_help(int argc, const char **argv)
884 struct option options[] = {
885 OPT_END(),
887 const char * const usage[] = {
888 "scalar help",
889 NULL
892 argc = parse_options(argc, argv, NULL, options,
893 usage, 0);
895 if (argc != 0)
896 usage_with_options(usage, options);
898 return run_git("help", "scalar", NULL);
901 static int cmd_version(int argc, const char **argv)
903 int verbose = 0, build_options = 0;
904 struct option options[] = {
905 OPT__VERBOSE(&verbose, N_("include Git version")),
906 OPT_BOOL(0, "build-options", &build_options,
907 N_("include Git's build options")),
908 OPT_END(),
910 const char * const usage[] = {
911 N_("scalar verbose [-v | --verbose] [--build-options]"),
912 NULL
914 struct strbuf buf = STRBUF_INIT;
916 argc = parse_options(argc, argv, NULL, options,
917 usage, 0);
919 if (argc != 0)
920 usage_with_options(usage, options);
922 get_version_info(&buf, build_options);
923 fprintf(stderr, "%s\n", buf.buf);
924 strbuf_release(&buf);
926 return 0;
929 static struct {
930 const char *name;
931 int (*fn)(int, const char **);
932 } builtins[] = {
933 { "clone", cmd_clone },
934 { "list", cmd_list },
935 { "register", cmd_register },
936 { "unregister", cmd_unregister },
937 { "run", cmd_run },
938 { "reconfigure", cmd_reconfigure },
939 { "delete", cmd_delete },
940 { "help", cmd_help },
941 { "version", cmd_version },
942 { "diagnose", cmd_diagnose },
943 { NULL, NULL},
946 int cmd_main(int argc, const char **argv)
948 struct strbuf scalar_usage = STRBUF_INIT;
949 int i;
951 while (argc > 1 && *argv[1] == '-') {
952 if (!strcmp(argv[1], "-C")) {
953 if (argc < 3)
954 die(_("-C requires a <directory>"));
955 if (chdir(argv[2]) < 0)
956 die_errno(_("could not change to '%s'"),
957 argv[2]);
958 argc -= 2;
959 argv += 2;
960 } else if (!strcmp(argv[1], "-c")) {
961 if (argc < 3)
962 die(_("-c requires a <key>=<value> argument"));
963 git_config_push_parameter(argv[2]);
964 argc -= 2;
965 argv += 2;
966 } else
967 break;
970 if (argc > 1) {
971 argv++;
972 argc--;
974 for (i = 0; builtins[i].name; i++)
975 if (!strcmp(builtins[i].name, argv[0]))
976 return !!builtins[i].fn(argc, argv);
979 strbuf_addstr(&scalar_usage,
980 N_("scalar [-C <directory>] [-c <key>=<value>] "
981 "<command> [<options>]\n\nCommands:\n"));
982 for (i = 0; builtins[i].name; i++)
983 strbuf_addf(&scalar_usage, "\t%s\n", builtins[i].name);
985 usage(scalar_usage.buf);