diff options
-rw-r--r-- | Documentation/git-cat-file.txt | 14 | ||||
-rw-r--r-- | builtin/cat-file.c | 132 | ||||
-rwxr-xr-x | t/t1006-cat-file.sh | 26 |
3 files changed, 137 insertions, 35 deletions
diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt index 319ab4cb08..3105fc0720 100644 --- a/Documentation/git-cat-file.txt +++ b/Documentation/git-cat-file.txt @@ -69,6 +69,20 @@ OPTIONS not be combined with any other options or arguments. See the section `BATCH OUTPUT` below for details. +--batch-all-objects:: + Instead of reading a list of objects on stdin, perform the + requested batch operation on all objects in the repository and + any alternate object stores (not just reachable objects). + Requires `--batch` or `--batch-check` be specified. Note that + the objects are visited in order sorted by their hashes. + +--buffer:: + Normally batch output is flushed after each object is output, so + that a process can interactively read and write from + `cat-file`. With this option, the output uses normal stdio + buffering; this is much more efficient when invoking + `--batch-check` on a large number of objects. + --allow-unknown-type:: Allow -s or -t to query broken/corrupt objects of unknown type. diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 049a95f1f1..07baad1e59 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -9,6 +9,16 @@ #include "userdiff.h" #include "streaming.h" #include "tree-walk.h" +#include "sha1-array.h" + +struct batch_options { + int enabled; + int follow_symlinks; + int print_contents; + int buffer_output; + int all_objects; + const char *format; +}; static int cat_one_file(int opt, const char *exp_type, const char *obj_name, int unknown_type) @@ -204,14 +214,25 @@ static size_t expand_format(struct strbuf *sb, const char *start, void *data) return end - start + 1; } -static void print_object_or_die(int fd, struct expand_data *data) +static void batch_write(struct batch_options *opt, const void *data, int len) +{ + if (opt->buffer_output) { + if (fwrite(data, 1, len, stdout) != len) + die_errno("unable to write to stdout"); + } else + write_or_die(1, data, len); +} + +static void print_object_or_die(struct batch_options *opt, struct expand_data *data) { const unsigned char *sha1 = data->sha1; assert(data->info.typep); if (data->type == OBJ_BLOB) { - if (stream_blob_to_fd(fd, sha1, NULL, 0) < 0) + if (opt->buffer_output) + fflush(stdout); + if (stream_blob_to_fd(1, sha1, NULL, 0) < 0) die("unable to stream %s to stdout", sha1_to_hex(sha1)); } else { @@ -227,29 +248,40 @@ static void print_object_or_die(int fd, struct expand_data *data) if (data->info.sizep && size != data->size) die("object %s changed size!?", sha1_to_hex(sha1)); - write_or_die(fd, contents, size); + batch_write(opt, contents, size); free(contents); } } -struct batch_options { - int enabled; - int follow_symlinks; - int print_contents; - const char *format; -}; - -static int batch_one_object(const char *obj_name, struct batch_options *opt, - struct expand_data *data) +static void batch_object_write(const char *obj_name, struct batch_options *opt, + struct expand_data *data) { struct strbuf buf = STRBUF_INIT; + + if (sha1_object_info_extended(data->sha1, &data->info, LOOKUP_REPLACE_OBJECT) < 0) { + printf("%s missing\n", obj_name ? obj_name : sha1_to_hex(data->sha1)); + fflush(stdout); + return; + } + + strbuf_expand(&buf, opt->format, expand_format, data); + strbuf_addch(&buf, '\n'); + batch_write(opt, buf.buf, buf.len); + strbuf_release(&buf); + + if (opt->print_contents) { + print_object_or_die(opt, data); + batch_write(opt, "\n", 1); + } +} + +static void batch_one_object(const char *obj_name, struct batch_options *opt, + struct expand_data *data) +{ struct object_context ctx; int flags = opt->follow_symlinks ? GET_SHA1_FOLLOW_SYMLINKS : 0; enum follow_symlinks_result result; - if (!obj_name) - return 1; - result = get_sha1_with_context(obj_name, flags, data->sha1, &ctx); if (result != FOUND) { switch (result) { @@ -274,7 +306,7 @@ static int batch_one_object(const char *obj_name, struct batch_options *opt, break; } fflush(stdout); - return 0; + return; } if (ctx.mode == 0) { @@ -282,24 +314,38 @@ static int batch_one_object(const char *obj_name, struct batch_options *opt, (uintmax_t)ctx.symlink_path.len, ctx.symlink_path.buf); fflush(stdout); - return 0; + return; } - if (sha1_object_info_extended(data->sha1, &data->info, LOOKUP_REPLACE_OBJECT) < 0) { - printf("%s missing\n", obj_name); - fflush(stdout); - return 0; - } + batch_object_write(obj_name, opt, data); +} - strbuf_expand(&buf, opt->format, expand_format, data); - strbuf_addch(&buf, '\n'); - write_or_die(1, buf.buf, buf.len); - strbuf_release(&buf); +struct object_cb_data { + struct batch_options *opt; + struct expand_data *expand; +}; - if (opt->print_contents) { - print_object_or_die(1, data); - write_or_die(1, "\n", 1); - } +static void batch_object_cb(const unsigned char sha1[20], void *vdata) +{ + struct object_cb_data *data = vdata; + hashcpy(data->expand->sha1, sha1); + batch_object_write(NULL, data->opt, data->expand); +} + +static int batch_loose_object(const unsigned char *sha1, + const char *path, + void *data) +{ + sha1_array_append(data, sha1); + return 0; +} + +static int batch_packed_object(const unsigned char *sha1, + struct packed_git *pack, + uint32_t pos, + void *data) +{ + sha1_array_append(data, sha1); return 0; } @@ -330,6 +376,21 @@ static int batch_objects(struct batch_options *opt) if (opt->print_contents) data.info.typep = &data.type; + if (opt->all_objects) { + struct sha1_array sa = SHA1_ARRAY_INIT; + struct object_cb_data cb; + + for_each_loose_object(batch_loose_object, &sa, 0); + for_each_packed_object(batch_packed_object, &sa, 0); + + cb.opt = opt; + cb.expand = &data; + sha1_array_for_each_unique(&sa, batch_object_cb, &cb); + + sha1_array_clear(&sa); + return 0; + } + /* * We are going to call get_sha1 on a potentially very large number of * objects. In most large cases, these will be actual object sha1s. The @@ -355,9 +416,7 @@ static int batch_objects(struct batch_options *opt) data.rest = p; } - retval = batch_one_object(buf.buf, opt, &data); - if (retval) - break; + batch_one_object(buf.buf, opt, &data); } strbuf_release(&buf); @@ -412,8 +471,9 @@ 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_BOOL( 0, "allow-unknown-type", &unknown_type, + 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")), { OPTION_CALLBACK, 0, "batch", &batch, "format", N_("show info and content of objects fed from the standard input"), PARSE_OPT_OPTARG, batch_option_callback }, @@ -422,6 +482,8 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, batch_option_callback }, OPT_BOOL(0, "follow-symlinks", &batch.follow_symlinks, N_("follow in-tree symlinks (used with --batch or --batch-check)")), + OPT_BOOL(0, "batch-all-objects", &batch.all_objects, + N_("show all objects with --batch or --batch-check")), OPT_END() }; @@ -446,7 +508,7 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) usage_with_options(cat_file_usage, options); } - if (batch.follow_symlinks && !batch.enabled) { + if ((batch.follow_symlinks || batch.all_objects) && !batch.enabled) { usage_with_options(cat_file_usage, options); } diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index 93a4794930..4f38078ff3 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -547,4 +547,30 @@ test_expect_success 'git cat-file --batch --follow-symlink returns correct sha a test_cmp expect actual ' +test_expect_success 'cat-file --batch-all-objects shows all objects' ' + # make new repos so we know the full set of objects; we will + # also make sure that there are some packed and some loose + # objects, some referenced and some not, and that there are + # some available only via alternates. + git init all-one && + ( + cd all-one && + echo content >file && + git add file && + git commit -qm base && + git rev-parse HEAD HEAD^{tree} HEAD:file && + git repack -ad && + echo not-cloned | git hash-object -w --stdin + ) >expect.unsorted && + git clone -s all-one all-two && + ( + cd all-two && + echo local-unref | git hash-object -w --stdin + ) >>expect.unsorted && + sort <expect.unsorted >expect && + git -C all-two cat-file --batch-all-objects \ + --batch-check="%(objectname)" >actual && + test_cmp expect actual +' + test_done |