Print the line log

'struct line_chunk' is used to make sure each file is scanned
only once when printing the lines. We track the starting line
number and the offsets of all lines in the range in this struct.

We use two functions from diff.c to generate meta info and hunk
headers in the usual format.

Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Bo Yang
2010-08-11 23:03:34 +08:00
committed by Junio C Hamano
parent 3583ee6caa
commit fdf5ea61f3

238
line.c
View File

@@ -1005,3 +1005,241 @@ static void assign_parents_range(struct rev_info *rev, struct commit *commit)
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);
fprintf(opt->file, "%s%s%s\n", 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\n\n", 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;
if (range == NULL)
return;
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 & EVIL_MERGE)
return flush_nontrivial_merge(rev, nontrivial);
while (range) {
if (range->diff)
diff_flush_filepair(rev, range);
range = range->next;
}
}