scalar: 'register' sets recommended config and starts maintenance

Let's start implementing the `register` command. With this commit, recommended settings are configured upon `scalar register`, and Git's background maintenance is started. The recommended config settings may very well change in the future. For example, once the built-in FSMonitor is available, we will want to enable it upon `scalar register`. For that reason, we explicitly support running `scalar register` in an already-registered enlistment. Co-authored-by: Victoria Dye <vdye@github.com> Signed-off-by: Derrick Stolee <dstolee@microsoft.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Derrick Stolee committed Dec 3, 2021 at 13:34 UTC d0feac4e8c0b60b16c06363f9feb3e2d0ecf00f1
2 files changed +266 -1
contrib/scalar/scalar.c
+249
@@ -5,11 +5,260 @@
5 #include "cache.h"
6 #include "gettext.h"
7 #include "parse-options.h"
8 +#include "config.h"
9 +#include "run-command.h"
10 +
11 +/*
12 + * Remove the deepest subdirectory in the provided path string. Path must not
13 + * include a trailing path separator. Returns 1 if parent directory found,
14 + * otherwise 0.
15 + */
16 +static int strbuf_parent_directory(struct strbuf *buf)
17 +{
18 + size_t len = buf->len;
19 + size_t offset = offset_1st_component(buf->buf);
20 + char *path_sep = find_last_dir_sep(buf->buf + offset);
21 + strbuf_setlen(buf, path_sep ? path_sep - buf->buf : offset);
22 +
23 + return buf->len < len;
24 +}
25 +
26 +static void setup_enlistment_directory(int argc, const char **argv,
27 + const char * const *usagestr,
28 + const struct option *options,
29 + struct strbuf *enlistment_root)
30 +{
31 + struct strbuf path = STRBUF_INIT;
32 + char *root;
33 + int enlistment_found = 0;
34 +
35 + if (startup_info->have_repository)
36 + BUG("gitdir already set up?!?");
37 +
38 + if (argc > 1)
39 + usage_with_options(usagestr, options);
40 +
41 + /* find the worktree, determine its corresponding root */
42 + if (argc == 1)
43 + strbuf_add_absolute_path(&path, argv[0]);
44 + else if (strbuf_getcwd(&path) < 0)
45 + die(_("need a working directory"));
46 +
47 + strbuf_trim_trailing_dir_sep(&path);
48 + do {
49 + const size_t len = path.len;
50 +
51 + /* check if currently in enlistment root with src/ workdir */
52 + strbuf_addstr(&path, "/src");
53 + if (is_nonbare_repository_dir(&path)) {
54 + if (enlistment_root)
55 + strbuf_add(enlistment_root, path.buf, len);
56 +
57 + enlistment_found = 1;
58 + break;
59 + }
60 +
61 + /* reset to original path */
62 + strbuf_setlen(&path, len);
63 +
64 + /* check if currently in workdir */
65 + if (is_nonbare_repository_dir(&path)) {
66 + if (enlistment_root) {
67 + /*
68 + * If the worktree's directory's name is `src`, the enlistment is the
69 + * parent directory, otherwise it is identical to the worktree.
70 + */
71 + root = strip_path_suffix(path.buf, "src");
72 + strbuf_addstr(enlistment_root, root ? root : path.buf);
73 + free(root);
74 + }
75 +
76 + enlistment_found = 1;
77 + break;
78 + }
79 + } while (strbuf_parent_directory(&path));
80 +
81 + if (!enlistment_found)
82 + die(_("could not find enlistment root"));
83 +
84 + if (chdir(path.buf) < 0)
85 + die_errno(_("could not switch to '%s'"), path.buf);
86 +
87 + strbuf_release(&path);
88 + setup_git_directory();
89 +}
90 +
91 +static int run_git(const char *arg, ...)
92 +{
93 + struct strvec argv = STRVEC_INIT;
94 + va_list args;
95 + const char *p;
96 + int res;
97 +
98 + va_start(args, arg);
99 + strvec_push(&argv, arg);
100 + while ((p = va_arg(args, const char *)))
101 + strvec_push(&argv, p);
102 + va_end(args);
103 +
104 + res = run_command_v_opt(argv.v, RUN_GIT_CMD);
105 +
106 + strvec_clear(&argv);
107 + return res;
108 +}
109 +
110 +static int set_recommended_config(void)
111 +{
112 + struct {
113 + const char *key;
114 + const char *value;
115 + } config[] = {
116 + { "am.keepCR", "true" },
117 + { "core.FSCache", "true" },
118 + { "core.multiPackIndex", "true" },
119 + { "core.preloadIndex", "true" },
120 +#ifndef WIN32
121 + { "core.untrackedCache", "true" },
122 +#else
123 + /*
124 + * Unfortunately, Scalar's Functional Tests demonstrated
125 + * that the untracked cache feature is unreliable on Windows
126 + * (which is a bummer because that platform would benefit the
127 + * most from it). For some reason, freshly created files seem
128 + * not to update the directory's `lastModified` time
129 + * immediately, but the untracked cache would need to rely on
130 + * that.
131 + *
132 + * Therefore, with a sad heart, we disable this very useful
133 + * feature on Windows.
134 + */
135 + { "core.untrackedCache", "false" },
136 +#endif
137 + { "core.logAllRefUpdates", "true" },
138 + { "credential.https://dev.azure.com.useHttpPath", "true" },
139 + { "credential.validate", "false" }, /* GCM4W-only */
140 + { "gc.auto", "0" },
141 + { "gui.GCWarning", "false" },
142 + { "index.threads", "true" },
143 + { "index.version", "4" },
144 + { "merge.stat", "false" },
145 + { "merge.renames", "true" },
146 + { "pack.useBitmaps", "false" },
147 + { "pack.useSparse", "true" },
148 + { "receive.autoGC", "false" },
149 + { "reset.quiet", "true" },
150 + { "feature.manyFiles", "false" },
151 + { "feature.experimental", "false" },
152 + { "fetch.unpackLimit", "1" },
153 + { "fetch.writeCommitGraph", "false" },
154 +#ifdef WIN32
155 + { "http.sslBackend", "schannel" },
156 +#endif
157 + { "status.aheadBehind", "false" },
158 + { "commitGraph.generationVersion", "1" },
159 + { "core.autoCRLF", "false" },
160 + { "core.safeCRLF", "false" },
161 + { "fetch.showForcedUpdates", "false" },
162 + { NULL, NULL },
163 + };
164 + int i;
165 + char *value;
166 +
167 + for (i = 0; config[i].key; i++) {
168 + if (git_config_get_string(config[i].key, &value)) {
169 + trace2_data_string("scalar", the_repository, config[i].key, "created");
170 + if (git_config_set_gently(config[i].key,
171 + config[i].value) < 0)
172 + return error(_("could not configure %s=%s"),
173 + config[i].key, config[i].value);
174 + } else {
175 + trace2_data_string("scalar", the_repository, config[i].key, "exists");
176 + free(value);
177 + }
178 + }
179 +
180 + /*
181 + * The `log.excludeDecoration` setting is special because it allows
182 + * for multiple values.
183 + */
184 + if (git_config_get_string("log.excludeDecoration", &value)) {
185 + trace2_data_string("scalar", the_repository,
186 + "log.excludeDecoration", "created");
187 + if (git_config_set_multivar_gently("log.excludeDecoration",
188 + "refs/prefetch/*",
189 + CONFIG_REGEX_NONE, 0))
190 + return error(_("could not configure "
191 + "log.excludeDecoration"));
192 + } else {
193 + trace2_data_string("scalar", the_repository,
194 + "log.excludeDecoration", "exists");
195 + free(value);
196 + }
197 +
198 + return 0;
199 +}
200 +
201 +static int start_maintenance(void)
202 +{
203 + return run_git("maintenance", "start", NULL);
204 +}
205 +
206 +static int add_enlistment(void)
207 +{
208 + int res;
209 +
210 + if (!the_repository->worktree)
211 + die(_("Scalar enlistments require a worktree"));
212 +
213 + res = run_git("config", "--global", "--get", "--fixed-value",
214 + "scalar.repo", the_repository->worktree, NULL);
215 +
216 + /*
217 + * If the setting is already there, then do nothing.
218 + */
219 + if (!res)
220 + return 0;
221 +
222 + return run_git("config", "--global", "--add",
223 + "scalar.repo", the_repository->worktree, NULL);
224 +}
225 +
226 +static int register_dir(void)
227 +{
228 + int res = add_enlistment();
229 +
230 + if (!res)
231 + res = set_recommended_config();
232 +
233 + if (!res)
234 + res = start_maintenance();
235 +
236 + return res;
237 +}
238 +
239 +static int cmd_register(int argc, const char **argv)
240 +{
241 + struct option options[] = {
242 + OPT_END(),
243 + };
244 + const char * const usage[] = {
245 + N_("scalar register [<enlistment>]"),
246 + NULL
247 + };
248 +
249 + argc = parse_options(argc, argv, NULL, options,
250 + usage, 0);
251 +
252 + setup_enlistment_directory(argc, argv, usage, options, NULL);
253 +
254 + return register_dir();
255 +}
256
257 static struct {
258 const char *name;
259 int (*fn)(int, const char **);
260 } builtins[] = {
261 + { "register", cmd_register },
262 { NULL, NULL},
263 };
264
contrib/scalar/scalar.txt
+17 -1
@@ -8,7 +8,7 @@ scalar - an opinionated repository management tool
8 SYNOPSIS
9 --------
10 [verse]
11 -scalar <command> [<options>]
11 +scalar register [<enlistment>]
12
13 DESCRIPTION
14 -----------
@@ -29,6 +29,22 @@ will be identical to the worktree.
29 The `scalar` command implements various subcommands, and different options
30 depending on the subcommand.
31
32 +COMMANDS
33 +--------
34 +
35 +Register
36 +~~~~~~~~
37 +
38 +register [<enlistment>]::
39 + Adds the enlistment's repository to the list of registered repositories
40 + and starts background maintenance. If `<enlistment>` is not provided,
41 + then the enlistment associated with the current working directory is
42 + registered.
43 ++
44 +Note: when this subcommand is called in a worktree that is called `src/`, its
45 +parent directory is considered to be the Scalar enlistment. If the worktree is
46 +_not_ called `src/`, it itself will be considered to be the Scalar enlistment.
47 +
48 SEE ALSO
49 --------
50 linkgit:git-maintenance[1].