diff options
Diffstat (limited to 'builtin/ls-tree.c')
-rw-r--r-- | builtin/ls-tree.c | 452 |
1 files changed, 452 insertions, 0 deletions
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c new file mode 100644 index 0000000..209d2dc --- /dev/null +++ b/builtin/ls-tree.c @@ -0,0 +1,452 @@ +/* + * GIT - The information manager from hell + * + * Copyright (C) Linus Torvalds, 2005 + */ +#include "builtin.h" +#include "config.h" +#include "gettext.h" +#include "hex.h" +#include "object-name.h" +#include "object-store-ll.h" +#include "blob.h" +#include "tree.h" +#include "commit.h" +#include "path.h" +#include "quote.h" +#include "parse-options.h" +#include "pathspec.h" + +static const char * const ls_tree_usage[] = { + N_("git ls-tree [<options>] <tree-ish> [<path>...]"), + NULL +}; + +static void expand_objectsize(struct strbuf *line, const struct object_id *oid, + const enum object_type type, unsigned int padded) +{ + if (type == OBJ_BLOB) { + unsigned long size; + if (oid_object_info(the_repository, oid, &size) < 0) + die(_("could not get object info about '%s'"), + oid_to_hex(oid)); + if (padded) + strbuf_addf(line, "%7"PRIuMAX, (uintmax_t)size); + else + strbuf_addf(line, "%"PRIuMAX, (uintmax_t)size); + } else if (padded) { + strbuf_addf(line, "%7s", "-"); + } else { + strbuf_addstr(line, "-"); + } +} + +struct ls_tree_options { + unsigned null_termination:1; + int abbrev; + enum ls_tree_path_options { + LS_RECURSIVE = 1 << 0, + LS_TREE_ONLY = 1 << 1, + LS_SHOW_TREES = 1 << 2, + } ls_options; + struct pathspec pathspec; + const char *prefix; + const char *format; +}; + +static int show_recursive(struct ls_tree_options *options, const char *base, + size_t baselen, const char *pathname) +{ + int i; + + if (options->ls_options & LS_RECURSIVE) + return 1; + + if (!options->pathspec.nr) + return 0; + + for (i = 0; i < options->pathspec.nr; i++) { + const char *spec = options->pathspec.items[i].match; + size_t len, speclen; + + if (strncmp(base, spec, baselen)) + continue; + len = strlen(pathname); + spec += baselen; + speclen = strlen(spec); + if (speclen <= len) + continue; + if (spec[len] && spec[len] != '/') + continue; + if (memcmp(pathname, spec, len)) + continue; + return 1; + } + return 0; +} + +static int show_tree_fmt(const struct object_id *oid, struct strbuf *base, + const char *pathname, unsigned mode, void *context) +{ + struct ls_tree_options *options = context; + int recurse = 0; + struct strbuf sb = STRBUF_INIT; + enum object_type type = object_type(mode); + const char *format = options->format; + + if (type == OBJ_TREE && show_recursive(options, base->buf, base->len, pathname)) + recurse = READ_TREE_RECURSIVE; + if (type == OBJ_TREE && recurse && !(options->ls_options & LS_SHOW_TREES)) + return recurse; + if (type == OBJ_BLOB && (options->ls_options & LS_TREE_ONLY)) + return 0; + + while (strbuf_expand_step(&sb, &format)) { + const char *end; + size_t len; + + if (skip_prefix(format, "%", &format)) + strbuf_addch(&sb, '%'); + else if ((len = strbuf_expand_literal(&sb, format))) + format += len; + else if (*format != '(') + die(_("bad ls-tree format: element '%s' " + "does not start with '('"), format); + else if (!(end = strchr(format + 1, ')'))) + die(_("bad ls-tree format: element '%s' " + "does not end in ')'"), format); + else if (skip_prefix(format, "(objectmode)", &format)) + strbuf_addf(&sb, "%06o", mode); + else if (skip_prefix(format, "(objecttype)", &format)) + strbuf_addstr(&sb, type_name(type)); + else if (skip_prefix(format, "(objectsize:padded)", &format)) + expand_objectsize(&sb, oid, type, 1); + else if (skip_prefix(format, "(objectsize)", &format)) + expand_objectsize(&sb, oid, type, 0); + else if (skip_prefix(format, "(objectname)", &format)) + strbuf_add_unique_abbrev(&sb, oid, options->abbrev); + else if (skip_prefix(format, "(path)", &format)) { + const char *name; + const char *prefix = options->prefix; + struct strbuf sbuf = STRBUF_INIT; + size_t baselen = base->len; + + strbuf_addstr(base, pathname); + name = relative_path(base->buf, prefix, &sbuf); + quote_c_style(name, &sb, NULL, 0); + strbuf_setlen(base, baselen); + strbuf_release(&sbuf); + } else + die(_("bad ls-tree format: %%%.*s"), + (int)(end - format + 1), format); + } + strbuf_addch(&sb, options->null_termination ? '\0' : '\n'); + fwrite(sb.buf, sb.len, 1, stdout); + strbuf_release(&sb); + return recurse; +} + +static int show_tree_common(struct ls_tree_options *options, int *recurse, + struct strbuf *base, const char *pathname, + enum object_type type) +{ + int ret = -1; + *recurse = 0; + + if (type == OBJ_BLOB) { + if (options->ls_options & LS_TREE_ONLY) + ret = 0; + } else if (type == OBJ_TREE && + show_recursive(options, base->buf, base->len, pathname)) { + *recurse = READ_TREE_RECURSIVE; + if (!(options->ls_options & LS_SHOW_TREES)) + ret = *recurse; + } + + return ret; +} + +static void show_tree_common_default_long(struct ls_tree_options *options, + struct strbuf *base, + const char *pathname, + const size_t baselen) +{ + const char *prefix = options->prefix; + + strbuf_addstr(base, pathname); + + if (options->null_termination) { + struct strbuf sb = STRBUF_INIT; + const char *name = relative_path(base->buf, prefix, &sb); + + fputs(name, stdout); + fputc('\0', stdout); + + strbuf_release(&sb); + } else { + write_name_quoted_relative(base->buf, prefix, stdout, '\n'); + } + + strbuf_setlen(base, baselen); +} + +static int show_tree_default(const struct object_id *oid, struct strbuf *base, + const char *pathname, unsigned mode, + void *context) +{ + struct ls_tree_options *options = context; + int early; + int recurse; + enum object_type type = object_type(mode); + + early = show_tree_common(options, &recurse, base, pathname, type); + if (early >= 0) + return early; + + printf("%06o %s %s\t", mode, type_name(object_type(mode)), + repo_find_unique_abbrev(the_repository, oid, options->abbrev)); + show_tree_common_default_long(options, base, pathname, base->len); + return recurse; +} + +static int show_tree_long(const struct object_id *oid, struct strbuf *base, + const char *pathname, unsigned mode, + void *context) +{ + struct ls_tree_options *options = context; + int early; + int recurse; + char size_text[24]; + enum object_type type = object_type(mode); + + early = show_tree_common(options, &recurse, base, pathname, type); + if (early >= 0) + return early; + + if (type == OBJ_BLOB) { + unsigned long size; + if (oid_object_info(the_repository, oid, &size) == OBJ_BAD) + xsnprintf(size_text, sizeof(size_text), "BAD"); + else + xsnprintf(size_text, sizeof(size_text), + "%" PRIuMAX, (uintmax_t)size); + } else { + xsnprintf(size_text, sizeof(size_text), "-"); + } + + printf("%06o %s %s %7s\t", mode, type_name(type), + repo_find_unique_abbrev(the_repository, oid, options->abbrev), + size_text); + show_tree_common_default_long(options, base, pathname, base->len); + return recurse; +} + +static int show_tree_name_only(const struct object_id *oid UNUSED, + struct strbuf *base, + const char *pathname, unsigned mode, + void *context) +{ + struct ls_tree_options *options = context; + int early; + int recurse; + const size_t baselen = base->len; + enum object_type type = object_type(mode); + const char *prefix; + + early = show_tree_common(options, &recurse, base, pathname, type); + if (early >= 0) + return early; + + prefix = options->prefix; + strbuf_addstr(base, pathname); + if (options->null_termination) { + struct strbuf sb = STRBUF_INIT; + const char *name = relative_path(base->buf, prefix, &sb); + + fputs(name, stdout); + fputc('\0', stdout); + + strbuf_release(&sb); + } else { + write_name_quoted_relative(base->buf, prefix, stdout, '\n'); + } + strbuf_setlen(base, baselen); + return recurse; +} + +static int show_tree_object(const struct object_id *oid, struct strbuf *base, + const char *pathname, unsigned mode, + void *context) +{ + struct ls_tree_options *options = context; + int early; + int recurse; + enum object_type type = object_type(mode); + const char *str; + + early = show_tree_common(options, &recurse, base, pathname, type); + if (early >= 0) + return early; + + str = repo_find_unique_abbrev(the_repository, oid, options->abbrev); + if (options->null_termination) { + fputs(str, stdout); + fputc('\0', stdout); + } else { + puts(str); + } + return recurse; +} + +enum ls_tree_cmdmode { + MODE_DEFAULT = 0, + MODE_LONG, + MODE_NAME_ONLY, + MODE_NAME_STATUS, + MODE_OBJECT_ONLY, +}; + +struct ls_tree_cmdmode_to_fmt { + enum ls_tree_cmdmode mode; + const char *const fmt; + read_tree_fn_t fn; +}; + +static struct ls_tree_cmdmode_to_fmt ls_tree_cmdmode_format[] = { + { + .mode = MODE_DEFAULT, + .fmt = "%(objectmode) %(objecttype) %(objectname)%x09%(path)", + .fn = show_tree_default, + }, + { + .mode = MODE_LONG, + .fmt = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)", + .fn = show_tree_long, + }, + { + .mode = MODE_NAME_ONLY, /* And MODE_NAME_STATUS */ + .fmt = "%(path)", + .fn = show_tree_name_only, + }, + { + .mode = MODE_OBJECT_ONLY, + .fmt = "%(objectname)", + .fn = show_tree_object + }, + { + /* fallback */ + .fn = show_tree_default, + }, +}; + +int cmd_ls_tree(int argc, const char **argv, const char *prefix) +{ + struct object_id oid; + struct tree *tree; + int i, full_tree = 0; + int full_name = !prefix || !*prefix; + read_tree_fn_t fn = NULL; + enum ls_tree_cmdmode cmdmode = MODE_DEFAULT; + int null_termination = 0; + struct ls_tree_options options = { 0 }; + const struct option ls_tree_options[] = { + OPT_BIT('d', NULL, &options.ls_options, N_("only show trees"), + LS_TREE_ONLY), + OPT_BIT('r', NULL, &options.ls_options, N_("recurse into subtrees"), + LS_RECURSIVE), + OPT_BIT('t', NULL, &options.ls_options, N_("show trees when recursing"), + LS_SHOW_TREES), + OPT_BOOL('z', NULL, &null_termination, + N_("terminate entries with NUL byte")), + OPT_CMDMODE('l', "long", &cmdmode, N_("include object size"), + MODE_LONG), + OPT_CMDMODE(0, "name-only", &cmdmode, N_("list only filenames"), + MODE_NAME_ONLY), + OPT_CMDMODE(0, "name-status", &cmdmode, N_("list only filenames"), + MODE_NAME_STATUS), + OPT_CMDMODE(0, "object-only", &cmdmode, N_("list only objects"), + MODE_OBJECT_ONLY), + OPT_BOOL(0, "full-name", &full_name, N_("use full path names")), + OPT_BOOL(0, "full-tree", &full_tree, + N_("list entire tree; not just current directory " + "(implies --full-name)")), + OPT_STRING_F(0, "format", &options.format, N_("format"), + N_("format to use for the output"), + PARSE_OPT_NONEG), + OPT__ABBREV(&options.abbrev), + OPT_END() + }; + struct ls_tree_cmdmode_to_fmt *m2f = ls_tree_cmdmode_format; + int ret; + + git_config(git_default_config, NULL); + + argc = parse_options(argc, argv, prefix, ls_tree_options, + ls_tree_usage, 0); + options.null_termination = null_termination; + + if (full_tree) + prefix = NULL; + options.prefix = full_name ? NULL : prefix; + + /* + * We wanted to detect conflicts between --name-only and + * --name-status, but once we're done with that subsequent + * code should only need to check the primary name. + */ + if (cmdmode == MODE_NAME_STATUS) + cmdmode = MODE_NAME_ONLY; + + /* -d -r should imply -t, but -d by itself should not have to. */ + if ( (LS_TREE_ONLY|LS_RECURSIVE) == + ((LS_TREE_ONLY|LS_RECURSIVE) & options.ls_options)) + options.ls_options |= LS_SHOW_TREES; + + if (options.format && cmdmode) + usage_msg_opt( + _("--format can't be combined with other format-altering options"), + ls_tree_usage, ls_tree_options); + if (argc < 1) + usage_with_options(ls_tree_usage, ls_tree_options); + if (repo_get_oid(the_repository, argv[0], &oid)) + die("Not a valid object name %s", argv[0]); + + /* + * show_recursive() rolls its own matching code and is + * generally ignorant of 'struct pathspec'. The magic mask + * cannot be lifted until it is converted to use + * match_pathspec() or tree_entry_interesting() + */ + parse_pathspec(&options.pathspec, PATHSPEC_ALL_MAGIC & + ~(PATHSPEC_FROMTOP | PATHSPEC_LITERAL), + PATHSPEC_PREFER_CWD, + prefix, argv + 1); + for (i = 0; i < options.pathspec.nr; i++) + options.pathspec.items[i].nowildcard_len = options.pathspec.items[i].len; + options.pathspec.has_wildcard = 0; + tree = parse_tree_indirect(&oid); + if (!tree) + die("not a tree object"); + /* + * The generic show_tree_fmt() is slower than show_tree(), so + * take the fast path if possible. + */ + while (m2f) { + if (!m2f->fmt) { + fn = options.format ? show_tree_fmt : show_tree_default; + } else if (options.format && !strcmp(options.format, m2f->fmt)) { + cmdmode = m2f->mode; + fn = m2f->fn; + } else if (!options.format && cmdmode == m2f->mode) { + fn = m2f->fn; + } else { + m2f++; + continue; + } + break; + } + + ret = !!read_tree(the_repository, tree, &options.pathspec, fn, &options); + clear_pathspec(&options.pathspec); + return ret; +} |