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].