/src/git/builtin/sparse-checkout.c
Line | Count | Source (jump to first uncovered line) |
1 | | #include "builtin.h" |
2 | | #include "config.h" |
3 | | #include "dir.h" |
4 | | #include "environment.h" |
5 | | #include "gettext.h" |
6 | | #include "object-file.h" |
7 | | #include "object-name.h" |
8 | | #include "parse-options.h" |
9 | | #include "pathspec.h" |
10 | | #include "repository.h" |
11 | | #include "strbuf.h" |
12 | | #include "string-list.h" |
13 | | #include "lockfile.h" |
14 | | #include "unpack-trees.h" |
15 | | #include "quote.h" |
16 | | #include "setup.h" |
17 | | #include "sparse-index.h" |
18 | | #include "worktree.h" |
19 | | |
20 | | static const char *empty_base = ""; |
21 | | |
22 | | static char const * const builtin_sparse_checkout_usage[] = { |
23 | | N_("git sparse-checkout (init | list | set | add | reapply | disable | check-rules) [<options>]"), |
24 | | NULL |
25 | | }; |
26 | | |
27 | | static void write_patterns_to_file(FILE *fp, struct pattern_list *pl) |
28 | 0 | { |
29 | 0 | int i; |
30 | |
|
31 | 0 | for (i = 0; i < pl->nr; i++) { |
32 | 0 | struct path_pattern *p = pl->patterns[i]; |
33 | |
|
34 | 0 | if (p->flags & PATTERN_FLAG_NEGATIVE) |
35 | 0 | fprintf(fp, "!"); |
36 | |
|
37 | 0 | fprintf(fp, "%s", p->pattern); |
38 | |
|
39 | 0 | if (p->flags & PATTERN_FLAG_MUSTBEDIR) |
40 | 0 | fprintf(fp, "/"); |
41 | |
|
42 | 0 | fprintf(fp, "\n"); |
43 | 0 | } |
44 | 0 | } |
45 | | |
46 | | static char const * const builtin_sparse_checkout_list_usage[] = { |
47 | | "git sparse-checkout list", |
48 | | NULL |
49 | | }; |
50 | | |
51 | | static int sparse_checkout_list(int argc, const char **argv, const char *prefix) |
52 | 0 | { |
53 | 0 | static struct option builtin_sparse_checkout_list_options[] = { |
54 | 0 | OPT_END(), |
55 | 0 | }; |
56 | 0 | struct pattern_list pl; |
57 | 0 | char *sparse_filename; |
58 | 0 | int res; |
59 | |
|
60 | 0 | setup_work_tree(); |
61 | 0 | if (!core_apply_sparse_checkout) |
62 | 0 | die(_("this worktree is not sparse")); |
63 | | |
64 | 0 | argc = parse_options(argc, argv, prefix, |
65 | 0 | builtin_sparse_checkout_list_options, |
66 | 0 | builtin_sparse_checkout_list_usage, 0); |
67 | |
|
68 | 0 | memset(&pl, 0, sizeof(pl)); |
69 | |
|
70 | 0 | pl.use_cone_patterns = core_sparse_checkout_cone; |
71 | |
|
72 | 0 | sparse_filename = get_sparse_checkout_filename(); |
73 | 0 | res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL, 0); |
74 | 0 | free(sparse_filename); |
75 | |
|
76 | 0 | if (res < 0) { |
77 | 0 | warning(_("this worktree is not sparse (sparse-checkout file may not exist)")); |
78 | 0 | return 0; |
79 | 0 | } |
80 | | |
81 | 0 | if (pl.use_cone_patterns) { |
82 | 0 | int i; |
83 | 0 | struct pattern_entry *pe; |
84 | 0 | struct hashmap_iter iter; |
85 | 0 | struct string_list sl = STRING_LIST_INIT_DUP; |
86 | |
|
87 | 0 | hashmap_for_each_entry(&pl.recursive_hashmap, &iter, pe, ent) { |
88 | | /* pe->pattern starts with "/", skip it */ |
89 | 0 | string_list_insert(&sl, pe->pattern + 1); |
90 | 0 | } |
91 | |
|
92 | 0 | string_list_sort(&sl); |
93 | |
|
94 | 0 | for (i = 0; i < sl.nr; i++) { |
95 | 0 | quote_c_style(sl.items[i].string, NULL, stdout, 0); |
96 | 0 | printf("\n"); |
97 | 0 | } |
98 | |
|
99 | 0 | string_list_clear(&sl, 0); |
100 | 0 | } else { |
101 | 0 | write_patterns_to_file(stdout, &pl); |
102 | 0 | } |
103 | |
|
104 | 0 | clear_pattern_list(&pl); |
105 | |
|
106 | 0 | return 0; |
107 | 0 | } |
108 | | |
109 | | static void clean_tracked_sparse_directories(struct repository *r) |
110 | 0 | { |
111 | 0 | int i, was_full = 0; |
112 | 0 | struct strbuf path = STRBUF_INIT; |
113 | 0 | size_t pathlen; |
114 | 0 | struct string_list_item *item; |
115 | 0 | struct string_list sparse_dirs = STRING_LIST_INIT_DUP; |
116 | | |
117 | | /* |
118 | | * If we are not using cone mode patterns, then we cannot |
119 | | * delete directories outside of the sparse cone. |
120 | | */ |
121 | 0 | if (!r || !r->index || !r->worktree) |
122 | 0 | return; |
123 | 0 | if (init_sparse_checkout_patterns(r->index) || |
124 | 0 | !r->index->sparse_checkout_patterns->use_cone_patterns) |
125 | 0 | return; |
126 | | |
127 | | /* |
128 | | * Use the sparse index as a data structure to assist finding |
129 | | * directories that are safe to delete. This conversion to a |
130 | | * sparse index will not delete directories that contain |
131 | | * conflicted entries or submodules. |
132 | | */ |
133 | 0 | if (r->index->sparse_index == INDEX_EXPANDED) { |
134 | | /* |
135 | | * If something, such as a merge conflict or other concern, |
136 | | * prevents us from converting to a sparse index, then do |
137 | | * not try deleting files. |
138 | | */ |
139 | 0 | if (convert_to_sparse(r->index, SPARSE_INDEX_MEMORY_ONLY)) |
140 | 0 | return; |
141 | 0 | was_full = 1; |
142 | 0 | } |
143 | | |
144 | 0 | strbuf_addstr(&path, r->worktree); |
145 | 0 | strbuf_complete(&path, '/'); |
146 | 0 | pathlen = path.len; |
147 | | |
148 | | /* |
149 | | * Collect directories that have gone out of scope but also |
150 | | * exist on disk, so there is some work to be done. We need to |
151 | | * store the entries in a list before exploring, since that might |
152 | | * expand the sparse-index again. |
153 | | */ |
154 | 0 | for (i = 0; i < r->index->cache_nr; i++) { |
155 | 0 | struct cache_entry *ce = r->index->cache[i]; |
156 | |
|
157 | 0 | if (S_ISSPARSEDIR(ce->ce_mode) && |
158 | 0 | repo_file_exists(r, ce->name)) |
159 | 0 | string_list_append(&sparse_dirs, ce->name); |
160 | 0 | } |
161 | |
|
162 | 0 | for_each_string_list_item(item, &sparse_dirs) { |
163 | 0 | struct dir_struct dir = DIR_INIT; |
164 | 0 | struct pathspec p = { 0 }; |
165 | 0 | struct strvec s = STRVEC_INIT; |
166 | |
|
167 | 0 | strbuf_setlen(&path, pathlen); |
168 | 0 | strbuf_addstr(&path, item->string); |
169 | |
|
170 | 0 | dir.flags |= DIR_SHOW_IGNORED_TOO; |
171 | |
|
172 | 0 | setup_standard_excludes(&dir); |
173 | 0 | strvec_push(&s, path.buf); |
174 | |
|
175 | 0 | parse_pathspec(&p, PATHSPEC_GLOB, 0, NULL, s.v); |
176 | 0 | fill_directory(&dir, r->index, &p); |
177 | |
|
178 | 0 | if (dir.nr) { |
179 | 0 | warning(_("directory '%s' contains untracked files," |
180 | 0 | " but is not in the sparse-checkout cone"), |
181 | 0 | item->string); |
182 | 0 | } else if (remove_dir_recursively(&path, 0)) { |
183 | | /* |
184 | | * Removal is "best effort". If something blocks |
185 | | * the deletion, then continue with a warning. |
186 | | */ |
187 | 0 | warning(_("failed to remove directory '%s'"), |
188 | 0 | item->string); |
189 | 0 | } |
190 | |
|
191 | 0 | strvec_clear(&s); |
192 | 0 | clear_pathspec(&p); |
193 | 0 | dir_clear(&dir); |
194 | 0 | } |
195 | |
|
196 | 0 | string_list_clear(&sparse_dirs, 0); |
197 | 0 | strbuf_release(&path); |
198 | |
|
199 | 0 | if (was_full) |
200 | 0 | ensure_full_index(r->index); |
201 | 0 | } |
202 | | |
203 | | static int update_working_directory(struct pattern_list *pl) |
204 | 0 | { |
205 | 0 | enum update_sparsity_result result; |
206 | 0 | struct unpack_trees_options o; |
207 | 0 | struct lock_file lock_file = LOCK_INIT; |
208 | 0 | struct repository *r = the_repository; |
209 | 0 | struct pattern_list *old_pl; |
210 | | |
211 | | /* If no branch has been checked out, there are no updates to make. */ |
212 | 0 | if (is_index_unborn(r->index)) |
213 | 0 | return UPDATE_SPARSITY_SUCCESS; |
214 | | |
215 | 0 | old_pl = r->index->sparse_checkout_patterns; |
216 | 0 | r->index->sparse_checkout_patterns = pl; |
217 | |
|
218 | 0 | memset(&o, 0, sizeof(o)); |
219 | 0 | o.verbose_update = isatty(2); |
220 | 0 | o.update = 1; |
221 | 0 | o.head_idx = -1; |
222 | 0 | o.src_index = r->index; |
223 | 0 | o.dst_index = r->index; |
224 | 0 | o.skip_sparse_checkout = 0; |
225 | |
|
226 | 0 | setup_work_tree(); |
227 | |
|
228 | 0 | repo_hold_locked_index(r, &lock_file, LOCK_DIE_ON_ERROR); |
229 | |
|
230 | 0 | setup_unpack_trees_porcelain(&o, "sparse-checkout"); |
231 | 0 | result = update_sparsity(&o, pl); |
232 | 0 | clear_unpack_trees_porcelain(&o); |
233 | |
|
234 | 0 | if (result == UPDATE_SPARSITY_WARNINGS) |
235 | | /* |
236 | | * We don't do any special handling of warnings from untracked |
237 | | * files in the way or dirty entries that can't be removed. |
238 | | */ |
239 | 0 | result = UPDATE_SPARSITY_SUCCESS; |
240 | 0 | if (result == UPDATE_SPARSITY_SUCCESS) |
241 | 0 | write_locked_index(r->index, &lock_file, COMMIT_LOCK); |
242 | 0 | else |
243 | 0 | rollback_lock_file(&lock_file); |
244 | |
|
245 | 0 | clean_tracked_sparse_directories(r); |
246 | |
|
247 | 0 | if (r->index->sparse_checkout_patterns != pl) { |
248 | 0 | clear_pattern_list(r->index->sparse_checkout_patterns); |
249 | 0 | FREE_AND_NULL(r->index->sparse_checkout_patterns); |
250 | 0 | } |
251 | 0 | r->index->sparse_checkout_patterns = old_pl; |
252 | |
|
253 | 0 | return result; |
254 | 0 | } |
255 | | |
256 | | static char *escaped_pattern(char *pattern) |
257 | 0 | { |
258 | 0 | char *p = pattern; |
259 | 0 | struct strbuf final = STRBUF_INIT; |
260 | |
|
261 | 0 | while (*p) { |
262 | 0 | if (is_glob_special(*p)) |
263 | 0 | strbuf_addch(&final, '\\'); |
264 | |
|
265 | 0 | strbuf_addch(&final, *p); |
266 | 0 | p++; |
267 | 0 | } |
268 | |
|
269 | 0 | return strbuf_detach(&final, NULL); |
270 | 0 | } |
271 | | |
272 | | static void write_cone_to_file(FILE *fp, struct pattern_list *pl) |
273 | 0 | { |
274 | 0 | int i; |
275 | 0 | struct pattern_entry *pe; |
276 | 0 | struct hashmap_iter iter; |
277 | 0 | struct string_list sl = STRING_LIST_INIT_DUP; |
278 | 0 | struct strbuf parent_pattern = STRBUF_INIT; |
279 | |
|
280 | 0 | hashmap_for_each_entry(&pl->parent_hashmap, &iter, pe, ent) { |
281 | 0 | if (hashmap_get_entry(&pl->recursive_hashmap, pe, ent, NULL)) |
282 | 0 | continue; |
283 | | |
284 | 0 | if (!hashmap_contains_parent(&pl->recursive_hashmap, |
285 | 0 | pe->pattern, |
286 | 0 | &parent_pattern)) |
287 | 0 | string_list_insert(&sl, pe->pattern); |
288 | 0 | } |
289 | |
|
290 | 0 | string_list_sort(&sl); |
291 | 0 | string_list_remove_duplicates(&sl, 0); |
292 | |
|
293 | 0 | fprintf(fp, "/*\n!/*/\n"); |
294 | |
|
295 | 0 | for (i = 0; i < sl.nr; i++) { |
296 | 0 | char *pattern = escaped_pattern(sl.items[i].string); |
297 | |
|
298 | 0 | if (strlen(pattern)) |
299 | 0 | fprintf(fp, "%s/\n!%s/*/\n", pattern, pattern); |
300 | 0 | free(pattern); |
301 | 0 | } |
302 | |
|
303 | 0 | string_list_clear(&sl, 0); |
304 | |
|
305 | 0 | hashmap_for_each_entry(&pl->recursive_hashmap, &iter, pe, ent) { |
306 | 0 | if (!hashmap_contains_parent(&pl->recursive_hashmap, |
307 | 0 | pe->pattern, |
308 | 0 | &parent_pattern)) |
309 | 0 | string_list_insert(&sl, pe->pattern); |
310 | 0 | } |
311 | |
|
312 | 0 | strbuf_release(&parent_pattern); |
313 | |
|
314 | 0 | string_list_sort(&sl); |
315 | 0 | string_list_remove_duplicates(&sl, 0); |
316 | |
|
317 | 0 | for (i = 0; i < sl.nr; i++) { |
318 | 0 | char *pattern = escaped_pattern(sl.items[i].string); |
319 | 0 | fprintf(fp, "%s/\n", pattern); |
320 | 0 | free(pattern); |
321 | 0 | } |
322 | |
|
323 | 0 | string_list_clear(&sl, 0); |
324 | 0 | } |
325 | | |
326 | | static int write_patterns_and_update(struct pattern_list *pl) |
327 | 0 | { |
328 | 0 | char *sparse_filename; |
329 | 0 | FILE *fp; |
330 | 0 | struct lock_file lk = LOCK_INIT; |
331 | 0 | int result; |
332 | |
|
333 | 0 | sparse_filename = get_sparse_checkout_filename(); |
334 | |
|
335 | 0 | if (safe_create_leading_directories(sparse_filename)) |
336 | 0 | die(_("failed to create directory for sparse-checkout file")); |
337 | | |
338 | 0 | hold_lock_file_for_update(&lk, sparse_filename, LOCK_DIE_ON_ERROR); |
339 | |
|
340 | 0 | result = update_working_directory(pl); |
341 | 0 | if (result) { |
342 | 0 | rollback_lock_file(&lk); |
343 | 0 | update_working_directory(NULL); |
344 | 0 | goto out; |
345 | 0 | } |
346 | | |
347 | 0 | fp = fdopen_lock_file(&lk, "w"); |
348 | 0 | if (!fp) |
349 | 0 | die_errno(_("unable to fdopen %s"), get_lock_file_path(&lk)); |
350 | | |
351 | 0 | if (core_sparse_checkout_cone) |
352 | 0 | write_cone_to_file(fp, pl); |
353 | 0 | else |
354 | 0 | write_patterns_to_file(fp, pl); |
355 | |
|
356 | 0 | if (commit_lock_file(&lk)) |
357 | 0 | die_errno(_("unable to write %s"), sparse_filename); |
358 | | |
359 | 0 | out: |
360 | 0 | clear_pattern_list(pl); |
361 | 0 | free(sparse_filename); |
362 | 0 | return result; |
363 | 0 | } |
364 | | |
365 | | enum sparse_checkout_mode { |
366 | | MODE_NO_PATTERNS = 0, |
367 | | MODE_ALL_PATTERNS = 1, |
368 | | MODE_CONE_PATTERNS = 2, |
369 | | }; |
370 | | |
371 | | static int set_config(enum sparse_checkout_mode mode) |
372 | 0 | { |
373 | | /* Update to use worktree config, if not already. */ |
374 | 0 | if (init_worktree_config(the_repository)) { |
375 | 0 | error(_("failed to initialize worktree config")); |
376 | 0 | return 1; |
377 | 0 | } |
378 | | |
379 | 0 | if (repo_config_set_worktree_gently(the_repository, |
380 | 0 | "core.sparseCheckout", |
381 | 0 | mode ? "true" : "false") || |
382 | 0 | repo_config_set_worktree_gently(the_repository, |
383 | 0 | "core.sparseCheckoutCone", |
384 | 0 | mode == MODE_CONE_PATTERNS ? |
385 | 0 | "true" : "false")) |
386 | 0 | return 1; |
387 | | |
388 | 0 | if (mode == MODE_NO_PATTERNS) |
389 | 0 | return set_sparse_index_config(the_repository, 0); |
390 | | |
391 | 0 | return 0; |
392 | 0 | } |
393 | | |
394 | 0 | static enum sparse_checkout_mode update_cone_mode(int *cone_mode) { |
395 | | /* If not specified, use previous definition of cone mode */ |
396 | 0 | if (*cone_mode == -1 && core_apply_sparse_checkout) |
397 | 0 | *cone_mode = core_sparse_checkout_cone; |
398 | | |
399 | | /* Set cone/non-cone mode appropriately */ |
400 | 0 | core_apply_sparse_checkout = 1; |
401 | 0 | if (*cone_mode == 1 || *cone_mode == -1) { |
402 | 0 | core_sparse_checkout_cone = 1; |
403 | 0 | return MODE_CONE_PATTERNS; |
404 | 0 | } |
405 | 0 | core_sparse_checkout_cone = 0; |
406 | 0 | return MODE_ALL_PATTERNS; |
407 | 0 | } |
408 | | |
409 | | static int update_modes(int *cone_mode, int *sparse_index) |
410 | 0 | { |
411 | 0 | int mode, record_mode; |
412 | | |
413 | | /* Determine if we need to record the mode; ensure sparse checkout on */ |
414 | 0 | record_mode = (*cone_mode != -1) || !core_apply_sparse_checkout; |
415 | |
|
416 | 0 | mode = update_cone_mode(cone_mode); |
417 | 0 | if (record_mode && set_config(mode)) |
418 | 0 | return 1; |
419 | | |
420 | | /* Set sparse-index/non-sparse-index mode if specified */ |
421 | 0 | if (*sparse_index >= 0) { |
422 | 0 | if (set_sparse_index_config(the_repository, *sparse_index) < 0) |
423 | 0 | die(_("failed to modify sparse-index config")); |
424 | | |
425 | | /* force an index rewrite */ |
426 | 0 | repo_read_index(the_repository); |
427 | 0 | the_repository->index->updated_workdir = 1; |
428 | |
|
429 | 0 | if (!*sparse_index) |
430 | 0 | ensure_full_index(the_repository->index); |
431 | 0 | } |
432 | | |
433 | 0 | return 0; |
434 | 0 | } |
435 | | |
436 | | static char const * const builtin_sparse_checkout_init_usage[] = { |
437 | | "git sparse-checkout init [--cone] [--[no-]sparse-index]", |
438 | | NULL |
439 | | }; |
440 | | |
441 | | static struct sparse_checkout_init_opts { |
442 | | int cone_mode; |
443 | | int sparse_index; |
444 | | } init_opts; |
445 | | |
446 | | static int sparse_checkout_init(int argc, const char **argv, const char *prefix) |
447 | 0 | { |
448 | 0 | struct pattern_list pl; |
449 | 0 | char *sparse_filename; |
450 | 0 | int res; |
451 | 0 | struct object_id oid; |
452 | |
|
453 | 0 | static struct option builtin_sparse_checkout_init_options[] = { |
454 | 0 | OPT_BOOL(0, "cone", &init_opts.cone_mode, |
455 | 0 | N_("initialize the sparse-checkout in cone mode")), |
456 | 0 | OPT_BOOL(0, "sparse-index", &init_opts.sparse_index, |
457 | 0 | N_("toggle the use of a sparse index")), |
458 | 0 | OPT_END(), |
459 | 0 | }; |
460 | |
|
461 | 0 | setup_work_tree(); |
462 | 0 | repo_read_index(the_repository); |
463 | |
|
464 | 0 | init_opts.cone_mode = -1; |
465 | 0 | init_opts.sparse_index = -1; |
466 | |
|
467 | 0 | argc = parse_options(argc, argv, prefix, |
468 | 0 | builtin_sparse_checkout_init_options, |
469 | 0 | builtin_sparse_checkout_init_usage, 0); |
470 | |
|
471 | 0 | if (update_modes(&init_opts.cone_mode, &init_opts.sparse_index)) |
472 | 0 | return 1; |
473 | | |
474 | 0 | memset(&pl, 0, sizeof(pl)); |
475 | |
|
476 | 0 | sparse_filename = get_sparse_checkout_filename(); |
477 | 0 | res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL, 0); |
478 | | |
479 | | /* If we already have a sparse-checkout file, use it. */ |
480 | 0 | if (res >= 0) { |
481 | 0 | free(sparse_filename); |
482 | 0 | clear_pattern_list(&pl); |
483 | 0 | return update_working_directory(NULL); |
484 | 0 | } |
485 | | |
486 | 0 | if (repo_get_oid(the_repository, "HEAD", &oid)) { |
487 | 0 | FILE *fp; |
488 | | |
489 | | /* assume we are in a fresh repo, but update the sparse-checkout file */ |
490 | 0 | if (safe_create_leading_directories(sparse_filename)) |
491 | 0 | die(_("unable to create leading directories of %s"), |
492 | 0 | sparse_filename); |
493 | 0 | fp = xfopen(sparse_filename, "w"); |
494 | 0 | if (!fp) |
495 | 0 | die(_("failed to open '%s'"), sparse_filename); |
496 | | |
497 | 0 | free(sparse_filename); |
498 | 0 | fprintf(fp, "/*\n!/*/\n"); |
499 | 0 | fclose(fp); |
500 | 0 | return 0; |
501 | 0 | } |
502 | | |
503 | 0 | free(sparse_filename); |
504 | |
|
505 | 0 | add_pattern("/*", empty_base, 0, &pl, 0); |
506 | 0 | add_pattern("!/*/", empty_base, 0, &pl, 0); |
507 | 0 | pl.use_cone_patterns = init_opts.cone_mode; |
508 | |
|
509 | 0 | return write_patterns_and_update(&pl); |
510 | 0 | } |
511 | | |
512 | | static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *path) |
513 | 0 | { |
514 | 0 | struct pattern_entry *e = xmalloc(sizeof(*e)); |
515 | 0 | e->patternlen = path->len; |
516 | 0 | e->pattern = strbuf_detach(path, NULL); |
517 | 0 | hashmap_entry_init(&e->ent, fspathhash(e->pattern)); |
518 | |
|
519 | 0 | hashmap_add(&pl->recursive_hashmap, &e->ent); |
520 | |
|
521 | 0 | while (e->patternlen) { |
522 | 0 | char *slash = strrchr(e->pattern, '/'); |
523 | 0 | char *oldpattern = e->pattern; |
524 | 0 | size_t newlen; |
525 | 0 | struct pattern_entry *dup; |
526 | |
|
527 | 0 | if (!slash || slash == e->pattern) |
528 | 0 | break; |
529 | | |
530 | 0 | newlen = slash - e->pattern; |
531 | 0 | e = xmalloc(sizeof(struct pattern_entry)); |
532 | 0 | e->patternlen = newlen; |
533 | 0 | e->pattern = xstrndup(oldpattern, newlen); |
534 | 0 | hashmap_entry_init(&e->ent, fspathhash(e->pattern)); |
535 | |
|
536 | 0 | dup = hashmap_get_entry(&pl->parent_hashmap, e, ent, NULL); |
537 | 0 | if (!dup) { |
538 | 0 | hashmap_add(&pl->parent_hashmap, &e->ent); |
539 | 0 | } else { |
540 | 0 | free(e->pattern); |
541 | 0 | free(e); |
542 | 0 | e = dup; |
543 | 0 | } |
544 | 0 | } |
545 | 0 | } |
546 | | |
547 | | static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl) |
548 | 0 | { |
549 | 0 | strbuf_trim(line); |
550 | |
|
551 | 0 | strbuf_trim_trailing_dir_sep(line); |
552 | |
|
553 | 0 | if (strbuf_normalize_path(line)) |
554 | 0 | die(_("could not normalize path %s"), line->buf); |
555 | | |
556 | 0 | if (!line->len) |
557 | 0 | return; |
558 | | |
559 | 0 | if (line->buf[0] != '/') |
560 | 0 | strbuf_insertstr(line, 0, "/"); |
561 | |
|
562 | 0 | insert_recursive_pattern(pl, line); |
563 | 0 | } |
564 | | |
565 | | static void add_patterns_from_input(struct pattern_list *pl, |
566 | | int argc, const char **argv, |
567 | | FILE *file) |
568 | 0 | { |
569 | 0 | int i; |
570 | 0 | if (core_sparse_checkout_cone) { |
571 | 0 | struct strbuf line = STRBUF_INIT; |
572 | |
|
573 | 0 | hashmap_init(&pl->recursive_hashmap, pl_hashmap_cmp, NULL, 0); |
574 | 0 | hashmap_init(&pl->parent_hashmap, pl_hashmap_cmp, NULL, 0); |
575 | 0 | pl->use_cone_patterns = 1; |
576 | |
|
577 | 0 | if (file) { |
578 | 0 | struct strbuf unquoted = STRBUF_INIT; |
579 | 0 | while (!strbuf_getline(&line, file)) { |
580 | 0 | if (line.buf[0] == '"') { |
581 | 0 | strbuf_reset(&unquoted); |
582 | 0 | if (unquote_c_style(&unquoted, line.buf, NULL)) |
583 | 0 | die(_("unable to unquote C-style string '%s'"), |
584 | 0 | line.buf); |
585 | | |
586 | 0 | strbuf_swap(&unquoted, &line); |
587 | 0 | } |
588 | | |
589 | 0 | strbuf_to_cone_pattern(&line, pl); |
590 | 0 | } |
591 | | |
592 | 0 | strbuf_release(&unquoted); |
593 | 0 | } else { |
594 | 0 | for (i = 0; i < argc; i++) { |
595 | 0 | strbuf_setlen(&line, 0); |
596 | 0 | strbuf_addstr(&line, argv[i]); |
597 | 0 | strbuf_to_cone_pattern(&line, pl); |
598 | 0 | } |
599 | 0 | } |
600 | 0 | strbuf_release(&line); |
601 | 0 | } else { |
602 | 0 | if (file) { |
603 | 0 | struct strbuf line = STRBUF_INIT; |
604 | |
|
605 | 0 | while (!strbuf_getline(&line, file)) |
606 | 0 | add_pattern(line.buf, empty_base, 0, pl, 0); |
607 | |
|
608 | 0 | strbuf_release(&line); |
609 | 0 | } else { |
610 | 0 | for (i = 0; i < argc; i++) |
611 | 0 | add_pattern(argv[i], empty_base, 0, pl, 0); |
612 | 0 | } |
613 | 0 | } |
614 | 0 | } |
615 | | |
616 | | enum modify_type { |
617 | | REPLACE, |
618 | | ADD, |
619 | | }; |
620 | | |
621 | | static void add_patterns_cone_mode(int argc, const char **argv, |
622 | | struct pattern_list *pl, |
623 | | int use_stdin) |
624 | 0 | { |
625 | 0 | struct strbuf buffer = STRBUF_INIT; |
626 | 0 | struct pattern_entry *pe; |
627 | 0 | struct hashmap_iter iter; |
628 | 0 | struct pattern_list existing; |
629 | 0 | char *sparse_filename = get_sparse_checkout_filename(); |
630 | |
|
631 | 0 | add_patterns_from_input(pl, argc, argv, |
632 | 0 | use_stdin ? stdin : NULL); |
633 | |
|
634 | 0 | memset(&existing, 0, sizeof(existing)); |
635 | 0 | existing.use_cone_patterns = core_sparse_checkout_cone; |
636 | |
|
637 | 0 | if (add_patterns_from_file_to_list(sparse_filename, "", 0, |
638 | 0 | &existing, NULL, 0)) |
639 | 0 | die(_("unable to load existing sparse-checkout patterns")); |
640 | 0 | free(sparse_filename); |
641 | |
|
642 | 0 | if (!existing.use_cone_patterns) |
643 | 0 | die(_("existing sparse-checkout patterns do not use cone mode")); |
644 | | |
645 | 0 | hashmap_for_each_entry(&existing.recursive_hashmap, &iter, pe, ent) { |
646 | 0 | if (!hashmap_contains_parent(&pl->recursive_hashmap, |
647 | 0 | pe->pattern, &buffer) || |
648 | 0 | !hashmap_contains_parent(&pl->parent_hashmap, |
649 | 0 | pe->pattern, &buffer)) { |
650 | 0 | strbuf_reset(&buffer); |
651 | 0 | strbuf_addstr(&buffer, pe->pattern); |
652 | 0 | insert_recursive_pattern(pl, &buffer); |
653 | 0 | } |
654 | 0 | } |
655 | |
|
656 | 0 | clear_pattern_list(&existing); |
657 | 0 | strbuf_release(&buffer); |
658 | 0 | } |
659 | | |
660 | | static void add_patterns_literal(int argc, const char **argv, |
661 | | struct pattern_list *pl, |
662 | | int use_stdin) |
663 | 0 | { |
664 | 0 | char *sparse_filename = get_sparse_checkout_filename(); |
665 | 0 | if (add_patterns_from_file_to_list(sparse_filename, "", 0, |
666 | 0 | pl, NULL, 0)) |
667 | 0 | die(_("unable to load existing sparse-checkout patterns")); |
668 | 0 | free(sparse_filename); |
669 | 0 | add_patterns_from_input(pl, argc, argv, use_stdin ? stdin : NULL); |
670 | 0 | } |
671 | | |
672 | | static int modify_pattern_list(int argc, const char **argv, int use_stdin, |
673 | | enum modify_type m) |
674 | 0 | { |
675 | 0 | int result; |
676 | 0 | int changed_config = 0; |
677 | 0 | struct pattern_list *pl = xcalloc(1, sizeof(*pl)); |
678 | |
|
679 | 0 | switch (m) { |
680 | 0 | case ADD: |
681 | 0 | if (core_sparse_checkout_cone) |
682 | 0 | add_patterns_cone_mode(argc, argv, pl, use_stdin); |
683 | 0 | else |
684 | 0 | add_patterns_literal(argc, argv, pl, use_stdin); |
685 | 0 | break; |
686 | | |
687 | 0 | case REPLACE: |
688 | 0 | add_patterns_from_input(pl, argc, argv, |
689 | 0 | use_stdin ? stdin : NULL); |
690 | 0 | break; |
691 | 0 | } |
692 | | |
693 | 0 | if (!core_apply_sparse_checkout) { |
694 | 0 | set_config(MODE_ALL_PATTERNS); |
695 | 0 | core_apply_sparse_checkout = 1; |
696 | 0 | changed_config = 1; |
697 | 0 | } |
698 | |
|
699 | 0 | result = write_patterns_and_update(pl); |
700 | |
|
701 | 0 | if (result && changed_config) |
702 | 0 | set_config(MODE_NO_PATTERNS); |
703 | |
|
704 | 0 | clear_pattern_list(pl); |
705 | 0 | free(pl); |
706 | 0 | return result; |
707 | 0 | } |
708 | | |
709 | | static void sanitize_paths(int argc, const char **argv, |
710 | | const char *prefix, int skip_checks) |
711 | 0 | { |
712 | 0 | int i; |
713 | |
|
714 | 0 | if (!argc) |
715 | 0 | return; |
716 | | |
717 | 0 | if (prefix && *prefix && core_sparse_checkout_cone) { |
718 | | /* |
719 | | * The args are not pathspecs, so unfortunately we |
720 | | * cannot imitate how cmd_add() uses parse_pathspec(). |
721 | | */ |
722 | 0 | int prefix_len = strlen(prefix); |
723 | |
|
724 | 0 | for (i = 0; i < argc; i++) |
725 | 0 | argv[i] = prefix_path(prefix, prefix_len, argv[i]); |
726 | 0 | } |
727 | |
|
728 | 0 | if (skip_checks) |
729 | 0 | return; |
730 | | |
731 | 0 | if (prefix && *prefix && !core_sparse_checkout_cone) |
732 | 0 | die(_("please run from the toplevel directory in non-cone mode")); |
733 | | |
734 | 0 | if (core_sparse_checkout_cone) { |
735 | 0 | for (i = 0; i < argc; i++) { |
736 | 0 | if (argv[i][0] == '/') |
737 | 0 | die(_("specify directories rather than patterns (no leading slash)")); |
738 | 0 | if (argv[i][0] == '!') |
739 | 0 | die(_("specify directories rather than patterns. If your directory starts with a '!', pass --skip-checks")); |
740 | 0 | if (strpbrk(argv[i], "*?[]")) |
741 | 0 | die(_("specify directories rather than patterns. If your directory really has any of '*?[]\\' in it, pass --skip-checks")); |
742 | 0 | } |
743 | 0 | } |
744 | | |
745 | 0 | for (i = 0; i < argc; i++) { |
746 | 0 | struct cache_entry *ce; |
747 | 0 | struct index_state *index = the_repository->index; |
748 | 0 | int pos = index_name_pos(index, argv[i], strlen(argv[i])); |
749 | |
|
750 | 0 | if (pos < 0) |
751 | 0 | continue; |
752 | 0 | ce = index->cache[pos]; |
753 | 0 | if (S_ISSPARSEDIR(ce->ce_mode)) |
754 | 0 | continue; |
755 | | |
756 | 0 | if (core_sparse_checkout_cone) |
757 | 0 | die(_("'%s' is not a directory; to treat it as a directory anyway, rerun with --skip-checks"), argv[i]); |
758 | 0 | else |
759 | 0 | warning(_("pass a leading slash before paths such as '%s' if you want a single file (see NON-CONE PROBLEMS in the git-sparse-checkout manual)."), argv[i]); |
760 | 0 | } |
761 | 0 | } |
762 | | |
763 | | static char const * const builtin_sparse_checkout_add_usage[] = { |
764 | | N_("git sparse-checkout add [--skip-checks] (--stdin | <patterns>)"), |
765 | | NULL |
766 | | }; |
767 | | |
768 | | static struct sparse_checkout_add_opts { |
769 | | int skip_checks; |
770 | | int use_stdin; |
771 | | } add_opts; |
772 | | |
773 | | static int sparse_checkout_add(int argc, const char **argv, const char *prefix) |
774 | 0 | { |
775 | 0 | static struct option builtin_sparse_checkout_add_options[] = { |
776 | 0 | OPT_BOOL_F(0, "skip-checks", &add_opts.skip_checks, |
777 | 0 | N_("skip some sanity checks on the given paths that might give false positives"), |
778 | 0 | PARSE_OPT_NONEG), |
779 | 0 | OPT_BOOL(0, "stdin", &add_opts.use_stdin, |
780 | 0 | N_("read patterns from standard in")), |
781 | 0 | OPT_END(), |
782 | 0 | }; |
783 | |
|
784 | 0 | setup_work_tree(); |
785 | 0 | if (!core_apply_sparse_checkout) |
786 | 0 | die(_("no sparse-checkout to add to")); |
787 | | |
788 | 0 | repo_read_index(the_repository); |
789 | |
|
790 | 0 | argc = parse_options(argc, argv, prefix, |
791 | 0 | builtin_sparse_checkout_add_options, |
792 | 0 | builtin_sparse_checkout_add_usage, 0); |
793 | |
|
794 | 0 | sanitize_paths(argc, argv, prefix, add_opts.skip_checks); |
795 | |
|
796 | 0 | return modify_pattern_list(argc, argv, add_opts.use_stdin, ADD); |
797 | 0 | } |
798 | | |
799 | | static char const * const builtin_sparse_checkout_set_usage[] = { |
800 | | N_("git sparse-checkout set [--[no-]cone] [--[no-]sparse-index] [--skip-checks] (--stdin | <patterns>)"), |
801 | | NULL |
802 | | }; |
803 | | |
804 | | static struct sparse_checkout_set_opts { |
805 | | int cone_mode; |
806 | | int sparse_index; |
807 | | int skip_checks; |
808 | | int use_stdin; |
809 | | } set_opts; |
810 | | |
811 | | static int sparse_checkout_set(int argc, const char **argv, const char *prefix) |
812 | 0 | { |
813 | 0 | int default_patterns_nr = 2; |
814 | 0 | const char *default_patterns[] = {"/*", "!/*/", NULL}; |
815 | |
|
816 | 0 | static struct option builtin_sparse_checkout_set_options[] = { |
817 | 0 | OPT_BOOL(0, "cone", &set_opts.cone_mode, |
818 | 0 | N_("initialize the sparse-checkout in cone mode")), |
819 | 0 | OPT_BOOL(0, "sparse-index", &set_opts.sparse_index, |
820 | 0 | N_("toggle the use of a sparse index")), |
821 | 0 | OPT_BOOL_F(0, "skip-checks", &set_opts.skip_checks, |
822 | 0 | N_("skip some sanity checks on the given paths that might give false positives"), |
823 | 0 | PARSE_OPT_NONEG), |
824 | 0 | OPT_BOOL_F(0, "stdin", &set_opts.use_stdin, |
825 | 0 | N_("read patterns from standard in"), |
826 | 0 | PARSE_OPT_NONEG), |
827 | 0 | OPT_END(), |
828 | 0 | }; |
829 | |
|
830 | 0 | setup_work_tree(); |
831 | 0 | repo_read_index(the_repository); |
832 | |
|
833 | 0 | set_opts.cone_mode = -1; |
834 | 0 | set_opts.sparse_index = -1; |
835 | |
|
836 | 0 | argc = parse_options(argc, argv, prefix, |
837 | 0 | builtin_sparse_checkout_set_options, |
838 | 0 | builtin_sparse_checkout_set_usage, 0); |
839 | |
|
840 | 0 | if (update_modes(&set_opts.cone_mode, &set_opts.sparse_index)) |
841 | 0 | return 1; |
842 | | |
843 | | /* |
844 | | * Cone mode automatically specifies the toplevel directory. For |
845 | | * non-cone mode, if nothing is specified, manually select just the |
846 | | * top-level directory (much as 'init' would do). |
847 | | */ |
848 | 0 | if (!core_sparse_checkout_cone && !set_opts.use_stdin && argc == 0) { |
849 | 0 | argv = default_patterns; |
850 | 0 | argc = default_patterns_nr; |
851 | 0 | } else { |
852 | 0 | sanitize_paths(argc, argv, prefix, set_opts.skip_checks); |
853 | 0 | } |
854 | |
|
855 | 0 | return modify_pattern_list(argc, argv, set_opts.use_stdin, REPLACE); |
856 | 0 | } |
857 | | |
858 | | static char const * const builtin_sparse_checkout_reapply_usage[] = { |
859 | | "git sparse-checkout reapply [--[no-]cone] [--[no-]sparse-index]", |
860 | | NULL |
861 | | }; |
862 | | |
863 | | static struct sparse_checkout_reapply_opts { |
864 | | int cone_mode; |
865 | | int sparse_index; |
866 | | } reapply_opts; |
867 | | |
868 | | static int sparse_checkout_reapply(int argc, const char **argv, |
869 | | const char *prefix) |
870 | 0 | { |
871 | 0 | static struct option builtin_sparse_checkout_reapply_options[] = { |
872 | 0 | OPT_BOOL(0, "cone", &reapply_opts.cone_mode, |
873 | 0 | N_("initialize the sparse-checkout in cone mode")), |
874 | 0 | OPT_BOOL(0, "sparse-index", &reapply_opts.sparse_index, |
875 | 0 | N_("toggle the use of a sparse index")), |
876 | 0 | OPT_END(), |
877 | 0 | }; |
878 | |
|
879 | 0 | setup_work_tree(); |
880 | 0 | if (!core_apply_sparse_checkout) |
881 | 0 | die(_("must be in a sparse-checkout to reapply sparsity patterns")); |
882 | | |
883 | 0 | reapply_opts.cone_mode = -1; |
884 | 0 | reapply_opts.sparse_index = -1; |
885 | |
|
886 | 0 | argc = parse_options(argc, argv, prefix, |
887 | 0 | builtin_sparse_checkout_reapply_options, |
888 | 0 | builtin_sparse_checkout_reapply_usage, 0); |
889 | |
|
890 | 0 | repo_read_index(the_repository); |
891 | |
|
892 | 0 | if (update_modes(&reapply_opts.cone_mode, &reapply_opts.sparse_index)) |
893 | 0 | return 1; |
894 | | |
895 | 0 | return update_working_directory(NULL); |
896 | 0 | } |
897 | | |
898 | | static char const * const builtin_sparse_checkout_disable_usage[] = { |
899 | | "git sparse-checkout disable", |
900 | | NULL |
901 | | }; |
902 | | |
903 | | static int sparse_checkout_disable(int argc, const char **argv, |
904 | | const char *prefix) |
905 | 0 | { |
906 | 0 | static struct option builtin_sparse_checkout_disable_options[] = { |
907 | 0 | OPT_END(), |
908 | 0 | }; |
909 | 0 | struct pattern_list pl; |
910 | | |
911 | | /* |
912 | | * We do not exit early if !core_apply_sparse_checkout; due to the |
913 | | * ability for users to manually muck things up between |
914 | | * direct editing of .git/info/sparse-checkout |
915 | | * running read-tree -m u HEAD or update-index --skip-worktree |
916 | | * direct toggling of config options |
917 | | * users might end up with an index with SKIP_WORKTREE bit set on |
918 | | * some files and not know how to undo it. So, here we just |
919 | | * forcibly return to a dense checkout regardless of initial state. |
920 | | */ |
921 | |
|
922 | 0 | setup_work_tree(); |
923 | 0 | argc = parse_options(argc, argv, prefix, |
924 | 0 | builtin_sparse_checkout_disable_options, |
925 | 0 | builtin_sparse_checkout_disable_usage, 0); |
926 | |
|
927 | 0 | repo_read_index(the_repository); |
928 | |
|
929 | 0 | memset(&pl, 0, sizeof(pl)); |
930 | 0 | hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0); |
931 | 0 | hashmap_init(&pl.parent_hashmap, pl_hashmap_cmp, NULL, 0); |
932 | 0 | pl.use_cone_patterns = 0; |
933 | 0 | core_apply_sparse_checkout = 1; |
934 | |
|
935 | 0 | add_pattern("/*", empty_base, 0, &pl, 0); |
936 | |
|
937 | 0 | prepare_repo_settings(the_repository); |
938 | 0 | the_repository->settings.sparse_index = 0; |
939 | |
|
940 | 0 | if (update_working_directory(&pl)) |
941 | 0 | die(_("error while refreshing working directory")); |
942 | | |
943 | 0 | clear_pattern_list(&pl); |
944 | 0 | return set_config(MODE_NO_PATTERNS); |
945 | 0 | } |
946 | | |
947 | | static char const * const builtin_sparse_checkout_check_rules_usage[] = { |
948 | | N_("git sparse-checkout check-rules [-z] [--skip-checks]" |
949 | | "[--[no-]cone] [--rules-file <file>]"), |
950 | | NULL |
951 | | }; |
952 | | |
953 | | static struct sparse_checkout_check_rules_opts { |
954 | | int cone_mode; |
955 | | int null_termination; |
956 | | char *rules_file; |
957 | | } check_rules_opts; |
958 | | |
959 | 0 | static int check_rules(struct pattern_list *pl, int null_terminated) { |
960 | 0 | struct strbuf line = STRBUF_INIT; |
961 | 0 | struct strbuf unquoted = STRBUF_INIT; |
962 | 0 | char *path; |
963 | 0 | int line_terminator = null_terminated ? 0 : '\n'; |
964 | 0 | strbuf_getline_fn getline_fn = null_terminated ? strbuf_getline_nul |
965 | 0 | : strbuf_getline; |
966 | 0 | the_repository->index->sparse_checkout_patterns = pl; |
967 | 0 | while (!getline_fn(&line, stdin)) { |
968 | 0 | path = line.buf; |
969 | 0 | if (!null_terminated && line.buf[0] == '"') { |
970 | 0 | strbuf_reset(&unquoted); |
971 | 0 | if (unquote_c_style(&unquoted, line.buf, NULL)) |
972 | 0 | die(_("unable to unquote C-style string '%s'"), |
973 | 0 | line.buf); |
974 | | |
975 | 0 | path = unquoted.buf; |
976 | 0 | } |
977 | | |
978 | 0 | if (path_in_sparse_checkout(path, the_repository->index)) |
979 | 0 | write_name_quoted(path, stdout, line_terminator); |
980 | 0 | } |
981 | 0 | strbuf_release(&line); |
982 | 0 | strbuf_release(&unquoted); |
983 | |
|
984 | 0 | return 0; |
985 | 0 | } |
986 | | |
987 | | static int sparse_checkout_check_rules(int argc, const char **argv, const char *prefix) |
988 | 0 | { |
989 | 0 | static struct option builtin_sparse_checkout_check_rules_options[] = { |
990 | 0 | OPT_BOOL('z', NULL, &check_rules_opts.null_termination, |
991 | 0 | N_("terminate input and output files by a NUL character")), |
992 | 0 | OPT_BOOL(0, "cone", &check_rules_opts.cone_mode, |
993 | 0 | N_("when used with --rules-file interpret patterns as cone mode patterns")), |
994 | 0 | OPT_FILENAME(0, "rules-file", &check_rules_opts.rules_file, |
995 | 0 | N_("use patterns in <file> instead of the current ones.")), |
996 | 0 | OPT_END(), |
997 | 0 | }; |
998 | |
|
999 | 0 | FILE *fp; |
1000 | 0 | int ret; |
1001 | 0 | struct pattern_list pl = {0}; |
1002 | 0 | char *sparse_filename; |
1003 | 0 | check_rules_opts.cone_mode = -1; |
1004 | |
|
1005 | 0 | argc = parse_options(argc, argv, prefix, |
1006 | 0 | builtin_sparse_checkout_check_rules_options, |
1007 | 0 | builtin_sparse_checkout_check_rules_usage, 0); |
1008 | |
|
1009 | 0 | if (check_rules_opts.rules_file && check_rules_opts.cone_mode < 0) |
1010 | 0 | check_rules_opts.cone_mode = 1; |
1011 | |
|
1012 | 0 | update_cone_mode(&check_rules_opts.cone_mode); |
1013 | 0 | pl.use_cone_patterns = core_sparse_checkout_cone; |
1014 | 0 | if (check_rules_opts.rules_file) { |
1015 | 0 | fp = xfopen(check_rules_opts.rules_file, "r"); |
1016 | 0 | add_patterns_from_input(&pl, argc, argv, fp); |
1017 | 0 | fclose(fp); |
1018 | 0 | } else { |
1019 | 0 | sparse_filename = get_sparse_checkout_filename(); |
1020 | 0 | if (add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, |
1021 | 0 | NULL, 0)) |
1022 | 0 | die(_("unable to load existing sparse-checkout patterns")); |
1023 | 0 | free(sparse_filename); |
1024 | 0 | } |
1025 | | |
1026 | 0 | ret = check_rules(&pl, check_rules_opts.null_termination); |
1027 | 0 | clear_pattern_list(&pl); |
1028 | 0 | free(check_rules_opts.rules_file); |
1029 | 0 | return ret; |
1030 | 0 | } |
1031 | | |
1032 | | int cmd_sparse_checkout(int argc, const char **argv, const char *prefix) |
1033 | 0 | { |
1034 | 0 | parse_opt_subcommand_fn *fn = NULL; |
1035 | 0 | struct option builtin_sparse_checkout_options[] = { |
1036 | 0 | OPT_SUBCOMMAND("list", &fn, sparse_checkout_list), |
1037 | 0 | OPT_SUBCOMMAND("init", &fn, sparse_checkout_init), |
1038 | 0 | OPT_SUBCOMMAND("set", &fn, sparse_checkout_set), |
1039 | 0 | OPT_SUBCOMMAND("add", &fn, sparse_checkout_add), |
1040 | 0 | OPT_SUBCOMMAND("reapply", &fn, sparse_checkout_reapply), |
1041 | 0 | OPT_SUBCOMMAND("disable", &fn, sparse_checkout_disable), |
1042 | 0 | OPT_SUBCOMMAND("check-rules", &fn, sparse_checkout_check_rules), |
1043 | 0 | OPT_END(), |
1044 | 0 | }; |
1045 | |
|
1046 | 0 | argc = parse_options(argc, argv, prefix, |
1047 | 0 | builtin_sparse_checkout_options, |
1048 | 0 | builtin_sparse_checkout_usage, 0); |
1049 | |
|
1050 | 0 | git_config(git_default_config, NULL); |
1051 | |
|
1052 | 0 | prepare_repo_settings(the_repository); |
1053 | 0 | the_repository->settings.command_requires_full_index = 0; |
1054 | |
|
1055 | 0 | return fn(argc, argv, prefix); |
1056 | 0 | } |