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