sparse-checkout: add '--stdin' option to set subcommand
[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
10 static char const * const builtin_sparse_checkout_usage[] = {
11         N_("git sparse-checkout (init|list|set) <options>"),
12         NULL
13 };
14
15 static char *get_sparse_checkout_filename(void)
16 {
17         return git_pathdup("info/sparse-checkout");
18 }
19
20 static void write_patterns_to_file(FILE *fp, struct pattern_list *pl)
21 {
22         int i;
23
24         for (i = 0; i < pl->nr; i++) {
25                 struct path_pattern *p = pl->patterns[i];
26
27                 if (p->flags & PATTERN_FLAG_NEGATIVE)
28                         fprintf(fp, "!");
29
30                 fprintf(fp, "%s", p->pattern);
31
32                 if (p->flags & PATTERN_FLAG_MUSTBEDIR)
33                         fprintf(fp, "/");
34
35                 fprintf(fp, "\n");
36         }
37 }
38
39 static int sparse_checkout_list(int argc, const char **argv)
40 {
41         struct pattern_list pl;
42         char *sparse_filename;
43         int res;
44
45         memset(&pl, 0, sizeof(pl));
46
47         sparse_filename = get_sparse_checkout_filename();
48         res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL);
49         free(sparse_filename);
50
51         if (res < 0) {
52                 warning(_("this worktree is not sparse (sparse-checkout file may not exist)"));
53                 return 0;
54         }
55
56         write_patterns_to_file(stdout, &pl);
57         clear_pattern_list(&pl);
58
59         return 0;
60 }
61
62 static int update_working_directory(void)
63 {
64         struct argv_array argv = ARGV_ARRAY_INIT;
65         int result = 0;
66         argv_array_pushl(&argv, "read-tree", "-m", "-u", "HEAD", NULL);
67
68         if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) {
69                 error(_("failed to update index with new sparse-checkout patterns"));
70                 result = 1;
71         }
72
73         argv_array_clear(&argv);
74         return result;
75 }
76
77 enum sparse_checkout_mode {
78         MODE_NO_PATTERNS = 0,
79         MODE_ALL_PATTERNS = 1,
80 };
81
82 static int set_config(enum sparse_checkout_mode mode)
83 {
84         const char *config_path;
85
86         if (git_config_set_gently("extensions.worktreeConfig", "true")) {
87                 error(_("failed to set extensions.worktreeConfig setting"));
88                 return 1;
89         }
90
91         config_path = git_path("config.worktree");
92         git_config_set_in_file_gently(config_path,
93                                       "core.sparseCheckout",
94                                       mode ? "true" : NULL);
95
96         return 0;
97 }
98
99 static int sparse_checkout_init(int argc, const char **argv)
100 {
101         struct pattern_list pl;
102         char *sparse_filename;
103         FILE *fp;
104         int res;
105         struct object_id oid;
106
107         if (set_config(MODE_ALL_PATTERNS))
108                 return 1;
109
110         memset(&pl, 0, sizeof(pl));
111
112         sparse_filename = get_sparse_checkout_filename();
113         res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL);
114
115         /* If we already have a sparse-checkout file, use it. */
116         if (res >= 0) {
117                 free(sparse_filename);
118                 goto reset_dir;
119         }
120
121         /* initial mode: all blobs at root */
122         fp = xfopen(sparse_filename, "w");
123         if (!fp)
124                 die(_("failed to open '%s'"), sparse_filename);
125
126         free(sparse_filename);
127         fprintf(fp, "/*\n!/*/\n");
128         fclose(fp);
129
130         if (get_oid("HEAD", &oid)) {
131                 /* assume we are in a fresh repo */
132                 return 0;
133         }
134
135 reset_dir:
136         return update_working_directory();
137 }
138
139 static int write_patterns_and_update(struct pattern_list *pl)
140 {
141         char *sparse_filename;
142         FILE *fp;
143
144         sparse_filename = get_sparse_checkout_filename();
145         fp = fopen(sparse_filename, "w");
146         write_patterns_to_file(fp, pl);
147         fclose(fp);
148         free(sparse_filename);
149
150         return update_working_directory();
151 }
152
153 static char const * const builtin_sparse_checkout_set_usage[] = {
154         N_("git sparse-checkout set (--stdin | <patterns>)"),
155         NULL
156 };
157
158 static struct sparse_checkout_set_opts {
159         int use_stdin;
160 } set_opts;
161
162 static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
163 {
164         static const char *empty_base = "";
165         int i;
166         struct pattern_list pl;
167         int result;
168         int changed_config = 0;
169
170         static struct option builtin_sparse_checkout_set_options[] = {
171                 OPT_BOOL(0, "stdin", &set_opts.use_stdin,
172                          N_("read patterns from standard in")),
173                 OPT_END(),
174         };
175
176         memset(&pl, 0, sizeof(pl));
177
178         argc = parse_options(argc, argv, prefix,
179                              builtin_sparse_checkout_set_options,
180                              builtin_sparse_checkout_set_usage,
181                              PARSE_OPT_KEEP_UNKNOWN);
182
183         if (set_opts.use_stdin) {
184                 struct strbuf line = STRBUF_INIT;
185
186                 while (!strbuf_getline(&line, stdin)) {
187                         char *buf = strbuf_detach(&line, NULL);
188                         add_pattern(buf, empty_base, 0, &pl, 0);
189                 }
190         } else {
191                 for (i = 0; i < argc; i++)
192                         add_pattern(argv[i], empty_base, 0, &pl, 0);
193         }
194
195         if (!core_apply_sparse_checkout) {
196                 set_config(MODE_ALL_PATTERNS);
197                 core_apply_sparse_checkout = 1;
198                 changed_config = 1;
199         }
200
201         result = write_patterns_and_update(&pl);
202
203         if (result && changed_config)
204                 set_config(MODE_NO_PATTERNS);
205
206         clear_pattern_list(&pl);
207         return result;
208 }
209
210 int cmd_sparse_checkout(int argc, const char **argv, const char *prefix)
211 {
212         static struct option builtin_sparse_checkout_options[] = {
213                 OPT_END(),
214         };
215
216         if (argc == 2 && !strcmp(argv[1], "-h"))
217                 usage_with_options(builtin_sparse_checkout_usage,
218                                    builtin_sparse_checkout_options);
219
220         argc = parse_options(argc, argv, prefix,
221                              builtin_sparse_checkout_options,
222                              builtin_sparse_checkout_usage,
223                              PARSE_OPT_STOP_AT_NON_OPTION);
224
225         git_config(git_default_config, NULL);
226
227         if (argc > 0) {
228                 if (!strcmp(argv[0], "list"))
229                         return sparse_checkout_list(argc, argv);
230                 if (!strcmp(argv[0], "init"))
231                         return sparse_checkout_init(argc, argv);
232                 if (!strcmp(argv[0], "set"))
233                         return sparse_checkout_set(argc, argv, prefix);
234         }
235
236         usage_with_options(builtin_sparse_checkout_usage,
237                            builtin_sparse_checkout_options);
238 }