git log -p --merge [[--] paths...]
[gitweb.git] / revision.c
index b963f2adfd92050310f7327e1ee10e21d5996a7c..7df9089f530d5171dc267d95eed4cf90ea51ea69 100644 (file)
@@ -280,7 +280,7 @@ int rev_same_tree_as_empty(struct rev_info *revs, struct tree *t1)
 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;
@@ -298,6 +298,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
                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
@@ -334,7 +335,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
                }
                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;
 }
 
@@ -536,6 +537,61 @@ void init_revisions(struct rev_info *revs)
        diff_setup(&revs->diffopt);
 }
 
+static void add_pending_commit_list(struct rev_info *revs,
+                                    struct commit_list *commit_list,
+                                    unsigned int flags)
+{
+       while (commit_list) {
+               struct object *object = &commit_list->item->object;
+               object->flags |= flags;
+               add_pending_object(revs, object, sha1_to_hex(object->sha1));
+               commit_list = commit_list->next;
+       }
+}
+
+static void prepare_show_merge(struct rev_info *revs)
+{
+       struct commit_list *bases;
+       struct commit *head, *other;
+       unsigned char sha1[20];
+       const char **prune = NULL;
+       int i, prune_num = 1; /* counting terminating NULL */
+
+       if (get_sha1("HEAD", sha1) || !(head = lookup_commit(sha1)))
+               die("--merge without HEAD?");
+       if (get_sha1("MERGE_HEAD", sha1) || !(other = lookup_commit(sha1)))
+               die("--merge without MERGE_HEAD?");
+       add_pending_object(revs, &head->object, "HEAD");
+       add_pending_object(revs, &other->object, "MERGE_HEAD");
+       bases = get_merge_bases(head, other, 1);
+       while (bases) {
+               struct commit *it = bases->item;
+               struct commit_list *n = bases->next;
+               free(bases);
+               bases = n;
+               it->object.flags |= UNINTERESTING;
+               add_pending_object(revs, &it->object, "(merge-base)");
+       }
+
+       if (!active_nr)
+               read_cache();
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (!ce_stage(ce))
+                       continue;
+               if (ce_path_match(ce, revs->prune_data)) {
+                       prune_num++;
+                       prune = xrealloc(prune, sizeof(*prune) * prune_num);
+                       prune[prune_num-2] = ce->name;
+                       prune[prune_num-1] = NULL;
+               }
+               while ((i+1 < active_nr) &&
+                      ce_same_name(ce, active_cache[i+1]))
+                       i++;
+       }
+       revs->prune_data = prune;
+}
+
 /*
  * Parse revision information, filling in the "rev_info" structure,
  * and removing the used arguments from the argument list.
@@ -545,7 +601,7 @@ void init_revisions(struct rev_info *revs)
  */
 int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def)
 {
-       int i, flags, seen_dashdash;
+       int i, flags, seen_dashdash, show_merge;
        const char **unrecognized = argv + 1;
        int left = 1;
 
@@ -562,7 +618,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                break;
        }
 
-       flags = 0;
+       flags = show_merge = 0;
        for (i = 1; i < argc; i++) {
                struct object *object;
                const char *arg = argv[i];
@@ -629,6 +685,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                def = argv[i];
                                continue;
                        }
+                       if (!strcmp(arg, "--merge")) {
+                               show_merge = 1;
+                               continue;
+                       }
                        if (!strcmp(arg, "--topo-order")) {
                                revs->topo_order = 1;
                                continue;
@@ -771,27 +831,46 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                        unsigned char from_sha1[20];
                        const char *next = dotdot + 2;
                        const char *this = arg;
+                       int symmetric = *next == '.';
+                       unsigned int flags_exclude = flags ^ UNINTERESTING;
+
                        *dotdot = 0;
+                       next += symmetric;
+
                        if (!*next)
                                next = "HEAD";
                        if (dotdot == arg)
                                this = "HEAD";
                        if (!get_sha1(this, from_sha1) &&
                            !get_sha1(next, sha1)) {
-                               struct object *exclude;
-                               struct object *include;
-
-                               exclude = get_reference(revs, this, from_sha1, flags ^ UNINTERESTING);
-                               include = get_reference(revs, next, sha1, flags);
-                               if (!exclude || !include)
-                                       die("Invalid revision range %s..%s", arg, next);
+                               struct commit *a, *b;
+                               struct commit_list *exclude;
+
+                               a = lookup_commit_reference(from_sha1);
+                               b = lookup_commit_reference(sha1);
+                               if (!a || !b) {
+                                       die(symmetric ?
+                                           "Invalid symmetric difference expression %s...%s" :
+                                           "Invalid revision range %s..%s",
+                                           arg, next);
+                               }
 
                                if (!seen_dashdash) {
                                        *dotdot = '.';
                                        verify_non_filename(revs->prefix, arg);
                                }
-                               add_pending_object(revs, exclude, this);
-                               add_pending_object(revs, include, next);
+
+                               if (symmetric) {
+                                       exclude = get_merge_bases(a, b, 1);
+                                       add_pending_commit_list(revs, exclude,
+                                                               flags_exclude);
+                                       free_commit_list(exclude);
+                                       a->object.flags |= flags;
+                               } else
+                                       a->object.flags |= flags_exclude;
+                               b->object.flags |= flags;
+                               add_pending_object(revs, &a->object, this);
+                               add_pending_object(revs, &b->object, next);
                                continue;
                        }
                        *dotdot = '.';
@@ -831,6 +910,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                object = get_reference(revs, arg, sha1, flags ^ local_flags);
                add_pending_object(revs, object, arg);
        }
+       if (show_merge)
+               prepare_show_merge(revs);
        if (def && !revs->pending.nr) {
                unsigned char sha1[20];
                struct object *object;
@@ -851,8 +932,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
        }
        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;
@@ -896,6 +976,8 @@ static int rewrite_one(struct rev_info *revs, struct commit **pp)
                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)
@@ -988,8 +1070,15 @@ struct commit *get_revision(struct rev_info *revs)
                    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);
                }