/src/git/builtin/ls-tree.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * GIT - The information manager from hell |
3 | | * |
4 | | * Copyright (C) Linus Torvalds, 2005 |
5 | | */ |
6 | | #include "builtin.h" |
7 | | #include "config.h" |
8 | | #include "gettext.h" |
9 | | #include "hex.h" |
10 | | #include "object-name.h" |
11 | | #include "object-store-ll.h" |
12 | | #include "tree.h" |
13 | | #include "path.h" |
14 | | #include "quote.h" |
15 | | #include "parse-options.h" |
16 | | #include "pathspec.h" |
17 | | |
18 | | static const char * const ls_tree_usage[] = { |
19 | | N_("git ls-tree [<options>] <tree-ish> [<path>...]"), |
20 | | NULL |
21 | | }; |
22 | | |
23 | | static void expand_objectsize(struct strbuf *line, const struct object_id *oid, |
24 | | const enum object_type type, unsigned int padded) |
25 | 0 | { |
26 | 0 | if (type == OBJ_BLOB) { |
27 | 0 | unsigned long size; |
28 | 0 | if (oid_object_info(the_repository, oid, &size) < 0) |
29 | 0 | die(_("could not get object info about '%s'"), |
30 | 0 | oid_to_hex(oid)); |
31 | 0 | if (padded) |
32 | 0 | strbuf_addf(line, "%7"PRIuMAX, (uintmax_t)size); |
33 | 0 | else |
34 | 0 | strbuf_addf(line, "%"PRIuMAX, (uintmax_t)size); |
35 | 0 | } else if (padded) { |
36 | 0 | strbuf_addf(line, "%7s", "-"); |
37 | 0 | } else { |
38 | 0 | strbuf_addstr(line, "-"); |
39 | 0 | } |
40 | 0 | } |
41 | | |
42 | | struct ls_tree_options { |
43 | | unsigned null_termination:1; |
44 | | int abbrev; |
45 | | enum ls_tree_path_options { |
46 | | LS_RECURSIVE = 1 << 0, |
47 | | LS_TREE_ONLY = 1 << 1, |
48 | | LS_SHOW_TREES = 1 << 2, |
49 | | } ls_options; |
50 | | struct pathspec pathspec; |
51 | | const char *prefix; |
52 | | const char *format; |
53 | | }; |
54 | | |
55 | | static int show_recursive(struct ls_tree_options *options, const char *base, |
56 | | size_t baselen, const char *pathname) |
57 | 0 | { |
58 | 0 | int i; |
59 | |
|
60 | 0 | if (options->ls_options & LS_RECURSIVE) |
61 | 0 | return 1; |
62 | | |
63 | 0 | if (!options->pathspec.nr) |
64 | 0 | return 0; |
65 | | |
66 | 0 | for (i = 0; i < options->pathspec.nr; i++) { |
67 | 0 | const char *spec = options->pathspec.items[i].match; |
68 | 0 | size_t len, speclen; |
69 | |
|
70 | 0 | if (strncmp(base, spec, baselen)) |
71 | 0 | continue; |
72 | 0 | len = strlen(pathname); |
73 | 0 | spec += baselen; |
74 | 0 | speclen = strlen(spec); |
75 | 0 | if (speclen <= len) |
76 | 0 | continue; |
77 | 0 | if (spec[len] && spec[len] != '/') |
78 | 0 | continue; |
79 | 0 | if (memcmp(pathname, spec, len)) |
80 | 0 | continue; |
81 | 0 | return 1; |
82 | 0 | } |
83 | 0 | return 0; |
84 | 0 | } |
85 | | |
86 | | static int show_tree_fmt(const struct object_id *oid, struct strbuf *base, |
87 | | const char *pathname, unsigned mode, void *context) |
88 | 0 | { |
89 | 0 | struct ls_tree_options *options = context; |
90 | 0 | int recurse = 0; |
91 | 0 | struct strbuf sb = STRBUF_INIT; |
92 | 0 | enum object_type type = object_type(mode); |
93 | 0 | const char *format = options->format; |
94 | |
|
95 | 0 | if (type == OBJ_TREE && show_recursive(options, base->buf, base->len, pathname)) |
96 | 0 | recurse = READ_TREE_RECURSIVE; |
97 | 0 | if (type == OBJ_TREE && recurse && !(options->ls_options & LS_SHOW_TREES)) |
98 | 0 | return recurse; |
99 | 0 | if (type == OBJ_BLOB && (options->ls_options & LS_TREE_ONLY)) |
100 | 0 | return 0; |
101 | | |
102 | 0 | while (strbuf_expand_step(&sb, &format)) { |
103 | 0 | size_t len; |
104 | |
|
105 | 0 | if (skip_prefix(format, "%", &format)) |
106 | 0 | strbuf_addch(&sb, '%'); |
107 | 0 | else if ((len = strbuf_expand_literal(&sb, format))) |
108 | 0 | format += len; |
109 | 0 | else if (skip_prefix(format, "(objectmode)", &format)) |
110 | 0 | strbuf_addf(&sb, "%06o", mode); |
111 | 0 | else if (skip_prefix(format, "(objecttype)", &format)) |
112 | 0 | strbuf_addstr(&sb, type_name(type)); |
113 | 0 | else if (skip_prefix(format, "(objectsize:padded)", &format)) |
114 | 0 | expand_objectsize(&sb, oid, type, 1); |
115 | 0 | else if (skip_prefix(format, "(objectsize)", &format)) |
116 | 0 | expand_objectsize(&sb, oid, type, 0); |
117 | 0 | else if (skip_prefix(format, "(objectname)", &format)) |
118 | 0 | strbuf_add_unique_abbrev(&sb, oid, options->abbrev); |
119 | 0 | else if (skip_prefix(format, "(path)", &format)) { |
120 | 0 | const char *name; |
121 | 0 | const char *prefix = options->prefix; |
122 | 0 | struct strbuf sbuf = STRBUF_INIT; |
123 | 0 | size_t baselen = base->len; |
124 | |
|
125 | 0 | strbuf_addstr(base, pathname); |
126 | 0 | name = relative_path(base->buf, prefix, &sbuf); |
127 | 0 | quote_c_style(name, &sb, NULL, 0); |
128 | 0 | strbuf_setlen(base, baselen); |
129 | 0 | strbuf_release(&sbuf); |
130 | 0 | } else |
131 | 0 | strbuf_expand_bad_format(format, "ls-tree"); |
132 | 0 | } |
133 | 0 | strbuf_addch(&sb, options->null_termination ? '\0' : '\n'); |
134 | 0 | fwrite(sb.buf, sb.len, 1, stdout); |
135 | 0 | strbuf_release(&sb); |
136 | 0 | return recurse; |
137 | 0 | } |
138 | | |
139 | | static int show_tree_common(struct ls_tree_options *options, int *recurse, |
140 | | struct strbuf *base, const char *pathname, |
141 | | enum object_type type) |
142 | 0 | { |
143 | 0 | int ret = -1; |
144 | 0 | *recurse = 0; |
145 | |
|
146 | 0 | if (type == OBJ_BLOB) { |
147 | 0 | if (options->ls_options & LS_TREE_ONLY) |
148 | 0 | ret = 0; |
149 | 0 | } else if (type == OBJ_TREE && |
150 | 0 | show_recursive(options, base->buf, base->len, pathname)) { |
151 | 0 | *recurse = READ_TREE_RECURSIVE; |
152 | 0 | if (!(options->ls_options & LS_SHOW_TREES)) |
153 | 0 | ret = *recurse; |
154 | 0 | } |
155 | |
|
156 | 0 | return ret; |
157 | 0 | } |
158 | | |
159 | | static void show_tree_common_default_long(struct ls_tree_options *options, |
160 | | struct strbuf *base, |
161 | | const char *pathname, |
162 | | const size_t baselen) |
163 | 0 | { |
164 | 0 | const char *prefix = options->prefix; |
165 | |
|
166 | 0 | strbuf_addstr(base, pathname); |
167 | |
|
168 | 0 | if (options->null_termination) { |
169 | 0 | struct strbuf sb = STRBUF_INIT; |
170 | 0 | const char *name = relative_path(base->buf, prefix, &sb); |
171 | |
|
172 | 0 | fputs(name, stdout); |
173 | 0 | fputc('\0', stdout); |
174 | |
|
175 | 0 | strbuf_release(&sb); |
176 | 0 | } else { |
177 | 0 | write_name_quoted_relative(base->buf, prefix, stdout, '\n'); |
178 | 0 | } |
179 | |
|
180 | 0 | strbuf_setlen(base, baselen); |
181 | 0 | } |
182 | | |
183 | | static int show_tree_default(const struct object_id *oid, struct strbuf *base, |
184 | | const char *pathname, unsigned mode, |
185 | | void *context) |
186 | 0 | { |
187 | 0 | struct ls_tree_options *options = context; |
188 | 0 | int early; |
189 | 0 | int recurse; |
190 | 0 | enum object_type type = object_type(mode); |
191 | |
|
192 | 0 | early = show_tree_common(options, &recurse, base, pathname, type); |
193 | 0 | if (early >= 0) |
194 | 0 | return early; |
195 | | |
196 | 0 | printf("%06o %s %s\t", mode, type_name(object_type(mode)), |
197 | 0 | repo_find_unique_abbrev(the_repository, oid, options->abbrev)); |
198 | 0 | show_tree_common_default_long(options, base, pathname, base->len); |
199 | 0 | return recurse; |
200 | 0 | } |
201 | | |
202 | | static int show_tree_long(const struct object_id *oid, struct strbuf *base, |
203 | | const char *pathname, unsigned mode, |
204 | | void *context) |
205 | 0 | { |
206 | 0 | struct ls_tree_options *options = context; |
207 | 0 | int early; |
208 | 0 | int recurse; |
209 | 0 | char size_text[24]; |
210 | 0 | enum object_type type = object_type(mode); |
211 | |
|
212 | 0 | early = show_tree_common(options, &recurse, base, pathname, type); |
213 | 0 | if (early >= 0) |
214 | 0 | return early; |
215 | | |
216 | 0 | if (type == OBJ_BLOB) { |
217 | 0 | unsigned long size; |
218 | 0 | if (oid_object_info(the_repository, oid, &size) == OBJ_BAD) |
219 | 0 | xsnprintf(size_text, sizeof(size_text), "BAD"); |
220 | 0 | else |
221 | 0 | xsnprintf(size_text, sizeof(size_text), |
222 | 0 | "%" PRIuMAX, (uintmax_t)size); |
223 | 0 | } else { |
224 | 0 | xsnprintf(size_text, sizeof(size_text), "-"); |
225 | 0 | } |
226 | |
|
227 | 0 | printf("%06o %s %s %7s\t", mode, type_name(type), |
228 | 0 | repo_find_unique_abbrev(the_repository, oid, options->abbrev), |
229 | 0 | size_text); |
230 | 0 | show_tree_common_default_long(options, base, pathname, base->len); |
231 | 0 | return recurse; |
232 | 0 | } |
233 | | |
234 | | static int show_tree_name_only(const struct object_id *oid UNUSED, |
235 | | struct strbuf *base, |
236 | | const char *pathname, unsigned mode, |
237 | | void *context) |
238 | 0 | { |
239 | 0 | struct ls_tree_options *options = context; |
240 | 0 | int early; |
241 | 0 | int recurse; |
242 | 0 | const size_t baselen = base->len; |
243 | 0 | enum object_type type = object_type(mode); |
244 | 0 | const char *prefix; |
245 | |
|
246 | 0 | early = show_tree_common(options, &recurse, base, pathname, type); |
247 | 0 | if (early >= 0) |
248 | 0 | return early; |
249 | | |
250 | 0 | prefix = options->prefix; |
251 | 0 | strbuf_addstr(base, pathname); |
252 | 0 | if (options->null_termination) { |
253 | 0 | struct strbuf sb = STRBUF_INIT; |
254 | 0 | const char *name = relative_path(base->buf, prefix, &sb); |
255 | |
|
256 | 0 | fputs(name, stdout); |
257 | 0 | fputc('\0', stdout); |
258 | |
|
259 | 0 | strbuf_release(&sb); |
260 | 0 | } else { |
261 | 0 | write_name_quoted_relative(base->buf, prefix, stdout, '\n'); |
262 | 0 | } |
263 | 0 | strbuf_setlen(base, baselen); |
264 | 0 | return recurse; |
265 | 0 | } |
266 | | |
267 | | static int show_tree_object(const struct object_id *oid, struct strbuf *base, |
268 | | const char *pathname, unsigned mode, |
269 | | void *context) |
270 | 0 | { |
271 | 0 | struct ls_tree_options *options = context; |
272 | 0 | int early; |
273 | 0 | int recurse; |
274 | 0 | enum object_type type = object_type(mode); |
275 | 0 | const char *str; |
276 | |
|
277 | 0 | early = show_tree_common(options, &recurse, base, pathname, type); |
278 | 0 | if (early >= 0) |
279 | 0 | return early; |
280 | | |
281 | 0 | str = repo_find_unique_abbrev(the_repository, oid, options->abbrev); |
282 | 0 | if (options->null_termination) { |
283 | 0 | fputs(str, stdout); |
284 | 0 | fputc('\0', stdout); |
285 | 0 | } else { |
286 | 0 | puts(str); |
287 | 0 | } |
288 | 0 | return recurse; |
289 | 0 | } |
290 | | |
291 | | enum ls_tree_cmdmode { |
292 | | MODE_DEFAULT = 0, |
293 | | MODE_LONG, |
294 | | MODE_NAME_ONLY, |
295 | | MODE_NAME_STATUS, |
296 | | MODE_OBJECT_ONLY, |
297 | | }; |
298 | | |
299 | | struct ls_tree_cmdmode_to_fmt { |
300 | | enum ls_tree_cmdmode mode; |
301 | | const char *const fmt; |
302 | | read_tree_fn_t fn; |
303 | | }; |
304 | | |
305 | | static struct ls_tree_cmdmode_to_fmt ls_tree_cmdmode_format[] = { |
306 | | { |
307 | | .mode = MODE_DEFAULT, |
308 | | .fmt = "%(objectmode) %(objecttype) %(objectname)%x09%(path)", |
309 | | .fn = show_tree_default, |
310 | | }, |
311 | | { |
312 | | .mode = MODE_LONG, |
313 | | .fmt = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)", |
314 | | .fn = show_tree_long, |
315 | | }, |
316 | | { |
317 | | .mode = MODE_NAME_ONLY, /* And MODE_NAME_STATUS */ |
318 | | .fmt = "%(path)", |
319 | | .fn = show_tree_name_only, |
320 | | }, |
321 | | { |
322 | | .mode = MODE_OBJECT_ONLY, |
323 | | .fmt = "%(objectname)", |
324 | | .fn = show_tree_object |
325 | | }, |
326 | | { |
327 | | /* fallback */ |
328 | | .fn = show_tree_default, |
329 | | }, |
330 | | }; |
331 | | |
332 | | int cmd_ls_tree(int argc, const char **argv, const char *prefix) |
333 | 0 | { |
334 | 0 | struct object_id oid; |
335 | 0 | struct tree *tree; |
336 | 0 | int i, full_tree = 0; |
337 | 0 | int full_name = !prefix || !*prefix; |
338 | 0 | read_tree_fn_t fn = NULL; |
339 | 0 | enum ls_tree_cmdmode cmdmode = MODE_DEFAULT; |
340 | 0 | int null_termination = 0; |
341 | 0 | struct ls_tree_options options = { 0 }; |
342 | 0 | const struct option ls_tree_options[] = { |
343 | 0 | OPT_BIT('d', NULL, &options.ls_options, N_("only show trees"), |
344 | 0 | LS_TREE_ONLY), |
345 | 0 | OPT_BIT('r', NULL, &options.ls_options, N_("recurse into subtrees"), |
346 | 0 | LS_RECURSIVE), |
347 | 0 | OPT_BIT('t', NULL, &options.ls_options, N_("show trees when recursing"), |
348 | 0 | LS_SHOW_TREES), |
349 | 0 | OPT_BOOL('z', NULL, &null_termination, |
350 | 0 | N_("terminate entries with NUL byte")), |
351 | 0 | OPT_CMDMODE('l', "long", &cmdmode, N_("include object size"), |
352 | 0 | MODE_LONG), |
353 | 0 | OPT_CMDMODE(0, "name-only", &cmdmode, N_("list only filenames"), |
354 | 0 | MODE_NAME_ONLY), |
355 | 0 | OPT_CMDMODE(0, "name-status", &cmdmode, N_("list only filenames"), |
356 | 0 | MODE_NAME_STATUS), |
357 | 0 | OPT_CMDMODE(0, "object-only", &cmdmode, N_("list only objects"), |
358 | 0 | MODE_OBJECT_ONLY), |
359 | 0 | OPT_BOOL(0, "full-name", &full_name, N_("use full path names")), |
360 | 0 | OPT_BOOL(0, "full-tree", &full_tree, |
361 | 0 | N_("list entire tree; not just current directory " |
362 | 0 | "(implies --full-name)")), |
363 | 0 | OPT_STRING_F(0, "format", &options.format, N_("format"), |
364 | 0 | N_("format to use for the output"), |
365 | 0 | PARSE_OPT_NONEG), |
366 | 0 | OPT__ABBREV(&options.abbrev), |
367 | 0 | OPT_END() |
368 | 0 | }; |
369 | 0 | struct ls_tree_cmdmode_to_fmt *m2f = ls_tree_cmdmode_format; |
370 | 0 | struct object_context obj_context = {0}; |
371 | 0 | int ret; |
372 | |
|
373 | 0 | git_config(git_default_config, NULL); |
374 | |
|
375 | 0 | argc = parse_options(argc, argv, prefix, ls_tree_options, |
376 | 0 | ls_tree_usage, 0); |
377 | 0 | options.null_termination = null_termination; |
378 | |
|
379 | 0 | if (full_tree) |
380 | 0 | prefix = NULL; |
381 | 0 | options.prefix = full_name ? NULL : prefix; |
382 | | |
383 | | /* |
384 | | * We wanted to detect conflicts between --name-only and |
385 | | * --name-status, but once we're done with that subsequent |
386 | | * code should only need to check the primary name. |
387 | | */ |
388 | 0 | if (cmdmode == MODE_NAME_STATUS) |
389 | 0 | cmdmode = MODE_NAME_ONLY; |
390 | | |
391 | | /* -d -r should imply -t, but -d by itself should not have to. */ |
392 | 0 | if ( (LS_TREE_ONLY|LS_RECURSIVE) == |
393 | 0 | ((LS_TREE_ONLY|LS_RECURSIVE) & options.ls_options)) |
394 | 0 | options.ls_options |= LS_SHOW_TREES; |
395 | |
|
396 | 0 | if (options.format && cmdmode) |
397 | 0 | usage_msg_opt( |
398 | 0 | _("--format can't be combined with other format-altering options"), |
399 | 0 | ls_tree_usage, ls_tree_options); |
400 | 0 | if (argc < 1) |
401 | 0 | usage_with_options(ls_tree_usage, ls_tree_options); |
402 | 0 | if (get_oid_with_context(the_repository, argv[0], |
403 | 0 | GET_OID_HASH_ANY, &oid, |
404 | 0 | &obj_context)) |
405 | 0 | die("Not a valid object name %s", argv[0]); |
406 | | |
407 | | /* |
408 | | * show_recursive() rolls its own matching code and is |
409 | | * generally ignorant of 'struct pathspec'. The magic mask |
410 | | * cannot be lifted until it is converted to use |
411 | | * match_pathspec() or tree_entry_interesting() |
412 | | */ |
413 | 0 | parse_pathspec(&options.pathspec, PATHSPEC_ALL_MAGIC & |
414 | 0 | ~(PATHSPEC_FROMTOP | PATHSPEC_LITERAL), |
415 | 0 | PATHSPEC_PREFER_CWD, |
416 | 0 | prefix, argv + 1); |
417 | 0 | for (i = 0; i < options.pathspec.nr; i++) |
418 | 0 | options.pathspec.items[i].nowildcard_len = options.pathspec.items[i].len; |
419 | 0 | options.pathspec.has_wildcard = 0; |
420 | 0 | tree = parse_tree_indirect(&oid); |
421 | 0 | if (!tree) |
422 | 0 | die("not a tree object"); |
423 | | /* |
424 | | * The generic show_tree_fmt() is slower than show_tree(), so |
425 | | * take the fast path if possible. |
426 | | */ |
427 | 0 | while (m2f) { |
428 | 0 | if (!m2f->fmt) { |
429 | 0 | fn = options.format ? show_tree_fmt : show_tree_default; |
430 | 0 | } else if (options.format && !strcmp(options.format, m2f->fmt)) { |
431 | 0 | cmdmode = m2f->mode; |
432 | 0 | fn = m2f->fn; |
433 | 0 | } else if (!options.format && cmdmode == m2f->mode) { |
434 | 0 | fn = m2f->fn; |
435 | 0 | } else { |
436 | 0 | m2f++; |
437 | 0 | continue; |
438 | 0 | } |
439 | 0 | break; |
440 | 0 | } |
441 | |
|
442 | 0 | ret = !!read_tree(the_repository, tree, &options.pathspec, fn, &options); |
443 | 0 | clear_pathspec(&options.pathspec); |
444 | 0 | object_context_release(&obj_context); |
445 | 0 | return ret; |
446 | 0 | } |