sparse-checkout: init and set in cone mode
[git] / builtin / sparse-checkout.c
1 #include "builtin.h"
2 #include "config.h"
3 #include "dir.h"
4 #include "parse-options.h"
5 #include "pathspec.h"
6 #include "repository.h"
7 #include "run-command.h"
8 #include "strbuf.h"
9 #include "string-list.h"
10
11 static char const * const builtin_sparse_checkout_usage[] = {
12         N_("git sparse-checkout (init|list|set|disable) <options>"),
13         NULL
14 };
15
16 static char *get_sparse_checkout_filename(void)
17 {
18         return git_pathdup("info/sparse-checkout");
19 }
20
21 static void write_patterns_to_file(FILE *fp, struct pattern_list *pl)
22 {
23         int i;
24
25         for (i = 0; i < pl->nr; i++) {
26                 struct path_pattern *p = pl->patterns[i];
27
28                 if (p->flags & PATTERN_FLAG_NEGATIVE)
29                         fprintf(fp, "!");
30
31                 fprintf(fp, "%s", p->pattern);
32
33                 if (p->flags & PATTERN_FLAG_MUSTBEDIR)
34                         fprintf(fp, "/");
35
36                 fprintf(fp, "\n");
37         }
38 }
39
40 static int sparse_checkout_list(int argc, const char **argv)
41 {
42         struct pattern_list pl;
43         char *sparse_filename;
44         int res;
45
46         memset(&pl, 0, sizeof(pl));
47
48         sparse_filename = get_sparse_checkout_filename();
49         res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL);
50         free(sparse_filename);
51
52         if (res < 0) {
53                 warning(_("this worktree is not sparse (sparse-checkout file may not exist)"));
54                 return 0;
55         }
56
57         write_patterns_to_file(stdout, &pl);
58         clear_pattern_list(&pl);
59
60         return 0;
61 }
62
63 static int update_working_directory(void)
64 {
65         struct argv_array argv = ARGV_ARRAY_INIT;
66         int result = 0;
67         argv_array_pushl(&argv, "read-tree", "-m", "-u", "HEAD", NULL);
68
69         if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) {
70                 error(_("failed to update index with new sparse-checkout patterns"));
71                 result = 1;
72         }
73
74         argv_array_clear(&argv);
75         return result;
76 }
77
78 static void write_cone_to_file(FILE *fp, struct pattern_list *pl)
79 {
80         int i;
81         struct pattern_entry *pe;
82         struct hashmap_iter iter;
83         struct string_list sl = STRING_LIST_INIT_DUP;
84
85         hashmap_for_each_entry(&pl->parent_hashmap, &iter, pe, ent)
86                 string_list_insert(&sl, pe->pattern);
87
88         string_list_sort(&sl);
89         string_list_remove_duplicates(&sl, 0);
90
91         fprintf(fp, "/*\n!/*/\n");
92
93         for (i = 0; i < sl.nr; i++) {
94                 char *pattern = sl.items[i].string;
95
96                 if (strlen(pattern))
97                         fprintf(fp, "%s/\n!%s/*/\n", pattern, pattern);
98         }
99
100         string_list_clear(&sl, 0);
101
102         hashmap_for_each_entry(&pl->recursive_hashmap, &iter, pe, ent)
103                 string_list_insert(&sl, pe->pattern);
104
105         string_list_sort(&sl);
106         string_list_remove_duplicates(&sl, 0);
107
108         for (i = 0; i < sl.nr; i++) {
109                 char *pattern = sl.items[i].string;
110                 fprintf(fp, "%s/\n", pattern);
111         }
112 }
113
114 static int write_patterns_and_update(struct pattern_list *pl)
115 {
116         char *sparse_filename;
117         FILE *fp;
118
119         sparse_filename = get_sparse_checkout_filename();
120         fp = fopen(sparse_filename, "w");
121
122         if (core_sparse_checkout_cone)
123                 write_cone_to_file(fp, pl);
124         else
125                 write_patterns_to_file(fp, pl);
126
127         fclose(fp);
128         free(sparse_filename);
129
130         return update_working_directory();
131 }
132
133 enum sparse_checkout_mode {
134         MODE_NO_PATTERNS = 0,
135         MODE_ALL_PATTERNS = 1,
136         MODE_CONE_PATTERNS = 2,
137 };
138
139 static int set_config(enum sparse_checkout_mode mode)
140 {
141         const char *config_path;
142
143         if (git_config_set_gently("extensions.worktreeConfig", "true")) {
144                 error(_("failed to set extensions.worktreeConfig setting"));
145                 return 1;
146         }
147
148         config_path = git_path("config.worktree");
149         git_config_set_in_file_gently(config_path,
150                                       "core.sparseCheckout",
151                                       mode ? "true" : NULL);
152
153         git_config_set_in_file_gently(config_path,
154                                       "core.sparseCheckoutCone",
155                                       mode == MODE_CONE_PATTERNS ? "true" : NULL);
156
157         return 0;
158 }
159
160 static char const * const builtin_sparse_checkout_init_usage[] = {
161         N_("git sparse-checkout init [--cone]"),
162         NULL
163 };
164
165 static struct sparse_checkout_init_opts {
166         int cone_mode;
167 } init_opts;
168
169 static int sparse_checkout_init(int argc, const char **argv)
170 {
171         struct pattern_list pl;
172         char *sparse_filename;
173         FILE *fp;
174         int res;
175         struct object_id oid;
176         int mode;
177
178         static struct option builtin_sparse_checkout_init_options[] = {
179                 OPT_BOOL(0, "cone", &init_opts.cone_mode,
180                          N_("initialize the sparse-checkout in cone mode")),
181                 OPT_END(),
182         };
183
184         argc = parse_options(argc, argv, NULL,
185                              builtin_sparse_checkout_init_options,
186                              builtin_sparse_checkout_init_usage, 0);
187
188         mode = init_opts.cone_mode ? MODE_CONE_PATTERNS : MODE_ALL_PATTERNS;
189
190         if (set_config(mode))
191                 return 1;
192
193         memset(&pl, 0, sizeof(pl));
194
195         sparse_filename = get_sparse_checkout_filename();
196         res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL);
197
198         /* If we already have a sparse-checkout file, use it. */
199         if (res >= 0) {
200                 free(sparse_filename);
201                 goto reset_dir;
202         }
203
204         /* initial mode: all blobs at root */
205         fp = xfopen(sparse_filename, "w");
206         if (!fp)
207                 die(_("failed to open '%s'"), sparse_filename);
208
209         free(sparse_filename);
210         fprintf(fp, "/*\n!/*/\n");
211         fclose(fp);
212
213         if (get_oid("HEAD", &oid)) {
214                 /* assume we are in a fresh repo */
215                 return 0;
216         }
217
218 reset_dir:
219         return update_working_directory();
220 }
221
222 static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *path)
223 {
224         struct pattern_entry *e = xmalloc(sizeof(*e));
225         e->patternlen = path->len;
226         e->pattern = strbuf_detach(path, NULL);
227         hashmap_entry_init(&e->ent, memhash(e->pattern, e->patternlen));
228
229         hashmap_add(&pl->recursive_hashmap, &e->ent);
230
231         while (e->patternlen) {
232                 char *slash = strrchr(e->pattern, '/');
233                 char *oldpattern = e->pattern;
234                 size_t newlen;
235
236                 if (slash == e->pattern)
237                         break;
238
239                 newlen = slash - e->pattern;
240                 e = xmalloc(sizeof(struct pattern_entry));
241                 e->patternlen = newlen;
242                 e->pattern = xstrndup(oldpattern, newlen);
243                 hashmap_entry_init(&e->ent, memhash(e->pattern, e->patternlen));
244
245                 if (!hashmap_get_entry(&pl->parent_hashmap, e, ent, NULL))
246                         hashmap_add(&pl->parent_hashmap, &e->ent);
247         }
248 }
249
250 static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl)
251 {
252         strbuf_trim(line);
253
254         strbuf_trim_trailing_dir_sep(line);
255
256         if (!line->len)
257                 return;
258
259         if (line->buf[0] != '/')
260                 strbuf_insert(line, 0, "/", 1);
261
262         insert_recursive_pattern(pl, line);
263 }
264
265 static char const * const builtin_sparse_checkout_set_usage[] = {
266         N_("git sparse-checkout set (--stdin | <patterns>)"),
267         NULL
268 };
269
270 static struct sparse_checkout_set_opts {
271         int use_stdin;
272 } set_opts;
273
274 static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
275 {
276         static const char *empty_base = "";
277         int i;
278         struct pattern_list pl;
279         int result;
280         int changed_config = 0;
281
282         static struct option builtin_sparse_checkout_set_options[] = {
283                 OPT_BOOL(0, "stdin", &set_opts.use_stdin,
284                          N_("read patterns from standard in")),
285                 OPT_END(),
286         };
287
288         memset(&pl, 0, sizeof(pl));
289
290         argc = parse_options(argc, argv, prefix,
291                              builtin_sparse_checkout_set_options,
292                              builtin_sparse_checkout_set_usage,
293                              PARSE_OPT_KEEP_UNKNOWN);
294
295         if (core_sparse_checkout_cone) {
296                 struct strbuf line = STRBUF_INIT;
297
298                 hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0);
299                 hashmap_init(&pl.parent_hashmap, pl_hashmap_cmp, NULL, 0);
300
301                 if (set_opts.use_stdin) {
302                         while (!strbuf_getline(&line, stdin))
303                                 strbuf_to_cone_pattern(&line, &pl);
304                 } else {
305                         for (i = 0; i < argc; i++) {
306                                 strbuf_setlen(&line, 0);
307                                 strbuf_addstr(&line, argv[i]);
308                                 strbuf_to_cone_pattern(&line, &pl);
309                         }
310                 }
311         } else {
312                 if (set_opts.use_stdin) {
313                         struct strbuf line = STRBUF_INIT;
314
315                         while (!strbuf_getline(&line, stdin)) {
316                                 size_t len;
317                                 char *buf = strbuf_detach(&line, &len);
318                                 add_pattern(buf, empty_base, 0, &pl, 0);
319                         }
320                 } else {
321                         for (i = 0; i < argc; i++)
322                                 add_pattern(argv[i], empty_base, 0, &pl, 0);
323                 }
324         }
325
326         if (!core_apply_sparse_checkout) {
327                 set_config(MODE_ALL_PATTERNS);
328                 core_apply_sparse_checkout = 1;
329                 changed_config = 1;
330         }
331
332         result = write_patterns_and_update(&pl);
333
334         if (result && changed_config)
335                 set_config(MODE_NO_PATTERNS);
336
337         clear_pattern_list(&pl);
338         return result;
339 }
340
341 static int sparse_checkout_disable(int argc, const char **argv)
342 {
343         char *sparse_filename;
344         FILE *fp;
345
346         if (set_config(MODE_ALL_PATTERNS))
347                 die(_("failed to change config"));
348
349         sparse_filename = get_sparse_checkout_filename();
350         fp = xfopen(sparse_filename, "w");
351         fprintf(fp, "/*\n");
352         fclose(fp);
353
354         if (update_working_directory())
355                 die(_("error while refreshing working directory"));
356
357         unlink(sparse_filename);
358         free(sparse_filename);
359
360         return set_config(MODE_NO_PATTERNS);
361 }
362
363 int cmd_sparse_checkout(int argc, const char **argv, const char *prefix)
364 {
365         static struct option builtin_sparse_checkout_options[] = {
366                 OPT_END(),
367         };
368
369         if (argc == 2 && !strcmp(argv[1], "-h"))
370                 usage_with_options(builtin_sparse_checkout_usage,
371                                    builtin_sparse_checkout_options);
372
373         argc = parse_options(argc, argv, prefix,
374                              builtin_sparse_checkout_options,
375                              builtin_sparse_checkout_usage,
376                              PARSE_OPT_STOP_AT_NON_OPTION);
377
378         git_config(git_default_config, NULL);
379
380         if (argc > 0) {
381                 if (!strcmp(argv[0], "list"))
382                         return sparse_checkout_list(argc, argv);
383                 if (!strcmp(argv[0], "init"))
384                         return sparse_checkout_init(argc, argv);
385                 if (!strcmp(argv[0], "set"))
386                         return sparse_checkout_set(argc, argv, prefix);
387                 if (!strcmp(argv[0], "disable"))
388                         return sparse_checkout_disable(argc, argv);
389         }
390
391         usage_with_options(builtin_sparse_checkout_usage,
392                            builtin_sparse_checkout_options);
393 }