Code

Add --relative-date option to the revision interface
[git.git] / revision.c
index d41e59b9ccbe30d0589d52d73e0c7bef26394a23..b588f7487035027755954575f922afee99651662 100644 (file)
@@ -31,17 +31,12 @@ static char *path_name(struct name_path *path, const char *name)
        return n;
 }
 
-struct object_list **add_object(struct object *obj,
-                                      struct object_list **p,
-                                      struct name_path *path,
-                                      const char *name)
+void add_object(struct object *obj,
+               struct object_array *p,
+               struct name_path *path,
+               const char *name)
 {
-       struct object_list *entry = xmalloc(sizeof(*entry));
-       entry->item = obj;
-       entry->next = *p;
-       entry->name = path_name(path, name);
-       *p = entry;
-       return &entry->next;
+       add_object_array(obj, path_name(path, name), p);
 }
 
 static void mark_blob_uninteresting(struct blob *blob)
@@ -53,8 +48,9 @@ static void mark_blob_uninteresting(struct blob *blob)
 
 void mark_tree_uninteresting(struct tree *tree)
 {
+       struct tree_desc desc;
+       struct name_entry entry;
        struct object *obj = &tree->object;
-       struct tree_entry_list *entry;
 
        if (obj->flags & UNINTERESTING)
                return;
@@ -63,17 +59,22 @@ void mark_tree_uninteresting(struct tree *tree)
                return;
        if (parse_tree(tree) < 0)
                die("bad tree %s", sha1_to_hex(obj->sha1));
-       entry = tree->entries;
-       tree->entries = NULL;
-       while (entry) {
-               struct tree_entry_list *next = entry->next;
-               if (entry->directory)
-                       mark_tree_uninteresting(entry->item.tree);
+
+       desc.buf = tree->buffer;
+       desc.size = tree->size;
+       while (tree_entry(&desc, &entry)) {
+               if (S_ISDIR(entry.mode))
+                       mark_tree_uninteresting(lookup_tree(entry.sha1));
                else
-                       mark_blob_uninteresting(entry->item.blob);
-               free(entry);
-               entry = next;
+                       mark_blob_uninteresting(lookup_blob(entry.sha1));
        }
+
+       /*
+        * We don't care about the tree any more
+        * after it has been marked uninteresting.
+        */
+       free(tree->buffer);
+       tree->buffer = NULL;
 }
 
 void mark_parents_uninteresting(struct commit *commit)
@@ -111,26 +112,32 @@ void mark_parents_uninteresting(struct commit *commit)
        }
 }
 
-static void add_pending_object(struct rev_info *revs, struct object *obj, const char *name)
+void add_pending_object(struct rev_info *revs, struct object *obj, const char *name)
 {
-       add_object(obj, &revs->pending_objects, NULL, name);
+       add_object_array(obj, name, &revs->pending);
 }
 
-static struct commit *get_commit_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags)
+static struct object *get_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags)
 {
        struct object *object;
 
        object = parse_object(sha1);
        if (!object)
                die("bad object %s", name);
+       object->flags |= flags;
+       return object;
+}
+
+static struct commit *handle_commit(struct rev_info *revs, struct object *object, const char *name)
+{
+       unsigned long flags = object->flags;
 
        /*
         * Tag object? Look what it points to..
         */
-       while (object->type == tag_type) {
+       while (object->type == OBJ_TAG) {
                struct tag *tag = (struct tag *) object;
-               object->flags |= flags;
-               if (revs->tag_objects && !(object->flags & UNINTERESTING))
+               if (revs->tag_objects && !(flags & UNINTERESTING))
                        add_pending_object(revs, object, tag->tag);
                object = parse_object(tag->tagged->sha1);
                if (!object)
@@ -141,12 +148,12 @@ static struct commit *get_commit_reference(struct rev_info *revs, const char *na
         * Commit object? Just return it, we'll do all the complex
         * reachability crud.
         */
-       if (object->type == commit_type) {
+       if (object->type == OBJ_COMMIT) {
                struct commit *commit = (struct commit *)object;
-               object->flags |= flags;
                if (parse_commit(commit) < 0)
                        die("unable to parse commit %s", name);
                if (flags & UNINTERESTING) {
+                       commit->object.flags |= UNINTERESTING;
                        mark_parents_uninteresting(commit);
                        revs->limited = 1;
                }
@@ -157,7 +164,7 @@ static struct commit *get_commit_reference(struct rev_info *revs, const char *na
         * Tree object? Either mark it uniniteresting, or add it
         * to the list of objects to look at later..
         */
-       if (object->type == tree_type) {
+       if (object->type == OBJ_TREE) {
                struct tree *tree = (struct tree *)object;
                if (!revs->tree_objects)
                        return NULL;
@@ -172,7 +179,7 @@ static struct commit *get_commit_reference(struct rev_info *revs, const char *na
        /*
         * Blob object? You know the drill by now..
         */
-       if (object->type == blob_type) {
+       if (object->type == OBJ_BLOB) {
                struct blob *blob = (struct blob *)object;
                if (!revs->blob_objects)
                        return NULL;
@@ -241,7 +248,7 @@ int rev_compare_tree(struct rev_info *revs, struct tree *t1, struct tree *t2)
                return REV_TREE_DIFFERENT;
        tree_difference = REV_TREE_SAME;
        if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "",
-                          &revs->diffopt) < 0)
+                          &revs->pruning) < 0)
                return REV_TREE_DIFFERENT;
        return tree_difference;
 }
@@ -264,7 +271,7 @@ int rev_same_tree_as_empty(struct rev_info *revs, struct tree *t1)
        empty.size = 0;
 
        tree_difference = 0;
-       retval = diff_tree(&empty, &real, "", &revs->diffopt);
+       retval = diff_tree(&empty, &real, "", &revs->pruning);
        free(tree);
 
        return retval >= 0 && !tree_difference;
@@ -273,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;
@@ -291,7 +298,8 @@ 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:
-                       if (p->object.flags & UNINTERESTING) {
+                       tree_same = 1;
+                       if (!revs->simplify_history || (p->object.flags & UNINTERESTING)) {
                                /* Even if a merge with an uninteresting
                                 * side branch brought the entire change
                                 * we are interested in, we do not want
@@ -327,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;
 }
 
@@ -375,6 +383,9 @@ static void add_parents_to_list(struct rev_info *revs, struct commit *commit, st
        if (revs->prune_fn)
                revs->prune_fn(revs, commit);
 
+       if (revs->no_walk)
+               return;
+
        parent = commit->parents;
        while (parent) {
                struct commit *p = parent->item;
@@ -451,21 +462,13 @@ static void limit_list(struct rev_info *revs)
        revs->commits = newlist;
 }
 
-static void add_one_commit(struct commit *commit, struct rev_info *revs)
-{
-       if (!commit || (commit->object.flags & SEEN))
-               return;
-       commit->object.flags |= SEEN;
-       commit_list_insert(commit, &revs->commits);
-}
-
 static int all_flags;
 static struct rev_info *all_revs;
 
 static int handle_one_ref(const char *path, const unsigned char *sha1)
 {
-       struct commit *commit = get_commit_reference(all_revs, path, sha1, all_flags);
-       add_one_commit(commit, all_revs);
+       struct object *object = get_reference(all_revs, path, sha1, all_flags);
+       add_pending_object(all_revs, object, "");
        return 0;
 }
 
@@ -476,15 +479,49 @@ static void handle_all(struct rev_info *revs, unsigned flags)
        for_each_ref(handle_one_ref);
 }
 
-void init_revisions(struct rev_info *revs)
+static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
+{
+       unsigned char sha1[20];
+       struct object *it;
+       struct commit *commit;
+       struct commit_list *parents;
+
+       if (*arg == '^') {
+               flags ^= UNINTERESTING;
+               arg++;
+       }
+       if (get_sha1(arg, sha1))
+               return 0;
+       while (1) {
+               it = get_reference(revs, arg, sha1, 0);
+               if (it->type != OBJ_TAG)
+                       break;
+               hashcpy(sha1, ((struct tag*)it)->tagged->sha1);
+       }
+       if (it->type != OBJ_COMMIT)
+               return 0;
+       commit = (struct commit *)it;
+       for (parents = commit->parents; parents; parents = parents->next) {
+               it = &parents->item->object;
+               it->flags |= flags;
+               add_pending_object(revs, it, arg);
+       }
+       return 1;
+}
+
+void init_revisions(struct rev_info *revs, const char *prefix)
 {
        memset(revs, 0, sizeof(*revs));
-       revs->diffopt.recursive = 1;
-       revs->diffopt.add_remove = file_add_remove;
-       revs->diffopt.change = file_change;
+
+       revs->abbrev = DEFAULT_ABBREV;
+       revs->ignore_merges = 1;
+       revs->simplify_history = 1;
+       revs->pruning.recursive = 1;
+       revs->pruning.add_remove = file_add_remove;
+       revs->pruning.change = file_change;
        revs->lifo = 1;
        revs->dense = 1;
-       revs->prefix = setup_git_directory();
+       revs->prefix = prefix;
        revs->max_age = -1;
        revs->min_age = -1;
        revs->max_count = -1;
@@ -494,6 +531,65 @@ void init_revisions(struct rev_info *revs)
 
        revs->topo_setter = topo_sort_default_setter;
        revs->topo_getter = topo_sort_default_getter;
+
+       revs->commit_format = CMIT_FMT_DEFAULT;
+
+       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;
 }
 
 /*
@@ -505,12 +601,10 @@ 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;
 
-       init_revisions(revs);
-
        /* First, search for "--" */
        seen_dashdash = 0;
        for (i = 1; i < argc; i++) {
@@ -524,15 +618,16 @@ 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 commit *commit;
+               struct object *object;
                const char *arg = argv[i];
                unsigned char sha1[20];
                char *dotdot;
                int local_flags;
 
                if (*arg == '-') {
+                       int opts;
                        if (!strncmp(arg, "--max-count=", 12)) {
                                revs->max_count = atoi(arg + 12);
                                continue;
@@ -590,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;
@@ -640,6 +739,93 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                revs->unpacked = 1;
                                continue;
                        }
+                       if (!strcmp(arg, "-r")) {
+                               revs->diff = 1;
+                               revs->diffopt.recursive = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "-t")) {
+                               revs->diff = 1;
+                               revs->diffopt.recursive = 1;
+                               revs->diffopt.tree_in_recursive = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "-m")) {
+                               revs->ignore_merges = 0;
+                               continue;
+                       }
+                       if (!strcmp(arg, "-c")) {
+                               revs->diff = 1;
+                               revs->dense_combined_merges = 0;
+                               revs->combine_merges = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--cc")) {
+                               revs->diff = 1;
+                               revs->dense_combined_merges = 1;
+                               revs->combine_merges = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "-v")) {
+                               revs->verbose_header = 1;
+                               continue;
+                       }
+                       if (!strncmp(arg, "--pretty", 8)) {
+                               revs->verbose_header = 1;
+                               revs->commit_format = get_commit_format(arg+8);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--root")) {
+                               revs->show_root_diff = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--no-commit-id")) {
+                               revs->no_commit_id = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--always")) {
+                               revs->always_show_header = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--no-abbrev")) {
+                               revs->abbrev = 0;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--abbrev")) {
+                               revs->abbrev = DEFAULT_ABBREV;
+                               continue;
+                       }
+                       if (!strncmp(arg, "--abbrev=", 9)) {
+                               revs->abbrev = strtoul(arg + 9, NULL, 10);
+                               if (revs->abbrev < MINIMUM_ABBREV)
+                                       revs->abbrev = MINIMUM_ABBREV;
+                               else if (revs->abbrev > 40)
+                                       revs->abbrev = 40;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--abbrev-commit")) {
+                               revs->abbrev_commit = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--full-diff")) {
+                               revs->diff = 1;
+                               revs->full_diff = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--full-history")) {
+                               revs->simplify_history = 0;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--relative-date")) {
+                               revs->relative_date = 1;
+                               continue;
+                       }
+                       opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);
+                       if (opts > 0) {
+                               revs->diff = 1;
+                               i += opts - 1;
+                               continue;
+                       }
                        *unrecognized++ = arg;
                        left++;
                        continue;
@@ -649,26 +835,57 @@ 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 commit *exclude;
-                               struct commit *include;
-
-                               exclude = get_commit_reference(revs, this, from_sha1, flags ^ UNINTERESTING);
-                               include = get_commit_reference(revs, next, sha1, flags);
-                               if (!exclude || !include)
-                                       die("Invalid revision range %s..%s", arg, next);
-                               add_one_commit(exclude, revs);
-                               add_one_commit(include, revs);
+                               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);
+                               }
+
+                               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 = '.';
                }
+               dotdot = strstr(arg, "^@");
+               if (dotdot && !dotdot[2]) {
+                       *dotdot = 0;
+                       if (add_parents_only(revs, arg, flags))
+                               continue;
+                       *dotdot = '^';
+               }
                local_flags = 0;
                if (*arg == '^') {
                        local_flags = UNINTERESTING;
@@ -680,39 +897,76 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                        if (seen_dashdash || local_flags)
                                die("bad revision '%s'", arg);
 
-                       /* If we didn't have a "--", all filenames must exist */
+                       /* If we didn't have a "--":
+                        * (1) all filenames must exist;
+                        * (2) all rev-args must not be interpretable
+                        *     as a valid filename.
+                        * but the latter we have checked in the main loop.
+                        */
                        for (j = i; j < argc; j++)
                                verify_filename(revs->prefix, argv[j]);
 
                        revs->prune_data = get_pathspec(revs->prefix, argv + i);
                        break;
                }
-               commit = get_commit_reference(revs, arg, sha1, flags ^ local_flags);
-               add_one_commit(commit, revs);
+               if (!seen_dashdash)
+                       verify_non_filename(revs->prefix, arg);
+               object = get_reference(revs, arg, sha1, flags ^ local_flags);
+               add_pending_object(revs, object, arg);
        }
-       if (def && !revs->commits) {
+       if (show_merge)
+               prepare_show_merge(revs);
+       if (def && !revs->pending.nr) {
                unsigned char sha1[20];
-               struct commit *commit;
+               struct object *object;
                if (get_sha1(def, sha1))
                        die("bad default revision '%s'", def);
-               commit = get_commit_reference(revs, def, sha1, 0);
-               add_one_commit(commit, revs);
+               object = get_reference(revs, def, sha1, 0);
+               add_pending_object(revs, object, def);
        }
 
        if (revs->topo_order || revs->unpacked)
                revs->limited = 1;
 
        if (revs->prune_data) {
-               diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
+               diff_tree_setup_paths(revs->prune_data, &revs->pruning);
                revs->prune_fn = try_to_simplify_commit;
+               if (!revs->full_diff)
+                       diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
        }
+       if (revs->combine_merges) {
+               revs->ignore_merges = 0;
+               if (revs->dense_combined_merges && !revs->diffopt.output_format)
+                       revs->diffopt.output_format = DIFF_FORMAT_PATCH;
+       }
+       revs->diffopt.abbrev = revs->abbrev;
+       if (diff_setup_done(&revs->diffopt) < 0)
+               die("diff_setup_done failed");
 
        return left;
 }
 
 void prepare_revision_walk(struct rev_info *revs)
 {
-       sort_by_date(&revs->commits);
+       int nr = revs->pending.nr;
+       struct object_array_entry *list = revs->pending.objects;
+
+       revs->pending.nr = 0;
+       revs->pending.alloc = 0;
+       revs->pending.objects = NULL;
+       while (--nr >= 0) {
+               struct commit *commit = handle_commit(revs, list->item, list->name);
+               if (commit) {
+                       if (!(commit->object.flags & SEEN)) {
+                               commit->object.flags |= SEEN;
+                               insert_by_date(commit, &revs->commits);
+                       }
+               }
+               list++;
+       }
+
+       if (revs->no_walk)
+               return;
        if (revs->limited)
                limit_list(revs);
        if (revs->topo_order)
@@ -727,6 +981,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)
@@ -777,9 +1033,11 @@ struct commit *get_revision(struct rev_info *revs)
        }
 
        do {
-               struct commit *commit = revs->commits->item;
+               struct commit_list *entry = revs->commits;
+               struct commit *commit = entry->item;
 
-               revs->commits = revs->commits->next;
+               revs->commits = entry->next;
+               free(entry);
 
                /*
                 * If we haven't done the list limiting, we need to look at
@@ -817,8 +1075,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);
                }