2005-04-08 00:16:10 +02:00
|
|
|
/*
|
|
|
|
* GIT - The information manager from hell
|
|
|
|
*
|
|
|
|
* Copyright (C) Linus Torvalds, 2005
|
|
|
|
*/
|
2005-04-08 00:13:13 +02:00
|
|
|
#include "cache.h"
|
2006-03-02 01:43:19 +01:00
|
|
|
#include "exec_cmd.h"
|
2006-04-02 14:44:09 +02:00
|
|
|
#include "tag.h"
|
|
|
|
#include "tree.h"
|
2006-05-24 13:08:46 +02:00
|
|
|
#include "builtin.h"
|
2008-05-23 16:19:42 +02:00
|
|
|
#include "parse-options.h"
|
2010-06-15 17:50:28 +02:00
|
|
|
#include "diff.h"
|
|
|
|
#include "userdiff.h"
|
2012-03-07 11:54:17 +01:00
|
|
|
#include "streaming.h"
|
2008-05-23 16:19:42 +02:00
|
|
|
|
|
|
|
#define BATCH 1
|
|
|
|
#define BATCH_CHECK 2
|
2006-03-02 01:43:19 +01:00
|
|
|
|
2006-08-14 22:19:15 +02:00
|
|
|
static void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size)
|
2006-03-02 01:43:19 +01:00
|
|
|
{
|
|
|
|
/* the parser in tag.c is useless here. */
|
|
|
|
const char *endp = buf + size;
|
|
|
|
const char *cp = buf;
|
|
|
|
|
|
|
|
while (cp < endp) {
|
|
|
|
char c = *cp++;
|
|
|
|
if (c != '\n')
|
|
|
|
continue;
|
|
|
|
if (7 <= endp - cp && !memcmp("tagger ", cp, 7)) {
|
|
|
|
const char *tagger = cp;
|
|
|
|
|
|
|
|
/* Found the tagger line. Copy out the contents
|
|
|
|
* of the buffer so far.
|
|
|
|
*/
|
2006-08-21 20:43:43 +02:00
|
|
|
write_or_die(1, buf, cp - buf);
|
2006-03-02 01:43:19 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Do something intelligent, like pretty-printing
|
|
|
|
* the date.
|
|
|
|
*/
|
|
|
|
while (cp < endp) {
|
|
|
|
if (*cp++ == '\n') {
|
|
|
|
/* tagger to cp is a line
|
|
|
|
* that has ident and time.
|
|
|
|
*/
|
|
|
|
const char *sp = tagger;
|
|
|
|
char *ep;
|
|
|
|
unsigned long date;
|
|
|
|
long tz;
|
|
|
|
while (sp < cp && *sp != '>')
|
|
|
|
sp++;
|
|
|
|
if (sp == cp) {
|
|
|
|
/* give up */
|
2006-08-21 20:43:43 +02:00
|
|
|
write_or_die(1, tagger,
|
2006-03-02 01:43:19 +01:00
|
|
|
cp - tagger);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
while (sp < cp &&
|
|
|
|
!('0' <= *sp && *sp <= '9'))
|
|
|
|
sp++;
|
2006-08-21 20:43:43 +02:00
|
|
|
write_or_die(1, tagger, sp - tagger);
|
2006-03-02 01:43:19 +01:00
|
|
|
date = strtoul(sp, &ep, 10);
|
|
|
|
tz = strtol(ep, NULL, 10);
|
2006-08-27 00:45:26 +02:00
|
|
|
sp = show_date(date, tz, 0);
|
2006-08-21 20:43:43 +02:00
|
|
|
write_or_die(1, sp, strlen(sp));
|
2006-03-02 01:43:19 +01:00
|
|
|
xwrite(1, "\n", 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (cp < endp && *cp == '\n')
|
|
|
|
/* end of header */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* At this point, we have copied out the header up to the end of
|
|
|
|
* the tagger line and cp points at one past \n. It could be the
|
|
|
|
* next header line after the tagger line, or it could be another
|
|
|
|
* \n that marks the end of the headers. We need to copy out the
|
|
|
|
* remainder as is.
|
|
|
|
*/
|
|
|
|
if (cp < endp)
|
2006-08-21 20:43:43 +02:00
|
|
|
write_or_die(1, cp, endp - cp);
|
2006-03-02 01:43:19 +01:00
|
|
|
}
|
2005-04-08 00:13:13 +02:00
|
|
|
|
2008-04-23 21:17:44 +02:00
|
|
|
static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
|
2005-04-08 00:13:13 +02:00
|
|
|
{
|
|
|
|
unsigned char sha1[20];
|
2007-02-26 20:55:59 +01:00
|
|
|
enum object_type type;
|
2010-06-15 17:50:28 +02:00
|
|
|
char *buf;
|
2005-04-08 00:13:13 +02:00
|
|
|
unsigned long size;
|
2010-06-15 17:50:28 +02:00
|
|
|
struct object_context obj_context;
|
2007-04-22 03:14:39 +02:00
|
|
|
|
2012-07-02 19:32:11 +02:00
|
|
|
if (get_sha1_with_context(obj_name, 0, sha1, &obj_context))
|
2007-04-22 03:14:39 +02:00
|
|
|
die("Not a valid object name %s", obj_name);
|
2005-12-04 02:57:48 +01:00
|
|
|
|
|
|
|
buf = NULL;
|
|
|
|
switch (opt) {
|
|
|
|
case 't':
|
2007-02-26 20:55:59 +01:00
|
|
|
type = sha1_object_info(sha1, NULL);
|
|
|
|
if (type > 0) {
|
|
|
|
printf("%s\n", typename(type));
|
2005-06-28 08:58:45 +02:00
|
|
|
return 0;
|
2005-05-02 04:28:18 +02:00
|
|
|
}
|
2005-12-04 02:57:48 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 's':
|
2007-02-26 20:55:59 +01:00
|
|
|
type = sha1_object_info(sha1, &size);
|
|
|
|
if (type > 0) {
|
2005-12-04 02:57:48 +01:00
|
|
|
printf("%lu\n", size);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'e':
|
|
|
|
return !has_sha1_file(sha1);
|
|
|
|
|
2006-03-02 01:43:19 +01:00
|
|
|
case 'p':
|
2007-02-26 20:55:59 +01:00
|
|
|
type = sha1_object_info(sha1, NULL);
|
|
|
|
if (type < 0)
|
2007-04-22 03:14:39 +02:00
|
|
|
die("Not a valid object name %s", obj_name);
|
2006-03-02 01:43:19 +01:00
|
|
|
|
|
|
|
/* custom pretty-print here */
|
2007-04-22 03:14:39 +02:00
|
|
|
if (type == OBJ_TREE) {
|
2010-05-14 11:31:33 +02:00
|
|
|
const char *ls_args[3] = { NULL };
|
|
|
|
ls_args[0] = "ls-tree";
|
|
|
|
ls_args[1] = obj_name;
|
2007-04-22 03:14:39 +02:00
|
|
|
return cmd_ls_tree(2, ls_args, NULL);
|
|
|
|
}
|
2006-03-02 01:43:19 +01:00
|
|
|
|
2012-03-07 11:54:17 +01:00
|
|
|
if (type == OBJ_BLOB)
|
|
|
|
return stream_blob_to_fd(1, sha1, NULL, 0);
|
2007-02-26 20:55:59 +01:00
|
|
|
buf = read_sha1_file(sha1, &type, &size);
|
2006-03-02 01:43:19 +01:00
|
|
|
if (!buf)
|
2007-04-22 03:14:39 +02:00
|
|
|
die("Cannot read object %s", obj_name);
|
2007-02-26 20:55:59 +01:00
|
|
|
if (type == OBJ_TAG) {
|
2006-08-14 22:19:15 +02:00
|
|
|
pprint_tag(sha1, buf, size);
|
|
|
|
return 0;
|
|
|
|
}
|
2006-03-02 01:43:19 +01:00
|
|
|
|
|
|
|
/* otherwise just spit out the data */
|
|
|
|
break;
|
2010-06-15 17:50:28 +02:00
|
|
|
|
|
|
|
case 'c':
|
|
|
|
if (!obj_context.path[0])
|
|
|
|
die("git cat-file --textconv %s: <object> must be <sha1:path>",
|
|
|
|
obj_name);
|
|
|
|
|
diff: do not use null sha1 as a sentinel value
The diff code represents paths using the diff_filespec
struct. This struct has a sha1 to represent the sha1 of the
content at that path, as well as a sha1_valid member which
indicates whether its sha1 field is actually useful. If
sha1_valid is not true, then the filespec represents a
working tree file (e.g., for the no-index case, or for when
the index is not up-to-date).
The diff_filespec is only used internally, though. At the
interfaces to the diff subsystem, callers feed the sha1
directly, and we create a diff_filespec from it. It's at
that point that we look at the sha1 and decide whether it is
valid or not; callers may pass the null sha1 as a sentinel
value to indicate that it is not.
We should not typically see the null sha1 coming from any
other source (e.g., in the index itself, or from a tree).
However, a corrupt tree might have a null sha1, which would
cause "diff --patch" to accidentally diff the working tree
version of a file instead of treating it as a blob.
This patch extends the edges of the diff interface to accept
a "sha1_valid" flag whenever we accept a sha1, and to use
that flag when creating a filespec. In some cases, this
means passing the flag through several layers, making the
code change larger than would be desirable.
One alternative would be to simply die() upon seeing
corrupted trees with null sha1s. However, this fix more
directly addresses the problem (while bogus sha1s in a tree
are probably a bad thing, it is really the sentinel
confusion sending us down the wrong code path that is what
makes it devastating). And it means that git is more capable
of examining and debugging these corrupted trees. For
example, you can still "diff --raw" such a tree to find out
when the bogus entry was introduced; you just cannot do a
"--patch" diff (just as you could not with any other
corrupted tree, as we do not have any content to diff).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-07-28 17:03:01 +02:00
|
|
|
if (!textconv_object(obj_context.path, obj_context.mode, sha1, 1, &buf, &size))
|
2010-06-15 17:50:28 +02:00
|
|
|
die("git cat-file --textconv: unable to run textconv on %s",
|
|
|
|
obj_name);
|
|
|
|
break;
|
|
|
|
|
2005-12-04 02:57:48 +01:00
|
|
|
case 0:
|
2012-03-07 11:54:17 +01:00
|
|
|
if (type_from_string(exp_type) == OBJ_BLOB) {
|
|
|
|
unsigned char blob_sha1[20];
|
|
|
|
if (sha1_object_info(sha1, NULL) == OBJ_TAG) {
|
|
|
|
enum object_type type;
|
|
|
|
unsigned long size;
|
|
|
|
char *buffer = read_sha1_file(sha1, &type, &size);
|
|
|
|
if (memcmp(buffer, "object ", 7) ||
|
|
|
|
get_sha1_hex(buffer + 7, blob_sha1))
|
|
|
|
die("%s not a valid tag", sha1_to_hex(sha1));
|
|
|
|
free(buffer);
|
|
|
|
} else
|
|
|
|
hashcpy(blob_sha1, sha1);
|
|
|
|
|
|
|
|
if (sha1_object_info(blob_sha1, NULL) == OBJ_BLOB)
|
|
|
|
return stream_blob_to_fd(1, blob_sha1, NULL, 0);
|
|
|
|
/*
|
|
|
|
* we attempted to dereference a tag to a blob
|
|
|
|
* and failed; there may be new dereference
|
|
|
|
* mechanisms this code is not aware of.
|
|
|
|
* fall-back to the usual case.
|
|
|
|
*/
|
|
|
|
}
|
2007-04-22 03:14:39 +02:00
|
|
|
buf = read_object_with_reference(sha1, exp_type, &size, NULL);
|
2005-12-04 02:57:48 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2009-01-04 19:38:41 +01:00
|
|
|
die("git cat-file: unknown option: %s", exp_type);
|
2005-04-08 18:16:38 +02:00
|
|
|
}
|
|
|
|
|
2005-05-02 04:28:18 +02:00
|
|
|
if (!buf)
|
2008-08-30 13:12:53 +02:00
|
|
|
die("git cat-file %s: bad file", obj_name);
|
2005-05-02 04:28:18 +02:00
|
|
|
|
2006-08-21 20:43:43 +02:00
|
|
|
write_or_die(1, buf, size);
|
2005-04-08 18:16:38 +02:00
|
|
|
return 0;
|
2005-04-08 00:13:13 +02:00
|
|
|
}
|
2008-04-23 21:17:44 +02:00
|
|
|
|
2008-04-23 21:17:47 +02:00
|
|
|
static int batch_one_object(const char *obj_name, int print_contents)
|
2008-04-23 21:17:46 +02:00
|
|
|
{
|
|
|
|
unsigned char sha1[20];
|
2008-06-09 02:02:21 +02:00
|
|
|
enum object_type type = 0;
|
2008-04-23 21:17:46 +02:00
|
|
|
unsigned long size;
|
2008-04-23 21:17:47 +02:00
|
|
|
void *contents = contents;
|
2008-04-23 21:17:46 +02:00
|
|
|
|
|
|
|
if (!obj_name)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
if (get_sha1(obj_name, sha1)) {
|
|
|
|
printf("%s missing\n", obj_name);
|
2008-06-03 20:34:17 +02:00
|
|
|
fflush(stdout);
|
2008-04-23 21:17:46 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-05-23 16:19:42 +02:00
|
|
|
if (print_contents == BATCH)
|
2008-04-23 21:17:47 +02:00
|
|
|
contents = read_sha1_file(sha1, &type, &size);
|
|
|
|
else
|
|
|
|
type = sha1_object_info(sha1, &size);
|
|
|
|
|
2008-06-09 02:02:21 +02:00
|
|
|
if (type <= 0) {
|
|
|
|
printf("%s missing\n", obj_name);
|
|
|
|
fflush(stdout);
|
2011-06-20 09:40:06 +02:00
|
|
|
if (print_contents == BATCH)
|
|
|
|
free(contents);
|
2008-06-09 02:02:21 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2008-04-23 21:17:46 +02:00
|
|
|
|
|
|
|
printf("%s %s %lu\n", sha1_to_hex(sha1), typename(type), size);
|
2008-04-23 21:17:47 +02:00
|
|
|
fflush(stdout);
|
|
|
|
|
2008-05-23 16:19:42 +02:00
|
|
|
if (print_contents == BATCH) {
|
2008-04-23 21:17:47 +02:00
|
|
|
write_or_die(1, contents, size);
|
|
|
|
printf("\n");
|
|
|
|
fflush(stdout);
|
2008-06-29 03:21:25 +02:00
|
|
|
free(contents);
|
2008-04-23 21:17:47 +02:00
|
|
|
}
|
2008-04-23 21:17:46 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-04-23 21:17:47 +02:00
|
|
|
static int batch_objects(int print_contents)
|
2008-04-23 21:17:46 +02:00
|
|
|
{
|
2008-10-09 21:12:12 +02:00
|
|
|
struct strbuf buf = STRBUF_INIT;
|
2008-04-23 21:17:46 +02:00
|
|
|
|
|
|
|
while (strbuf_getline(&buf, stdin, '\n') != EOF) {
|
2008-04-23 21:17:47 +02:00
|
|
|
int error = batch_one_object(buf.buf, print_contents);
|
2008-04-23 21:17:46 +02:00
|
|
|
if (error)
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-05-23 16:19:42 +02:00
|
|
|
static const char * const cat_file_usage[] = {
|
2010-06-15 17:50:28 +02:00
|
|
|
"git cat-file (-t|-s|-e|-p|<type>|--textconv) <object>",
|
2009-05-25 12:33:15 +02:00
|
|
|
"git cat-file (--batch|--batch-check) < <list_of_objects>",
|
2008-05-23 16:19:42 +02:00
|
|
|
NULL
|
|
|
|
};
|
2008-04-23 21:17:45 +02:00
|
|
|
|
2010-06-15 17:50:28 +02:00
|
|
|
static int git_cat_file_config(const char *var, const char *value, void *cb)
|
|
|
|
{
|
drop odd return value semantics from userdiff_config
When the userdiff_config function was introduced in be58e70
(diff: unify external diff and funcname parsing code,
2008-10-05), it used a return value convention unlike any
other config callback. Like other callbacks, it used "-1" to
signal error. But it returned "1" to indicate that it found
something, and "0" otherwise; other callbacks simply
returned "0" to indicate that no error occurred.
This distinction was necessary at the time, because the
userdiff namespace overlapped slightly with the color
configuration namespace. So "diff.color.foo" could mean "the
'foo' slot of diff coloring" or "the 'foo' component of the
"color" userdiff driver". Because the color-parsing code
would die on an unknown color slot, we needed the userdiff
code to indicate that it had matched the variable, letting
us bypass the color-parsing code entirely.
Later, in 8b8e862 (ignore unknown color configuration,
2009-12-12), the color-parsing code learned to silently
ignore unknown slots. This means we no longer need to
protect userdiff-matched variables from reaching the
color-parsing code.
We can therefore change the userdiff_config calling
convention to a more normal one. This drops some code from
each caller, which is nice. But more importantly, it reduces
the cognitive load for readers who may wonder why
userdiff_config is unlike every other config callback.
There's no need to add a new test confirming that this
works; t4020 already contains a test that sets
diff.color.external.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-02-07 19:23:02 +01:00
|
|
|
if (userdiff_config(var, value) < 0)
|
2010-06-15 17:50:28 +02:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
return git_default_config(var, value, cb);
|
|
|
|
}
|
|
|
|
|
2008-04-23 21:17:44 +02:00
|
|
|
int cmd_cat_file(int argc, const char **argv, const char *prefix)
|
|
|
|
{
|
2008-05-23 16:19:42 +02:00
|
|
|
int opt = 0, batch = 0;
|
2008-04-23 21:17:45 +02:00
|
|
|
const char *exp_type = NULL, *obj_name = NULL;
|
2008-04-23 21:17:44 +02:00
|
|
|
|
2008-05-23 16:19:42 +02:00
|
|
|
const struct option options[] = {
|
|
|
|
OPT_GROUP("<type> can be one of: blob, tree, commit, tag"),
|
|
|
|
OPT_SET_INT('t', NULL, &opt, "show object type", 't'),
|
|
|
|
OPT_SET_INT('s', NULL, &opt, "show object size", 's'),
|
|
|
|
OPT_SET_INT('e', NULL, &opt,
|
|
|
|
"exit with zero when there's no error", 'e'),
|
|
|
|
OPT_SET_INT('p', NULL, &opt, "pretty-print object's content", 'p'),
|
2010-06-15 17:50:28 +02:00
|
|
|
OPT_SET_INT(0, "textconv", &opt,
|
|
|
|
"for blob objects, run textconv on object's content", 'c'),
|
2008-05-23 16:19:42 +02:00
|
|
|
OPT_SET_INT(0, "batch", &batch,
|
2010-02-04 06:23:18 +01:00
|
|
|
"show info and content of objects fed from the standard input",
|
|
|
|
BATCH),
|
2008-05-23 16:19:42 +02:00
|
|
|
OPT_SET_INT(0, "batch-check", &batch,
|
2010-02-04 06:23:18 +01:00
|
|
|
"show info about objects fed from the standard input",
|
2008-05-23 16:19:42 +02:00
|
|
|
BATCH_CHECK),
|
|
|
|
OPT_END()
|
|
|
|
};
|
2008-04-23 21:17:47 +02:00
|
|
|
|
2010-06-15 17:50:28 +02:00
|
|
|
git_config(git_cat_file_config, NULL);
|
2008-04-23 21:17:45 +02:00
|
|
|
|
2008-05-23 16:19:42 +02:00
|
|
|
if (argc != 3 && argc != 2)
|
|
|
|
usage_with_options(cat_file_usage, options);
|
2008-04-23 21:17:45 +02:00
|
|
|
|
2009-05-23 20:53:12 +02:00
|
|
|
argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0);
|
2008-04-23 21:17:46 +02:00
|
|
|
|
2008-05-23 16:19:42 +02:00
|
|
|
if (opt) {
|
|
|
|
if (argc == 1)
|
|
|
|
obj_name = argv[0];
|
|
|
|
else
|
|
|
|
usage_with_options(cat_file_usage, options);
|
|
|
|
}
|
|
|
|
if (!opt && !batch) {
|
|
|
|
if (argc == 2) {
|
|
|
|
exp_type = argv[0];
|
|
|
|
obj_name = argv[1];
|
|
|
|
} else
|
|
|
|
usage_with_options(cat_file_usage, options);
|
|
|
|
}
|
|
|
|
if (batch && (opt || argc)) {
|
|
|
|
usage_with_options(cat_file_usage, options);
|
2008-04-23 21:17:44 +02:00
|
|
|
}
|
|
|
|
|
2008-05-23 16:19:42 +02:00
|
|
|
if (batch)
|
2008-04-23 21:17:47 +02:00
|
|
|
return batch_objects(batch);
|
2008-04-23 21:17:46 +02:00
|
|
|
|
2008-04-23 21:17:44 +02:00
|
|
|
return cat_one_file(opt, exp_type, obj_name);
|
|
|
|
}
|