summaryrefslogtreecommitdiff
path: root/builtin/cat-file.c
diff options
context:
space:
mode:
Diffstat (limited to 'builtin/cat-file.c')
-rw-r--r--builtin/cat-file.c397
1 files changed, 311 insertions, 86 deletions
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index df99df4db1..4bffd7a2d8 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -5,52 +5,115 @@
*/
#include "cache.h"
#include "builtin.h"
+#include "diff.h"
#include "parse-options.h"
#include "userdiff.h"
#include "streaming.h"
+#include "tree-walk.h"
+#include "sha1-array.h"
-static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
+struct batch_options {
+ int enabled;
+ int follow_symlinks;
+ 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)
{
- unsigned char sha1[20];
+ struct object_id oid;
enum object_type type;
char *buf;
unsigned long size;
struct object_context obj_context;
+ struct object_info oi = OBJECT_INFO_INIT;
+ 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, sha1, &obj_context))
+ if (get_sha1_with_context(obj_name, GET_SHA1_RECORD_PATH,
+ 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':
- type = sha1_object_info(sha1, NULL);
- if (type > 0) {
- printf("%s\n", typename(type));
+ oi.typename = &sb;
+ if (sha1_object_info_extended(oid.hash, &oi, flags) < 0)
+ die("git cat-file: could not get object info");
+ if (sb.len) {
+ printf("%s\n", sb.buf);
+ strbuf_release(&sb);
return 0;
}
break;
case 's':
- type = sha1_object_info(sha1, &size);
- if (type > 0) {
- printf("%lu\n", size);
- return 0;
- }
- break;
+ oi.sizep = &size;
+ if (sha1_object_info_extended(oid.hash, &oi, flags) < 0)
+ die("git cat-file: could not get object info");
+ printf("%lu\n", size);
+ return 0;
case 'e':
- return !has_sha1_file(sha1);
+ 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, sha1, 1, &buf, &size))
+ if (textconv_object(path, obj_context.mode, &oid, 1, &buf, &size))
break;
case 'p':
- type = sha1_object_info(sha1, NULL);
+ type = sha1_object_info(oid.hash, NULL);
if (type < 0)
die("Not a valid object name %s", obj_name);
@@ -63,8 +126,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
}
if (type == OBJ_BLOB)
- return stream_blob_to_fd(1, sha1, NULL, 0);
- buf = read_sha1_file(sha1, &type, &size);
+ return stream_blob_to_fd(1, &oid, NULL, 0);
+ buf = read_sha1_file(oid.hash, &type, &size);
if (!buf)
die("Cannot read object %s", obj_name);
@@ -73,19 +136,19 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
case 0:
if (type_from_string(exp_type) == OBJ_BLOB) {
- unsigned char blob_sha1[20];
- if (sha1_object_info(sha1, NULL) == OBJ_TAG) {
- char *buffer = read_sha1_file(sha1, &type, &size);
+ struct object_id blob_oid;
+ if (sha1_object_info(oid.hash, NULL) == OBJ_TAG) {
+ char *buffer = read_sha1_file(oid.hash, &type, &size);
const char *target;
if (!skip_prefix(buffer, "object ", &target) ||
- get_sha1_hex(target, blob_sha1))
- die("%s not a valid tag", sha1_to_hex(sha1));
+ get_oid_hex(target, &blob_oid))
+ die("%s not a valid tag", oid_to_hex(&oid));
free(buffer);
} else
- hashcpy(blob_sha1, sha1);
+ oidcpy(&blob_oid, &oid);
- if (sha1_object_info(blob_sha1, NULL) == OBJ_BLOB)
- return stream_blob_to_fd(1, blob_sha1, NULL, 0);
+ if (sha1_object_info(blob_oid.hash, NULL) == OBJ_BLOB)
+ return stream_blob_to_fd(1, &blob_oid, NULL, 0);
/*
* we attempted to dereference a tag to a blob
* and failed; there may be new dereference
@@ -93,7 +156,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
* fall-back to the usual case.
*/
}
- buf = read_object_with_reference(sha1, exp_type, &size, NULL);
+ buf = read_object_with_reference(oid.hash, exp_type, &size, NULL);
break;
default:
@@ -104,16 +167,18 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
die("git cat-file %s: bad file", obj_name);
write_or_die(1, buf, size);
+ free(buf);
+ free(obj_context.path);
return 0;
}
struct expand_data {
- unsigned char sha1[20];
+ struct object_id oid;
enum object_type type;
unsigned long size;
- unsigned long disk_size;
+ off_t disk_size;
const char *rest;
- unsigned char delta_base_sha1[20];
+ struct object_id delta_base_oid;
/*
* If mark_query is true, we do not expand anything, but rather
@@ -134,6 +199,13 @@ struct expand_data {
* elements above, so you can retrieve the response from there.
*/
struct object_info info;
+
+ /*
+ * This flag will be true if the requested batch format and options
+ * don't require us to call sha1_object_info, which can then be
+ * optimized out.
+ */
+ unsigned skip_object_info : 1;
};
static int is_atom(const char *atom, const char *s, int slen)
@@ -149,7 +221,7 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len,
if (is_atom("objectname", atom, len)) {
if (!data->mark_query)
- strbuf_addstr(sb, sha1_to_hex(data->sha1));
+ strbuf_addstr(sb, oid_to_hex(&data->oid));
} else if (is_atom("objecttype", atom, len)) {
if (data->mark_query)
data->info.typep = &data->type;
@@ -164,7 +236,7 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len,
if (data->mark_query)
data->info.disk_sizep = &data->disk_size;
else
- strbuf_addf(sb, "%lu", data->disk_size);
+ strbuf_addf(sb, "%"PRIuMAX, (uintmax_t)data->disk_size);
} else if (is_atom("rest", atom, len)) {
if (data->mark_query)
data->split_on_whitespace = 1;
@@ -172,9 +244,10 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len,
strbuf_addstr(sb, data->rest);
} else if (is_atom("deltabase", atom, len)) {
if (data->mark_query)
- data->info.delta_base_sha1 = data->delta_base_sha1;
+ data->info.delta_base_sha1 = data->delta_base_oid.hash;
else
- strbuf_addstr(sb, sha1_to_hex(data->delta_base_sha1));
+ strbuf_addstr(sb,
+ oid_to_hex(&data->delta_base_oid));
} else
die("unknown format element: %.*s", len, atom);
}
@@ -194,69 +267,166 @@ 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;
+ const struct object_id *oid = &data->oid;
assert(data->info.typep);
if (data->type == OBJ_BLOB) {
- if (stream_blob_to_fd(fd, sha1, NULL, 0) < 0)
- die("unable to stream %s to stdout", sha1_to_hex(sha1));
+ if (opt->buffer_output)
+ fflush(stdout);
+ 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 {
enum object_type type;
unsigned long size;
void *contents;
- contents = read_sha1_file(sha1, &type, &size);
+ contents = read_sha1_file(oid->hash, &type, &size);
if (!contents)
- die("object %s disappeared", sha1_to_hex(sha1));
+ die("object %s disappeared", oid_to_hex(oid));
if (type != data->type)
- die("object %s changed type!?", sha1_to_hex(sha1));
+ die("object %s changed type!?", oid_to_hex(oid));
if (data->info.sizep && size != data->size)
- die("object %s changed size!?", sha1_to_hex(sha1));
+ die("object %s changed size!?", oid_to_hex(oid));
- write_or_die(fd, contents, size);
+ batch_write(opt, contents, size);
free(contents);
}
}
-struct batch_options {
- int enabled;
- 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 (!obj_name)
- return 1;
-
- if (get_sha1(obj_name, data->sha1)) {
- printf("%s missing\n", obj_name);
- fflush(stdout);
- return 0;
- }
-
- if (sha1_object_info_extended(data->sha1, &data->info, LOOKUP_REPLACE_OBJECT) < 0) {
- printf("%s missing\n", obj_name);
+ if (!data->skip_object_info &&
+ sha1_object_info_extended(data->oid.hash, &data->info, LOOKUP_REPLACE_OBJECT) < 0) {
+ printf("%s missing\n",
+ obj_name ? obj_name : oid_to_hex(&data->oid));
fflush(stdout);
- return 0;
+ return;
}
strbuf_expand(&buf, opt->format, expand_format, data);
strbuf_addch(&buf, '\n');
- write_or_die(1, buf.buf, buf.len);
+ batch_write(opt, buf.buf, buf.len);
strbuf_release(&buf);
if (opt->print_contents) {
- print_object_or_die(1, data);
- write_or_die(1, "\n", 1);
+ 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;
+
+ result = get_sha1_with_context(obj_name, flags, data->oid.hash, &ctx);
+ if (result != FOUND) {
+ switch (result) {
+ case MISSING_OBJECT:
+ printf("%s missing\n", obj_name);
+ break;
+ case DANGLING_SYMLINK:
+ printf("dangling %"PRIuMAX"\n%s\n",
+ (uintmax_t)strlen(obj_name), obj_name);
+ break;
+ case SYMLINK_LOOP:
+ printf("loop %"PRIuMAX"\n%s\n",
+ (uintmax_t)strlen(obj_name), obj_name);
+ break;
+ case NOT_DIR:
+ printf("notdir %"PRIuMAX"\n%s\n",
+ (uintmax_t)strlen(obj_name), obj_name);
+ break;
+ default:
+ die("BUG: unknown get_sha1_with_context result %d\n",
+ result);
+ break;
+ }
+ fflush(stdout);
+ return;
+ }
+
+ if (ctx.mode == 0) {
+ printf("symlink %"PRIuMAX"\n%s\n",
+ (uintmax_t)ctx.symlink_path.len,
+ ctx.symlink_path.buf);
+ fflush(stdout);
+ return;
+ }
+
+ batch_object_write(obj_name, opt, data);
+}
+
+struct object_cb_data {
+ struct batch_options *opt;
+ struct expand_data *expand;
+};
+
+static int batch_object_cb(const struct object_id *oid, void *vdata)
+{
+ struct object_cb_data *data = vdata;
+ oidcpy(&data->expand->oid, oid);
+ batch_object_write(NULL, data->opt, data->expand);
+ return 0;
+}
+
+static int batch_loose_object(const struct object_id *oid,
+ const char *path,
+ void *data)
+{
+ oid_array_append(data, oid);
+ return 0;
+}
+
+static int batch_packed_object(const struct object_id *oid,
+ struct packed_git *pack,
+ uint32_t pos,
+ void *data)
+{
+ oid_array_append(data, oid);
return 0;
}
@@ -279,6 +449,14 @@ 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 = OBJECT_INFO_INIT;
+ if (!memcmp(&data.info, &empty, sizeof(empty)))
+ data.skip_object_info = 1;
+ }
/*
* If we are printing out the object, then always fill in the type,
@@ -287,6 +465,21 @@ static int batch_objects(struct batch_options *opt)
if (opt->print_contents)
data.info.typep = &data.type;
+ if (opt->all_objects) {
+ struct oid_array sa = OID_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;
+ oid_array_for_each_unique(&sa, batch_object_cb, &cb);
+
+ oid_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
@@ -297,7 +490,7 @@ static int batch_objects(struct batch_options *opt)
save_warning = warn_on_object_refname_ambiguity;
warn_on_object_refname_ambiguity = 0;
- while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+ while (strbuf_getline(&buf, stdin) != EOF) {
if (data.split_on_whitespace) {
/*
* Split at first whitespace, tying off the beginning
@@ -312,9 +505,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);
@@ -323,8 +514,8 @@ static int batch_objects(struct batch_options *opt)
}
static const char * const cat_file_usage[] = {
- N_("git cat-file (-t | -s | -e | -p | <type> | --textconv) <object>"),
- N_("git cat-file (--batch | --batch-check) < <list-of-objects>"),
+ 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
};
@@ -342,9 +533,8 @@ static int batch_option_callback(const struct option *opt,
{
struct batch_options *bo = opt->value;
- if (unset) {
- memset(bo, 0, sizeof(*bo));
- return 0;
+ if (bo->enabled) {
+ return 1;
}
bo->enabled = 1;
@@ -359,34 +549,46 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
int opt = 0;
const char *exp_type = NULL, *obj_name = NULL;
struct batch_options batch = {0};
+ int unknown_type = 0;
const struct option options[] = {
OPT_GROUP(N_("<type> can be one of: blob, tree, commit, tag")),
- OPT_SET_INT('t', NULL, &opt, N_("show object type"), 't'),
- OPT_SET_INT('s', NULL, &opt, N_("show object size"), 's'),
- OPT_SET_INT('e', NULL, &opt,
+ OPT_CMDMODE('t', NULL, &opt, N_("show object type"), 't'),
+ OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'),
+ OPT_CMDMODE('e', NULL, &opt,
N_("exit with zero when there's no error"), 'e'),
- OPT_SET_INT('p', NULL, &opt, N_("pretty-print object's content"), 'p'),
- OPT_SET_INT(0, "textconv", &opt,
+ 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")),
{ OPTION_CALLBACK, 0, "batch", &batch, "format",
N_("show info and content of objects fed from the standard input"),
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_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()
};
git_config(git_cat_file_config, NULL);
- if (argc != 3 && argc != 2)
- usage_with_options(cat_file_usage, options);
-
+ batch.buffer_output = -1;
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);
@@ -398,12 +600,35 @@ 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)) {
+ 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;
+
if (batch.enabled)
return batch_objects(&batch);
- return cat_one_file(opt, exp_type, obj_name);
+ if (unknown_type && opt != 't' && opt != 's')
+ die("git cat-file --allow-unknown-type: use with -s or -t");
+ return cat_one_file(opt, exp_type, obj_name, unknown_type);
}