]>
Commit | Line | Data |
---|---|---|
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 | */ | |
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 | ||
c76a53eb | 201 | static int toggle_maintenance(int enable) |
d0feac4e | 202 | { |
c76a53eb | 203 | return run_git("maintenance", enable ? "start" : "unregister", NULL); |
d0feac4e DS |
204 | } |
205 | ||
c76a53eb | 206 | static 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 | ||
228 | static 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 | ||
241 | static 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 |
254 | static 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 |
264 | static 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 |
282 | static 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 |
300 | static 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 |
346 | static 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 | ||
356 | int 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 | } |