]> git.ipfire.org Git - thirdparty/git.git/blame - contrib/scalar/scalar.c
scalar: enable built-in FSMonitor on `register`
[thirdparty/git.git] / contrib / scalar / scalar.c
CommitLineData
0a43fb22
JS
1/*
2 * The Scalar command-line interface.
3 */
4
5#include "cache.h"
6#include "gettext.h"
7#include "parse-options.h"
d0feac4e
DS
8#include "config.h"
9#include "run-command.h"
3f1917dc
MJC
10#include "simple-ipc.h"
11#include "fsmonitor-ipc.h"
12#include "fsmonitor-settings.h"
546f822d 13#include "refs.h"
d85ada7c
MJC
14#include "dir.h"
15#include "packfile.h"
ddc35d83 16#include "help.h"
aa5c79a3 17#include "archive.h"
93e804b2 18#include "object-store.h"
d0feac4e 19
d0feac4e
DS
20static void setup_enlistment_directory(int argc, const char **argv,
21 const char * const *usagestr,
22 const struct option *options,
23 struct strbuf *enlistment_root)
24{
25 struct strbuf path = STRBUF_INIT;
65f6a9eb
VD
26 int enlistment_is_repo_parent = 0;
27 size_t len;
d0feac4e
DS
28
29 if (startup_info->have_repository)
30 BUG("gitdir already set up?!?");
31
32 if (argc > 1)
33 usage_with_options(usagestr, options);
34
35 /* find the worktree, determine its corresponding root */
b4485574 36 if (argc == 1) {
d0feac4e 37 strbuf_add_absolute_path(&path, argv[0]);
b4485574
JS
38 if (!is_directory(path.buf))
39 die(_("'%s' does not exist"), path.buf);
65f6a9eb
VD
40 if (chdir(path.buf) < 0)
41 die_errno(_("could not switch to '%s'"), path.buf);
b4485574 42 } else if (strbuf_getcwd(&path) < 0)
d0feac4e
DS
43 die(_("need a working directory"));
44
45 strbuf_trim_trailing_dir_sep(&path);
d0feac4e 46
65f6a9eb
VD
47 /* check if currently in enlistment root with src/ workdir */
48 len = path.len;
49 strbuf_addstr(&path, "/src");
50 if (is_nonbare_repository_dir(&path)) {
51 enlistment_is_repo_parent = 1;
52 if (chdir(path.buf) < 0)
53 die_errno(_("could not switch to '%s'"), path.buf);
54 }
55 strbuf_setlen(&path, len);
d0feac4e 56
65f6a9eb 57 setup_git_directory();
d0feac4e 58
65f6a9eb
VD
59 if (!the_repository->worktree)
60 die(_("Scalar enlistments require a worktree"));
d0feac4e 61
65f6a9eb
VD
62 if (enlistment_root) {
63 if (enlistment_is_repo_parent)
64 strbuf_addbuf(enlistment_root, &path);
65 else
66 strbuf_addstr(enlistment_root, the_repository->worktree);
67 }
d0feac4e
DS
68
69 strbuf_release(&path);
d0feac4e
DS
70}
71
72static int run_git(const char *arg, ...)
73{
74 struct strvec argv = STRVEC_INIT;
75 va_list args;
76 const char *p;
77 int res;
78
79 va_start(args, arg);
80 strvec_push(&argv, arg);
81 while ((p = va_arg(args, const char *)))
82 strvec_push(&argv, p);
83 va_end(args);
84
85 res = run_command_v_opt(argv.v, RUN_GIT_CMD);
86
87 strvec_clear(&argv);
88 return res;
89}
90
d934a11c
VD
91struct scalar_config {
92 const char *key;
93 const char *value;
94 int overwrite_on_reconfigure;
95};
96
97static int set_scalar_config(const struct scalar_config *config, int reconfigure)
98{
99 char *value = NULL;
100 int res;
101
102 if ((reconfigure && config->overwrite_on_reconfigure) ||
103 git_config_get_string(config->key, &value)) {
104 trace2_data_string("scalar", the_repository, config->key, "created");
105 res = git_config_set_gently(config->key, config->value);
106 } else {
107 trace2_data_string("scalar", the_repository, config->key, "exists");
108 res = 0;
109 }
110
111 free(value);
112 return res;
113}
114
3f1917dc
MJC
115static int have_fsmonitor_support(void)
116{
117 return fsmonitor_ipc__is_supported() &&
118 fsm_settings__get_reason(the_repository) == FSMONITOR_REASON_OK;
119}
120
cb59d55e 121static int set_recommended_config(int reconfigure)
d0feac4e 122{
d934a11c 123 struct scalar_config config[] = {
cb59d55e
JS
124 /* Required */
125 { "am.keepCR", "true", 1 },
126 { "core.FSCache", "true", 1 },
127 { "core.multiPackIndex", "true", 1 },
128 { "core.preloadIndex", "true", 1 },
d0feac4e 129#ifndef WIN32
cb59d55e 130 { "core.untrackedCache", "true", 1 },
d0feac4e
DS
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 */
cb59d55e 144 { "core.untrackedCache", "false", 1 },
d0feac4e 145#endif
cb59d55e
JS
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 },
cb59d55e
JS
158 { "feature.manyFiles", "false", 1 },
159 { "feature.experimental", "false", 1 },
160 { "fetch.unpackLimit", "1", 1 },
161 { "fetch.writeCommitGraph", "false", 1 },
d0feac4e 162#ifdef WIN32
cb59d55e 163 { "http.sslBackend", "schannel", 1 },
d0feac4e 164#endif
cb59d55e 165 /* Optional */
d0feac4e
DS
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++) {
d934a11c
VD
177 if (set_scalar_config(config + i, reconfigure))
178 return error(_("could not configure %s=%s"),
179 config[i].key, config[i].value);
d0feac4e
DS
180 }
181
3f1917dc
MJC
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);
187 }
188
d0feac4e
DS
189 /*
190 * The `log.excludeDecoration` setting is special because it allows
191 * for multiple values.
192 */
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);
205 }
206
207 return 0;
208}
209
c76a53eb 210static int toggle_maintenance(int enable)
d0feac4e 211{
c76a53eb 212 return run_git("maintenance", enable ? "start" : "unregister", NULL);
d0feac4e
DS
213}
214
c76a53eb 215static int add_or_remove_enlistment(int add)
d0feac4e
DS
216{
217 int res;
218
219 if (!the_repository->worktree)
220 die(_("Scalar enlistments require a worktree"));
221
222 res = run_git("config", "--global", "--get", "--fixed-value",
223 "scalar.repo", the_repository->worktree, NULL);
224
225 /*
c76a53eb
DS
226 * If we want to add and the setting is already there, then do nothing.
227 * If we want to remove and the setting is not there, then do nothing.
d0feac4e 228 */
c76a53eb 229 if ((add && !res) || (!add && res))
d0feac4e
DS
230 return 0;
231
c76a53eb
DS
232 return run_git("config", "--global", add ? "--add" : "--unset",
233 add ? "--no-fixed-value" : "--fixed-value",
d0feac4e
DS
234 "scalar.repo", the_repository->worktree, NULL);
235}
236
3f1917dc
MJC
237static int start_fsmonitor_daemon(void)
238{
239 assert(have_fsmonitor_support());
240
241 if (fsmonitor_ipc__get_state() != IPC_STATE__LISTENING)
242 return run_git("fsmonitor--daemon", "start", NULL);
243
244 return 0;
245}
246
d0feac4e
DS
247static int register_dir(void)
248{
d2a79bc9
VD
249 if (add_or_remove_enlistment(1))
250 return error(_("could not add enlistment"));
d0feac4e 251
d2a79bc9
VD
252 if (set_recommended_config(0))
253 return error(_("could not set recommended config"));
d0feac4e 254
d2a79bc9
VD
255 if (toggle_maintenance(1))
256 return error(_("could not turn on maintenance"));
c76a53eb 257
3f1917dc
MJC
258 if (have_fsmonitor_support() && start_fsmonitor_daemon()) {
259 return error(_("could not start the FSMonitor daemon"));
260 }
261
d2a79bc9 262 return 0;
c76a53eb
DS
263}
264
265static int unregister_dir(void)
266{
267 int res = 0;
268
adedcee8 269 if (toggle_maintenance(0))
d2a79bc9 270 res = error(_("could not turn off maintenance"));
c76a53eb 271
adedcee8 272 if (add_or_remove_enlistment(0))
d2a79bc9 273 res = error(_("could not remove enlistment"));
d0feac4e
DS
274
275 return res;
276}
277
aa5c79a3
JS
278static int add_directory_to_archiver(struct strvec *archiver_args,
279 const char *path, int recurse)
280{
281 int at_root = !*path;
282 DIR *dir = opendir(at_root ? "." : path);
283 struct dirent *e;
284 struct strbuf buf = STRBUF_INIT;
285 size_t len;
286 int res = 0;
287
288 if (!dir)
289 return error_errno(_("could not open directory '%s'"), path);
290
291 if (!at_root)
292 strbuf_addf(&buf, "%s/", path);
293 len = buf.len;
294 strvec_pushf(archiver_args, "--prefix=%s", buf.buf);
295
296 while (!res && (e = readdir(dir))) {
297 if (!strcmp(".", e->d_name) || !strcmp("..", e->d_name))
298 continue;
299
300 strbuf_setlen(&buf, len);
301 strbuf_addstr(&buf, e->d_name);
302
303 if (e->d_type == DT_REG)
304 strvec_pushf(archiver_args, "--add-file=%s", buf.buf);
305 else if (e->d_type != DT_DIR)
306 warning(_("skipping '%s', which is neither file nor "
307 "directory"), buf.buf);
308 else if (recurse &&
309 add_directory_to_archiver(archiver_args,
310 buf.buf, recurse) < 0)
311 res = -1;
312 }
313
314 closedir(dir);
315 strbuf_release(&buf);
316 return res;
317}
318
0ed5b13f
JS
319#ifndef WIN32
320#include <sys/statvfs.h>
321#endif
322
323static int get_disk_info(struct strbuf *out)
324{
325#ifdef WIN32
326 struct strbuf buf = STRBUF_INIT;
327 char volume_name[MAX_PATH], fs_name[MAX_PATH];
328 DWORD serial_number, component_length, flags;
329 ULARGE_INTEGER avail2caller, total, avail;
330
331 strbuf_realpath(&buf, ".", 1);
332 if (!GetDiskFreeSpaceExA(buf.buf, &avail2caller, &total, &avail)) {
333 error(_("could not determine free disk size for '%s'"),
334 buf.buf);
335 strbuf_release(&buf);
336 return -1;
337 }
338
339 strbuf_setlen(&buf, offset_1st_component(buf.buf));
340 if (!GetVolumeInformationA(buf.buf, volume_name, sizeof(volume_name),
341 &serial_number, &component_length, &flags,
342 fs_name, sizeof(fs_name))) {
343 error(_("could not get info for '%s'"), buf.buf);
344 strbuf_release(&buf);
345 return -1;
346 }
347 strbuf_addf(out, "Available space on '%s': ", buf.buf);
348 strbuf_humanise_bytes(out, avail2caller.QuadPart);
349 strbuf_addch(out, '\n');
350 strbuf_release(&buf);
351#else
352 struct strbuf buf = STRBUF_INIT;
353 struct statvfs stat;
354
355 strbuf_realpath(&buf, ".", 1);
356 if (statvfs(buf.buf, &stat) < 0) {
357 error_errno(_("could not determine free disk size for '%s'"),
358 buf.buf);
359 strbuf_release(&buf);
360 return -1;
361 }
362
363 strbuf_addf(out, "Available space on '%s': ", buf.buf);
364 strbuf_humanise_bytes(out, st_mult(stat.f_bsize, stat.f_bavail));
365 strbuf_addf(out, " (mount flags 0x%lx)\n", stat.f_flag);
366 strbuf_release(&buf);
367#endif
368 return 0;
369}
370
546f822d
JS
371/* printf-style interface, expects `<key>=<value>` argument */
372static int set_config(const char *fmt, ...)
373{
374 struct strbuf buf = STRBUF_INIT;
375 char *value;
376 int res;
377 va_list args;
378
379 va_start(args, fmt);
380 strbuf_vaddf(&buf, fmt, args);
381 va_end(args);
382
383 value = strchr(buf.buf, '=');
384 if (value)
385 *(value++) = '\0';
386 res = git_config_set_gently(buf.buf, value);
387 strbuf_release(&buf);
388
389 return res;
390}
391
392static char *remote_default_branch(const char *url)
393{
394 struct child_process cp = CHILD_PROCESS_INIT;
395 struct strbuf out = STRBUF_INIT;
396
397 cp.git_cmd = 1;
398 strvec_pushl(&cp.args, "ls-remote", "--symref", url, "HEAD", NULL);
399 if (!pipe_command(&cp, NULL, 0, &out, 0, NULL, 0)) {
400 const char *line = out.buf;
401
402 while (*line) {
403 const char *eol = strchrnul(line, '\n'), *p;
404 size_t len = eol - line;
405 char *branch;
406
407 if (!skip_prefix(line, "ref: ", &p) ||
408 !strip_suffix_mem(line, &len, "\tHEAD")) {
409 line = eol + (*eol == '\n');
410 continue;
411 }
412
413 eol = line + len;
414 if (skip_prefix(p, "refs/heads/", &p)) {
415 branch = xstrndup(p, eol - p);
416 strbuf_release(&out);
417 return branch;
418 }
419
420 error(_("remote HEAD is not a branch: '%.*s'"),
421 (int)(eol - p), p);
422 strbuf_release(&out);
423 return NULL;
424 }
425 }
426 warning(_("failed to get default branch name from remote; "
427 "using local default"));
428 strbuf_reset(&out);
429
430 child_process_init(&cp);
431 cp.git_cmd = 1;
432 strvec_pushl(&cp.args, "symbolic-ref", "--short", "HEAD", NULL);
433 if (!pipe_command(&cp, NULL, 0, &out, 0, NULL, 0)) {
434 strbuf_trim(&out);
435 return strbuf_detach(&out, NULL);
436 }
437
438 strbuf_release(&out);
439 error(_("failed to get default branch name"));
440 return NULL;
441}
442
d85ada7c
MJC
443static int delete_enlistment(struct strbuf *enlistment)
444{
445#ifdef WIN32
446 struct strbuf parent = STRBUF_INIT;
65f6a9eb
VD
447 size_t offset;
448 char *path_sep;
d85ada7c
MJC
449#endif
450
451 if (unregister_dir())
9b24bb92 452 return error(_("failed to unregister repository"));
d85ada7c
MJC
453
454#ifdef WIN32
455 /*
456 * Change the current directory to one outside of the enlistment so
457 * that we may delete everything underneath it.
458 */
65f6a9eb
VD
459 offset = offset_1st_component(enlistment->buf);
460 path_sep = find_last_dir_sep(enlistment->buf + offset);
461 strbuf_add(&parent, enlistment->buf,
462 path_sep ? path_sep - enlistment->buf : offset);
9b24bb92
VD
463 if (chdir(parent.buf) < 0) {
464 int res = error_errno(_("could not switch to '%s'"), parent.buf);
465 strbuf_release(&parent);
466 return res;
467 }
d85ada7c
MJC
468 strbuf_release(&parent);
469#endif
470
471 if (remove_dir_recursively(enlistment, 0))
9b24bb92 472 return error(_("failed to delete enlistment directory"));
d85ada7c
MJC
473
474 return 0;
475}
476
ddc35d83
JS
477/*
478 * Dummy implementation; Using `get_version_info()` would cause a link error
479 * without this.
480 */
481void load_builtin_commands(const char *prefix, struct cmdnames *cmds)
482{
483 die("not implemented");
484}
485
546f822d
JS
486static int cmd_clone(int argc, const char **argv)
487{
488 const char *branch = NULL;
4368e40b 489 int full_clone = 0, single_branch = 0;
546f822d
JS
490 struct option clone_options[] = {
491 OPT_STRING('b', "branch", &branch, N_("<branch>"),
492 N_("branch to checkout after clone")),
493 OPT_BOOL(0, "full-clone", &full_clone,
494 N_("when cloning, create full working directory")),
4368e40b
JS
495 OPT_BOOL(0, "single-branch", &single_branch,
496 N_("only download metadata for the branch that will "
497 "be checked out")),
546f822d
JS
498 OPT_END(),
499 };
500 const char * const clone_usage[] = {
501 N_("scalar clone [<options>] [--] <repo> [<dir>]"),
502 NULL
503 };
504 const char *url;
505 char *enlistment = NULL, *dir = NULL;
506 struct strbuf buf = STRBUF_INIT;
507 int res;
508
509 argc = parse_options(argc, argv, NULL, clone_options, clone_usage, 0);
510
511 if (argc == 2) {
512 url = argv[0];
513 enlistment = xstrdup(argv[1]);
514 } else if (argc == 1) {
515 url = argv[0];
516
517 strbuf_addstr(&buf, url);
518 /* Strip trailing slashes, if any */
519 while (buf.len > 0 && is_dir_sep(buf.buf[buf.len - 1]))
520 strbuf_setlen(&buf, buf.len - 1);
521 /* Strip suffix `.git`, if any */
522 strbuf_strip_suffix(&buf, ".git");
523
524 enlistment = find_last_dir_sep(buf.buf);
525 if (!enlistment) {
526 die(_("cannot deduce worktree name from '%s'"), url);
527 }
528 enlistment = xstrdup(enlistment + 1);
529 } else {
530 usage_msg_opt(_("You must specify a repository to clone."),
531 clone_usage, clone_options);
532 }
533
534 if (is_directory(enlistment))
535 die(_("directory '%s' exists already"), enlistment);
536
537 dir = xstrfmt("%s/src", enlistment);
538
539 strbuf_reset(&buf);
540 if (branch)
541 strbuf_addf(&buf, "init.defaultBranch=%s", branch);
542 else {
543 char *b = repo_default_branch_name(the_repository, 1);
544 strbuf_addf(&buf, "init.defaultBranch=%s", b);
545 free(b);
546 }
547
548 if ((res = run_git("-c", buf.buf, "init", "--", dir, NULL)))
549 goto cleanup;
550
551 if (chdir(dir) < 0) {
552 res = error_errno(_("could not switch to '%s'"), dir);
553 goto cleanup;
554 }
555
556 setup_git_directory();
557
558 /* common-main already logs `argv` */
559 trace2_def_repo(the_repository);
560
561 if (!branch && !(branch = remote_default_branch(url))) {
562 res = error(_("failed to get default branch for '%s'"), url);
563 goto cleanup;
564 }
565
566 if (set_config("remote.origin.url=%s", url) ||
567 set_config("remote.origin.fetch="
4368e40b
JS
568 "+refs/heads/%s:refs/remotes/origin/%s",
569 single_branch ? branch : "*",
570 single_branch ? branch : "*") ||
546f822d
JS
571 set_config("remote.origin.promisor=true") ||
572 set_config("remote.origin.partialCloneFilter=blob:none")) {
573 res = error(_("could not configure remote in '%s'"), dir);
574 goto cleanup;
575 }
576
577 if (!full_clone &&
578 (res = run_git("sparse-checkout", "init", "--cone", NULL)))
579 goto cleanup;
580
cb59d55e 581 if (set_recommended_config(0))
546f822d
JS
582 return error(_("could not configure '%s'"), dir);
583
584 if ((res = run_git("fetch", "--quiet", "origin", NULL))) {
585 warning(_("partial clone failed; attempting full clone"));
586
587 if (set_config("remote.origin.promisor") ||
588 set_config("remote.origin.partialCloneFilter")) {
589 res = error(_("could not configure for full clone"));
590 goto cleanup;
591 }
592
593 if ((res = run_git("fetch", "--quiet", "origin", NULL)))
594 goto cleanup;
595 }
596
597 if ((res = set_config("branch.%s.remote=origin", branch)))
598 goto cleanup;
599 if ((res = set_config("branch.%s.merge=refs/heads/%s",
600 branch, branch)))
601 goto cleanup;
602
603 strbuf_reset(&buf);
604 strbuf_addf(&buf, "origin/%s", branch);
605 res = run_git("checkout", "-f", "-t", buf.buf, NULL);
606 if (res)
607 goto cleanup;
608
609 res = register_dir();
610
611cleanup:
612 free(enlistment);
613 free(dir);
614 strbuf_release(&buf);
615 return res;
616}
617
93e804b2
MJC
618static void dir_file_stats_objects(const char *full_path, size_t full_path_len,
619 const char *file_name, void *data)
620{
621 struct strbuf *buf = data;
622 struct stat st;
623
624 if (!stat(full_path, &st))
625 strbuf_addf(buf, "%-70s %16" PRIuMAX "\n", file_name,
626 (uintmax_t)st.st_size);
627}
628
629static int dir_file_stats(struct object_directory *object_dir, void *data)
630{
631 struct strbuf *buf = data;
632
633 strbuf_addf(buf, "Contents of %s:\n", object_dir->path);
634
635 for_each_file_in_pack_dir(object_dir->path, dir_file_stats_objects,
636 data);
637
638 return 0;
639}
640
15d8adcc
MJC
641static int count_files(char *path)
642{
643 DIR *dir = opendir(path);
644 struct dirent *e;
645 int count = 0;
646
647 if (!dir)
648 return 0;
649
650 while ((e = readdir(dir)) != NULL)
651 if (!is_dot_or_dotdot(e->d_name) && e->d_type == DT_REG)
652 count++;
653
654 closedir(dir);
655 return count;
656}
657
658static void loose_objs_stats(struct strbuf *buf, const char *path)
659{
660 DIR *dir = opendir(path);
661 struct dirent *e;
662 int count;
663 int total = 0;
664 unsigned char c;
665 struct strbuf count_path = STRBUF_INIT;
666 size_t base_path_len;
667
668 if (!dir)
669 return;
670
671 strbuf_addstr(buf, "Object directory stats for ");
672 strbuf_add_absolute_path(buf, path);
673 strbuf_addstr(buf, ":\n");
674
675 strbuf_add_absolute_path(&count_path, path);
676 strbuf_addch(&count_path, '/');
677 base_path_len = count_path.len;
678
679 while ((e = readdir(dir)) != NULL)
680 if (!is_dot_or_dotdot(e->d_name) &&
681 e->d_type == DT_DIR && strlen(e->d_name) == 2 &&
682 !hex_to_bytes(&c, e->d_name, 1)) {
683 strbuf_setlen(&count_path, base_path_len);
684 strbuf_addstr(&count_path, e->d_name);
685 total += (count = count_files(count_path.buf));
686 strbuf_addf(buf, "%s : %7d files\n", e->d_name, count);
687 }
688
689 strbuf_addf(buf, "Total: %d loose objects", total);
690
691 strbuf_release(&count_path);
692 closedir(dir);
693}
694
aa5c79a3
JS
695static int cmd_diagnose(int argc, const char **argv)
696{
697 struct option options[] = {
698 OPT_END(),
699 };
700 const char * const usage[] = {
701 N_("scalar diagnose [<enlistment>]"),
702 NULL
703 };
704 struct strbuf zip_path = STRBUF_INIT;
705 struct strvec archiver_args = STRVEC_INIT;
706 char **argv_copy = NULL;
707 int stdout_fd = -1, archiver_fd = -1;
708 time_t now = time(NULL);
709 struct tm tm;
4f40f6cb 710 struct strbuf buf = STRBUF_INIT;
aa5c79a3
JS
711 int res = 0;
712
713 argc = parse_options(argc, argv, NULL, options,
714 usage, 0);
715
716 setup_enlistment_directory(argc, argv, usage, options, &zip_path);
717
718 strbuf_addstr(&zip_path, "/.scalarDiagnostics/scalar_");
719 strbuf_addftime(&zip_path,
720 "%Y%m%d_%H%M%S", localtime_r(&now, &tm), 0, 0);
721 strbuf_addstr(&zip_path, ".zip");
722 switch (safe_create_leading_directories(zip_path.buf)) {
723 case SCLD_EXISTS:
724 case SCLD_OK:
725 break;
726 default:
727 error_errno(_("could not create directory for '%s'"),
728 zip_path.buf);
729 goto diagnose_cleanup;
730 }
731 stdout_fd = dup(1);
732 if (stdout_fd < 0) {
733 res = error_errno(_("could not duplicate stdout"));
734 goto diagnose_cleanup;
735 }
736
737 archiver_fd = xopen(zip_path.buf, O_CREAT | O_WRONLY | O_TRUNC, 0666);
738 if (archiver_fd < 0 || dup2(archiver_fd, 1) < 0) {
739 res = error_errno(_("could not redirect output"));
740 goto diagnose_cleanup;
741 }
742
743 init_zip_archiver();
744 strvec_pushl(&archiver_args, "scalar-diagnose", "--format=zip", NULL);
745
746 strbuf_reset(&buf);
747 strbuf_addstr(&buf, "Collecting diagnostic info\n\n");
748 get_version_info(&buf, 1);
749
750 strbuf_addf(&buf, "Enlistment root: %s\n", the_repository->worktree);
0ed5b13f 751 get_disk_info(&buf);
aa5c79a3
JS
752 write_or_die(stdout_fd, buf.buf, buf.len);
753 strvec_pushf(&archiver_args,
754 "--add-virtual-file=diagnostics.log:%.*s",
755 (int)buf.len, buf.buf);
756
93e804b2
MJC
757 strbuf_reset(&buf);
758 strbuf_addstr(&buf, "--add-virtual-file=packs-local.txt:");
759 dir_file_stats(the_repository->objects->odb, &buf);
760 foreach_alt_odb(dir_file_stats, &buf);
761 strvec_push(&archiver_args, buf.buf);
762
15d8adcc
MJC
763 strbuf_reset(&buf);
764 strbuf_addstr(&buf, "--add-virtual-file=objects-local.txt:");
765 loose_objs_stats(&buf, ".git/objects");
766 strvec_push(&archiver_args, buf.buf);
767
aa5c79a3
JS
768 if ((res = add_directory_to_archiver(&archiver_args, ".git", 0)) ||
769 (res = add_directory_to_archiver(&archiver_args, ".git/hooks", 0)) ||
770 (res = add_directory_to_archiver(&archiver_args, ".git/info", 0)) ||
771 (res = add_directory_to_archiver(&archiver_args, ".git/logs", 1)) ||
772 (res = add_directory_to_archiver(&archiver_args, ".git/objects/info", 0)))
773 goto diagnose_cleanup;
774
775 strvec_pushl(&archiver_args, "--prefix=",
776 oid_to_hex(the_hash_algo->empty_tree), "--", NULL);
777
778 /* `write_archive()` modifies the `argv` passed to it. Let it. */
779 argv_copy = xmemdupz(archiver_args.v,
780 sizeof(char *) * archiver_args.nr);
781 res = write_archive(archiver_args.nr, (const char **)argv_copy, NULL,
782 the_repository, NULL, 0);
783 if (res) {
784 error(_("failed to write archive"));
785 goto diagnose_cleanup;
786 }
787
788 if (!res)
789 fprintf(stderr, "\n"
790 "Diagnostics complete.\n"
791 "All of the gathered info is captured in '%s'\n",
792 zip_path.buf);
793
794diagnose_cleanup:
795 if (archiver_fd >= 0) {
796 close(1);
797 dup2(stdout_fd, 1);
798 }
799 free(argv_copy);
800 strvec_clear(&archiver_args);
801 strbuf_release(&zip_path);
aa5c79a3
JS
802 strbuf_release(&buf);
803
804 return res;
805}
806
2b710457
DS
807static int cmd_list(int argc, const char **argv)
808{
809 if (argc != 1)
810 die(_("`scalar list` does not take arguments"));
811
812 if (run_git("config", "--global", "--get-all", "scalar.repo", NULL) < 0)
813 return -1;
814 return 0;
815}
816
d0feac4e
DS
817static int cmd_register(int argc, const char **argv)
818{
819 struct option options[] = {
820 OPT_END(),
821 };
822 const char * const usage[] = {
823 N_("scalar register [<enlistment>]"),
824 NULL
825 };
826
827 argc = parse_options(argc, argv, NULL, options,
828 usage, 0);
829
830 setup_enlistment_directory(argc, argv, usage, options, NULL);
831
832 return register_dir();
833}
0a43fb22 834
45826760
JS
835static int get_scalar_repos(const char *key, const char *value, void *data)
836{
837 struct string_list *list = data;
838
839 if (!strcmp(key, "scalar.repo"))
840 string_list_append(list, value);
841
842 return 0;
843}
844
cb59d55e
JS
845static int cmd_reconfigure(int argc, const char **argv)
846{
45826760 847 int all = 0;
cb59d55e 848 struct option options[] = {
45826760
JS
849 OPT_BOOL('a', "all", &all,
850 N_("reconfigure all registered enlistments")),
cb59d55e
JS
851 OPT_END(),
852 };
853 const char * const usage[] = {
45826760 854 N_("scalar reconfigure [--all | <enlistment>]"),
cb59d55e
JS
855 NULL
856 };
45826760
JS
857 struct string_list scalar_repos = STRING_LIST_INIT_DUP;
858 int i, res = 0;
859 struct repository r = { NULL };
860 struct strbuf commondir = STRBUF_INIT, gitdir = STRBUF_INIT;
cb59d55e
JS
861
862 argc = parse_options(argc, argv, NULL, options,
863 usage, 0);
864
45826760
JS
865 if (!all) {
866 setup_enlistment_directory(argc, argv, usage, options, NULL);
867
868 return set_recommended_config(1);
869 }
870
871 if (argc > 0)
872 usage_msg_opt(_("--all or <enlistment>, but not both"),
873 usage, options);
874
875 git_config(get_scalar_repos, &scalar_repos);
cb59d55e 876
45826760
JS
877 for (i = 0; i < scalar_repos.nr; i++) {
878 const char *dir = scalar_repos.items[i].string;
879
880 strbuf_reset(&commondir);
881 strbuf_reset(&gitdir);
882
883 if (chdir(dir) < 0) {
884 warning_errno(_("could not switch to '%s'"), dir);
885 res = -1;
886 } else if (discover_git_directory(&commondir, &gitdir) < 0) {
887 warning_errno(_("git repository gone in '%s'"), dir);
888 res = -1;
889 } else {
890 git_config_clear();
891
892 the_repository = &r;
893 r.commondir = commondir.buf;
894 r.gitdir = gitdir.buf;
895
896 if (set_recommended_config(1) < 0)
897 res = -1;
898 }
899 }
900
901 string_list_clear(&scalar_repos, 1);
902 strbuf_release(&commondir);
903 strbuf_release(&gitdir);
904
905 return res;
cb59d55e
JS
906}
907
7020c88c
DS
908static int cmd_run(int argc, const char **argv)
909{
910 struct option options[] = {
911 OPT_END(),
912 };
913 struct {
914 const char *arg, *task;
915 } tasks[] = {
916 { "config", NULL },
917 { "commit-graph", "commit-graph" },
918 { "fetch", "prefetch" },
919 { "loose-objects", "loose-objects" },
920 { "pack-files", "incremental-repack" },
921 { NULL, NULL }
922 };
923 struct strbuf buf = STRBUF_INIT;
924 const char *usagestr[] = { NULL, NULL };
925 int i;
926
927 strbuf_addstr(&buf, N_("scalar run <task> [<enlistment>]\nTasks:\n"));
928 for (i = 0; tasks[i].arg; i++)
929 strbuf_addf(&buf, "\t%s\n", tasks[i].arg);
930 usagestr[0] = buf.buf;
931
932 argc = parse_options(argc, argv, NULL, options,
933 usagestr, 0);
934
935 if (!argc)
936 usage_with_options(usagestr, options);
937
938 if (!strcmp("all", argv[0])) {
939 i = -1;
940 } else {
941 for (i = 0; tasks[i].arg && strcmp(tasks[i].arg, argv[0]); i++)
942 ; /* keep looking for the task */
943
944 if (i > 0 && !tasks[i].arg) {
945 error(_("no such task: '%s'"), argv[0]);
946 usage_with_options(usagestr, options);
947 }
948 }
949
950 argc--;
951 argv++;
952 setup_enlistment_directory(argc, argv, usagestr, options, NULL);
953 strbuf_release(&buf);
954
955 if (i == 0)
956 return register_dir();
957
958 if (i > 0)
959 return run_git("maintenance", "run",
960 "--task", tasks[i].task, NULL);
961
962 if (register_dir())
963 return -1;
964 for (i = 1; tasks[i].arg; i++)
965 if (run_git("maintenance", "run",
966 "--task", tasks[i].task, NULL))
967 return -1;
968 return 0;
969}
970
f5f0842d
JS
971static int remove_deleted_enlistment(struct strbuf *path)
972{
973 int res = 0;
974 strbuf_realpath_forgiving(path, path->buf, 1);
975
976 if (run_git("config", "--global",
977 "--unset", "--fixed-value",
978 "scalar.repo", path->buf, NULL) < 0)
979 res = -1;
980
981 if (run_git("config", "--global",
982 "--unset", "--fixed-value",
983 "maintenance.repo", path->buf, NULL) < 0)
984 res = -1;
985
986 return res;
987}
988
c76a53eb
DS
989static int cmd_unregister(int argc, const char **argv)
990{
991 struct option options[] = {
992 OPT_END(),
993 };
994 const char * const usage[] = {
995 N_("scalar unregister [<enlistment>]"),
996 NULL
997 };
998
999 argc = parse_options(argc, argv, NULL, options,
1000 usage, 0);
1001
f5f0842d
JS
1002 /*
1003 * Be forgiving when the enlistment or worktree does not even exist any
1004 * longer; This can be the case if a user deleted the worktree by
1005 * mistake and _still_ wants to unregister the thing.
1006 */
1007 if (argc == 1) {
1008 struct strbuf src_path = STRBUF_INIT, workdir_path = STRBUF_INIT;
1009
1010 strbuf_addf(&src_path, "%s/src/.git", argv[0]);
1011 strbuf_addf(&workdir_path, "%s/.git", argv[0]);
1012 if (!is_directory(src_path.buf) && !is_directory(workdir_path.buf)) {
1013 /* remove possible matching registrations */
1014 int res = -1;
1015
1016 strbuf_strip_suffix(&src_path, "/.git");
1017 res = remove_deleted_enlistment(&src_path) && res;
1018
1019 strbuf_strip_suffix(&workdir_path, "/.git");
1020 res = remove_deleted_enlistment(&workdir_path) && res;
1021
1022 strbuf_release(&src_path);
1023 strbuf_release(&workdir_path);
1024 return res;
1025 }
1026 strbuf_release(&src_path);
1027 strbuf_release(&workdir_path);
1028 }
1029
c76a53eb
DS
1030 setup_enlistment_directory(argc, argv, usage, options, NULL);
1031
1032 return unregister_dir();
1033}
1034
d85ada7c
MJC
1035static int cmd_delete(int argc, const char **argv)
1036{
1037 char *cwd = xgetcwd();
1038 struct option options[] = {
1039 OPT_END(),
1040 };
1041 const char * const usage[] = {
1042 N_("scalar delete <enlistment>"),
1043 NULL
1044 };
1045 struct strbuf enlistment = STRBUF_INIT;
1046 int res = 0;
1047
1048 argc = parse_options(argc, argv, NULL, options,
1049 usage, 0);
1050
1051 if (argc != 1)
1052 usage_with_options(usage, options);
1053
1054 setup_enlistment_directory(argc, argv, usage, options, &enlistment);
1055
1056 if (dir_inside_of(cwd, enlistment.buf) >= 0)
1057 res = error(_("refusing to delete current working directory"));
1058 else {
1059 close_object_store(the_repository->objects);
1060 res = delete_enlistment(&enlistment);
1061 }
1062 strbuf_release(&enlistment);
1063 free(cwd);
1064
1065 return res;
1066}
1067
ddc35d83
JS
1068static int cmd_version(int argc, const char **argv)
1069{
1070 int verbose = 0, build_options = 0;
1071 struct option options[] = {
1072 OPT__VERBOSE(&verbose, N_("include Git version")),
1073 OPT_BOOL(0, "build-options", &build_options,
1074 N_("include Git's build options")),
1075 OPT_END(),
1076 };
1077 const char * const usage[] = {
1078 N_("scalar verbose [-v | --verbose] [--build-options]"),
1079 NULL
1080 };
1081 struct strbuf buf = STRBUF_INIT;
1082
1083 argc = parse_options(argc, argv, NULL, options,
1084 usage, 0);
1085
1086 if (argc != 0)
1087 usage_with_options(usage, options);
1088
1089 get_version_info(&buf, build_options);
1090 fprintf(stderr, "%s\n", buf.buf);
1091 strbuf_release(&buf);
1092
1093 return 0;
1094}
1095
0a43fb22
JS
1096static struct {
1097 const char *name;
1098 int (*fn)(int, const char **);
1099} builtins[] = {
546f822d 1100 { "clone", cmd_clone },
2b710457 1101 { "list", cmd_list },
d0feac4e 1102 { "register", cmd_register },
c76a53eb 1103 { "unregister", cmd_unregister },
7020c88c 1104 { "run", cmd_run },
cb59d55e 1105 { "reconfigure", cmd_reconfigure },
d85ada7c 1106 { "delete", cmd_delete },
ddc35d83 1107 { "version", cmd_version },
aa5c79a3 1108 { "diagnose", cmd_diagnose },
0a43fb22
JS
1109 { NULL, NULL},
1110};
1111
1112int cmd_main(int argc, const char **argv)
1113{
1114 struct strbuf scalar_usage = STRBUF_INIT;
1115 int i;
1116
2ae8eb5d
JS
1117 while (argc > 1 && *argv[1] == '-') {
1118 if (!strcmp(argv[1], "-C")) {
1119 if (argc < 3)
1120 die(_("-C requires a <directory>"));
1121 if (chdir(argv[2]) < 0)
1122 die_errno(_("could not change to '%s'"),
1123 argv[2]);
1124 argc -= 2;
1125 argv += 2;
1126 } else if (!strcmp(argv[1], "-c")) {
1127 if (argc < 3)
1128 die(_("-c requires a <key>=<value> argument"));
1129 git_config_push_parameter(argv[2]);
1130 argc -= 2;
1131 argv += 2;
1132 } else
1133 break;
1134 }
1135
0a43fb22
JS
1136 if (argc > 1) {
1137 argv++;
1138 argc--;
1139
1140 for (i = 0; builtins[i].name; i++)
1141 if (!strcmp(builtins[i].name, argv[0]))
1142 return !!builtins[i].fn(argc, argv);
1143 }
1144
1145 strbuf_addstr(&scalar_usage,
2ae8eb5d
JS
1146 N_("scalar [-C <directory>] [-c <key>=<value>] "
1147 "<command> [<options>]\n\nCommands:\n"));
0a43fb22
JS
1148 for (i = 0; builtins[i].name; i++)
1149 strbuf_addf(&scalar_usage, "\t%s\n", builtins[i].name);
1150
1151 usage(scalar_usage.buf);
1152}