strbuf_split_buf(): simplify iteration
[gitweb.git] / revision.c
index 86d17c6d7a66b75dc2d5a5e70bb3e57d0aef02f1..d7d621cdbf224f5a2bc1780e9602dcd3d08c1d57 100644 (file)
@@ -139,11 +139,32 @@ void mark_tree_uninteresting(struct tree *tree)
 
 void mark_parents_uninteresting(struct commit *commit)
 {
-       struct commit_list *parents = commit->parents;
+       struct commit_list *parents = NULL, *l;
+
+       for (l = commit->parents; l; l = l->next)
+               commit_list_insert(l->item, &parents);
 
        while (parents) {
                struct commit *commit = parents->item;
-               if (!(commit->object.flags & UNINTERESTING)) {
+               l = parents;
+               parents = parents->next;
+               free(l);
+
+               while (commit) {
+                       /*
+                        * A missing commit is ok iff its parent is marked
+                        * uninteresting.
+                        *
+                        * We just mark such a thing parsed, so that when
+                        * it is popped next time around, we won't be trying
+                        * to parse it and get an error.
+                        */
+                       if (!has_sha1_file(commit->object.sha1))
+                               commit->object.parsed = 1;
+
+                       if (commit->object.flags & UNINTERESTING)
+                               break;
+
                        commit->object.flags |= UNINTERESTING;
 
                        /*
@@ -154,21 +175,13 @@ void mark_parents_uninteresting(struct commit *commit)
                         * wasn't uninteresting), in which case we need
                         * to mark its parents recursively too..
                         */
-                       if (commit->parents)
-                               mark_parents_uninteresting(commit);
-               }
+                       if (!commit->parents)
+                               break;
 
-               /*
-                * A missing commit is ok iff its parent is marked
-                * uninteresting.
-                *
-                * We just mark such a thing parsed, so that when
-                * it is popped next time around, we won't be trying
-                * to parse it and get an error.
-                */
-               if (!has_sha1_file(commit->object.sha1))
-                       commit->object.parsed = 1;
-               parents = parents->next;
+                       for (l = commit->parents->next; l; l = l->next)
+                               commit_list_insert(l->item, &parents);
+                       commit = commit->parents->item;
+               }
        }
 }
 
@@ -332,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;
@@ -345,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)
 {
@@ -987,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);
@@ -1033,9 +1048,9 @@ void init_revisions(struct rev_info *revs, const char *prefix)
 
        revs->commit_format = CMIT_FMT_DEFAULT;
 
+       init_grep_defaults();
+       grep_init(&revs->grep_filter, prefix);
        revs->grep_filter.status_only = 1;
-       revs->grep_filter.pattern_tail = &(revs->grep_filter.pattern_list);
-       revs->grep_filter.header_tail = &(revs->grep_filter.header_list);
        revs->grep_filter.regflags = REG_NEWLINE;
 
        diff_setup(&revs->diffopt);
@@ -1101,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) {
@@ -1119,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;
 
@@ -1188,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;
 }
 
@@ -1244,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)
@@ -1281,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;
@@ -1345,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;
@@ -1562,15 +1595,25 @@ 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;
+       } else if (!strcmp(arg, "--grep-debug")) {
+               revs->grep_filter.debug = 1;
+       } else if (!strcmp(arg, "--basic-regexp")) {
+               grep_set_pattern_type_option(GREP_PATTERN_TYPE_BRE, &revs->grep_filter);
        } else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) {
-               revs->grep_filter.regflags |= REG_EXTENDED;
+               grep_set_pattern_type_option(GREP_PATTERN_TYPE_ERE, &revs->grep_filter);
        } else if (!strcmp(arg, "--regexp-ignore-case") || !strcmp(arg, "-i")) {
                revs->grep_filter.regflags |= REG_ICASE;
+               DIFF_OPT_SET(&revs->diffopt, PICKAXE_IGNORE_CASE);
        } else if (!strcmp(arg, "--fixed-strings") || !strcmp(arg, "-F")) {
-               revs->grep_filter.fixed = 1;
+               grep_set_pattern_type_option(GREP_PATTERN_TYPE_FIXED, &revs->grep_filter);
+       } else if (!strcmp(arg, "--perl-regexp")) {
+               grep_set_pattern_type_option(GREP_PATTERN_TYPE_PCRE, &revs->grep_filter);
        } else if (!strcmp(arg, "--all-match")) {
                revs->grep_filter.all_match = 1;
        } else if ((argcount = parse_long_opt("encoding", argv, &optarg))) {
@@ -1673,7 +1716,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 {
@@ -1692,7 +1746,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;
 
@@ -1701,21 +1755,28 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
                submodule = opt->submodule;
 
        /* First, search for "--" */
-       seen_dashdash = 0;
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-               if (strcmp(arg, "--"))
-                       continue;
-               argv[i] = NULL;
-               argc = i;
-               if (argv[i + 1])
-                       append_prune_data(&prune_data, argv + i + 1);
+       if (opt && opt->assume_dashdash) {
                seen_dashdash = 1;
-               break;
+       } else {
+               seen_dashdash = 0;
+               for (i = 1; i < argc; i++) {
+                       const char *arg = argv[i];
+                       if (strcmp(arg, "--"))
+                               continue;
+                       argv[i] = NULL;
+                       argc = i;
+                       if (argv[i + 1])
+                               append_prune_data(&prune_data, argv + i + 1);
+                       seen_dashdash = 1;
+                       break;
+               }
        }
 
        /* 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];
@@ -1751,7 +1812,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);
@@ -1763,7 +1825,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;
@@ -1802,11 +1864,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! */
@@ -1835,6 +1897,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
        revs->diffopt.abbrev = revs->abbrev;
        diff_setup_done(&revs->diffopt);
 
+       grep_commit_pattern_type(GREP_PATTERN_TYPE_UNSPECIFIED,
+                                &revs->grep_filter);
        compile_grep_patterns(&revs->grep_filter);
 
        if (revs->reverse && revs->reflog_info)
@@ -1850,6 +1914,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
 
        if (revs->reflog_info && revs->graph)
                die("cannot combine --walk-reflogs with --graph");
+       if (!revs->reflog_info && revs->grep_filter.use_reflog_filter)
+               die("cannot use --grep-reflog without --walk-reflogs");
 
        return left;
 }
@@ -1928,8 +1994,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);
@@ -1937,6 +2004,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;
@@ -1949,8 +2018,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
@@ -1994,25 +2068,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);
@@ -2024,9 +2104,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);
@@ -2047,10 +2128,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;
@@ -2061,7 +2148,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++;
@@ -2069,6 +2156,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)
@@ -2132,10 +2221,38 @@ 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));
+
+       /* Prepend "fake" headers as needed */
+       if (opt->grep_filter.use_reflog_filter) {
+               strbuf_addstr(&buf, "reflog ");
+               get_reflog_message(&buf, opt->reflog_info);
+               strbuf_addch(&buf, '\n');
+       }
+
+       /* Copy the commit to temporary if we are using "fake" headers */
+       if (buf.len)
+               strbuf_addstr(&buf, commit->buffer);
+
+       /* Append "fake" message parts as needed */
+       if (opt->show_notes) {
+               if (!buf.len)
+                       strbuf_addstr(&buf, commit->buffer);
+               format_display_notes(commit->object.sha1, &buf,
+                                    get_log_output_encoding(), 0);
+       }
+
+       /* Find either in the commit object, or in the temporary */
+       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)
@@ -2318,29 +2435,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)