]> git.ipfire.org Git - thirdparty/git.git/blame - contrib/scalar/scalar.c
scalar: implement 'scalar list'
[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"
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 */
16static 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
26static 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
91static 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
110static 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
c76a53eb 201static int toggle_maintenance(int enable)
d0feac4e 202{
c76a53eb 203 return run_git("maintenance", enable ? "start" : "unregister", NULL);
d0feac4e
DS
204}
205
c76a53eb 206static int add_or_remove_enlistment(int add)
d0feac4e
DS
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 /*
c76a53eb
DS
217 * If we want to add and the setting is already there, then do nothing.
218 * If we want to remove and the setting is not there, then do nothing.
d0feac4e 219 */
c76a53eb 220 if ((add && !res) || (!add && res))
d0feac4e
DS
221 return 0;
222
c76a53eb
DS
223 return run_git("config", "--global", add ? "--add" : "--unset",
224 add ? "--no-fixed-value" : "--fixed-value",
d0feac4e
DS
225 "scalar.repo", the_repository->worktree, NULL);
226}
227
228static int register_dir(void)
229{
c76a53eb 230 int res = add_or_remove_enlistment(1);
d0feac4e
DS
231
232 if (!res)
233 res = set_recommended_config();
234
235 if (!res)
c76a53eb
DS
236 res = toggle_maintenance(1);
237
238 return res;
239}
240
241static int unregister_dir(void)
242{
243 int res = 0;
244
245 if (toggle_maintenance(0) < 0)
246 res = -1;
247
248 if (add_or_remove_enlistment(0) < 0)
249 res = -1;
d0feac4e
DS
250
251 return res;
252}
253
2b710457
DS
254static int cmd_list(int argc, const char **argv)
255{
256 if (argc != 1)
257 die(_("`scalar list` does not take arguments"));
258
259 if (run_git("config", "--global", "--get-all", "scalar.repo", NULL) < 0)
260 return -1;
261 return 0;
262}
263
d0feac4e
DS
264static int cmd_register(int argc, const char **argv)
265{
266 struct option options[] = {
267 OPT_END(),
268 };
269 const char * const usage[] = {
270 N_("scalar register [<enlistment>]"),
271 NULL
272 };
273
274 argc = parse_options(argc, argv, NULL, options,
275 usage, 0);
276
277 setup_enlistment_directory(argc, argv, usage, options, NULL);
278
279 return register_dir();
280}
0a43fb22 281
f5f0842d
JS
282static int remove_deleted_enlistment(struct strbuf *path)
283{
284 int res = 0;
285 strbuf_realpath_forgiving(path, path->buf, 1);
286
287 if (run_git("config", "--global",
288 "--unset", "--fixed-value",
289 "scalar.repo", path->buf, NULL) < 0)
290 res = -1;
291
292 if (run_git("config", "--global",
293 "--unset", "--fixed-value",
294 "maintenance.repo", path->buf, NULL) < 0)
295 res = -1;
296
297 return res;
298}
299
c76a53eb
DS
300static int cmd_unregister(int argc, const char **argv)
301{
302 struct option options[] = {
303 OPT_END(),
304 };
305 const char * const usage[] = {
306 N_("scalar unregister [<enlistment>]"),
307 NULL
308 };
309
310 argc = parse_options(argc, argv, NULL, options,
311 usage, 0);
312
f5f0842d
JS
313 /*
314 * Be forgiving when the enlistment or worktree does not even exist any
315 * longer; This can be the case if a user deleted the worktree by
316 * mistake and _still_ wants to unregister the thing.
317 */
318 if (argc == 1) {
319 struct strbuf src_path = STRBUF_INIT, workdir_path = STRBUF_INIT;
320
321 strbuf_addf(&src_path, "%s/src/.git", argv[0]);
322 strbuf_addf(&workdir_path, "%s/.git", argv[0]);
323 if (!is_directory(src_path.buf) && !is_directory(workdir_path.buf)) {
324 /* remove possible matching registrations */
325 int res = -1;
326
327 strbuf_strip_suffix(&src_path, "/.git");
328 res = remove_deleted_enlistment(&src_path) && res;
329
330 strbuf_strip_suffix(&workdir_path, "/.git");
331 res = remove_deleted_enlistment(&workdir_path) && res;
332
333 strbuf_release(&src_path);
334 strbuf_release(&workdir_path);
335 return res;
336 }
337 strbuf_release(&src_path);
338 strbuf_release(&workdir_path);
339 }
340
c76a53eb
DS
341 setup_enlistment_directory(argc, argv, usage, options, NULL);
342
343 return unregister_dir();
344}
345
0a43fb22
JS
346static struct {
347 const char *name;
348 int (*fn)(int, const char **);
349} builtins[] = {
2b710457 350 { "list", cmd_list },
d0feac4e 351 { "register", cmd_register },
c76a53eb 352 { "unregister", cmd_unregister },
0a43fb22
JS
353 { NULL, NULL},
354};
355
356int cmd_main(int argc, const char **argv)
357{
358 struct strbuf scalar_usage = STRBUF_INIT;
359 int i;
360
361 if (argc > 1) {
362 argv++;
363 argc--;
364
365 for (i = 0; builtins[i].name; i++)
366 if (!strcmp(builtins[i].name, argv[0]))
367 return !!builtins[i].fn(argc, argv);
368 }
369
370 strbuf_addstr(&scalar_usage,
371 N_("scalar <command> [<options>]\n\nCommands:\n"));
372 for (i = 0; builtins[i].name; i++)
373 strbuf_addf(&scalar_usage, "\t%s\n", builtins[i].name);
374
375 usage(scalar_usage.buf);
376}