proc makewindow {} {
global canv canv2 canv3 linespc charspc ctext cflist textfont
- global findtype findloc findstring fstring geometry
+ global findtype findtypemenu findloc findstring fstring geometry
global entries sha1entry sha1string sha1but
global maincursor textcursor
global rowctxmenu
entry $fstring -width 30 -font $textfont -textvariable findstring
pack $fstring -side left -expand 1 -fill x
set findtype Exact
- tk_optionMenu .ctop.top.bar.findtype findtype Exact IgnCase Regexp
+ set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
+ findtype Exact IgnCase Regexp]
set findloc "All fields"
tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
- Comments Author Committer
+ Comments Author Committer Files Pickaxe
pack .ctop.top.bar.findloc -side right
pack .ctop.top.bar.findtype -side right
+ # for making sure type==Exact whenever loc==Pickaxe
+ trace add variable findloc write findlocchange
panedwindow .ctop.cdet -orient horizontal
.ctop add .ctop.cdet
bindkey b "$ctext yview scroll -1 pages"
bindkey d "$ctext yview scroll 18 units"
bindkey u "$ctext yview scroll -18 units"
- bindkey / findnext
+ bindkey / {findnext 1}
+ bindkey <Key-Return> {findnext 0}
bindkey ? findprev
bindkey f nextfile
bind . <Control-q> doquit
bind . <Control-f> dofind
- bind . <Control-g> findnext
+ bind . <Control-g> {findnext 0}
bind . <Control-r> findprev
bind . <Control-equal> {incrfont 1}
bind . <Control-KP_Add> {incrfont 1}
-command {diffvssel 1}
$rowctxmenu add command -label "Make patch" -command mkpatch
$rowctxmenu add command -label "Create tag" -command mktag
+ $rowctxmenu add command -label "Write commit to file" -command writecommit
}
# when we make a key binding for the toplevel, make sure
global numcommits lineid linehtag linentag linedtag
global mainfont namefont canv canv2 canv3 selectedline
global matchinglines foundstring foundstrlen
+
+ stopfindproc
unmarkmatches
focus .
set matchinglines {}
- set fldtypes {Headline Author Date Committer CDate Comment}
+ if {$findloc == "Pickaxe"} {
+ findpatches
+ return
+ }
if {$findtype == "IgnCase"} {
set foundstring [string tolower $findstring]
} else {
}
set foundstrlen [string length $findstring]
if {$foundstrlen == 0} return
+ if {$findloc == "Files"} {
+ findfiles
+ return
+ }
if {![info exists selectedline]} {
set oldsel -1
} else {
set oldsel $selectedline
}
set didsel 0
+ set fldtypes {Headline Author Date Committer CDate Comment}
for {set l 0} {$l < $numcommits} {incr l} {
set id $lineid($l)
set info $commitinfo($id)
}
}
-proc findnext {} {
+proc findnext {restart} {
global matchinglines selectedline
if {![info exists matchinglines]} {
- dofind
+ if {$restart} {
+ dofind
+ }
return
}
if {![info exists selectedline]} return
}
}
+proc findlocchange {name ix op} {
+ global findloc findtype findtypemenu
+ if {$findloc == "Pickaxe"} {
+ set findtype Exact
+ set state disabled
+ } else {
+ set state normal
+ }
+ $findtypemenu entryconf 1 -state $state
+ $findtypemenu entryconf 2 -state $state
+}
+
+proc stopfindproc {{done 0}} {
+ global findprocpid findprocfile findids
+ global ctext findoldcursor phase maincursor textcursor
+ global findinprogress
+
+ catch {unset findids}
+ if {[info exists findprocpid]} {
+ if {!$done} {
+ catch {exec kill $findprocpid}
+ }
+ catch {close $findprocfile}
+ unset findprocpid
+ }
+ if {[info exists findinprogress]} {
+ unset findinprogress
+ if {$phase != "incrdraw"} {
+ . config -cursor $maincursor
+ $ctext config -cursor $textcursor
+ }
+ }
+}
+
+proc findpatches {} {
+ global findstring selectedline numcommits
+ global findprocpid findprocfile
+ global finddidsel ctext lineid findinprogress
+ global findinsertpos
+
+ if {$numcommits == 0} return
+
+ # make a list of all the ids to search, starting at the one
+ # after the selected line (if any)
+ if {[info exists selectedline]} {
+ set l $selectedline
+ } else {
+ set l -1
+ }
+ set inputids {}
+ for {set i 0} {$i < $numcommits} {incr i} {
+ if {[incr l] >= $numcommits} {
+ set l 0
+ }
+ append inputids $lineid($l) "\n"
+ }
+
+ if {[catch {
+ set f [open [list | git-diff-tree --stdin -s -r -S$findstring \
+ << $inputids] r]
+ } err]} {
+ error_popup "Error starting search process: $err"
+ return
+ }
+
+ set findinsertpos end
+ set findprocfile $f
+ set findprocpid [pid $f]
+ fconfigure $f -blocking 0
+ fileevent $f readable readfindproc
+ set finddidsel 0
+ . config -cursor watch
+ $ctext config -cursor watch
+ set findinprogress 1
+}
+
+proc readfindproc {} {
+ global findprocfile finddidsel
+ global idline matchinglines findinsertpos
+
+ set n [gets $findprocfile line]
+ if {$n < 0} {
+ if {[eof $findprocfile]} {
+ stopfindproc 1
+ if {!$finddidsel} {
+ bell
+ }
+ }
+ return
+ }
+ if {![regexp {^[0-9a-f]{40}} $line id]} {
+ error_popup "Can't parse git-diff-tree output: $line"
+ stopfindproc
+ return
+ }
+ if {![info exists idline($id)]} {
+ puts stderr "spurious id: $id"
+ return
+ }
+ set l $idline($id)
+ insertmatch $l $id
+}
+
+proc insertmatch {l id} {
+ global matchinglines findinsertpos finddidsel
+
+ if {$findinsertpos == "end"} {
+ if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
+ set matchinglines [linsert $matchinglines 0 $l]
+ set findinsertpos 1
+ } else {
+ lappend matchinglines $l
+ }
+ } else {
+ set matchinglines [linsert $matchinglines $findinsertpos $l]
+ incr findinsertpos
+ }
+ markheadline $l $id
+ if {!$finddidsel} {
+ findselectline $l
+ set finddidsel 1
+ }
+}
+
+proc findfiles {} {
+ global selectedline numcommits lineid ctext
+ global ffileline finddidsel parents nparents
+ global findinprogress findstartline findinsertpos
+ global treediffs fdiffids fdiffsneeded fdiffpos
+ global findmergefiles
+
+ if {$numcommits == 0} return
+
+ if {[info exists selectedline]} {
+ set l [expr {$selectedline + 1}]
+ } else {
+ set l 0
+ }
+ set ffileline $l
+ set findstartline $l
+ set diffsneeded {}
+ set fdiffsneeded {}
+ while 1 {
+ set id $lineid($l)
+ if {$findmergefiles || $nparents($id) == 1} {
+ foreach p $parents($id) {
+ if {![info exists treediffs([list $id $p])]} {
+ append diffsneeded "$id $p\n"
+ lappend fdiffsneeded [list $id $p]
+ }
+ }
+ }
+ if {[incr l] >= $numcommits} {
+ set l 0
+ }
+ if {$l == $findstartline} break
+ }
+
+ # start off a git-diff-tree process if needed
+ if {$diffsneeded ne {}} {
+ if {[catch {
+ set df [open [list | git-diff-tree -r --stdin << $diffsneeded] r]
+ } err ]} {
+ error_popup "Error starting search process: $err"
+ return
+ }
+ catch {unset fdiffids}
+ set fdiffpos 0
+ fconfigure $df -blocking 0
+ fileevent $df readable [list readfilediffs $df]
+ }
+
+ set finddidsel 0
+ set findinsertpos end
+ set id $lineid($l)
+ set p [lindex $parents($id) 0]
+ . config -cursor watch
+ $ctext config -cursor watch
+ set findinprogress 1
+ findcont [list $id $p]
+ update
+}
+
+proc readfilediffs {df} {
+ global findids fdiffids fdiffs
+
+ set n [gets $df line]
+ if {$n < 0} {
+ if {[eof $df]} {
+ donefilediff
+ if {[catch {close $df} err]} {
+ stopfindproc
+ bell
+ error_popup "Error in git-diff-tree: $err"
+ } elseif {[info exists findids]} {
+ set ids $findids
+ stopfindproc
+ bell
+ error_popup "Couldn't find diffs for {$ids}"
+ }
+ }
+ return
+ }
+ if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} {
+ # start of a new string of diffs
+ donefilediff
+ set fdiffids [list $id $p]
+ set fdiffs {}
+ } elseif {[string match ":*" $line]} {
+ lappend fdiffs [lindex $line 5]
+ }
+}
+
+proc donefilediff {} {
+ global fdiffids fdiffs treediffs findids
+ global fdiffsneeded fdiffpos
+
+ if {[info exists fdiffids]} {
+ while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
+ && $fdiffpos < [llength $fdiffsneeded]} {
+ # git-diff-tree doesn't output anything for a commit
+ # which doesn't change anything
+ set nullids [lindex $fdiffsneeded $fdiffpos]
+ set treediffs($nullids) {}
+ if {[info exists findids] && $nullids eq $findids} {
+ unset findids
+ findcont $nullids
+ }
+ incr fdiffpos
+ }
+ incr fdiffpos
+
+ if {![info exists treediffs($fdiffids)]} {
+ set treediffs($fdiffids) $fdiffs
+ }
+ if {[info exists findids] && $fdiffids eq $findids} {
+ unset findids
+ findcont $fdiffids
+ }
+ }
+}
+
+proc findcont {ids} {
+ global findids treediffs parents nparents treepending
+ global ffileline findstartline finddidsel
+ global lineid numcommits matchinglines findinprogress
+ global findmergefiles
+
+ set id [lindex $ids 0]
+ set p [lindex $ids 1]
+ set pi [lsearch -exact $parents($id) $p]
+ set l $ffileline
+ while 1 {
+ if {$findmergefiles || $nparents($id) == 1} {
+ if {![info exists treediffs($ids)]} {
+ set findids $ids
+ set ffileline $l
+ return
+ }
+ set doesmatch 0
+ foreach f $treediffs($ids) {
+ set x [findmatches $f]
+ if {$x != {}} {
+ set doesmatch 1
+ break
+ }
+ }
+ if {$doesmatch} {
+ insertmatch $l $id
+ set pi $nparents($id)
+ }
+ } else {
+ set pi $nparents($id)
+ }
+ if {[incr pi] >= $nparents($id)} {
+ set pi 0
+ if {[incr l] >= $numcommits} {
+ set l 0
+ }
+ if {$l == $findstartline} break
+ set id $lineid($l)
+ }
+ set p [lindex $parents($id) $pi]
+ set ids [list $id $p]
+ }
+ stopfindproc
+ if {!$finddidsel} {
+ bell
+ }
+}
+
+# mark a commit as matching by putting a yellow background
+# behind the headline
+proc markheadline {l id} {
+ global canv mainfont linehtag commitinfo
+
+ set bbox [$canv bbox $linehtag($l)]
+ set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
+ $canv lower $t
+}
+
+# mark the bits of a headline, author or date that match a find string
proc markmatches {canv l str tag matches font} {
set bbox [$canv bbox $tag]
set x0 [lindex $bbox 0]
}
proc unmarkmatches {} {
- global matchinglines
+ global matchinglines findids
allcanvs delete matches
catch {unset matchinglines}
+ catch {unset findids}
}
proc selcanvline {w x y} {
global canv canv2 canv3 ctext commitinfo selectedline
global lineid linehtag linentag linedtag
global canvy0 linespc parents nparents
- global cflist currentid sha1entry diffids
+ global cflist currentid sha1entry
global commentend seenfile idtags
$canv delete hover
if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
set id $lineid($l)
set currentid $id
- set diffids [concat $id $parents($id)]
$sha1entry delete 0 end
$sha1entry insert 0 $id
$sha1entry selection from 0
$cflist delete 0 end
$cflist insert end "Comments"
- if {$nparents($id) == 1} {
- startdiff
- }
+ startdiff $id $parents($id)
+}
+
+proc startdiff {id vs} {
+ global diffpending diffpindex
+ global diffindex difffilestart seenfile
+ global curdifftag curtagstart
+
+ set diffpending $vs
+ set diffpindex 0
catch {unset seenfile}
+ set diffindex 0
+ catch {unset difffilestart}
+ set curdifftag Comments
+ set curtagstart 0.0
+ contdiff [list $id [lindex $vs 0]]
}
-proc startdiff {} {
+proc contdiff {ids} {
global treediffs diffids treepending
- if {![info exists treediffs($diffids)]} {
+ if {![info exists treediffs($ids)]} {
+ set diffids $ids
if {![info exists treepending]} {
- gettreediffs $diffids
+ gettreediffs $ids
}
} else {
- addtocflist $diffids
+ addtocflist $ids
}
}
}
proc addtocflist {ids} {
- global diffids treediffs cflist
- if {$ids != $diffids} {
- gettreediffs $diffids
- return
- }
+ global treediffs cflist diffpindex
+
+ set colors {black blue green red cyan magenta}
+ set color [lindex $colors [expr {$diffpindex % [llength $colors]}]]
foreach f $treediffs($ids) {
$cflist insert end $f
+ $cflist itemconf end -foreground $color
}
getblobdiffs $ids
}
}
proc gettreediffline {gdtf ids} {
- global treediffs treepending
+ global treediffs treepending diffids
set n [gets $gdtf line]
if {$n < 0} {
if {![eof $gdtf]} return
close $gdtf
unset treepending
- addtocflist $ids
+ if {[info exists diffids]} {
+ if {$ids != $diffids} {
+ gettreediffs $diffids
+ } else {
+ addtocflist $ids
+ }
+ }
return
}
set file [lindex $line 5]
}
proc getblobdiffs {ids} {
- global diffopts blobdifffd env curdifftag curtagstart
- global diffindex difffilestart nextupdate
+ global diffopts blobdifffd diffids env
+ global nextupdate
set id [lindex $ids 0]
set p [lindex $ids 1]
}
fconfigure $bdf -blocking 0
set blobdifffd($ids) $bdf
- set curdifftag Comments
- set curtagstart 0.0
- set diffindex 0
- catch {unset difffilestart}
- fileevent $bdf readable "getblobdiffline $bdf {$ids}"
+ fileevent $bdf readable [list getblobdiffline $bdf $ids]
set nextupdate [expr {[clock clicks -milliseconds] + 100}]
}
proc getblobdiffline {bdf ids} {
global diffids blobdifffd ctext curdifftag curtagstart seenfile
global diffnexthead diffnextnote diffindex difffilestart
- global nextupdate
+ global nextupdate diffpending diffpindex
set n [gets $bdf line]
if {$n < 0} {
if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
$ctext tag add $curdifftag $curtagstart end
set seenfile($curdifftag) 1
+ if {[incr diffpindex] < [llength $diffpending]} {
+ set id [lindex $ids 0]
+ set p [lindex $diffpending $diffpindex]
+ contdiff [list $id $p]
+ }
}
}
return
}
proc listboxsel {} {
- global ctext cflist currentid treediffs seenfile
+ global ctext cflist currentid treediffs
if {![info exists currentid]} return
set sel [lsort [$cflist curselection]]
if {$sel eq {}} return
proc diffvssel {dirn} {
global rowmenuid selectedline lineid
global ctext cflist
- global diffids commitinfo
+ global commitinfo
if {![info exists selectedline]} return
if {$dirn} {
$ctext conf -state disabled
$ctext tag delete Comments
$ctext tag remove found 1.0 end
- set diffids [list $newid $oldid]
- startdiff
+ startdiff [list $newid $oldid]
}
proc mkpatch {} {
catch {destroy $top}
toplevel $top
label $top.title -text "Generate patch"
- grid $top.title -
+ grid $top.title - -pady 10
label $top.from -text "From:"
- entry $top.fromsha1 -width 40
+ entry $top.fromsha1 -width 40 -relief flat
$top.fromsha1 insert 0 $oldid
$top.fromsha1 conf -state readonly
grid $top.from $top.fromsha1 -sticky w
- entry $top.fromhead -width 60
+ entry $top.fromhead -width 60 -relief flat
$top.fromhead insert 0 $oldhead
$top.fromhead conf -state readonly
grid x $top.fromhead -sticky w
label $top.to -text "To:"
- entry $top.tosha1 -width 40
+ entry $top.tosha1 -width 40 -relief flat
$top.tosha1 insert 0 $newid
$top.tosha1 conf -state readonly
grid $top.to $top.tosha1 -sticky w
- entry $top.tohead -width 60
+ entry $top.tohead -width 60 -relief flat
$top.tohead insert 0 $newhead
$top.tohead conf -state readonly
grid x $top.tohead -sticky w
catch {destroy $top}
toplevel $top
label $top.title -text "Create tag"
- grid $top.title -
+ grid $top.title - -pady 10
label $top.id -text "ID:"
- entry $top.sha1 -width 40
+ entry $top.sha1 -width 40 -relief flat
$top.sha1 insert 0 $rowmenuid
$top.sha1 conf -state readonly
grid $top.id $top.sha1 -sticky w
- entry $top.head -width 40
+ entry $top.head -width 60 -relief flat
$top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
$top.head conf -state readonly
grid x $top.head -sticky w
label $top.tlab -text "Tag name:"
- entry $top.tag -width 40
+ entry $top.tag -width 60
grid $top.tlab $top.tag -sticky w
frame $top.buts
button $top.buts.gen -text "Create" -command mktaggo
mktagcan
}
+proc writecommit {} {
+ global rowmenuid wrcomtop commitinfo wrcomcmd
+
+ set top .writecommit
+ set wrcomtop $top
+ catch {destroy $top}
+ toplevel $top
+ label $top.title -text "Write commit to file"
+ grid $top.title - -pady 10
+ label $top.id -text "ID:"
+ entry $top.sha1 -width 40 -relief flat
+ $top.sha1 insert 0 $rowmenuid
+ $top.sha1 conf -state readonly
+ grid $top.id $top.sha1 -sticky w
+ entry $top.head -width 60 -relief flat
+ $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
+ $top.head conf -state readonly
+ grid x $top.head -sticky w
+ label $top.clab -text "Command:"
+ entry $top.cmd -width 60 -textvariable wrcomcmd
+ grid $top.clab $top.cmd -sticky w -pady 10
+ label $top.flab -text "Output file:"
+ entry $top.fname -width 60
+ $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
+ grid $top.flab $top.fname -sticky w
+ frame $top.buts
+ button $top.buts.gen -text "Write" -command wrcomgo
+ button $top.buts.can -text "Cancel" -command wrcomcan
+ grid $top.buts.gen $top.buts.can
+ grid columnconfigure $top.buts 0 -weight 1 -uniform a
+ grid columnconfigure $top.buts 1 -weight 1 -uniform a
+ grid $top.buts - -pady 10 -sticky ew
+ focus $top.fname
+}
+
+proc wrcomgo {} {
+ global wrcomtop
+
+ set id [$wrcomtop.sha1 get]
+ set cmd "echo $id | [$wrcomtop.cmd get]"
+ set fname [$wrcomtop.fname get]
+ if {[catch {exec sh -c $cmd >$fname &} err]} {
+ error_popup "Error writing commit: $err"
+ }
+ catch {destroy $wrcomtop}
+ unset wrcomtop
+}
+
+proc wrcomcan {} {
+ global wrcomtop
+
+ catch {destroy $wrcomtop}
+ unset wrcomtop
+}
+
proc doquit {} {
global stopped
set stopped 100
set datemode 0
set boldnames 0
set diffopts "-U 5 -p"
+set wrcomcmd "git-diff-tree --stdin -p --pretty"
set mainfont {Helvetica 9}
set textfont {Courier 9}
+set findmergefiles 0
set colors {green red blue magenta darkgrey brown orange}