#include "graph.h"
#include "userdiff.h"
#include "line-log.h"
-#include "argv-array.h"
+#include "strvec.h"
+#include "bloom.h"
static void range_set_grow(struct range_set *rs, size_t extra)
{
assert(out->nr == 0);
while (i < a->nr || j < b->nr) {
- struct range *new;
+ struct range *new_range;
if (i < a->nr && j < b->nr) {
if (ra[i].start < rb[j].start)
- new = &ra[i++];
+ new_range = &ra[i++];
else if (ra[i].start > rb[j].start)
- new = &rb[j++];
+ new_range = &rb[j++];
else if (ra[i].end < rb[j].end)
- new = &ra[i++];
+ new_range = &ra[i++];
else
- new = &rb[j++];
+ new_range = &rb[j++];
} else if (i < a->nr) /* b exhausted */
- new = &ra[i++];
+ new_range = &ra[i++];
else /* a exhausted */
- new = &rb[j++];
- if (new->start == new->end)
+ new_range = &rb[j++];
+ if (new_range->start == new_range->end)
; /* empty range */
- else if (!out->nr || out->ranges[out->nr-1].end < new->start) {
+ else if (!out->nr || out->ranges[out->nr-1].end < new_range->start) {
range_set_grow(out, 1);
- out->ranges[out->nr].start = new->start;
- out->ranges[out->nr].end = new->end;
+ out->ranges[out->nr].start = new_range->start;
+ out->ranges[out->nr].end = new_range->end;
out->nr++;
- } else if (out->ranges[out->nr-1].end < new->end) {
- out->ranges[out->nr-1].end = new->end;
+ } else if (out->ranges[out->nr-1].end < new_range->end) {
+ out->ranges[out->nr-1].end = new_range->end;
}
}
}
struct object *obj = revs->pending.objects[i].item;
if (obj->flags & UNINTERESTING)
continue;
- obj = deref_tag(obj, NULL, 0);
- if (obj->type != OBJ_COMMIT)
+ obj = deref_tag(revs->repo, obj, NULL, 0);
+ if (!obj || obj->type != OBJ_COMMIT)
die("Non commit %s?", revs->pending.objects[i].name);
if (commit)
die("More than one commit to dig from: %s and %s?",
return (struct commit *) commit;
}
-static void fill_blob_sha1(struct commit *commit, struct diff_filespec *spec)
+static void fill_blob_sha1(struct repository *r, struct commit *commit,
+ struct diff_filespec *spec)
{
- unsigned mode;
+ unsigned short mode;
struct object_id oid;
- if (get_tree_entry(commit->object.oid.hash, spec->path,
- oid.hash, &mode))
+ if (get_tree_entry(r, &commit->object.oid, spec->path, &oid, &mode))
die("There is no path %s in the commit", spec->path);
fill_filespec(spec, &oid, 1, mode);
return;
}
-static void fill_line_ends(struct diff_filespec *spec, long *lines,
+static void fill_line_ends(struct repository *r,
+ struct diff_filespec *spec,
+ long *lines,
unsigned long **line_ends)
{
int num = 0, size = 50;
unsigned long *ends = NULL;
char *data = NULL;
- if (diff_populate_filespec(spec, 0))
+ if (diff_populate_filespec(r, spec, NULL))
die("Cannot read blob %s", oid_to_hex(&spec->oid));
ALLOC_ARRAY(ends, size);
}
static struct line_log_data *
-parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
+parse_lines(struct repository *r, struct commit *commit,
+ const char *prefix, struct string_list *args)
{
long lines = 0;
unsigned long *ends = NULL;
long begin = 0, end = 0;
long anchor;
- name_part = skip_range_arg(item->string);
+ name_part = skip_range_arg(item->string, r->index);
if (!name_part || *name_part != ':' || !name_part[1])
die("-L argument not 'start,end:file' or ':funcname:file': %s",
item->string);
name_part);
spec = alloc_filespec(full_name);
- fill_blob_sha1(commit, spec);
- fill_line_ends(spec, &lines, &ends);
+ fill_blob_sha1(r, commit, spec);
+ fill_line_ends(r, spec, &lines, &ends);
cb_data.spec = spec;
cb_data.lines = lines;
cb_data.line_ends = ends;
if (parse_range_arg(range_part, nth_line, &cb_data,
lines, anchor, &begin, &end,
- full_name))
+ full_name, r->index))
die("malformed -L argument '%s'", range_part);
- if (lines < end || ((lines || begin) && lines < begin))
+ if ((!lines && (begin || end)) || lines < begin)
die("file %s has only %lu lines", name_part, lines);
if (begin < 1)
begin = 1;
- if (end < 1)
+ if (end < 1 || lines < end)
end = lines;
begin--;
line_log_data_insert(&ranges, full_name, begin, end);
static void add_line_range(struct rev_info *revs, struct commit *commit,
struct line_log_data *range)
{
- struct line_log_data *old = NULL;
- struct line_log_data *new = NULL;
+ struct line_log_data *old_line = NULL;
+ struct line_log_data *new_line = NULL;
- old = lookup_decoration(&revs->line_log_data, &commit->object);
- if (old && range) {
- new = line_log_data_merge(old, range);
- free_line_log_data(old);
+ old_line = lookup_decoration(&revs->line_log_data, &commit->object);
+ if (old_line && range) {
+ new_line = line_log_data_merge(old_line, range);
+ free_line_log_data(old_line);
} else if (range)
- new = line_log_data_copy(range);
+ new_line = line_log_data_copy(range);
- if (new)
- add_decoration(&revs->line_log_data, &commit->object, new);
+ if (new_line)
+ add_decoration(&revs->line_log_data, &commit->object, new_line);
}
static void clear_commit_line_range(struct rev_info *revs, struct commit *commit)
return ret;
}
+static int same_paths_in_pathspec_and_range(struct pathspec *pathspec,
+ struct line_log_data *range)
+{
+ int i;
+ struct line_log_data *r;
+
+ for (i = 0, r = range; i < pathspec->nr && r; i++, r = r->next)
+ if (strcmp(pathspec->items[i].match, r->path))
+ return 0;
+ if (i < pathspec->nr || r)
+ /* different number of pathspec items and ranges */
+ return 0;
+
+ return 1;
+}
+
+static void parse_pathspec_from_ranges(struct pathspec *pathspec,
+ struct line_log_data *range)
+{
+ struct line_log_data *r;
+ struct strvec array = STRVEC_INIT;
+ const char **paths;
+
+ for (r = range; r; r = r->next)
+ strvec_push(&array, r->path);
+ paths = strvec_detach(&array);
+
+ parse_pathspec(pathspec, 0, PATHSPEC_PREFER_FULL, "", paths);
+ /* strings are now owned by pathspec */
+ free(paths);
+}
+
void line_log_init(struct rev_info *rev, const char *prefix, struct string_list *args)
{
struct commit *commit = NULL;
struct line_log_data *range;
commit = check_single_commit(rev);
- range = parse_lines(commit, prefix, args);
+ range = parse_lines(rev->diffopt.repo, commit, prefix, args);
add_line_range(rev, commit, range);
- if (!rev->diffopt.detect_rename) {
- struct line_log_data *r;
- struct argv_array array = ARGV_ARRAY_INIT;
- const char **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);
- }
+ parse_pathspec_from_ranges(&rev->diffopt.pathspec, range);
}
static void move_diff_queue(struct diff_queue_struct *dst,
struct diff_queue_struct *queue,
struct commit *commit, struct commit *parent)
{
+ struct object_id *tree_oid, *parent_tree_oid;
+
assert(commit);
+ tree_oid = get_commit_tree_oid(commit);
+ parent_tree_oid = parent ? get_commit_tree_oid(parent) : NULL;
+
+ if (opt->detect_rename &&
+ !same_paths_in_pathspec_and_range(&opt->pathspec, range)) {
+ clear_pathspec(&opt->pathspec);
+ parse_pathspec_from_ranges(&opt->pathspec, range);
+ }
DIFF_QUEUE_CLEAR(&diff_queued_diff);
- diff_tree_oid(parent ? &parent->tree->object.oid : NULL,
- &commit->tree->object.oid, "", opt);
- if (opt->detect_rename) {
+ diff_tree_oid(parent_tree_oid, tree_oid, "", opt);
+ if (opt->detect_rename && diff_might_be_rename()) {
+ /* must look at the full tree diff to detect renames */
+ clear_pathspec(&opt->pathspec);
+ DIFF_QUEUE_CLEAR(&diff_queued_diff);
+
+ diff_tree_oid(parent_tree_oid, tree_oid, "", opt);
+
filter_diffs_for_paths(range, 1);
- if (diff_might_be_rename())
- diffcore_std(opt);
+ diffcore_std(opt);
filter_diffs_for_paths(range, 0);
}
move_diff_queue(queue, &diff_queued_diff);
return;
if (pair->one->oid_valid)
- fill_line_ends(pair->one, &p_lines, &p_ends);
- fill_line_ends(pair->two, &t_lines, &t_ends);
+ fill_line_ends(rev->diffopt.repo, pair->one, &p_lines, &p_ends);
+ fill_line_ends(rev->diffopt.repo, pair->two, &t_lines, &t_ends);
fprintf(opt->file, "%s%sdiff --git a/%s b/%s%s\n", prefix, c_meta, pair->one->path, pair->two->path, c_reset);
fprintf(opt->file, "%s%s--- %s%s%s\n", prefix, c_meta,
return 0;
assert(pair->two->oid_valid);
- diff_populate_filespec(pair->two, 0);
+ diff_populate_filespec(rev->diffopt.repo, pair->two, NULL);
file_target.ptr = pair->two->data;
file_target.size = pair->two->size;
if (pair->one->oid_valid) {
- diff_populate_filespec(pair->one, 0);
+ diff_populate_filespec(rev->diffopt.repo, pair->one, NULL);
file_parent.ptr = pair->one->data;
file_parent.size = pair->one->size;
} else {
static struct diff_filepair *diff_filepair_dup(struct diff_filepair *pair)
{
- struct diff_filepair *new = xmalloc(sizeof(struct diff_filepair));
- new->one = pair->one;
- new->two = pair->two;
- new->one->count++;
- new->two->count++;
- return new;
+ struct diff_filepair *new_filepair = xmalloc(sizeof(struct diff_filepair));
+ new_filepair->one = pair->one;
+ new_filepair->two = pair->two;
+ new_filepair->one->count++;
+ new_filepair->two->count++;
+ return new_filepair;
}
static void free_diffqueues(int n, struct diff_queue_struct *dq)
int line_log_print(struct rev_info *rev, struct commit *commit)
{
- struct line_log_data *range = lookup_line_range(rev, commit);
show_log(rev);
- dump_diff_hacky(rev, range);
+ if (!(rev->diffopt.output_format & DIFF_FORMAT_NO_OUTPUT)) {
+ struct line_log_data *range = lookup_line_range(rev, commit);
+ dump_diff_hacky(rev, range);
+ }
return 1;
}
+static int bloom_filter_check(struct rev_info *rev,
+ struct commit *commit,
+ struct line_log_data *range)
+{
+ struct bloom_filter *filter;
+ struct bloom_key key;
+ int result = 0;
+
+ if (!commit->parents)
+ return 1;
+
+ if (!rev->bloom_filter_settings ||
+ !(filter = get_bloom_filter(rev->repo, commit)))
+ return 1;
+
+ if (!range)
+ return 0;
+
+ while (!result && range) {
+ fill_bloom_key(range->path, strlen(range->path), &key, rev->bloom_filter_settings);
+
+ if (bloom_filter_contains(filter, &key, rev->bloom_filter_settings))
+ result = 1;
+
+ clear_bloom_key(&key);
+ range = range->next;
+ }
+
+ return result;
+}
+
static int process_ranges_ordinary_commit(struct rev_info *rev, struct commit *commit,
struct line_log_data *range)
{
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);
free_line_log_data(parent_range);
/* NEEDSWORK leaking like a sieve */
}
-static int process_ranges_arbitrary_commit(struct rev_info *rev, struct commit *commit)
+int line_log_process_ranges_arbitrary_commit(struct rev_info *rev, struct commit *commit)
{
struct line_log_data *range = lookup_line_range(rev, commit);
int changed = 0;
if (range) {
- if (!commit->parents || !commit->parents->next)
+ if (commit->parents && !bloom_filter_check(rev, commit, range)) {
+ struct line_log_data *prange = line_log_data_copy(range);
+ add_line_range(rev, commit->parents->item, prange);
+ clear_commit_line_range(rev, commit);
+ } else if (!commit->parents || !commit->parents->next)
changed = process_ranges_ordinary_commit(rev, commit, range);
else
changed = process_ranges_merge_commit(rev, commit, range);
while (list) {
struct commit_list *to_free = NULL;
commit = list->item;
- if (process_ranges_arbitrary_commit(rev, commit)) {
+ if (line_log_process_ranges_arbitrary_commit(rev, commit)) {
*pp = list;
pp = &list->next;
} else