diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt index 30d585af5d92515cc047b4844ee906265850c195..7c57eca9b08a73074430c5cb249fd95393d36557 100644 --- a/Documentation/git-cat-file.txt +++ b/Documentation/git-cat-file.txt @@ -58,12 +58,16 @@ OPTIONS to apply the filter to the content recorded in the index at . --batch:: - Print the SHA-1, type, size, and contents of each object provided on - stdin. May not be combined with any other options or arguments. +--batch=:: + 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. --batch-check:: - Print the SHA-1, type, and size of each object provided on stdin. May not - be combined with any other options or arguments. +--batch-check=:: + Print object information for each object provided on stdin. May + not be combined with any other options or arguments. See the + section `BATCH OUTPUT` below for details. OUTPUT ------ @@ -78,23 +82,52 @@ If '-p' is specified, the contents of are pretty-printed. If is specified, the raw (though uncompressed) contents of the will be returned. -If '--batch' is specified, output of the following form is printed for each -object specified on stdin: +BATCH OUTPUT +------------ + +If `--batch` or `--batch-check` is given, `cat-file` will read objects +from stdin, one per line, and print information about them. + +Each line is considered as a whole object name, and is parsed as if +given to linkgit:git-rev-parse[1]. + +You can specify the information shown for each object by using a custom +``. The `` is copied literally to stdout for each +object, with placeholders of the form `%(atom)` expanded, followed by a +newline. The available atoms are: + +`objectname`:: + The 40-hex object name of the object. + +`objecttype`:: + The type of of the object (the same as `cat-file -t` reports). + +`objectsize`:: + The size, in bytes, of the object (the same as `cat-file -s` + reports). + +If no format is specified, the default format is `%(objectname) +%(objecttype) %(objectsize)`. + +If `--batch` is specified, the object information is followed by the +object contents (consisting of `%(objectsize)` bytes), followed by a +newline. + +For example, `--batch` without a custom format would produce: ------------ SP SP LF LF ------------ -If '--batch-check' is specified, output of the following form is printed for -each object specified on stdin: +Whereas `--batch-check='%(objectname) %(objecttype)'` would produce: ------------ - SP SP LF + SP LF ------------ -For both '--batch' and '--batch-check', output of the following form is printed -for each object specified on stdin that does not exist in the repository: +If a name is specified on stdin that cannot be resolved to an object in +the repository, then `cat-file` will ignore any custom format and print: ------------ SP missing LF diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 5254fe8956235b6572da71f93212b04af3a44588..b43a0c50403403b5dcf6b415dea092ee64b66558 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -114,6 +114,66 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) return 0; } +struct expand_data { + unsigned char sha1[20]; + enum object_type type; + unsigned long size; + + /* + * If mark_query is true, we do not expand anything, but rather + * just mark the object_info with items we wish to query. + */ + int mark_query; + + /* + * After a mark_query run, this object_info is set up to be + * passed to sha1_object_info_extended. It will point to the data + * elements above, so you can retrieve the response from there. + */ + struct object_info info; +}; + +static int is_atom(const char *atom, const char *s, int slen) +{ + int alen = strlen(atom); + return alen == slen && !memcmp(atom, s, alen); +} + +static void expand_atom(struct strbuf *sb, const char *atom, int len, + void *vdata) +{ + struct expand_data *data = vdata; + + if (is_atom("objectname", atom, len)) { + if (!data->mark_query) + strbuf_addstr(sb, sha1_to_hex(data->sha1)); + } else if (is_atom("objecttype", atom, len)) { + if (!data->mark_query) + strbuf_addstr(sb, typename(data->type)); + } else if (is_atom("objectsize", atom, len)) { + if (data->mark_query) + data->info.sizep = &data->size; + else + strbuf_addf(sb, "%lu", data->size); + } else + die("unknown format element: %.*s", len, atom); +} + +static size_t expand_format(struct strbuf *sb, const char *start, void *data) +{ + const char *end; + + if (*start != '(') + return 0; + end = strchr(start + 1, ')'); + if (!end) + die("format element '%s' does not end in ')'", start); + + expand_atom(sb, start + 1, end - start - 1, data); + + return end - start + 1; +} + static void print_object_or_die(int fd, const unsigned char *sha1, enum object_type type, unsigned long size) { @@ -142,35 +202,37 @@ static void print_object_or_die(int fd, const unsigned char *sha1, struct batch_options { int enabled; int print_contents; + const char *format; }; -static int batch_one_object(const char *obj_name, struct batch_options *opt) +static int batch_one_object(const char *obj_name, struct batch_options *opt, + struct expand_data *data) { - unsigned char sha1[20]; - enum object_type type = 0; - unsigned long size; + struct strbuf buf = STRBUF_INIT; if (!obj_name) return 1; - if (get_sha1(obj_name, sha1)) { + if (get_sha1(obj_name, data->sha1)) { printf("%s missing\n", obj_name); fflush(stdout); return 0; } - type = sha1_object_info(sha1, &size); - if (type <= 0) { + data->type = sha1_object_info_extended(data->sha1, &data->info); + if (data->type <= 0) { printf("%s missing\n", obj_name); fflush(stdout); return 0; } - printf("%s %s %lu\n", sha1_to_hex(sha1), typename(type), size); - fflush(stdout); + strbuf_expand(&buf, opt->format, expand_format, data); + strbuf_addch(&buf, '\n'); + write_or_die(1, buf.buf, buf.len); + strbuf_release(&buf); if (opt->print_contents) { - print_object_or_die(1, sha1, type, size); + print_object_or_die(1, data->sha1, data->type, data->size); write_or_die(1, "\n", 1); } return 0; @@ -179,9 +241,23 @@ static int batch_one_object(const char *obj_name, struct batch_options *opt) static int batch_objects(struct batch_options *opt) { struct strbuf buf = STRBUF_INIT; + struct expand_data data; + + if (!opt->format) + opt->format = "%(objectname) %(objecttype) %(objectsize)"; + + /* + * Expand once with our special mark_query flag, which will prime the + * object_info to be handed to sha1_object_info_extended for each + * object. + */ + memset(&data, 0, sizeof(data)); + data.mark_query = 1; + strbuf_expand(&buf, opt->format, expand_format, &data); + data.mark_query = 0; while (strbuf_getline(&buf, stdin, '\n') != EOF) { - int error = batch_one_object(buf.buf, opt); + int error = batch_one_object(buf.buf, opt, &data); if (error) return error; } @@ -216,6 +292,7 @@ static int batch_option_callback(const struct option *opt, bo->enabled = 1; bo->print_contents = !strcmp(opt->long_name, "batch"); + bo->format = arg; return 0; } @@ -235,12 +312,12 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) OPT_SET_INT('p', NULL, &opt, N_("pretty-print object's content"), 'p'), OPT_SET_INT(0, "textconv", &opt, N_("for blob objects, run textconv on object's content"), 'c'), - { OPTION_CALLBACK, 0, "batch", &batch, NULL, + { OPTION_CALLBACK, 0, "batch", &batch, "format", N_("show info and content of objects fed from the standard input"), - PARSE_OPT_NOARG, batch_option_callback }, - { OPTION_CALLBACK, 0, "batch-check", &batch, NULL, + PARSE_OPT_OPTARG, batch_option_callback }, + { OPTION_CALLBACK, 0, "batch-check", &batch, "format", N_("show info about objects fed from the standard input"), - PARSE_OPT_NOARG, batch_option_callback }, + PARSE_OPT_OPTARG, batch_option_callback }, OPT_END() }; diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index c2f25039e811c0c35c6258d76829a4fb394982fb..4e911fb43d8ea2c699b3a836547b618274db6e05 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -72,6 +72,12 @@ $content" echo_without_newline $sha1 | git cat-file --batch-check >actual && test_cmp expect actual ' + + test_expect_success "custom --batch-check format" ' + echo "$type $sha1" >expect && + echo $sha1 | git cat-file --batch-check="%(objecttype) %(objectname)" >actual && + test_cmp expect actual + ' } hello_content="Hello World"