]> git.ipfire.org Git - thirdparty/git.git/blob - contrib/scalar/scalar.c
diagnose.c: add option to configure archive contents
[thirdparty/git.git] / contrib / scalar / scalar.c
1 /*
2 * The Scalar command-line interface.
3 */
4
5 #include "cache.h"
6 #include "gettext.h"
7 #include "parse-options.h"
8 #include "config.h"
9 #include "run-command.h"
10 #include "refs.h"
11 #include "dir.h"
12 #include "packfile.h"
13 #include "help.h"
14 #include "diagnose.h"
15
16 /*
17 * Remove the deepest subdirectory in the provided path string. Path must not
18 * include a trailing path separator. Returns 1 if parent directory found,
19 * otherwise 0.
20 */
21 static int strbuf_parent_directory(struct strbuf *buf)
22 {
23 size_t len = buf->len;
24 size_t offset = offset_1st_component(buf->buf);
25 char *path_sep = find_last_dir_sep(buf->buf + offset);
26 strbuf_setlen(buf, path_sep ? path_sep - buf->buf : offset);
27
28 return buf->len < len;
29 }
30
31 static void setup_enlistment_directory(int argc, const char **argv,
32 const char * const *usagestr,
33 const struct option *options,
34 struct strbuf *enlistment_root)
35 {
36 struct strbuf path = STRBUF_INIT;
37 char *root;
38 int enlistment_found = 0;
39
40 if (startup_info->have_repository)
41 BUG("gitdir already set up?!?");
42
43 if (argc > 1)
44 usage_with_options(usagestr, options);
45
46 /* find the worktree, determine its corresponding root */
47 if (argc == 1) {
48 strbuf_add_absolute_path(&path, argv[0]);
49 if (!is_directory(path.buf))
50 die(_("'%s' does not exist"), path.buf);
51 } else if (strbuf_getcwd(&path) < 0)
52 die(_("need a working directory"));
53
54 strbuf_trim_trailing_dir_sep(&path);
55 do {
56 const size_t len = path.len;
57
58 /* check if currently in enlistment root with src/ workdir */
59 strbuf_addstr(&path, "/src");
60 if (is_nonbare_repository_dir(&path)) {
61 if (enlistment_root)
62 strbuf_add(enlistment_root, path.buf, len);
63
64 enlistment_found = 1;
65 break;
66 }
67
68 /* reset to original path */
69 strbuf_setlen(&path, len);
70
71 /* check if currently in workdir */
72 if (is_nonbare_repository_dir(&path)) {
73 if (enlistment_root) {
74 /*
75 * If the worktree's directory's name is `src`, the enlistment is the
76 * parent directory, otherwise it is identical to the worktree.
77 */
78 root = strip_path_suffix(path.buf, "src");
79 strbuf_addstr(enlistment_root, root ? root : path.buf);
80 free(root);
81 }
82
83 enlistment_found = 1;
84 break;
85 }
86 } while (strbuf_parent_directory(&path));
87
88 if (!enlistment_found)
89 die(_("could not find enlistment root"));
90
91 if (chdir(path.buf) < 0)
92 die_errno(_("could not switch to '%s'"), path.buf);
93
94 strbuf_release(&path);
95 setup_git_directory();
96 }
97
98 static int run_git(const char *arg, ...)
99 {
100 struct strvec argv = STRVEC_INIT;
101 va_list args;
102 const char *p;
103 int res;
104
105 va_start(args, arg);
106 strvec_push(&argv, arg);
107 while ((p = va_arg(args, const char *)))
108 strvec_push(&argv, p);
109 va_end(args);
110
111 res = run_command_v_opt(argv.v, RUN_GIT_CMD);
112
113 strvec_clear(&argv);
114 return res;
115 }
116
117 static int set_recommended_config(int reconfigure)
118 {
119 struct {
120 const char *key;
121 const char *value;
122 int overwrite_on_reconfigure;
123 } config[] = {
124 /* Required */
125 { "am.keepCR", "true", 1 },
126 { "core.FSCache", "true", 1 },
127 { "core.multiPackIndex", "true", 1 },
128 { "core.preloadIndex", "true", 1 },
129 #ifndef WIN32
130 { "core.untrackedCache", "true", 1 },
131 #else
132 /*
133 * Unfortunately, Scalar's Functional Tests demonstrated
134 * that the untracked cache feature is unreliable on Windows
135 * (which is a bummer because that platform would benefit the
136 * most from it). For some reason, freshly created files seem
137 * not to update the directory's `lastModified` time
138 * immediately, but the untracked cache would need to rely on
139 * that.
140 *
141 * Therefore, with a sad heart, we disable this very useful
142 * feature on Windows.
143 */
144 { "core.untrackedCache", "false", 1 },
145 #endif
146 { "core.logAllRefUpdates", "true", 1 },
147 { "credential.https://dev.azure.com.useHttpPath", "true", 1 },
148 { "credential.validate", "false", 1 }, /* GCM4W-only */
149 { "gc.auto", "0", 1 },
150 { "gui.GCWarning", "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 },
172 };
173 int i;
174 char *value;
175
176 for (i = 0; config[i].key; i++) {
177 if ((reconfigure && config[i].overwrite_on_reconfigure) ||
178 git_config_get_string(config[i].key, &value)) {
179 trace2_data_string("scalar", the_repository, config[i].key, "created");
180 if (git_config_set_gently(config[i].key,
181 config[i].value) < 0)
182 return error(_("could not configure %s=%s"),
183 config[i].key, config[i].value);
184 } else {
185 trace2_data_string("scalar", the_repository, config[i].key, "exists");
186 free(value);
187 }
188 }
189
190 /*
191 * The `log.excludeDecoration` setting is special because it allows
192 * for multiple values.
193 */
194 if (git_config_get_string("log.excludeDecoration", &value)) {
195 trace2_data_string("scalar", the_repository,
196 "log.excludeDecoration", "created");
197 if (git_config_set_multivar_gently("log.excludeDecoration",
198 "refs/prefetch/*",
199 CONFIG_REGEX_NONE, 0))
200 return error(_("could not configure "
201 "log.excludeDecoration"));
202 } else {
203 trace2_data_string("scalar", the_repository,
204 "log.excludeDecoration", "exists");
205 free(value);
206 }
207
208 return 0;
209 }
210
211 static int toggle_maintenance(int enable)
212 {
213 return run_git("maintenance", enable ? "start" : "unregister", NULL);
214 }
215
216 static int add_or_remove_enlistment(int add)
217 {
218 int res;
219
220 if (!the_repository->worktree)
221 die(_("Scalar enlistments require a worktree"));
222
223 res = run_git("config", "--global", "--get", "--fixed-value",
224 "scalar.repo", the_repository->worktree, NULL);
225
226 /*
227 * If we want to add and the setting is already there, then do nothing.
228 * If we want to remove and the setting is not there, then do nothing.
229 */
230 if ((add && !res) || (!add && res))
231 return 0;
232
233 return run_git("config", "--global", add ? "--add" : "--unset",
234 add ? "--no-fixed-value" : "--fixed-value",
235 "scalar.repo", the_repository->worktree, NULL);
236 }
237
238 static int register_dir(void)
239 {
240 int res = add_or_remove_enlistment(1);
241
242 if (!res)
243 res = set_recommended_config(0);
244
245 if (!res)
246 res = toggle_maintenance(1);
247
248 return res;
249 }
250
251 static int unregister_dir(void)
252 {
253 int res = 0;
254
255 if (toggle_maintenance(0) < 0)
256 res = -1;
257
258 if (add_or_remove_enlistment(0) < 0)
259 res = -1;
260
261 return res;
262 }
263
264 /* printf-style interface, expects `<key>=<value>` argument */
265 static int set_config(const char *fmt, ...)
266 {
267 struct strbuf buf = STRBUF_INIT;
268 char *value;
269 int res;
270 va_list args;
271
272 va_start(args, fmt);
273 strbuf_vaddf(&buf, fmt, args);
274 va_end(args);
275
276 value = strchr(buf.buf, '=');
277 if (value)
278 *(value++) = '\0';
279 res = git_config_set_gently(buf.buf, value);
280 strbuf_release(&buf);
281
282 return res;
283 }
284
285 static char *remote_default_branch(const char *url)
286 {
287 struct child_process cp = CHILD_PROCESS_INIT;
288 struct strbuf out = STRBUF_INIT;
289
290 cp.git_cmd = 1;
291 strvec_pushl(&cp.args, "ls-remote", "--symref", url, "HEAD", NULL);
292 if (!pipe_command(&cp, NULL, 0, &out, 0, NULL, 0)) {
293 const char *line = out.buf;
294
295 while (*line) {
296 const char *eol = strchrnul(line, '\n'), *p;
297 size_t len = eol - line;
298 char *branch;
299
300 if (!skip_prefix(line, "ref: ", &p) ||
301 !strip_suffix_mem(line, &len, "\tHEAD")) {
302 line = eol + (*eol == '\n');
303 continue;
304 }
305
306 eol = line + len;
307 if (skip_prefix(p, "refs/heads/", &p)) {
308 branch = xstrndup(p, eol - p);
309 strbuf_release(&out);
310 return branch;
311 }
312
313 error(_("remote HEAD is not a branch: '%.*s'"),
314 (int)(eol - p), p);
315 strbuf_release(&out);
316 return NULL;
317 }
318 }
319 warning(_("failed to get default branch name from remote; "
320 "using local default"));
321 strbuf_reset(&out);
322
323 child_process_init(&cp);
324 cp.git_cmd = 1;
325 strvec_pushl(&cp.args, "symbolic-ref", "--short", "HEAD", NULL);
326 if (!pipe_command(&cp, NULL, 0, &out, 0, NULL, 0)) {
327 strbuf_trim(&out);
328 return strbuf_detach(&out, NULL);
329 }
330
331 strbuf_release(&out);
332 error(_("failed to get default branch name"));
333 return NULL;
334 }
335
336 static int delete_enlistment(struct strbuf *enlistment)
337 {
338 #ifdef WIN32
339 struct strbuf parent = STRBUF_INIT;
340 #endif
341
342 if (unregister_dir())
343 die(_("failed to unregister repository"));
344
345 #ifdef WIN32
346 /*
347 * Change the current directory to one outside of the enlistment so
348 * that we may delete everything underneath it.
349 */
350 strbuf_addbuf(&parent, enlistment);
351 strbuf_parent_directory(&parent);
352 if (chdir(parent.buf) < 0)
353 die_errno(_("could not switch to '%s'"), parent.buf);
354 strbuf_release(&parent);
355 #endif
356
357 if (remove_dir_recursively(enlistment, 0))
358 die(_("failed to delete enlistment directory"));
359
360 return 0;
361 }
362
363 /*
364 * Dummy implementation; Using `get_version_info()` would cause a link error
365 * without this.
366 */
367 void load_builtin_commands(const char *prefix, struct cmdnames *cmds)
368 {
369 die("not implemented");
370 }
371
372 static int cmd_clone(int argc, const char **argv)
373 {
374 const char *branch = NULL;
375 int full_clone = 0, single_branch = 0;
376 struct option clone_options[] = {
377 OPT_STRING('b', "branch", &branch, N_("<branch>"),
378 N_("branch to checkout after clone")),
379 OPT_BOOL(0, "full-clone", &full_clone,
380 N_("when cloning, create full working directory")),
381 OPT_BOOL(0, "single-branch", &single_branch,
382 N_("only download metadata for the branch that will "
383 "be checked out")),
384 OPT_END(),
385 };
386 const char * const clone_usage[] = {
387 N_("scalar clone [<options>] [--] <repo> [<dir>]"),
388 NULL
389 };
390 const char *url;
391 char *enlistment = NULL, *dir = NULL;
392 struct strbuf buf = STRBUF_INIT;
393 int res;
394
395 argc = parse_options(argc, argv, NULL, clone_options, clone_usage, 0);
396
397 if (argc == 2) {
398 url = argv[0];
399 enlistment = xstrdup(argv[1]);
400 } else if (argc == 1) {
401 url = argv[0];
402
403 strbuf_addstr(&buf, url);
404 /* Strip trailing slashes, if any */
405 while (buf.len > 0 && is_dir_sep(buf.buf[buf.len - 1]))
406 strbuf_setlen(&buf, buf.len - 1);
407 /* Strip suffix `.git`, if any */
408 strbuf_strip_suffix(&buf, ".git");
409
410 enlistment = find_last_dir_sep(buf.buf);
411 if (!enlistment) {
412 die(_("cannot deduce worktree name from '%s'"), url);
413 }
414 enlistment = xstrdup(enlistment + 1);
415 } else {
416 usage_msg_opt(_("You must specify a repository to clone."),
417 clone_usage, clone_options);
418 }
419
420 if (is_directory(enlistment))
421 die(_("directory '%s' exists already"), enlistment);
422
423 dir = xstrfmt("%s/src", enlistment);
424
425 strbuf_reset(&buf);
426 if (branch)
427 strbuf_addf(&buf, "init.defaultBranch=%s", branch);
428 else {
429 char *b = repo_default_branch_name(the_repository, 1);
430 strbuf_addf(&buf, "init.defaultBranch=%s", b);
431 free(b);
432 }
433
434 if ((res = run_git("-c", buf.buf, "init", "--", dir, NULL)))
435 goto cleanup;
436
437 if (chdir(dir) < 0) {
438 res = error_errno(_("could not switch to '%s'"), dir);
439 goto cleanup;
440 }
441
442 setup_git_directory();
443
444 /* common-main already logs `argv` */
445 trace2_def_repo(the_repository);
446
447 if (!branch && !(branch = remote_default_branch(url))) {
448 res = error(_("failed to get default branch for '%s'"), url);
449 goto cleanup;
450 }
451
452 if (set_config("remote.origin.url=%s", url) ||
453 set_config("remote.origin.fetch="
454 "+refs/heads/%s:refs/remotes/origin/%s",
455 single_branch ? branch : "*",
456 single_branch ? branch : "*") ||
457 set_config("remote.origin.promisor=true") ||
458 set_config("remote.origin.partialCloneFilter=blob:none")) {
459 res = error(_("could not configure remote in '%s'"), dir);
460 goto cleanup;
461 }
462
463 if (!full_clone &&
464 (res = run_git("sparse-checkout", "init", "--cone", NULL)))
465 goto cleanup;
466
467 if (set_recommended_config(0))
468 return error(_("could not configure '%s'"), dir);
469
470 if ((res = run_git("fetch", "--quiet", "origin", NULL))) {
471 warning(_("partial clone failed; attempting full clone"));
472
473 if (set_config("remote.origin.promisor") ||
474 set_config("remote.origin.partialCloneFilter")) {
475 res = error(_("could not configure for full clone"));
476 goto cleanup;
477 }
478
479 if ((res = run_git("fetch", "--quiet", "origin", NULL)))
480 goto cleanup;
481 }
482
483 if ((res = set_config("branch.%s.remote=origin", branch)))
484 goto cleanup;
485 if ((res = set_config("branch.%s.merge=refs/heads/%s",
486 branch, branch)))
487 goto cleanup;
488
489 strbuf_reset(&buf);
490 strbuf_addf(&buf, "origin/%s", branch);
491 res = run_git("checkout", "-f", "-t", buf.buf, NULL);
492 if (res)
493 goto cleanup;
494
495 res = register_dir();
496
497 cleanup:
498 free(enlistment);
499 free(dir);
500 strbuf_release(&buf);
501 return res;
502 }
503
504 static int cmd_diagnose(int argc, const char **argv)
505 {
506 struct option options[] = {
507 OPT_END(),
508 };
509 const char * const usage[] = {
510 N_("scalar diagnose [<enlistment>]"),
511 NULL
512 };
513 struct strbuf zip_path = STRBUF_INIT;
514 time_t now = time(NULL);
515 struct tm tm;
516 int res = 0;
517
518 argc = parse_options(argc, argv, NULL, options,
519 usage, 0);
520
521 setup_enlistment_directory(argc, argv, usage, options, &zip_path);
522
523 strbuf_addstr(&zip_path, "/.scalarDiagnostics/scalar_");
524 strbuf_addftime(&zip_path,
525 "%Y%m%d_%H%M%S", localtime_r(&now, &tm), 0, 0);
526 strbuf_addstr(&zip_path, ".zip");
527 switch (safe_create_leading_directories(zip_path.buf)) {
528 case SCLD_EXISTS:
529 case SCLD_OK:
530 break;
531 default:
532 error_errno(_("could not create directory for '%s'"),
533 zip_path.buf);
534 goto diagnose_cleanup;
535 }
536
537 res = create_diagnostics_archive(&zip_path, DIAGNOSE_ALL);
538
539 diagnose_cleanup:
540 strbuf_release(&zip_path);
541 return res;
542 }
543
544 static int cmd_list(int argc, const char **argv)
545 {
546 if (argc != 1)
547 die(_("`scalar list` does not take arguments"));
548
549 if (run_git("config", "--global", "--get-all", "scalar.repo", NULL) < 0)
550 return -1;
551 return 0;
552 }
553
554 static int cmd_register(int argc, const char **argv)
555 {
556 struct option options[] = {
557 OPT_END(),
558 };
559 const char * const usage[] = {
560 N_("scalar register [<enlistment>]"),
561 NULL
562 };
563
564 argc = parse_options(argc, argv, NULL, options,
565 usage, 0);
566
567 setup_enlistment_directory(argc, argv, usage, options, NULL);
568
569 return register_dir();
570 }
571
572 static int get_scalar_repos(const char *key, const char *value, void *data)
573 {
574 struct string_list *list = data;
575
576 if (!strcmp(key, "scalar.repo"))
577 string_list_append(list, value);
578
579 return 0;
580 }
581
582 static int cmd_reconfigure(int argc, const char **argv)
583 {
584 int all = 0;
585 struct option options[] = {
586 OPT_BOOL('a', "all", &all,
587 N_("reconfigure all registered enlistments")),
588 OPT_END(),
589 };
590 const char * const usage[] = {
591 N_("scalar reconfigure [--all | <enlistment>]"),
592 NULL
593 };
594 struct string_list scalar_repos = STRING_LIST_INIT_DUP;
595 int i, res = 0;
596 struct repository r = { NULL };
597 struct strbuf commondir = STRBUF_INIT, gitdir = STRBUF_INIT;
598
599 argc = parse_options(argc, argv, NULL, options,
600 usage, 0);
601
602 if (!all) {
603 setup_enlistment_directory(argc, argv, usage, options, NULL);
604
605 return set_recommended_config(1);
606 }
607
608 if (argc > 0)
609 usage_msg_opt(_("--all or <enlistment>, but not both"),
610 usage, options);
611
612 git_config(get_scalar_repos, &scalar_repos);
613
614 for (i = 0; i < scalar_repos.nr; i++) {
615 const char *dir = scalar_repos.items[i].string;
616
617 strbuf_reset(&commondir);
618 strbuf_reset(&gitdir);
619
620 if (chdir(dir) < 0) {
621 warning_errno(_("could not switch to '%s'"), dir);
622 res = -1;
623 } else if (discover_git_directory(&commondir, &gitdir) < 0) {
624 warning_errno(_("git repository gone in '%s'"), dir);
625 res = -1;
626 } else {
627 git_config_clear();
628
629 the_repository = &r;
630 r.commondir = commondir.buf;
631 r.gitdir = gitdir.buf;
632
633 if (set_recommended_config(1) < 0)
634 res = -1;
635 }
636 }
637
638 string_list_clear(&scalar_repos, 1);
639 strbuf_release(&commondir);
640 strbuf_release(&gitdir);
641
642 return res;
643 }
644
645 static int cmd_run(int argc, const char **argv)
646 {
647 struct option options[] = {
648 OPT_END(),
649 };
650 struct {
651 const char *arg, *task;
652 } tasks[] = {
653 { "config", NULL },
654 { "commit-graph", "commit-graph" },
655 { "fetch", "prefetch" },
656 { "loose-objects", "loose-objects" },
657 { "pack-files", "incremental-repack" },
658 { NULL, NULL }
659 };
660 struct strbuf buf = STRBUF_INIT;
661 const char *usagestr[] = { NULL, NULL };
662 int i;
663
664 strbuf_addstr(&buf, N_("scalar run <task> [<enlistment>]\nTasks:\n"));
665 for (i = 0; tasks[i].arg; i++)
666 strbuf_addf(&buf, "\t%s\n", tasks[i].arg);
667 usagestr[0] = buf.buf;
668
669 argc = parse_options(argc, argv, NULL, options,
670 usagestr, 0);
671
672 if (!argc)
673 usage_with_options(usagestr, options);
674
675 if (!strcmp("all", argv[0])) {
676 i = -1;
677 } else {
678 for (i = 0; tasks[i].arg && strcmp(tasks[i].arg, argv[0]); i++)
679 ; /* keep looking for the task */
680
681 if (i > 0 && !tasks[i].arg) {
682 error(_("no such task: '%s'"), argv[0]);
683 usage_with_options(usagestr, options);
684 }
685 }
686
687 argc--;
688 argv++;
689 setup_enlistment_directory(argc, argv, usagestr, options, NULL);
690 strbuf_release(&buf);
691
692 if (i == 0)
693 return register_dir();
694
695 if (i > 0)
696 return run_git("maintenance", "run",
697 "--task", tasks[i].task, NULL);
698
699 if (register_dir())
700 return -1;
701 for (i = 1; tasks[i].arg; i++)
702 if (run_git("maintenance", "run",
703 "--task", tasks[i].task, NULL))
704 return -1;
705 return 0;
706 }
707
708 static int remove_deleted_enlistment(struct strbuf *path)
709 {
710 int res = 0;
711 strbuf_realpath_forgiving(path, path->buf, 1);
712
713 if (run_git("config", "--global",
714 "--unset", "--fixed-value",
715 "scalar.repo", path->buf, NULL) < 0)
716 res = -1;
717
718 if (run_git("config", "--global",
719 "--unset", "--fixed-value",
720 "maintenance.repo", path->buf, NULL) < 0)
721 res = -1;
722
723 return res;
724 }
725
726 static int cmd_unregister(int argc, const char **argv)
727 {
728 struct option options[] = {
729 OPT_END(),
730 };
731 const char * const usage[] = {
732 N_("scalar unregister [<enlistment>]"),
733 NULL
734 };
735
736 argc = parse_options(argc, argv, NULL, options,
737 usage, 0);
738
739 /*
740 * Be forgiving when the enlistment or worktree does not even exist any
741 * longer; This can be the case if a user deleted the worktree by
742 * mistake and _still_ wants to unregister the thing.
743 */
744 if (argc == 1) {
745 struct strbuf src_path = STRBUF_INIT, workdir_path = STRBUF_INIT;
746
747 strbuf_addf(&src_path, "%s/src/.git", argv[0]);
748 strbuf_addf(&workdir_path, "%s/.git", argv[0]);
749 if (!is_directory(src_path.buf) && !is_directory(workdir_path.buf)) {
750 /* remove possible matching registrations */
751 int res = -1;
752
753 strbuf_strip_suffix(&src_path, "/.git");
754 res = remove_deleted_enlistment(&src_path) && res;
755
756 strbuf_strip_suffix(&workdir_path, "/.git");
757 res = remove_deleted_enlistment(&workdir_path) && res;
758
759 strbuf_release(&src_path);
760 strbuf_release(&workdir_path);
761 return res;
762 }
763 strbuf_release(&src_path);
764 strbuf_release(&workdir_path);
765 }
766
767 setup_enlistment_directory(argc, argv, usage, options, NULL);
768
769 return unregister_dir();
770 }
771
772 static int cmd_delete(int argc, const char **argv)
773 {
774 char *cwd = xgetcwd();
775 struct option options[] = {
776 OPT_END(),
777 };
778 const char * const usage[] = {
779 N_("scalar delete <enlistment>"),
780 NULL
781 };
782 struct strbuf enlistment = STRBUF_INIT;
783 int res = 0;
784
785 argc = parse_options(argc, argv, NULL, options,
786 usage, 0);
787
788 if (argc != 1)
789 usage_with_options(usage, options);
790
791 setup_enlistment_directory(argc, argv, usage, options, &enlistment);
792
793 if (dir_inside_of(cwd, enlistment.buf) >= 0)
794 res = error(_("refusing to delete current working directory"));
795 else {
796 close_object_store(the_repository->objects);
797 res = delete_enlistment(&enlistment);
798 }
799 strbuf_release(&enlistment);
800 free(cwd);
801
802 return res;
803 }
804
805 static int cmd_version(int argc, const char **argv)
806 {
807 int verbose = 0, build_options = 0;
808 struct option options[] = {
809 OPT__VERBOSE(&verbose, N_("include Git version")),
810 OPT_BOOL(0, "build-options", &build_options,
811 N_("include Git's build options")),
812 OPT_END(),
813 };
814 const char * const usage[] = {
815 N_("scalar verbose [-v | --verbose] [--build-options]"),
816 NULL
817 };
818 struct strbuf buf = STRBUF_INIT;
819
820 argc = parse_options(argc, argv, NULL, options,
821 usage, 0);
822
823 if (argc != 0)
824 usage_with_options(usage, options);
825
826 get_version_info(&buf, build_options);
827 fprintf(stderr, "%s\n", buf.buf);
828 strbuf_release(&buf);
829
830 return 0;
831 }
832
833 static struct {
834 const char *name;
835 int (*fn)(int, const char **);
836 } builtins[] = {
837 { "clone", cmd_clone },
838 { "list", cmd_list },
839 { "register", cmd_register },
840 { "unregister", cmd_unregister },
841 { "run", cmd_run },
842 { "reconfigure", cmd_reconfigure },
843 { "delete", cmd_delete },
844 { "version", cmd_version },
845 { "diagnose", cmd_diagnose },
846 { NULL, NULL},
847 };
848
849 int cmd_main(int argc, const char **argv)
850 {
851 struct strbuf scalar_usage = STRBUF_INIT;
852 int i;
853
854 while (argc > 1 && *argv[1] == '-') {
855 if (!strcmp(argv[1], "-C")) {
856 if (argc < 3)
857 die(_("-C requires a <directory>"));
858 if (chdir(argv[2]) < 0)
859 die_errno(_("could not change to '%s'"),
860 argv[2]);
861 argc -= 2;
862 argv += 2;
863 } else if (!strcmp(argv[1], "-c")) {
864 if (argc < 3)
865 die(_("-c requires a <key>=<value> argument"));
866 git_config_push_parameter(argv[2]);
867 argc -= 2;
868 argv += 2;
869 } else
870 break;
871 }
872
873 if (argc > 1) {
874 argv++;
875 argc--;
876
877 for (i = 0; builtins[i].name; i++)
878 if (!strcmp(builtins[i].name, argv[0]))
879 return !!builtins[i].fn(argc, argv);
880 }
881
882 strbuf_addstr(&scalar_usage,
883 N_("scalar [-C <directory>] [-c <key>=<value>] "
884 "<command> [<options>]\n\nCommands:\n"));
885 for (i = 0; builtins[i].name; i++)
886 strbuf_addf(&scalar_usage, "\t%s\n", builtins[i].name);
887
888 usage(scalar_usage.buf);
889 }