1
0
Fork 0
mirror of https://github.com/git/git.git synced 2024-05-19 10:36:09 +02:00

Merge branch 'tr/line-log'

* tr/line-log:
  git-log(1): remove --full-line-diff description
  line-log: fix documentation formatting
  log -L: improve comments in process_all_files()
  log -L: store the path instead of a diff_filespec
  log -L: test merge of parallel modify/rename
  t4211: pass -M to 'git log -M -L...' test
  log -L: fix overlapping input ranges
  log -L: check range set invariants when we look it up
  Speed up log -L... -M
  log -L: :pattern:file syntax to find by funcname
  Implement line-history search (git log -L)
  Export rewrite_parents() for 'log -L'
  Refactor parse_loc
This commit is contained in:
Junio C Hamano 2013-06-02 16:00:44 -07:00
commit ed73fe5642
31 changed files with 3374 additions and 123 deletions

View File

@ -9,28 +9,11 @@
--show-stats::
Include additional statistics at the end of blame output.
-L <start>,<end>::
-L <start>,<end>, -L :<regex>::
Annotate only the given line range. <start> and <end> can take
one of these forms:
- number
+
If <start> or <end> is a number, it specifies an
absolute line number (lines count from 1).
+
- /regex/
+
This form will use the first line matching the given
POSIX regex. If <end> is a regex, it will search
starting at the line given by <start>.
+
- +offset or -offset
+
This is only valid for <end> and will specify a number
of lines before or after the line given by <start>.
+
include::line-range-format.txt[]
-l::
Show long rev (Default: off).

View File

@ -8,9 +8,9 @@ git-blame - Show what revision and author last modified each line of a file
SYNOPSIS
--------
[verse]
'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental] [-L n,m]
[-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>] [--abbrev=<n>]
[<rev> | --contents <file> | --reverse <rev>] [--] <file>
'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental]
[-L n,m | -L :fn] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
[--abbrev=<n>] [<rev> | --contents <file> | --reverse <rev>] [--] <file>
DESCRIPTION
-----------

View File

@ -62,6 +62,19 @@ produced by --stat etc.
Note that only message is considered, if also a diff is shown
its size is not included.
-L <start>,<end>:<file>, -L :<regex>:<file>::
Trace the evolution of the line range given by "<start>,<end>"
(or the funcname regex <regex>) within the <file>. You may
not give any pathspec limiters. This is currently limited to
a walk starting from a single revision, i.e., you may only
give zero or one positive revision arguments.
You can specify this option more than once.
+
<start> and <end> can take one of these forms:
include::line-range-format.txt[]
<revision range>::
Show only commits in the specified revision range. When no
<revision range> is specified, it defaults to `HEAD` (i.e. the
@ -140,6 +153,11 @@ Examples
This makes sense only when following a strict policy of merging all
topic branches when staying on a single integration branch.
git log -L '/int main/',/^}/:main.c::
Shows how the function `main()` in the file 'main.c' evolved
over time.
`git log -3`::
Limits the number of commits to show to 3.

View File

@ -0,0 +1,25 @@
- number
+
If <start> or <end> is a number, it specifies an
absolute line number (lines count from 1).
+
- /regex/
+
This form will use the first line matching the given
POSIX regex. If <end> is a regex, it will search
starting at the line given by <start>.
+
- +offset or -offset
+
This is only valid for <end> and will specify a number
of lines before or after the line given by <start>.
+
- :regex
+
If the option's argument is of the form :regex, it denotes the range
from the first funcname line that matches <regex>, up to the next
funcname line.
+

View File

@ -681,6 +681,8 @@ LIB_H += help.h
LIB_H += http.h
LIB_H += kwset.h
LIB_H += levenshtein.h
LIB_H += line-log.h
LIB_H += line-range.h
LIB_H += list-objects.h
LIB_H += ll-merge.h
LIB_H += log-tree.h
@ -808,6 +810,8 @@ LIB_OBJS += hex.o
LIB_OBJS += ident.o
LIB_OBJS += kwset.o
LIB_OBJS += levenshtein.o
LIB_OBJS += line-log.o
LIB_OBJS += line-range.o
LIB_OBJS += list-objects.o
LIB_OBJS += ll-merge.o
LIB_OBJS += lockfile.o

View File

@ -21,6 +21,7 @@
#include "parse-options.h"
#include "utf8.h"
#include "userdiff.h"
#include "line-range.h"
static char blame_usage[] = N_("git blame [options] [rev-opts] [rev] [--] file");
@ -566,11 +567,16 @@ static void dup_entry(struct blame_entry *dst, struct blame_entry *src)
dst->score = 0;
}
static const char *nth_line(struct scoreboard *sb, int lno)
static const char *nth_line(struct scoreboard *sb, long lno)
{
return sb->final_buf + sb->lineno[lno];
}
static const char *nth_line_cb(void *data, long lno)
{
return nth_line((struct scoreboard *)data, lno);
}
/*
* It is known that lines between tlno to same came from parent, and e
* has an overlap with that range. it also is known that parent's
@ -1931,83 +1937,6 @@ static const char *add_prefix(const char *prefix, const char *path)
return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
}
/*
* Parsing of (comma separated) one item in the -L option
*/
static const char *parse_loc(const char *spec,
struct scoreboard *sb, long lno,
long begin, long *ret)
{
char *term;
const char *line;
long num;
int reg_error;
regex_t regexp;
regmatch_t match[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(sb, begin);
if (!(reg_error = regcomp(&regexp, spec + 1, REG_NEWLINE)) &&
!(reg_error = regexec(&regexp, line, 1, match, 0))) {
const char *cp = line + match[0].rm_so;
const char *nline;
while (begin++ < lno) {
nline = nth_line(sb, begin);
if (line <= cp && cp < nline)
break;
line = nline;
}
*ret = begin;
regfree(&regexp);
*term++ = '/';
return term;
}
else {
char errbuf[1024];
regerror(reg_error, &regexp, errbuf, 1024);
die("-L parameter '%s': %s", spec + 1, errbuf);
}
}
/*
* Parsing of -L option
*/
@ -2016,15 +1945,7 @@ static void prepare_blame_range(struct scoreboard *sb,
long lno,
long *bottom, long *top)
{
const char *term;
term = parse_loc(bottomtop, sb, lno, 1, bottom);
if (*term == ',') {
term = parse_loc(term + 1, sb, lno, *bottom + 1, top);
if (*term)
usage(blame_usage);
}
if (*term)
if (parse_range_arg(bottomtop, nth_line_cb, sb, lno, bottom, top, sb->path))
usage(blame_usage);
}
@ -2574,10 +2495,6 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
bottom = top = 0;
if (bottomtop)
prepare_blame_range(&sb, bottomtop, lno, &bottom, &top);
if (bottom && top && top < bottom) {
long tmp;
tmp = top; top = bottom; bottom = tmp;
}
if (bottom < 1)
bottom = 1;
if (top < 1)

View File

@ -19,6 +19,7 @@
#include "remote.h"
#include "string-list.h"
#include "parse-options.h"
#include "line-log.h"
#include "branch.h"
#include "streaming.h"
#include "version.h"
@ -42,6 +43,12 @@ static const char * const builtin_log_usage[] = {
NULL
};
struct line_opt_callback_data {
struct rev_info *rev;
const char *prefix;
struct string_list args;
};
static int parse_decoration_style(const char *var, const char *value)
{
switch (git_config_maybe_bool(var, value)) {
@ -76,6 +83,19 @@ static int decorate_callback(const struct option *opt, const char *arg, int unse
return 0;
}
static int log_line_range_callback(const struct option *option, const char *arg, int unset)
{
struct line_opt_callback_data *data = option->value;
if (!arg)
return -1;
data->rev->line_level_traverse = 1;
string_list_append(&data->args, arg);
return 0;
}
static void cmd_log_init_defaults(struct rev_info *rev)
{
if (fmt_pretty)
@ -98,6 +118,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
{
struct userformat_want w;
int quiet = 0, source = 0, mailmap = 0;
static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP};
const struct option builtin_log_options[] = {
OPT_BOOL(0, "quiet", &quiet, N_("suppress diff output")),
@ -105,9 +126,15 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")),
{ OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"),
PARSE_OPT_OPTARG, decorate_callback},
OPT_CALLBACK('L', NULL, &line_cb, "n,m:file",
"Process line range n,m in file, counting from 1",
log_line_range_callback),
OPT_END()
};
line_cb.rev = rev;
line_cb.prefix = prefix;
mailmap = use_mailmap_config;
argc = parse_options(argc, argv, prefix,
builtin_log_options, builtin_log_usage,
@ -161,6 +188,10 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
rev->show_decorations = 1;
load_ref_decorations(decoration_style);
}
if (rev->line_level_traverse)
line_log_init(rev, line_cb.prefix, &line_cb.args);
setup_pager();
}

1273
line-log.c Normal file
View File

@ -0,0 +1,1273 @@
#include "git-compat-util.h"
#include "line-range.h"
#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 "userdiff.h"
#include "line-log.h"
static void range_set_grow(struct range_set *rs, size_t extra)
{
ALLOC_GROW(rs->ranges, rs->nr + extra, rs->alloc);
}
/* Either initialization would be fine */
#define RANGE_SET_INIT {0}
static void range_set_init(struct range_set *rs, size_t prealloc)
{
rs->alloc = rs->nr = 0;
rs->ranges = NULL;
if (prealloc)
range_set_grow(rs, prealloc);
}
static void range_set_release(struct range_set *rs)
{
free(rs->ranges);
rs->alloc = rs->nr = 0;
rs->ranges = NULL;
}
/* dst must be uninitialized! */
static void range_set_copy(struct range_set *dst, struct range_set *src)
{
range_set_init(dst, src->nr);
memcpy(dst->ranges, src->ranges, src->nr*sizeof(struct range_set));
dst->nr = src->nr;
}
static void range_set_move(struct range_set *dst, struct range_set *src)
{
range_set_release(dst);
dst->ranges = src->ranges;
dst->nr = src->nr;
dst->alloc = src->alloc;
src->ranges = NULL;
src->alloc = src->nr = 0;
}
/* tack on a _new_ range _at the end_ */
static void range_set_append_unsafe(struct range_set *rs, long a, long b)
{
assert(a <= b);
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;
const struct range *s = _s;
/* this could be simply 'return r.start-s.start', but for the types */
if (r->start == s->start)
return 0;
if (r->start < s->start)
return -1;
return 1;
}
/*
* 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 */
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;
} else {
rs->ranges[o].start = rs->ranges[i].start;
rs->ranges[o].end = rs->ranges[i].end;
o++;
}
}
assert(o <= rs->nr);
rs->nr = o;
range_set_check_invariants(rs);
}
/*
* Union of range sets (i.e., sets of line numbers). Used to merge
* them when searches meet at a common ancestor.
*
* This is also where the ranges are consolidated into canonical form:
* overlapping and adjacent ranges are merged, and empty ranges are
* removed.
*/
static void range_set_union(struct range_set *out,
struct range_set *a, struct range_set *b)
{
int i = 0, j = 0, o = 0;
struct range *ra = a->ranges;
struct range *rb = b->ranges;
/* cannot make an alias of out->ranges: it may change during grow */
assert(out->nr == 0);
while (i < a->nr || j < b->nr) {
struct range *new;
if (i < a->nr && j < b->nr) {
if (ra[i].start < rb[j].start)
new = &ra[i++];
else if (ra[i].start > rb[j].start)
new = &rb[j++];
else if (ra[i].end < rb[j].end)
new = &ra[i++];
else
new = &rb[j++];
} else if (i < a->nr) /* b exhausted */
new = &ra[i++];
else /* a exhausted */
new = &rb[j++];
if (new->start == new->end)
; /* empty range */
else if (!o || out->ranges[o-1].end < new->start) {
range_set_grow(out, 1);
out->ranges[o].start = new->start;
out->ranges[o].end = new->end;
o++;
} else if (out->ranges[o-1].end < new->end) {
out->ranges[o-1].end = new->end;
}
}
out->nr = o;
}
/*
* Difference of range sets (out = a \ b). Pass the "interesting"
* ranges as 'a' and the target side of the diff as 'b': it removes
* the ranges for which the commit is responsible.
*/
static void range_set_difference(struct range_set *out,
struct range_set *a, struct range_set *b)
{
int i, j = 0;
for (i = 0; i < a->nr; i++) {
long start = a->ranges[i].start;
long end = a->ranges[i].end;
while (start < end) {
while (j < b->nr && start >= b->ranges[j].end)
/*
* a: |-------
* b: ------|
*/
j++;
if (j >= b->nr || end < b->ranges[j].start) {
/*
* b exhausted, or
* a: ----|
* b: |----
*/
range_set_append(out, start, end);
break;
}
if (start >= b->ranges[j].start) {
/*
* a: |--????
* b: |------|
*/
start = b->ranges[j].end;
} else if (end > b->ranges[j].start) {
/*
* a: |-----|
* b: |--?????
*/
if (start < b->ranges[j].start)
range_set_append(out, start, b->ranges[j].start);
start = b->ranges[j].end;
}
}
}
}
static void diff_ranges_init(struct diff_ranges *diff)
{
range_set_init(&diff->parent, 0);
range_set_init(&diff->target, 0);
}
static void diff_ranges_release(struct diff_ranges *diff)
{
range_set_release(&diff->parent);
range_set_release(&diff->target);
}
void line_log_data_init(struct line_log_data *r)
{
memset(r, 0, sizeof(struct line_log_data));
range_set_init(&r->ranges, 0);
}
static void line_log_data_clear(struct line_log_data *r)
{
range_set_release(&r->ranges);
if (r->pair)
diff_free_filepair(r->pair);
}
static void free_line_log_data(struct line_log_data *r)
{
while (r) {
struct line_log_data *next = r->next;
line_log_data_clear(r);
free(r);
r = next;
}
}
static struct line_log_data *
search_line_log_data(struct line_log_data *list, const char *path,
struct line_log_data **insertion_point)
{
struct line_log_data *p = list;
if (insertion_point)
*insertion_point = NULL;
while (p) {
int cmp = strcmp(p->path, path);
if (!cmp)
return p;
if (insertion_point && cmp < 0)
*insertion_point = p;
p = p->next;
}
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,
char *path,
long begin, long end)
{
struct line_log_data *ip;
struct line_log_data *p = search_line_log_data(*list, path, &ip);
if (p) {
range_set_append_unsafe(&p->ranges, begin, end);
sort_and_merge_range_set(&p->ranges);
free(path);
return;
}
p = xcalloc(1, sizeof(struct line_log_data));
p->path = path;
range_set_append(&p->ranges, begin, end);
if (ip) {
p->next = ip->next;
ip->next = p;
} else {
p->next = *list;
*list = p;
}
}
struct collect_diff_cbdata {
struct diff_ranges *diff;
};
static int collect_diff_cb(long start_a, long count_a,
long start_b, long count_b,
void *data)
{
struct collect_diff_cbdata *d = data;
if (count_a >= 0)
range_set_append(&d->diff->parent, start_a, start_a + count_a);
if (count_b >= 0)
range_set_append(&d->diff->target, start_b, start_b + count_b);
return 0;
}
static void collect_diff(mmfile_t *parent, mmfile_t *target, struct diff_ranges *out)
{
struct collect_diff_cbdata cbdata = {NULL};
xpparam_t xpp;
xdemitconf_t xecfg;
xdemitcb_t ecb;
memset(&xpp, 0, sizeof(xpp));
memset(&xecfg, 0, sizeof(xecfg));
xecfg.ctxlen = xecfg.interhunkctxlen = 0;
cbdata.diff = out;
xecfg.hunk_func = collect_diff_cb;
memset(&ecb, 0, sizeof(ecb));
ecb.priv = &cbdata;
xdi_diff(parent, target, &xpp, &xecfg, &ecb);
}
/*
* These are handy for debugging. Removing them with #if 0 silences
* the "unused function" warning.
*/
#if 0
static void dump_range_set(struct range_set *rs, const char *desc)
{
int i;
printf("range set %s (%d items):\n", desc, rs->nr);
for (i = 0; i < rs->nr; i++)
printf("\t[%ld,%ld]\n", rs->ranges[i].start, rs->ranges[i].end);
}
static void dump_line_log_data(struct line_log_data *r)
{
char buf[4096];
while (r) {
snprintf(buf, 4096, "file %s\n", r->path);
dump_range_set(&r->ranges, buf);
r = r->next;
}
}
static void dump_diff_ranges(struct diff_ranges *diff, const char *desc)
{
int i;
assert(diff->parent.nr == diff->target.nr);
printf("diff ranges %s (%d items):\n", desc, diff->parent.nr);
printf("\tparent\ttarget\n");
for (i = 0; i < diff->parent.nr; i++) {
printf("\t[%ld,%ld]\t[%ld,%ld]\n",
diff->parent.ranges[i].start,
diff->parent.ranges[i].end,
diff->target.ranges[i].start,
diff->target.ranges[i].end);
}
}
#endif
static int ranges_overlap(struct range *a, struct range *b)
{
return !(a->end <= b->start || b->end <= a->start);
}
/*
* Given a diff and the set of interesting ranges, determine all hunks
* of the diff which touch (overlap) at least one of the interesting
* ranges in the target.
*/
static void diff_ranges_filter_touched(struct diff_ranges *out,
struct diff_ranges *diff,
struct range_set *rs)
{
int i, j = 0;
assert(out->target.nr == 0);
for (i = 0; i < diff->target.nr; i++) {
while (diff->target.ranges[i].start > rs->ranges[j].end) {
j++;
if (j == rs->nr)
return;
}
if (ranges_overlap(&diff->target.ranges[i], &rs->ranges[j])) {
range_set_append(&out->parent,
diff->parent.ranges[i].start,
diff->parent.ranges[i].end);
range_set_append(&out->target,
diff->target.ranges[i].start,
diff->target.ranges[i].end);
}
}
}
/*
* Adjust the line counts in 'rs' to account for the lines
* added/removed in the diff.
*/
static void range_set_shift_diff(struct range_set *out,
struct range_set *rs,
struct diff_ranges *diff)
{
int i, j = 0;
long offset = 0;
struct range *src = rs->ranges;
struct range *target = diff->target.ranges;
struct range *parent = diff->parent.ranges;
for (i = 0; i < rs->nr; i++) {
while (j < diff->target.nr && src[i].start >= target[j].start) {
offset += (parent[j].end-parent[j].start)
- (target[j].end-target[j].start);
j++;
}
range_set_append(out, src[i].start+offset, src[i].end+offset);
}
}
/*
* Given a diff and the set of interesting ranges, map the ranges
* across the diff. That is: observe that the target commit takes
* blame for all the + (target-side) ranges. So for every pair of
* ranges in the diff that was touched, we remove the latter and add
* its parent side.
*/
static void range_set_map_across_diff(struct range_set *out,
struct range_set *rs,
struct diff_ranges *diff,
struct diff_ranges **touched_out)
{
struct diff_ranges *touched = xmalloc(sizeof(*touched));
struct range_set tmp1 = RANGE_SET_INIT;
struct range_set tmp2 = RANGE_SET_INIT;
diff_ranges_init(touched);
diff_ranges_filter_touched(touched, diff, rs);
range_set_difference(&tmp1, rs, &touched->target);
range_set_shift_diff(&tmp2, &tmp1, diff);
range_set_union(out, &tmp2, &touched->parent);
range_set_release(&tmp1);
range_set_release(&tmp2);
*touched_out = touched;
}
static struct commit *check_single_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)
die("No commit specified?");
return (struct commit *) commit;
}
static void fill_blob_sha1(struct commit *commit, struct diff_filespec *spec)
{
unsigned mode;
unsigned char sha1[20];
if (get_tree_entry(commit->object.sha1, spec->path,
sha1, &mode))
die("There is no path %s in the commit", spec->path);
fill_filespec(spec, sha1, 1, mode);
return;
}
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-1;
*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;
}
static struct line_log_data *
parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
{
long lines = 0;
unsigned long *ends = NULL;
struct nth_line_cb cb_data;
struct string_list_item *item;
struct line_log_data *ranges = NULL;
for_each_string_list_item(item, args) {
const char *name_part, *range_part;
char *full_name;
struct diff_filespec *spec;
long begin = 0, end = 0;
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",
item->string);
range_part = xstrndup(item->string, name_part - item->string);
name_part++;
full_name = prefix_path(prefix, prefix ? strlen(prefix) : 0,
name_part);
spec = alloc_filespec(full_name);
fill_blob_sha1(commit, spec);
fill_line_ends(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, &begin, &end,
full_name))
die("malformed -L argument '%s'", range_part);
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, full_name, begin, end);
free_filespec(spec);
free(ends);
ends = NULL;
}
return ranges;
}
static struct line_log_data *line_log_data_copy_one(struct line_log_data *r)
{
struct line_log_data *ret = xmalloc(sizeof(*ret));
assert(r);
line_log_data_init(ret);
range_set_copy(&ret->ranges, &r->ranges);
ret->path = xstrdup(r->path);
return ret;
}
static struct line_log_data *
line_log_data_copy(struct line_log_data *r)
{
struct line_log_data *ret = NULL;
struct line_log_data *tmp = NULL, *prev = NULL;
assert(r);
ret = tmp = prev = line_log_data_copy_one(r);
r = r->next;
while (r) {
tmp = line_log_data_copy_one(r);
prev->next = tmp;
prev = tmp;
r = r->next;
}
return ret;
}
/* merge two range sets across files */
static struct line_log_data *line_log_data_merge(struct line_log_data *a,
struct line_log_data *b)
{
struct line_log_data *head = NULL, **pp = &head;
while (a || b) {
struct line_log_data *src;
struct line_log_data *src2 = NULL;
struct line_log_data *d;
int cmp;
if (!a)
cmp = 1;
else if (!b)
cmp = -1;
else
cmp = strcmp(a->path, b->path);
if (cmp < 0) {
src = a;
a = a->next;
} else if (cmp == 0) {
src = a;
a = a->next;
src2 = b;
b = b->next;
} else {
src = b;
b = b->next;
}
d = xmalloc(sizeof(struct line_log_data));
line_log_data_init(d);
d->path = xstrdup(src->path);
*pp = d;
pp = &d->next;
if (src2)
range_set_union(&d->ranges, &src->ranges, &src2->ranges);
else
range_set_copy(&d->ranges, &src->ranges);
}
return head;
}
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;
old = lookup_decoration(&revs->line_log_data, &commit->object);
if (old && range) {
new = line_log_data_merge(old, range);
free_line_log_data(old);
} else if (range)
new = line_log_data_copy(range);
if (new)
add_decoration(&revs->line_log_data, &commit->object, new);
}
static void clear_commit_line_range(struct rev_info *revs, struct commit *commit)
{
struct line_log_data *r;
r = lookup_decoration(&revs->line_log_data, &commit->object);
if (!r)
return;
free_line_log_data(r);
add_decoration(&revs->line_log_data, &commit->object, NULL);
}
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;
}
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);
add_line_range(rev, commit, range);
if (!rev->diffopt.detect_rename) {
int i, count = 0;
struct line_log_data *r = range;
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->path);
r = r->next;
}
paths[count] = NULL;
init_pathspec(&rev->diffopt.pathspec, paths);
free(paths);
}
}
static void load_tree_desc(struct tree_desc *desc, void **tree,
const unsigned char *sha1)
{
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);
}
static int count_parents(struct commit *commit)
{
struct commit_list *parents = commit->parents;
int count = 0;
while (parents) {
count++;
parents = parents->next;
}
return count;
}
static void move_diff_queue(struct diff_queue_struct *dst,
struct diff_queue_struct *src)
{
assert(src != dst);
memcpy(dst, src, sizeof(struct diff_queue_struct));
DIFF_QUEUE_CLEAR(src);
}
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)
{
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);
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)
{
if (line == 0)
return (char *)data;
else
return (char *)data + ends[line] + 1;
}
static void print_line(const char *prefix, char first,
long line, unsigned long *ends, void *data,
const char *color, const char *reset)
{
char *begin = get_nth_line(line, ends, data);
char *end = get_nth_line(line+1, ends, data);
int had_nl = 0;
if (end > begin && end[-1] == '\n') {
end--;
had_nl = 1;
}
fputs(prefix, stdout);
fputs(color, stdout);
putchar(first);
fwrite(begin, 1, end-begin, stdout);
fputs(reset, stdout);
putchar('\n');
if (!had_nl)
fputs("\\ No newline at end of file\n", stdout);
}
static char *output_prefix(struct diff_options *opt)
{
char *prefix = "";
if (opt->output_prefix) {
struct strbuf *sb = opt->output_prefix(opt, opt->output_prefix_data);
prefix = sb->buf;
}
return prefix;
}
static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range)
{
int i, j = 0;
long p_lines, t_lines;
unsigned long *p_ends = NULL, *t_ends = NULL;
struct diff_filepair *pair = range->pair;
struct diff_ranges *diff = &range->diff;
struct diff_options *opt = &rev->diffopt;
char *prefix = output_prefix(opt);
const char *c_reset = diff_get_color(opt->use_color, DIFF_RESET);
const char *c_frag = diff_get_color(opt->use_color, DIFF_FRAGINFO);
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);
if (!pair || !diff)
return;
if (pair->one->sha1_valid)
fill_line_ends(pair->one, &p_lines, &p_ends);
fill_line_ends(pair->two, &t_lines, &t_ends);
printf("%s%sdiff --git a/%s b/%s%s\n", prefix, c_meta, pair->one->path, pair->two->path, c_reset);
printf("%s%s--- %s%s%s\n", prefix, c_meta,
pair->one->sha1_valid ? "a/" : "",
pair->one->sha1_valid ? pair->one->path : "/dev/null",
c_reset);
printf("%s%s+++ b/%s%s\n", prefix, c_meta, pair->two->path, c_reset);
for (i = 0; i < range->ranges.nr; i++) {
long p_start, p_end;
long t_start = range->ranges.ranges[i].start;
long t_end = range->ranges.ranges[i].end;
long t_cur = t_start;
int j_last;
while (j < diff->target.nr && diff->target.ranges[j].end < t_start)
j++;
if (j == diff->target.nr || diff->target.ranges[j].start > t_end)
continue;
/* Scan ahead to determine the last diff that falls in this range */
j_last = j;
while (j_last < diff->target.nr && diff->target.ranges[j_last].start < t_end)
j_last++;
if (j_last > j)
j_last--;
/*
* Compute parent hunk headers: we know that the diff
* has the correct line numbers (but not all hunks).
* So it suffices to shift the start/end according to
* the line numbers of the first/last hunk(s) that
* fall in this range.
*/
if (t_start < diff->target.ranges[j].start)
p_start = diff->parent.ranges[j].start - (diff->target.ranges[j].start-t_start);
else
p_start = diff->parent.ranges[j].start;
if (t_end > diff->target.ranges[j_last].end)
p_end = diff->parent.ranges[j_last].end + (t_end-diff->target.ranges[j_last].end);
else
p_end = diff->parent.ranges[j_last].end;
if (!p_start && !p_end) {
p_start = -1;
p_end = -1;
}
/* Now output a diff hunk for this range */
printf("%s%s@@ -%ld,%ld +%ld,%ld @@%s\n",
prefix, c_frag,
p_start+1, p_end-p_start, t_start+1, t_end-t_start,
c_reset);
while (j < diff->target.nr && diff->target.ranges[j].start < t_end) {
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);
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 < diff->target.ranges[j].end && t_cur < t_end; t_cur++)
print_line(prefix, '+', t_cur, t_ends, pair->two->data,
c_new, c_reset);
j++;
}
for (; t_cur < t_end; t_cur++)
print_line(prefix, ' ', t_cur, t_ends, pair->two->data,
c_plain, c_reset);
}
free(p_ends);
free(t_ends);
}
/*
* NEEDSWORK: manually building a diff here is not the Right
* Thing(tm). log -L should be built into the diff pipeline.
*/
static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range)
{
puts(output_prefix(&rev->diffopt));
while (range) {
dump_diff_hacky_one(rev, range);
range = range->next;
}
}
/*
* Unlike most other functions, this destructively operates on
* 'range'.
*/
static int process_diff_filepair(struct rev_info *rev,
struct diff_filepair *pair,
struct line_log_data *range,
struct diff_ranges **diff_out)
{
struct line_log_data *rg = range;
struct range_set tmp;
struct diff_ranges diff;
mmfile_t file_parent, file_target;
assert(pair->two->path);
while (rg) {
assert(rg->path);
if (!strcmp(rg->path, pair->two->path))
break;
rg = rg->next;
}
if (!rg)
return 0;
if (rg->ranges.nr == 0)
return 0;
assert(pair->two->sha1_valid);
diff_populate_filespec(pair->two, 0);
file_target.ptr = pair->two->data;
file_target.size = pair->two->size;
if (pair->one->sha1_valid) {
diff_populate_filespec(pair->one, 0);
file_parent.ptr = pair->one->data;
file_parent.size = pair->one->size;
} else {
file_parent.ptr = "";
file_parent.size = 0;
}
diff_ranges_init(&diff);
collect_diff(&file_parent, &file_target, &diff);
/* NEEDSWORK should apply some heuristics to prevent mismatches */
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);
range_set_release(&rg->ranges);
range_set_move(&rg->ranges, &tmp);
diff_ranges_release(&diff);
return ((*diff_out)->parent.nr > 0);
}
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;
}
static void free_diffqueues(int n, struct diff_queue_struct *dq)
{
int i, j;
for (i = 0; i < n; i++)
for (j = 0; j < dq[i].nr; j++)
diff_free_filepair(dq[i].queue[j]);
free(dq);
}
static int process_all_files(struct line_log_data **range_out,
struct rev_info *rev,
struct diff_queue_struct *queue,
struct line_log_data *range)
{
int i, changed = 0;
*range_out = line_log_data_copy(range);
for (i = 0; i < queue->nr; i++) {
struct diff_ranges *pairdiff = NULL;
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++;
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));
}
}
return changed;
}
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);
return 1;
}
static int process_ranges_ordinary_commit(struct rev_info *rev, struct commit *commit,
struct line_log_data *range)
{
struct commit *parent = NULL;
struct diff_queue_struct queue;
struct line_log_data *parent_range;
int changed;
if (commit->parents)
parent = commit->parents->item;
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);
return changed;
}
static int process_ranges_merge_commit(struct rev_info *rev, struct commit *commit,
struct line_log_data *range)
{
struct diff_queue_struct *diffqueues;
struct line_log_data **cand;
struct commit **parents;
struct commit_list *p;
int i;
int nparents = count_parents(commit);
diffqueues = xmalloc(nparents * sizeof(*diffqueues));
cand = xmalloc(nparents * sizeof(*cand));
parents = xmalloc(nparents * sizeof(*parents));
p = commit->parents;
for (i = 0; i < nparents; i++) {
parents[i] = p->item;
p = p->next;
queue_diffs(range, &rev->diffopt, &diffqueues[i], commit, parents[i]);
}
for (i = 0; i < nparents; i++) {
int changed;
cand[i] = NULL;
changed = process_all_files(&cand[i], rev, &diffqueues[i], range);
if (!changed) {
/*
* This parent can take all the blame, so we
* don't follow any other path in history
*/
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;
free(parents);
free(cand);
free_diffqueues(nparents, diffqueues);
/* NEEDSWORK leaking like a sieve */
return 0;
}
}
/*
* No single parent took the blame. We add the candidates
* from the above loop to the parents.
*/
for (i = 0; i < nparents; i++) {
add_line_range(rev, parents[i], cand[i]);
}
clear_commit_line_range(rev, commit);
free(parents);
free(cand);
free_diffqueues(nparents, diffqueues);
return 1;
/* NEEDSWORK evil merge detection stuff */
/* NEEDSWORK leaking like a sieve */
}
static int 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)
changed = process_ranges_ordinary_commit(rev, commit, range);
else
changed = process_ranges_merge_commit(rev, commit, range);
}
if (!changed)
commit->object.flags |= TREESAME;
return changed;
}
static enum rewrite_result line_log_rewrite_one(struct rev_info *rev, struct commit **pp)
{
for (;;) {
struct commit *p = *pp;
if (p->parents && p->parents->next)
return rewrite_one_ok;
if (p->object.flags & UNINTERESTING)
return rewrite_one_ok;
if (!(p->object.flags & TREESAME))
return rewrite_one_ok;
if (!p->parents)
return rewrite_one_noparents;
*pp = p->parents->item;
}
}
int line_log_filter(struct rev_info *rev)
{
struct commit *commit;
struct commit_list *list = rev->commits;
struct commit_list *out = NULL, **pp = &out;
while (list) {
struct commit_list *to_free = NULL;
commit = list->item;
if (process_ranges_arbitrary_commit(rev, commit)) {
*pp = list;
pp = &list->next;
} else
to_free = list;
list = list->next;
free(to_free);
}
*pp = NULL;
for (list = out; list; list = list->next)
rewrite_parents(rev, list->item, line_log_rewrite_one);
rev->commits = out;
return 0;
}

53
line-log.h Normal file
View File

@ -0,0 +1,53 @@
#ifndef LINE_LOG_H
#define LINE_LOG_H
#include "diffcore.h"
struct rev_info;
struct commit;
/* A range [start,end]. Lines are numbered starting at 0, and the
* ranges include start but exclude end. */
struct range {
long start, end;
};
/* A set of ranges. The ranges must always be disjoint and sorted. */
struct range_set {
int alloc, nr;
struct range *ranges;
};
/* A diff, encoded as the set of pre- and post-image ranges where the
* files differ. A pair of ranges corresponds to a hunk. */
struct diff_ranges {
struct range_set parent;
struct range_set target;
};
/* Linked list of interesting files and their associated ranges. The
* list must be kept sorted by path.
*
* For simplicity, even though this is highly redundant, each
* line_log_data owns its 'path'.
*/
struct line_log_data {
struct line_log_data *next;
char *path;
char status;
struct range_set ranges;
int arg_alloc, arg_nr;
const char **args;
struct diff_filepair *pair;
struct diff_ranges diff;
};
extern void line_log_data_init(struct line_log_data *r);
extern void line_log_init(struct rev_info *rev, const char *prefix, struct string_list *args);
extern int line_log_filter(struct rev_info *rev);
extern int line_log_print(struct rev_info *rev, struct commit *commit);
#endif /* LINE_LOG_H */

243
line-range.c Normal file
View File

@ -0,0 +1,243 @@
#include "git-compat-util.h"
#include "line-range.h"
#include "xdiff-interface.h"
#include "strbuf.h"
#include "userdiff.h"
/*
* Parse one item in the -L option
*/
static 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];
/* 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 (!ret)
return term;
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) {
if (ret)
*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;
/* in the scan-only case we are not interested in the regex */
if (!ret)
return term+1;
/* try [spec+1 .. term-1] as regexp */
*term = 0;
begin--; /* input is in human terms */
line = nth_line(data, begin);
if (!(reg_error = regcomp(&regexp, spec + 1, REG_NEWLINE)) &&
!(reg_error = regexec(&regexp, 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(&regexp);
*term++ = '/';
return term;
}
else {
char errbuf[1024];
regerror(reg_error, &regexp, errbuf, 1024);
die("-L parameter '%s': %s", spec + 1, errbuf);
}
}
static int match_funcname(xdemitconf_t *xecfg, const char *bol, const char *eol)
{
if (xecfg) {
char buf[1];
return xecfg->find_func(bol, eol - bol, buf, 1,
xecfg->find_func_priv) >= 0;
}
if (bol == eol)
return 0;
if (isalpha(*bol) || *bol == '_' || *bol == '$')
return 1;
return 0;
}
static const char *find_funcname_matching_regexp(xdemitconf_t *xecfg, const char *start,
regex_t *regexp)
{
int reg_error;
regmatch_t match[1];
while (1) {
const char *bol, *eol;
reg_error = regexec(regexp, start, 1, match, 0);
if (reg_error == REG_NOMATCH)
return NULL;
else if (reg_error) {
char errbuf[1024];
regerror(reg_error, regexp, errbuf, 1024);
die("-L parameter: regexec() failed: %s", errbuf);
}
/* determine extent of line matched */
bol = start+match[0].rm_so;
eol = start+match[0].rm_eo;
while (bol > start && *bol != '\n')
bol--;
if (*bol == '\n')
bol++;
while (*eol && *eol != '\n')
eol++;
if (*eol == '\n')
eol++;
/* is it a funcname line? */
if (match_funcname(xecfg, (char*) bol, (char*) eol))
return bol;
start = eol;
}
}
static const char *parse_range_funcname(const char *arg, nth_line_fn_t nth_line_cb,
void *cb_data, long lines, long *begin, long *end,
const char *path)
{
char *pattern;
const char *term;
struct userdiff_driver *drv;
xdemitconf_t *xecfg = NULL;
const char *start;
const char *p;
int reg_error;
regex_t regexp;
assert(*arg == ':');
term = arg+1;
while (*term && *term != ':') {
if (*term == '\\' && *(term+1))
term++;
term++;
}
if (term == arg+1)
return NULL;
if (!begin) /* skip_range_arg case */
return term;
pattern = xstrndup(arg+1, term-(arg+1));
start = nth_line_cb(cb_data, 0);
drv = userdiff_find_by_path(path);
if (drv && drv->funcname.pattern) {
const struct userdiff_funcname *pe = &drv->funcname;
xecfg = xcalloc(1, sizeof(*xecfg));
xdiff_set_find_func(xecfg, pe->pattern, pe->cflags);
}
reg_error = regcomp(&regexp, pattern, REG_NEWLINE);
if (reg_error) {
char errbuf[1024];
regerror(reg_error, &regexp, errbuf, 1024);
die("-L parameter '%s': %s", pattern, errbuf);
}
p = find_funcname_matching_regexp(xecfg, (char*) start, &regexp);
if (!p)
die("-L parameter '%s': no match", pattern);
*begin = 0;
while (p > nth_line_cb(cb_data, *begin))
(*begin)++;
if (*begin >= lines)
die("-L parameter '%s' matches at EOF", pattern);
*end = *begin+1;
while (*end < lines) {
const char *bol = nth_line_cb(cb_data, *end);
const char *eol = nth_line_cb(cb_data, *end+1);
if (match_funcname(xecfg, bol, eol))
break;
(*end)++;
}
regfree(&regexp);
free(xecfg);
free(pattern);
/* compensate for 1-based numbering */
(*begin)++;
return term;
}
int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,
void *cb_data, long lines, long *begin, long *end,
const char *path)
{
if (*arg == ':') {
arg = parse_range_funcname(arg, nth_line_cb, cb_data, lines, begin, end, path);
if (!arg || *arg)
return -1;
return 0;
}
arg = parse_loc(arg, nth_line_cb, cb_data, lines, 1, begin);
if (*arg == ',')
arg = parse_loc(arg + 1, nth_line_cb, cb_data, lines, *begin + 1, end);
if (*arg)
return -1;
return 0;
}
const char *skip_range_arg(const char *arg)
{
if (*arg == ':')
return parse_range_funcname(arg, NULL, NULL, 0, NULL, NULL, NULL);
arg = parse_loc(arg, NULL, NULL, 0, -1, NULL);
if (*arg == ',')
arg = parse_loc(arg+1, NULL, NULL, 0, 0, NULL);
return arg;
}

36
line-range.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef LINE_RANGE_H
#define LINE_RANGE_H
/*
* Parse one item in an -L begin,end option w.r.t. the notional file
* object 'cb_data' consisting of 'lines' lines.
*
* The 'nth_line_cb' callback is used to determine the start of the
* line 'lno' inside the 'cb_data'. The caller is expected to already
* have a suitable map at hand to make this a constant-time lookup.
*
* Returns 0 in case of success and -1 if there was an error. The
* actual range is stored in *begin and *end. The counting starts
* at 1! In case of error, the caller should show usage message.
*/
typedef const char *(*nth_line_fn_t)(void *data, long lno);
extern int parse_range_arg(const char *arg,
nth_line_fn_t nth_line_cb,
void *cb_data, long lines,
long *begin, long *end,
const char *path);
/*
* Scan past a range argument that could be parsed by
* 'parse_range_arg', to help the caller determine the start of the
* filename in '-L n,m:file' syntax.
*
* Returns a pointer to the first character after the 'n,m' part, or
* NULL in case the argument is obviously malformed.
*/
extern const char *skip_range_arg(const char *arg);
#endif /* LINE_RANGE_H */

View File

@ -10,6 +10,7 @@
#include "color.h"
#include "gpg-interface.h"
#include "sequencer.h"
#include "line-log.h"
struct decoration name_decoration = { "object names" };
@ -796,6 +797,9 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit)
log.parent = NULL;
opt->loginfo = &log;
if (opt->line_level_traverse)
return line_log_print(opt, commit);
shown = log_tree_diff(opt, commit, &log);
if (!shown && opt->loginfo && opt->always_show_header) {
log.parent = NULL;

View File

@ -13,6 +13,7 @@
#include "decorate.h"
#include "log-tree.h"
#include "string-list.h"
#include "line-log.h"
#include "mailmap.h"
volatile show_early_output_fn_t show_early_output;
@ -1914,6 +1915,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
if (revs->combine_merges)
revs->ignore_merges = 0;
revs->diffopt.abbrev = revs->abbrev;
if (revs->line_level_traverse) {
revs->limited = 1;
revs->topo_order = 1;
}
diff_setup_done(&revs->diffopt);
grep_commit_pattern_type(GREP_PATTERN_TYPE_UNSPECIFIED,
@ -2185,6 +2192,8 @@ int prepare_revision_walk(struct rev_info *revs)
return -1;
if (revs->topo_order)
sort_in_topological_order(&revs->commits, revs->lifo);
if (revs->line_level_traverse)
line_log_filter(revs);
if (revs->simplify_merges)
simplify_merges(revs);
if (revs->children.name)
@ -2192,12 +2201,6 @@ int prepare_revision_walk(struct rev_info *revs)
return 0;
}
enum rewrite_result {
rewrite_one_ok,
rewrite_one_noparents,
rewrite_one_error
};
static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp)
{
struct commit_list *cache = NULL;
@ -2219,12 +2222,13 @@ static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp
}
}
static int rewrite_parents(struct rev_info *revs, struct commit *commit)
int rewrite_parents(struct rev_info *revs, struct commit *commit,
rewrite_parent_fn_t rewrite_parent)
{
struct commit_list **pp = &commit->parents;
while (*pp) {
struct commit_list *parent = *pp;
switch (rewrite_one(revs, &parent->item)) {
switch (rewrite_parent(revs, &parent->item)) {
case rewrite_one_ok:
break;
case rewrite_one_noparents:
@ -2390,7 +2394,7 @@ enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
if (action == commit_show &&
!revs->show_all &&
revs->prune && revs->dense && want_ancestry(revs)) {
if (rewrite_parents(revs, commit) < 0)
if (rewrite_parents(revs, commit, rewrite_one) < 0)
return commit_error;
}
return action;

View File

@ -97,7 +97,8 @@ struct rev_info {
cherry_mark:1,
bisect:1,
ancestry_path:1,
first_parent_only:1;
first_parent_only:1,
line_level_traverse:1;
/* Diff flags */
unsigned int diff:1,
@ -176,6 +177,9 @@ struct rev_info {
int count_left;
int count_right;
int count_same;
/* line level range that we are chasing */
struct decoration line_log_data;
};
#define REV_TREE_SAME 0
@ -242,4 +246,14 @@ enum commit_action {
extern enum commit_action get_commit_action(struct rev_info *revs, struct commit *commit);
extern enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit);
enum rewrite_result {
rewrite_one_ok,
rewrite_one_noparents,
rewrite_one_error
};
typedef enum rewrite_result (*rewrite_parent_fn_t)(struct rev_info *revs, struct commit **pp);
extern int rewrite_parents(struct rev_info *revs, struct commit *commit,
rewrite_parent_fn_t rewrite_parent);
#endif

34
t/perf/p4211-line-log.sh Executable file
View File

@ -0,0 +1,34 @@
#!/bin/sh
test_description='Tests log -L performance'
. ./perf-lib.sh
test_perf_default_repo
# Pick a file to log pseudo-randomly. The sort key is the blob hash,
# so it is stable.
test_expect_success 'select a file' '
git ls-tree HEAD | grep ^100644 |
sort -k 3 | head -1 | cut -f 2 >filelist
'
file=$(cat filelist)
export file
test_perf 'git rev-list --topo-order (baseline)' '
git rev-list --topo-order HEAD >/dev/null
'
test_perf 'git log --follow (baseline for -M)' '
git log --oneline --follow -- "$file" >/dev/null
'
test_perf 'git log -L' '
git log -L 1:"$file" >/dev/null
'
test_perf 'git log -M -L' '
git log -M -L 1:"$file" >/dev/null
'
test_done

67
t/t4211-line-log.sh Executable file
View File

@ -0,0 +1,67 @@
#!/bin/sh
test_description='test log -L'
. ./test-lib.sh
test_expect_success 'setup (import history)' '
git fast-import < "$TEST_DIRECTORY"/t4211/history.export &&
git reset --hard
'
canned_test_1 () {
test_expect_$1 "$2" "
git log $2 >actual &&
test_cmp \"\$TEST_DIRECTORY\"/t4211/expect.$3 actual
"
}
canned_test () {
canned_test_1 success "$@"
}
canned_test_failure () {
canned_test_1 failure "$@"
}
test_bad_opts () {
test_expect_success "invalid args: $1" "
test_must_fail git log $1 2>errors &&
grep '$2' errors
"
}
canned_test "-L 4,12:a.c simple" simple-f
canned_test "-L 4,+9:a.c simple" simple-f
canned_test "-L '/long f/,/^}/:a.c' simple" simple-f
canned_test "-L :f:a.c simple" simple-f-to-main
canned_test "-L '/main/,/^}/:a.c' simple" simple-main
canned_test "-L :main:a.c simple" simple-main-to-end
canned_test "-L 1,+4:a.c simple" beginning-of-file
canned_test "-L 20:a.c simple" end-of-file
canned_test "-L '/long f/',/^}/:a.c -L /main/,/^}/:a.c simple" two-ranges
canned_test "-L 24,+1:a.c simple" vanishes-early
canned_test "-M -L '/long f/,/^}/:b.c' move-support" move-support-f
canned_test "-M -L ':f:b.c' parallel-change" parallel-change-f-to-main
canned_test "-L 4,12:a.c -L :main:a.c simple" multiple
canned_test "-L 4,18:a.c -L :main:a.c simple" multiple-overlapping
canned_test "-L :main:a.c -L 4,18:a.c simple" multiple-overlapping
canned_test "-L 4:a.c -L 8,12:a.c simple" multiple-superset
canned_test "-L 8,12:a.c -L 4:a.c simple" multiple-superset
test_bad_opts "-L" "switch.*requires a value"
test_bad_opts "-L b.c" "argument.*not of the form"
test_bad_opts "-L 1:" "argument.*not of the form"
test_bad_opts "-L 1:nonexistent" "There is no path"
test_bad_opts "-L 1:simple" "There is no path"
test_bad_opts "-L '/foo:b.c'" "argument.*not of the form"
test_bad_opts "-L 1000:b.c" "has only.*lines"
test_bad_opts "-L 1,1000:b.c" "has only.*lines"
test_bad_opts "-L :b.c" "argument.*not of the form"
test_bad_opts "-L :foo:b.c" "no match"
test_done

View File

@ -0,0 +1,43 @@
commit 4a23ae5c98d59a58c6da036156959f2dc9f472ad
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:47:40 2013 +0100
change at very beginning
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -1,3 +1,4 @@
+#include <unistd.h>
#include <stdio.h>
long f(long x)
commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:45:16 2013 +0100
touch both functions
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -1,3 +1,3 @@
#include <stdio.h>
-int f(int x)
+long f(long x)
commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:44:48 2013 +0100
initial
diff --git a/a.c b/a.c
--- /dev/null
+++ b/a.c
@@ -0,0 +1,3 @@
+#include <stdio.h>
+
+int f(int x)

View File

@ -0,0 +1,62 @@
commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:48:43 2013 +0100
change back to complete line
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -20,3 +20,5 @@
printf("%ld\n", f(15));
return 0;
-}
\ No newline at end of file
+}
+
+/* incomplete lines are bad! */
commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:48:10 2013 +0100
change to an incomplete line at end
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -20,3 +20,3 @@
printf("%ld\n", f(15));
return 0;
-}
+}
\ No newline at end of file
commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:45:16 2013 +0100
touch both functions
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -19,3 +19,3 @@
- printf("%d\n", f(15));
+ printf("%ld\n", f(15));
return 0;
}
commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:44:48 2013 +0100
initial
diff --git a/a.c b/a.c
--- /dev/null
+++ b/a.c
@@ -0,0 +18,3 @@
+ printf("%d\n", f(15));
+ return 0;
+}

View File

@ -0,0 +1,80 @@
commit 6ce3c4ff690136099bb17e1a8766b75764726ea7
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:49:50 2013 +0100
another simple change
diff --git a/b.c b/b.c
--- a/b.c
+++ b/b.c
@@ -4,9 +4,9 @@
long f(long x)
{
int s = 0;
while (x) {
- x >>= 1;
+ x /= 2;
s++;
}
return s;
}
commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:45:16 2013 +0100
touch both functions
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -3,9 +3,9 @@
-int f(int x)
+long f(long x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
return s;
}
commit f04fb20f2c77850996cba739709acc6faecc58f7
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:44:55 2013 +0100
change f()
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -3,8 +3,9 @@
int f(int x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
+ return s;
}
commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:44:48 2013 +0100
initial
diff --git a/a.c b/a.c
--- /dev/null
+++ b/a.c
@@ -0,0 +3,8 @@
+int f(int x)
+{
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+}

104
t/t4211/expect.multiple Normal file
View File

@ -0,0 +1,104 @@
commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:48:43 2013 +0100
change back to complete line
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -18,5 +18,7 @@
int main ()
{
printf("%ld\n", f(15));
return 0;
-}
\ No newline at end of file
+}
+
+/* incomplete lines are bad! */
commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:48:10 2013 +0100
change to an incomplete line at end
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -18,5 +18,5 @@
int main ()
{
printf("%ld\n", f(15));
return 0;
-}
+}
\ No newline at end of file
commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:45:16 2013 +0100
touch both functions
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -3,9 +3,9 @@
-int f(int x)
+long f(long x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
return s;
}
@@ -17,5 +17,5 @@
int main ()
{
- printf("%d\n", f(15));
+ printf("%ld\n", f(15));
return 0;
}
commit f04fb20f2c77850996cba739709acc6faecc58f7
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:44:55 2013 +0100
change f()
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -3,8 +3,9 @@
int f(int x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
+ return s;
}
commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:44:48 2013 +0100
initial
diff --git a/a.c b/a.c
--- /dev/null
+++ b/a.c
@@ -0,0 +3,8 @@
+int f(int x)
+{
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+}

View File

@ -0,0 +1,187 @@
commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:48:43 2013 +0100
change back to complete line
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -4,19 +4,21 @@
long f(long x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
return s;
}
/*
* This is only an example!
*/
int main ()
{
printf("%ld\n", f(15));
return 0;
-}
\ No newline at end of file
+}
+
+/* incomplete lines are bad! */
commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:48:10 2013 +0100
change to an incomplete line at end
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -4,19 +4,19 @@
long f(long x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
return s;
}
/*
* This is only an example!
*/
int main ()
{
printf("%ld\n", f(15));
return 0;
-}
+}
\ No newline at end of file
commit 39b6eb2d5b706d3322184a169f666f25ed3fbd00
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:45:41 2013 +0100
touch comment
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -3,19 +3,19 @@
long f(long x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
return s;
}
/*
- * A comment.
+ * This is only an example!
*/
int main ()
{
printf("%ld\n", f(15));
return 0;
}
commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:45:16 2013 +0100
touch both functions
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -3,19 +3,19 @@
-int f(int x)
+long f(long x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
return s;
}
/*
* A comment.
*/
int main ()
{
- printf("%d\n", f(15));
+ printf("%ld\n", f(15));
return 0;
}
commit f04fb20f2c77850996cba739709acc6faecc58f7
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:44:55 2013 +0100
change f()
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -3,18 +3,19 @@
int f(int x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
+ return s;
}
/*
* A comment.
*/
int main ()
{
printf("%d\n", f(15));
return 0;
}
commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:44:48 2013 +0100
initial
diff --git a/a.c b/a.c
--- /dev/null
+++ b/a.c
@@ -0,0 +3,18 @@
+int f(int x)
+{
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+}
+
+/*
+ * A comment.
+ */
+
+int main ()
+{
+ printf("%d\n", f(15));
+ return 0;
+}

View File

@ -0,0 +1,59 @@
commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:45:16 2013 +0100
touch both functions
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -3,9 +3,9 @@
-int f(int x)
+long f(long x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
return s;
}
commit f04fb20f2c77850996cba739709acc6faecc58f7
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:44:55 2013 +0100
change f()
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -3,8 +3,9 @@
int f(int x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
+ return s;
}
commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:44:48 2013 +0100
initial
diff --git a/a.c b/a.c
--- /dev/null
+++ b/a.c
@@ -0,0 +3,8 @@
+int f(int x)
+{
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+}

View File

@ -0,0 +1,160 @@
commit 0469c60bc4837d52d97b1f081dec5f98dea20fed
Merge: ba227c6 6ce3c4f
Author: Thomas Rast <trast@inf.ethz.ch>
Date: Fri Apr 12 16:16:24 2013 +0200
Merge across the rename
commit 6ce3c4ff690136099bb17e1a8766b75764726ea7
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:49:50 2013 +0100
another simple change
diff --git a/b.c b/b.c
--- a/b.c
+++ b/b.c
@@ -4,14 +4,14 @@
long f(long x)
{
int s = 0;
while (x) {
- x >>= 1;
+ x /= 2;
s++;
}
return s;
}
/*
* This is only an example!
*/
commit ba227c6632349700fbb957dec2b50f5e2358be3f
Author: Thomas Rast <trast@inf.ethz.ch>
Date: Fri Apr 12 16:15:57 2013 +0200
change on another line of history while rename happens
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -4,14 +4,14 @@
long f(long x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
return s;
}
/*
- * This is only an example!
+ * This is only a short example!
*/
commit 39b6eb2d5b706d3322184a169f666f25ed3fbd00
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:45:41 2013 +0100
touch comment
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -3,14 +3,14 @@
long f(long x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
return s;
}
/*
- * A comment.
+ * This is only an example!
*/
commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:45:16 2013 +0100
touch both functions
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -3,14 +3,14 @@
-int f(int x)
+long f(long x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
return s;
}
/*
* A comment.
*/
commit f04fb20f2c77850996cba739709acc6faecc58f7
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:44:55 2013 +0100
change f()
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -3,13 +3,14 @@
int f(int x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
+ return s;
}
/*
* A comment.
*/
commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:44:48 2013 +0100
initial
diff --git a/a.c b/a.c
--- /dev/null
+++ b/a.c
@@ -0,0 +3,13 @@
+int f(int x)
+{
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+}
+
+/*
+ * A comment.
+ */
+

59
t/t4211/expect.simple-f Normal file
View File

@ -0,0 +1,59 @@
commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:45:16 2013 +0100
touch both functions
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -3,9 +3,9 @@
-int f(int x)
+long f(long x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
return s;
}
commit f04fb20f2c77850996cba739709acc6faecc58f7
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:44:55 2013 +0100
change f()
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -3,8 +3,9 @@
int f(int x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
+ return s;
}
commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:44:48 2013 +0100
initial
diff --git a/a.c b/a.c
--- /dev/null
+++ b/a.c
@@ -0,0 +3,8 @@
+int f(int x)
+{
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+}

View File

@ -0,0 +1,100 @@
commit 39b6eb2d5b706d3322184a169f666f25ed3fbd00
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:45:41 2013 +0100
touch comment
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -3,14 +3,14 @@
long f(long x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
return s;
}
/*
- * A comment.
+ * This is only an example!
*/
commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:45:16 2013 +0100
touch both functions
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -3,14 +3,14 @@
-int f(int x)
+long f(long x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
return s;
}
/*
* A comment.
*/
commit f04fb20f2c77850996cba739709acc6faecc58f7
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:44:55 2013 +0100
change f()
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -3,13 +3,14 @@
int f(int x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
+ return s;
}
/*
* A comment.
*/
commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:44:48 2013 +0100
initial
diff --git a/a.c b/a.c
--- /dev/null
+++ b/a.c
@@ -0,0 +3,13 @@
+int f(int x)
+{
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+}
+
+/*
+ * A comment.
+ */
+

View File

@ -0,0 +1,68 @@
commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:48:43 2013 +0100
change back to complete line
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -18,5 +18,5 @@
int main ()
{
printf("%ld\n", f(15));
return 0;
-}
\ No newline at end of file
+}
commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:48:10 2013 +0100
change to an incomplete line at end
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -18,5 +18,5 @@
int main ()
{
printf("%ld\n", f(15));
return 0;
-}
+}
\ No newline at end of file
commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:45:16 2013 +0100
touch both functions
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -17,5 +17,5 @@
int main ()
{
- printf("%d\n", f(15));
+ printf("%ld\n", f(15));
return 0;
}
commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:44:48 2013 +0100
initial
diff --git a/a.c b/a.c
--- /dev/null
+++ b/a.c
@@ -0,0 +16,5 @@
+int main ()
+{
+ printf("%d\n", f(15));
+ return 0;
+}

View File

@ -0,0 +1,70 @@
commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:48:43 2013 +0100
change back to complete line
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -18,5 +18,7 @@
int main ()
{
printf("%ld\n", f(15));
return 0;
-}
\ No newline at end of file
+}
+
+/* incomplete lines are bad! */
commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:48:10 2013 +0100
change to an incomplete line at end
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -18,5 +18,5 @@
int main ()
{
printf("%ld\n", f(15));
return 0;
-}
+}
\ No newline at end of file
commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:45:16 2013 +0100
touch both functions
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -17,5 +17,5 @@
int main ()
{
- printf("%d\n", f(15));
+ printf("%ld\n", f(15));
return 0;
}
commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:44:48 2013 +0100
initial
diff --git a/a.c b/a.c
--- /dev/null
+++ b/a.c
@@ -0,0 +16,5 @@
+int main ()
+{
+ printf("%d\n", f(15));
+ return 0;
+}

102
t/t4211/expect.two-ranges Normal file
View File

@ -0,0 +1,102 @@
commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:48:43 2013 +0100
change back to complete line
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -18,5 +18,5 @@
int main ()
{
printf("%ld\n", f(15));
return 0;
-}
\ No newline at end of file
+}
commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:48:10 2013 +0100
change to an incomplete line at end
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -18,5 +18,5 @@
int main ()
{
printf("%ld\n", f(15));
return 0;
-}
+}
\ No newline at end of file
commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:45:16 2013 +0100
touch both functions
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -3,9 +3,9 @@
-int f(int x)
+long f(long x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
return s;
}
@@ -17,5 +17,5 @@
int main ()
{
- printf("%d\n", f(15));
+ printf("%ld\n", f(15));
return 0;
}
commit f04fb20f2c77850996cba739709acc6faecc58f7
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:44:55 2013 +0100
change f()
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -3,8 +3,9 @@
int f(int x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
+ return s;
}
commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:44:48 2013 +0100
initial
diff --git a/a.c b/a.c
--- /dev/null
+++ b/a.c
@@ -0,0 +3,8 @@
+int f(int x)
+{
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+}

View File

@ -0,0 +1,39 @@
commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:48:43 2013 +0100
change back to complete line
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -22,1 +24,1 @@
-}
\ No newline at end of file
+/* incomplete lines are bad! */
commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:48:10 2013 +0100
change to an incomplete line at end
diff --git a/a.c b/a.c
--- a/a.c
+++ b/a.c
@@ -22,1 +22,1 @@
-}
+}
\ No newline at end of file
commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
Author: Thomas Rast <trast@student.ethz.ch>
Date: Thu Feb 28 10:44:48 2013 +0100
initial
diff --git a/a.c b/a.c
--- /dev/null
+++ b/a.c
@@ -0,0 +20,1 @@
+}

406
t/t4211/history.export Normal file
View File

@ -0,0 +1,406 @@
blob
mark :1
data 157
#include <stdio.h>
int f(int x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
}
/*
* A comment.
*/
int main ()
{
printf("%d\n", f(15));
return 0;
}
reset refs/tags/simple
commit refs/tags/simple
mark :2
author Thomas Rast <trast@student.ethz.ch> 1362044688 +0100
committer Thomas Rast <trast@student.ethz.ch> 1362044688 +0100
data 8
initial
M 100644 :1 a.c
blob
mark :3
data 168
#include <stdio.h>
int f(int x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
return s;
}
/*
* A comment.
*/
int main ()
{
printf("%d\n", f(15));
return 0;
}
commit refs/tags/simple
mark :4
author Thomas Rast <trast@student.ethz.ch> 1362044695 +0100
committer Thomas Rast <trast@student.ethz.ch> 1362044695 +0100
data 11
change f()
from :2
M 100644 :3 a.c
blob
mark :5
data 171
#include <stdio.h>
long f(long x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
return s;
}
/*
* A comment.
*/
int main ()
{
printf("%ld\n", f(15));
return 0;
}
commit refs/tags/simple
mark :6
author Thomas Rast <trast@student.ethz.ch> 1362044716 +0100
committer Thomas Rast <trast@student.ethz.ch> 1362044716 +0100
data 21
touch both functions
from :4
M 100644 :5 a.c
blob
mark :7
data 185
#include <stdio.h>
long f(long x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
return s;
}
/*
* This is only an example!
*/
int main ()
{
printf("%ld\n", f(15));
return 0;
}
commit refs/tags/simple
mark :8
author Thomas Rast <trast@student.ethz.ch> 1362044741 +0100
committer Thomas Rast <trast@student.ethz.ch> 1362044741 +0100
data 14
touch comment
from :6
M 100644 :7 a.c
blob
mark :9
data 205
#include <unistd.h>
#include <stdio.h>
long f(long x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
return s;
}
/*
* This is only an example!
*/
int main ()
{
printf("%ld\n", f(15));
return 0;
}
commit refs/tags/simple
mark :10
author Thomas Rast <trast@student.ethz.ch> 1362044860 +0100
committer Thomas Rast <trast@student.ethz.ch> 1362044860 +0100
data 25
change at very beginning
from :8
M 100644 :9 a.c
blob
mark :11
data 204
#include <unistd.h>
#include <stdio.h>
long f(long x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
return s;
}
/*
* This is only an example!
*/
int main ()
{
printf("%ld\n", f(15));
return 0;
}
commit refs/tags/simple
mark :12
author Thomas Rast <trast@student.ethz.ch> 1362044890 +0100
committer Thomas Rast <trast@student.ethz.ch> 1362044890 +0100
data 36
change to an incomplete line at end
from :10
M 100644 :11 a.c
blob
mark :13
data 238
#include <unistd.h>
#include <stdio.h>
long f(long x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
return s;
}
/*
* This is only an example!
*/
int main ()
{
printf("%ld\n", f(15));
return 0;
}
/* incomplete lines are bad! */
commit refs/tags/simple
mark :14
author Thomas Rast <trast@student.ethz.ch> 1362044923 +0100
committer Thomas Rast <trast@student.ethz.ch> 1362044923 +0100
data 29
change back to complete line
from :12
M 100644 :13 a.c
commit refs/tags/move-support
mark :15
author Thomas Rast <trast@student.ethz.ch> 1362044968 +0100
committer Thomas Rast <trast@student.ethz.ch> 1362044968 +0100
data 10
move file
from :14
D a.c
M 100644 :13 b.c
blob
mark :16
data 237
#include <unistd.h>
#include <stdio.h>
long f(long x)
{
int s = 0;
while (x) {
x /= 2;
s++;
}
return s;
}
/*
* This is only an example!
*/
int main ()
{
printf("%ld\n", f(15));
return 0;
}
/* incomplete lines are bad! */
commit refs/tags/move-support
mark :17
author Thomas Rast <trast@student.ethz.ch> 1362044990 +0100
committer Thomas Rast <trast@student.ethz.ch> 1362044990 +0100
data 22
another simple change
from :15
M 100644 :16 b.c
blob
mark :18
data 254
#include <unistd.h>
#include <stdio.h>
long f(long x);
/*
* This is only an example!
*/
int main ()
{
printf("%ld\n", f(15));
return 0;
}
/* incomplete lines are bad! */
long f(long x)
{
int s = 0;
while (x) {
x /= 2;
s++;
}
return s;
}
commit refs/heads/master
mark :19
author Thomas Rast <trast@student.ethz.ch> 1362045024 +0100
committer Thomas Rast <trast@student.ethz.ch> 1362045024 +0100
data 21
move within the file
from :17
M 100644 :18 b.c
blob
mark :20
data 243
#include <unistd.h>
#include <stdio.h>
long f(long x)
{
int s = 0;
while (x) {
x >>= 1;
s++;
}
return s;
}
/*
* This is only a short example!
*/
int main ()
{
printf("%ld\n", f(15));
return 0;
}
/* incomplete lines are bad! */
commit refs/heads/parallel-change
mark :21
author Thomas Rast <trast@inf.ethz.ch> 1365776157 +0200
committer Thomas Rast <trast@inf.ethz.ch> 1365776157 +0200
data 55
change on another line of history while rename happens
from :14
M 100644 :20 a.c
blob
mark :22
data 242
#include <unistd.h>
#include <stdio.h>
long f(long x)
{
int s = 0;
while (x) {
x /= 2;
s++;
}
return s;
}
/*
* This is only a short example!
*/
int main ()
{
printf("%ld\n", f(15));
return 0;
}
/* incomplete lines are bad! */
commit refs/heads/parallel-change
mark :23
author Thomas Rast <trast@inf.ethz.ch> 1365776184 +0200
committer Thomas Rast <trast@inf.ethz.ch> 1365776191 +0200
data 24
Merge across the rename
from :21
merge :17
D a.c
M 100644 :22 b.c
reset refs/heads/parallel-change
from :23

View File

@ -175,6 +175,12 @@ test_expect_success 'blame -L with invalid end' '
grep "has only 2 lines" errors
'
test_expect_success 'blame parses <end> part of -L' '
git blame -L1,1 tres >out &&
cat out &&
test $(wc -l < out) -eq 1
'
test_expect_success 'indent of line numbers, nine lines' '
git blame nine_lines >actual &&
test $(grep -c " " actual) = 0