cat-file --textconv/--filters: allow specifying the path separately

There are circumstances when it is relatively easy to figure out the object name for a given path, but not the name of the containing tree. For example, when looking at a diff generated by Git, the object names are recorded, but not the revision. As a matter of fact, the revisions from which the diff was generated may not even exist locally. In such a case, the user would have to generate a fake revision just to be able to use --textconv or --filters. Let's simplify this dramatically, because we do not really need that revision at all: all we care about is that we know the path. In the scenario described above, we do know the path, and we just want to specify it separately from the object name. Example usage: git cat-file --textconv --path=main.c 0f1937fd Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Johannes Schindelin committed Sep 9, 2016 at 12:10 UTC 7bcf341453572a227036afec76bdab451b798870
3 files changed +47 -6
Documentation/git-cat-file.txt
+6 -1
@@ -9,7 +9,7 @@ git-cat-file - Provide content or type and size information for repository objec
9 SYNOPSIS
10 --------
11 [verse]
12 -'git cat-file' (-t [--allow-unknown-type]| -s [--allow-unknown-type]| -e | -p | <type> | --textconv | --filters ) <object>
12 +'git cat-file' (-t [--allow-unknown-type]| -s [--allow-unknown-type]| -e | -p | <type> | --textconv | --filters ) [--path=<path>] <object>
13 'git cat-file' (--batch | --batch-check) [--follow-symlinks]
14
15 DESCRIPTION
@@ -64,6 +64,11 @@ OPTIONS
64 end-of-line conversion, etc). In this case, <object> has to be of
65 the form <tree-ish>:<path>, or :<path>.
66
67 +--path=<path>::
68 + For use with --textconv or --filters, to allow specifying an object
69 + name and a path separately, e.g. when it is difficult to figure out
70 + the revision from which the blob came.
71 +
72 --batch::
73 --batch=<format>::
74 Print object information and contents for each object provided
builtin/cat-file.c
+21 -5
@@ -20,6 +20,8 @@ struct batch_options {
20 const char *format;
21 };
22
23 +static const char *force_path;
24 +
25 static int filter_object(const char *path, unsigned mode,
26 const unsigned char *sha1,
27 char **buf, unsigned long *size)
@@ -53,6 +55,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
55 struct object_info oi = {NULL};
56 struct strbuf sb = STRBUF_INIT;
57 unsigned flags = LOOKUP_REPLACE_OBJECT;
58 + const char *path = force_path;
59
60 if (unknown_type)
61 flags |= LOOKUP_UNKNOWN_OBJECT;
@@ -60,6 +63,11 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
63 if (get_sha1_with_context(obj_name, 0, sha1, &obj_context))
64 die("Not a valid object name %s", obj_name);
65
66 + if (!path)
67 + path = obj_context.path;
68 + if (obj_context.mode == S_IFINVALID)
69 + obj_context.mode = 0100644;
70 +
71 buf = NULL;
72 switch (opt) {
73 case 't':
@@ -84,21 +92,22 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
92 return !has_sha1_file(sha1);
93
94 case 'w':
87 - if (!obj_context.path[0])
95 + if (!path[0])
96 die("git cat-file --filters %s: <object> must be "
97 "<sha1:path>", obj_name);
98
91 - if (filter_object(obj_context.path, obj_context.mode,
99 + if (filter_object(path, obj_context.mode,
100 sha1, &buf, &size))
101 return -1;
102 break;
103
104 case 'c':
97 - if (!obj_context.path[0])
105 + if (!path[0])
106 die("git cat-file --textconv %s: <object> must be <sha1:path>",
107 obj_name);
108
101 - if (textconv_object(obj_context.path, obj_context.mode, sha1, 1, &buf, &size))
109 + if (textconv_object(path, obj_context.mode,
110 + sha1, 1, &buf, &size))
111 break;
112
113 case 'p':
@@ -472,7 +481,7 @@ static int batch_objects(struct batch_options *opt)
481 }
482
483 static const char * const cat_file_usage[] = {
475 - N_("git cat-file (-t [--allow-unknown-type]|-s [--allow-unknown-type]|-e|-p|<type>|--textconv|--filters) <object>"),
484 + N_("git cat-file (-t [--allow-unknown-type]|-s [--allow-unknown-type]|-e|-p|<type>|--textconv|--filters) [--path=<path>] <object>"),
485 N_("git cat-file (--batch | --batch-check) [--follow-symlinks]"),
486 NULL
487 };
@@ -520,6 +529,8 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
529 N_("for blob objects, run textconv on object's content"), 'c'),
530 OPT_CMDMODE(0, "filters", &opt,
531 N_("for blob objects, run filters on object's content"), 'w'),
532 + OPT_STRING(0, "path", &force_path, N_("blob"),
533 + N_("use a specific path for --textconv/--filters")),
534 OPT_BOOL(0, "allow-unknown-type", &unknown_type,
535 N_("allow -s and -t to work with broken/corrupt objects")),
536 OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")),
@@ -562,6 +573,11 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
573 usage_with_options(cat_file_usage, options);
574 }
575
576 + if (force_path && opt != 'c' && opt != 'w') {
577 + error("--path=<path> needs --textconv or --filters");
578 + usage_with_options(cat_file_usage, options);
579 + }
580 +
581 if (batch.buffer_output < 0)
582 batch.buffer_output = batch.all_objects;
583
t/t8010-cat-file-filters.sh
+20
@@ -31,4 +31,24 @@ test_expect_success 'cat-file --filters converts to worktree version' '
31 has_cr actual
32 '
33
34 +test_expect_success 'cat-file --filters --path=<path> works' '
35 + sha1=$(git rev-parse -q --verify HEAD:world.txt) &&
36 + git cat-file --filters --path=world.txt $sha1 >actual &&
37 + has_cr actual
38 +'
39 +
40 +test_expect_success 'cat-file --textconv --path=<path> works' '
41 + sha1=$(git rev-parse -q --verify HEAD:world.txt) &&
42 + test_config diff.txt.textconv "tr A-Za-z N-ZA-Mn-za-m <" &&
43 + git cat-file --textconv --path=hello.txt $sha1 >rot13 &&
44 + test uryyb = "$(cat rot13 | remove_cr)"
45 +'
46 +
47 +test_expect_success '--path=<path> complains without --textconv/--filters' '
48 + sha1=$(git rev-parse -q --verify HEAD:world.txt) &&
49 + test_must_fail git cat-file --path=hello.txt blob $sha1 >actual 2>err &&
50 + test ! -s actual &&
51 + grep "path.*needs.*filters" err
52 +'
53 +
54 test_done