Merge branch 'js/cat-file-filters'
authorJunio C Hamano <gitster@pobox.com>
Wed, 21 Sep 2016 22:15:18 +0000 (15:15 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 21 Sep 2016 22:15:19 +0000 (15:15 -0700)
Even though "git hash-objects", which is a tool to take an
on-filesystem data stream and put it into the Git object store,
allowed to perform the "outside-world-to-Git" conversions (e.g.
end-of-line conversions and application of the clean-filter), and
it had the feature on by default from very early days, its reverse
operation "git cat-file", which takes an object from the Git object
store and externalize for the consumption by the outside world,
lacked an equivalent mechanism to run the "Git-to-outside-world"
conversion. The command learned the "--filters" option to do so.

* js/cat-file-filters:
cat-file: support --textconv/--filters in batch mode
cat-file --textconv/--filters: allow specifying the path separately
cat-file: introduce the --filters option
cat-file: fix a grammo in the man page

1  2 
builtin/cat-file.c
diff --combined builtin/cat-file.c
index 3ee9bc9ad18bf478d7e8fbf4a9950cb58a1a29c1,59dea48dae0606d3ff36fc2d6f8bc7448273fb7c..94e67ebb7eec087390cfecc5c4371a974401d602
@@@ -17,13 -17,38 +17,38 @@@ struct batch_options 
        int print_contents;
        int buffer_output;
        int all_objects;
+       int cmdmode; /* may be 'w' or 'c' for --filters or --textconv */
        const char *format;
  };
  
 -                       const unsigned char *sha1,
+ static const char *force_path;
+ static int filter_object(const char *path, unsigned mode,
 -      *buf = read_sha1_file(sha1, &type, size);
++                       const struct object_id *oid,
+                        char **buf, unsigned long *size)
+ {
+       enum object_type type;
 -                      sha1_to_hex(sha1), path);
++      *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_info oi = {NULL};
        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, 0, 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':
                oi.typename = &sb;
 -              if (sha1_object_info_extended(sha1, &oi, flags) < 0)
 +              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);
  
        case 's':
                oi.sizep = &size;
 -              if (sha1_object_info_extended(sha1, &oi, flags) < 0)
 +              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);
  
 -                                sha1, &buf, &size))
+       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, &oid, 1, &buf, &size))
 -              if (textconv_object(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);
  
                }
  
                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);
  
  
        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
                         * 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:
  }
  
  struct expand_data {
 -      unsigned char sha1[20];
 +      struct object_id oid;
        enum object_type type;
        unsigned long 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
@@@ -176,7 -218,7 +217,7 @@@ static void expand_atom(struct strbuf *
  
        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;
                        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);
  }
@@@ -233,28 -274,53 +274,53 @@@ static void batch_write(struct batch_op
  
  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 (opt->buffer_output)
                        fflush(stdout);
-               if (stream_blob_to_fd(1, oid, NULL, 0) < 0)
+               if (opt->cmdmode) {
+                       char *contents;
+                       unsigned long size;
+                       if (!data->rest)
 -                              die("missing path for '%s'", sha1_to_hex(sha1));
++                              die("missing path for '%s'", oid_to_hex(oid));
+                       if (opt->cmdmode == 'w') {
 -                              if (filter_object(data->rest, 0100644, sha1,
++                              if (filter_object(data->rest, 0100644, oid,
+                                                 &contents, &size))
+                                       die("could not convert '%s' %s",
 -                                          sha1_to_hex(sha1), data->rest);
++                                          oid_to_hex(oid), data->rest);
+                       } else if (opt->cmdmode == 'c') {
+                               enum object_type type;
 -                              if (!textconv_object(data->rest, 0100644, sha1,
++                              if (!textconv_object(data->rest, 0100644, oid,
+                                                    1, &contents, &size))
 -                                      contents = read_sha1_file(sha1, &type,
++                                      contents = read_sha1_file(oid->hash, &type,
+                                                                 &size);
+                               if (!contents)
+                                       die("could not convert '%s' %s",
 -                                          sha1_to_hex(sha1), data->rest);
++                                          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, sha1, NULL, 0) < 0)
 -                      die("unable to stream %s to stdout", sha1_to_hex(sha1));
++              } 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));
  
                batch_write(opt, contents, size);
                free(contents);
@@@ -267,9 -333,8 +333,9 @@@ static void batch_object_write(const ch
        struct strbuf buf = STRBUF_INIT;
  
        if (!data->skip_object_info &&
 -          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));
 +          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;
        }
@@@ -292,7 -357,7 +358,7 @@@ static void batch_one_object(const cha
        int flags = opt->follow_symlinks ? GET_SHA1_FOLLOW_SYMLINKS : 0;
        enum follow_symlinks_result result;
  
 -      result = get_sha1_with_context(obj_name, flags, data->sha1, &ctx);
 +      result = get_sha1_with_context(obj_name, flags, data->oid.hash, &ctx);
        if (result != FOUND) {
                switch (result) {
                case MISSING_OBJECT:
@@@ -338,7 -403,7 +404,7 @@@ struct object_cb_data 
  static void batch_object_cb(const unsigned char sha1[20], void *vdata)
  {
        struct object_cb_data *data = vdata;
 -      hashcpy(data->expand->sha1, sha1);
 +      hashcpy(data->expand->oid.hash, sha1);
        batch_object_write(NULL, data->opt, data->expand);
  }
  
@@@ -378,6 -443,8 +444,8 @@@ static int batch_objects(struct batch_o
        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;
  }
  
  static const char * const cat_file_usage[] = {
-       N_("git cat-file (-t [--allow-unknown-type] | -s [--allow-unknown-type] | -e | -p | <type> | --textconv) <object>"),
-       N_("git cat-file (--batch | --batch-check) [--follow-symlinks]"),
 -      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]"),
++      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
  };
  
@@@ -488,6 -555,10 +556,10 @@@ int cmd_cat_file(int argc, const char *
                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")),
        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);
                } else
                        usage_with_options(cat_file_usage, options);
        }
-       if (batch.enabled && (opt || argc)) {
-               usage_with_options(cat_file_usage, options);
+       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;