@@ -10,7 +10,7 @@ SYNOPSIS
--------
[verse]
'git ls-tree' [-d] [-r] [-t] [-l] [-z]
- [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]]
+ [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]] [--format=<format>]
<tree-ish> [<path>...]
DESCRIPTION
@@ -74,6 +74,16 @@ OPTIONS
Do not limit the listing to the current working directory.
Implies --full-name.
+--format=<format>::
+ A string that interpolates `%(fieldname)` from the result
+ being shown. It also interpolates `%%` to `%`, and
+ `%xx` where `xx` are hex digits interpolates to character
+ with hex code `xx`; for example `%00` interpolates to
+ `\0` (NUL), `%09` to `\t` (TAB) and `%0a` to `\n` (LF).
+ When specified, `--format` cannot be combined with other
+ format-altering options, including `--long`, `--name-only`
+ and `--object-only`.
+
[<path>...]::
When paths are given, show them (note that this isn't really raw
pathnames, but rather a list of patterns to match). Otherwise
@@ -82,16 +92,29 @@ OPTIONS
Output Format
-------------
- <mode> SP <type> SP <object> TAB <file>
+
+The output format of `ls-tree` is determined by either the `--format`
+option, or other format-altering options such as `--name-only` etc.
+(see `--format` above).
+
+The use of certain `--format` directives is equivalent to using those
+options, but invoking the full formatting machinery can be slower than
+using an appropriate formatting option.
+
+In cases where the `--format` would exactly map to an existing option
+`ls-tree` will use the appropriate faster path. Thus the default format
+is equivalent to:
+
+ %(objectmode) %(objecttype) %(objectname)%x09%(path)
This output format is compatible with what `--index-info --stdin` of
'git update-index' expects.
When the `-l` option is used, format changes to
- <mode> SP <type> SP <object> SP <object size> TAB <file>
+ %(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)
-Object size identified by <object> is given in bytes, and right-justified
+Object size identified by <objectname> is given in bytes, and right-justified
with minimum width of 7 characters. Object size is given only for blobs
(file) entries; for other entries `-` character is used in place of size.
@@ -100,6 +123,34 @@ quoted as explained for the configuration variable `core.quotePath`
(see linkgit:git-config[1]). Using `-z` the filename is output
verbatim and the line is terminated by a NUL byte.
+Customized format:
+
+It is possible to print in a custom format by using the `--format` option,
+which is able to interpolate different fields using a `%(fieldname)` notation.
+For example, if you only care about the "objectname" and "path" fields, you
+can execute with a specific "--format" like
+
+ git ls-tree --format='%(objectname) %(path)' <tree-ish>
+
+FIELD NAMES
+-----------
+
+Various values from structured fields can be used to interpolate
+into the resulting output. For each outputing line, the following
+names can be used:
+
+objectmode::
+ The mode of the object.
+objecttype::
+ The type of the object (`blob` or `tree`).
+objectname::
+ The name of the object.
+objectsize[:padded]::
+ The size of the object ("-" if it's a tree).
+ It also supports a padded format of size with "%(size:padded)".
+path::
+ The pathname of the object.
+
GIT
---
Part of the linkgit:git[1] suite
@@ -32,7 +32,10 @@ static const char *ls_tree_prefix;
#define FIELD_MODE (1 << 4)
#define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */
#define FIELD_LONG_DEFAULT (FIELD_DEFAULT | FIELD_SIZE)
-
+static const char *format;
+static const char *default_format = "%(objectmode) %(objecttype) %(objectname)%x09%(path)";
+static const char *long_format = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)";
+static const char *name_only_format = "%(path)";
struct show_tree_data {
unsigned mode;
enum object_type type;
@@ -53,6 +56,72 @@ static enum mutx_option {
MODE_LONG,
} cmdmode;
+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, "-");
+ }
+}
+
+static size_t expand_show_tree(struct strbuf *sb, const char *start,
+ void *context)
+{
+ struct show_tree_data *data = context;
+ const char *end;
+ const char *p;
+ unsigned int errlen;
+ size_t len = strbuf_expand_literal_cb(sb, start, NULL);
+
+ if (len)
+ return len;
+ if (*start != '(')
+ die(_("bad ls-tree format: element '%s' does not start with '('"), start);
+
+ end = strchr(start + 1, ')');
+ if (!end)
+ die(_("bad ls-tree format: element '%s' does not end in ')'"), start);
+
+ len = end - start + 1;
+ if (skip_prefix(start, "(objectmode)", &p)) {
+ strbuf_addf(sb, "%06o", data->mode);
+ } else if (skip_prefix(start, "(objecttype)", &p)) {
+ strbuf_addstr(sb, type_name(data->type));
+ } else if (skip_prefix(start, "(objectsize:padded)", &p)) {
+ expand_objectsize(sb, data->oid, data->type, 1);
+ } else if (skip_prefix(start, "(objectsize)", &p)) {
+ expand_objectsize(sb, data->oid, data->type, 0);
+ } else if (skip_prefix(start, "(objectname)", &p)) {
+ strbuf_add_unique_abbrev(sb, data->oid, abbrev);
+ } else if (skip_prefix(start, "(path)", &p)) {
+ const char *name = data->base->buf;
+ const char *prefix = chomp_prefix ? ls_tree_prefix : NULL;
+ struct strbuf quoted = STRBUF_INIT;
+ struct strbuf sbuf = STRBUF_INIT;
+ strbuf_addstr(data->base, data->pathname);
+ name = relative_path(data->base->buf, prefix, &sbuf);
+ quote_c_style(name, "ed, NULL, 0);
+ strbuf_addbuf(sb, "ed);
+ strbuf_release(&sbuf);
+ strbuf_release("ed);
+ } else {
+ errlen = (unsigned long)len;
+ die(_("bad ls-tree format: %%%.*s"), errlen, start);
+ }
+ return len;
+}
+
static int parse_shown_fields(unsigned int *shown_fields)
{
if (cmdmode == MODE_NAME_ONLY) {
@@ -99,6 +168,38 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname
return 0;
}
+static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,
+ const char *pathname, unsigned mode, void *context)
+{
+ size_t baselen;
+ int recurse = 0;
+ struct strbuf sb = STRBUF_INIT;
+ enum object_type type = object_type(mode);
+
+ struct show_tree_data data = {
+ .mode = mode,
+ .type = type,
+ .oid = oid,
+ .pathname = pathname,
+ .base = base,
+ };
+
+ if (type == OBJ_TREE && show_recursive(base->buf, base->len, pathname))
+ recurse = READ_TREE_RECURSIVE;
+ if (type == OBJ_TREE && recurse && !(ls_options & LS_SHOW_TREES))
+ return recurse;
+ if (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))
+ return 0;
+
+ baselen = base->len;
+ strbuf_expand(&sb, format, expand_show_tree, &data);
+ strbuf_addch(&sb, line_termination);
+ fwrite(sb.buf, sb.len, 1, stdout);
+ strbuf_release(&sb);
+ strbuf_setlen(base, baselen);
+ return recurse;
+}
+
static int show_default(struct show_tree_data *data)
{
size_t baselen = data->base->len;
@@ -178,6 +279,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
struct tree *tree;
int i, full_tree = 0;
unsigned int shown_fields = 0;
+ read_tree_fn_t fn = show_tree;
const struct option ls_tree_options[] = {
OPT_BIT('d', NULL, &ls_options, N_("only show trees"),
LS_TREE_ONLY),
@@ -198,6 +300,9 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
OPT_BOOL(0, "full-tree", &full_tree,
N_("list entire tree; not just current directory "
"(implies --full-name)")),
+ OPT_STRING_F(0, "format", &format, N_("format"),
+ N_("format to use for the output"),
+ PARSE_OPT_NONEG),
OPT__ABBREV(&abbrev),
OPT_END()
};
@@ -218,6 +323,10 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))
ls_options |= LS_SHOW_TREES;
+ if (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 (get_oid(argv[0], &oid))
@@ -242,6 +351,20 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
parse_shown_fields(&shown_fields);
- return !!read_tree(the_repository, tree, &pathspec, show_tree,
- &shown_fields);
+ /*
+ * The generic show_tree_fmt() is slower than show_tree(), so
+ * take the fast path if possible.
+ */
+ if (format && (!strcmp(format, default_format))) {
+ fn = show_tree;
+ } else if (format && (!strcmp(format, long_format))) {
+ shown_fields = shown_fields | FIELD_SIZE;
+ fn = show_tree;
+ } else if (format && (!strcmp(format, name_only_format))) {
+ shown_fields = FIELD_PATH_NAME;
+ fn = show_tree;
+ } else if (format)
+ fn = show_tree_fmt;
+
+ return !!read_tree(the_repository, tree, &pathspec, fn, &shown_fields);
}
new file mode 100755
@@ -0,0 +1,81 @@
+#!/bin/sh
+
+test_description='ls-tree --format'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+
+test_expect_success 'ls-tree --format usage' '
+ test_expect_code 129 git ls-tree --format=fmt -l HEAD &&
+ test_expect_code 129 git ls-tree --format=fmt --name-only HEAD &&
+ test_expect_code 129 git ls-tree --format=fmt --name-status HEAD
+'
+
+test_expect_success 'setup' '
+ mkdir dir &&
+ test_commit dir/sub-file &&
+ test_commit top-file
+'
+
+test_ls_tree_format () {
+ format=$1 &&
+ opts=$2 &&
+ fmtopts=$3 &&
+ shift 2 &&
+ git ls-tree $opts -r HEAD >expect.raw &&
+ sed "s/^/> /" >expect <expect.raw &&
+ git ls-tree --format="> $format" -r $fmtopts HEAD >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success 'ls-tree --format=<default-like>' '
+ test_ls_tree_format \
+ "%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
+ ""
+'
+
+test_expect_success 'ls-tree --format=<long-like>' '
+ test_ls_tree_format \
+ "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)" \
+ "--long"
+'
+
+test_expect_success 'ls-tree --format=<name-only-like>' '
+ test_ls_tree_format \
+ "%(path)" \
+ "--name-only"
+'
+
+test_expect_success 'ls-tree combine --format=<default-like> and -t' '
+ test_ls_tree_format \
+ "%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
+ "-t" \
+ "-t"
+'
+
+test_expect_success 'ls-tree combine --format=<default-like> and --full-name' '
+ test_ls_tree_format \
+ "%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
+ "--full-name" \
+ "--full-name"
+'
+
+test_expect_success 'ls-tree combine --format=<default-like> and --full-tree' '
+ test_ls_tree_format \
+ "%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
+ "--full-tree" \
+ "--full-tree"
+'
+
+test_expect_success 'ls-tree hit fast-path with --format=<default-like>' '
+ git ls-tree -r HEAD >expect &&
+ git ls-tree --format="%(objectmode) %(objecttype) %(objectname)%x09%(path)" -r HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'ls-tree hit fast-path with --format=<name-only-like>' '
+ git ls-tree -r --name-only HEAD >expect &&
+ git ls-tree --format="%(path)" -r HEAD >actual &&
+ test_cmp expect actual
+'
+test_done