Skip to content

Commit

Permalink
Merge branch 'js/cat-file-filters'
Browse files Browse the repository at this point in the history
Even though "git hash-objects", which is a tool to take an
on-filesystem data stream and put it into the Git object store,
allowed to perform the "outside-world-to-Git" conversions (e.g.
end-of-line conversions and application of the clean-filter), and
it had the feature on by default from very early days, its reverse
operation "git cat-file", which takes an object from the Git object
store and externalize for the consumption by the outside world,
lacked an equivalent mechanism to run the "Git-to-outside-world"
conversion.  The command learned the "--filters" option to do so.

* js/cat-file-filters:
  cat-file: support --textconv/--filters in batch mode
  cat-file --textconv/--filters: allow specifying the path separately
  cat-file: introduce the --filters option
  cat-file: fix a grammo in the man page
  • Loading branch information
gitster committed Sep 21, 2016
2 parents 07d8724 + 3214594 commit 7889ed2
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 18 deletions.
40 changes: 30 additions & 10 deletions Documentation/git-cat-file.txt
Expand Up @@ -9,18 +9,22 @@ git-cat-file - Provide content or type and size information for repository objec
SYNOPSIS
--------
[verse]
'git cat-file' (-t [--allow-unknown-type]| -s [--allow-unknown-type]| -e | -p | <type> | --textconv ) <object>
'git cat-file' (--batch | --batch-check) [--follow-symlinks]
'git cat-file' (-t [--allow-unknown-type]| -s [--allow-unknown-type]| -e | -p | <type> | --textconv | --filters ) [--path=<path>] <object>
'git cat-file' (--batch | --batch-check) [ --textconv | --filters ] [--follow-symlinks]

DESCRIPTION
-----------
In its first form, the command provides the content or the type of an object in
the repository. The type is required unless `-t` or `-p` is used to find the
object type, or `-s` is used to find the object size, or `--textconv` is used
(which implies type "blob").
object type, or `-s` is used to find the object size, or `--textconv` or
`--filters` is used (which imply type "blob").

In the second form, a list of objects (separated by linefeeds) is provided on
stdin, and the SHA-1, type, and size of each object is printed on stdout.
stdin, and the SHA-1, type, and size of each object is printed on stdout. The
output format can be overridden using the optional `<format>` argument. If
either `--textconv` or `--filters` was specified, the input is expected to
list the object names followed by the path name, separated by a single white
space, so that the appropriate drivers can be determined.

OPTIONS
-------
Expand Down Expand Up @@ -54,19 +58,35 @@ OPTIONS

--textconv::
Show the content as transformed by a textconv filter. In this case,
<object> has be of the form <tree-ish>:<path>, or :<path> in order
to apply the filter to the content recorded in the index at <path>.
<object> has to be of the form <tree-ish>:<path>, or :<path> in
order to apply the filter to the content recorded in the index at
<path>.

--filters::
Show the content as converted by the filters configured in
the current working tree for the given <path> (i.e. smudge filters,
end-of-line conversion, etc). In this case, <object> has to be of
the form <tree-ish>:<path>, or :<path>.

--path=<path>::
For use with --textconv or --filters, to allow specifying an object
name and a path separately, e.g. when it is difficult to figure out
the revision from which the blob came.

--batch::
--batch=<format>::
Print object information and contents for each object provided
on stdin. May not be combined with any other options or arguments.
See the section `BATCH OUTPUT` below for details.
on stdin. May not be combined with any other options or arguments
except `--textconv` or `--filters`, in which case the input lines
also need to specify the path, separated by white space. See the
section `BATCH OUTPUT` below for details.

--batch-check::
--batch-check=<format>::
Print object information for each object provided on stdin. May
not be combined with any other options or arguments. See the
not be combined with any other options or arguments except
`--textconv` or `--filters`, in which case the input lines also
need to specify the path, separated by white space. See the
section `BATCH OUTPUT` below for details.

--batch-all-objects::
Expand Down
104 changes: 96 additions & 8 deletions builtin/cat-file.c
Expand Up @@ -17,9 +17,34 @@ struct batch_options {
int print_contents;
int buffer_output;
int all_objects;
int cmdmode; /* may be 'w' or 'c' for --filters or --textconv */
const char *format;
};

static const char *force_path;

static int filter_object(const char *path, unsigned mode,
const struct object_id *oid,
char **buf, unsigned long *size)
{
enum object_type type;

*buf = read_sha1_file(oid->hash, &type, size);
if (!*buf)
return error(_("cannot read object %s '%s'"),
oid_to_hex(oid), path);
if ((type == OBJ_BLOB) && S_ISREG(mode)) {
struct strbuf strbuf = STRBUF_INIT;
if (convert_to_working_tree(path, *buf, *size, &strbuf)) {
free(*buf);
*size = strbuf.len;
*buf = strbuf_detach(&strbuf, NULL);
}
}

return 0;
}

static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
int unknown_type)
{
Expand All @@ -31,13 +56,19 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
struct object_info oi = {NULL};
struct strbuf sb = STRBUF_INIT;
unsigned flags = LOOKUP_REPLACE_OBJECT;
const char *path = force_path;

if (unknown_type)
flags |= LOOKUP_UNKNOWN_OBJECT;

if (get_sha1_with_context(obj_name, 0, oid.hash, &obj_context))
die("Not a valid object name %s", obj_name);

if (!path)
path = obj_context.path;
if (obj_context.mode == S_IFINVALID)
obj_context.mode = 0100644;

buf = NULL;
switch (opt) {
case 't':
Expand All @@ -61,12 +92,22 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
case 'e':
return !has_object_file(&oid);

case 'w':
if (!path[0])
die("git cat-file --filters %s: <object> must be "
"<sha1:path>", obj_name);

if (filter_object(path, obj_context.mode,
&oid, &buf, &size))
return -1;
break;

case 'c':
if (!obj_context.path[0])
if (!path[0])
die("git cat-file --textconv %s: <object> must be <sha1:path>",
obj_name);

if (textconv_object(obj_context.path, obj_context.mode, &oid, 1, &buf, &size))
if (textconv_object(path, obj_context.mode, &oid, 1, &buf, &size))
break;

case 'p':
Expand Down Expand Up @@ -240,7 +281,32 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d
if (data->type == OBJ_BLOB) {
if (opt->buffer_output)
fflush(stdout);
if (stream_blob_to_fd(1, oid, NULL, 0) < 0)
if (opt->cmdmode) {
char *contents;
unsigned long size;

if (!data->rest)
die("missing path for '%s'", oid_to_hex(oid));

if (opt->cmdmode == 'w') {
if (filter_object(data->rest, 0100644, oid,
&contents, &size))
die("could not convert '%s' %s",
oid_to_hex(oid), data->rest);
} else if (opt->cmdmode == 'c') {
enum object_type type;
if (!textconv_object(data->rest, 0100644, oid,
1, &contents, &size))
contents = read_sha1_file(oid->hash, &type,
&size);
if (!contents)
die("could not convert '%s' %s",
oid_to_hex(oid), data->rest);
} else
die("BUG: invalid cmdmode: %c", opt->cmdmode);
batch_write(opt, contents, size);
free(contents);
} else if (stream_blob_to_fd(1, oid, NULL, 0) < 0)
die("unable to stream %s to stdout", oid_to_hex(oid));
}
else {
Expand Down Expand Up @@ -378,6 +444,8 @@ static int batch_objects(struct batch_options *opt)
data.mark_query = 1;
strbuf_expand(&buf, opt->format, expand_format, &data);
data.mark_query = 0;
if (opt->cmdmode)
data.split_on_whitespace = 1;

if (opt->all_objects) {
struct object_info empty;
Expand Down Expand Up @@ -442,8 +510,8 @@ static int batch_objects(struct batch_options *opt)
}

static const char * const cat_file_usage[] = {
N_("git cat-file (-t [--allow-unknown-type] | -s [--allow-unknown-type] | -e | -p | <type> | --textconv) <object>"),
N_("git cat-file (--batch | --batch-check) [--follow-symlinks]"),
N_("git cat-file (-t [--allow-unknown-type] | -s [--allow-unknown-type] | -e | -p | <type> | --textconv | --filters) [--path=<path>] <object>"),
N_("git cat-file (--batch | --batch-check) [--follow-symlinks] [--textconv | --filters]"),
NULL
};

Expand Down Expand Up @@ -488,6 +556,10 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
OPT_CMDMODE('p', NULL, &opt, N_("pretty-print object's content"), 'p'),
OPT_CMDMODE(0, "textconv", &opt,
N_("for blob objects, run textconv on object's content"), 'c'),
OPT_CMDMODE(0, "filters", &opt,
N_("for blob objects, run filters on object's content"), 'w'),
OPT_STRING(0, "path", &force_path, N_("blob"),
N_("use a specific path for --textconv/--filters")),
OPT_BOOL(0, "allow-unknown-type", &unknown_type,
N_("allow -s and -t to work with broken/corrupt objects")),
OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")),
Expand All @@ -510,7 +582,9 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0);

if (opt) {
if (argc == 1)
if (batch.enabled && (opt == 'c' || opt == 'w'))
batch.cmdmode = opt;
else if (argc == 1)
obj_name = argv[0];
else
usage_with_options(cat_file_usage, options);
Expand All @@ -522,14 +596,28 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
} else
usage_with_options(cat_file_usage, options);
}
if (batch.enabled && (opt || argc)) {
usage_with_options(cat_file_usage, options);
if (batch.enabled) {
if (batch.cmdmode != opt || argc)
usage_with_options(cat_file_usage, options);
if (batch.cmdmode && batch.all_objects)
die("--batch-all-objects cannot be combined with "
"--textconv nor with --filters");
}

if ((batch.follow_symlinks || batch.all_objects) && !batch.enabled) {
usage_with_options(cat_file_usage, options);
}

if (force_path && opt != 'c' && opt != 'w') {
error("--path=<path> needs --textconv or --filters");
usage_with_options(cat_file_usage, options);
}

if (force_path && batch.enabled) {
error("--path=<path> incompatible with --batch");
usage_with_options(cat_file_usage, options);
}

if (batch.buffer_output < 0)
batch.buffer_output = batch.all_objects;

Expand Down
64 changes: 64 additions & 0 deletions t/t8010-cat-file-filters.sh
@@ -0,0 +1,64 @@
#!/bin/sh

test_description='git cat-file filters support'
. ./test-lib.sh

test_expect_success 'setup ' '
echo "*.txt eol=crlf diff=txt" >.gitattributes &&
echo "hello" | append_cr >world.txt &&
git add .gitattributes world.txt &&
test_tick &&
git commit -m "Initial commit"
'

has_cr () {
tr '\015' Q <"$1" | grep Q >/dev/null
}

test_expect_success 'no filters with `git show`' '
git show HEAD:world.txt >actual &&
! has_cr actual
'

test_expect_success 'no filters with cat-file' '
git cat-file blob HEAD:world.txt >actual &&
! has_cr actual
'

test_expect_success 'cat-file --filters converts to worktree version' '
git cat-file --filters HEAD:world.txt >actual &&
has_cr actual
'

test_expect_success 'cat-file --filters --path=<path> works' '
sha1=$(git rev-parse -q --verify HEAD:world.txt) &&
git cat-file --filters --path=world.txt $sha1 >actual &&
has_cr actual
'

test_expect_success 'cat-file --textconv --path=<path> works' '
sha1=$(git rev-parse -q --verify HEAD:world.txt) &&
test_config diff.txt.textconv "tr A-Za-z N-ZA-Mn-za-m <" &&
git cat-file --textconv --path=hello.txt $sha1 >rot13 &&
test uryyb = "$(cat rot13 | remove_cr)"
'

test_expect_success '--path=<path> complains without --textconv/--filters' '
sha1=$(git rev-parse -q --verify HEAD:world.txt) &&
test_must_fail git cat-file --path=hello.txt blob $sha1 >actual 2>err &&
test ! -s actual &&
grep "path.*needs.*filters" err
'

test_expect_success 'cat-file --textconv --batch works' '
sha1=$(git rev-parse -q --verify HEAD:world.txt) &&
test_config diff.txt.textconv "tr A-Za-z N-ZA-Mn-za-m <" &&
printf "%s hello.txt\n%s hello\n" $sha1 $sha1 |
git cat-file --textconv --batch >actual &&
printf "%s blob 6\nuryyb\r\n\n%s blob 6\nhello\n\n" \
$sha1 $sha1 >expect &&
test_cmp expect actual
'

test_done

0 comments on commit 7889ed2

Please sign in to comment.