Merge branch 'th/diff'
authorJunio C Hamano <junkio@cox.net>
Wed, 5 Jul 2006 23:31:24 +0000 (16:31 -0700)
committerJunio C Hamano <junkio@cox.net>
Wed, 5 Jul 2006 23:31:24 +0000 (16:31 -0700)
* th/diff:
builtin-diff: turn recursive on when defaulting to --patch format.
t4013: note improvements brought by the new output code.
t4013: add format-patch tests.
format-patch: fix diff format option implementation
combine-diff.c: type sanity.
t4013 test updates for new output code.
Fix some more diff options changes.
Fix diff-tree -s
log --raw: Don't descend into subdirectories by default
diff-tree: Use ---\n as a message separator
Print empty line between raw, stat, summary and patch
t4013: add more tests around -c and --cc
whatchanged: Default to DIFF_FORMAT_RAW
Don't xcalloc() struct diffstat_t
Add msg_sep to diff_options
DIFF_FORMAT_RAW is not default anymore
Set default diff output format after parsing command line
Make --raw option available for all diff commands
Merge with_raw, with_stat and summary variables to output_format
t4013: add tests for diff/log family output options.

1  2 
builtin-log.c
combine-diff.c
diff.c
diff.h
revision.c
diff --combined builtin-log.c
index f9515a8a4ac8a53e87b6fb9f73e1964350481bfb,bcd4e5e161d4f8a315aebb8f741b0a1b9870cb71..864c6cd9ea1777bc15b12333b88b2e3a3f013321
  /* this is in builtin-diff.c */
  void add_head(struct rev_info *revs);
  
- static int cmd_log_wc(int argc, const char **argv, char **envp,
+ static void cmd_log_init(int argc, const char **argv, char **envp,
                      struct rev_info *rev)
  {
-       struct commit *commit;
        rev->abbrev = DEFAULT_ABBREV;
        rev->commit_format = CMIT_FMT_DEFAULT;
        rev->verbose_header = 1;
        argc = setup_revisions(argc, argv, rev, "HEAD");
-       if (rev->always_show_header) {
-               if (rev->diffopt.pickaxe || rev->diffopt.filter) {
-                       rev->always_show_header = 0;
-                       if (rev->diffopt.output_format == DIFF_FORMAT_RAW)
-                               rev->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT;
-               }
-       }
+       if (rev->diffopt.pickaxe || rev->diffopt.filter)
+               rev->always_show_header = 0;
        if (argc > 1)
                die("unrecognized argument: %s", argv[1]);
+ }
+ static int cmd_log_walk(struct rev_info *rev)
+ {
+       struct commit *commit;
  
        prepare_revision_walk(rev);
        setup_pager();
@@@ -54,7 -51,10 +51,10 @@@ int cmd_whatchanged(int argc, const cha
        rev.diff = 1;
        rev.diffopt.recursive = 1;
        rev.simplify_history = 0;
-       return cmd_log_wc(argc, argv, envp, &rev);
+       cmd_log_init(argc, argv, envp, &rev);
+       if (!rev.diffopt.output_format)
+               rev.diffopt.output_format = DIFF_FORMAT_RAW;
+       return cmd_log_walk(&rev);
  }
  
  int cmd_show(int argc, const char **argv, char **envp)
@@@ -69,7 -69,8 +69,8 @@@
        rev.always_show_header = 1;
        rev.ignore_merges = 0;
        rev.no_walk = 1;
-       return cmd_log_wc(argc, argv, envp, &rev);
+       cmd_log_init(argc, argv, envp, &rev);
+       return cmd_log_walk(&rev);
  }
  
  int cmd_log(int argc, const char **argv, char **envp)
@@@ -78,8 -79,8 +79,8 @@@
  
        init_revisions(&rev);
        rev.always_show_header = 1;
-       rev.diffopt.recursive = 1;
-       return cmd_log_wc(argc, argv, envp, &rev);
+       cmd_log_init(argc, argv, envp, &rev);
+       return cmd_log_walk(&rev);
  }
  
  static int istitlechar(char c)
@@@ -160,65 -161,6 +161,65 @@@ static void reopen_stdout(struct commi
        freopen(filename, "w", stdout);
  }
  
 +static int get_patch_id(struct commit *commit, struct diff_options *options,
 +              unsigned char *sha1)
 +{
 +      diff_tree_sha1(commit->parents->item->object.sha1, commit->object.sha1,
 +                      "", options);
 +      diffcore_std(options);
 +      return diff_flush_patch_id(options, sha1);
 +}
 +
 +static void get_patch_ids(struct rev_info *rev, struct diff_options *options)
 +{
 +      struct rev_info check_rev;
 +      struct commit *commit;
 +      struct object *o1, *o2;
 +      unsigned flags1, flags2;
 +      unsigned char sha1[20];
 +
 +      if (rev->pending.nr != 2)
 +              die("Need exactly one range.");
 +
 +      o1 = rev->pending.objects[0].item;
 +      flags1 = o1->flags;
 +      o2 = rev->pending.objects[1].item;
 +      flags2 = o2->flags;
 +
 +      if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING))
 +              die("Not a range.");
 +
 +      diff_setup(options);
 +      options->recursive = 1;
 +      if (diff_setup_done(options) < 0)
 +              die("diff_setup_done failed");
 +
 +      /* given a range a..b get all patch ids for b..a */
 +      init_revisions(&check_rev);
 +      o1->flags ^= UNINTERESTING;
 +      o2->flags ^= UNINTERESTING;
 +      add_pending_object(&check_rev, o1, "o1");
 +      add_pending_object(&check_rev, o2, "o2");
 +      prepare_revision_walk(&check_rev);
 +
 +      while ((commit = get_revision(&check_rev)) != NULL) {
 +              /* ignore merges */
 +              if (commit->parents && commit->parents->next)
 +                      continue;
 +
 +              if (!get_patch_id(commit, options, sha1))
 +                      created_object(sha1, xcalloc(1, sizeof(struct object)));
 +      }
 +
 +      /* reset for next revision walk */
 +      clear_commit_marks((struct commit *)o1,
 +                      SEEN | UNINTERESTING | SHOWN | ADDED);
 +      clear_commit_marks((struct commit *)o2,
 +                      SEEN | UNINTERESTING | SHOWN | ADDED);
 +      o1->flags = flags1;
 +      o2->flags = flags2;
 +}
 +
  int cmd_format_patch(int argc, const char **argv, char **envp)
  {
        struct commit *commit;
        int numbered = 0;
        int start_number = -1;
        int keep_subject = 0;
 +      int ignore_if_in_upstream = 0;
 +      struct diff_options patch_id_opts;
        char *add_signoff = NULL;
  
        init_revisions(&rev);
        rev.commit_format = CMIT_FMT_EMAIL;
        rev.verbose_header = 1;
        rev.diff = 1;
-       rev.diffopt.with_raw = 0;
-       rev.diffopt.with_stat = 1;
        rev.combine_merges = 0;
        rev.ignore_merges = 1;
-       rev.diffopt.output_format = DIFF_FORMAT_PATCH;
+       rev.diffopt.msg_sep = "";
+       rev.diffopt.recursive = 1;
  
        git_config(git_format_config);
        rev.extra_headers = extra_headers;
                        rev.mime_boundary = git_version_string;
                else if (!strncmp(argv[i], "--attach=", 9))
                        rev.mime_boundary = argv[i] + 9;
 +              else if (!strcmp(argv[i], "--ignore-if-in-upstream"))
 +                      ignore_if_in_upstream = 1;
                else
                        argv[j++] = argv[i];
        }
        if (argc > 1)
                die ("unrecognized argument: %s", argv[1]);
  
+       if (!rev.diffopt.output_format)
+               rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
        if (output_directory) {
                if (use_stdout)
                        die("standard output, or directory, which one?");
                add_head(&rev);
        }
  
 +      if (ignore_if_in_upstream)
 +              get_patch_ids(&rev, &patch_id_opts);
 +
        if (!use_stdout)
                realstdout = fdopen(dup(1), "w");
  
        prepare_revision_walk(&rev);
        while ((commit = get_revision(&rev)) != NULL) {
 +              unsigned char sha1[20];
 +
                /* ignore merges */
                if (commit->parents && commit->parents->next)
                        continue;
 +
 +              if (ignore_if_in_upstream &&
 +                              !get_patch_id(commit, &patch_id_opts, sha1) &&
 +                              lookup_object(sha1))
 +                      continue;
 +
                nr++;
                list = realloc(list, nr * sizeof(list[0]));
                list[nr - 1] = commit;
diff --combined combine-diff.c
index 22542217ee6aeba5058d02d99b27e20ca894af7f,2fd0ced3957a41d8c383b2487ddfdea77d1b3985..caffb926ea49de03c4ced157736626719439a756
@@@ -205,8 -205,7 +205,8 @@@ static void consume_line(void *state_, 
  }
  
  static void combine_diff(const unsigned char *parent, mmfile_t *result_file,
 -                       struct sline *sline, int cnt, int n, int num_parent)
 +                       struct sline *sline, unsigned int cnt, int n,
 +                       int num_parent)
  {
        unsigned int p_lno, lno;
        unsigned long nmask = (1UL << n);
@@@ -294,7 -293,7 +294,7 @@@ static unsigned long find_next(struct s
                               unsigned long mark,
                               unsigned long i,
                               unsigned long cnt,
 -                             int uninteresting)
 +                             int look_for_uninteresting)
  {
        /* We have examined up to i-1 and are about to look at i.
         * Find next interesting or uninteresting line.  Here,
         * that are surrounded by interesting() ones.
         */
        while (i <= cnt)
 -              if (uninteresting
 +              if (look_for_uninteresting
                    ? !(sline[i].flag & mark)
                    : (sline[i].flag & mark))
                        return i;
@@@ -490,7 -489,7 +490,7 @@@ static int make_hunks(struct sline *sli
        return has_interesting;
  }
  
 -static void show_parent_lno(struct sline *sline, unsigned long l0, unsigned long l1, unsigned long cnt, int n)
 +static void show_parent_lno(struct sline *sline, unsigned long l0, unsigned long l1, int n)
  {
        l0 = sline[l0].p_lno[n];
        l1 = sline[l1].p_lno[n];
@@@ -524,7 -523,7 +524,7 @@@ static void dump_sline(struct sline *sl
                        rlines--; /* pointing at the last delete hunk */
                for (i = 0; i <= num_parent; i++) putchar(combine_marker);
                for (i = 0; i < num_parent; i++)
 -                      show_parent_lno(sline, lno, hunk_end, cnt, i);
 +                      show_parent_lno(sline, lno, hunk_end, i);
                printf(" +%lu,%lu ", lno+1, rlines);
                for (i = 0; i <= num_parent; i++) putchar(combine_marker);
                putchar('\n');
@@@ -620,18 -619,18 +620,18 @@@ static int show_patch_diff(struct combi
                if (0 <= (fd = open(elem->path, O_RDONLY)) &&
                    !fstat(fd, &st)) {
                        int len = st.st_size;
 -                      int cnt = 0;
 +                      int sz = 0;
  
                        elem->mode = canon_mode(st.st_mode);
                        result_size = len;
                        result = xmalloc(len + 1);
 -                      while (cnt < len) {
 -                              int done = xread(fd, result+cnt, len-cnt);
 +                      while (sz < len) {
 +                              int done = xread(fd, result+sz, len-sz);
                                if (done == 0)
                                        break;
                                if (done < 0)
                                        die("read error '%s'", elem->path);
 -                              cnt += done;
 +                              sz += done;
                        }
                        result[len] = 0;
                }
                        close(fd);
        }
  
 -      for (cnt = 0, cp = result; cp - result < result_size; cp++) {
 +      for (cnt = 0, cp = result; cp < result + result_size; cp++) {
                if (*cp == '\n')
                        cnt++;
        }
                sline[lno].lost_tail = &sline[lno].lost_head;
                sline[lno].flag = 0;
        }
 -      for (lno = 0, cp = result; cp - result < result_size; cp++) {
 +      for (lno = 0, cp = result; cp < result + result_size; cp++) {
                if (*cp == '\n') {
                        sline[lno].len = cp - sline[lno].bol;
                        lno++;
                const char *abb;
  
                if (rev->loginfo)
-                       show_log(rev, rev->loginfo, "\n");
+                       show_log(rev, opt->msg_sep);
                dump_quoted_path(dense ? "diff --cc " : "diff --combined ", elem->path);
                printf("index ");
                for (i = 0; i < num_parent; i++) {
        }
        free(result);
  
 -      for (i = 0; i < cnt; i++) {
 -              if (sline[i].lost_head) {
 -                      struct lline *ll = sline[i].lost_head;
 +      for (lno = 0; lno < cnt; lno++) {
 +              if (sline[lno].lost_head) {
 +                      struct lline *ll = sline[lno].lost_head;
                        while (ll) {
                                struct lline *tmp = ll;
                                ll = ll->next;
@@@ -770,9 -769,9 +770,9 @@@ static void show_raw_diff(struct combin
                inter_name_termination = 0;
  
        if (rev->loginfo)
-               show_log(rev, rev->loginfo, "\n");
+               show_log(rev, opt->msg_sep);
  
-       if (opt->output_format == DIFF_FORMAT_RAW) {
+       if (opt->output_format & DIFF_FORMAT_RAW) {
                offset = strlen(COLONS) - num_parent;
                if (offset < 0)
                        offset = 0;
                printf(" %s ", diff_unique_abbrev(p->sha1, opt->abbrev));
        }
  
-       if (opt->output_format == DIFF_FORMAT_RAW ||
-           opt->output_format == DIFF_FORMAT_NAME_STATUS) {
+       if (opt->output_format & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME_STATUS)) {
                for (i = 0; i < num_parent; i++)
                        putchar(p->parent[i].status);
                putchar(inter_name_termination);
@@@ -819,17 -817,12 +818,12 @@@ void show_combined_diff(struct combine_
        struct diff_options *opt = &rev->diffopt;
        if (!p->len)
                return;
-       switch (opt->output_format) {
-       case DIFF_FORMAT_RAW:
-       case DIFF_FORMAT_NAME_STATUS:
-       case DIFF_FORMAT_NAME:
+       if (opt->output_format & (DIFF_FORMAT_RAW |
+                                 DIFF_FORMAT_NAME |
+                                 DIFF_FORMAT_NAME_STATUS)) {
                show_raw_diff(p, num_parent, rev);
-               return;
-       case DIFF_FORMAT_PATCH:
+       } else if (opt->output_format & DIFF_FORMAT_PATCH) {
                show_patch_diff(p, num_parent, dense, rev);
-               return;
-       default:
-               return;
        }
  }
  
@@@ -842,22 -835,20 +836,20 @@@ void diff_tree_combined(const unsigned 
        struct diff_options *opt = &rev->diffopt;
        struct diff_options diffopts;
        struct combine_diff_path *p, *paths = NULL;
-       int i, num_paths;
-       int do_diffstat;
+       int i, num_paths, needsep, show_log_first;
  
-       do_diffstat = (opt->output_format == DIFF_FORMAT_DIFFSTAT ||
-                      opt->with_stat);
        diffopts = *opt;
-       diffopts.with_raw = 0;
-       diffopts.with_stat = 0;
+       diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
        diffopts.recursive = 1;
  
+       show_log_first = !!rev->loginfo;
+       needsep = 0;
        /* find set of paths that everybody touches */
        for (i = 0; i < num_parent; i++) {
                /* show stat against the first parent even
                 * when doing combined diff.
                 */
-               if (i == 0 && do_diffstat)
+               if (i == 0 && opt->output_format & DIFF_FORMAT_DIFFSTAT)
                        diffopts.output_format = DIFF_FORMAT_DIFFSTAT;
                else
                        diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
                diffcore_std(&diffopts);
                paths = intersect_paths(paths, i, num_parent);
  
-               if (do_diffstat && rev->loginfo)
-                       show_log(rev, rev->loginfo,
-                                opt->with_stat ? "---\n" : "\n");
+               if (show_log_first && i == 0) {
+                       show_log(rev, opt->msg_sep);
+                       if (rev->verbose_header && opt->output_format)
+                               putchar(opt->line_termination);
+               }
                diff_flush(&diffopts);
-               if (opt->with_stat)
-                       putchar('\n');
        }
  
        /* find out surviving paths */
                        num_paths++;
        }
        if (num_paths) {
-               if (opt->with_raw) {
-                       int saved_format = opt->output_format;
-                       opt->output_format = DIFF_FORMAT_RAW;
+               if (opt->output_format & (DIFF_FORMAT_RAW |
+                                         DIFF_FORMAT_NAME |
+                                         DIFF_FORMAT_NAME_STATUS)) {
                        for (p = paths; p; p = p->next) {
-                               show_combined_diff(p, num_parent, dense, rev);
+                               if (p->len)
+                                       show_raw_diff(p, num_parent, rev);
                        }
-                       opt->output_format = saved_format;
-                       putchar(opt->line_termination);
+                       needsep = 1;
                }
-               for (p = paths; p; p = p->next) {
-                       show_combined_diff(p, num_parent, dense, rev);
+               else if (opt->output_format & DIFF_FORMAT_DIFFSTAT)
+                       needsep = 1;
+               if (opt->output_format & DIFF_FORMAT_PATCH) {
+                       if (needsep)
+                               putchar(opt->line_termination);
+                       for (p = paths; p; p = p->next) {
+                               if (p->len)
+                                       show_patch_diff(p, num_parent, dense,
+                                                       rev);
+                       }
                }
        }
  
diff --combined diff.c
index 428ff786ebdbd48a47ae5faf4c65c01e38403ad4,1c131ff4dcdab52abec6f182e1a9310820532189..507e4019e8bc2764daaf31ce76112ab509895704
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -583,7 -583,7 +583,7 @@@ static unsigned char *deflate_it(char *
        z_stream stream;
  
        memset(&stream, 0, sizeof(stream));
 -      deflateInit(&stream, Z_BEST_COMPRESSION);
 +      deflateInit(&stream, zlib_compression_level);
        bound = deflateBound(&stream, size);
        deflated = xmalloc(bound);
        stream.next_out = deflated;
@@@ -1420,11 -1420,11 +1420,11 @@@ static void run_checkdiff(struct diff_f
  void diff_setup(struct diff_options *options)
  {
        memset(options, 0, sizeof(*options));
-       options->output_format = DIFF_FORMAT_RAW;
        options->line_termination = '\n';
        options->break_opt = -1;
        options->rename_limit = -1;
        options->context = 3;
+       options->msg_sep = "";
  
        options->change = diff_change;
        options->add_remove = diff_addremove;
@@@ -1438,22 -1438,28 +1438,28 @@@ int diff_setup_done(struct diff_option
            (0 <= options->rename_limit && !options->detect_rename))
                return -1;
  
+       if (options->output_format & (DIFF_FORMAT_NAME |
+                                     DIFF_FORMAT_NAME_STATUS |
+                                     DIFF_FORMAT_CHECKDIFF |
+                                     DIFF_FORMAT_NO_OUTPUT))
+               options->output_format &= ~(DIFF_FORMAT_RAW |
+                                           DIFF_FORMAT_DIFFSTAT |
+                                           DIFF_FORMAT_SUMMARY |
+                                           DIFF_FORMAT_PATCH);
        /*
         * These cases always need recursive; we do not drop caller-supplied
         * recursive bits for other formats here.
         */
-       if ((options->output_format == DIFF_FORMAT_PATCH) ||
-           (options->output_format == DIFF_FORMAT_DIFFSTAT) ||
-           (options->output_format == DIFF_FORMAT_CHECKDIFF))
+       if (options->output_format & (DIFF_FORMAT_PATCH |
+                                     DIFF_FORMAT_DIFFSTAT |
+                                     DIFF_FORMAT_CHECKDIFF))
                options->recursive = 1;
        /*
-        * These combinations do not make sense.
+        * Also pickaxe would not work very well if you do not say recursive
         */
-       if (options->output_format == DIFF_FORMAT_RAW)
-               options->with_raw = 0;
-       if (options->output_format == DIFF_FORMAT_DIFFSTAT)
-               options->with_stat  = 0;
+       if (options->pickaxe)
+               options->recursive = 1;
  
        if (options->detect_rename && options->rename_limit < 0)
                options->rename_limit = diff_rename_limit_default;
@@@ -1526,22 -1532,22 +1532,22 @@@ int diff_opt_parse(struct diff_options 
  {
        const char *arg = av[0];
        if (!strcmp(arg, "-p") || !strcmp(arg, "-u"))
-               options->output_format = DIFF_FORMAT_PATCH;
+               options->output_format |= DIFF_FORMAT_PATCH;
        else if (opt_arg(arg, 'U', "unified", &options->context))
-               options->output_format = DIFF_FORMAT_PATCH;
+               options->output_format |= DIFF_FORMAT_PATCH;
+       else if (!strcmp(arg, "--raw"))
+               options->output_format |= DIFF_FORMAT_RAW;
        else if (!strcmp(arg, "--patch-with-raw")) {
-               options->output_format = DIFF_FORMAT_PATCH;
-               options->with_raw = 1;
+               options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW;
        }
        else if (!strcmp(arg, "--stat"))
-               options->output_format = DIFF_FORMAT_DIFFSTAT;
+               options->output_format |= DIFF_FORMAT_DIFFSTAT;
        else if (!strcmp(arg, "--check"))
-               options->output_format = DIFF_FORMAT_CHECKDIFF;
+               options->output_format |= DIFF_FORMAT_CHECKDIFF;
        else if (!strcmp(arg, "--summary"))
-               options->summary = 1;
+               options->output_format |= DIFF_FORMAT_SUMMARY;
        else if (!strcmp(arg, "--patch-with-stat")) {
-               options->output_format = DIFF_FORMAT_PATCH;
-               options->with_stat = 1;
+               options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_DIFFSTAT;
        }
        else if (!strcmp(arg, "-z"))
                options->line_termination = 0;
        else if (!strcmp(arg, "--full-index"))
                options->full_index = 1;
        else if (!strcmp(arg, "--binary")) {
-               options->output_format = DIFF_FORMAT_PATCH;
+               options->output_format |= DIFF_FORMAT_PATCH;
                options->full_index = options->binary = 1;
        }
        else if (!strcmp(arg, "--name-only"))
-               options->output_format = DIFF_FORMAT_NAME;
+               options->output_format |= DIFF_FORMAT_NAME;
        else if (!strcmp(arg, "--name-status"))
-               options->output_format = DIFF_FORMAT_NAME_STATUS;
+               options->output_format |= DIFF_FORMAT_NAME_STATUS;
        else if (!strcmp(arg, "-R"))
                options->reverse_diff = 1;
        else if (!strncmp(arg, "-S", 2))
                options->pickaxe = arg + 2;
-       else if (!strcmp(arg, "-s"))
-               options->output_format = DIFF_FORMAT_NO_OUTPUT;
+       else if (!strcmp(arg, "-s")) {
+               options->output_format |= DIFF_FORMAT_NO_OUTPUT;
+       }
        else if (!strncmp(arg, "-O", 2))
                options->orderfile = arg + 2;
        else if (!strncmp(arg, "--diff-filter=", 14))
@@@ -1737,15 -1744,17 +1744,17 @@@ const char *diff_unique_abbrev(const un
  }
  
  static void diff_flush_raw(struct diff_filepair *p,
-                          int line_termination,
-                          int inter_name_termination,
-                          struct diff_options *options,
-                          int output_format)
+                          struct diff_options *options)
  {
        int two_paths;
        char status[10];
        int abbrev = options->abbrev;
        const char *path_one, *path_two;
+       int inter_name_termination = '\t';
+       int line_termination = options->line_termination;
+       if (!line_termination)
+               inter_name_termination = 0;
  
        path_one = p->one->path;
        path_two = p->two->path;
                two_paths = 0;
                break;
        }
-       if (output_format != DIFF_FORMAT_NAME_STATUS) {
+       if (!(options->output_format & DIFF_FORMAT_NAME_STATUS)) {
                printf(":%06o %06o %s ",
                       p->one->mode, p->two->mode,
                       diff_unique_abbrev(p->one->sha1, abbrev));
@@@ -1983,48 -1992,30 +1992,30 @@@ static void diff_resolve_rename_copy(vo
        diff_debug_queue("resolve-rename-copy done", q);
  }
  
- static void flush_one_pair(struct diff_filepair *p,
-                          int diff_output_format,
-                          struct diff_options *options,
-                          struct diffstat_t *diffstat)
+ static int check_pair_status(struct diff_filepair *p)
  {
-       int inter_name_termination = '\t';
-       int line_termination = options->line_termination;
-       if (!line_termination)
-               inter_name_termination = 0;
        switch (p->status) {
        case DIFF_STATUS_UNKNOWN:
-               break;
+               return 0;
        case 0:
                die("internal error in diff-resolve-rename-copy");
-               break;
        default:
-               switch (diff_output_format) {
-               case DIFF_FORMAT_DIFFSTAT:
-                       diff_flush_stat(p, options, diffstat);
-                       break;
-               case DIFF_FORMAT_CHECKDIFF:
-                       diff_flush_checkdiff(p, options);
-                       break;
-               case DIFF_FORMAT_PATCH:
-                       diff_flush_patch(p, options);
-                       break;
-               case DIFF_FORMAT_RAW:
-               case DIFF_FORMAT_NAME_STATUS:
-                       diff_flush_raw(p, line_termination,
-                                      inter_name_termination,
-                                      options, diff_output_format);
-                       break;
-               case DIFF_FORMAT_NAME:
-                       diff_flush_name(p, line_termination);
-                       break;
-               case DIFF_FORMAT_NO_OUTPUT:
-                       break;
-               }
+               return 1;
        }
  }
  
+ static void flush_one_pair(struct diff_filepair *p, struct diff_options *opt)
+ {
+       int fmt = opt->output_format;
+       if (fmt & DIFF_FORMAT_CHECKDIFF)
+               diff_flush_checkdiff(p, opt);
+       else if (fmt & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME_STATUS))
+               diff_flush_raw(p, opt);
+       else if (fmt & DIFF_FORMAT_NAME)
+               diff_flush_name(p, opt->line_termination);
+ }
  static void show_file_mode_name(const char *newdelete, struct diff_filespec *fs)
  {
        if (fs->mode)
@@@ -2104,197 -2095,96 +2095,235 @@@ static void diff_summary(struct diff_fi
        }
  }
  
- void diff_flush(struct diff_options *options)
 +struct patch_id_t {
 +      struct xdiff_emit_state xm;
 +      SHA_CTX *ctx;
 +      int patchlen;
 +};
 +
 +static int remove_space(char *line, int len)
 +{
 +      int i;
 +        char *dst = line;
 +        unsigned char c;
 +
 +        for (i = 0; i < len; i++)
 +                if (!isspace((c = line[i])))
 +                        *dst++ = c;
 +
 +        return dst - line;
 +}
 +
 +static void patch_id_consume(void *priv, char *line, unsigned long len)
 +{
 +      struct patch_id_t *data = priv;
 +      int new_len;
 +
 +      /* Ignore line numbers when computing the SHA1 of the patch */
 +      if (!strncmp(line, "@@ -", 4))
 +              return;
 +
 +      new_len = remove_space(line, len);
 +
 +      SHA1_Update(data->ctx, line, new_len);
 +      data->patchlen += new_len;
 +}
 +
 +/* returns 0 upon success, and writes result into sha1 */
 +static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
 +{
 +      struct diff_queue_struct *q = &diff_queued_diff;
 +      int i;
 +      SHA_CTX ctx;
 +      struct patch_id_t data;
 +      char buffer[PATH_MAX * 4 + 20];
 +
 +      SHA1_Init(&ctx);
 +      memset(&data, 0, sizeof(struct patch_id_t));
 +      data.ctx = &ctx;
 +      data.xm.consume = patch_id_consume;
 +
 +      for (i = 0; i < q->nr; i++) {
 +              xpparam_t xpp;
 +              xdemitconf_t xecfg;
 +              xdemitcb_t ecb;
 +              mmfile_t mf1, mf2;
 +              struct diff_filepair *p = q->queue[i];
 +              int len1, len2;
 +
 +              if (p->status == 0)
 +                      return error("internal diff status error");
 +              if (p->status == DIFF_STATUS_UNKNOWN)
 +                      continue;
 +              if (diff_unmodified_pair(p))
 +                      continue;
 +              if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
 +                  (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
 +                      continue;
 +              if (DIFF_PAIR_UNMERGED(p))
 +                      continue;
 +
 +              diff_fill_sha1_info(p->one);
 +              diff_fill_sha1_info(p->two);
 +              if (fill_mmfile(&mf1, p->one) < 0 ||
 +                              fill_mmfile(&mf2, p->two) < 0)
 +                      return error("unable to read files to diff");
 +
 +              /* Maybe hash p->two? into the patch id? */
 +              if (mmfile_is_binary(&mf2))
 +                      continue;
 +
 +              len1 = remove_space(p->one->path, strlen(p->one->path));
 +              len2 = remove_space(p->two->path, strlen(p->two->path));
 +              if (p->one->mode == 0)
 +                      len1 = snprintf(buffer, sizeof(buffer),
 +                                      "diff--gita/%.*sb/%.*s"
 +                                      "newfilemode%06o"
 +                                      "---/dev/null"
 +                                      "+++b/%.*s",
 +                                      len1, p->one->path,
 +                                      len2, p->two->path,
 +                                      p->two->mode,
 +                                      len2, p->two->path);
 +              else if (p->two->mode == 0)
 +                      len1 = snprintf(buffer, sizeof(buffer),
 +                                      "diff--gita/%.*sb/%.*s"
 +                                      "deletedfilemode%06o"
 +                                      "---a/%.*s"
 +                                      "+++/dev/null",
 +                                      len1, p->one->path,
 +                                      len2, p->two->path,
 +                                      p->one->mode,
 +                                      len1, p->one->path);
 +              else
 +                      len1 = snprintf(buffer, sizeof(buffer),
 +                                      "diff--gita/%.*sb/%.*s"
 +                                      "---a/%.*s"
 +                                      "+++b/%.*s",
 +                                      len1, p->one->path,
 +                                      len2, p->two->path,
 +                                      len1, p->one->path,
 +                                      len2, p->two->path);
 +              SHA1_Update(&ctx, buffer, len1);
 +
 +              xpp.flags = XDF_NEED_MINIMAL;
 +              xecfg.ctxlen = 3;
 +              xecfg.flags = XDL_EMIT_FUNCNAMES;
 +              ecb.outf = xdiff_outf;
 +              ecb.priv = &data;
 +              xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
 +      }
 +
 +      SHA1_Final(sha1, &ctx);
 +      return 0;
 +}
 +
 +int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1)
 +{
 +      struct diff_queue_struct *q = &diff_queued_diff;
 +      int i;
 +      int result = diff_get_patch_id(options, sha1);
 +
 +      for (i = 0; i < q->nr; i++)
 +              diff_free_filepair(q->queue[i]);
 +
 +      free(q->queue);
 +      q->queue = NULL;
 +      q->nr = q->alloc = 0;
 +
 +      return result;
 +}
 +
+ static int is_summary_empty(const struct diff_queue_struct *q)
  {
-       struct diff_queue_struct *q = &diff_queued_diff;
        int i;
-       int diff_output_format = options->output_format;
-       struct diffstat_t *diffstat = NULL;
  
-       if (diff_output_format == DIFF_FORMAT_DIFFSTAT || options->with_stat) {
-               diffstat = xcalloc(sizeof (struct diffstat_t), 1);
-               diffstat->xm.consume = diffstat_consume;
+       for (i = 0; i < q->nr; i++) {
+               const struct diff_filepair *p = q->queue[i];
+               switch (p->status) {
+               case DIFF_STATUS_DELETED:
+               case DIFF_STATUS_ADDED:
+               case DIFF_STATUS_COPIED:
+               case DIFF_STATUS_RENAMED:
+                       return 0;
+               default:
+                       if (p->score)
+                               return 0;
+                       if (p->one->mode && p->two->mode &&
+                           p->one->mode != p->two->mode)
+                               return 0;
+                       break;
+               }
        }
+       return 1;
+ }
+ void diff_flush(struct diff_options *options)
+ {
+       struct diff_queue_struct *q = &diff_queued_diff;
+       int i, output_format = options->output_format;
+       int separator = 0;
+       /*
+        * Order: raw, stat, summary, patch
+        * or:    name/name-status/checkdiff (other bits clear)
+        */
+       if (!q->nr)
+               goto free_queue;
  
-       if (options->with_raw) {
+       if (output_format & (DIFF_FORMAT_RAW |
+                            DIFF_FORMAT_NAME |
+                            DIFF_FORMAT_NAME_STATUS |
+                            DIFF_FORMAT_CHECKDIFF)) {
                for (i = 0; i < q->nr; i++) {
                        struct diff_filepair *p = q->queue[i];
-                       flush_one_pair(p, DIFF_FORMAT_RAW, options, NULL);
+                       if (check_pair_status(p))
+                               flush_one_pair(p, options);
                }
-               putchar(options->line_termination);
+               separator++;
        }
-       if (options->with_stat) {
+       if (output_format & DIFF_FORMAT_DIFFSTAT) {
+               struct diffstat_t diffstat;
+               memset(&diffstat, 0, sizeof(struct diffstat_t));
+               diffstat.xm.consume = diffstat_consume;
                for (i = 0; i < q->nr; i++) {
                        struct diff_filepair *p = q->queue[i];
-                       flush_one_pair(p, DIFF_FORMAT_DIFFSTAT, options,
-                                      diffstat);
+                       if (check_pair_status(p))
+                               diff_flush_stat(p, options, &diffstat);
                }
-               show_stats(diffstat);
-               free(diffstat);
-               diffstat = NULL;
-               if (options->summary)
-                       for (i = 0; i < q->nr; i++)
-                               diff_summary(q->queue[i]);
-               if (options->stat_sep)
-                       fputs(options->stat_sep, stdout);
-               else
-                       putchar(options->line_termination);
-       }
-       for (i = 0; i < q->nr; i++) {
-               struct diff_filepair *p = q->queue[i];
-               flush_one_pair(p, diff_output_format, options, diffstat);
+               show_stats(&diffstat);
+               separator++;
        }
  
-       if (diffstat) {
-               show_stats(diffstat);
-               free(diffstat);
+       if (output_format & DIFF_FORMAT_SUMMARY && !is_summary_empty(q)) {
+               for (i = 0; i < q->nr; i++)
+                       diff_summary(q->queue[i]);
+               separator++;
        }
  
-       for (i = 0; i < q->nr; i++) {
-               if (diffstat && options->summary)
-                       diff_summary(q->queue[i]);
-               diff_free_filepair(q->queue[i]);
+       if (output_format & DIFF_FORMAT_PATCH) {
+               if (separator) {
+                       if (options->stat_sep) {
+                               /* attach patch instead of inline */
+                               fputs(options->stat_sep, stdout);
+                       } else {
+                               putchar(options->line_termination);
+                       }
+               }
+               for (i = 0; i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       if (check_pair_status(p))
+                               diff_flush_patch(p, options);
+               }
        }
  
+       for (i = 0; i < q->nr; i++)
+               diff_free_filepair(q->queue[i]);
+ free_queue:
        free(q->queue);
        q->queue = NULL;
        q->nr = q->alloc = 0;
diff --combined diff.h
index d5068af7d16a0905edfeaa24276461c05437f3be,729cd02510cfb3d39646475131ee523f03def8fb..d5573947b3295a00258cec2cb07905da5a5b8292
--- 1/diff.h
--- 2/diff.h
+++ b/diff.h
@@@ -20,19 -20,31 +20,31 @@@ typedef void (*add_remove_fn_t)(struct 
                    const unsigned char *sha1,
                    const char *base, const char *path);
  
+ #define DIFF_FORMAT_RAW               0x0001
+ #define DIFF_FORMAT_DIFFSTAT  0x0002
+ #define DIFF_FORMAT_SUMMARY   0x0004
+ #define DIFF_FORMAT_PATCH     0x0008
+ /* These override all above */
+ #define DIFF_FORMAT_NAME      0x0010
+ #define DIFF_FORMAT_NAME_STATUS       0x0020
+ #define DIFF_FORMAT_CHECKDIFF 0x0040
+ /* Same as output_format = 0 but we know that -s flag was given
+  * and we should not give default value to output_format.
+  */
+ #define DIFF_FORMAT_NO_OUTPUT 0x0080
  struct diff_options {
        const char *filter;
        const char *orderfile;
        const char *pickaxe;
        unsigned recursive:1,
-                with_raw:1,
-                with_stat:1,
                 tree_in_recursive:1,
                 binary:1,
                 full_index:1,
                 silent_on_remove:1,
                 find_copies_harder:1,
-                summary:1,
                 color_diff:1;
        int context;
        int break_opt;
@@@ -45,6 -57,7 +57,7 @@@
        int rename_limit;
        int setup;
        int abbrev;
+       const char *msg_sep;
        const char *stat_sep;
        long xdl_opts;
  
@@@ -151,15 -164,6 +164,6 @@@ extern void diffcore_std_no_resolve(str
  "                show all files diff when -S is used and hit is found.\n"
  
  extern int diff_queue_is_empty(void);
- #define DIFF_FORMAT_RAW               1
- #define DIFF_FORMAT_PATCH     2
- #define DIFF_FORMAT_NO_OUTPUT 3
- #define DIFF_FORMAT_NAME      4
- #define DIFF_FORMAT_NAME_STATUS       5
- #define DIFF_FORMAT_DIFFSTAT  6
- #define DIFF_FORMAT_CHECKDIFF 7
  extern void diff_flush(struct diff_options*);
  
  /* diff-raw status letters */
@@@ -184,6 -188,4 +188,6 @@@ extern int run_diff_files(struct rev_in
  
  extern int run_diff_index(struct rev_info *revs, int cached);
  
 +extern int diff_flush_patch_id(struct diff_options *, unsigned char *);
 +
  #endif /* DIFF_H */
diff --combined revision.c
index 880fb7bb30b75d9c5bb6a8aa037a6048cba5c922,ae4ca8200367a919ed5166a96624abbbe8fef15c..ab89c22417cace2ee56cd88a7135967690be586a
@@@ -280,7 -280,7 +280,7 @@@ int rev_same_tree_as_empty(struct rev_i
  static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
  {
        struct commit_list **pp, *parent;
 -      int tree_changed = 0;
 +      int tree_changed = 0, tree_same = 0;
  
        if (!commit->tree)
                return;
                parse_commit(p);
                switch (rev_compare_tree(revs, p->tree, commit->tree)) {
                case REV_TREE_SAME:
 +                      tree_same = 1;
                        if (!revs->simplify_history || (p->object.flags & UNINTERESTING)) {
                                /* Even if a merge with an uninteresting
                                 * side branch brought the entire change
                }
                die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
        }
 -      if (tree_changed)
 +      if (tree_changed && !tree_same)
                commit->object.flags |= TREECHANGE;
  }
  
@@@ -852,8 -851,7 +852,7 @@@ int setup_revisions(int argc, const cha
        }
        if (revs->combine_merges) {
                revs->ignore_merges = 0;
-               if (revs->dense_combined_merges &&
-                   (revs->diffopt.output_format != DIFF_FORMAT_DIFFSTAT))
+               if (revs->dense_combined_merges && !revs->diffopt.output_format)
                        revs->diffopt.output_format = DIFF_FORMAT_PATCH;
        }
        revs->diffopt.abbrev = revs->abbrev;
@@@ -897,8 -895,6 +896,8 @@@ static int rewrite_one(struct rev_info 
                struct commit *p = *pp;
                if (!revs->limited)
                        add_parents_to_list(revs, p, &revs->commits);
 +              if (p->parents && p->parents->next)
 +                      return 0;
                if (p->object.flags & (TREECHANGE | UNINTERESTING))
                        return 0;
                if (!p->parents)
@@@ -991,15 -987,8 +990,15 @@@ struct commit *get_revision(struct rev_
                    commit->parents && commit->parents->next)
                        continue;
                if (revs->prune_fn && revs->dense) {
 -                      if (!(commit->object.flags & TREECHANGE))
 -                              continue;
 +                      /* Commit without changes? */
 +                      if (!(commit->object.flags & TREECHANGE)) {
 +                              /* drop merges unless we want parenthood */
 +                              if (!revs->parents)
 +                                      continue;
 +                              /* non-merge - always ignore it */
 +                              if (!commit->parents || !commit->parents->next)
 +                                      continue;
 +                      }
                        if (revs->parents)
                                rewrite_parents(revs, commit);
                }