We support the syntax like:
-L n1,m1 pathspec1 -L n2,m2 pathspec2.
Make the parse-options API not stop when encounter a
non-option argument, report the status and go on parsing
the remain options.
Thanks-to: Jonathan Nieder [off-list ref]
Signed-off-by: Bo Yang <redacted>
---
parse-options.c | 3 ++-
parse-options.h | 3 ++-
2 files changed, 4 insertions(+), 2 deletions(-)
With the two new APIs of parse options added in the previous
commit, we parse the multiple '-L n,m <file>' syntax.
Signed-off-by: Bo Yang <redacted>
---
builtin/log.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 101 insertions(+), 1 deletions(-)
@@ -19,6 +19,7 @@#include"remote.h"#include"string-list.h"#include"parse-options.h"+#include"line.h"/* Set a default date-time format for git log ("log.date" config variable) */staticconstchar*default_date_mode=NULL;
@@ -49,12 +63,41 @@ static int parse_decoration_style(const char *var, const char *value)return-1;}+staticintlog_line_range_callback(conststructoption*option,constchar*arg,intunset)+{+structline_opt_callback_data*data=option->value;+structdiff_line_range*r=*data->range;+structparse_opt_ctx_t*ctx=data->ctx;+if(!arg)+return-1;++if(r->nr==0&&r->next==NULL){+ctx->out[ctx->cpidx++]=dashdash;+}++diff_line_range_append(r,arg);+data->rev->line=1;+return0;+}+staticvoidcmd_log_init(intargc,constchar**argv,constchar*prefix,structrev_info*rev,structsetup_revision_opt*opt){inti;intdecoration_given=0;structuserformat_wantw;+constchar*path=NULL,*pathspec=NULL;+staticstructdiff_line_range*range=NULL;+staticstructparse_opt_ctx_tctx;+staticstructline_opt_callback_dataline_cb={&range,&ctx,NULL};+staticconststructoptionoptions[]={+OPT_CALLBACK('L',NULL,&line_cb,"n,m","Process only line range n,m, counting from 1",log_line_range_callback),+OPT_END()+};++line_cb.rev=rev;+range=xmalloc(sizeof(*range));+DIFF_LINE_RANGE_INIT(range);rev->abbrev=DEFAULT_ABBREV;rev->commit_format=CMIT_FMT_DEFAULT;
@@ -75,6 +118,58 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,*/if(argc==2&&!strcmp(argv[1],"-h"))usage(builtin_log_usage);++parse_options_start(&ctx,argc,argv,prefix,PARSE_OPT_KEEP_DASHDASH|+PARSE_OPT_KEEP_ARGV0|PARSE_OPT_STOP_AT_NON_OPTION);+for(;;){+switch(parse_options_step(&ctx,options,log_opt_usage)){+casePARSE_OPT_HELP:+exit(129);+casePARSE_OPT_DONE:+gotoparse_done;+casePARSE_OPT_NON_OPTION:+path=parse_options_current(&ctx);+pathspec=prefix_path(prefix,prefix?strlen(prefix):0,path);+range->spec=alloc_filespec(pathspec);+free((void*)pathspec);+if(range->nr==0){+if(range->next){+die("Path %s need a -L <range> option\n"+"If you want follow the history of the whole file "+"whether to using 'git log' without -L or using "+"'git log -L 1,$ <path>'",range->spec->path);+}else{+parse_options_next(&ctx,1);+continue;+}+}+structdiff_line_range*r=xmalloc(sizeof(*r));+DIFF_LINE_RANGE_INIT(r);+r->next=range;+range=r;+parse_options_next(&ctx,1);+continue;+casePARSE_OPT_UNKNOWN:+parse_options_next(&ctx,1);+continue;+}++parse_revision_opt(rev,&ctx,options,log_opt_usage);+}+parse_done:+argc=parse_options_end(&ctx);++/* die if '-L <range>' with no pathspec follow */+if(range->nr>0&&range->spec==NULL){+die("Each -L should follow a pathspec");+}+/* clear up the last range */+if(range->nr==0){+structdiff_line_range*r=range->next;+DIFF_LINE_RANGE_CLEAR(range);+range=r;+}+argc=setup_revisions(argc,argv,rev,opt);memset(&w,0,sizeof(w));
@@ -125,6 +220,11 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,rev->show_decorations=1;load_ref_decorations(decoration_style);}++/* Test whether line level history is asked for */+if(range&&range->nr>0){+setup_line(rev,range);+}}/*
1. parse_options_current: get the current option/argument the API
now is dealing with;
2. parse_options_next: make the API to deal with the next
option/argument.
Signed-off-by: Bo Yang <redacted>
---
parse-options.c | 19 +++++++++++++++++++
parse-options.h | 4 ++++
2 files changed, 23 insertions(+), 0 deletions(-)
'struct diff_line_range' is the main data structure to store
the user interesting line range. There is one 'diff_line_range'
for each file, and there are multiple 'struct range' in each
'diff_line_range'. In this way, we support multiple ranges.
Within 'struct range', there are multiple 'struct print_range'
which represent a diff chunk.
Signed-off-by: Bo Yang <redacted>
---
Makefile | 2 +
diffcore.h | 1 +
line.c | 456 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
line.h | 122 ++++++++++++++++
revision.c | 6 +
revision.h | 8 +-
6 files changed, 593 insertions(+), 2 deletions(-)
create mode 100644 line.c
create mode 100644 line.h
@@ -23,6 +23,7 @@#define MINIMUM_BREAK_SIZE 400 /* do not break a file smaller than this */structuserdiff_driver;+structdiff_options;structdiff_filespec{unsignedcharsha1[20];
@@ -0,0 +1,456 @@+#include"line.h"+#include"cache.h"+#include"tag.h"+#include"blob.h"+#include"tree.h"+#include"commit.h"+#include"diff.h"+#include"decorate.h"+#include"revision.h"+#include"xdiff-interface.h"+#include"strbuf.h"+#include"log-tree.h"++staticvoidcleanup(structdiff_line_range*r)+{+while(r){+structdiff_line_range*next=r->next;+DIFF_LINE_RANGE_CLEAR(r);+free(r);+r=next;+}+}++staticstructobject*verify_commit(structrev_info*revs)+{+structobject*commit=NULL;+constchar*name=NULL;+inti;++for(i=0;i<revs->pending.nr;i++){+structobject*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,name);+commit=obj;+name=revs->pending.objects[i].name;+}++if(commit==NULL)+die("No commit specified?");++returncommit;+}++staticvoidfill_blob_sha1(structcommit*commit,structdiff_line_range*r)+{+unsignedmode;+unsignedcharsha1[20];++while(r){+if(get_tree_entry(commit->object.sha1,r->spec->path,+sha1,&mode))+gotoerror;+fill_filespec(r->spec,sha1,mode);+r=r->next;+}++return;+error:+die("There is no path %s in the commit",r->spec->path);+}++staticvoidfill_line_ends(structdiff_filespec*spec,long*lines,+unsignedlong**line_ends)+{+intnum=0,size=50;+longcur=0;+unsignedlong*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(int));+ends[cur++]=-1;+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(int));+*lines=cur;+*line_ends=ends;+}++staticconstchar*nth_line(structdiff_filespec*spec,intline,+longlines,unsignedlong*line_ends)+{+assert(line<lines);+assert(spec&&spec->data);++return(char*)spec->data+line_ends[line]+1;+}++/*+*copiedfromblame.c,indeed,wecaneventousethistotest+*whetherlinelogworks.:)+*/+staticconstchar*parse_loc(constchar*spec,structdiff_filespec*file,+longlines,unsignedlong*line_ends,+longbegin,long*ret)+{+char*term;+constchar*line;+longnum;+intreg_error;+regex_tregexp;+regmatch_tmatch[1];++/* Allow "-L <something>,+20" to mean starting at <something>+*for20lines,or"-L <something>,-5"for5linesendingat+*<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;+elseif(!num)+*ret=begin;+else+*ret=begin+num;+returnterm;+}+returnspec;+}+num=strtol(spec,&term,10);+if(term!=spec){+*ret=num;+returnterm;+}+if(spec[0]!='/')+returnspec;++/* it could be a regexp of form /.../ */+for(term=(char*)spec+1;*term&&*term!='/';term++){+if(*term=='\\')+term++;+}+if(*term!='/')+returnspec;++/* try [spec+1 .. term-1] as regexp */+*term=0;+begin--;/* input is in human terms */+line=nth_line(file,begin,lines,line_ends);++if(!(reg_error=regcomp(®exp,spec+1,REG_NEWLINE))&&+!(reg_error=regexec(®exp,line,1,match,0))){+constchar*cp=line+match[0].rm_so;+constchar*nline;++while(begin++<lines){+nline=nth_line(file,begin,lines,line_ends);+if(line<=cp&&cp<nline)+break;+line=nline;+}+*ret=begin;+regfree(®exp);+*term++='/';+returnterm;+}+else{+charerrbuf[1024];+regerror(reg_error,®exp,errbuf,1024);+die("-L parameter '%s': %s",spec+1,errbuf);+}+}++staticvoidparse_range(longlines,unsignedlong*line_ends,+structrange*r,structdiff_filespec*spec)+{+constchar*term;++term=parse_loc(r->arg,spec,lines,line_ends,1,&r->start);+if(*term==','){+term=parse_loc(term+1,spec,lines,line_ends,+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>r->end){+longtmp=r->start;+r->start=r->end;+r->end=tmp;+}++if(r->start<1)+r->start=1;+if(r->end>=lines)+r->end=lines-1;+}++staticvoidparse_lines(structcommit*commit,structdiff_line_range*r)+{+inti;+structrange*old_range=NULL;+longlines=0;+unsignedlong*ends=NULL;++while(r){+structdiff_filespec*spec=r->spec;+intnum=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);+}+}++/*+*Insertanewlinerangeintoadiff_line_rangestruct,andkeepthe+*r->rangessortedbytheirstartinglinenumber.+*/+structrange*diff_line_range_insert(structdiff_line_range*r,constchar*arg,+intstart,intend)+{+inti=0;+structrange*rs=r->ranges;+intleft_extend=0,right_extend=0;++assert(r!=NULL);+assert(start<=end);++if(r->nr==0||rs[r->nr-1].end<start-1){+DIFF_LINE_RANGE_GROW(r);+rs=r->ranges;+intnum=r->nr-1;+rs[num].arg=arg;+rs[num].start=start;+rs[num].end=end;+returnrs+num;+}++for(;i<r->nr;i++){+if(rs[i].end<start-1)+continue;+if(rs[i].end==start-1){+rs[i].end=end;+right_extend=1;+gotoout;+}++assert(rs[i].end>start-1);+if(rs[i].start<=start){+if(rs[i].end<end){+rs[i].end=end;+right_extend=1;+}+gotoout;+}elseif(rs[i].start<=end+1){+rs[i].start=start;+left_extend=1;+if(rs[i].end<end){+rs[i].end=end;+right_extend=1;+}+gotoout;+}else{+intnum=r->nr-i;+DIFF_LINE_RANGE_GROW(r);+rs=r->ranges;+memmove(rs+i+1,rs+i,num*sizeof(structrange));+rs[i].arg=arg;+rs[i].start=start;+rs[i].end=end;+gotoout;+}+}++out:+assert(r->nr!=i);+if(left_extend){+intj=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(structrange));+r->nr-=i-j-1;+}+if(right_extend){+intj=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(structrange));+}+r->nr-=j-i-1;+}+assert(r->nr);++returnrs+i;+}++voiddiff_line_range_clear(structdiff_line_range*r)+{+inti=0,zero=0;++for(;i<r->nr;i++){+structrange*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;+}++voiddiff_line_range_append(structdiff_line_range*r,constchar*arg)+{+DIFF_LINE_RANGE_GROW(r);+r->ranges[r->nr-1].arg=arg;+}++structdiff_line_range*diff_line_range_merge(structdiff_line_range*out,+structdiff_line_range*other)+{+structdiff_line_range*one=out,*two=other;+structdiff_line_range*pone;++while(one){+structdiff_line_range*ptwo;+two=other;+ptwo=other;+while(two){+if(!strcmp(one->spec->path,two->spec->path)){+inti=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;++returnout;+}++voidadd_line_range(structrev_info*revs,structcommit*commit,structdiff_line_range*r)+{+structdiff_line_range*ret=NULL;++if(r!=NULL){+ret=lookup_decoration(&revs->line_range,&commit->object);+if(ret!=NULL){+diff_line_range_merge(ret,r);+}else{+add_decoration(&revs->line_range,&commit->object,r);+}+commit->object.flags|=RANGE_UPDATE;+}+}++structdiff_line_range*lookup_line_range(structrev_info*revs,structcommit*commit)+{+structdiff_line_range*ret=NULL;++ret=lookup_decoration(&revs->line_range,&commit->object);+returnret;+}++voidsetup_line(structrev_info*rev,structdiff_line_range*r)+{+structcommit*commit=NULL;+structdiff_options*opt=&rev->diffopt;++commit=(structcommit*)verify_commit(rev);+parse_lines(commit,r);++add_line_range(rev,commit,r);+/*+*Notewesupport-M/-Ctodetectfilerename+*/+opt->nr_paths=0;+diff_tree_release_paths(opt);+}+
Both 'git blame -L' and 'git log -L' parse the same style
of line number arguments, so put the 'parse_loc' function
to line.c and export it.
The caller of parse_loc should provide a callback function
which is used to calculate the nth line start position.
Other parts such as regexp search, line number parsing are
abstracted and re-used.
Signed-off-by: Bo Yang <redacted>
---
builtin/blame.c | 89 +++++-------------------------------------------------
line.c | 35 ++++++++++++---------
line.h | 5 +++
3 files changed, 34 insertions(+), 95 deletions(-)
@@ -1907,83 +1913,6 @@ static const char *add_prefix(const char *prefix, const char *path)}/*-*Parsingof(commaseparated)oneiteminthe-Loption-*/-staticconstchar*parse_loc(constchar*spec,-structscoreboard*sb,longlno,-longbegin,long*ret)-{-char*term;-constchar*line;-longnum;-intreg_error;-regex_tregexp;-regmatch_tmatch[1];--/* Allow "-L <something>,+20" to mean starting at <something>-*for20lines,or"-L <something>,-5"for5linesendingat-*<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;-elseif(!num)-*ret=begin;-else-*ret=begin+num;-returnterm;-}-returnspec;-}-num=strtol(spec,&term,10);-if(term!=spec){-*ret=num;-returnterm;-}-if(spec[0]!='/')-returnspec;--/* it could be a regexp of form /.../ */-for(term=(char*)spec+1;*term&&*term!='/';term++){-if(*term=='\\')-term++;-}-if(*term!='/')-returnspec;--/* try [spec+1 .. term-1] as regexp */-*term=0;-begin--;/* input is in human terms */-line=nth_line(sb,begin);--if(!(reg_error=regcomp(®exp,spec+1,REG_NEWLINE))&&-!(reg_error=regexec(®exp,line,1,match,0))){-constchar*cp=line+match[0].rm_so;-constchar*nline;--while(begin++<lno){-nline=nth_line(sb,begin);-if(line<=cp&&cp<nline)-break;-line=nline;-}-*ret=begin;-regfree(®exp);-*term++='/';-returnterm;-}-else{-charerrbuf[1024];-regerror(reg_error,®exp,errbuf,1024);-die("-L parameter '%s': %s",spec+1,errbuf);-}-}--/**Parsingof-Loption*/staticvoidprepare_blame_range(structscoreboard*sb,
Use fill_metainfo to fill the line level diff meta data,
emit_line to print out a line and quote_two to quote
paths.
Signed-off-by: Bo Yang <redacted>
---
diff.c | 6 +++---
diff.h | 17 +++++++++++++++++
2 files changed, 20 insertions(+), 3 deletions(-)
Both single range clone and deeply clone are supported.
Signed-off-by: Bo Yang <redacted>
---
line.c | 39 +++++++++++++++++++++++++++++++++++++++
line.h | 4 ++++
2 files changed, 43 insertions(+), 0 deletions(-)
The interesting line range will be passed to all of its parents.
For non-merge commit, we just map_range the ranges. Generally, the
algorithm do:
1. Run a diffcore_std to find out the pairs;
2. Run a xdi_diff_hunks on each interesting file pair;
3. The map_range_cb callback will be invoked for each diff hunk,
and in the function map_lines we will calculate the pre-image
range from the post-image range.
For merge commit, another take_range pass will be done except the
above normal map_range work. It is used to subtract each same
part lines of the current range out. After this pass, if there is
any line range left, this means the merge must be a non-trivial
merge. This is how the non-trivial merge detect work.
The algorithm that map lines from post-image to pre-image is in
the function map_lines. Generally, we use simple line number
calculation method to do the map.
Signed-off-by: Bo Yang <redacted>
---
line.c | 453 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
revision.h | 5 +-
2 files changed, 457 insertions(+), 1 deletions(-)
@@ -498,3 +498,456 @@ void setup_line(struct rev_info *rev, struct diff_line_range *r)diff_tree_release_paths(opt);}+structtake_range_cb_data{+structdiff_line_range*interesting;+structdiff_line_range*range;+longplno,tlno;+intdiff;+};++#define SCALE_FACTOR 4+voidmap_lines(longp_start,longp_end,longt_start,longt_end,+longstart,longend,long*o_start,long*o_end)+{+/*+*xdiffalwaysassign'same'withthelastlinenumberoftwo+*sameranges.Whensomelinesareaddedfromscratch,+*p_start=same+1;+*p_end=same;+*so,wecanunderstandthefollowingcondition.+*/+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;+}++/*+*Anewstrategyforlinesmapping:+*+*Whenthepre-imageisnomorethan1/4ofthepost-image,it+*takenosensetosaywhattherealpre-imageis,wancanjust+*treatallthepost-imagelinesasaddedfromscratch.+*/+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){+inttemp=*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;+}++staticvoidmap_range(structtake_range_cb_data*data,intsame,+longp_start,longp_end,longt_start,longt_end)+{+structrange*ranges=data->interesting->ranges;+longtakens,takene,start,end;+inti=0,out=0,added=0;+longop_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){+structprint_pair*pair=&ranges[i].pair;+PRINT_PAIR_GROW(pair);+structprint_range*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 */+}else{+structrange*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;+}+}++staticvoidtake_range(structtake_range_cb_data*data,+longp_start,longp_end,longt_start,longt_end)+{+structrange*ranges=data->interesting->ranges;+longtakens,takene,start,end;+inti=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){+longtmp=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){+intnum=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;+}+}++staticvoidtake_range_cb(void*data,longsame,longp_next,longt_next)+{+structtake_range_cb_data*d=data;+longp_start=d->plno+1,t_start=d->tlno+1;+longp_end=p_start+same-t_start,t_end=same;++/* If one file is added from scratch, this may confuse take_range */+if(t_end>=t_start)+take_range(d,p_start,p_end,t_start,t_end);+d->plno=p_next;+d->tlno=t_next;+}++staticvoidmap_range_cb(void*data,longsame,longp_next,longt_next)+{+structtake_range_cb_data*d=data;++longp_start=d->plno+1;+longt_start=d->tlno+1;+longp_end=same-t_start+p_start;+longt_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;+}++staticvoidassign_range_to_parent(structrev_info*rev,structcommit*c,+structcommit*p,structdiff_line_range*r,+structdiff_options*opt,intmap)+{+structdiff_line_range*rr=xmalloc(sizeof(*rr));+structdiff_line_range*cr=rr,*prev_r=rr;+structtree_descdesc1,desc2;+void*tree1=NULL,*tree2=NULL;+unsignedlongsize1,size2;+structdiff_queue_struct*queue;+structtake_range_cb_datacb={NULL,cr,0,0};+xpparam_txpp;+xdemitconf_txecfg;+inti,diff=0;+xdiff_emit_hunk_consume_fnfn=map?map_range_cb:take_range_cb;++DIFF_LINE_RANGE_INIT(cr);+memset(&xpp,0,sizeof(xpp));+memset(&xecfg,0,sizeof(xecfg));+xpp.flags=XDF_NEED_MINIMAL;+xecfg.ctxlen=xecfg.interhunkctxlen=0;++/*+*Composeuptwotrees,forrootcommit,wemakeupaemptytree.+*/+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++){+structdiff_filepair*pair=queue->queue[i];+structdiff_line_range*rg=r;+mmfile_tfile_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){+structdiff_line_range*tmp=xmalloc(sizeof(*tmp));+cr->next=tmp;+prev_r=cr;+cr=tmp;+}elseif(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;+/*+*Theremainpartisthesamepart.+*Insteadofcalculatingthetruelinenumberofthetwofiles,+*usethebiggestinteger.+*/+if(map)+map_range(&cb,1,cb.plno+1,0x7FFFFFFF,cb.tlno+1,0x7FFFFFFF);+else+take_range(&cb,cb.plno+1,0x7FFFFFFF,cb.tlno+1,0x7FFFFFFF);+}+opt->output_format=DIFF_FORMAT_NO_OUTPUT;+diff_flush(opt);++/* Collect the untouch ranges, this comes from the files not changed+*betweentwocommit.+*/+structdiff_line_range*rg=r;+while(rg){+/* clear the touch one to make it usable in next round */+if(rg->touch){+rg->touch=0;+}else{+structdiff_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(rr){+assert(p);+add_line_range(rev,p,rr);+}++/* and the ranges of current commit c is updated */+c->object.flags&=~RANGE_UPDATE;+if(diff)+c->object.flags|=NEED_PRINT;++if(tree1)+free(tree1);+if(tree2)+free(tree2);+}++staticvoiddiff_update_parent_range(structrev_info*rev,structcommit*commit)+{+structdiff_line_range*r=lookup_line_range(rev,commit);+structcommit_list*parents=commit->parents;+structcommit*c=NULL;+if(parents){+assert(parents->next==NULL);+c=parents->item;+}++assign_range_to_parent(rev,commit,c,r,&rev->diffopt,1);+}++staticvoidassign_parents_range(structrev_info*rev,structcommit*commit)+{+structcommit_list*parents=commit->parents;+structdiff_line_range*r=lookup_line_range(rev,commit);+structdiff_line_range*copy=NULL,*range=NULL;+intnontrivial=0;++/*+*Ifweareinlinearhistory,updaterangeandflushthepatchif+*necessary+*/+if(parents==NULL||parents->next==NULL){+returndiff_update_parent_range(rev,commit);+}++/*+*Loopontheparentsandassigntherangestodifferent+*parents,ifthereisanyrangeleft,thiscommitmust+*beanevilmerge.+*/+copy=diff_line_range_clone_deeply(r);+parents=commit->parents;+while(parents){+structcommit*p=parents->item;+assign_range_to_parent(rev,commit,p,r,&rev->diffopt,1);+assign_range_to_parent(rev,commit,p,copy,&rev->diffopt,0);+parents=parents->next;+}++/*+*yes,thismustbeanevilmerge.+*/+range=copy;+while(range){+if(range->nr){+commit->object.flags|=NEED_PRINT|EVIL_MERGE;+nontrivial=1;+}+range=range->next;+}++if(nontrivial){+add_decoration(&rev->nontrivial_merge,&commit->object,copy);+}else{+cleanup(copy);+}+}+
Since ranges may change in different branches, we should
make sure we do not pass range to parent until all the
ranges get 'combined' at the commit which is a split commit.
So, topological traversing is necessary.
Signed-off-by: Bo Yang <redacted>
---
builtin/log.c | 5 ++++-
line.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
line.h | 2 ++
3 files changed, 60 insertions(+), 1 deletions(-)
'struct line_chunk' is used to make sure each file is scaned
only once when printing the lines. We trace the line number and
the start position of intermediate line in this struct.
Two helper functions from diff.c are used to generate the
consistent format of diff meta info.
Signed-off-by: Bo Yang <redacted>
---
line.c | 245 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 245 insertions(+), 0 deletions(-)
Always print the interesting ranges even if the current
commit does not change any line of it.
Signed-off-by: Bo Yang <redacted>
---
builtin/log.c | 3 +++
line.c | 11 +++++++++--
revision.h | 3 ++-
3 files changed, 14 insertions(+), 3 deletions(-)
@@ -92,6 +93,7 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,staticstructline_opt_callback_dataline_cb={&range,&ctx,NULL};staticconststructoptionoptions[]={OPT_CALLBACK('L',NULL,&line_cb,"n,m","Process only line range n,m, counting from 1",log_line_range_callback),+OPT_BOOLEAN(0,"always-print",&always_print,"Always print the interesting range even if the current commit does not change any line of it"),OPT_END()};
@@ -224,6 +226,7 @@ parse_done:/* Test whether line level history is asked for */if(range&&range->nr>0){setup_line(rev,range);+rev->always_print=always_print;}}
@@ -19,6 +20,9 @@ command to control what is shown and how, and options applicable to the 'git diff-*' commands to control how the changes each commit introduces are shown.+With '-L' option, the command will help to trace the history of user specified+line ranges. It can trace multiple ranges coming from multiple files.+ OPTIONS -------
@@ -72,6 +76,32 @@ include::diff-options.txt[] to be prefixed with "\-- " to separate them from options or refnames.+-L <start>,<end>::+ The 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>.++++--always-print::+ Always print the interesting range even if the current commit+ does not change any line of the range.+ include::rev-list-options.txt[]
t4301: for simple linear history only
t4302: for history contains merge
Signed-off-by: Bo Yang <redacted>
---
t/t4301-log-line-single-history.sh | 342 ++++++++++++++++++++++++++++++++++++
t/t4302-log-line-merge-history.sh | 114 ++++++++++++
2 files changed, 456 insertions(+), 0 deletions(-)
create mode 100755 t/t4301-log-line-single-history.sh
create mode 100755 t/t4302-log-line-merge-history.sh
@@ -0,0 +1,342 @@+#!/bin/sh+#+# Copyright (c) 2010 Bo Yang+#++test_description='Testgitlog-Lwithsinglelineofhistory++'+../test-lib.sh+."$TEST_DIRECTORY"/diff-lib.sh++echo>path0'voidfunc(){+inta=0;+intb=1;+intc;+c=a+b;+}+'++echo>path1'voidoutput(){+printf("hello world");+}+'++test_expect_success\+'add path0/path1 and commit.'\+'gitaddpath0path1&&+gitcommit-m"Base commit"'++echo>path0'voidfunc(){+inta=10;+intb=11;+intc;+c=a+b;+}+'++echo>path1'voidoutput(){+constchar*str="hello world!";+printf("%s",str);+}+'++test_expect_success\+'Change the 2,3 lines of path0 and path1.'\+'gitaddpath0path1&&+gitcommit-m"Change 2,3 lines of path0 and path1"'++echo>path0'voidfunc(){+inta=10;+intb=11;+intc;+c=10*(a+b);+}+'++test_expect_success\+'Change the 5th line of path0.'\+'gitaddpath0&&+gitcommit-m"Change the 5th line of path0"'++echo>path0'voidfunc(){+inta=10;+intb=11;+printf("%d",a-b);+}+'++test_expect_success\+'Final change of path0.'\+'gitaddpath0&&+gitcommit-m"Final change of path0"'++test_expect_success\+'Show the line level log of path0'\+'git log --pretty=format:%s%n%b -L /func/,/^}/ path0 > current-path0'++test_expect_success\+'Show the line level log of path1'\+'git log --pretty=format:%s%n%b -L /output/,/^}/ path1 > current-path1'++test_expect_success\+'Show the line level log of two files'\+'git log --pretty=format:%s%n%b -L /func/,/^}/ path0 -L /output/,/^}/ path1 > current-pathall'++test_expect_success\+'Test the line number argument'\+'git log --pretty=format:%s%n%b -L 1,2 path0 > current-linenum'++test_expect_success\+'Test the --always-print option'\+'git log --pretty=format:%s%n%b --always-print -L 1,2 path0 > current-always'++cat>expected-path0<<\EOF+Finalchangeofpath0++diff--gita/path0b/path0+index44db133..1518c15100644+---a/path0++++b/path0+@@-1,6+1,5@@+voidfunc(){+inta=10;+intb=11;+-intc;+-c=10*(a+b);++printf("%d",a-b);+}++Changethe5thlineofpath0++diff--gita/path0b/path0+index9ef1692..44db133100644+---a/path0++++b/path0+@@-1,6+1,6@@+voidfunc(){+inta=10;+intb=11;+intc;+-c=a+b;++c=10*(a+b);+}++Change2,3linesofpath0andpath1++diff--gita/path0b/path0+indexaabffdf..9ef1692100644+---a/path0++++b/path0+@@-1,6+1,6@@+voidfunc(){+-inta=0;+-intb=1;++inta=10;++intb=11;+intc;+c=a+b;+}++Basecommit++diff--gita/path0b/path0+newfilemode100644+index0000000..aabffdf+---/dev/null++++b/path0+@@-0,0+1,6@@++voidfunc(){++inta=0;++intb=1;++intc;++c=a+b;++}+EOF++cat>expected-path1<<\EOF+Change2,3linesofpath0andpath1++diff--gita/path1b/path1+index997d841..1d711b5100644+---a/path1++++b/path1+@@-1,3+1,4@@+voidoutput(){+-printf("hello world");++constchar*str="hello world!";++printf("%s",str);+}++Basecommit++diff--gita/path1b/path1+newfilemode100644+index0000000..997d841+---/dev/null++++b/path1+@@-0,0+1,3@@++voidoutput(){++printf("hello world");++}+EOF++cat>expected-pathall<<\EOF+Finalchangeofpath0++diff--gita/path0b/path0+index44db133..1518c15100644+---a/path0++++b/path0+@@-1,6+1,5@@+voidfunc(){+inta=10;+intb=11;+-intc;+-c=10*(a+b);++printf("%d",a-b);+}++Changethe5thlineofpath0++diff--gita/path0b/path0+index9ef1692..44db133100644+---a/path0++++b/path0+@@-1,6+1,6@@+voidfunc(){+inta=10;+intb=11;+intc;+-c=a+b;++c=10*(a+b);+}++Change2,3linesofpath0andpath1++diff--gita/path0b/path0+indexaabffdf..9ef1692100644+---a/path0++++b/path0+@@-1,6+1,6@@+voidfunc(){+-inta=0;+-intb=1;++inta=10;++intb=11;+intc;+c=a+b;+}+diff--gita/path1b/path1+index997d841..1d711b5100644+---a/path1++++b/path1+@@-1,3+1,4@@+voidoutput(){+-printf("hello world");++constchar*str="hello world!";++printf("%s",str);+}++Basecommit++diff--gita/path0b/path0+newfilemode100644+index0000000..aabffdf+---/dev/null++++b/path0+@@-0,0+1,6@@++voidfunc(){++inta=0;++intb=1;++intc;++c=a+b;++}+diff--gita/path1b/path1+newfilemode100644+index0000000..997d841+---/dev/null++++b/path1+@@-0,0+1,3@@++voidoutput(){++printf("hello world");++}+EOF++cat>expected-linenum<<\EOF+Change2,3linesofpath0andpath1++diff--gita/path0b/path0+indexaabffdf..9ef1692100644+---a/path0++++b/path0+@@-1,2+1,2@@+voidfunc(){+-inta=0;++inta=10;++Basecommit++diff--gita/path0b/path0+newfilemode100644+index0000000..aabffdf+---/dev/null++++b/path0+@@-0,0+1,2@@++voidfunc(){++inta=0;+EOF++cat>expected-always<<\EOF+Finalchangeofpath0++diff--gita/path0b/path0+index44db133..1518c15100644+---a/path0++++b/path0+@@-1,2+1,2@@+voidfunc(){+inta=10;++Changethe5thlineofpath0++diff--gita/path0b/path0+index9ef1692..44db133100644+---a/path0++++b/path0+@@-1,2+1,2@@+voidfunc(){+inta=10;++Change2,3linesofpath0andpath1++diff--gita/path0b/path0+indexaabffdf..9ef1692100644+---a/path0++++b/path0+@@-1,2+1,2@@+voidfunc(){+-inta=0;++inta=10;++Basecommit++diff--gita/path0b/path0+newfilemode100644+index0000000..aabffdf+---/dev/null++++b/path0+@@-0,0+1,2@@++voidfunc(){++inta=0;+EOF++test_expect_success\+'validate the output.'\+'test_cmpcurrent-path0expected-path0&&+test_cmpcurrent-path1expected-path1&&+test_cmpcurrent-pathallexpected-pathall&&+test_cmpcurrent-linenumexpected-linenum&&+test_cmpcurrent-alwaysexpected-always'++test_done
@@ -0,0 +1,114 @@+#!/bin/sh+#+# Copyright (c) 2010 Bo Yang+#++test_description='Testgitlog-Lwithmergecommit++'+../test-lib.sh+."$TEST_DIRECTORY"/diff-lib.sh++echo>path0'voidfunc(){+printf("hello");+}+'++test_expect_success\+'Add path0 and commit.'\+'gitaddpath0&&+gitcommit-m"Base commit"'++echo>path0'voidfunc(){+printf("hello earth");+}+'++test_expect_success\+'Change path0 in master.'\+'gitaddpath0&&+gitcommit-m"Change path0 in master"'++test_expect_success\+'Make a new branch from the base commit'\+'git checkout -b feature master^'++echo>path0'voidfunc(){+print("hello moon");+}+'++test_expect_success\+'Change path0 in feature.'\+'gitaddpath0&&+gitcommit-m"Change path0 in feature"'++test_expect_success\+'Merge the master to feature'\+'! git merge master'++echo>path0'voidfunc(){+printf("hello earth and moon");+}+'++test_expect_success\+'Resolve the conflict'\+'gitaddpath0&&+gitcommit-m"Merge two branches"'++test_expect_success\+'Show the line level log of path0'\+'git log --pretty=format:%s%n%b -L /func/,/^}/ path0 > current'++cat>expected<<\EOF+Mergetwobranches++nontrivialmergefound+path0++@@2,1@@+printf("hello earth and moon");+++Changepath0inmaster++diff--gita/path0b/path0+indexf628dea..bef7fa3100644+---a/path0++++b/path0+@@-1,3+1,3@@+voidfunc(){+-printf("hello");++printf("hello earth");+}++Changepath0infeature++diff--gita/path0b/path0+indexf628dea..a940ef6100644+---a/path0++++b/path0+@@-1,3+1,3@@+voidfunc(){+-printf("hello");++print("hello moon");+}++Basecommit++diff--gita/path0b/path0+newfilemode100644+index0000000..f628dea+---/dev/null++++b/path0+@@-0,0+1,3@@++voidfunc(){++printf("hello");++}+EOF+test_expect_success\+'validate the output.'\+'test_cmp current expected'++test_done
+-L <start>,<end>::
+ The 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>.
++
If the parsing code for -L <start>,<end> is the same for git-blame and
for git-log, and therefore documentation is the same or nearly the
same for this option, wouldn't it be better to separate this
documentation into separate file, e.g. line-range-option.txt, and
include it both in git-blame and git-log manpages? If there are minor
differences, they can be covered by ifdefs.
--
Jakub Narebski
Poland
ShadeHawk on #git
Hi Jakub,
On Sun, Jul 11, 2010 at 4:27 PM, Jakub Narebski [off-list ref] wrote:
If the parsing code for -L <start>,<end> is the same for git-blame and
for git-log, and therefore documentation is the same or nearly the
same for this option, wouldn't it be better to separate this
documentation into separate file, e.g. line-range-option.txt, and
include it both in git-blame and git-log manpages? If there are minor
differences, they can be covered by ifdefs.
Thanks a lot for your advice, I have revised the patch.
-----------------------------------------------------------
From 88ed88a53d83c2d46fa4917008efadc531ba1068 Mon Sep 17 00:00:00 2001
From: Bo Yang <redacted>
Date: Sat, 26 Jun 2010 01:35:48 -0700
Subject: [PATCH v3 revised 13/13] some document update
Both 'git log' and 'git blame' support the same
format of '-L' arguments, so put the argument
format description into a new file.
Signed-off-by: Bo Yang <redacted>
---
Documentation/blame-options.txt | 19 +------------------
Documentation/git-log.txt | 13 +++++++++++++
Documentation/line-range-format.txt | 18 ++++++++++++++++++
3 files changed, 32 insertions(+), 18 deletions(-)
create mode 100644 Documentation/line-range-format.txt
@@ -13,24 +13,7 @@ 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).
@@ -19,6 +20,9 @@ command to control what is shown and how, and
options applicable to
the 'git diff-*' commands to control how the changes
each commit introduces are shown.
+With '-L' option, the command will help to trace the history of user specified
+line ranges. It can trace multiple ranges coming from multiple files.
+
OPTIONS
-------
@@ -72,6 +76,15 @@ include::diff-options.txt[] to be prefixed with "\-- " to separate them from options or refnames.+-L <start>,<end>::+ The line range. <start> and <end> can take one of these forms:++include::line-range-format.txt[]++--always-print::+ Always print the interesting range even if the current commit+ does not change any line of the range.+ include::rev-list-options.txt[]
diff --git a/Documentation/line-range-format.txt
b/Documentation/line-range-format.txt
new file mode 100644
index 0000000..265bc23
@@ -0,0 +1,18 @@+- 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>.++
Hi Junio,
I have make a revised version of this patch to fix a bug of it.
That's that I declare a 'unsigned long *ends;" but allocate memory
using "xmalloc(size*sizeof(int))" for it. And this new patch fix it.
-------------------------------------------------------------------------
From 7608bb8d67126b6d9d5436f00e8743ac0600db6f Mon Sep 17 00:00:00 2001
From: Bo Yang <redacted>
Date: Fri, 25 Jun 2010 19:37:32 -0700
Subject: [PATCH v3 revised 03/13] add the basic data structure for
line level history
'struct diff_line_range' is the main data structure to store
the user interesting line range. There is one 'diff_line_range'
for each file, and there are multiple 'struct range' in each
'diff_line_range'. In this way, we support multiple ranges.
Within 'struct range', there are multiple 'struct print_range'
which represent a diff chunk.
Signed-off-by: Bo Yang <redacted>
---
Makefile | 2 +
diffcore.h | 1 +
line.c | 456 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
line.h | 122 ++++++++++++++++
revision.c | 6 +
revision.h | 8 +-
6 files changed, 593 insertions(+), 2 deletions(-)
create mode 100644 line.c
create mode 100644 line.h
@@ -23,6 +23,7 @@#define MINIMUM_BREAK_SIZE 400 /* do not break a file smaller than this */structuserdiff_driver;+structdiff_options;structdiff_filespec{unsignedcharsha1[20];
@@ -0,0 +1,456 @@+#include"line.h"+#include"cache.h"+#include"tag.h"+#include"blob.h"+#include"tree.h"+#include"commit.h"+#include"diff.h"+#include"decorate.h"+#include"revision.h"+#include"xdiff-interface.h"+#include"strbuf.h"+#include"log-tree.h"++staticvoidcleanup(structdiff_line_range*r)+{+while(r){+structdiff_line_range*next=r->next;+DIFF_LINE_RANGE_CLEAR(r);+free(r);+r=next;+}+}++staticstructobject*verify_commit(structrev_info*revs)+{+structobject*commit=NULL;+constchar*name=NULL;+inti;++for(i=0;i<revs->pending.nr;i++){+structobject*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,name);+commit=obj;+name=revs->pending.objects[i].name;+}++if(commit==NULL)+die("No commit specified?");++returncommit;+}++staticvoidfill_blob_sha1(structcommit*commit,structdiff_line_range*r)+{+unsignedmode;+unsignedcharsha1[20];++while(r){+if(get_tree_entry(commit->object.sha1,r->spec->path,+sha1,&mode))+gotoerror;+fill_filespec(r->spec,sha1,mode);+r=r->next;+}++return;+error:+die("There is no path %s in the commit",r->spec->path);+}++staticvoidfill_line_ends(structdiff_filespec*spec,long*lines,+unsignedlong**line_ends)+{+intnum=0,size=50;+longcur=0;+unsignedlong*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++]=-1;+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;+}++staticconstchar*nth_line(structdiff_filespec*spec,intline,+longlines,unsignedlong*line_ends)+{+assert(line<lines);+assert(spec&&spec->data);++return(char*)spec->data+line_ends[line]+1;+}++/*+*copiedfromblame.c,indeed,wecaneventousethistotest+*whetherlinelogworks.:)+*/+staticconstchar*parse_loc(constchar*spec,structdiff_filespec*file,+longlines,unsignedlong*line_ends,+longbegin,long*ret)+{+char*term;+constchar*line;+longnum;+intreg_error;+regex_tregexp;+regmatch_tmatch[1];++/* Allow "-L <something>,+20" to mean starting at <something>+*for20lines,or"-L <something>,-5"for5linesendingat+*<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;+elseif(!num)+*ret=begin;+else+*ret=begin+num;+returnterm;+}+returnspec;+}+num=strtol(spec,&term,10);+if(term!=spec){+*ret=num;+returnterm;+}+if(spec[0]!='/')+returnspec;++/* it could be a regexp of form /.../ */+for(term=(char*)spec+1;*term&&*term!='/';term++){+if(*term=='\\')+term++;+}+if(*term!='/')+returnspec;++/* try [spec+1 .. term-1] as regexp */+*term=0;+begin--;/* input is in human terms */+line=nth_line(file,begin,lines,line_ends);++if(!(reg_error=regcomp(®exp,spec+1,REG_NEWLINE))&&+!(reg_error=regexec(®exp,line,1,match,0))){+constchar*cp=line+match[0].rm_so;+constchar*nline;++while(begin++<lines){+nline=nth_line(file,begin,lines,line_ends);+if(line<=cp&&cp<nline)+break;+line=nline;+}+*ret=begin;+regfree(®exp);+*term++='/';+returnterm;+}+else{+charerrbuf[1024];+regerror(reg_error,®exp,errbuf,1024);+die("-L parameter '%s': %s",spec+1,errbuf);+}+}++staticvoidparse_range(longlines,unsignedlong*line_ends,+structrange*r,structdiff_filespec*spec)+{+constchar*term;++term=parse_loc(r->arg,spec,lines,line_ends,1,&r->start);+if(*term==','){+term=parse_loc(term+1,spec,lines,line_ends,+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>r->end){+longtmp=r->start;+r->start=r->end;+r->end=tmp;+}++if(r->start<1)+r->start=1;+if(r->end>=lines)+r->end=lines-1;+}++staticvoidparse_lines(structcommit*commit,structdiff_line_range*r)+{+inti;+structrange*old_range=NULL;+longlines=0;+unsignedlong*ends=NULL;++while(r){+structdiff_filespec*spec=r->spec;+intnum=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);+}+}++/*+*Insertanewlinerangeintoadiff_line_rangestruct,andkeepthe+*r->rangessortedbytheirstartinglinenumber.+*/+structrange*diff_line_range_insert(structdiff_line_range*r,const
From: Junio C Hamano <hidden> Date: 2016-06-15 22:49:07
Bo Yang [off-list ref] writes:
Subject: Re: [PATCH v3 01/13] parse-options: stop when encounter a non-option
We support the syntax like:
-L n1,m1 pathspec1 -L n2,m2 pathspec2.
Make the parse-options API not stop when encounter a
non-option argument, report the status and go on parsing
the remain options.
-ECANTPARSE
Read the above again and wonder...
Does it stop, like the subject line says, or does it not stop???
Perhaps you meant to say something like this...
Subject: [PATCH v3 01/13] parse-options: enhance STOP_AT_NON_OPTION
Make parse_options_step() report PARSE_OPT_NON_OPTION, instead of
PARSE_OPT_DONE, to the caller, when it sees a non-option argument.
This will help implementing a nonstandard option syntax that takes more
than one parameters to an option, e.g.
-L n1,m1 pathspec1 -L n2,m2 pathspec2
by directly calling parse_options_step(). The parse_options() API only
calls parse_options_step() once, and its callers are not affected by
this change.
Thanks-to: Jonathan Nieder [off-list ref]
Signed-off-by: Bo Yang [off-list ref]
Currently blame and shortlog seems to use parse_options_step() but neither
of them uses STOP_AT_NON_OPTION, so this change shouldn't break them.
quoted hunk
Thanks-to: Jonathan Nieder [off-list ref]
Signed-off-by: Bo Yang <redacted>
---
parse-options.c | 3 ++-
parse-options.h | 3 ++-
2 files changed, 4 insertions(+), 2 deletions(-)
From: Junio C Hamano <hidden> Date: 2016-06-15 22:49:07
Bo Yang [off-list ref] writes:
Subject: [PATCH v3 revised 03/13] add the basic data structure for line level history
'struct diff_line_range' is the main data structure to store
the user interesting line range. There is one 'diff_line_range'
-ECANTPARSE. Perhaps "to keep track of the line ranges the user is
interested in"?
Is it the end user, or the code, that is interested in the range recorded
in this structure? If you are adjusting the line range while traversing
the history, then it is more of the latter; i.e. "the user originally
started digging from this range, but after examining the diff that affect
that range by this commit, we found that the range corresponds to this
widened/narrowed range in the history leading to the commit, so we will
update the range and use it from now on until we hit another commit that
affects this range". In that case "to keep track of the line ranges we
are currently interested in" would be more appropriate than "the ranges
the user is interested in".
for each file, and there are multiple 'struct range' in each
'diff_line_range'. In this way, we support multiple ranges.
Within 'struct range', there are multiple 'struct print_range'
which represent a diff chunk.
Why not use "int found = -1" instead and point at the commit in the
pending.objects[] array you found with that variable? Or use a variable
of the type of a pointer to an element in the pending.objects[] array,
initialized to NULL.
That way, you do not have to tell stupid compilers that name is set once
commit is set to avoid warnings, which is the only reason you initialize
name to NULL.
+ 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, name);
+ commit = obj;
+ name = revs->pending.objects[i].name;
+ }
+
+ 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);
+}
These two look vaguely familiar... Cut and paste without refactoring?
+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++] = -1;
Wasn't this an array of unsigned longs?
+static const char *nth_line(struct diff_filespec *spec, int line,
+ long lines, unsigned long *line_ends)
+{
+ assert(line < lines);
+ assert(spec && spec->data);
Why aren't "line" and "lines" of the same type?
+static void parse_range(long lines, unsigned long *line_ends,
+ struct range *r, struct diff_filespec *spec)
+{
+ const char *term;
+
+ term = parse_loc(r->arg, spec, lines, line_ends, 1, &r->start);
+ if (*term == ',') {
+ term = parse_loc(term + 1, spec, lines, line_ends,
+ r->start + 1, &r->end);
+ if (*term) {
+ die("-L parameter's argument should be <start>,<end>");
+ }
Excess {} around a single statement.
+struct range *diff_line_range_insert(struct diff_line_range *r, const
char *arg,
+ int start, int end)
+{
+ int i = 0;
+ struct range *rs = r->ranges;
+ int left_extend = 0, right_extend = 0;
+
+ assert(r != NULL);
+ assert(start <= end);
+
+ if (r->nr == 0 || rs[r->nr - 1].end < start - 1) {
+ DIFF_LINE_RANGE_GROW(r);
+ rs = r->ranges;
+ int num = r->nr - 1;
decl-after-statement
...
+out:
+ assert(r->nr != i);
+ if (left_extend) {
+ ALLOC_GROW((p)->ranges, (p)->nr, (p)->alloc); \
+ } while (0);
+
+#define PRINT_PAIR_CLEAR(p) \
+ do { \
+ (p)->alloc = (p)->nr = 0; \
+ if ((p)->ranges) \
+ free((p)->ranges); \
+ (p)->ranges = NULL; \
+ } while(0);
+
+struct range {
+ const char *arg; //The argument to specify this line range
No C++/C99 comments.
Do you need to keep track of this string representation once you have
parsed the user input and your main computation have started?
Isn't "range" too generic a term? Unless you make this as a static
declaration only visible to functions where "range" can only mean "line
ranges" in their context, that is.
+#define RANGE_INIT(r) \
Same comment for the name possibly being too generic.
+extern struct range *diff_line_range_insert(struct diff_line_range
*r, const char *arg,
From: Junio C Hamano <hidden> Date: 2016-06-15 22:49:07
Bo Yang [off-list ref] writes:
Both 'git blame -L' and 'git log -L' parse the same style
of line number arguments, so put the 'parse_loc' function
to line.c and export it.
The caller of parse_loc should provide a callback function
which is used to calculate the nth line start position.
"the start position of the nth line"?
quoted hunk
Other parts such as regexp search, line number parsing are
abstracted and re-used.
Signed-off-by: Bo Yang <redacted>
---
builtin/blame.c | 89 +++++-------------------------------------------------
line.c | 35 ++++++++++++---------
line.h | 5 +++
3 files changed, 34 insertions(+), 95 deletions(-)
From: Junio C Hamano <hidden> Date: 2016-06-15 22:49:07
Bo Yang [off-list ref] writes:
Both single range clone and deeply clone are supported.
Please explain what you mean by "deeply clone" (no, not just a language
nitpick that it should at least be "deep clone"), and document which one
needs to be used in what situations to help people who would want to
decide which of these functions to call.
What I am trying to get at is that you might want to have only one kind of
clone that is _semantically_ deep (i.e. the list is copied, but the
elements are shared with refcounting), but makes a copy of the element
that is being updated on demand (i.e. have an API function to allow users
to modify, which internally does copy-on-write if it is still shared). I
don't know that kind of arrangement makes sense; it depends on how the
cloned ranges are used but I haven't looked at the callers.
From: Junio C Hamano <hidden> Date: 2016-06-15 22:49:07
Bo Yang [off-list ref] writes:
The interesting line range will be passed to all of its parents.
This needs too much context to understand what you are saying, doesn't it?
What is "it" (presumably "a commit" but that is merely a guess from the
use of word "parents" and in git the only thing that has parents are
commits)? In what context is this "passing" happen (presumably while
digging the history with specific line ranges in mind, but the Subject
line does not even give any useful hint)?
For non-merge commit, we just map_range the ranges. Generally, the
algorithm do:
s/do//;
1. Run a diffcore_std to find out the pairs;
s/Run/Runs/; ( won't do any more language nitpicks from now on in this
message.
quoted hunk
2. Run a xdi_diff_hunks on each interesting file pair;
3. The map_range_cb callback will be invoked for each diff hunk,
and in the function map_lines we will calculate the pre-image
range from the post-image range.
For merge commit, another take_range pass will be done except the
above normal map_range work. It is used to subtract each same
part lines of the current range out. After this pass, if there is
any line range left, this means the merge must be a non-trivial
merge. This is how the non-trivial merge detect work.
The algorithm that map lines from post-image to pre-image is in
the function map_lines. Generally, we use simple line number
calculation method to do the map.
Signed-off-by: Bo Yang <redacted>
---
line.c | 453 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
revision.h | 5 +-
2 files changed, 457 insertions(+), 1 deletions(-)
+ int diff;
+};
+
+#define SCALE_FACTOR 4
+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)
What does "p", "t", "o" and "" stand for?
+{
+ /*
+ * xdiff always assign 'same' with the last line number of two
+ * same ranges. When some lines are added from scratch,
+ * p_start = same + 1;
+ * p_end = same;
+ * so, we can understand the following condition.
Not me, sorry.
+ */
+ 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;
+ }
+
+ /*
+ * A new strategy for lines mapping:
New compared to which old one? I think s/strategy/heuristic/ might be a
better wording here.
+ * When the pre-image is no more than 1/4 of the post-image, it
+ * take no sense to say what the real pre-image is, wan can just
+ * treat all the post-image lines as added from scratch.
What kind of weird result would you get if you do not use this heuristic?
I am guessing the rationale is something like "If we consider too small
contributions from the preimage in a hunk, we end up splitting the hunk
into very many smaller pieces and will keep digging into older commits.
The result will show commits that add a line here and another line there
without a real impact to the resulting logic. It is easier to read the
logic if we stop traversal at a commit that adds large material relative
to the original to a hunk." but it is unclear.
+ */
+ if (SCALE_FACTOR * (p_end - p_start + 1) < (t_end - t_start + 1)) {
+ *o_start = *o_end = 0;
+ return;
+ }
...
+static void map_range(struct take_range_cb_data *data, int same,
+ long p_start, long p_end, long t_start, long t_end)
+{
...
+ if (added) {
+ /* Code movement/copy detect here */
This probably will not even compile.
+ } else {
...
+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, this may confuse take_range */
+ if (t_end >= t_start)
+ take_range(d, p_start, p_end, t_start, t_end);
Is this a "FIXME" comment, or is it justifying the if statement?
+ /*
+ * 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, 0x7FFFFFFF, cb.tlno + 1, 0x7FFFFFFF);
+ else
+ take_range(&cb, cb.plno + 1, 0x7FFFFFFF, cb.tlno + 1, 0x7FFFFFFF);
+ }
Do you mean INT_MAX or something (I didn't bother to check the type you
are using)?
As there is no good explanation of parameters to map/take_range(), it is
hard to see if an arbitrary large number like this is correct, and if it
were correct, then perhaps it means the *_end paramters are not useful to
these functions? I dunno.
From: Junio C Hamano <hidden> Date: 2016-06-15 22:49:07
Bo Yang [off-list ref] writes:
Since ranges may change in different branches, we should
make sure we do not pass range to parent until all the
ranges get 'combined' at the commit which is a split commit.
So, topological traversing is necessary.
Without explaining what a "split commit" is, "So, ... is necessary"
doesn't really justify this.
@@ -1196,3 +1196,57 @@ static void line_log_flush(struct rev_info *rev, struct commit *c)}}+intcmd_line_log_walk(structrev_info*rev)+{+structcommit*commit;+structcommit_list*list=NULL;+structdiff_line_range*r=NULL;++if(prepare_revision_walk(rev))+die("revision walk prepare failed");++list=rev->commits;+if(list){+list->item->object.flags|=RANGE_UPDATE;+list=list->next;+}+/* Clear the flags */+while(list){+list->item->object.flags&=0x0;
Just assign "= 0" instead, please.
But more importantly, why is it safe to clear all the flags? Even if your
traversal is limited (i.e. prepare_revision_walk() already walked
everything), at least wouldn't you need to keep flags like SYMMETRIC_LEFT,
UNINTERESTING, BOUNDARY etc. depending on what the end user asked from the
command line?
If you ask prepare_revison_walk()->limit_list() callchain to run a
limited and topo-sorted traversal, wouldn't the resulting revs->list
have commits in the order you need already? Why do you need to have a
custom walker here?
I am not suggesting to roll this logic into prepare_revision_walk() and
limit_list(); there are a lot more than "we need topological order" going
on in this function, and the log message needs to explain what they are.
Is this really a traversal flag that affects how the history is walked?
Hmm, a 'line' means topologically traverse at least. So, I added it
here. And I can't find a better place to put it. :)
I have changed my code according your comments, thanks a lot!
--
Regards!
Bo
----------------------------
My blog: http://blog.morebits.org
Why Git: http://www.whygitisbetterthanx.com/