xread/xwrite: clip MAX_IO_SIZE to SSIZE_MAX
[gitweb.git] / line-log.c
index 624423142ef99e4ad0a008b738c5978bb9521467..c2d01dccc2a12a767c442de58ed78df130699d74 100644 (file)
@@ -12,6 +12,7 @@
 #include "strbuf.h"
 #include "log-tree.h"
 #include "graph.h"
+#include "userdiff.h"
 #include "line-log.h"
 
 static void range_set_grow(struct range_set *rs, size_t extra)
@@ -55,16 +56,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)
+static 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++;
 }
 
+static 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;
@@ -79,21 +85,41 @@ static int range_cmp(const void *_r, const void *_s)
 }
 
 /*
- * 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
+ * Check that the ranges are non-empty, sorted and non-overlapping
+ */
+static void range_set_check_invariants(struct range_set *rs)
+{
+       int i;
+
+       if (!rs)
+               return;
+
+       if (rs->nr)
+               assert(rs->ranges[0].start < rs->ranges[0].end);
+
+       for (i = 1; i < rs->nr; i++) {
+               assert(rs->ranges[i-1].end < rs->ranges[i].start);
+               assert(rs->ranges[i].start < rs->ranges[i].end);
+       }
+}
+
+/*
+ * 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)
 {
        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;
@@ -102,6 +128,8 @@ static void sort_and_merge_range_set(struct range_set *rs)
        }
        assert(o <= rs->nr);
        rs->nr = o;
+
+       range_set_check_invariants(rs);
 }
 
 /*
@@ -240,7 +268,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)
@@ -250,23 +278,28 @@ 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);
+               range_set_append_unsafe(&p->ranges, begin, end);
                sort_and_merge_range_set(&p->ranges);
-               free_filespec(spec);
+               free(path);
                return;
        }
 
        p = xcalloc(1, sizeof(struct line_log_data));
-       p->spec = spec;
+       p->path = path;
        range_set_append(&p->ranges, begin, end);
+       sort_and_merge_range_set(&p->ranges);
        if (ip) {
                p->next = ip->next;
                ip->next = p;
@@ -329,7 +362,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;
        }
@@ -438,7 +471,6 @@ static void range_set_map_across_diff(struct range_set *out,
        *touched_out = touched;
 }
 
-
 static struct commit *check_single_commit(struct rev_info *revs)
 {
        struct object *commit = NULL;
@@ -537,7 +569,7 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
 
        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;
 
@@ -559,7 +591,8 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
                cb_data.line_ends = ends;
 
                if (parse_range_arg(range_part, nth_line, &cb_data,
-                                   lines, &begin, &end))
+                                   lines, &begin, &end,
+                                   full_name))
                        die("malformed -L argument '%s'", range_part);
                if (begin < 1)
                        begin = 1;
@@ -568,8 +601,9 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
                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;
        }
@@ -585,9 +619,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;
 }
@@ -627,7 +659,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;
@@ -642,8 +674,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)
@@ -686,8 +717,13 @@ static struct line_log_data *lookup_line_range(struct rev_info *revs,
                                               struct commit *commit)
 {
        struct line_log_data *ret = NULL;
+       struct line_log_data *d;
 
        ret = lookup_decoration(&revs->line_log_data, &commit->object);
+
+       for (d = ret; d; d = d->next)
+               range_set_check_invariants(&d->ranges);
+
        return ret;
 }
 
@@ -711,7 +747,7 @@ void line_log_init(struct rev_info *rev, const char *prefix, struct string_list
                paths = xmalloc((count+1)*sizeof(char *));
                r = range;
                for (i = 0; i < count; i++) {
-                       paths[i] = xstrdup(r->spec->path);
+                       paths[i] = xstrdup(r->path);
                        r = r->next;
                }
                paths[count] = NULL;
@@ -749,7 +785,50 @@ static void move_diff_queue(struct diff_queue_struct *dst,
        DIFF_QUEUE_CLEAR(src);
 }
 
-static void queue_diffs(struct diff_options *opt,
+static void filter_diffs_for_paths(struct line_log_data *range, int keep_deletions)
+{
+       int i;
+       struct diff_queue_struct outq;
+       DIFF_QUEUE_CLEAR(&outq);
+
+       for (i = 0; i < diff_queued_diff.nr; i++) {
+               struct diff_filepair *p = diff_queued_diff.queue[i];
+               struct line_log_data *rg = NULL;
+
+               if (!DIFF_FILE_VALID(p->two)) {
+                       if (keep_deletions)
+                               diff_q(&outq, p);
+                       else
+                               diff_free_filepair(p);
+                       continue;
+               }
+               for (rg = range; rg; rg = rg->next) {
+                       if (!strcmp(rg->path, p->two->path))
+                               break;
+               }
+               if (rg)
+                       diff_q(&outq, p);
+               else
+                       diff_free_filepair(p);
+       }
+       free(diff_queued_diff.queue);
+       diff_queued_diff = outq;
+}
+
+static inline int diff_might_be_rename(void)
+{
+       int i;
+       for (i = 0; i < diff_queued_diff.nr; i++)
+               if (!DIFF_FILE_VALID(diff_queued_diff.queue[i]->one)) {
+                       /* fprintf(stderr, "diff_might_be_rename found creation of: %s\n", */
+                       /*      diff_queued_diff.queue[i]->two->path); */
+                       return 1;
+               }
+       return 0;
+}
+
+static void queue_diffs(struct line_log_data *range,
+                       struct diff_options *opt,
                        struct diff_queue_struct *queue,
                        struct commit *commit, struct commit *parent)
 {
@@ -765,7 +844,12 @@ static void queue_diffs(struct diff_options *opt,
 
        DIFF_QUEUE_CLEAR(&diff_queued_diff);
        diff_tree(&desc1, &desc2, "", opt);
-       diffcore_std(opt);
+       if (opt->detect_rename) {
+               filter_diffs_for_paths(range, 1);
+               if (diff_might_be_rename())
+                       diffcore_std(opt);
+               filter_diffs_for_paths(range, 0);
+       }
        move_diff_queue(queue, &diff_queued_diff);
 
        if (tree1)
@@ -943,8 +1027,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;
        }
@@ -972,7 +1056,8 @@ static int process_diff_filepair(struct rev_info *rev,
        collect_diff(&file_parent, &file_target, &diff);
 
        /* 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);
@@ -1014,11 +1099,24 @@ 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]);
@@ -1049,7 +1147,7 @@ static int process_ranges_ordinary_commit(struct rev_info *rev, struct commit *c
        if (commit->parents)
                parent = commit->parents->item;
 
-       queue_diffs(&rev->diffopt, &queue, commit, parent);
+       queue_diffs(range, &rev->diffopt, &queue, commit, parent);
        changed = process_all_files(&parent_range, rev, &queue, range);
        if (parent)
                add_line_range(rev, parent, parent_range);
@@ -1074,7 +1172,7 @@ static int process_ranges_merge_commit(struct rev_info *rev, struct commit *comm
        for (i = 0; i < nparents; i++) {
                parents[i] = p->item;
                p = p->next;
-               queue_diffs(&rev->diffopt, &diffqueues[i], commit, parents[i]);
+               queue_diffs(range, &rev->diffopt, &diffqueues[i], commit, parents[i]);
        }
 
        for (i = 0; i < nparents; i++) {