revision: add --grep-reflog to filter commits by reflog messages
[git.git] / revision.c
index 90376e8e19878b8cc67c2f18a92cf05615b90bd8..109bec144398699093312eaf79088834ab198f90 100644 (file)
@@ -345,6 +345,7 @@ static int tree_difference = REV_TREE_SAME;
 static void file_add_remove(struct diff_options *options,
                    int addremove, unsigned mode,
                    const unsigned char *sha1,
+                   int sha1_valid,
                    const char *fullpath, unsigned dirty_submodule)
 {
        int diff = addremove == '+' ? REV_TREE_NEW : REV_TREE_OLD;
@@ -358,6 +359,7 @@ static void file_change(struct diff_options *options,
                 unsigned old_mode, unsigned new_mode,
                 const unsigned char *old_sha1,
                 const unsigned char *new_sha1,
+                int old_sha1_valid, int new_sha1_valid,
                 const char *fullpath,
                 unsigned old_dirty_submodule, unsigned new_dirty_submodule)
 {
@@ -1000,7 +1002,7 @@ static int add_parents_only(struct rev_info *revs, const char *arg_, int flags)
                flags ^= UNINTERESTING;
                arg++;
        }
-       if (get_sha1(arg, sha1))
+       if (get_sha1_committish(arg, sha1))
                return 0;
        while (1) {
                it = get_reference(revs, arg, sha1, 0);
@@ -1114,16 +1116,16 @@ static void prepare_show_merge(struct rev_info *revs)
        revs->limited = 1;
 }
 
-int handle_revision_arg(const char *arg_, struct rev_info *revs,
-                       int flags,
-                       int cant_be_filename)
+int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsigned revarg_opt)
 {
-       unsigned mode;
+       struct object_context oc;
        char *dotdot;
        struct object *object;
        unsigned char sha1[20];
        int local_flags;
        const char *arg = arg_;
+       int cant_be_filename = revarg_opt & REVARG_CANNOT_BE_FILENAME;
+       unsigned get_sha1_flags = 0;
 
        dotdot = strstr(arg, "..");
        if (dotdot) {
@@ -1132,17 +1134,29 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs,
                const char *this = arg;
                int symmetric = *next == '.';
                unsigned int flags_exclude = flags ^ UNINTERESTING;
+               static const char head_by_default[] = "HEAD";
                unsigned int a_flags;
 
                *dotdot = 0;
                next += symmetric;
 
                if (!*next)
-                       next = "HEAD";
+                       next = head_by_default;
                if (dotdot == arg)
-                       this = "HEAD";
-               if (!get_sha1(this, from_sha1) &&
-                   !get_sha1(next, sha1)) {
+                       this = head_by_default;
+               if (this == head_by_default && next == head_by_default &&
+                   !symmetric) {
+                       /*
+                        * Just ".."?  That is not a range but the
+                        * pathspec for the parent directory.
+                        */
+                       if (!cant_be_filename) {
+                               *dotdot = '.';
+                               return -1;
+                       }
+               }
+               if (!get_sha1_committish(this, from_sha1) &&
+                   !get_sha1_committish(next, sha1)) {
                        struct commit *a, *b;
                        struct commit_list *exclude;
 
@@ -1201,13 +1215,17 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs,
                local_flags = UNINTERESTING;
                arg++;
        }
-       if (get_sha1_with_mode(arg, sha1, &mode))
+
+       if (revarg_opt & REVARG_COMMITTISH)
+               get_sha1_flags = GET_SHA1_COMMITTISH;
+
+       if (get_sha1_with_context(arg, get_sha1_flags, sha1, &oc))
                return revs->ignore_missing ? 0 : -1;
        if (!cant_be_filename)
                verify_non_filename(revs->prefix, arg);
        object = get_reference(revs, arg, sha1, flags ^ local_flags);
        add_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags);
-       add_pending_object_with_mode(revs, object, arg, mode);
+       add_pending_object_with_mode(revs, object, arg, oc.mode);
        return 0;
 }
 
@@ -1257,7 +1275,7 @@ static void read_revisions_from_stdin(struct rev_info *revs,
                        }
                        die("options not supported in --stdin mode");
                }
-               if (handle_revision_arg(sb.buf, revs, 0, 1))
+               if (handle_revision_arg(sb.buf, revs, 0, REVARG_CANNOT_BE_FILENAME))
                        die("bad revision '%s'", sb.buf);
        }
        if (seen_dashdash)
@@ -1294,7 +1312,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
            !strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk") ||
            !strcmp(arg, "--bisect") || !prefixcmp(arg, "--glob=") ||
            !prefixcmp(arg, "--branches=") || !prefixcmp(arg, "--tags=") ||
-           !prefixcmp(arg, "--remotes="))
+           !prefixcmp(arg, "--remotes=") || !prefixcmp(arg, "--no-walk="))
        {
                unkv[(*unkc)++] = arg;
                return 1;
@@ -1358,11 +1376,13 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->topo_order = 1;
        } else if (!strcmp(arg, "--simplify-merges")) {
                revs->simplify_merges = 1;
+               revs->topo_order = 1;
                revs->rewrite_parents = 1;
                revs->simplify_history = 0;
                revs->limited = 1;
        } else if (!strcmp(arg, "--simplify-by-decoration")) {
                revs->simplify_merges = 1;
+               revs->topo_order = 1;
                revs->rewrite_parents = 1;
                revs->simplify_history = 0;
                revs->simplify_by_decoration = 1;
@@ -1575,6 +1595,9 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
        } else if ((argcount = parse_long_opt("committer", argv, &optarg))) {
                add_header_grep(revs, GREP_HEADER_COMMITTER, optarg);
                return argcount;
+       } else if ((argcount = parse_long_opt("grep-reflog", argv, &optarg))) {
+               add_header_grep(revs, GREP_HEADER_REFLOG, optarg);
+               return argcount;
        } else if ((argcount = parse_long_opt("grep", argv, &optarg))) {
                add_message_grep(revs, optarg);
                return argcount;
@@ -1689,7 +1712,18 @@ static int handle_revision_pseudo_opt(const char *submodule,
        } else if (!strcmp(arg, "--not")) {
                *flags ^= UNINTERESTING;
        } else if (!strcmp(arg, "--no-walk")) {
-               revs->no_walk = 1;
+               revs->no_walk = REVISION_WALK_NO_WALK_SORTED;
+       } else if (!prefixcmp(arg, "--no-walk=")) {
+               /*
+                * Detached form ("--no-walk X" as opposed to "--no-walk=X")
+                * not allowed, since the argument is optional.
+                */
+               if (!strcmp(arg + 10, "sorted"))
+                       revs->no_walk = REVISION_WALK_NO_WALK_SORTED;
+               else if (!strcmp(arg + 10, "unsorted"))
+                       revs->no_walk = REVISION_WALK_NO_WALK_UNSORTED;
+               else
+                       return error("invalid argument to --no-walk");
        } else if (!strcmp(arg, "--do-walk")) {
                revs->no_walk = 0;
        } else {
@@ -1708,7 +1742,7 @@ static int handle_revision_pseudo_opt(const char *submodule,
  */
 int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *opt)
 {
-       int i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0;
+       int i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0, revarg_opt;
        struct cmdline_pathspec prune_data;
        const char *submodule = NULL;
 
@@ -1736,6 +1770,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
 
        /* Second, deal with arguments and options */
        flags = 0;
+       revarg_opt = opt ? opt->revarg_opt : 0;
+       if (seen_dashdash)
+               revarg_opt |= REVARG_CANNOT_BE_FILENAME;
        read_from_stdin = 0;
        for (left = i = 1; i < argc; i++) {
                const char *arg = argv[i];
@@ -1771,7 +1808,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
                        continue;
                }
 
-               if (handle_revision_arg(arg, revs, flags, seen_dashdash)) {
+
+               if (handle_revision_arg(arg, revs, flags, revarg_opt)) {
                        int j;
                        if (seen_dashdash || *arg == '^')
                                die("bad revision '%s'", arg);
@@ -1783,7 +1821,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
                         * but the latter we have checked in the main loop.
                         */
                        for (j = i; j < argc; j++)
-                               verify_filename(revs->prefix, argv[j]);
+                               verify_filename(revs->prefix, argv[j], j == i);
 
                        append_prune_data(&prune_data, argv + i);
                        break;
@@ -1822,11 +1860,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
        if (revs->def && !revs->pending.nr && !got_rev_arg) {
                unsigned char sha1[20];
                struct object *object;
-               unsigned mode;
-               if (get_sha1_with_mode(revs->def, sha1, &mode))
+               struct object_context oc;
+               if (get_sha1_with_context(revs->def, 0, sha1, &oc))
                        die("bad default revision '%s'", revs->def);
                object = get_reference(revs, revs->def, sha1, 0);
-               add_pending_object_with_mode(revs, object, revs->def, mode);
+               add_pending_object_with_mode(revs, object, revs->def, oc.mode);
        }
 
        /* Did the user ask for any diff output? Run the diff! */
@@ -1853,8 +1891,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
        if (revs->combine_merges)
                revs->ignore_merges = 0;
        revs->diffopt.abbrev = revs->abbrev;
-       if (diff_setup_done(&revs->diffopt) < 0)
-               die("diff_setup_done failed");
+       diff_setup_done(&revs->diffopt);
 
        compile_grep_patterns(&revs->grep_filter);
 
@@ -1949,8 +1986,9 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c
        }
 
        /*
-        * Do we know what commit all of our parents should be rewritten to?
-        * Otherwise we are not ready to rewrite this one yet.
+        * Do we know what commit all of our parents that matter
+        * should be rewritten to?  Otherwise we are not ready to
+        * rewrite this one yet.
         */
        for (cnt = 0, p = commit->parents; p; p = p->next) {
                pst = locate_simplify_state(revs, p->item);
@@ -1958,6 +1996,8 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c
                        tail = &commit_list_insert(p->item, tail)->next;
                        cnt++;
                }
+               if (revs->first_parent_only)
+                       break;
        }
        if (cnt) {
                tail = &commit_list_insert(commit, tail)->next;
@@ -1970,8 +2010,13 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c
        for (p = commit->parents; p; p = p->next) {
                pst = locate_simplify_state(revs, p->item);
                p->item = pst->simplified;
+               if (revs->first_parent_only)
+                       break;
        }
-       cnt = remove_duplicate_parents(commit);
+       if (!revs->first_parent_only)
+               cnt = remove_duplicate_parents(commit);
+       else
+               cnt = 1;
 
        /*
         * It is possible that we are a merge and one side branch
@@ -2015,25 +2060,31 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c
 
 static void simplify_merges(struct rev_info *revs)
 {
-       struct commit_list *list;
+       struct commit_list *list, *next;
        struct commit_list *yet_to_do, **tail;
+       struct commit *commit;
 
-       if (!revs->topo_order)
-               sort_in_topological_order(&revs->commits, revs->lifo);
        if (!revs->prune)
                return;
 
        /* feed the list reversed */
        yet_to_do = NULL;
-       for (list = revs->commits; list; list = list->next)
-               commit_list_insert(list->item, &yet_to_do);
+       for (list = revs->commits; list; list = next) {
+               commit = list->item;
+               next = list->next;
+               /*
+                * Do not free(list) here yet; the original list
+                * is used later in this function.
+                */
+               commit_list_insert(commit, &yet_to_do);
+       }
        while (yet_to_do) {
                list = yet_to_do;
                yet_to_do = NULL;
                tail = &yet_to_do;
                while (list) {
-                       struct commit *commit = list->item;
-                       struct commit_list *next = list->next;
+                       commit = list->item;
+                       next = list->next;
                        free(list);
                        list = next;
                        tail = simplify_one(revs, commit, tail);
@@ -2045,9 +2096,10 @@ static void simplify_merges(struct rev_info *revs)
        revs->commits = NULL;
        tail = &revs->commits;
        while (list) {
-               struct commit *commit = list->item;
-               struct commit_list *next = list->next;
                struct merge_simplify_state *st;
+
+               commit = list->item;
+               next = list->next;
                free(list);
                list = next;
                st = locate_simplify_state(revs, commit);
@@ -2068,10 +2120,16 @@ static void set_children(struct rev_info *revs)
        }
 }
 
+void reset_revision_walk(void)
+{
+       clear_object_flags(SEEN | ADDED | SHOWN);
+}
+
 int prepare_revision_walk(struct rev_info *revs)
 {
        int nr = revs->pending.nr;
        struct object_array_entry *e, *list;
+       struct commit_list **next = &revs->commits;
 
        e = list = revs->pending.objects;
        revs->pending.nr = 0;
@@ -2082,7 +2140,7 @@ int prepare_revision_walk(struct rev_info *revs)
                if (commit) {
                        if (!(commit->object.flags & SEEN)) {
                                commit->object.flags |= SEEN;
-                               commit_list_insert_by_date(commit, &revs->commits);
+                               next = commit_list_append(commit, next);
                        }
                }
                e++;
@@ -2090,6 +2148,8 @@ int prepare_revision_walk(struct rev_info *revs)
        if (!revs->leak_pending)
                free(list);
 
+       if (revs->no_walk != REVISION_WALK_NO_WALK_UNSORTED)
+               commit_list_sort_by_date(&revs->commits);
        if (revs->no_walk)
                return 0;
        if (revs->limited)
@@ -2153,10 +2213,23 @@ static int rewrite_parents(struct rev_info *revs, struct commit *commit)
 
 static int commit_match(struct commit *commit, struct rev_info *opt)
 {
+       int retval;
+       struct strbuf buf = STRBUF_INIT;
        if (!opt->grep_filter.pattern_list && !opt->grep_filter.header_list)
                return 1;
-       return grep_buffer(&opt->grep_filter,
-                          commit->buffer, strlen(commit->buffer));
+       if (opt->reflog_info) {
+               strbuf_addstr(&buf, "reflog ");
+               get_reflog_message(&buf, opt->reflog_info);
+               strbuf_addch(&buf, '\n');
+               strbuf_addstr(&buf, commit->buffer);
+       }
+       if (buf.len)
+               retval = grep_buffer(&opt->grep_filter, buf.buf, buf.len);
+       else
+               retval = grep_buffer(&opt->grep_filter,
+                                    commit->buffer, strlen(commit->buffer));
+       strbuf_release(&buf);
+       return retval;
 }
 
 static inline int want_ancestry(struct rev_info *revs)
@@ -2339,29 +2412,28 @@ static struct commit *get_revision_internal(struct rev_info *revs)
        }
 
        /*
-        * Now pick up what they want to give us
+        * If our max_count counter has reached zero, then we are done. We
+        * don't simply return NULL because we still might need to show
+        * boundary commits. But we want to avoid calling get_revision_1, which
+        * might do a considerable amount of work finding the next commit only
+        * for us to throw it away.
+        *
+        * If it is non-zero, then either we don't have a max_count at all
+        * (-1), or it is still counting, in which case we decrement.
         */
-       c = get_revision_1(revs);
-       if (c) {
-               while (0 < revs->skip_count) {
-                       revs->skip_count--;
-                       c = get_revision_1(revs);
-                       if (!c)
-                               break;
+       if (revs->max_count) {
+               c = get_revision_1(revs);
+               if (c) {
+                       while (0 < revs->skip_count) {
+                               revs->skip_count--;
+                               c = get_revision_1(revs);
+                               if (!c)
+                                       break;
+                       }
                }
-       }
 
-       /*
-        * Check the max_count.
-        */
-       switch (revs->max_count) {
-       case -1:
-               break;
-       case 0:
-               c = NULL;
-               break;
-       default:
-               revs->max_count--;
+               if (revs->max_count > 0)
+                       revs->max_count--;
        }
 
        if (c)