|  | /* | 
|  | * GIT - The information manager from hell | 
|  | * | 
|  | * Copyright (C) Linus Torvalds, 2005 | 
|  | */ | 
|  | #include "cache.h" | 
|  | #include "exec_cmd.h" | 
|  | #include "tag.h" | 
|  | #include "tree.h" | 
|  | #include "builtin.h" | 
|  | #include "parse-options.h" | 
|  |  | 
|  | #define BATCH 1 | 
|  | #define BATCH_CHECK 2 | 
|  |  | 
|  | static void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size) | 
|  | { | 
|  | /* 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. | 
|  | */ | 
|  | write_or_die(1, buf, cp - buf); | 
|  |  | 
|  | /* | 
|  | * 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 */ | 
|  | write_or_die(1, tagger, | 
|  | cp - tagger); | 
|  | break; | 
|  | } | 
|  | while (sp < cp && | 
|  | !('0' <= *sp && *sp <= '9')) | 
|  | sp++; | 
|  | write_or_die(1, tagger, sp - tagger); | 
|  | date = strtoul(sp, &ep, 10); | 
|  | tz = strtol(ep, NULL, 10); | 
|  | sp = show_date(date, tz, 0); | 
|  | write_or_die(1, sp, strlen(sp)); | 
|  | 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) | 
|  | write_or_die(1, cp, endp - cp); | 
|  | } | 
|  |  | 
|  | static int cat_one_file(int opt, const char *exp_type, const char *obj_name) | 
|  | { | 
|  | unsigned char sha1[20]; | 
|  | enum object_type type; | 
|  | void *buf; | 
|  | unsigned long size; | 
|  |  | 
|  | if (get_sha1(obj_name, sha1)) | 
|  | die("Not a valid object name %s", obj_name); | 
|  |  | 
|  | buf = NULL; | 
|  | switch (opt) { | 
|  | case 't': | 
|  | type = sha1_object_info(sha1, NULL); | 
|  | if (type > 0) { | 
|  | printf("%s\n", typename(type)); | 
|  | return 0; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case 's': | 
|  | type = sha1_object_info(sha1, &size); | 
|  | if (type > 0) { | 
|  | printf("%lu\n", size); | 
|  | return 0; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case 'e': | 
|  | return !has_sha1_file(sha1); | 
|  |  | 
|  | case 'p': | 
|  | type = sha1_object_info(sha1, NULL); | 
|  | if (type < 0) | 
|  | die("Not a valid object name %s", obj_name); | 
|  |  | 
|  | /* custom pretty-print here */ | 
|  | if (type == OBJ_TREE) { | 
|  | const char *ls_args[3] = {"ls-tree", obj_name, NULL}; | 
|  | return cmd_ls_tree(2, ls_args, NULL); | 
|  | } | 
|  |  | 
|  | buf = read_sha1_file(sha1, &type, &size); | 
|  | if (!buf) | 
|  | die("Cannot read object %s", obj_name); | 
|  | if (type == OBJ_TAG) { | 
|  | pprint_tag(sha1, buf, size); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* otherwise just spit out the data */ | 
|  | break; | 
|  | case 0: | 
|  | buf = read_object_with_reference(sha1, exp_type, &size, NULL); | 
|  | break; | 
|  |  | 
|  | default: | 
|  | die("git cat-file: unknown option: %s", exp_type); | 
|  | } | 
|  |  | 
|  | if (!buf) | 
|  | die("git cat-file %s: bad file", obj_name); | 
|  |  | 
|  | write_or_die(1, buf, size); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int batch_one_object(const char *obj_name, int print_contents) | 
|  | { | 
|  | unsigned char sha1[20]; | 
|  | enum object_type type = 0; | 
|  | unsigned long size; | 
|  | void *contents = contents; | 
|  |  | 
|  | if (!obj_name) | 
|  | return 1; | 
|  |  | 
|  | if (get_sha1(obj_name, sha1)) { | 
|  | printf("%s missing\n", obj_name); | 
|  | fflush(stdout); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (print_contents == BATCH) | 
|  | contents = read_sha1_file(sha1, &type, &size); | 
|  | else | 
|  | type = sha1_object_info(sha1, &size); | 
|  |  | 
|  | if (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); | 
|  |  | 
|  | if (print_contents == BATCH) { | 
|  | write_or_die(1, contents, size); | 
|  | printf("\n"); | 
|  | fflush(stdout); | 
|  | free(contents); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int batch_objects(int print_contents) | 
|  | { | 
|  | struct strbuf buf = STRBUF_INIT; | 
|  |  | 
|  | while (strbuf_getline(&buf, stdin, '\n') != EOF) { | 
|  | int error = batch_one_object(buf.buf, print_contents); | 
|  | if (error) | 
|  | return error; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const char * const cat_file_usage[] = { | 
|  | "git cat-file (-t|-s|-e|-p|<type>) <object>", | 
|  | "git cat-file (--batch|--batch-check) < <list_of_objects>", | 
|  | NULL | 
|  | }; | 
|  |  | 
|  | int cmd_cat_file(int argc, const char **argv, const char *prefix) | 
|  | { | 
|  | int opt = 0, batch = 0; | 
|  | const char *exp_type = NULL, *obj_name = NULL; | 
|  |  | 
|  | 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'), | 
|  | OPT_SET_INT(0, "batch", &batch, | 
|  | "show info and content of objects fed from the standard input", | 
|  | BATCH), | 
|  | OPT_SET_INT(0, "batch-check", &batch, | 
|  | "show info about objects fed from the standard input", | 
|  | BATCH_CHECK), | 
|  | OPT_END() | 
|  | }; | 
|  |  | 
|  | git_config(git_default_config, NULL); | 
|  |  | 
|  | if (argc != 3 && argc != 2) | 
|  | usage_with_options(cat_file_usage, options); | 
|  |  | 
|  | argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0); | 
|  |  | 
|  | 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); | 
|  | } | 
|  |  | 
|  | if (batch) | 
|  | return batch_objects(batch); | 
|  |  | 
|  | return cat_one_file(opt, exp_type, obj_name); | 
|  | } |