mirror of
https://github.com/git/git.git
synced 2026-03-12 09:59:45 +01:00
Walking forward through history (i.e., topologically earliest commits first), we filter the parent list of every commit as follows. Consider a parent P: - If P touches any of the interesting line ranges, we keep it. - If P is a merge and it takes all the interesting line ranges from one of its parents, P is rewritten to this parent, else we keep P. - Otherwise, P is rewritten to its (only) parent P^. Signed-off-by: Bo Yang <struggleyb.nku@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1510 lines
36 KiB
C
1510 lines
36 KiB
C
#include "cache.h"
|
|
#include "tag.h"
|
|
#include "blob.h"
|
|
#include "tree.h"
|
|
#include "diff.h"
|
|
#include "commit.h"
|
|
#include "decorate.h"
|
|
#include "revision.h"
|
|
#include "xdiff-interface.h"
|
|
#include "strbuf.h"
|
|
#include "log-tree.h"
|
|
#include "graph.h"
|
|
#include "line.h"
|
|
|
|
static int limited;
|
|
|
|
static void cleanup(struct diff_line_range *r)
|
|
{
|
|
while (r) {
|
|
struct diff_line_range *next = r->next;
|
|
DIFF_LINE_RANGE_CLEAR(r);
|
|
free(r);
|
|
r = next;
|
|
}
|
|
}
|
|
|
|
static struct object *verify_commit(struct rev_info *revs)
|
|
{
|
|
struct object *commit = NULL;
|
|
int found = -1;
|
|
int i;
|
|
|
|
for (i = 0; i < revs->pending.nr; i++) {
|
|
struct object *obj = revs->pending.objects[i].item;
|
|
if (obj->flags & UNINTERESTING)
|
|
continue;
|
|
while (obj->type == OBJ_TAG)
|
|
obj = deref_tag(obj, NULL, 0);
|
|
if (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?",
|
|
revs->pending.objects[i].name,
|
|
revs->pending.objects[found].name);
|
|
commit = obj;
|
|
found = i;
|
|
}
|
|
|
|
if (commit == NULL)
|
|
die("No commit specified?");
|
|
|
|
return commit;
|
|
}
|
|
|
|
static void fill_blob_sha1(struct commit *commit, struct diff_line_range *r)
|
|
{
|
|
unsigned mode;
|
|
unsigned char sha1[20];
|
|
|
|
while (r) {
|
|
if (get_tree_entry(commit->object.sha1, r->spec->path,
|
|
sha1, &mode))
|
|
goto error;
|
|
fill_filespec(r->spec, sha1, mode);
|
|
r = r->next;
|
|
}
|
|
|
|
return;
|
|
error:
|
|
die("There is no path %s in the commit", r->spec->path);
|
|
}
|
|
|
|
static void fill_line_ends(struct diff_filespec *spec, long *lines,
|
|
unsigned long **line_ends)
|
|
{
|
|
int num = 0, size = 50;
|
|
long cur = 0;
|
|
unsigned long *ends = NULL;
|
|
char *data = NULL;
|
|
|
|
if (diff_populate_filespec(spec, 0))
|
|
die("Cannot read blob %s", sha1_to_hex(spec->sha1));
|
|
|
|
ends = xmalloc(size * sizeof(*ends));
|
|
ends[cur++] = 0;
|
|
data = spec->data;
|
|
while (num < spec->size) {
|
|
if (data[num] == '\n' || num == spec->size - 1) {
|
|
ALLOC_GROW(ends, (cur + 1), size);
|
|
ends[cur++] = num;
|
|
}
|
|
num++;
|
|
}
|
|
|
|
/* shrink the array to fit the elements */
|
|
ends = xrealloc(ends, cur * sizeof(*ends));
|
|
*lines = cur;
|
|
*line_ends = ends;
|
|
}
|
|
|
|
struct nth_line_cb {
|
|
struct diff_filespec *spec;
|
|
long lines;
|
|
unsigned long *line_ends;
|
|
};
|
|
|
|
static const char *nth_line(void *data, long line)
|
|
{
|
|
struct nth_line_cb *d = data;
|
|
assert(d && line < d->lines);
|
|
assert(d->spec && d->spec->data);
|
|
|
|
if (line == 0)
|
|
return (char *)d->spec->data;
|
|
else
|
|
return (char *)d->spec->data + d->line_ends[line] + 1;
|
|
}
|
|
|
|
/*
|
|
* Parsing of (comma separated) one item in the -L option
|
|
*/
|
|
const char *parse_loc(const char *spec, nth_line_fn_t nth_line,
|
|
void *data, long lines, long begin, long *ret)
|
|
{
|
|
char *term;
|
|
const char *line;
|
|
long num;
|
|
int reg_error;
|
|
regex_t regexp;
|
|
regmatch_t match[1];
|
|
|
|
/* Catch the '$' matcher, now it is used to match the last
|
|
* line of the file. */
|
|
if (spec[0] == '$') {
|
|
*ret = lines;
|
|
return spec + 1;
|
|
}
|
|
|
|
/* Allow "-L <something>,+20" to mean starting at <something>
|
|
* for 20 lines, or "-L <something>,-5" for 5 lines ending at
|
|
* <something>.
|
|
*/
|
|
if (1 < begin && (spec[0] == '+' || spec[0] == '-')) {
|
|
num = strtol(spec + 1, &term, 10);
|
|
if (term != spec + 1) {
|
|
if (spec[0] == '-')
|
|
num = 0 - num;
|
|
if (0 < num)
|
|
*ret = begin + num - 2;
|
|
else if (!num)
|
|
*ret = begin;
|
|
else
|
|
*ret = begin + num;
|
|
return term;
|
|
}
|
|
return spec;
|
|
}
|
|
num = strtol(spec, &term, 10);
|
|
if (term != spec) {
|
|
*ret = num;
|
|
return term;
|
|
}
|
|
if (spec[0] != '/')
|
|
return spec;
|
|
|
|
/* it could be a regexp of form /.../ */
|
|
for (term = (char *) spec + 1; *term && *term != '/'; term++) {
|
|
if (*term == '\\')
|
|
term++;
|
|
}
|
|
if (*term != '/')
|
|
return spec;
|
|
|
|
/* try [spec+1 .. term-1] as regexp */
|
|
*term = 0;
|
|
begin--; /* input is in human terms */
|
|
line = nth_line(data, begin);
|
|
|
|
if (!(reg_error = regcomp(®exp, spec + 1, REG_NEWLINE)) &&
|
|
!(reg_error = regexec(®exp, line, 1, match, 0))) {
|
|
const char *cp = line + match[0].rm_so;
|
|
const char *nline;
|
|
|
|
while (begin++ < lines) {
|
|
nline = nth_line(data, begin);
|
|
if (line <= cp && cp < nline)
|
|
break;
|
|
line = nline;
|
|
}
|
|
*ret = begin;
|
|
regfree(®exp);
|
|
*term++ = '/';
|
|
return term;
|
|
} else {
|
|
char errbuf[1024];
|
|
regerror(reg_error, ®exp, errbuf, 1024);
|
|
die("-L parameter '%s': %s", spec + 1, errbuf);
|
|
}
|
|
}
|
|
|
|
static void parse_range(long lines, unsigned long *line_ends,
|
|
struct line_range *r, struct diff_filespec *spec)
|
|
{
|
|
const char *term;
|
|
struct nth_line_cb data = {spec, lines, line_ends};
|
|
|
|
term = parse_loc(r->arg, nth_line, &data, lines - 1, 1, &r->start);
|
|
if (*term == ',') {
|
|
term = parse_loc(term + 1, nth_line, &data, lines - 1,
|
|
r->start + 1, &r->end);
|
|
if (*term)
|
|
die("-L parameter's argument should be <start>,<end>");
|
|
}
|
|
|
|
if (*term)
|
|
die("-L parameter's argument should be <start>,<end>");
|
|
|
|
if (r->start < 1)
|
|
r->start = 1;
|
|
if (r->end >= lines)
|
|
r->end = lines - 1;
|
|
|
|
if (r->start > r->end) {
|
|
long tmp = r->start;
|
|
r->start = r->end;
|
|
r->end = tmp;
|
|
}
|
|
}
|
|
|
|
static void parse_lines(struct commit *commit, struct diff_line_range *r)
|
|
{
|
|
int i;
|
|
struct line_range *old_range = NULL;
|
|
long lines = 0;
|
|
unsigned long *ends = NULL;
|
|
|
|
while (r) {
|
|
struct diff_filespec *spec = r->spec;
|
|
int num = r->nr;
|
|
assert(spec);
|
|
fill_blob_sha1(commit, r);
|
|
old_range = r->ranges;
|
|
r->ranges = NULL;
|
|
r->nr = r->alloc = 0;
|
|
fill_line_ends(spec, &lines, &ends);
|
|
for (i = 0; i < num; i++) {
|
|
parse_range(lines, ends, old_range + i, spec);
|
|
diff_line_range_insert(r, old_range[i].arg,
|
|
old_range[i].start, old_range[i].end);
|
|
}
|
|
|
|
free(ends);
|
|
ends = NULL;
|
|
|
|
r = r->next;
|
|
free(old_range);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Insert a new line range into a diff_line_range struct, and keep the
|
|
* r->ranges sorted by their starting line number.
|
|
*/
|
|
struct line_range *diff_line_range_insert(struct diff_line_range *r,
|
|
const char *arg, int start, int end)
|
|
{
|
|
int i = 0;
|
|
struct line_range *rs = r->ranges;
|
|
int left_merge = 0, right_merge = 0;
|
|
|
|
assert(r != NULL);
|
|
assert(start <= end);
|
|
|
|
if (r->nr == 0 || rs[r->nr - 1].end < start - 1) {
|
|
int num = 0;
|
|
DIFF_LINE_RANGE_GROW(r);
|
|
rs = r->ranges;
|
|
num = r->nr - 1;
|
|
rs[num].arg = arg;
|
|
rs[num].start = start;
|
|
rs[num].end = end;
|
|
return rs + num;
|
|
}
|
|
|
|
for (; i < r->nr; i++) {
|
|
if (rs[i].end < start - 1)
|
|
continue;
|
|
if (rs[i].end == start - 1) {
|
|
rs[i].end = end;
|
|
right_merge = 1;
|
|
goto out;
|
|
}
|
|
|
|
assert(rs[i].end > start - 1);
|
|
if (rs[i].start <= start) {
|
|
if (rs[i].end < end) {
|
|
rs[i].end = end;
|
|
right_merge = 1;
|
|
}
|
|
goto out;
|
|
} else if (rs[i].start <= end + 1) {
|
|
rs[i].start = start;
|
|
left_merge = 1;
|
|
if (rs[i].end < end) {
|
|
rs[i].end = end;
|
|
right_merge = 1;
|
|
}
|
|
goto out;
|
|
} else {
|
|
int num = r->nr - i;
|
|
DIFF_LINE_RANGE_GROW(r);
|
|
rs = r->ranges;
|
|
memmove(rs + i + 1, rs + i, num * sizeof(struct line_range));
|
|
rs[i].arg = arg;
|
|
rs[i].start = start;
|
|
rs[i].end = end;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
out:
|
|
assert(r->nr != i);
|
|
if (left_merge) {
|
|
int j = i;
|
|
for (; j > -1; j--) {
|
|
if (rs[j].end >= rs[i].start - 1)
|
|
if (rs[j].start < rs[i].start)
|
|
rs[i].start = rs[j].start;
|
|
}
|
|
memmove(rs + j + 1, rs + i, (r->nr - i) * sizeof(struct line_range));
|
|
r->nr -= i - j - 1;
|
|
}
|
|
if (right_merge) {
|
|
int j = i;
|
|
for (; j < r->nr; j++) {
|
|
if (rs[j].start <= rs[i].end + 1)
|
|
if (rs[j].end > rs[i].end)
|
|
rs[i].end = rs[j].end;
|
|
}
|
|
if (j < r->nr)
|
|
memmove(rs + i + 1, rs + j, (r->nr - j) * sizeof(struct line_range));
|
|
r->nr -= j - i - 1;
|
|
}
|
|
assert(r->nr);
|
|
|
|
return rs + i;
|
|
}
|
|
|
|
void diff_line_range_clear(struct diff_line_range *r)
|
|
{
|
|
int i = 0, zero = 0;
|
|
|
|
for (; i < r->nr; i++) {
|
|
struct line_range *rg = r->ranges + i;
|
|
RANGE_CLEAR(rg);
|
|
}
|
|
|
|
if (r->prev) {
|
|
zero = 0;
|
|
if (r->prev->count == 1)
|
|
zero = 1;
|
|
free_filespec(r->prev);
|
|
if (zero)
|
|
r->prev = NULL;
|
|
}
|
|
if (r->spec) {
|
|
zero = 0;
|
|
if (r->spec->count == 1)
|
|
zero = 1;
|
|
free_filespec(r->spec);
|
|
if (zero)
|
|
r->spec = NULL;
|
|
}
|
|
|
|
r->status = '\0';
|
|
r->alloc = r->nr = 0;
|
|
|
|
if (r->ranges)
|
|
free(r->ranges);
|
|
r->ranges = NULL;
|
|
r->next = NULL;
|
|
}
|
|
|
|
void diff_line_range_append(struct diff_line_range *r, const char *arg)
|
|
{
|
|
DIFF_LINE_RANGE_GROW(r);
|
|
r->ranges[r->nr - 1].arg = arg;
|
|
}
|
|
|
|
struct diff_line_range *diff_line_range_clone(struct diff_line_range *r)
|
|
{
|
|
struct diff_line_range *ret = xmalloc(sizeof(*ret));
|
|
int i = 0;
|
|
|
|
assert(r);
|
|
DIFF_LINE_RANGE_INIT(ret);
|
|
ret->ranges = xcalloc(r->nr, sizeof(struct line_range));
|
|
memcpy(ret->ranges, r->ranges, sizeof(struct line_range) * r->nr);
|
|
|
|
ret->alloc = ret->nr = r->nr;
|
|
|
|
for (; i < ret->nr; i++)
|
|
PRINT_PAIR_INIT(&ret->ranges[i].pair);
|
|
|
|
ret->spec = r->spec;
|
|
assert(ret->spec);
|
|
ret->spec->count++;
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct diff_line_range *diff_line_range_clone_deeply(struct diff_line_range *r)
|
|
{
|
|
struct diff_line_range *ret = NULL;
|
|
struct diff_line_range *tmp = NULL, *prev = NULL;
|
|
|
|
assert(r);
|
|
ret = tmp = prev = diff_line_range_clone(r);
|
|
r = r->next;
|
|
while (r) {
|
|
tmp = diff_line_range_clone(r);
|
|
prev->next = tmp;
|
|
prev = tmp;
|
|
r = r->next;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct diff_line_range *diff_line_range_merge(struct diff_line_range *out,
|
|
struct diff_line_range *other)
|
|
{
|
|
struct diff_line_range *one = out, *two = other;
|
|
struct diff_line_range *pone = NULL;
|
|
|
|
while (one) {
|
|
struct diff_line_range *ptwo;
|
|
two = other;
|
|
ptwo = other;
|
|
while (two) {
|
|
if (!strcmp(one->spec->path, two->spec->path)) {
|
|
int i = 0;
|
|
for (; i < two->nr; i++) {
|
|
diff_line_range_insert(one, NULL,
|
|
two->ranges[i].start,
|
|
two->ranges[i].end);
|
|
}
|
|
if (two == other)
|
|
other = other->next;
|
|
else
|
|
ptwo->next = two->next;
|
|
DIFF_LINE_RANGE_CLEAR(two);
|
|
free(two);
|
|
two = NULL;
|
|
|
|
break;
|
|
}
|
|
|
|
ptwo = two;
|
|
two = two->next;
|
|
}
|
|
|
|
pone = one;
|
|
one = one->next;
|
|
}
|
|
pone->next = other;
|
|
|
|
return out;
|
|
}
|
|
|
|
void add_line_range(struct rev_info *revs, struct commit *commit,
|
|
struct diff_line_range *r)
|
|
{
|
|
struct diff_line_range *ret = NULL;
|
|
|
|
ret = lookup_decoration(&revs->line_range, &commit->object);
|
|
if (ret != NULL && r != NULL)
|
|
diff_line_range_merge(ret, r);
|
|
else
|
|
add_decoration(&revs->line_range, &commit->object, r);
|
|
|
|
if (r != NULL)
|
|
commit->object.flags |= RANGE_UPDATE;
|
|
}
|
|
|
|
struct diff_line_range *lookup_line_range(struct rev_info *revs,
|
|
struct commit *commit)
|
|
{
|
|
struct diff_line_range *ret = NULL;
|
|
|
|
ret = lookup_decoration(&revs->line_range, &commit->object);
|
|
return ret;
|
|
}
|
|
|
|
void setup_line(struct rev_info *rev, struct diff_line_range *r)
|
|
{
|
|
struct commit *commit = NULL;
|
|
struct diff_options *opt = &rev->diffopt;
|
|
|
|
commit = (struct commit *)verify_commit(rev);
|
|
parse_lines(commit, r);
|
|
|
|
add_line_range(rev, commit, r);
|
|
/*
|
|
* Note we support -M/-C to detect file rename
|
|
*/
|
|
opt->nr_paths = 0;
|
|
diff_tree_release_paths(opt);
|
|
}
|
|
|
|
struct take_range_cb_data {
|
|
struct diff_line_range *interesting; /* currently interesting ranges */
|
|
struct diff_line_range *range;
|
|
/* the ranges corresponds to the interesting ranges of parent commit */
|
|
long plno, tlno;
|
|
/* the last line number of diff hunk */
|
|
int diff;
|
|
/* whether there is some line changes between the current
|
|
* commit and its parent */
|
|
};
|
|
|
|
#define SCALE_FACTOR 4
|
|
/*
|
|
* [p_start, p_end] represents the pre-image of current diff hunk,
|
|
* [t_start, t_end] represents the post-image of the current diff hunk,
|
|
* [start, end] represents the currently interesting line range in
|
|
* post-image,
|
|
* [o_start, o_end] represents the original line range that coresponds
|
|
* to current line range.
|
|
*/
|
|
void map_lines(long p_start, long p_end, long t_start, long t_end,
|
|
long start, long end, long *o_start, long *o_end)
|
|
{
|
|
/*
|
|
* Normally, p_start should be less than p_end, so does the
|
|
* t_start and t_end. But when the line range is added from
|
|
* scratch, p_start will be greater than p_end. When the line
|
|
* range is deleted, t_start will be greater than t_end.
|
|
*/
|
|
if (p_start > p_end) {
|
|
*o_start = *o_end = 0;
|
|
return;
|
|
}
|
|
/* A deletion */
|
|
if (t_start > t_end) {
|
|
*o_start = p_start;
|
|
*o_end = p_end;
|
|
return;
|
|
}
|
|
|
|
if (start == t_start && end == t_end) {
|
|
*o_start = p_start;
|
|
*o_end = p_end;
|
|
return;
|
|
}
|
|
|
|
if (start == t_start) {
|
|
*o_start = p_start;
|
|
*o_end = p_start + (end - start);
|
|
if (*o_end > p_end)
|
|
*o_end = p_end;
|
|
return;
|
|
}
|
|
|
|
if (end == t_end) {
|
|
*o_start = p_end - (end - start);
|
|
if (*o_start < p_start)
|
|
*o_start = p_start;
|
|
*o_end = p_end;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* A heuristic for lines mapping:
|
|
*
|
|
* When the pre-image is no more than 1/SCALE_FACTOR of the post-image,
|
|
* there is no effective way to find out which part of pre-image
|
|
* corresponds to the currently interesting range of post-image.
|
|
* And we are in the danger of tracking totally useless lines.
|
|
* So, we just treat all the post-image lines as added from scratch.
|
|
*/
|
|
if (SCALE_FACTOR * (p_end - p_start + 1) < (t_end - t_start + 1)) {
|
|
*o_start = *o_end = 0;
|
|
return;
|
|
}
|
|
|
|
*o_start = p_start + start - t_start;
|
|
*o_end = p_end - (t_end - end);
|
|
|
|
if (*o_start > *o_end) {
|
|
int temp = *o_start;
|
|
*o_start = *o_end;
|
|
*o_end = temp;
|
|
}
|
|
|
|
if (*o_start < p_start)
|
|
*o_start = p_start;
|
|
if (*o_end > p_end)
|
|
*o_end = p_end;
|
|
}
|
|
|
|
/*
|
|
* When same == 1:
|
|
* [p_start, p_end] represents the diff hunk line range of pre-image,
|
|
* [t_start, t_end] represents the diff hunk line range of post-image.
|
|
* When same == 0, they represent a range of identical lines between
|
|
* two images.
|
|
*
|
|
* This function find out the corresponding line ranges of currently
|
|
* interesting ranges which this diff hunk touches.
|
|
*/
|
|
static void map_range(struct take_range_cb_data *data, int same,
|
|
long p_start, long p_end, long t_start, long t_end)
|
|
{
|
|
struct line_range *ranges = data->interesting->ranges;
|
|
long takens, takene, start, end;
|
|
int i = 0, out = 0, added = 0;
|
|
long op_start = p_start, op_end = p_end, ot_start = t_start, ot_end = t_end;
|
|
|
|
for (; i < data->interesting->nr; i++) {
|
|
added = 0;
|
|
if (t_start > ranges[i].end)
|
|
continue;
|
|
if (t_end < ranges[i].start)
|
|
break;
|
|
|
|
if (t_start > ranges[i].start) {
|
|
start = t_start;
|
|
takens = p_start;
|
|
if (t_end >= ranges[i].end) {
|
|
end = ranges[i].end;
|
|
takene = p_start + end - t_start;
|
|
} else {
|
|
end = t_end;
|
|
takene = p_end;
|
|
out = 1;
|
|
}
|
|
} else {
|
|
start = ranges[i].start;
|
|
takens = p_start + start - t_start;
|
|
if (t_end >= ranges[i].end) {
|
|
end = ranges[i].end;
|
|
takene = p_start + end - t_start;
|
|
} else {
|
|
end = t_end;
|
|
takene = p_end;
|
|
out = 1;
|
|
}
|
|
}
|
|
|
|
if (!same) {
|
|
struct print_pair *pair = &ranges[i].pair;
|
|
struct print_range *rr = NULL;
|
|
PRINT_PAIR_GROW(pair);
|
|
rr = pair->ranges + pair->nr - 1;
|
|
PRINT_RANGE_INIT(rr);
|
|
rr->start = start;
|
|
rr->end = end;
|
|
map_lines(op_start, op_end, ot_start, ot_end, start, end,
|
|
&takens, &takene);
|
|
if (takens == 0 && takene == 0) {
|
|
added = 1;
|
|
rr->line_added = 1;
|
|
}
|
|
rr->pstart = takens;
|
|
rr->pend = takene;
|
|
data->diff = 1;
|
|
data->interesting->diff = 1;
|
|
ranges[i].diff = 1;
|
|
}
|
|
if (added) {
|
|
/* Code movement/copy detect here, now place two dummy statements here */
|
|
int dummy = 0;
|
|
dummy = 1;
|
|
} else {
|
|
struct line_range *added_range = diff_line_range_insert(data->range,
|
|
NULL, takens, takene);
|
|
assert(added_range);
|
|
ranges[i].pstart = added_range->start;
|
|
ranges[i].pend = added_range->end;
|
|
}
|
|
|
|
t_start = end + 1;
|
|
p_start = takene + 1;
|
|
|
|
if (out)
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* [p_start, p_end] represents the line range of pre-image,
|
|
* [t_start, t_end] represents the line range of post-image,
|
|
* and they are identical lines.
|
|
*
|
|
* This function substracts out the identical lines between current
|
|
* commit and its parent, from currently interesting ranges.
|
|
*/
|
|
static void take_range(struct take_range_cb_data *data,
|
|
long p_start, long p_end, long t_start, long t_end)
|
|
{
|
|
struct line_range *ranges = data->interesting->ranges;
|
|
long takens, takene, start, end;
|
|
int i = 0, out = 0, added = 0;
|
|
|
|
for (; i < data->interesting->nr; i++) {
|
|
added = 0;
|
|
if (t_start > ranges[i].end)
|
|
continue;
|
|
if (t_end < ranges[i].start)
|
|
break;
|
|
|
|
if (t_start > ranges[i].start) {
|
|
long tmp = ranges[i].end;
|
|
ranges[i].end = t_start - 1;
|
|
start = t_start;
|
|
takens = p_start;
|
|
if (t_end >= tmp) {
|
|
end = tmp;
|
|
takene = p_start + end - t_start;
|
|
p_start = takene + 1;
|
|
t_start = end + 1;
|
|
} else {
|
|
end = t_end;
|
|
takene = p_end;
|
|
diff_line_range_insert(data->interesting, NULL,
|
|
t_end + 1, tmp);
|
|
out = 1;
|
|
}
|
|
} else {
|
|
start = ranges[i].start;
|
|
takens = p_start + start - t_start;
|
|
if (t_end >= ranges[i].end) {
|
|
int num = data->interesting->nr - 1;
|
|
end = ranges[i].end;
|
|
takene = p_start + end - t_start;
|
|
t_start = end + 1;
|
|
p_start = takene + 1;
|
|
memmove(ranges + i, ranges + i + 1, (num - i) * sizeof(*ranges));
|
|
data->interesting->nr = num;
|
|
i--;
|
|
} else {
|
|
end = t_end;
|
|
takene = p_end;
|
|
ranges[i].start = t_end + 1;
|
|
out = 1;
|
|
}
|
|
}
|
|
|
|
diff_line_range_insert(data->range, NULL, takens, takene);
|
|
|
|
if (out)
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void take_range_cb(void *data, long same, long p_next, long t_next)
|
|
{
|
|
struct take_range_cb_data *d = data;
|
|
long p_start = d->plno + 1, t_start = d->tlno + 1;
|
|
long p_end = p_start + same - t_start, t_end = same;
|
|
|
|
/* If one file is added from scratch, we should not bother to call
|
|
* take_range, since there is nothing to take
|
|
*/
|
|
if (t_end >= t_start)
|
|
take_range(d, p_start, p_end, t_start, t_end);
|
|
d->plno = p_next;
|
|
d->tlno = t_next;
|
|
}
|
|
|
|
static void map_range_cb(void *data, long same, long p_next, long t_next)
|
|
{
|
|
struct take_range_cb_data *d = data;
|
|
|
|
long p_start = d->plno + 1;
|
|
long t_start = d->tlno + 1;
|
|
long p_end = same - t_start + p_start;
|
|
long t_end = same;
|
|
|
|
/* Firstly, take the unchanged lines from child */
|
|
if (t_end >= t_start)
|
|
map_range(d, 1, p_start, p_end, t_start, t_end);
|
|
|
|
/* find out which lines to print */
|
|
t_start = same + 1;
|
|
p_start = d->plno + t_start - d->tlno;
|
|
map_range(d, 0, p_start, p_next, t_start, t_next);
|
|
|
|
d->plno = p_next;
|
|
d->tlno = t_next;
|
|
}
|
|
|
|
/*
|
|
* We support two kinds of operation in this function:
|
|
* 1. map == 0, take the same lines from the current commit and assign it
|
|
* to parent;
|
|
* 2. map == 1, in addition to the same lines, we also map the changed lines
|
|
* from the current commit to the parent according to the
|
|
* diff output.
|
|
* take_range_cb and take_range are used to take same lines from current commit
|
|
* to parents.
|
|
* map_range_cb and map_range are used to map line ranges to the parent.
|
|
*/
|
|
static int assign_range_to_parent(struct rev_info *rev, struct commit *c,
|
|
struct commit *p, struct diff_line_range *r,
|
|
struct diff_options *opt, int map)
|
|
{
|
|
struct diff_line_range *rr = xmalloc(sizeof(*rr));
|
|
struct diff_line_range *cr = rr, *prev_r = rr;
|
|
struct diff_line_range *rg = NULL;
|
|
struct tree_desc desc1, desc2;
|
|
void *tree1 = NULL, *tree2 = NULL;
|
|
unsigned long size1, size2;
|
|
struct diff_queue_struct *queue;
|
|
struct take_range_cb_data cb = {NULL, cr, 0, 0};
|
|
xpparam_t xpp;
|
|
xdemitconf_t xecfg;
|
|
int i, diff = 0;
|
|
xdiff_emit_hunk_consume_fn fn = map ? map_range_cb : take_range_cb;
|
|
|
|
DIFF_LINE_RANGE_INIT(cr);
|
|
memset(&xpp, 0, sizeof(xpp));
|
|
memset(&xecfg, 0, sizeof(xecfg));
|
|
xecfg.ctxlen = xecfg.interhunkctxlen = 0;
|
|
|
|
/*
|
|
* Compose up two trees, for root commit, we make up a empty tree.
|
|
*/
|
|
assert(c);
|
|
tree2 = read_object_with_reference(c->tree->object.sha1, "tree",
|
|
&size2, NULL);
|
|
if (tree2 == NULL)
|
|
die("Unable to read tree (%s)", sha1_to_hex(c->tree->object.sha1));
|
|
init_tree_desc(&desc2, tree2, size2);
|
|
if (p) {
|
|
tree1 = read_object_with_reference(p->tree->object.sha1,
|
|
"tree", &size1, NULL);
|
|
if (tree1 == NULL)
|
|
die("Unable to read tree (%s)",
|
|
sha1_to_hex(p->tree->object.sha1));
|
|
init_tree_desc(&desc1, tree1, size1);
|
|
} else {
|
|
init_tree_desc(&desc1, "", 0);
|
|
}
|
|
|
|
DIFF_QUEUE_CLEAR(&diff_queued_diff);
|
|
diff_tree(&desc1, &desc2, "", opt);
|
|
diffcore_std(opt);
|
|
|
|
queue = &diff_queued_diff;
|
|
for (i = 0; i < queue->nr; i++) {
|
|
struct diff_filepair *pair = queue->queue[i];
|
|
struct diff_line_range *rg = r;
|
|
mmfile_t file_p, file_t;
|
|
assert(pair->two->path);
|
|
while (rg) {
|
|
assert(rg->spec->path);
|
|
if (!strcmp(rg->spec->path, pair->two->path))
|
|
break;
|
|
rg = rg->next;
|
|
}
|
|
|
|
if (rg == NULL)
|
|
continue;
|
|
rg->touch = 1;
|
|
if (rg->nr == 0)
|
|
continue;
|
|
|
|
rg->status = pair->status;
|
|
assert(pair->two->sha1_valid);
|
|
diff_populate_filespec(pair->two, 0);
|
|
file_t.ptr = pair->two->data;
|
|
file_t.size = pair->two->size;
|
|
|
|
if (rg->prev)
|
|
free_filespec(rg->prev);
|
|
rg->prev = pair->one;
|
|
rg->prev->count++;
|
|
if (pair->one->sha1_valid) {
|
|
diff_populate_filespec(pair->one, 0);
|
|
file_p.ptr = pair->one->data;
|
|
file_p.size = pair->one->size;
|
|
} else {
|
|
file_p.ptr = "";
|
|
file_p.size = 0;
|
|
}
|
|
|
|
if (cr->nr != 0) {
|
|
struct diff_line_range *tmp = xmalloc(sizeof(*tmp));
|
|
cr->next = tmp;
|
|
prev_r = cr;
|
|
cr = tmp;
|
|
} else if (cr->spec)
|
|
DIFF_LINE_RANGE_CLEAR(cr);
|
|
|
|
DIFF_LINE_RANGE_INIT(cr);
|
|
if (pair->one->sha1_valid) {
|
|
cr->spec = pair->one;
|
|
cr->spec->count++;
|
|
}
|
|
|
|
cb.interesting = rg;
|
|
cb.range = cr;
|
|
cb.diff = 0;
|
|
cb.plno = cb.tlno = 0;
|
|
xdi_diff_hunks(&file_p, &file_t, fn, &cb, &xpp, &xecfg);
|
|
if (cb.diff)
|
|
diff = 1;
|
|
/*
|
|
* The remain part is the same part.
|
|
* Instead of calculating the true line number of the two files,
|
|
* use the biggest integer.
|
|
*/
|
|
if (map)
|
|
map_range(&cb, 1, cb.plno + 1, INT_MAX, cb.tlno + 1, INT_MAX);
|
|
else
|
|
take_range(&cb, cb.plno + 1, INT_MAX, cb.tlno + 1, INT_MAX);
|
|
}
|
|
opt->output_format = DIFF_FORMAT_NO_OUTPUT;
|
|
diff_flush(opt);
|
|
|
|
/*
|
|
* Collect the untouch ranges, this comes from the files not changed
|
|
* between two commit.
|
|
*/
|
|
rg = r;
|
|
while (rg) {
|
|
/* clear the touch one to make it usable in next round */
|
|
if (rg->touch) {
|
|
rg->touch = 0;
|
|
} else {
|
|
struct diff_line_range *untouch = diff_line_range_clone(rg);
|
|
if (prev_r == rr && rr->nr == 0) {
|
|
rr = prev_r = untouch;
|
|
} else {
|
|
prev_r->next = untouch;
|
|
prev_r = untouch;
|
|
}
|
|
}
|
|
rg = rg->next;
|
|
}
|
|
|
|
if (cr->nr == 0) {
|
|
DIFF_LINE_RANGE_CLEAR(cr);
|
|
free(cr);
|
|
if (prev_r == cr)
|
|
rr = NULL;
|
|
else
|
|
prev_r->next = NULL;
|
|
}
|
|
|
|
if (!map)
|
|
goto out;
|
|
|
|
if (rr) {
|
|
assert(p);
|
|
add_line_range(rev, p, rr);
|
|
} else {
|
|
/*
|
|
* If there is no new ranges assigned to the parent,
|
|
* we should mark it as a 'root' commit.
|
|
*/
|
|
free(c->parents);
|
|
c->parents = NULL;
|
|
}
|
|
|
|
/* and the ranges of current commit c is updated */
|
|
c->object.flags &= ~RANGE_UPDATE;
|
|
if (diff)
|
|
c->object.flags |= NEED_PRINT;
|
|
|
|
out:
|
|
if (tree1)
|
|
free(tree1);
|
|
if (tree2)
|
|
free(tree2);
|
|
|
|
return diff;
|
|
}
|
|
|
|
static void diff_update_parent_range(struct rev_info *rev,
|
|
struct commit *commit)
|
|
{
|
|
struct diff_line_range *r = lookup_line_range(rev, commit);
|
|
struct commit_list *parents = commit->parents;
|
|
struct commit *c = NULL;
|
|
if (parents) {
|
|
assert(parents->next == NULL);
|
|
c = parents->item;
|
|
}
|
|
|
|
assign_range_to_parent(rev, commit, c, r, &rev->diffopt, 1);
|
|
}
|
|
|
|
struct commit_state {
|
|
struct diff_line_range *range;
|
|
struct object obj;
|
|
};
|
|
|
|
static void assign_parents_range(struct rev_info *rev, struct commit *commit)
|
|
{
|
|
struct commit_list *parents = commit->parents;
|
|
struct diff_line_range *r = lookup_line_range(rev, commit);
|
|
struct diff_line_range *evil = NULL, *range = NULL;
|
|
struct decoration parents_state;
|
|
struct commit_state *state = NULL;
|
|
int nontrivial = 0;
|
|
|
|
memset(&parents_state, 0, sizeof(parents_state));
|
|
/*
|
|
* If we are in linear history, update range and flush the patch if
|
|
* necessary
|
|
*/
|
|
if (parents == NULL || parents->next == NULL)
|
|
return diff_update_parent_range(rev, commit);
|
|
|
|
/*
|
|
* Loop on the parents and assign the ranges to different
|
|
* parents, if there is any range left, this commit must
|
|
* be an evil merge.
|
|
*/
|
|
evil = diff_line_range_clone_deeply(r);
|
|
parents = commit->parents;
|
|
while (parents) {
|
|
struct commit *p = parents->item;
|
|
int diff = 0;
|
|
struct diff_line_range *origin_range = lookup_line_range(rev, p);
|
|
if (origin_range)
|
|
origin_range = diff_line_range_clone_deeply(origin_range);
|
|
|
|
state = xmalloc(sizeof(*state));
|
|
state->range = origin_range;
|
|
state->obj = p->object;
|
|
add_decoration(&parents_state, &p->object, state);
|
|
diff = assign_range_to_parent(rev, commit, p, r, &rev->diffopt, 1);
|
|
/* Since all the ranges comes from this parent, we can ignore others */
|
|
if (diff == 0) {
|
|
/* restore the state of parents before this one */
|
|
parents = commit->parents;
|
|
while (parents->item != p) {
|
|
struct commit_list *list = parents;
|
|
struct diff_line_range *line_range = NULL;
|
|
parents = parents->next;
|
|
line_range = lookup_line_range(rev, list->item);
|
|
cleanup(line_range);
|
|
state = lookup_decoration(&parents_state, &list->item->object);
|
|
add_decoration(&parents_state, &list->item->object, NULL);
|
|
add_line_range(rev, list->item, state->range);
|
|
list->item->object = state->obj;
|
|
free(state);
|
|
free(list);
|
|
}
|
|
|
|
commit->parents = parents;
|
|
parents = parents->next;
|
|
commit->parents->next = NULL;
|
|
|
|
/* free the non-use commit_list */
|
|
while (parents) {
|
|
struct commit_list *list = parents;
|
|
parents = parents->next;
|
|
free(list);
|
|
}
|
|
goto out;
|
|
}
|
|
/* take the ranges from 'commit', try to detect nontrivial merge */
|
|
assign_range_to_parent(rev, commit, p, evil, &rev->diffopt, 0);
|
|
parents = parents->next;
|
|
}
|
|
|
|
commit->object.flags |= NONTRIVIAL_MERGE;
|
|
/*
|
|
* yes, this must be an evil merge.
|
|
*/
|
|
range = evil;
|
|
while (range) {
|
|
if (range->nr) {
|
|
commit->object.flags |= EVIL_MERGE;
|
|
nontrivial = 1;
|
|
}
|
|
range = range->next;
|
|
}
|
|
|
|
out:
|
|
/* Never print out any diff for a merge commit */
|
|
commit->object.flags &= ~NEED_PRINT;
|
|
|
|
parents = commit->parents;
|
|
while (parents) {
|
|
state = lookup_decoration(&parents_state, &parents->item->object);
|
|
if (state) {
|
|
cleanup(state->range);
|
|
free(state);
|
|
}
|
|
parents = parents->next;
|
|
}
|
|
|
|
if (nontrivial)
|
|
add_decoration(&rev->nontrivial_merge, &commit->object, evil);
|
|
else
|
|
cleanup(evil);
|
|
}
|
|
|
|
struct line_chunk {
|
|
int lone, ltwo;
|
|
const char *one, *two;
|
|
const char *one_end, *two_end;
|
|
struct diff_line_range *range;
|
|
};
|
|
|
|
static void flush_lines(struct diff_options *opt, const char **ptr, const char *end,
|
|
int slno, int elno, int *lno, const char *color, const char heading)
|
|
{
|
|
const char *p = *ptr;
|
|
struct strbuf buf = STRBUF_INIT;
|
|
const char *reset;
|
|
|
|
if (*color)
|
|
reset = diff_get_color_opt(opt, DIFF_RESET);
|
|
else
|
|
reset = "";
|
|
|
|
strbuf_addf(&buf, "%s%c", color, heading);
|
|
while (*ptr < end && *lno < slno) {
|
|
if (**ptr == '\n') {
|
|
(*lno)++;
|
|
if (*lno == slno) {
|
|
(*ptr)++;
|
|
break;
|
|
}
|
|
}
|
|
(*ptr)++;
|
|
}
|
|
assert(*ptr <= end);
|
|
p = *ptr;
|
|
|
|
while (*ptr < end && *lno <= elno) {
|
|
if (**ptr == '\n') {
|
|
fprintf(opt->file, "%s", buf.buf);
|
|
if (*ptr - p)
|
|
fwrite(p, *ptr - p, 1, opt->file);
|
|
fprintf(opt->file, "%s\n", reset);
|
|
p = *ptr + 1;
|
|
(*lno)++;
|
|
}
|
|
(*ptr)++;
|
|
}
|
|
if (*lno <= elno) {
|
|
fprintf(opt->file, "%s", buf.buf);
|
|
if (*ptr - p)
|
|
fwrite(p, *ptr - p, 1, opt->file);
|
|
fprintf(opt->file, "%s\n", reset);
|
|
}
|
|
strbuf_release(&buf);
|
|
}
|
|
|
|
static void diff_flush_range(struct diff_options *opt, struct line_chunk *chunk,
|
|
struct line_range *range)
|
|
{
|
|
struct print_pair *pair = &range->pair;
|
|
const char *old = diff_get_color_opt(opt, DIFF_FILE_OLD);
|
|
const char *new = diff_get_color_opt(opt, DIFF_FILE_NEW);
|
|
int i, cur = range->start;
|
|
|
|
for (i = 0; i < pair->nr; i++) {
|
|
struct print_range *pr = pair->ranges + i;
|
|
if (cur < pr->start)
|
|
flush_lines(opt, &chunk->two, chunk->two_end,
|
|
cur, pr->start - 1, &chunk->ltwo, "", ' ');
|
|
|
|
if (!pr->line_added)
|
|
flush_lines(opt, &chunk->one, chunk->one_end,
|
|
pr->pstart, pr->pend, &chunk->lone, old, '-');
|
|
flush_lines(opt, &chunk->two, chunk->two_end,
|
|
pr->start, pr->end, &chunk->ltwo, new, '+');
|
|
|
|
cur = pr->end + 1;
|
|
}
|
|
|
|
if (cur <= range->end) {
|
|
flush_lines(opt, &chunk->two, chunk->two_end,
|
|
cur, range->end, &chunk->ltwo, "", ' ');
|
|
}
|
|
}
|
|
|
|
static void diff_flush_chunks(struct diff_options *opt, struct line_chunk *chunk)
|
|
{
|
|
struct diff_line_range *range = chunk->range;
|
|
const char *set = diff_get_color_opt(opt, DIFF_FRAGINFO);
|
|
const char *reset = diff_get_color_opt(opt, DIFF_RESET);
|
|
int i;
|
|
|
|
for (i = 0; i < range->nr; i++) {
|
|
struct line_range *r = range->ranges + i;
|
|
long lenp = r->pend - r->pstart + 1, pstart = r->pstart;
|
|
long len = r->end - r->start + 1;
|
|
if (pstart == 0)
|
|
lenp = 0;
|
|
|
|
fprintf(opt->file, "%s@@ -%ld,%ld +%ld,%ld @@%s\n",
|
|
set, pstart, lenp, r->start, len, reset);
|
|
|
|
diff_flush_range(opt, chunk, r);
|
|
}
|
|
}
|
|
|
|
static void diff_flush_filepair(struct rev_info *rev, struct diff_line_range *range)
|
|
{
|
|
struct diff_options *opt = &rev->diffopt;
|
|
struct diff_filespec *one = range->prev, *two = range->spec;
|
|
struct diff_filepair p = {one, two, range->status, 0};
|
|
struct strbuf header = STRBUF_INIT, meta = STRBUF_INIT;
|
|
const char *a_prefix, *b_prefix;
|
|
const char *name_a, *name_b, *a_one, *b_two;
|
|
const char *lbl[2];
|
|
const char *set = diff_get_color_opt(opt, DIFF_METAINFO);
|
|
const char *reset = diff_get_color_opt(opt, DIFF_RESET);
|
|
struct line_chunk chunk;
|
|
int must_show_header;
|
|
|
|
/*
|
|
* the ranges that touch no different file, in this case
|
|
* the line number will not change, and of course we have
|
|
* no sensible rang->pair since there is no diff run.
|
|
*/
|
|
if (one == NULL)
|
|
return;
|
|
|
|
if (range->status == DIFF_STATUS_DELETED)
|
|
die("We are following an nonexistent file, interesting!");
|
|
|
|
name_a = one->path;
|
|
name_b = two->path;
|
|
fill_metainfo(&meta, name_a, name_b, one, two, opt, &p, &must_show_header,
|
|
DIFF_OPT_TST(opt, COLOR_DIFF));
|
|
|
|
diff_set_mnemonic_prefix(opt, "a/", "b/");
|
|
if (DIFF_OPT_TST(opt, REVERSE_DIFF)) {
|
|
a_prefix = opt->b_prefix;
|
|
b_prefix = opt->a_prefix;
|
|
} else {
|
|
a_prefix = opt->a_prefix;
|
|
b_prefix = opt->b_prefix;
|
|
}
|
|
|
|
name_a = DIFF_FILE_VALID(one) ? name_a : name_b;
|
|
name_b = DIFF_FILE_VALID(two) ? name_b : name_a;
|
|
|
|
a_one = quote_two(a_prefix, name_a + (*name_a == '/'));
|
|
b_two = quote_two(b_prefix, name_b + (*name_b == '/'));
|
|
lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
|
|
lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
|
|
strbuf_addf(&header, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
|
|
if (lbl[0][0] == '/') {
|
|
strbuf_addf(&header, "%snew file mode %06o%s\n", set, two->mode, reset);
|
|
} else if (lbl[1][0] == '/') {
|
|
strbuf_addf(&header, "%sdeleted file mode %06o%s\n", set, one->mode, reset);
|
|
} else if (one->mode != two->mode) {
|
|
strbuf_addf(&header, "%sold mode %06o%s\n", set, one->mode, reset);
|
|
strbuf_addf(&header, "%snew mode %06o%s\n", set, two->mode, reset);
|
|
}
|
|
|
|
fprintf(opt->file, "%s%s", header.buf, meta.buf);
|
|
strbuf_release(&meta);
|
|
strbuf_release(&header);
|
|
fprintf(opt->file, "%s--- %s%s\n", set, lbl[0], reset);
|
|
fprintf(opt->file, "%s+++ %s%s\n", set, lbl[1], reset);
|
|
free((void *)a_one);
|
|
free((void *)b_two);
|
|
|
|
chunk.one = one->data;
|
|
chunk.one_end = (const char *)one->data + one->size;
|
|
chunk.lone = 1;
|
|
chunk.two = two->data;
|
|
chunk.two_end = (const char *)two->data + two->size;
|
|
chunk.ltwo = 1;
|
|
chunk.range = range;
|
|
diff_flush_chunks(&rev->diffopt, &chunk);
|
|
}
|
|
|
|
#define EVIL_MERGE_STR "nontrivial merge found"
|
|
static void flush_nontrivial_merge(struct rev_info *rev,
|
|
struct diff_line_range *range)
|
|
{
|
|
struct diff_options *opt = &rev->diffopt;
|
|
const char *reset = diff_get_color_opt(opt, DIFF_RESET);
|
|
const char *frag = diff_get_color_opt(opt, DIFF_FRAGINFO);
|
|
const char *meta = diff_get_color_opt(opt, DIFF_METAINFO);
|
|
const char *new = diff_get_color_opt(opt, DIFF_FILE_NEW);
|
|
char *line_prefix = "";
|
|
struct strbuf *msgbuf;
|
|
int evil = 0;
|
|
struct diff_line_range *r = range;
|
|
|
|
if (opt && opt->output_prefix) {
|
|
msgbuf = opt->output_prefix(opt, opt->output_prefix_data);
|
|
line_prefix = msgbuf->buf;
|
|
}
|
|
|
|
while (r) {
|
|
if (r->nr)
|
|
evil = 1;
|
|
r = r->next;
|
|
}
|
|
|
|
if (!evil)
|
|
return;
|
|
|
|
fprintf(opt->file, "%s%s%s%s\n", line_prefix, meta, EVIL_MERGE_STR, reset);
|
|
|
|
while (range) {
|
|
if (range->nr) {
|
|
int lno = 1;
|
|
const char *ptr = range->spec->data;
|
|
const char *end = (const char *)range->spec->data + range->spec->size;
|
|
int i = 0;
|
|
fprintf(opt->file, "%s%s%s%s\n", line_prefix,
|
|
meta, range->spec->path, reset);
|
|
for (; i < range->nr; i++) {
|
|
struct line_range *r = range->ranges + i;
|
|
fprintf(opt->file, "%s@@ %ld,%ld @@%s\n", frag, r->start,
|
|
r->end - r->start + 1, reset);
|
|
flush_lines(opt, &ptr, end, r->start, r->end,
|
|
&lno, new, ' ');
|
|
}
|
|
fprintf(opt->file, "\n");
|
|
}
|
|
range = range->next;
|
|
}
|
|
}
|
|
|
|
static void line_log_flush(struct rev_info *rev, struct commit *c)
|
|
{
|
|
struct diff_line_range *range = lookup_line_range(rev, c);
|
|
struct diff_line_range *nontrivial = lookup_decoration(&rev->nontrivial_merge,
|
|
&c->object);
|
|
struct log_info log;
|
|
struct diff_options *opt = &rev->diffopt;
|
|
|
|
if (range == NULL || !(c->object.flags & NONTRIVIAL_MERGE ||
|
|
c->object.flags & NEED_PRINT))
|
|
return;
|
|
|
|
if (rev->graph)
|
|
graph_update(rev->graph, c);
|
|
log.commit = c;
|
|
log.parent = NULL;
|
|
rev->loginfo = &log;
|
|
show_log(rev);
|
|
rev->loginfo = NULL;
|
|
/*
|
|
* Add a new line after each commit message, of course we should
|
|
* add --graph alignment later when the patches comes to master.
|
|
*/
|
|
fprintf(rev->diffopt.file, "\n");
|
|
|
|
if (c->object.flags & NONTRIVIAL_MERGE)
|
|
flush_nontrivial_merge(rev, nontrivial);
|
|
else {
|
|
while (range) {
|
|
if (range->diff)
|
|
diff_flush_filepair(rev, range);
|
|
range = range->next;
|
|
}
|
|
}
|
|
|
|
while (rev->graph && !graph_is_commit_finished(rev->graph)) {
|
|
struct strbuf sb;
|
|
strbuf_init(&sb, 0);
|
|
graph_next_line(rev->graph, &sb);
|
|
fputs(sb.buf, opt->file);
|
|
}
|
|
}
|
|
|
|
int cmd_line_log_walk(struct rev_info *rev)
|
|
{
|
|
struct commit *commit;
|
|
struct commit_list *list = NULL;
|
|
struct diff_line_range *r = NULL;
|
|
|
|
if (prepare_revision_walk(rev))
|
|
die("revision walk prepare failed");
|
|
|
|
list = rev->commits;
|
|
if (list && !limited) {
|
|
list->item->object.flags |= RANGE_UPDATE;
|
|
list = list->next;
|
|
}
|
|
/* Clear the flags */
|
|
while (list && !limited) {
|
|
list->item->object.flags &= ~(RANGE_UPDATE | NONTRIVIAL_MERGE |
|
|
NEED_PRINT | EVIL_MERGE);
|
|
list = list->next;
|
|
}
|
|
|
|
list = rev->commits;
|
|
while (list) {
|
|
struct commit_list *need_free = list;
|
|
commit = list->item;
|
|
|
|
if (commit->object.flags & RANGE_UPDATE)
|
|
assign_parents_range(rev, commit);
|
|
|
|
if (commit->object.flags & NEED_PRINT ||
|
|
commit->object.flags & NONTRIVIAL_MERGE || rev->graph)
|
|
line_log_flush(rev, commit);
|
|
|
|
r = lookup_line_range(rev, commit);
|
|
if (r) {
|
|
cleanup(r);
|
|
r = NULL;
|
|
add_line_range(rev, commit, r);
|
|
}
|
|
|
|
r = lookup_decoration(&rev->nontrivial_merge, &commit->object);
|
|
if (r) {
|
|
cleanup(r);
|
|
r = NULL;
|
|
add_decoration(&rev->nontrivial_merge, &commit->object, r);
|
|
}
|
|
|
|
list = list->next;
|
|
free(need_free);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static enum rewrite_result rewrite_one(struct rev_info *rev, struct commit **pp)
|
|
{
|
|
struct diff_line_range *r = NULL;
|
|
struct commit *p;
|
|
while (1) {
|
|
p = *pp;
|
|
if (p->object.flags & RANGE_UPDATE)
|
|
assign_parents_range(rev, p);
|
|
if (p->object.flags & NEED_PRINT || p->object.flags & NONTRIVIAL_MERGE)
|
|
return rewrite_one_ok;
|
|
if (!p->parents)
|
|
return rewrite_one_noparents;
|
|
|
|
r = lookup_line_range(rev, p);
|
|
if (!r)
|
|
return rewrite_one_noparents;
|
|
*pp = p->parents->item;
|
|
}
|
|
}
|
|
|
|
/* The rev->commits must be sorted in topologically order */
|
|
void limit_list_line(struct rev_info *rev)
|
|
{
|
|
struct commit_list *list = rev->commits;
|
|
struct commit_list *commits = xmalloc(sizeof(struct commit_list));
|
|
struct commit_list *out = commits, *prev = commits;
|
|
struct commit *c;
|
|
struct diff_line_range *r;
|
|
|
|
if (list) {
|
|
list->item->object.flags |= RANGE_UPDATE;
|
|
list = list->next;
|
|
}
|
|
/* Clear the flags */
|
|
while (list) {
|
|
list->item->object.flags &= ~(RANGE_UPDATE | NONTRIVIAL_MERGE |
|
|
NEED_PRINT | EVIL_MERGE);
|
|
list = list->next;
|
|
}
|
|
|
|
list = rev->commits;
|
|
while (list) {
|
|
c = list->item;
|
|
|
|
if (c->object.flags & RANGE_UPDATE)
|
|
assign_parents_range(rev, c);
|
|
|
|
if (c->object.flags & NEED_PRINT || c->object.flags & NONTRIVIAL_MERGE) {
|
|
if (rewrite_parents(rev, c, rewrite_one))
|
|
die("Can't rewrite parent for commit %s",
|
|
sha1_to_hex(c->object.sha1));
|
|
commits->item = c;
|
|
commits->next = xmalloc(sizeof(struct commit_list));
|
|
prev = commits;
|
|
commits = commits->next;
|
|
} else {
|
|
r = lookup_line_range(rev, c);
|
|
if (r) {
|
|
cleanup(r);
|
|
r = NULL;
|
|
add_line_range(rev, c, r);
|
|
}
|
|
}
|
|
|
|
list = list->next;
|
|
}
|
|
|
|
prev->next = NULL;
|
|
free(commits);
|
|
|
|
list = rev->commits;
|
|
while (list) {
|
|
struct commit_list *l = list;
|
|
list = list->next;
|
|
free(l);
|
|
}
|
|
|
|
rev->commits = out;
|
|
limited = 1;
|
|
}
|