Skip to content

Commit ce74de9

Browse files
adlternativegitster
authored andcommitted
ls-files: introduce "--format" option
Add a new option "--format" that outputs index entries informations in a custom format, taking inspiration from the option with the same name in the `git ls-tree` command. "--format" cannot used with "-s", "-o", "-k", "-t", " --resolve-undo","--deduplicate" and "--eol". Signed-off-by: ZheNing Hu <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 8168d5e commit ce74de9

File tree

3 files changed

+228
-1
lines changed

3 files changed

+228
-1
lines changed

Documentation/git-ls-files.txt

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ SYNOPSIS
2020
[--exclude-standard]
2121
[--error-unmatch] [--with-tree=<tree-ish>]
2222
[--full-name] [--recurse-submodules]
23-
[--abbrev[=<n>]] [--] [<file>...]
23+
[--abbrev[=<n>]] [--format=<format>] [--] [<file>...]
2424

2525
DESCRIPTION
2626
-----------
@@ -192,6 +192,13 @@ followed by the ("attr/<eolattr>").
192192
to the contained files. Sparse directories will be shown with a
193193
trailing slash, such as "x/" for a sparse directory "x".
194194

195+
--format=<format>::
196+
A string that interpolates `%(fieldname)` from the result being shown.
197+
It also interpolates `%%` to `%`, and `%xx` where `xx` are hex digits
198+
interpolates to character with hex code `xx`; for example `%00`
199+
interpolates to `\0` (NUL), `%09` to `\t` (TAB) and %0a to `\n` (LF).
200+
--format cannot be combined with `-s`, `-o`, `-k`, `-t`, `--resolve-undo`
201+
and `--eol`.
195202
\--::
196203
Do not interpret any more arguments as options.
197204

@@ -223,6 +230,36 @@ quoted as explained for the configuration variable `core.quotePath`
223230
(see linkgit:git-config[1]). Using `-z` the filename is output
224231
verbatim and the line is terminated by a NUL byte.
225232

233+
It is possible to print in a custom format by using the `--format`
234+
option, which is able to interpolate different fields using
235+
a `%(fieldname)` notation. For example, if you only care about the
236+
"objectname" and "path" fields, you can execute with a specific
237+
"--format" like
238+
239+
git ls-files --format='%(objectname) %(path)'
240+
241+
FIELD NAMES
242+
-----------
243+
The way each path is shown can be customized by using the
244+
`--format=<format>` option, where the %(fieldname) in the
245+
<format> string for various aspects of the index entry are
246+
interpolated. The following "fieldname" are understood:
247+
248+
objectmode::
249+
The mode of the file which is recorded in the index.
250+
objectname::
251+
The name of the file which is recorded in the index.
252+
stage::
253+
The stage of the file which is recorded in the index.
254+
eolinfo:index::
255+
eolinfo:worktree::
256+
The <eolinfo> (see the description of the `--eol` option) of
257+
the contents in the index or in the worktree for the path.
258+
eolattr::
259+
The <eolattr> (see the description of the `--eol` option)
260+
that applies to the path.
261+
path::
262+
The pathname of the file which is recorded in the index.
226263

227264
EXCLUDE PATTERNS
228265
----------------

builtin/ls-files.c

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "quote.h"
1212
#include "dir.h"
1313
#include "builtin.h"
14+
#include "strbuf.h"
1415
#include "tree.h"
1516
#include "cache-tree.h"
1617
#include "parse-options.h"
@@ -48,6 +49,7 @@ static char *ps_matched;
4849
static const char *with_tree;
4950
static int exc_given;
5051
static int exclude_args;
52+
static const char *format;
5153

5254
static const char *tag_cached = "";
5355
static const char *tag_unmerged = "";
@@ -85,6 +87,16 @@ static void write_name(const char *name)
8587
stdout, line_terminator);
8688
}
8789

90+
static void write_name_to_buf(struct strbuf *sb, const char *name)
91+
{
92+
const char *rel = relative_path(name, prefix_len ? prefix : NULL, sb);
93+
94+
if (line_terminator)
95+
quote_c_style(rel, sb, NULL, 0);
96+
else
97+
strbuf_addstr(sb, rel);
98+
}
99+
88100
static const char *get_tag(const struct cache_entry *ce, const char *tag)
89101
{
90102
static char alttag[4];
@@ -222,6 +234,73 @@ static void show_submodule(struct repository *superproject,
222234
repo_clear(&subrepo);
223235
}
224236

237+
struct show_index_data {
238+
const char *pathname;
239+
struct index_state *istate;
240+
const struct cache_entry *ce;
241+
};
242+
243+
static size_t expand_show_index(struct strbuf *sb, const char *start,
244+
void *context)
245+
{
246+
struct show_index_data *data = context;
247+
const char *end;
248+
const char *p;
249+
size_t len = strbuf_expand_literal_cb(sb, start, NULL);
250+
struct stat st;
251+
252+
if (len)
253+
return len;
254+
if (*start != '(')
255+
die(_("bad ls-files format: element '%s' "
256+
"does not start with '('"), start);
257+
258+
end = strchr(start + 1, ')');
259+
if (!end)
260+
die(_("bad ls-files format: element '%s'"
261+
"does not end in ')'"), start);
262+
263+
len = end - start + 1;
264+
if (skip_prefix(start, "(objectmode)", &p))
265+
strbuf_addf(sb, "%06o", data->ce->ce_mode);
266+
else if (skip_prefix(start, "(objectname)", &p))
267+
strbuf_add_unique_abbrev(sb, &data->ce->oid, abbrev);
268+
else if (skip_prefix(start, "(stage)", &p))
269+
strbuf_addf(sb, "%d", ce_stage(data->ce));
270+
else if (skip_prefix(start, "(eolinfo:index)", &p))
271+
strbuf_addstr(sb, S_ISREG(data->ce->ce_mode) ?
272+
get_cached_convert_stats_ascii(data->istate,
273+
data->ce->name) : "");
274+
else if (skip_prefix(start, "(eolinfo:worktree)", &p))
275+
strbuf_addstr(sb, !lstat(data->pathname, &st) &&
276+
S_ISREG(st.st_mode) ?
277+
get_wt_convert_stats_ascii(data->pathname) : "");
278+
else if (skip_prefix(start, "(eolattr)", &p))
279+
strbuf_addstr(sb, get_convert_attr_ascii(data->istate,
280+
data->pathname));
281+
else if (skip_prefix(start, "(path)", &p))
282+
write_name_to_buf(sb, data->pathname);
283+
else
284+
die(_("bad ls-files format: %%%.*s"), (int)len, start);
285+
286+
return len;
287+
}
288+
289+
static void show_ce_fmt(struct repository *repo, const struct cache_entry *ce,
290+
const char *format, const char *fullname) {
291+
struct show_index_data data = {
292+
.pathname = fullname,
293+
.istate = repo->index,
294+
.ce = ce,
295+
};
296+
struct strbuf sb = STRBUF_INIT;
297+
298+
strbuf_expand(&sb, format, expand_show_index, &data);
299+
strbuf_addch(&sb, line_terminator);
300+
fwrite(sb.buf, sb.len, 1, stdout);
301+
strbuf_release(&sb);
302+
}
303+
225304
static void show_ce(struct repository *repo, struct dir_struct *dir,
226305
const struct cache_entry *ce, const char *fullname,
227306
const char *tag)
@@ -236,6 +315,12 @@ static void show_ce(struct repository *repo, struct dir_struct *dir,
236315
max_prefix_len, ps_matched,
237316
S_ISDIR(ce->ce_mode) ||
238317
S_ISGITLINK(ce->ce_mode))) {
318+
if (format) {
319+
show_ce_fmt(repo, ce, format, fullname);
320+
print_debug(ce);
321+
return;
322+
}
323+
239324
tag = get_tag(ce, tag);
240325

241326
if (!show_stage) {
@@ -675,6 +760,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
675760
N_("suppress duplicate entries")),
676761
OPT_BOOL(0, "sparse", &show_sparse_dirs,
677762
N_("show sparse directories in the presence of a sparse index")),
763+
OPT_STRING_F(0, "format", &format, N_("format"),
764+
N_("format to use for the output"),
765+
PARSE_OPT_NONEG),
678766
OPT_END()
679767
};
680768
int ret = 0;
@@ -699,6 +787,13 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
699787
for (i = 0; i < exclude_list.nr; i++) {
700788
add_pattern(exclude_list.items[i].string, "", 0, pl, --exclude_args);
701789
}
790+
791+
if (format && (show_stage || show_others || show_killed ||
792+
show_resolve_undo || skipping_duplicates || show_eol || show_tag))
793+
usage_msg_opt(_("--format cannot be used with -s, -o, -k, -t, "
794+
"--resolve-undo, --deduplicate, --eol"),
795+
ls_files_usage, builtin_ls_files_options);
796+
702797
if (show_tag || show_valid_bit || show_fsmonitor_bit) {
703798
tag_cached = "H ";
704799
tag_unmerged = "M ";

t/t3013-ls-files-format.sh

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/bin/sh
2+
3+
test_description='git ls-files --format test'
4+
5+
TEST_PASSES_SANITIZE_LEAK=true
6+
. ./test-lib.sh
7+
8+
for flag in -s -o -k -t --resolve-undo --deduplicate --eol
9+
do
10+
test_expect_success "usage: --format is incompatible with $flag" '
11+
test_expect_code 129 git ls-files --format="%(objectname)" $flag
12+
'
13+
done
14+
15+
test_expect_success 'setup' '
16+
printf "LINEONE\nLINETWO\nLINETHREE\n" >o1.txt &&
17+
printf "LINEONE\r\nLINETWO\r\nLINETHREE\r\n" >o2.txt &&
18+
printf "LINEONE\r\nLINETWO\nLINETHREE\n" >o3.txt &&
19+
git add o?.txt &&
20+
oid=$(git hash-object o1.txt) &&
21+
git update-index --add --cacheinfo 120000 $oid o4.txt &&
22+
git update-index --add --cacheinfo 160000 $oid o5.txt &&
23+
git update-index --add --cacheinfo 100755 $oid o6.txt &&
24+
git commit -m base
25+
'
26+
27+
test_expect_success 'git ls-files --format objectmode v.s. -s' '
28+
git ls-files -s >files &&
29+
cut -d" " -f1 files >expect &&
30+
git ls-files --format="%(objectmode)" >actual &&
31+
test_cmp expect actual
32+
'
33+
34+
test_expect_success 'git ls-files --format objectname v.s. -s' '
35+
git ls-files -s >files &&
36+
cut -d" " -f2 files >expect &&
37+
git ls-files --format="%(objectname)" >actual &&
38+
test_cmp expect actual
39+
'
40+
41+
test_expect_success 'git ls-files --format v.s. --eol' '
42+
git ls-files --eol >tmp &&
43+
sed -e "s/ / /g" -e "s/ */ /g" tmp >expect 2>err &&
44+
test_must_be_empty err &&
45+
git ls-files --format="i/%(eolinfo:index) w/%(eolinfo:worktree) attr/%(eolattr) %(path)" >actual 2>err &&
46+
test_must_be_empty err &&
47+
test_cmp expect actual
48+
'
49+
50+
test_expect_success 'git ls-files --format path v.s. -s' '
51+
git ls-files -s >files &&
52+
cut -f2 files >expect &&
53+
git ls-files --format="%(path)" >actual &&
54+
test_cmp expect actual
55+
'
56+
57+
test_expect_success 'git ls-files --format with -m' '
58+
echo change >o1.txt &&
59+
cat >expect <<-\EOF &&
60+
o1.txt
61+
o4.txt
62+
o5.txt
63+
o6.txt
64+
EOF
65+
git ls-files --format="%(path)" -m >actual &&
66+
test_cmp expect actual
67+
'
68+
69+
test_expect_success 'git ls-files --format with -d' '
70+
echo o7 >o7.txt &&
71+
git add o7.txt &&
72+
rm o7.txt &&
73+
cat >expect <<-\EOF &&
74+
o4.txt
75+
o5.txt
76+
o6.txt
77+
o7.txt
78+
EOF
79+
git ls-files --format="%(path)" -d >actual &&
80+
test_cmp expect actual
81+
'
82+
83+
test_expect_success 'git ls-files --format v.s -s' '
84+
git ls-files --stage >expect &&
85+
git ls-files --format="%(objectmode) %(objectname) %(stage)%x09%(path)" >actual &&
86+
test_cmp expect actual
87+
'
88+
89+
test_expect_success 'git ls-files --format with --debug' '
90+
git ls-files --debug >expect &&
91+
git ls-files --format="%(path)" --debug >actual &&
92+
test_cmp expect actual
93+
'
94+
95+
test_done

0 commit comments

Comments
 (0)