#!/usr/bin/ruby # vim: et sw=2 VER="(985514d)" ME="bldiff: " $col="1;36" $cadd="1;32" $cdel="1;31" $shadd=false $shdel=false $noctx=false $rev=nil $opt=nil $incl=[] $excl=[] def in_list(s,list) for i in list if i.match(s) return true end end return false end def regexfix2(str) tmp=[] for t in str.scan(/(_)|(\|)|([^_\|]*)/).flatten.compact tmp.push(t) if not t.empty? end res="" while t=tmp.shift case t when("_") then res+=(tmp.empty? or tmp[0]=="|") ? '([ ."\[\]\(\)\{\}]+|$)' : '[ ."\[\]\(\)\{\}]+' when("|") then res+="|" else res+="#{t}" end end return res end def repo_type parr=Dir.pwd.split("/") while true return nil if parr.length<2 if parr[1]=="dev" or parr[1]=="cygdrive" return nil if parr.length<4 end p=parr.join("/") parr.pop return "svn" if File.exist?(p+"/.svn") return "dot" if File.exist?(p+"/.dot") end return nil end def ex_cat(fn) if $type=="svn" if $opt=="-c" rev=($rev.to_i)-1 return IO.popen("svn cat -r #{rev} #{fn.dump}") end if $opt=="-r" rev=$rev.split(":")[0].to_i return IO.popen("svn cat -r #{rev} #{fn.dump}") end return IO.popen("svn cat #{fn.dump}") end if $type=="dot" if $opt=="-c" rev=($rev.to_i)-1 return nil if rev==0 return IO.popen("dot cat -r #{rev} #{fn.dump}") end if $opt=="-r" return IO.popen("dot cat -r #{$rev} #{fn.dump}") end if $opt=="-s" return IO.popen("dot cat -s #{fn.dump}") end if $opt=="--stage" return IO.popen("dot cat #{fn.dump}") end return IO.popen("dot cat #{fn.dump}") end $stderr.puts ME+"unknown repository type" exit end def ex_diff(fn) if $type=="svn" if $opt=="-c" return IO.popen("svn diff -c #{$rev} #{fn.dump}") end if $opt=="-r" return IO.popen("svn diff -r #{$rev} #{fn.dump}") end return IO.popen("svn diff #{fn.dump}") end if $type=="dot" if $opt=="-c" return IO.popen("dot diff -c #{$rev} #{fn.dump}") end if $opt=="-r" return IO.popen("dot diff -r #{$rev} #{fn.dump}") end if $opt=="-s" return IO.popen("dot diff -s #{fn.dump}") end if $opt=="--stage" return IO.popen("dot diff --stage #{fn.dump}") end return IO.popen("dot diff #{fn.dump}") end $stderr.puts ME+"unknown repository type" exit end $output_cnt=0 class Block def initialize(_hdr,_fld,_sep,_bi) @lines=[] @lsts=[] @hdr=_hdr @hdr2=nil @fld=_fld @sep=_sep @hst=" " @hst2=nil @bi=_bi @link=nil @diff=false @dadd=false @ddel=false @moved=false end def hdr return @hdr end def hdr2 return @hdr2 end def fld=(_fld) @fld=_fld end def sep=(_sep) @sep=_sep end def op return 1 if @hst=="-" and !@hst2 return 2 if @hst=="+" and !@hst2 return 3 if @hst=="-" and @hst2=="+" return 0 end def st return @hst end def st=(_st) @hst=_st end def st2(_hdr2,_st2) @hdr2=_hdr2 @hst2=_st2 end def bi return @bi end def link return @link end def link=(_link) @link=_link end def moved @moved=true end def header?(s) return (@hdr==s or @fld==s or @sep==s) ? true : false end def upd_ctx(ctx,fbuf,m) tmp=ctx.first tctx=ctx.slice(1,ctx.length) di=i=(tmp[1].to_i)-1 bi=@bi+2 s=tctx.first if m[4].match(s) fi=nil f=false i=di while i>bi and not m[1].match(fbuf[i]) fi=i if not m[4].match(fbuf[i]) f=true if s==fbuf[i] break if fi and f break if not m[4].match(fbuf[i]) i-=1 end if f and fi and not include?(fbuf[fi]) addline(fbuf[fi]," ") end end for x in tctx addline(x," ") if not header?(x) end end def addline(l,_st) @diff=true if _st!=" " @dadd=true if _st=="+" @ddel=true if _st=="-" @lines.push(l) @lsts.push(_st) end def length return @lines.length end def last return @lines.last end def include?(l) return @lines.include?(l) end def diff? return (@diff or @hst!=" ") ? true : false end def deleted? return (@hst=="-" and !@hst2) ? true : false end def output(out) if not $incl.empty?() ok=false ok=true if in_list(@hdr,$incl) ok=true if @hdr2 and in_list(@hdr2,$incl) return if not ok end return if in_list(@hdr,$excl) return if @hdr2 and in_list(@hdr2,$excl) validate if @moved return if not diff? normalize if $noctx or (@link and not @moved and (op==0 or op==3)) if $shadd return if not (@dadd or @hst=="+") if $istty c=$col if @hdr2 out.print "\e[#{c}m",@hdr2,"\e[m\n" else out.print "\e[#{c}m",@hdr,"\e[m\n" end out.puts @fld,@sep else out.puts "\n" if $output_cnt>0 if @hdr2 out.puts @hdr2 else out.puts @hdr end out.puts @fld,@sep end b=nil getline do |l,st| b=l if st==" " and !/^\t|^ /.match(l) if st=="+" out.puts b if /^\t|^ /.match(l) and b out.puts l b=nil end end out.puts @sep $output_cnt+=1 return end if $shdel return if not (@ddel or @hst=="-") if $istty c=$col if @hdr2 out.print "\e[#{c}m",@hdr2,"\e[m\n" else out.print "\e[#{c}m",@hdr,"\e[m\n" end out.puts @fld,@sep else out.puts "\n" if $output_cnt>0 if @hdr2 out.puts @hdr2 else out.puts @hdr end out.puts @fld,@sep end b=nil getline do |l,st| b=l if st==" " and !/^\t|^ /.match(l) if st=="-" out.puts b if /^\t|^ /.match(l) and b out.puts l b=nil end end out.puts @sep $output_cnt+=1 return end if $istty c=$col c=$cadd if @hst=="+" c=$cdel if @hst=="-" out.print "\e[#{c}m",@hdr,"\e[m\n" if @hdr2 c=nil c=$cadd if @hst2=="+" c=$cdel if @hst2=="-" out.print "\e[#{c}m",@hdr2,"\e[m\n" end out.puts @fld,@sep if @diff or @hst=="+" if $noctx getline do |l,st| b=l if st==" " and !/^\t|^ /.match(l) if st!=" " c=nil c=$cadd if st=="+" c=$cdel if st=="-" out.print "\e[m",spc(b),"\e[m\n" if /^\t|^ /.match(l) and b out.print "\e[#{c}m",spc(l),"\e[m\n" b=nil end end else getline do |l,st| c=nil c=$cadd if st=="+" c=$cdel if st=="-" out.print "\e[#{c}m",l,"\e[m\n" end end end out.puts @sep return end out.puts "\n" if $output_cnt>0 out.print @hst,@hdr,"\n" if @hdr2 out.print @hst2,@hdr2,"\n" end out.print " ",spc(@fld),"\n" out.print " ",@sep,"\n" if @diff or @hst=="+" if $noctx getline do |l,st| b=l if st==" " and !/^\t|^ /.match(l) if st!=" " out.print " ",spc(b),"\n" if /^\t|^ /.match(l) and b out.print st,spc(l),"\n" b=nil end end else getline { |l,st| out.print st,spc(l),"\n" } end end out.print " ",@sep,"\n" $output_cnt+=1 end private def getline @lines.length.times { |i| yield @lines[i],@lsts[i] } end def validate l1=[] l2=[] getline do |l,st| return if st==" " l1.push(l) if st=="-" l2.push(l) if st=="+" end return if l1.length!=l2.length l1.length.times do |i| return if l1[i]!=l2[i] end @diff=false @dadd=false @ddel=false end def normalize lines=[] lsts=[] c=@lines.length i=0 while ii and @lsts[i]!=" " and @lsts[j]!=" " and @lsts[j]!=@lsts[i] @lines.delete_at(j) @lsts.delete_at(j) @lines.delete_at(i) @lsts.delete_at(i) c-=2 next end end i+=1 end end def spc(s) tmp=s.split(/\t+/) tmp2=s.scan(/[^\t]*\t*/) ret="" tmp.length.times do |i| t=tmp2[i].count("\t")*8-(tmp[i].length%8) if t>0 ret+=tmp[i]+" "*t else ret+=tmp[i] end end return ret end end def usage me=ME.delete(":") puts "Usage: "+me+"[-c | -r ] " puts " -a - show only additions (no markers)" puts " -d - show only deletions (no markers)" puts " -q - show only changes (no context)" puts " -c - display changeset from revision " puts " -r - compare revision to wcopy" puts " -r : - compare revision to (SVN)" puts " -s - compare stage to wcopy (DOT)" puts " --stage - compare HEAD to stage (DOT)" puts " -B - include blocks matching RegEx " puts " -S - skip blocks matching RegEx " exit end def parse_opts(opts) i=0 while i=ARGV.length usage end fn2=nil fn=ARGV[i] fn2=ARGV[i+1] if ARGV[i+1] and not ARGV[i+1].empty? if fn2 $type="file" else $type=repo_type end if !$type $stderr.puts ME+"not in repository" exit end if $type=="dot" if $opt=="-r" and $rev.include?(":") $stderr.puts ME+"rev to rev diff not supported" exit end end fbuf=[] if fn2 begin f=File.open(fn,"r") rescue $stderr.puts ME+"unable to open file: "+fn exit end else f=ex_cat(fn) end if f for l in f l.chop! fbuf.push(l) end f.close end dbuf=[] if fn2 f=IO.popen("diff -u #{fn.dump} #{fn2.dump}") else f=ex_diff(fn) end for l in f l.chop! dbuf.push(l) end f.close def get_bl(ctx,fbuf,m,blocks) if ctx.empty? $stderr.puts ME+"panic: context empty" exit end tmp=ctx.first tctx=ctx.slice(1,ctx.length) di=i=(tmp[1].to_i)-1 hdr=fld=sep=nil bi=nil tctx.length.times do |t| return nil if fbuf[di+t]!=tctx[t] end while i>1 if m[0].match(fbuf[i]) hdr=fbuf[i] fld=fbuf[i+1] sep=fbuf[i+2] bi=i break end if m[1].match(fbuf[i]) if m[0].match(fbuf[i-2]) hdr=fbuf[i-2] fld=fbuf[i-1] sep=fbuf[i] bi=i-2 end break end i-=1 end return nil if !bi bl=nil for b in blocks.reverse next if b.bi!=bi if b.hdr==hdr or b.hdr2==hdr bl=b break end end if !bl bl=Block.new(hdr,fld,sep,bi) blocks.push(bl) end return bl end def get_bl_data(di,fbuf,m,ei=nil) i=di+1 while !m[1].match(fbuf[i]) and (!ei or i<=ei) yield fbuf[i] i+=1 end end blocks=[] ctx=[] ctf=0 cbl=bl1=bl2=nil di=nil bi=nil inbl=false bchg=false hdr=hdr2=fld=sep=nil hst=hst2=nil lst=nil m=[] m[0]=Regexp.new("^#") m[1]=Regexp.new("^--+") m[2]=Regexp.new("^\t") m[3]=Regexp.new("^ ") m[4]=Regexp.new("^\t|^ ") for l in dbuf if tmp=/^@@ -([0-9]*),[0-9]* \+([0-9]*),[0-9]* @@$/.match(l) ti=(tmp[1].to_i)-2 if bchg and bl1 and bl2 # update context of block merge/split get_bl_data(di,fbuf,m,ti) do |s| bl1.addline(s,"-") bl2.addline(s,"+") end end cbl=bl1=bl2=nil di=ti bi=nil inbl=false bchg=false hdr=hdr2=fld=sep=nil lst=nil ctx=[] ctx.push(tmp) ctf=1 next end next if ctf==0 st=l.slice(0,1) s=l.slice(1,l.length) di+=1 if st==" " or st=="-" ctf=4 if ctf==1 and m[0].match(s) if ctf==1 and st==" " and (!m[1].match(s) or m[0].match(fbuf[di-2])) ctx.push(s) next end next if s.empty? if ctf==1 inbl=true if cbl=get_bl(ctx,fbuf,m,blocks) ctf=2 end if not inbl and !hdr and m[0].match(s) hdr=s hst=st bi=di next end if not inbl and hdr # got block header if m[0].match(s) hdr2=s hst2=st next end if !fld or !m[1].match(s) fld=s next end sep=s if hst==" " # noop block bl1=bl2=Block.new(hdr,fld,sep,bi) blocks.push(bl1) inbl=true bchg=false next end if hst=="-" # del block bl=nil for b in blocks.reverse if b.hdr==hdr bl=b break end end if bl and bl.op==2 and bl.st!=hst # moved block (should be rare) bl1=bl bl1.st=" " bl1.moved if hdr2 bl2=Block.new(hdr2,fld,sep,bi) bl2.st=hst2 blocks.push(bl2) end inbl=true next end # new deleted block bl1=Block.new(hdr,fld,sep,bi) bl1.st="-" # check for rename if hdr2 bl=nil for b in blocks.reverse if b.hdr==hdr2 bl=b break end end if bl and bl.op==2 and bl.st!=hst2 # moved block (should be rare) bl2=bl bl2.st=" " bl2.moved else # renamed block bl2=bl1 bl2.st2(hdr2,hst2) end blocks.push(bl1) inbl=true next end if lst=="-" # block merge for b in blocks.reverse if b.op!=1 bl2=b bl1.link=bl2 bl2.link=bl1 bchg=true break end end end blocks.push(bl1) inbl=true next end if hst=="+" # add block bl=nil for b in blocks.reverse if b.hdr==hdr bl=b break end end if bl and bl.op==1 and bl.st!=hst # moved block (should be rare) bl2=bl bl2.st=" " bl2.moved inbl=true next end # new added block bl2=Block.new(hdr,fld,sep,bi) bl2.st="+" if lst=="+" # block split for b in blocks.reverse if b.op==0 or b.op==3 bl1=b bl1.link=bl2 bl2.link=bl1 bchg=true break end end end blocks.push(bl2) inbl=true next end next end next if not inbl # in block code lst=nil if st==" " # noop line if !bl2 and cbl if cbl.op==3 or cbl.op==0 # check for split if cbl.link bl1=cbl bl2=cbl.link bchg=true else bl2=cbl end end if cbl.op==1 and cbl.link # deleted block, check for merge bl1=cbl bl2=cbl.link bchg=true end if bchg and bl1 and bl2 # update context of block merge/split for x in ctx.slice(1,ctx.length) bl1.addline(x,"-") bl2.addline(x,"+") end ctf=3 end if bl2 and ctf==2 bl2.upd_ctx(ctx,fbuf,m) ctf=3 end end if m[1].match(s) cbl=bl1=bl2=nil lst=st inbl=false bchg=false hdr=hdr2=fld=sep=nil next end if bchg and bl1 and bl2 bl1.addline(s,"-") bl2.addline(s,"+") next end bl2.addline(s," ") if bl2 next end if st=="-" # del line if !bl1 and cbl if cbl.op==3 or cbl.op==0 # check for split if cbl.link bl1=cbl bl2=cbl.link bchg=true else bl1=bl2=cbl end end # deleted block, check for merge if cbl.op==1 bl1=cbl if cbl.link bl2=cbl.link bchg=true end end if bchg and bl1 and bl2 # update context of block merge/split for x in ctx.slice(1,ctx.length) bl1.addline(x,"-") bl2.addline(x,"+") end ctf=3 end if bl1 and ctf==2 bl1.upd_ctx(ctx,fbuf,m) ctf=3 end end if m[1].match(s) # possible block merge bl1=nil lst=st inbl=false hdr=hdr2=fld=sep=nil next end bl1.addline(s,st) if bl1 next end if st=="+" # add line if !bl2 and cbl if cbl.op==3 or cbl.op==0 # check for split if cbl.link bl1=cbl bl2=cbl.link bchg=true else bl2=cbl end end if cbl.op==1 and cbl.link # deleted block, check for merge bl1=cbl bl2=cbl.link bchg=true end if bchg and bl1 and bl2 # update context of block merge/split for x in ctx.slice(1,ctx.length) bl1.addline(x,"-") bl2.addline(x,"+") end ctf=3 end if bl2 and ctf==2 bl2.upd_ctx(ctx,fbuf,m) ctf=3 end end if m[1].match(s) # possible block split bl2=nil lst=st inbl=false hdr=hdr2=fld=sep=nil next end bl2.addline(s,st) if bl2 next end end if bchg and bl1 and bl2 # update context of block merge/split get_bl_data(di,fbuf,m) do |s| bl1.addline(s,"-") bl2.addline(s,"+") end end exit if blocks.empty? if $istty out=IO.popen("less -fqRFX","w") else out=$stdout end begin blocks.each { |b| b.output(out) } rescue Errno::EPIPE end out.close if out!=$stdout