#include "strbuf.h"
#include "log-tree.h"
#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)
{
/* 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;
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;
}
/* 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;
}
/*
- * 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 sort_and_merge_range_set(struct range_set *rs)
+static void range_set_check_invariants(struct range_set *rs)
{
int i;
- int o = 1; /* output cursor */
- qsort(rs->ranges, rs->nr, sizeof(struct range), range_cmp);
+ if (!rs)
+ return;
+
+ if (rs->nr)
+ assert(rs->ranges[0].start < rs->ranges[0].end);
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;
+ 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
+ */
+void sort_and_merge_range_set(struct range_set *rs)
+{
+ int i;
+ int o = 0; /* output cursor */
+
+ qsort(rs->ranges, rs->nr, sizeof(struct range), range_cmp);
+
+ 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;
}
assert(o <= rs->nr);
rs->nr = o;
+
+ range_set_check_invariants(rs);
}
/*
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);
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)
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;
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;
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);
}
/*
{
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;
}
*touched_out = touched;
}
-
static struct commit *check_single_commit(struct rev_info *revs)
{
struct object *commit = NULL;
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);
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) {
}
/* shrink the array to fit the elements */
- ends = xrealloc(ends, cur * sizeof(*ends));
+ REALLOC_ARRAY(ends, cur);
*lines = cur-1;
*line_ends = ends;
}
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++;
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))
+ 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;
}
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;
}
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;
}
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)
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;
}
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);
+
+ for (r = range; r; r = r->next)
+ argv_array_push(&array, r->path);
+ paths = argv_array_detach(&array);
+
+ parse_pathspec(&rev->diffopt.pathspec, 0,
+ PATHSPEC_PREFER_FULL, "", paths);
+ /* strings are now owned by pathspec */
free(paths);
}
}
-static void load_tree_desc(struct tree_desc *desc, void **tree,
- const unsigned char *sha1)
+static void move_diff_queue(struct diff_queue_struct *dst,
+ struct diff_queue_struct *src)
{
- 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);
+ assert(src != dst);
+ memcpy(dst, src, sizeof(struct diff_queue_struct));
+ DIFF_QUEUE_CLEAR(src);
}
-static int count_parents(struct commit *commit)
+static void filter_diffs_for_paths(struct line_log_data *range, int keep_deletions)
{
- struct commit_list *parents = commit->parents;
- int count = 0;
- while (parents) {
- count++;
- parents = parents->next;
+ 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);
}
- return count;
+ free(diff_queued_diff.queue);
+ diff_queued_diff = outq;
}
-static void move_diff_queue(struct diff_queue_struct *dst,
- struct diff_queue_struct *src)
+static inline int diff_might_be_rename(void)
{
- assert(src != dst);
- memcpy(dst, src, sizeof(struct diff_queue_struct));
- DIFF_QUEUE_CLEAR(src);
+ 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 diff_options *opt,
+static void queue_diffs(struct line_log_data *range,
+ struct diff_options *opt,
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);
- diffcore_std(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())
+ diffcore_std(opt);
+ 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)
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;
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);
}
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);
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;
}
}
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);
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;
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);
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++) {
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++) {
*/
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);