Merge branch 'maint'
authorJunio C Hamano <gitster@pobox.com>
Tue, 26 Feb 2008 08:14:22 +0000 (00:14 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 26 Feb 2008 08:14:22 +0000 (00:14 -0800)
* maint:
Documentation/git-am.txt: Pass -r in the example invocation of rm -f .dotest
timezone_names[]: fixed the tz offset for New Zealand.
filter-branch documentation: non-zero exit status in command abort the filter
rev-parse: fix potential bus error with --parseopt option spec handling
Use a single implementation and API for copy_file()
Documentation/git-filter-branch: add a new msg-filter example
Correct fast-export file mode strings to match fast-import standard

1  2 
builtin-fast-export.c
builtin-init-db.c
builtin-rerere.c
cache.h
diff.c
diff --combined builtin-fast-export.c
index f741df522014b9495f18cf13c2e01382da71c41d,724cff35d37a42daa85edd41b97f0ef20d01f684..4bd1356d50508efab81066e79abd7abcc1443627
@@@ -123,7 -123,7 +123,7 @@@ static void show_filemodify(struct diff
                        printf("D %s\n", spec->path);
                else {
                        struct object *object = lookup_object(spec->sha1);
-                       printf("M 0%06o :%d %s\n", spec->mode,
+                       printf("M %06o :%d %s\n", spec->mode,
                               get_object_mark(object), spec->path);
                }
        }
@@@ -383,8 -383,7 +383,8 @@@ int cmd_fast_export(int argc, const cha
  
        get_tags_and_duplicates(&revs.pending, &extra_refs);
  
 -      prepare_revision_walk(&revs);
 +      if (prepare_revision_walk(&revs))
 +              die("revision walk setup failed");
        revs.diffopt.format_callback = show_filemodify;
        DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
        while ((commit = get_revision(&revs))) {
diff --combined builtin-init-db.c
index 5d7cdda93314b1d40f5f512897e8a35af0480a8f,ff6e87777ae4c2a883db3836cb9acfee31a7281d..79eaf8d6edf18675897f6eed571289fce450526a
@@@ -29,27 -29,6 +29,6 @@@ static void safe_create_dir(const char 
                die("Could not make %s writable by group\n", dir);
  }
  
- static int copy_file(const char *dst, const char *src, int mode)
- {
-       int fdi, fdo, status;
-       mode = (mode & 0111) ? 0777 : 0666;
-       if ((fdi = open(src, O_RDONLY)) < 0)
-               return fdi;
-       if ((fdo = open(dst, O_WRONLY | O_CREAT | O_EXCL, mode)) < 0) {
-               close(fdi);
-               return fdo;
-       }
-       status = copy_fd(fdi, fdo);
-       if (close(fdo) != 0)
-               return error("%s: write error: %s", dst, strerror(errno));
-       if (!status && adjust_shared_perm(dst))
-               return -1;
-       return status;
- }
  static void copy_templates_1(char *path, int baselen,
                             char *template, int template_baselen,
                             DIR *dir)
@@@ -141,9 -120,9 +120,9 @@@ static void copy_templates(const char *
                 */
                template_dir = DEFAULT_GIT_TEMPLATE_DIR;
                if (!is_absolute_path(template_dir)) {
 -                      const char *exec_path = git_exec_path();
 -                      template_dir = prefix_path(exec_path, strlen(exec_path),
 -                                                 template_dir);
 +                      struct strbuf d = STRBUF_INIT;
 +                      strbuf_addf(&d, "%s/%s", git_exec_path(), template_dir);
 +                      template_dir = strbuf_detach(&d, NULL);
                }
        }
        strcpy(template_path, template_dir);
diff --combined builtin-rerere.c
index b0c17bde879d42c16fe5c7f0756763cd638f8bac,b2971f34456d15cd155b9ac7d207602f9822d149..c607aade637423f0129167163fa4055b08546248
@@@ -149,8 -149,8 +149,8 @@@ static int find_conflict(struct path_li
                if (ce_stage(e2) == 2 &&
                    ce_stage(e3) == 3 &&
                    ce_same_name(e2, e3) &&
 -                  S_ISREG(ntohl(e2->ce_mode)) &&
 -                  S_ISREG(ntohl(e3->ce_mode))) {
 +                  S_ISREG(e2->ce_mode) &&
 +                  S_ISREG(e3->ce_mode)) {
                        path_list_insert((const char *)e2->name, conflict);
                        i++; /* skip over both #2 and #3 */
                }
@@@ -267,23 -267,6 +267,6 @@@ static int diff_two(const char *file1, 
        return 0;
  }
  
- static int copy_file(const char *src, const char *dest)
- {
-       FILE *in, *out;
-       char buffer[32768];
-       int count;
-       if (!(in = fopen(src, "r")))
-               return error("Could not open %s", src);
-       if (!(out = fopen(dest, "w")))
-               return error("Could not open %s", dest);
-       while ((count = fread(buffer, 1, sizeof(buffer), in)))
-               fwrite(buffer, 1, count, out);
-       fclose(in);
-       fclose(out);
-       return 0;
- }
  static int do_plain_rerere(struct path_list *rr, int fd)
  {
        struct path_list conflict = { NULL, 0, 0, 1 };
                        continue;
  
                fprintf(stderr, "Recorded resolution for '%s'.\n", path);
-               copy_file(path, rr_path(name, "postimage"));
+               copy_file(rr_path(name, "postimage"), path, 0666);
  tail_optimization:
                if (i < rr->nr - 1)
                        memmove(rr->items + i,
diff --combined cache.h
index 4fa69f0ee463967e848ffe9860e94e03fe187753,98cfed63ee7f11ef647474057077fdb4e669a376..922bed921213e4cc0e40c6f14533024c240af9ed
+++ b/cache.h
@@@ -3,7 -3,6 +3,7 @@@
  
  #include "git-compat-util.h"
  #include "strbuf.h"
 +#include "hash.h"
  
  #include SHA1_HEADER
  #include <zlib.h>
@@@ -95,148 -94,66 +95,148 @@@ struct cache_time 
   * We save the fields in big-endian order to allow using the
   * index file over NFS transparently.
   */
 +struct ondisk_cache_entry {
 +      struct cache_time ctime;
 +      struct cache_time mtime;
 +      unsigned int dev;
 +      unsigned int ino;
 +      unsigned int mode;
 +      unsigned int uid;
 +      unsigned int gid;
 +      unsigned int size;
 +      unsigned char sha1[20];
 +      unsigned short flags;
 +      char name[FLEX_ARRAY]; /* more */
 +};
 +
  struct cache_entry {
 -      struct cache_time ce_ctime;
 -      struct cache_time ce_mtime;
 +      unsigned int ce_ctime;
 +      unsigned int ce_mtime;
        unsigned int ce_dev;
        unsigned int ce_ino;
        unsigned int ce_mode;
        unsigned int ce_uid;
        unsigned int ce_gid;
        unsigned int ce_size;
 +      unsigned int ce_flags;
        unsigned char sha1[20];
 -      unsigned short ce_flags;
 +      struct cache_entry *next;
        char name[FLEX_ARRAY]; /* more */
  };
  
  #define CE_NAMEMASK  (0x0fff)
  #define CE_STAGEMASK (0x3000)
 -#define CE_UPDATE    (0x4000)
  #define CE_VALID     (0x8000)
  #define CE_STAGESHIFT 12
  
 -#define create_ce_flags(len, stage) htons((len) | ((stage) << CE_STAGESHIFT))
 -#define ce_namelen(ce) (CE_NAMEMASK & ntohs((ce)->ce_flags))
 +/* In-memory only */
 +#define CE_UPDATE    (0x10000)
 +#define CE_REMOVE    (0x20000)
 +#define CE_UPTODATE  (0x40000)
 +
 +#define CE_HASHED    (0x100000)
 +#define CE_UNHASHED  (0x200000)
 +
 +/*
 + * Copy the sha1 and stat state of a cache entry from one to
 + * another. But we never change the name, or the hash state!
 + */
 +#define CE_STATE_MASK (CE_HASHED | CE_UNHASHED)
 +static inline void copy_cache_entry(struct cache_entry *dst, struct cache_entry *src)
 +{
 +      unsigned int state = dst->ce_flags & CE_STATE_MASK;
 +
 +      /* Don't copy hash chain and name */
 +      memcpy(dst, src, offsetof(struct cache_entry, next));
 +
 +      /* Restore the hash state */
 +      dst->ce_flags = (dst->ce_flags & ~CE_STATE_MASK) | state;
 +}
 +
 +/*
 + * We don't actually *remove* it, we can just mark it invalid so that
 + * we won't find it in lookups.
 + *
 + * Not only would we have to search the lists (simple enough), but
 + * we'd also have to rehash other hash buckets in case this makes the
 + * hash bucket empty (common). So it's much better to just mark
 + * it.
 + */
 +static inline void remove_index_entry(struct cache_entry *ce)
 +{
 +      ce->ce_flags |= CE_UNHASHED;
 +}
 +
 +static inline unsigned create_ce_flags(size_t len, unsigned stage)
 +{
 +      if (len >= CE_NAMEMASK)
 +              len = CE_NAMEMASK;
 +      return (len | (stage << CE_STAGESHIFT));
 +}
 +
 +static inline size_t ce_namelen(const struct cache_entry *ce)
 +{
 +      size_t len = ce->ce_flags & CE_NAMEMASK;
 +      if (len < CE_NAMEMASK)
 +              return len;
 +      return strlen(ce->name + CE_NAMEMASK) + CE_NAMEMASK;
 +}
 +
  #define ce_size(ce) cache_entry_size(ce_namelen(ce))
 -#define ce_stage(ce) ((CE_STAGEMASK & ntohs((ce)->ce_flags)) >> CE_STAGESHIFT)
 +#define ondisk_ce_size(ce) ondisk_cache_entry_size(ce_namelen(ce))
 +#define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT)
 +#define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE)
 +#define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE)
  
  #define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644)
  static inline unsigned int create_ce_mode(unsigned int mode)
  {
        if (S_ISLNK(mode))
 -              return htonl(S_IFLNK);
 +              return S_IFLNK;
        if (S_ISDIR(mode) || S_ISGITLINK(mode))
 -              return htonl(S_IFGITLINK);
 -      return htonl(S_IFREG | ce_permissions(mode));
 +              return S_IFGITLINK;
 +      return S_IFREG | ce_permissions(mode);
  }
  static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned int mode)
  {
        extern int trust_executable_bit, has_symlinks;
        if (!has_symlinks && S_ISREG(mode) &&
 -          ce && S_ISLNK(ntohl(ce->ce_mode)))
 +          ce && S_ISLNK(ce->ce_mode))
                return ce->ce_mode;
        if (!trust_executable_bit && S_ISREG(mode)) {
 -              if (ce && S_ISREG(ntohl(ce->ce_mode)))
 +              if (ce && S_ISREG(ce->ce_mode))
                        return ce->ce_mode;
                return create_ce_mode(0666);
        }
        return create_ce_mode(mode);
  }
 +static inline int ce_to_dtype(const struct cache_entry *ce)
 +{
 +      unsigned ce_mode = ntohl(ce->ce_mode);
 +      if (S_ISREG(ce_mode))
 +              return DT_REG;
 +      else if (S_ISDIR(ce_mode) || S_ISGITLINK(ce_mode))
 +              return DT_DIR;
 +      else if (S_ISLNK(ce_mode))
 +              return DT_LNK;
 +      else
 +              return DT_UNKNOWN;
 +}
  #define canon_mode(mode) \
        (S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \
        S_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFGITLINK)
  
  #define cache_entry_size(len) ((offsetof(struct cache_entry,name) + (len) + 8) & ~7)
 +#define ondisk_cache_entry_size(len) ((offsetof(struct ondisk_cache_entry,name) + (len) + 8) & ~7)
  
  struct index_state {
        struct cache_entry **cache;
        unsigned int cache_nr, cache_alloc, cache_changed;
        struct cache_tree *cache_tree;
        time_t timestamp;
 -      void *mmap;
 -      size_t mmap_size;
 +      void *alloc;
 +      unsigned name_hash_initialized : 1;
 +      struct hash_table name_hash;
  };
  
  extern struct index_state the_index;
  #define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL)
  #define ce_match_stat(ce, st, options) ie_match_stat(&the_index, (ce), (st), (options))
  #define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options))
 +#define cache_name_exists(name, namelen) index_name_exists(&the_index, (name), (namelen))
  #endif
  
  enum object_type {
@@@ -347,7 -263,6 +347,7 @@@ extern int read_index_from(struct index
  extern int write_index(struct index_state *, int newfd);
  extern int discard_index(struct index_state *);
  extern int verify_path(const char *path);
 +extern int index_name_exists(struct index_state *istate, const char *name, int namelen);
  extern int index_name_pos(struct index_state *, const char *name, int namelen);
  #define ADD_CACHE_OK_TO_ADD 1         /* Ok to add */
  #define ADD_CACHE_OK_TO_REPLACE 2     /* Ok to replace file/directory */
@@@ -415,14 -330,6 +415,14 @@@ extern size_t packed_git_limit
  extern size_t delta_base_cache_limit;
  extern int auto_crlf;
  
 +enum safe_crlf {
 +      SAFE_CRLF_FALSE = 0,
 +      SAFE_CRLF_FAIL = 1,
 +      SAFE_CRLF_WARN = 2,
 +};
 +
 +extern enum safe_crlf safe_crlf;
 +
  #define GIT_REPO_VERSION 0
  extern int repository_format_version;
  extern int check_repository_format(void);
@@@ -683,9 -590,6 +683,9 @@@ extern int git_config_set_multivar(cons
  extern int git_config_rename_section(const char *, const char *);
  extern const char *git_etc_gitconfig(void);
  extern int check_repository_format_version(const char *var, const char *value);
 +extern int git_env_bool(const char *, int);
 +extern int git_config_system(void);
 +extern int git_config_global(void);
  extern int config_error_nonbool(const char *);
  
  #define MAX_GITNAME (1000)
@@@ -698,6 -602,7 +698,7 @@@ extern const char *git_log_output_encod
  /* IO helper functions */
  extern void maybe_flush_or_die(FILE *, const char *);
  extern int copy_fd(int ifd, int ofd);
+ extern int copy_file(const char *dst, const char *src, int mode);
  extern int read_in_full(int fd, void *buf, size_t count);
  extern int write_in_full(int fd, const void *buf, size_t count);
  extern void write_or_die(int fd, const void *buf, size_t count);
@@@ -731,8 -636,7 +732,8 @@@ extern void trace_argv_printf(const cha
  
  /* convert.c */
  /* returns 1 if *dst was used */
 -extern int convert_to_git(const char *path, const char *src, size_t len, struct strbuf *dst);
 +extern int convert_to_git(const char *path, const char *src, size_t len,
 +                          struct strbuf *dst, enum safe_crlf checksafe);
  extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst);
  
  /* add */
@@@ -751,7 -655,6 +752,7 @@@ void shift_tree(const unsigned char *, 
  #define WS_TRAILING_SPACE     01
  #define WS_SPACE_BEFORE_TAB   02
  #define WS_INDENT_WITH_NON_TAB        04
 +#define WS_CR_AT_EOL           010
  #define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB)
  extern unsigned whitespace_rule_cfg;
  extern unsigned whitespace_rule(const char *);
@@@ -760,7 -663,6 +761,7 @@@ extern unsigned check_and_emit_line(con
      FILE *stream, const char *set,
      const char *reset, const char *ws);
  extern char *whitespace_error_string(unsigned ws);
 +extern int ws_fix_copy(char *, const char *, int, unsigned, int *);
  
  /* ls-files */
  int pathspec_match(const char **spec, char *matched, const char *filename, int skiplen);
diff --combined diff.c
index 99daca6103d0235a851ed6de8c16bd86df194158,76ba5f4afc162d6cbc31bfaa6cb1875a3975833e..dff826b6347bfde01907c0c3b5f1873cd951c1fe
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -20,7 -20,7 +20,7 @@@
  
  static int diff_detect_rename_default;
  static int diff_rename_limit_default = 100;
 -static int diff_use_color_default;
 +int diff_use_color_default = -1;
  static const char *external_diff_cmd_cfg;
  int diff_auto_refresh_index = 1;
  
@@@ -191,7 -191,7 +191,7 @@@ int git_diff_basic_config(const char *v
                }
        }
  
 -      return git_default_config(var, value);
 +      return git_color_default_config(var, value);
  }
  
  static char *quote_two(const char *one, const char *two)
@@@ -272,8 -272,8 +272,8 @@@ static void print_line_count(int count
        }
  }
  
- static void copy_file(int prefix, const char *data, int size,
-               const char *set, const char *reset)
+ static void copy_file_with_prefix(int prefix, const char *data, int size,
+                                 const char *set, const char *reset)
  {
        int ch, nl_just_seen = 1;
        while (0 < size--) {
@@@ -331,9 -331,9 +331,9 @@@ static void emit_rewrite_diff(const cha
        print_line_count(lc_b);
        printf(" @@%s\n", reset);
        if (lc_a)
-               copy_file('-', one->data, one->size, old, reset);
+               copy_file_with_prefix('-', one->data, one->size, old, reset);
        if (lc_b)
-               copy_file('+', two->data, two->size, new, reset);
+               copy_file_with_prefix('+', two->data, two->size, new, reset);
  }
  
  static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
@@@ -982,90 -982,6 +982,90 @@@ static void show_numstat(struct diffsta
        }
  }
  
 +struct diffstat_dir {
 +      struct diffstat_file **files;
 +      int nr, percent, cumulative;
 +};
 +
 +static long gather_dirstat(struct diffstat_dir *dir, unsigned long changed, const char *base, int baselen)
 +{
 +      unsigned long this_dir = 0;
 +      unsigned int sources = 0;
 +
 +      while (dir->nr) {
 +              struct diffstat_file *f = *dir->files;
 +              int namelen = strlen(f->name);
 +              unsigned long this;
 +              char *slash;
 +
 +              if (namelen < baselen)
 +                      break;
 +              if (memcmp(f->name, base, baselen))
 +                      break;
 +              slash = strchr(f->name + baselen, '/');
 +              if (slash) {
 +                      int newbaselen = slash + 1 - f->name;
 +                      this = gather_dirstat(dir, changed, f->name, newbaselen);
 +                      sources++;
 +              } else {
 +                      if (f->is_unmerged || f->is_binary)
 +                              this = 0;
 +                      else
 +                              this = f->added + f->deleted;
 +                      dir->files++;
 +                      dir->nr--;
 +                      sources += 2;
 +              }
 +              this_dir += this;
 +      }
 +
 +      /*
 +       * We don't report dirstat's for
 +       *  - the top level
 +       *  - or cases where everything came from a single directory
 +       *    under this directory (sources == 1).
 +       */
 +      if (baselen && sources != 1) {
 +              int permille = this_dir * 1000 / changed;
 +              if (permille) {
 +                      int percent = permille / 10;
 +                      if (percent >= dir->percent) {
 +                              printf("%4d.%01d%% %.*s\n", percent, permille % 10, baselen, base);
 +                              if (!dir->cumulative)
 +                                      return 0;
 +                      }
 +              }
 +      }
 +      return this_dir;
 +}
 +
 +static void show_dirstat(struct diffstat_t *data, struct diff_options *options)
 +{
 +      int i;
 +      unsigned long changed;
 +      struct diffstat_dir dir;
 +
 +      /* Calculate total changes */
 +      changed = 0;
 +      for (i = 0; i < data->nr; i++) {
 +              if (data->files[i]->is_binary || data->files[i]->is_unmerged)
 +                      continue;
 +              changed += data->files[i]->added;
 +              changed += data->files[i]->deleted;
 +      }
 +
 +      /* This can happen even with many files, if everything was renames */
 +      if (!changed)
 +              return;
 +
 +      /* Show all directories with more than x% of the changes */
 +      dir.files = data->files;
 +      dir.nr = data->nr;
 +      dir.percent = options->dirstat_percent;
 +      dir.cumulative = options->output_format & DIFF_FORMAT_CUMULATIVE;
 +      gather_dirstat(&dir, changed, "", 0);
 +}
 +
  static void free_diffstat_info(struct diffstat_t *diffstat)
  {
        int i;
@@@ -1283,7 -1199,7 +1283,7 @@@ static struct builtin_funcname_pattern 
                        "new\\|return\\|switch\\|throw\\|while\\)\n"
                        "^[     ]*\\(\\([       ]*"
                        "[A-Za-z_][A-Za-z_0-9]*\\)\\{2,\\}"
 -                      "[      ]*([^;]*$\\)" },
 +                      "[      ]*([^;]*\\)$" },
        { "tex", "^\\(\\\\\\(sub\\)*section{.*\\)$" },
  };
  
@@@ -1596,22 -1512,17 +1596,22 @@@ static int reuse_worktree_file(const ch
        if (pos < 0)
                return 0;
        ce = active_cache[pos];
 -      if ((lstat(name, &st) < 0) ||
 -          !S_ISREG(st.st_mode) || /* careful! */
 -          ce_match_stat(ce, &st, 0) ||
 -          hashcmp(sha1, ce->sha1))
 +
 +      /*
 +       * This is not the sha1 we are looking for, or
 +       * unreusable because it is not a regular file.
 +       */
 +      if (hashcmp(sha1, ce->sha1) || !S_ISREG(ce->ce_mode))
                return 0;
 -      /* we return 1 only when we can stat, it is a regular file,
 -       * stat information matches, and sha1 recorded in the cache
 -       * matches.  I.e. we know the file in the work tree really is
 -       * the same as the <name, sha1> pair.
 +
 +      /*
 +       * If ce matches the file in the work tree, we can reuse it.
         */
 -      return 1;
 +      if (ce_uptodate(ce) ||
 +          (!lstat(name, &st) && !ce_match_stat(ce, &st, 0)))
 +              return 1;
 +
 +      return 0;
  }
  
  static int populate_from_stdin(struct diff_filespec *s)
@@@ -1715,7 -1626,7 +1715,7 @@@ int diff_populate_filespec(struct diff_
                 * Convert from working tree format to canonical git format
                 */
                strbuf_init(&buf, 0);
 -              if (convert_to_git(s->path, s->data, s->size, &buf)) {
 +              if (convert_to_git(s->path, s->data, s->size, &buf, safe_crlf)) {
                        size_t size = 0;
                        munmap(s->data, s->size);
                        s->should_munmap = 0;
@@@ -2134,13 -2045,12 +2134,13 @@@ void diff_setup(struct diff_options *op
        options->line_termination = '\n';
        options->break_opt = -1;
        options->rename_limit = -1;
 +      options->dirstat_percent = 3;
        options->context = 3;
        options->msg_sep = "";
  
        options->change = diff_change;
        options->add_remove = diff_addremove;
 -      if (diff_use_color_default)
 +      if (diff_use_color_default > 0)
                DIFF_OPT_SET(options, COLOR_DIFF);
        else
                DIFF_OPT_CLR(options, COLOR_DIFF);
@@@ -2176,7 -2086,6 +2176,7 @@@ int diff_setup_done(struct diff_option
                                            DIFF_FORMAT_NUMSTAT |
                                            DIFF_FORMAT_DIFFSTAT |
                                            DIFF_FORMAT_SHORTSTAT |
 +                                          DIFF_FORMAT_DIRSTAT |
                                            DIFF_FORMAT_SUMMARY |
                                            DIFF_FORMAT_PATCH);
  
                                      DIFF_FORMAT_NUMSTAT |
                                      DIFF_FORMAT_DIFFSTAT |
                                      DIFF_FORMAT_SHORTSTAT |
 +                                    DIFF_FORMAT_DIRSTAT |
                                      DIFF_FORMAT_SUMMARY |
                                      DIFF_FORMAT_CHECKDIFF))
                DIFF_OPT_SET(options, RECURSIVE);
@@@ -2299,10 -2207,6 +2299,10 @@@ int diff_opt_parse(struct diff_options 
                options->output_format |= DIFF_FORMAT_NUMSTAT;
        else if (!strcmp(arg, "--shortstat"))
                options->output_format |= DIFF_FORMAT_SHORTSTAT;
 +      else if (opt_arg(arg, 'X', "dirstat", &options->dirstat_percent))
 +              options->output_format |= DIFF_FORMAT_DIRSTAT;
 +      else if (!strcmp(arg, "--cumulative"))
 +              options->output_format |= DIFF_FORMAT_CUMULATIVE;
        else if (!strcmp(arg, "--check"))
                options->output_format |= DIFF_FORMAT_CHECKDIFF;
        else if (!strcmp(arg, "--summary"))
@@@ -3021,7 -2925,7 +3021,7 @@@ void diff_flush(struct diff_options *op
                separator++;
        }
  
 -      if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT)) {
 +      if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIRSTAT)) {
                struct diffstat_t diffstat;
  
                memset(&diffstat, 0, sizeof(struct diffstat_t));
                        if (check_pair_status(p))
                                diff_flush_stat(p, options, &diffstat);
                }
 +              if (output_format & DIFF_FORMAT_DIRSTAT)
 +                      show_dirstat(&diffstat, options);
                if (output_format & DIFF_FORMAT_NUMSTAT)
                        show_numstat(&diffstat, options);
                if (output_format & DIFF_FORMAT_DIFFSTAT)