2 * The Scalar command-line interface.
7 #include "parse-options.h"
9 #include "run-command.h"
15 #include "object-store.h"
18 * Remove the deepest subdirectory in the provided path string. Path must not
19 * include a trailing path separator. Returns 1 if parent directory found,
22 static int strbuf_parent_directory(struct strbuf
*buf
)
24 size_t len
= buf
->len
;
25 size_t offset
= offset_1st_component(buf
->buf
);
26 char *path_sep
= find_last_dir_sep(buf
->buf
+ offset
);
27 strbuf_setlen(buf
, path_sep
? path_sep
- buf
->buf
: offset
);
29 return buf
->len
< len
;
32 static void setup_enlistment_directory(int argc
, const char **argv
,
33 const char * const *usagestr
,
34 const struct option
*options
,
35 struct strbuf
*enlistment_root
)
37 struct strbuf path
= STRBUF_INIT
;
39 int enlistment_found
= 0;
41 if (startup_info
->have_repository
)
42 BUG("gitdir already set up?!?");
45 usage_with_options(usagestr
, options
);
47 /* find the worktree, determine its corresponding root */
49 strbuf_add_absolute_path(&path
, argv
[0]);
50 if (!is_directory(path
.buf
))
51 die(_("'%s' does not exist"), path
.buf
);
52 } else if (strbuf_getcwd(&path
) < 0)
53 die(_("need a working directory"));
55 strbuf_trim_trailing_dir_sep(&path
);
57 const size_t len
= path
.len
;
59 /* check if currently in enlistment root with src/ workdir */
60 strbuf_addstr(&path
, "/src");
61 if (is_nonbare_repository_dir(&path
)) {
63 strbuf_add(enlistment_root
, path
.buf
, len
);
69 /* reset to original path */
70 strbuf_setlen(&path
, len
);
72 /* check if currently in workdir */
73 if (is_nonbare_repository_dir(&path
)) {
74 if (enlistment_root
) {
76 * If the worktree's directory's name is `src`, the enlistment is the
77 * parent directory, otherwise it is identical to the worktree.
79 root
= strip_path_suffix(path
.buf
, "src");
80 strbuf_addstr(enlistment_root
, root
? root
: path
.buf
);
87 } while (strbuf_parent_directory(&path
));
89 if (!enlistment_found
)
90 die(_("could not find enlistment root"));
92 if (chdir(path
.buf
) < 0)
93 die_errno(_("could not switch to '%s'"), path
.buf
);
95 strbuf_release(&path
);
96 setup_git_directory();
99 static int run_git(const char *arg
, ...)
101 struct strvec argv
= STRVEC_INIT
;
107 strvec_push(&argv
, arg
);
108 while ((p
= va_arg(args
, const char *)))
109 strvec_push(&argv
, p
);
112 res
= run_command_v_opt(argv
.v
, RUN_GIT_CMD
);
118 static int set_recommended_config(int reconfigure
)
123 int overwrite_on_reconfigure
;
126 { "am.keepCR", "true", 1 },
127 { "core.FSCache", "true", 1 },
128 { "core.multiPackIndex", "true", 1 },
129 { "core.preloadIndex", "true", 1 },
131 { "core.untrackedCache", "true", 1 },
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
142 * Therefore, with a sad heart, we disable this very useful
143 * feature on Windows.
145 { "core.untrackedCache", "false", 1 },
147 { "core.logAllRefUpdates", "true", 1 },
148 { "credential.https://dev.azure.com.useHttpPath", "true", 1 },
149 { "credential.validate", "false", 1 }, /* GCM4W-only */
150 { "gc.auto", "0", 1 },
151 { "gui.GCWarning", "false", 1 },
152 { "index.threads", "true", 1 },
153 { "index.version", "4", 1 },
154 { "merge.stat", "false", 1 },
155 { "merge.renames", "true", 1 },
156 { "pack.useBitmaps", "false", 1 },
157 { "pack.useSparse", "true", 1 },
158 { "receive.autoGC", "false", 1 },
159 { "feature.manyFiles", "false", 1 },
160 { "feature.experimental", "false", 1 },
161 { "fetch.unpackLimit", "1", 1 },
162 { "fetch.writeCommitGraph", "false", 1 },
164 { "http.sslBackend", "schannel", 1 },
167 { "status.aheadBehind", "false" },
168 { "commitGraph.generationVersion", "1" },
169 { "core.autoCRLF", "false" },
170 { "core.safeCRLF", "false" },
171 { "fetch.showForcedUpdates", "false" },
177 for (i
= 0; config
[i
].key
; i
++) {
178 if ((reconfigure
&& config
[i
].overwrite_on_reconfigure
) ||
179 git_config_get_string(config
[i
].key
, &value
)) {
180 trace2_data_string("scalar", the_repository
, config
[i
].key
, "created");
181 if (git_config_set_gently(config
[i
].key
,
182 config
[i
].value
) < 0)
183 return error(_("could not configure %s=%s"),
184 config
[i
].key
, config
[i
].value
);
186 trace2_data_string("scalar", the_repository
, config
[i
].key
, "exists");
192 * The `log.excludeDecoration` setting is special because it allows
193 * for multiple values.
195 if (git_config_get_string("log.excludeDecoration", &value
)) {
196 trace2_data_string("scalar", the_repository
,
197 "log.excludeDecoration", "created");
198 if (git_config_set_multivar_gently("log.excludeDecoration",
200 CONFIG_REGEX_NONE
, 0))
201 return error(_("could not configure "
202 "log.excludeDecoration"));
204 trace2_data_string("scalar", the_repository
,
205 "log.excludeDecoration", "exists");
212 static int toggle_maintenance(int enable
)
214 return run_git("maintenance", enable
? "start" : "unregister", NULL
);
217 static int add_or_remove_enlistment(int add
)
221 if (!the_repository
->worktree
)
222 die(_("Scalar enlistments require a worktree"));
224 res
= run_git("config", "--global", "--get", "--fixed-value",
225 "scalar.repo", the_repository
->worktree
, NULL
);
228 * If we want to add and the setting is already there, then do nothing.
229 * If we want to remove and the setting is not there, then do nothing.
231 if ((add
&& !res
) || (!add
&& res
))
234 return run_git("config", "--global", add
? "--add" : "--unset",
235 add
? "--no-fixed-value" : "--fixed-value",
236 "scalar.repo", the_repository
->worktree
, NULL
);
239 static int register_dir(void)
241 int res
= add_or_remove_enlistment(1);
244 res
= set_recommended_config(0);
247 res
= toggle_maintenance(1);
252 static int unregister_dir(void)
256 if (toggle_maintenance(0) < 0)
259 if (add_or_remove_enlistment(0) < 0)
265 static int add_directory_to_archiver(struct strvec
*archiver_args
,
266 const char *path
, int recurse
)
268 int at_root
= !*path
;
269 DIR *dir
= opendir(at_root
? "." : path
);
271 struct strbuf buf
= STRBUF_INIT
;
276 return error_errno(_("could not open directory '%s'"), path
);
279 strbuf_addf(&buf
, "%s/", path
);
281 strvec_pushf(archiver_args
, "--prefix=%s", buf
.buf
);
283 while (!res
&& (e
= readdir(dir
))) {
284 if (!strcmp(".", e
->d_name
) || !strcmp("..", e
->d_name
))
287 strbuf_setlen(&buf
, len
);
288 strbuf_addstr(&buf
, e
->d_name
);
290 if (e
->d_type
== DT_REG
)
291 strvec_pushf(archiver_args
, "--add-file=%s", buf
.buf
);
292 else if (e
->d_type
!= DT_DIR
)
293 warning(_("skipping '%s', which is neither file nor "
294 "directory"), buf
.buf
);
296 add_directory_to_archiver(archiver_args
,
297 buf
.buf
, recurse
) < 0)
302 strbuf_release(&buf
);
307 #include <sys/statvfs.h>
310 static int get_disk_info(struct strbuf
*out
)
313 struct strbuf buf
= STRBUF_INIT
;
314 char volume_name
[MAX_PATH
], fs_name
[MAX_PATH
];
315 DWORD serial_number
, component_length
, flags
;
316 ULARGE_INTEGER avail2caller
, total
, avail
;
318 strbuf_realpath(&buf
, ".", 1);
319 if (!GetDiskFreeSpaceExA(buf
.buf
, &avail2caller
, &total
, &avail
)) {
320 error(_("could not determine free disk size for '%s'"),
322 strbuf_release(&buf
);
326 strbuf_setlen(&buf
, offset_1st_component(buf
.buf
));
327 if (!GetVolumeInformationA(buf
.buf
, volume_name
, sizeof(volume_name
),
328 &serial_number
, &component_length
, &flags
,
329 fs_name
, sizeof(fs_name
))) {
330 error(_("could not get info for '%s'"), buf
.buf
);
331 strbuf_release(&buf
);
334 strbuf_addf(out
, "Available space on '%s': ", buf
.buf
);
335 strbuf_humanise_bytes(out
, avail2caller
.QuadPart
);
336 strbuf_addch(out
, '\n');
337 strbuf_release(&buf
);
339 struct strbuf buf
= STRBUF_INIT
;
342 strbuf_realpath(&buf
, ".", 1);
343 if (statvfs(buf
.buf
, &stat
) < 0) {
344 error_errno(_("could not determine free disk size for '%s'"),
346 strbuf_release(&buf
);
350 strbuf_addf(out
, "Available space on '%s': ", buf
.buf
);
351 strbuf_humanise_bytes(out
, st_mult(stat
.f_bsize
, stat
.f_bavail
));
352 strbuf_addf(out
, " (mount flags 0x%lx)\n", stat
.f_flag
);
353 strbuf_release(&buf
);
358 /* printf-style interface, expects `<key>=<value>` argument */
359 static int set_config(const char *fmt
, ...)
361 struct strbuf buf
= STRBUF_INIT
;
367 strbuf_vaddf(&buf
, fmt
, args
);
370 value
= strchr(buf
.buf
, '=');
373 res
= git_config_set_gently(buf
.buf
, value
);
374 strbuf_release(&buf
);
379 static char *remote_default_branch(const char *url
)
381 struct child_process cp
= CHILD_PROCESS_INIT
;
382 struct strbuf out
= STRBUF_INIT
;
385 strvec_pushl(&cp
.args
, "ls-remote", "--symref", url
, "HEAD", NULL
);
386 if (!pipe_command(&cp
, NULL
, 0, &out
, 0, NULL
, 0)) {
387 const char *line
= out
.buf
;
390 const char *eol
= strchrnul(line
, '\n'), *p
;
391 size_t len
= eol
- line
;
394 if (!skip_prefix(line
, "ref: ", &p
) ||
395 !strip_suffix_mem(line
, &len
, "\tHEAD")) {
396 line
= eol
+ (*eol
== '\n');
401 if (skip_prefix(p
, "refs/heads/", &p
)) {
402 branch
= xstrndup(p
, eol
- p
);
403 strbuf_release(&out
);
407 error(_("remote HEAD is not a branch: '%.*s'"),
409 strbuf_release(&out
);
413 warning(_("failed to get default branch name from remote; "
414 "using local default"));
417 child_process_init(&cp
);
419 strvec_pushl(&cp
.args
, "symbolic-ref", "--short", "HEAD", NULL
);
420 if (!pipe_command(&cp
, NULL
, 0, &out
, 0, NULL
, 0)) {
422 return strbuf_detach(&out
, NULL
);
425 strbuf_release(&out
);
426 error(_("failed to get default branch name"));
430 static int delete_enlistment(struct strbuf
*enlistment
)
433 struct strbuf parent
= STRBUF_INIT
;
436 if (unregister_dir())
437 die(_("failed to unregister repository"));
441 * Change the current directory to one outside of the enlistment so
442 * that we may delete everything underneath it.
444 strbuf_addbuf(&parent
, enlistment
);
445 strbuf_parent_directory(&parent
);
446 if (chdir(parent
.buf
) < 0)
447 die_errno(_("could not switch to '%s'"), parent
.buf
);
448 strbuf_release(&parent
);
451 if (remove_dir_recursively(enlistment
, 0))
452 die(_("failed to delete enlistment directory"));
458 * Dummy implementation; Using `get_version_info()` would cause a link error
461 void load_builtin_commands(const char *prefix
, struct cmdnames
*cmds
)
463 die("not implemented");
466 static int cmd_clone(int argc
, const char **argv
)
468 const char *branch
= NULL
;
469 int full_clone
= 0, single_branch
= 0;
470 struct option clone_options
[] = {
471 OPT_STRING('b', "branch", &branch
, N_("<branch>"),
472 N_("branch to checkout after clone")),
473 OPT_BOOL(0, "full-clone", &full_clone
,
474 N_("when cloning, create full working directory")),
475 OPT_BOOL(0, "single-branch", &single_branch
,
476 N_("only download metadata for the branch that will "
480 const char * const clone_usage
[] = {
481 N_("scalar clone [<options>] [--] <repo> [<dir>]"),
485 char *enlistment
= NULL
, *dir
= NULL
;
486 struct strbuf buf
= STRBUF_INIT
;
489 argc
= parse_options(argc
, argv
, NULL
, clone_options
, clone_usage
, 0);
493 enlistment
= xstrdup(argv
[1]);
494 } else if (argc
== 1) {
497 strbuf_addstr(&buf
, url
);
498 /* Strip trailing slashes, if any */
499 while (buf
.len
> 0 && is_dir_sep(buf
.buf
[buf
.len
- 1]))
500 strbuf_setlen(&buf
, buf
.len
- 1);
501 /* Strip suffix `.git`, if any */
502 strbuf_strip_suffix(&buf
, ".git");
504 enlistment
= find_last_dir_sep(buf
.buf
);
506 die(_("cannot deduce worktree name from '%s'"), url
);
508 enlistment
= xstrdup(enlistment
+ 1);
510 usage_msg_opt(_("You must specify a repository to clone."),
511 clone_usage
, clone_options
);
514 if (is_directory(enlistment
))
515 die(_("directory '%s' exists already"), enlistment
);
517 dir
= xstrfmt("%s/src", enlistment
);
521 strbuf_addf(&buf
, "init.defaultBranch=%s", branch
);
523 char *b
= repo_default_branch_name(the_repository
, 1);
524 strbuf_addf(&buf
, "init.defaultBranch=%s", b
);
528 if ((res
= run_git("-c", buf
.buf
, "init", "--", dir
, NULL
)))
531 if (chdir(dir
) < 0) {
532 res
= error_errno(_("could not switch to '%s'"), dir
);
536 setup_git_directory();
538 /* common-main already logs `argv` */
539 trace2_def_repo(the_repository
);
541 if (!branch
&& !(branch
= remote_default_branch(url
))) {
542 res
= error(_("failed to get default branch for '%s'"), url
);
546 if (set_config("remote.origin.url=%s", url
) ||
547 set_config("remote.origin.fetch="
548 "+refs/heads/%s:refs/remotes/origin/%s",
549 single_branch
? branch
: "*",
550 single_branch
? branch
: "*") ||
551 set_config("remote.origin.promisor=true") ||
552 set_config("remote.origin.partialCloneFilter=blob:none")) {
553 res
= error(_("could not configure remote in '%s'"), dir
);
558 (res
= run_git("sparse-checkout", "init", "--cone", NULL
)))
561 if (set_recommended_config(0))
562 return error(_("could not configure '%s'"), dir
);
564 if ((res
= run_git("fetch", "--quiet", "origin", NULL
))) {
565 warning(_("partial clone failed; attempting full clone"));
567 if (set_config("remote.origin.promisor") ||
568 set_config("remote.origin.partialCloneFilter")) {
569 res
= error(_("could not configure for full clone"));
573 if ((res
= run_git("fetch", "--quiet", "origin", NULL
)))
577 if ((res
= set_config("branch.%s.remote=origin", branch
)))
579 if ((res
= set_config("branch.%s.merge=refs/heads/%s",
584 strbuf_addf(&buf
, "origin/%s", branch
);
585 res
= run_git("checkout", "-f", "-t", buf
.buf
, NULL
);
589 res
= register_dir();
594 strbuf_release(&buf
);
598 static void dir_file_stats_objects(const char *full_path
, size_t full_path_len
,
599 const char *file_name
, void *data
)
601 struct strbuf
*buf
= data
;
604 if (!stat(full_path
, &st
))
605 strbuf_addf(buf
, "%-70s %16" PRIuMAX
"\n", file_name
,
606 (uintmax_t)st
.st_size
);
609 static int dir_file_stats(struct object_directory
*object_dir
, void *data
)
611 struct strbuf
*buf
= data
;
613 strbuf_addf(buf
, "Contents of %s:\n", object_dir
->path
);
615 for_each_file_in_pack_dir(object_dir
->path
, dir_file_stats_objects
,
621 static int count_files(char *path
)
623 DIR *dir
= opendir(path
);
630 while ((e
= readdir(dir
)) != NULL
)
631 if (!is_dot_or_dotdot(e
->d_name
) && e
->d_type
== DT_REG
)
638 static void loose_objs_stats(struct strbuf
*buf
, const char *path
)
640 DIR *dir
= opendir(path
);
645 struct strbuf count_path
= STRBUF_INIT
;
646 size_t base_path_len
;
651 strbuf_addstr(buf
, "Object directory stats for ");
652 strbuf_add_absolute_path(buf
, path
);
653 strbuf_addstr(buf
, ":\n");
655 strbuf_add_absolute_path(&count_path
, path
);
656 strbuf_addch(&count_path
, '/');
657 base_path_len
= count_path
.len
;
659 while ((e
= readdir(dir
)) != NULL
)
660 if (!is_dot_or_dotdot(e
->d_name
) &&
661 e
->d_type
== DT_DIR
&& strlen(e
->d_name
) == 2 &&
662 !hex_to_bytes(&c
, e
->d_name
, 1)) {
663 strbuf_setlen(&count_path
, base_path_len
);
664 strbuf_addstr(&count_path
, e
->d_name
);
665 total
+= (count
= count_files(count_path
.buf
));
666 strbuf_addf(buf
, "%s : %7d files\n", e
->d_name
, count
);
669 strbuf_addf(buf
, "Total: %d loose objects", total
);
671 strbuf_release(&count_path
);
675 static int cmd_diagnose(int argc
, const char **argv
)
677 struct option options
[] = {
680 const char * const usage
[] = {
681 N_("scalar diagnose [<enlistment>]"),
684 struct strbuf zip_path
= STRBUF_INIT
;
685 struct strvec archiver_args
= STRVEC_INIT
;
686 char **argv_copy
= NULL
;
687 int stdout_fd
= -1, archiver_fd
= -1;
688 time_t now
= time(NULL
);
690 struct strbuf path
= STRBUF_INIT
, buf
= STRBUF_INIT
;
693 argc
= parse_options(argc
, argv
, NULL
, options
,
696 setup_enlistment_directory(argc
, argv
, usage
, options
, &zip_path
);
698 strbuf_addstr(&zip_path
, "/.scalarDiagnostics/scalar_");
699 strbuf_addftime(&zip_path
,
700 "%Y%m%d_%H%M%S", localtime_r(&now
, &tm
), 0, 0);
701 strbuf_addstr(&zip_path
, ".zip");
702 switch (safe_create_leading_directories(zip_path
.buf
)) {
707 error_errno(_("could not create directory for '%s'"),
709 goto diagnose_cleanup
;
713 res
= error_errno(_("could not duplicate stdout"));
714 goto diagnose_cleanup
;
717 archiver_fd
= xopen(zip_path
.buf
, O_CREAT
| O_WRONLY
| O_TRUNC
, 0666);
718 if (archiver_fd
< 0 || dup2(archiver_fd
, 1) < 0) {
719 res
= error_errno(_("could not redirect output"));
720 goto diagnose_cleanup
;
724 strvec_pushl(&archiver_args
, "scalar-diagnose", "--format=zip", NULL
);
727 strbuf_addstr(&buf
, "Collecting diagnostic info\n\n");
728 get_version_info(&buf
, 1);
730 strbuf_addf(&buf
, "Enlistment root: %s\n", the_repository
->worktree
);
732 write_or_die(stdout_fd
, buf
.buf
, buf
.len
);
733 strvec_pushf(&archiver_args
,
734 "--add-virtual-file=diagnostics.log:%.*s",
735 (int)buf
.len
, buf
.buf
);
738 strbuf_addstr(&buf
, "--add-virtual-file=packs-local.txt:");
739 dir_file_stats(the_repository
->objects
->odb
, &buf
);
740 foreach_alt_odb(dir_file_stats
, &buf
);
741 strvec_push(&archiver_args
, buf
.buf
);
744 strbuf_addstr(&buf
, "--add-virtual-file=objects-local.txt:");
745 loose_objs_stats(&buf
, ".git/objects");
746 strvec_push(&archiver_args
, buf
.buf
);
748 if ((res
= add_directory_to_archiver(&archiver_args
, ".git", 0)) ||
749 (res
= add_directory_to_archiver(&archiver_args
, ".git/hooks", 0)) ||
750 (res
= add_directory_to_archiver(&archiver_args
, ".git/info", 0)) ||
751 (res
= add_directory_to_archiver(&archiver_args
, ".git/logs", 1)) ||
752 (res
= add_directory_to_archiver(&archiver_args
, ".git/objects/info", 0)))
753 goto diagnose_cleanup
;
755 strvec_pushl(&archiver_args
, "--prefix=",
756 oid_to_hex(the_hash_algo
->empty_tree
), "--", NULL
);
758 /* `write_archive()` modifies the `argv` passed to it. Let it. */
759 argv_copy
= xmemdupz(archiver_args
.v
,
760 sizeof(char *) * archiver_args
.nr
);
761 res
= write_archive(archiver_args
.nr
, (const char **)argv_copy
, NULL
,
762 the_repository
, NULL
, 0);
764 error(_("failed to write archive"));
765 goto diagnose_cleanup
;
770 "Diagnostics complete.\n"
771 "All of the gathered info is captured in '%s'\n",
775 if (archiver_fd
>= 0) {
780 strvec_clear(&archiver_args
);
781 strbuf_release(&zip_path
);
782 strbuf_release(&path
);
783 strbuf_release(&buf
);
788 static int cmd_list(int argc
, const char **argv
)
791 die(_("`scalar list` does not take arguments"));
793 if (run_git("config", "--global", "--get-all", "scalar.repo", NULL
) < 0)
798 static int cmd_register(int argc
, const char **argv
)
800 struct option options
[] = {
803 const char * const usage
[] = {
804 N_("scalar register [<enlistment>]"),
808 argc
= parse_options(argc
, argv
, NULL
, options
,
811 setup_enlistment_directory(argc
, argv
, usage
, options
, NULL
);
813 return register_dir();
816 static int get_scalar_repos(const char *key
, const char *value
, void *data
)
818 struct string_list
*list
= data
;
820 if (!strcmp(key
, "scalar.repo"))
821 string_list_append(list
, value
);
826 static int cmd_reconfigure(int argc
, const char **argv
)
829 struct option options
[] = {
830 OPT_BOOL('a', "all", &all
,
831 N_("reconfigure all registered enlistments")),
834 const char * const usage
[] = {
835 N_("scalar reconfigure [--all | <enlistment>]"),
838 struct string_list scalar_repos
= STRING_LIST_INIT_DUP
;
840 struct repository r
= { NULL
};
841 struct strbuf commondir
= STRBUF_INIT
, gitdir
= STRBUF_INIT
;
843 argc
= parse_options(argc
, argv
, NULL
, options
,
847 setup_enlistment_directory(argc
, argv
, usage
, options
, NULL
);
849 return set_recommended_config(1);
853 usage_msg_opt(_("--all or <enlistment>, but not both"),
856 git_config(get_scalar_repos
, &scalar_repos
);
858 for (i
= 0; i
< scalar_repos
.nr
; i
++) {
859 const char *dir
= scalar_repos
.items
[i
].string
;
861 strbuf_reset(&commondir
);
862 strbuf_reset(&gitdir
);
864 if (chdir(dir
) < 0) {
865 warning_errno(_("could not switch to '%s'"), dir
);
867 } else if (discover_git_directory(&commondir
, &gitdir
) < 0) {
868 warning_errno(_("git repository gone in '%s'"), dir
);
874 r
.commondir
= commondir
.buf
;
875 r
.gitdir
= gitdir
.buf
;
877 if (set_recommended_config(1) < 0)
882 string_list_clear(&scalar_repos
, 1);
883 strbuf_release(&commondir
);
884 strbuf_release(&gitdir
);
889 static int cmd_run(int argc
, const char **argv
)
891 struct option options
[] = {
895 const char *arg
, *task
;
898 { "commit-graph", "commit-graph" },
899 { "fetch", "prefetch" },
900 { "loose-objects", "loose-objects" },
901 { "pack-files", "incremental-repack" },
904 struct strbuf buf
= STRBUF_INIT
;
905 const char *usagestr
[] = { NULL
, NULL
};
908 strbuf_addstr(&buf
, N_("scalar run <task> [<enlistment>]\nTasks:\n"));
909 for (i
= 0; tasks
[i
].arg
; i
++)
910 strbuf_addf(&buf
, "\t%s\n", tasks
[i
].arg
);
911 usagestr
[0] = buf
.buf
;
913 argc
= parse_options(argc
, argv
, NULL
, options
,
917 usage_with_options(usagestr
, options
);
919 if (!strcmp("all", argv
[0])) {
922 for (i
= 0; tasks
[i
].arg
&& strcmp(tasks
[i
].arg
, argv
[0]); i
++)
923 ; /* keep looking for the task */
925 if (i
> 0 && !tasks
[i
].arg
) {
926 error(_("no such task: '%s'"), argv
[0]);
927 usage_with_options(usagestr
, options
);
933 setup_enlistment_directory(argc
, argv
, usagestr
, options
, NULL
);
934 strbuf_release(&buf
);
937 return register_dir();
940 return run_git("maintenance", "run",
941 "--task", tasks
[i
].task
, NULL
);
945 for (i
= 1; tasks
[i
].arg
; i
++)
946 if (run_git("maintenance", "run",
947 "--task", tasks
[i
].task
, NULL
))
952 static int remove_deleted_enlistment(struct strbuf
*path
)
955 strbuf_realpath_forgiving(path
, path
->buf
, 1);
957 if (run_git("config", "--global",
958 "--unset", "--fixed-value",
959 "scalar.repo", path
->buf
, NULL
) < 0)
962 if (run_git("config", "--global",
963 "--unset", "--fixed-value",
964 "maintenance.repo", path
->buf
, NULL
) < 0)
970 static int cmd_unregister(int argc
, const char **argv
)
972 struct option options
[] = {
975 const char * const usage
[] = {
976 N_("scalar unregister [<enlistment>]"),
980 argc
= parse_options(argc
, argv
, NULL
, options
,
984 * Be forgiving when the enlistment or worktree does not even exist any
985 * longer; This can be the case if a user deleted the worktree by
986 * mistake and _still_ wants to unregister the thing.
989 struct strbuf src_path
= STRBUF_INIT
, workdir_path
= STRBUF_INIT
;
991 strbuf_addf(&src_path
, "%s/src/.git", argv
[0]);
992 strbuf_addf(&workdir_path
, "%s/.git", argv
[0]);
993 if (!is_directory(src_path
.buf
) && !is_directory(workdir_path
.buf
)) {
994 /* remove possible matching registrations */
997 strbuf_strip_suffix(&src_path
, "/.git");
998 res
= remove_deleted_enlistment(&src_path
) && res
;
1000 strbuf_strip_suffix(&workdir_path
, "/.git");
1001 res
= remove_deleted_enlistment(&workdir_path
) && res
;
1003 strbuf_release(&src_path
);
1004 strbuf_release(&workdir_path
);
1007 strbuf_release(&src_path
);
1008 strbuf_release(&workdir_path
);
1011 setup_enlistment_directory(argc
, argv
, usage
, options
, NULL
);
1013 return unregister_dir();
1016 static int cmd_delete(int argc
, const char **argv
)
1018 char *cwd
= xgetcwd();
1019 struct option options
[] = {
1022 const char * const usage
[] = {
1023 N_("scalar delete <enlistment>"),
1026 struct strbuf enlistment
= STRBUF_INIT
;
1029 argc
= parse_options(argc
, argv
, NULL
, options
,
1033 usage_with_options(usage
, options
);
1035 setup_enlistment_directory(argc
, argv
, usage
, options
, &enlistment
);
1037 if (dir_inside_of(cwd
, enlistment
.buf
) >= 0)
1038 res
= error(_("refusing to delete current working directory"));
1040 close_object_store(the_repository
->objects
);
1041 res
= delete_enlistment(&enlistment
);
1043 strbuf_release(&enlistment
);
1049 static int cmd_version(int argc
, const char **argv
)
1051 int verbose
= 0, build_options
= 0;
1052 struct option options
[] = {
1053 OPT__VERBOSE(&verbose
, N_("include Git version")),
1054 OPT_BOOL(0, "build-options", &build_options
,
1055 N_("include Git's build options")),
1058 const char * const usage
[] = {
1059 N_("scalar verbose [-v | --verbose] [--build-options]"),
1062 struct strbuf buf
= STRBUF_INIT
;
1064 argc
= parse_options(argc
, argv
, NULL
, options
,
1068 usage_with_options(usage
, options
);
1070 get_version_info(&buf
, build_options
);
1071 fprintf(stderr
, "%s\n", buf
.buf
);
1072 strbuf_release(&buf
);
1079 int (*fn
)(int, const char **);
1081 { "clone", cmd_clone
},
1082 { "list", cmd_list
},
1083 { "register", cmd_register
},
1084 { "unregister", cmd_unregister
},
1086 { "reconfigure", cmd_reconfigure
},
1087 { "delete", cmd_delete
},
1088 { "version", cmd_version
},
1089 { "diagnose", cmd_diagnose
},
1093 int cmd_main(int argc
, const char **argv
)
1095 struct strbuf scalar_usage
= STRBUF_INIT
;
1098 while (argc
> 1 && *argv
[1] == '-') {
1099 if (!strcmp(argv
[1], "-C")) {
1101 die(_("-C requires a <directory>"));
1102 if (chdir(argv
[2]) < 0)
1103 die_errno(_("could not change to '%s'"),
1107 } else if (!strcmp(argv
[1], "-c")) {
1109 die(_("-c requires a <key>=<value> argument"));
1110 git_config_push_parameter(argv
[2]);
1121 for (i
= 0; builtins
[i
].name
; i
++)
1122 if (!strcmp(builtins
[i
].name
, argv
[0]))
1123 return !!builtins
[i
].fn(argc
, argv
);
1126 strbuf_addstr(&scalar_usage
,
1127 N_("scalar [-C <directory>] [-c <key>=<value>] "
1128 "<command> [<options>]\n\nCommands:\n"));
1129 for (i
= 0; builtins
[i
].name
; i
++)
1130 strbuf_addf(&scalar_usage
, "\t%s\n", builtins
[i
].name
);
1132 usage(scalar_usage
.buf
);