rm: reuse strbuf for all remove_dir_recursively() calls
[gitweb.git] / line-log.c
index 5a12ceb012ee656d9495dc43d924ec4d983d61b6..bbe31ed6fbb7c103c739f7e9a3c45cde5d36fcec 100644 (file)
@@ -14,6 +14,7 @@
 #include "graph.h"
 #include "userdiff.h"
 #include "line-log.h"
+#include "argv-array.h"
 
 static void range_set_grow(struct range_set *rs, size_t extra)
 {
@@ -23,7 +24,7 @@ static void range_set_grow(struct range_set *rs, size_t extra)
 /* Either initialization would be fine */
 #define RANGE_SET_INIT {0}
 
-static void range_set_init(struct range_set *rs, size_t prealloc)
+void range_set_init(struct range_set *rs, size_t prealloc)
 {
        rs->alloc = rs->nr = 0;
        rs->ranges = NULL;
@@ -31,7 +32,7 @@ static void range_set_init(struct range_set *rs, size_t prealloc)
                range_set_grow(rs, prealloc);
 }
 
-static void range_set_release(struct range_set *rs)
+void range_set_release(struct range_set *rs)
 {
        free(rs->ranges);
        rs->alloc = rs->nr = 0;
@@ -56,16 +57,21 @@ static void range_set_move(struct range_set *dst, struct range_set *src)
 }
 
 /* tack on a _new_ range _at the end_ */
-static void range_set_append(struct range_set *rs, long a, long b)
+void range_set_append_unsafe(struct range_set *rs, long a, long b)
 {
        assert(a <= b);
-       assert(rs->nr == 0 || rs->ranges[rs->nr-1].end <= a);
        range_set_grow(rs, 1);
        rs->ranges[rs->nr].start = a;
        rs->ranges[rs->nr].end = b;
        rs->nr++;
 }
 
+void range_set_append(struct range_set *rs, long a, long b)
+{
+       assert(rs->nr == 0 || rs->ranges[rs->nr-1].end <= a);
+       range_set_append_unsafe(rs, a, b);
+}
+
 static int range_cmp(const void *_r, const void *_s)
 {
        const struct range *r = _r;
@@ -99,21 +105,22 @@ static void range_set_check_invariants(struct range_set *rs)
 }
 
 /*
- * Helper: In-place pass of sorting and merging the ranges in the
- * range set, to re-establish the invariants after another operation
- *
- * NEEDSWORK currently not needed
+ * In-place pass of sorting and merging the ranges in the range set,
+ * to establish the invariants when we get the ranges from the user
  */
-static void sort_and_merge_range_set(struct range_set *rs)
+void sort_and_merge_range_set(struct range_set *rs)
 {
        int i;
-       int o = 1; /* output cursor */
+       int o = 0; /* output cursor */
 
        qsort(rs->ranges, rs->nr, sizeof(struct range), range_cmp);
 
-       for (i = 1; i < rs->nr; i++) {
-               if (rs->ranges[i].start <= rs->ranges[o-1].end) {
-                       rs->ranges[o-1].end = rs->ranges[i].end;
+       for (i = 0; i < rs->nr; i++) {
+               if (rs->ranges[i].start == rs->ranges[i].end)
+                       continue;
+               if (o > 0 && rs->ranges[i].start <= rs->ranges[o-1].end) {
+                       if (rs->ranges[o-1].end < rs->ranges[i].end)
+                               rs->ranges[o-1].end = rs->ranges[i].end;
                } else {
                        rs->ranges[o].start = rs->ranges[i].start;
                        rs->ranges[o].end = rs->ranges[i].end;
@@ -231,7 +238,7 @@ static void diff_ranges_release(struct diff_ranges *diff)
        range_set_release(&diff->target);
 }
 
-void line_log_data_init(struct line_log_data *r)
+static void line_log_data_init(struct line_log_data *r)
 {
        memset(r, 0, sizeof(struct line_log_data));
        range_set_init(&r->ranges, 0);
@@ -262,7 +269,7 @@ search_line_log_data(struct line_log_data *list, const char *path,
        if (insertion_point)
                *insertion_point = NULL;
        while (p) {
-               int cmp = strcmp(p->spec->path, path);
+               int cmp = strcmp(p->path, path);
                if (!cmp)
                        return p;
                if (insertion_point && cmp < 0)
@@ -272,22 +279,25 @@ search_line_log_data(struct line_log_data *list, const char *path,
        return NULL;
 }
 
+/*
+ * Note: takes ownership of 'path', which happens to be what the only
+ * caller needs.
+ */
 static void line_log_data_insert(struct line_log_data **list,
-                                struct diff_filespec *spec,
+                                char *path,
                                 long begin, long end)
 {
        struct line_log_data *ip;
-       struct line_log_data *p = search_line_log_data(*list, spec->path, &ip);
+       struct line_log_data *p = search_line_log_data(*list, path, &ip);
 
        if (p) {
-               range_set_append(&p->ranges, begin, end);
-               sort_and_merge_range_set(&p->ranges);
-               free_filespec(spec);
+               range_set_append_unsafe(&p->ranges, begin, end);
+               free(path);
                return;
        }
 
        p = xcalloc(1, sizeof(struct line_log_data));
-       p->spec = spec;
+       p->path = path;
        range_set_append(&p->ranges, begin, end);
        if (ip) {
                p->next = ip->next;
@@ -316,7 +326,7 @@ static int collect_diff_cb(long start_a, long count_a,
        return 0;
 }
 
-static void collect_diff(mmfile_t *parent, mmfile_t *target, struct diff_ranges *out)
+static int collect_diff(mmfile_t *parent, mmfile_t *target, struct diff_ranges *out)
 {
        struct collect_diff_cbdata cbdata = {NULL};
        xpparam_t xpp;
@@ -331,7 +341,7 @@ static void collect_diff(mmfile_t *parent, mmfile_t *target, struct diff_ranges
        xecfg.hunk_func = collect_diff_cb;
        memset(&ecb, 0, sizeof(ecb));
        ecb.priv = &cbdata;
-       xdi_diff(parent, target, &xpp, &xecfg, &ecb);
+       return xdi_diff(parent, target, &xpp, &xecfg, &ecb);
 }
 
 /*
@@ -351,7 +361,7 @@ static void dump_line_log_data(struct line_log_data *r)
 {
        char buf[4096];
        while (r) {
-               snprintf(buf, 4096, "file %s\n", r->spec->path);
+               snprintf(buf, 4096, "file %s\n", r->path);
                dump_range_set(&r->ranges, buf);
                r = r->next;
        }
@@ -493,7 +503,7 @@ static void fill_blob_sha1(struct commit *commit, struct diff_filespec *spec)
        unsigned mode;
        unsigned char sha1[20];
 
-       if (get_tree_entry(commit->object.sha1, spec->path,
+       if (get_tree_entry(commit->object.oid.hash, spec->path,
                           sha1, &mode))
                die("There is no path %s in the commit", spec->path);
        fill_filespec(spec, sha1, 1, mode);
@@ -512,7 +522,7 @@ static void fill_line_ends(struct diff_filespec *spec, long *lines,
        if (diff_populate_filespec(spec, 0))
                die("Cannot read blob %s", sha1_to_hex(spec->sha1));
 
-       ends = xmalloc(size * sizeof(*ends));
+       ALLOC_ARRAY(ends, size);
        ends[cur++] = 0;
        data = spec->data;
        while (num < spec->size) {
@@ -524,7 +534,7 @@ static void fill_line_ends(struct diff_filespec *spec, long *lines,
        }
 
        /* shrink the array to fit the elements */
-       ends = xrealloc(ends, cur * sizeof(*ends));
+       REALLOC_ARRAY(ends, cur);
        *lines = cur-1;
        *line_ends = ends;
 }
@@ -555,16 +565,18 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
        struct nth_line_cb cb_data;
        struct string_list_item *item;
        struct line_log_data *ranges = NULL;
+       struct line_log_data *p;
 
        for_each_string_list_item(item, args) {
                const char *name_part, *range_part;
-               const char *full_name;
+               char *full_name;
                struct diff_filespec *spec;
                long begin = 0, end = 0;
+               long anchor;
 
                name_part = skip_range_arg(item->string);
                if (!name_part || *name_part != ':' || !name_part[1])
-                       die("-L argument '%s' not of the form start,end:file",
+                       die("-L argument not 'start,end:file' or ':funcname:file': %s",
                            item->string);
                range_part = xstrndup(item->string, name_part - item->string);
                name_part++;
@@ -579,23 +591,33 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
                cb_data.lines = lines;
                cb_data.line_ends = ends;
 
+               p = search_line_log_data(ranges, full_name, NULL);
+               if (p && p->ranges.nr)
+                       anchor = p->ranges.ranges[p->ranges.nr - 1].end + 1;
+               else
+                       anchor = 1;
+
                if (parse_range_arg(range_part, nth_line, &cb_data,
-                                   lines, &begin, &end,
-                                   spec->path))
+                                   lines, anchor, &begin, &end,
+                                   full_name))
                        die("malformed -L argument '%s'", range_part);
+               if (lines < end || ((lines || begin) && lines < begin))
+                       die("file %s has only %lu lines", name_part, lines);
                if (begin < 1)
                        begin = 1;
                if (end < 1)
                        end = lines;
                begin--;
-               if (lines < end || lines < begin)
-                       die("file %s has only %ld lines", name_part, lines);
-               line_log_data_insert(&ranges, spec, begin, end);
+               line_log_data_insert(&ranges, full_name, begin, end);
 
+               free_filespec(spec);
                free(ends);
                ends = NULL;
        }
 
+       for (p = ranges; p; p = p->next)
+               sort_and_merge_range_set(&p->ranges);
+
        return ranges;
 }
 
@@ -607,9 +629,7 @@ static struct line_log_data *line_log_data_copy_one(struct line_log_data *r)
        line_log_data_init(ret);
        range_set_copy(&ret->ranges, &r->ranges);
 
-       ret->spec = r->spec;
-       assert(ret->spec);
-       ret->spec->count++;
+       ret->path = xstrdup(r->path);
 
        return ret;
 }
@@ -649,7 +669,7 @@ static struct line_log_data *line_log_data_merge(struct line_log_data *a,
                else if (!b)
                        cmp = -1;
                else
-                       cmp = strcmp(a->spec->path, b->spec->path);
+                       cmp = strcmp(a->path, b->path);
                if (cmp < 0) {
                        src = a;
                        a = a->next;
@@ -664,8 +684,7 @@ static struct line_log_data *line_log_data_merge(struct line_log_data *a,
                }
                d = xmalloc(sizeof(struct line_log_data));
                line_log_data_init(d);
-               d->spec = src->spec;
-               d->spec->count++;
+               d->path = xstrdup(src->path);
                *pp = d;
                pp = &d->next;
                if (src2)
@@ -728,44 +747,19 @@ void line_log_init(struct rev_info *rev, const char *prefix, struct string_list
        add_line_range(rev, commit, range);
 
        if (!rev->diffopt.detect_rename) {
-               int i, count = 0;
-               struct line_log_data *r = range;
+               struct line_log_data *r;
+               struct argv_array array = ARGV_ARRAY_INIT;
                const char **paths;
-               while (r) {
-                       count++;
-                       r = r->next;
-               }
-               paths = xmalloc((count+1)*sizeof(char *));
-               r = range;
-               for (i = 0; i < count; i++) {
-                       paths[i] = xstrdup(r->spec->path);
-                       r = r->next;
-               }
-               paths[count] = NULL;
-               init_pathspec(&rev->diffopt.pathspec, paths);
-               free(paths);
-       }
-}
 
-static void load_tree_desc(struct tree_desc *desc, void **tree,
-                          const unsigned char *sha1)
-{
-       unsigned long size;
-       *tree = read_object_with_reference(sha1, tree_type, &size, NULL);
-       if (!*tree)
-               die("Unable to read tree (%s)", sha1_to_hex(sha1));
-       init_tree_desc(desc, *tree, size);
-}
+               for (r = range; r; r = r->next)
+                       argv_array_push(&array, r->path);
+               paths = argv_array_detach(&array);
 
-static int count_parents(struct commit *commit)
-{
-       struct commit_list *parents = commit->parents;
-       int count = 0;
-       while (parents) {
-               count++;
-               parents = parents->next;
+               parse_pathspec(&rev->diffopt.pathspec, 0,
+                              PATHSPEC_PREFER_FULL, "", paths);
+               /* strings are now owned by pathspec */
+               free(paths);
        }
-       return count;
 }
 
 static void move_diff_queue(struct diff_queue_struct *dst,
@@ -794,7 +788,7 @@ static void filter_diffs_for_paths(struct line_log_data *range, int keep_deletio
                        continue;
                }
                for (rg = range; rg; rg = rg->next) {
-                       if (!strcmp(rg->spec->path, p->two->path))
+                       if (!strcmp(rg->path, p->two->path))
                                break;
                }
                if (rg)
@@ -823,18 +817,11 @@ static void queue_diffs(struct line_log_data *range,
                        struct diff_queue_struct *queue,
                        struct commit *commit, struct commit *parent)
 {
-       void *tree1 = NULL, *tree2 = NULL;
-       struct tree_desc desc1, desc2;
-
        assert(commit);
-       load_tree_desc(&desc2, &tree2, commit->tree->object.sha1);
-       if (parent)
-               load_tree_desc(&desc1, &tree1, parent->tree->object.sha1);
-       else
-               init_tree_desc(&desc1, "", 0);
 
        DIFF_QUEUE_CLEAR(&diff_queued_diff);
-       diff_tree(&desc1, &desc2, "", opt);
+       diff_tree_sha1(parent ? parent->tree->object.oid.hash : NULL,
+                       commit->tree->object.oid.hash, "", opt);
        if (opt->detect_rename) {
                filter_diffs_for_paths(range, 1);
                if (diff_might_be_rename())
@@ -842,11 +829,6 @@ static void queue_diffs(struct line_log_data *range,
                filter_diffs_for_paths(range, 0);
        }
        move_diff_queue(queue, &diff_queued_diff);
-
-       if (tree1)
-               free(tree1);
-       if (tree2)
-               free(tree2);
 }
 
 static char *get_nth_line(long line, unsigned long *ends, void *data)
@@ -907,7 +889,7 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
        const char *c_meta = diff_get_color(opt->use_color, DIFF_METAINFO);
        const char *c_old = diff_get_color(opt->use_color, DIFF_FILE_OLD);
        const char *c_new = diff_get_color(opt->use_color, DIFF_FILE_NEW);
-       const char *c_plain = diff_get_color(opt->use_color, DIFF_PLAIN);
+       const char *c_context = diff_get_color(opt->use_color, DIFF_CONTEXT);
 
        if (!pair || !diff)
                return;
@@ -971,7 +953,7 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
                        int k;
                        for (; t_cur < diff->target.ranges[j].start; t_cur++)
                                print_line(prefix, ' ', t_cur, t_ends, pair->two->data,
-                                          c_plain, c_reset);
+                                          c_context, c_reset);
                        for (k = diff->parent.ranges[j].start; k < diff->parent.ranges[j].end; k++)
                                print_line(prefix, '-', k, p_ends, pair->one->data,
                                           c_old, c_reset);
@@ -982,7 +964,7 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
                }
                for (; t_cur < t_end; t_cur++)
                        print_line(prefix, ' ', t_cur, t_ends, pair->two->data,
-                                  c_plain, c_reset);
+                                  c_context, c_reset);
        }
 
        free(p_ends);
@@ -1018,8 +1000,8 @@ static int process_diff_filepair(struct rev_info *rev,
 
        assert(pair->two->path);
        while (rg) {
-               assert(rg->spec->path);
-               if (!strcmp(rg->spec->path, pair->two->path))
+               assert(rg->path);
+               if (!strcmp(rg->path, pair->two->path))
                        break;
                rg = rg->next;
        }
@@ -1044,10 +1026,12 @@ static int process_diff_filepair(struct rev_info *rev,
        }
 
        diff_ranges_init(&diff);
-       collect_diff(&file_parent, &file_target, &diff);
+       if (collect_diff(&file_parent, &file_target, &diff))
+               die("unable to generate diff for %s", pair->one->path);
 
        /* NEEDSWORK should apply some heuristics to prevent mismatches */
-       rg->spec->path = xstrdup(pair->one->path);
+       free(rg->path);
+       rg->path = xstrdup(pair->one->path);
 
        range_set_init(&tmp, 0);
        range_set_map_across_diff(&tmp, &rg->ranges, &diff, diff_out);
@@ -1089,16 +1073,30 @@ static int process_all_files(struct line_log_data **range_out,
 
        for (i = 0; i < queue->nr; i++) {
                struct diff_ranges *pairdiff = NULL;
-               if (process_diff_filepair(rev, queue->queue[i], *range_out, &pairdiff)) {
+               struct diff_filepair *pair = queue->queue[i];
+               if (process_diff_filepair(rev, pair, *range_out, &pairdiff)) {
+                       /*
+                        * Store away the diff for later output.  We
+                        * tuck it in the ranges we got as _input_,
+                        * since that's the commit that caused the
+                        * diff.
+                        *
+                        * NEEDSWORK not enough when we get around to
+                        * doing something interesting with merges;
+                        * currently each invocation on a merge parent
+                        * trashes the previous one's diff.
+                        *
+                        * NEEDSWORK tramples over data structures not owned here
+                        */
                        struct line_log_data *rg = range;
                        changed++;
-                       /* NEEDSWORK tramples over data structures not owned here */
-                       while (rg && strcmp(rg->spec->path, queue->queue[i]->two->path))
+                       while (rg && strcmp(rg->path, pair->two->path))
                                rg = rg->next;
                        assert(rg);
                        rg->pair = diff_filepair_dup(queue->queue[i]);
                        memcpy(&rg->diff, pairdiff, sizeof(struct diff_ranges));
                }
+               free(pairdiff);
        }
 
        return changed;
@@ -1139,11 +1137,14 @@ static int process_ranges_merge_commit(struct rev_info *rev, struct commit *comm
        struct commit **parents;
        struct commit_list *p;
        int i;
-       int nparents = count_parents(commit);
+       int nparents = commit_list_count(commit->parents);
+
+       if (nparents > 1 && rev->first_parent_only)
+               nparents = 1;
 
-       diffqueues = xmalloc(nparents * sizeof(*diffqueues));
-       cand = xmalloc(nparents * sizeof(*cand));
-       parents = xmalloc(nparents * sizeof(*parents));
+       ALLOC_ARRAY(diffqueues, nparents);
+       ALLOC_ARRAY(cand, nparents);
+       ALLOC_ARRAY(parents, nparents);
 
        p = commit->parents;
        for (i = 0; i < nparents; i++) {
@@ -1163,9 +1164,7 @@ static int process_ranges_merge_commit(struct rev_info *rev, struct commit *comm
                         */
                        add_line_range(rev, parents[i], cand[i]);
                        clear_commit_line_range(rev, commit);
-                       commit->parents = xmalloc(sizeof(struct commit_list));
-                       commit->parents->item = parents[i];
-                       commit->parents->next = NULL;
+                       commit_list_append(parents[i], &commit->parents);
                        free(parents);
                        free(cand);
                        free_diffqueues(nparents, diffqueues);