configure.ac: link with -liconv for locale_charset()
[gitweb.git] / revision.c
index 84ccc0529b75508be5d1603a9e73790ca2b102fe..f82b833fbceada4c4b32e669fd33966c751249e8 100644 (file)
@@ -15,6 +15,7 @@
 #include "string-list.h"
 #include "line-log.h"
 #include "mailmap.h"
+#include "commit-slab.h"
 
 volatile show_early_output_fn_t show_early_output;
 
@@ -1419,26 +1420,40 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi
                }
                if (!get_sha1_committish(this, from_sha1) &&
                    !get_sha1_committish(next, sha1)) {
-                       struct commit *a, *b;
-                       struct commit_list *exclude;
-
-                       a = lookup_commit_reference(from_sha1);
-                       b = lookup_commit_reference(sha1);
-                       if (!a || !b) {
-                               if (revs->ignore_missing)
-                                       return 0;
-                               die(symmetric ?
-                                   "Invalid symmetric difference expression %s...%s" :
-                                   "Invalid revision range %s..%s",
-                                   arg, next);
-                       }
+                       struct object *a_obj, *b_obj;
 
                        if (!cant_be_filename) {
                                *dotdot = '.';
                                verify_non_filename(revs->prefix, arg);
                        }
 
-                       if (symmetric) {
+                       a_obj = parse_object(from_sha1);
+                       b_obj = parse_object(sha1);
+                       if (!a_obj || !b_obj) {
+                       missing:
+                               if (revs->ignore_missing)
+                                       return 0;
+                               die(symmetric
+                                   ? "Invalid symmetric difference expression %s"
+                                   : "Invalid revision range %s", arg);
+                       }
+
+                       if (!symmetric) {
+                               /* just A..B */
+                               a_flags = flags_exclude;
+                       } else {
+                               /* A...B -- find merge bases between the two */
+                               struct commit *a, *b;
+                               struct commit_list *exclude;
+
+                               a = (a_obj->type == OBJ_COMMIT
+                                    ? (struct commit *)a_obj
+                                    : lookup_commit_reference(a_obj->sha1));
+                               b = (b_obj->type == OBJ_COMMIT
+                                    ? (struct commit *)b_obj
+                                    : lookup_commit_reference(b_obj->sha1));
+                               if (!a || !b)
+                                       goto missing;
                                exclude = get_merge_bases(a, b, 1);
                                add_rev_cmdline_list(revs, exclude,
                                                     REV_CMD_MERGE_BASE,
@@ -1446,17 +1461,18 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi
                                add_pending_commit_list(revs, exclude,
                                                        flags_exclude);
                                free_commit_list(exclude);
+
                                a_flags = flags | SYMMETRIC_LEFT;
-                       } else
-                               a_flags = flags_exclude;
-                       a->object.flags |= a_flags;
-                       b->object.flags |= flags;
-                       add_rev_cmdline(revs, &a->object, this,
+                       }
+
+                       a_obj->flags |= a_flags;
+                       b_obj->flags |= flags;
+                       add_rev_cmdline(revs, a_obj, this,
                                        REV_CMD_LEFT, a_flags);
-                       add_rev_cmdline(revs, &b->object, next,
+                       add_rev_cmdline(revs, b_obj, next,
                                        REV_CMD_RIGHT, flags);
-                       add_pending_object(revs, &a->object, this);
-                       add_pending_object(revs, &b->object, next);
+                       add_pending_object(revs, a_obj, this);
+                       add_pending_object(revs, b_obj, next);
                        return 0;
                }
                *dotdot = '.';
@@ -2763,7 +2779,7 @@ static int commit_match(struct commit *commit, struct rev_info *opt)
        return retval;
 }
 
-static inline int want_ancestry(struct rev_info *revs)
+static inline int want_ancestry(const struct rev_info *revs)
 {
        return (revs->rewrite_parents || revs->children.name);
 }
@@ -2820,6 +2836,14 @@ enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
        if (action == commit_show &&
            !revs->show_all &&
            revs->prune && revs->dense && want_ancestry(revs)) {
+               /*
+                * --full-diff on simplified parents is no good: it
+                * will show spurious changes from the commits that
+                * were elided.  So we save the parents on the side
+                * when --full-diff is in effect.
+                */
+               if (revs->full_diff)
+                       save_parents(revs, commit);
                if (rewrite_parents(revs, commit, rewrite_one) < 0)
                        return commit_error;
        }
@@ -2839,6 +2863,7 @@ static struct commit *get_revision_1(struct rev_info *revs)
                free(entry);
 
                if (revs->reflog_info) {
+                       save_parents(revs, commit);
                        fake_reflog_parent(revs->reflog_info, commit);
                        commit->object.flags &= ~(ADDED | SEEN | SHOWN);
                }
@@ -3038,6 +3063,8 @@ struct commit *get_revision(struct rev_info *revs)
        c = get_revision_internal(revs);
        if (c && revs->graph)
                graph_update(revs->graph, c);
+       if (!c)
+               free_saved_parents(revs);
        return c;
 }
 
@@ -3069,3 +3096,54 @@ void put_revision_mark(const struct rev_info *revs, const struct commit *commit)
        fputs(mark, stdout);
        putchar(' ');
 }
+
+define_commit_slab(saved_parents, struct commit_list *);
+
+#define EMPTY_PARENT_LIST ((struct commit_list *)-1)
+
+void save_parents(struct rev_info *revs, struct commit *commit)
+{
+       struct commit_list **pp;
+
+       if (!revs->saved_parents_slab) {
+               revs->saved_parents_slab = xmalloc(sizeof(struct saved_parents));
+               init_saved_parents(revs->saved_parents_slab);
+       }
+
+       pp = saved_parents_at(revs->saved_parents_slab, commit);
+
+       /*
+        * When walking with reflogs, we may visit the same commit
+        * several times: once for each appearance in the reflog.
+        *
+        * In this case, save_parents() will be called multiple times.
+        * We want to keep only the first set of parents.  We need to
+        * store a sentinel value for an empty (i.e., NULL) parent
+        * list to distinguish it from a not-yet-saved list, however.
+        */
+       if (*pp)
+               return;
+       if (commit->parents)
+               *pp = copy_commit_list(commit->parents);
+       else
+               *pp = EMPTY_PARENT_LIST;
+}
+
+struct commit_list *get_saved_parents(struct rev_info *revs, const struct commit *commit)
+{
+       struct commit_list *parents;
+
+       if (!revs->saved_parents_slab)
+               return commit->parents;
+
+       parents = *saved_parents_at(revs->saved_parents_slab, commit);
+       if (parents == EMPTY_PARENT_LIST)
+               return NULL;
+       return parents;
+}
+
+void free_saved_parents(struct rev_info *revs)
+{
+       if (revs->saved_parents_slab)
+               clear_saved_parents(revs->saved_parents_slab);
+}