Merge master.kernel.org:/pub/scm/gitk/gitk
[git.git] / gitk
diff --git a/gitk b/gitk
index e72c9c7..5ebcf33 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -37,7 +37,7 @@ proc getcommits {rargs} {
        set parsed_args $rargs
     }
     if [catch {
        set parsed_args $rargs
     }
     if [catch {
-       set commfd [open "|git-rev-list --header --merge-order $parsed_args" r]
+       set commfd [open "|git-rev-list --header --topo-order $parsed_args" r]
     } err] {
        puts stderr "Error executing git-rev-list: $err"
        exit 1
     } err] {
        puts stderr "Error executing git-rev-list: $err"
        exit 1
@@ -60,7 +60,7 @@ proc getcommitlines {commfd}  {
     set stuff [read $commfd]
     if {$stuff == {}} {
        if {![eof $commfd]} return
     set stuff [read $commfd]
     if {$stuff == {}} {
        if {![eof $commfd]} return
-       # this works around what is apparently a bug in Tcl...
+       # set it blocking so we wait for the process to terminate
        fconfigure $commfd -blocking 1
        if {![catch {close $commfd} err]} {
            after idle finishcommits
        fconfigure $commfd -blocking 1
        if {![catch {close $commfd} err]} {
            after idle finishcommits
@@ -270,10 +270,10 @@ proc error_popup msg {
 
 proc makewindow {} {
     global canv canv2 canv3 linespc charspc ctext cflist textfont
 
 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 entries sha1entry sha1string sha1but
     global maincursor textcursor
-    global rowctxmenu
+    global rowctxmenu gaudydiff
 
     menu .bar
     .bar add cascade -label "File" -menu .bar.file
 
     menu .bar
     .bar add cascade -label "File" -menu .bar.file
@@ -342,12 +342,15 @@ proc makewindow {} {
     entry $fstring -width 30 -font $textfont -textvariable findstring
     pack $fstring -side left -expand 1 -fill x
     set findtype Exact
     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 \
     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
     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
 
     panedwindow .ctop.cdet -orient horizontal
     .ctop add .ctop.cdet
@@ -361,11 +364,17 @@ proc makewindow {} {
     pack $ctext -side left -fill both -expand 1
     .ctop.cdet add .ctop.cdet.left
 
     pack $ctext -side left -fill both -expand 1
     .ctop.cdet add .ctop.cdet.left
 
-    $ctext tag conf filesep -font [concat $textfont bold]
-    $ctext tag conf hunksep -back blue -fore white
-    $ctext tag conf d0 -back "#ff8080"
-    $ctext tag conf d1 -back green
-    $ctext tag conf found -back yellow
+    $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
+    if {$gaudydiff} {
+       $ctext tag conf hunksep -back blue -fore white
+       $ctext tag conf d0 -back "#ff8080"
+       $ctext tag conf d1 -back green
+    } else {
+       $ctext tag conf hunksep -fore blue
+       $ctext tag conf d0 -fore red
+       $ctext tag conf d1 -fore "#00a000"
+       $ctext tag conf found -back yellow
+    }
 
     frame .ctop.cdet.right
     set cflist .ctop.cdet.right.cfiles
 
     frame .ctop.cdet.right
     set cflist .ctop.cdet.right.cfiles
@@ -397,12 +406,13 @@ proc makewindow {} {
     bindkey b "$ctext yview scroll -1 pages"
     bindkey d "$ctext yview scroll 18 units"
     bindkey u "$ctext yview scroll -18 units"
     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
     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}
     bind . <Control-r> findprev
     bind . <Control-equal> {incrfont 1}
     bind . <Control-KP_Add> {incrfont 1}
@@ -425,6 +435,8 @@ proc makewindow {} {
     $rowctxmenu add command -label "Diff selected -> this" \
        -command {diffvssel 1}
     $rowctxmenu add command -label "Make patch" -command mkpatch
     $rowctxmenu add command -label "Diff selected -> this" \
        -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
 }
 
 # when we make a key binding for the toplevel, make sure
@@ -459,8 +471,10 @@ proc savestuff {w} {
     if {![winfo viewable .]} return
     catch {
        set f [open "~/.gitk-new" w]
     if {![winfo viewable .]} return
     catch {
        set f [open "~/.gitk-new" w]
-       puts $f "set mainfont {$mainfont}"
-       puts $f "set textfont {$textfont}"
+       puts $f [list set mainfont $mainfont]
+       puts $f [list set textfont $textfont]
+       puts $f [list set findmergefiles $findmergefiles]
+       puts $f [list set gaudydiff $gaudydiff]
        puts $f "set geometry(width) [winfo width .ctop]"
        puts $f "set geometry(height) [winfo height .ctop]"
        puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
        puts $f "set geometry(width) [winfo width .ctop]"
        puts $f "set geometry(height) [winfo height .ctop]"
        puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
@@ -671,7 +685,7 @@ proc drawcommitline {level} {
     global oldlevel oldnlines oldtodo
     global idtags idline idheads
     global lineno lthickness mainline sidelines
     global oldlevel oldnlines oldtodo
     global idtags idline idheads
     global lineno lthickness mainline sidelines
-    global commitlisted rowtextx
+    global commitlisted rowtextx idpos
 
     incr numcommits
     incr lineno
 
     incr numcommits
     incr lineno
@@ -732,47 +746,9 @@ proc drawcommitline {level} {
        set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
     }
     set rowtextx($lineno) $xt
        set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
     }
     set rowtextx($lineno) $xt
-    set marks {}
-    set ntags 0
-    if {[info exists idtags($id)]} {
-       set marks $idtags($id)
-       set ntags [llength $marks]
-    }
-    if {[info exists idheads($id)]} {
-       set marks [concat $marks $idheads($id)]
-    }
-    if {$marks != {}} {
-       set delta [expr {int(0.5 * ($linespc - $lthickness))}]
-       set yt [expr $y1 - 0.5 * $linespc]
-       set yb [expr $yt + $linespc - 1]
-       set xvals {}
-       set wvals {}
-       foreach tag $marks {
-           set wid [font measure $mainfont $tag]
-           lappend xvals $xt
-           lappend wvals $wid
-           set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
-       }
-       set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
-                  -width $lthickness -fill black]
-       $canv lower $t
-       foreach tag $marks x $xvals wid $wvals {
-           set xl [expr $x + $delta]
-           set xr [expr $x + $delta + $wid + $lthickness]
-           if {[incr ntags -1] >= 0} {
-               # draw a tag
-               $canv create polygon $x [expr $yt + $delta] $xl $yt\
-                   $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
-                   -width 1 -outline black -fill yellow
-           } else {
-               # draw a head
-               set xl [expr $xl - $delta/2]
-               $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
-                   -width 1 -outline black -fill green
-           }
-           $canv create text $xl $y1 -anchor w -text $tag \
-               -font $mainfont
-       }
+    set idpos($id) [list $x $xt $y1]
+    if {[info exists idtags($id)] || [info exists idheads($id)]} {
+       set xt [drawtags $id $x $xt $y1]
     }
     set headline [lindex $commitinfo($id) 0]
     set name [lindex $commitinfo($id) 1]
     }
     set headline [lindex $commitinfo($id) 0]
     set name [lindex $commitinfo($id) 1]
@@ -786,6 +762,58 @@ proc drawcommitline {level} {
                               -text $date -font $mainfont]
 }
 
                               -text $date -font $mainfont]
 }
 
+proc drawtags {id x xt y1} {
+    global idtags idheads
+    global linespc lthickness
+    global canv mainfont
+
+    set marks {}
+    set ntags 0
+    if {[info exists idtags($id)]} {
+       set marks $idtags($id)
+       set ntags [llength $marks]
+    }
+    if {[info exists idheads($id)]} {
+       set marks [concat $marks $idheads($id)]
+    }
+    if {$marks eq {}} {
+       return $xt
+    }
+
+    set delta [expr {int(0.5 * ($linespc - $lthickness))}]
+    set yt [expr $y1 - 0.5 * $linespc]
+    set yb [expr $yt + $linespc - 1]
+    set xvals {}
+    set wvals {}
+    foreach tag $marks {
+       set wid [font measure $mainfont $tag]
+       lappend xvals $xt
+       lappend wvals $wid
+       set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
+    }
+    set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
+              -width $lthickness -fill black -tags tag.$id]
+    $canv lower $t
+    foreach tag $marks x $xvals wid $wvals {
+       set xl [expr $x + $delta]
+       set xr [expr $x + $delta + $wid + $lthickness]
+       if {[incr ntags -1] >= 0} {
+           # draw a tag
+           $canv create polygon $x [expr $yt + $delta] $xl $yt\
+               $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
+               -width 1 -outline black -fill yellow -tags tag.$id
+       } else {
+           # draw a head
+           set xl [expr $xl - $delta/2]
+           $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
+               -width 1 -outline black -fill green -tags tag.$id
+       }
+       $canv create text $xl $y1 -anchor w -text $tag \
+           -font $mainfont -tags tag.$id
+    }
+    return $xt
+}
+
 proc updatetodo {level noshortcut} {
     global currentparents ncleft todo
     global mainline oldlevel oldtodo oldnlines
 proc updatetodo {level noshortcut} {
     global currentparents ncleft todo
     global mainline oldlevel oldtodo oldnlines
@@ -1120,10 +1148,15 @@ proc dofind {} {
     global numcommits lineid linehtag linentag linedtag
     global mainfont namefont canv canv2 canv3 selectedline
     global matchinglines foundstring foundstrlen
     global numcommits lineid linehtag linentag linedtag
     global mainfont namefont canv canv2 canv3 selectedline
     global matchinglines foundstring foundstrlen
+
+    stopfindproc
     unmarkmatches
     focus .
     set matchinglines {}
     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 {
     if {$findtype == "IgnCase"} {
        set foundstring [string tolower $findstring]
     } else {
@@ -1131,12 +1164,17 @@ proc dofind {} {
     }
     set foundstrlen [string length $findstring]
     if {$foundstrlen == 0} return
     }
     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
     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)
     for {set l 0} {$l < $numcommits} {incr l} {
        set id $lineid($l)
        set info $commitinfo($id)
@@ -1186,10 +1224,12 @@ proc findselectline {l} {
     }
 }
 
     }
 }
 
-proc findnext {} {
+proc findnext {restart} {
     global matchinglines selectedline
     if {![info exists matchinglines]} {
     global matchinglines selectedline
     if {![info exists matchinglines]} {
-       dofind
+       if {$restart} {
+           dofind
+       }
        return
     }
     if {![info exists selectedline]} return
        return
     }
     if {![info exists selectedline]} return
@@ -1221,6 +1261,308 @@ proc findprev {} {
     }
 }
 
     }
 }
 
+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 markmatches {canv l str tag matches font} {
     set bbox [$canv bbox $tag]
     set x0 [lindex $bbox 0]
@@ -1239,9 +1581,10 @@ proc markmatches {canv l str tag matches font} {
 }
 
 proc unmarkmatches {} {
 }
 
 proc unmarkmatches {} {
-    global matchinglines
+    global matchinglines findids
     allcanvs delete matches
     catch {unset matchinglines}
     allcanvs delete matches
     catch {unset matchinglines}
+    catch {unset findids}
 }
 
 proc selcanvline {w x y} {
 }
 
 proc selcanvline {w x y} {
@@ -1266,8 +1609,8 @@ proc selectline {l} {
     global canv canv2 canv3 ctext commitinfo selectedline
     global lineid linehtag linentag linedtag
     global canvy0 linespc parents nparents
     global canv canv2 canv3 ctext commitinfo selectedline
     global lineid linehtag linentag linedtag
     global canvy0 linespc parents nparents
-    global cflist currentid sha1entry diffids
-    global commentend seenfile idtags
+    global cflist currentid sha1entry
+    global commentend idtags
     $canv delete hover
     if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
     $canv delete secsel
     $canv delete hover
     if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
     $canv delete secsel
@@ -1320,7 +1663,6 @@ proc selectline {l} {
 
     set id $lineid($l)
     set currentid $id
 
     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
     $sha1entry delete 0 end
     $sha1entry insert 0 $id
     $sha1entry selection from 0
@@ -1350,21 +1692,33 @@ proc selectline {l} {
 
     $cflist delete 0 end
     $cflist insert end "Comments"
 
     $cflist delete 0 end
     $cflist insert end "Comments"
-    if {$nparents($id) == 1} {
-       startdiff
-    }
-    catch {unset seenfile}
+    startdiff $id $parents($id)
+}
+
+proc startdiff {id vs} {
+    global diffpending diffpindex
+    global diffindex difffilestart
+    global curdifftag curtagstart
+
+    set diffpending $vs
+    set diffpindex 0
+    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
 
     global treediffs diffids treepending
 
-    if {![info exists treediffs($diffids)]} {
+    set diffids $ids
+    if {![info exists treediffs($ids)]} {
        if {![info exists treepending]} {
        if {![info exists treepending]} {
-           gettreediffs $diffids
+           gettreediffs $ids
        }
     } else {
        }
     } else {
-       addtocflist $diffids
+       addtocflist $ids
     }
 }
 
     }
 }
 
@@ -1377,13 +1731,13 @@ proc selnextline {dir} {
 }
 
 proc 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
     foreach f $treediffs($ids) {
        $cflist insert end $f
+       $cflist itemconf end -foreground $color
     }
     getblobdiffs $ids
 }
     }
     getblobdiffs $ids
 }
@@ -1400,13 +1754,19 @@ proc gettreediffs {ids} {
 }
 
 proc gettreediffline {gdtf 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
     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]
        return
     }
     set file [lindex $line 5]
@@ -1414,8 +1774,8 @@ proc gettreediffline {gdtf ids} {
 }
 
 proc getblobdiffs {ids} {
 }
 
 proc getblobdiffs {ids} {
-    global diffopts blobdifffd env curdifftag curtagstart
-    global diffindex difffilestart nextupdate
+    global diffopts blobdifffd diffids env
+    global nextupdate diffinhdr
 
     set id [lindex $ids 0]
     set p [lindex $ids 1]
 
     set id [lindex $ids 0]
     set p [lindex $ids 1]
@@ -1424,20 +1784,18 @@ proc getblobdiffs {ids} {
        puts "error getting diffs: $err"
        return
     }
        puts "error getting diffs: $err"
        return
     }
+    set diffinhdr 0
     fconfigure $bdf -blocking 0
     set blobdifffd($ids) $bdf
     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} {
     set nextupdate [expr {[clock clicks -milliseconds] + 100}]
 }
 
 proc getblobdiffline {bdf ids} {
-    global diffids blobdifffd ctext curdifftag curtagstart seenfile
+    global diffids blobdifffd ctext curdifftag curtagstart
     global diffnexthead diffnextnote diffindex difffilestart
     global diffnexthead diffnextnote diffindex difffilestart
-    global nextupdate
+    global nextupdate diffpending diffpindex diffinhdr
+    global gaudydiff
 
     set n [gets $bdf line]
     if {$n < 0} {
 
     set n [gets $bdf line]
     if {$n < 0} {
@@ -1445,7 +1803,11 @@ proc getblobdiffline {bdf ids} {
            close $bdf
            if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
                $ctext tag add $curdifftag $curtagstart end
            close $bdf
            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
            }
        }
        return
@@ -1454,18 +1816,12 @@ proc getblobdiffline {bdf ids} {
        return
     }
     $ctext conf -state normal
        return
     }
     $ctext conf -state normal
-    if {[regexp {^---[ \t]+([^/])*/(.*)} $line match s1 fname]} {
+    if {[regexp {^diff --git a/(.*) b/} $line match fname]} {
        # start of a new file
        $ctext insert end "\n"
        $ctext tag add $curdifftag $curtagstart end
        # start of a new file
        $ctext insert end "\n"
        $ctext tag add $curdifftag $curtagstart end
-       set seenfile($curdifftag) 1
        set curtagstart [$ctext index "end - 1c"]
        set header $fname
        set curtagstart [$ctext index "end - 1c"]
        set header $fname
-       if {[info exists diffnexthead]} {
-           set fname $diffnexthead
-           set header "$diffnexthead ($diffnextnote)"
-           unset diffnexthead
-       }
        set here [$ctext index "end - 1c"]
        set difffilestart($diffindex) $here
        incr diffindex
        set here [$ctext index "end - 1c"]
        set difffilestart($diffindex) $here
        incr diffindex
@@ -1477,37 +1833,33 @@ proc getblobdiffline {bdf ids} {
        set l [expr {(78 - [string length $header]) / 2}]
        set pad [string range "----------------------------------------" 1 $l]
        $ctext insert end "$pad $header $pad\n" filesep
        set l [expr {(78 - [string length $header]) / 2}]
        set pad [string range "----------------------------------------" 1 $l]
        $ctext insert end "$pad $header $pad\n" filesep
-    } elseif {[string range $line 0 2] == "+++"} {
-       # no need to do anything with this
-    } elseif {[regexp {^Created: (.*) \((mode: *[0-7]*)\)} $line match fn m]} {
-       set diffnexthead $fn
-       set diffnextnote "created, mode $m"
-    } elseif {[string range $line 0 8] == "Deleted: "} {
-       set diffnexthead [string range $line 9 end]
-       set diffnextnote "deleted"
-    } elseif {[regexp {^diff --git a/(.*) b/} $line match fn]} {
-       # save the filename in case the next thing is "new file mode ..."
-       set diffnexthead $fn
-       set diffnextnote "modified"
-    } elseif {[regexp {^new file mode ([0-7]+)} $line match m]} {
-       set diffnextnote "new file, mode $m"
-    } elseif {[string range $line 0 11] == "deleted file"} {
-       set diffnextnote "deleted"
+       set diffinhdr 1
+    } elseif {[regexp {^(---|\+\+\+)} $line]} {
+       set diffinhdr 0
     } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
                   $line match f1l f1c f2l f2c rest]} {
     } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
                   $line match f1l f1c f2l f2c rest]} {
-       $ctext insert end "\t" hunksep
-       $ctext insert end "    $f1l    " d0 "    $f2l    " d1
-       $ctext insert end "    $rest \n" hunksep
+       if {$gaudydiff} {
+           $ctext insert end "\t" hunksep
+           $ctext insert end "    $f1l    " d0 "    $f2l    " d1
+           $ctext insert end "    $rest \n" hunksep
+       } else {
+           $ctext insert end "$line\n" hunksep
+       }
+       set diffinhdr 0
     } else {
        set x [string range $line 0 0]
        if {$x == "-" || $x == "+"} {
            set tag [expr {$x == "+"}]
     } else {
        set x [string range $line 0 0]
        if {$x == "-" || $x == "+"} {
            set tag [expr {$x == "+"}]
-           set line [string range $line 1 end]
+           if {$gaudydiff} {
+               set line [string range $line 1 end]
+           }
            $ctext insert end "$line\n" d$tag
        } elseif {$x == " "} {
            $ctext insert end "$line\n" d$tag
        } elseif {$x == " "} {
-           set line [string range $line 1 end]
+           if {$gaudydiff} {
+               set line [string range $line 1 end]
+           }
            $ctext insert end "$line\n"
            $ctext insert end "$line\n"
-       } elseif {$x == "\\"} {
+       } elseif {$diffinhdr || $x == "\\"} {
            # e.g. "\ No newline at end of file"
            $ctext insert end "$line\n" filesep
        } else {
            # e.g. "\ No newline at end of file"
            $ctext insert end "$line\n" filesep
        } else {
@@ -1515,7 +1867,6 @@ proc getblobdiffline {bdf ids} {
            if {$curdifftag != "Comments"} {
                $ctext insert end "\n"
                $ctext tag add $curdifftag $curtagstart end
            if {$curdifftag != "Comments"} {
                $ctext insert end "\n"
                $ctext tag add $curdifftag $curtagstart end
-               set seenfile($curdifftag) 1
                set curtagstart [$ctext index "end - 1c"]
                set curdifftag Comments
            }
                set curtagstart [$ctext index "end - 1c"]
                set curdifftag Comments
            }
@@ -1543,7 +1894,7 @@ proc nextfile {} {
 }
 
 proc listboxsel {} {
 }
 
 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
     if {![info exists currentid]} return
     set sel [lsort [$cflist curselection]]
     if {$sel eq {}} return
@@ -1615,18 +1966,35 @@ proc sha1change {n1 n2 op} {
 
 proc gotocommit {} {
     global sha1string currentid idline tagids
 
 proc gotocommit {} {
     global sha1string currentid idline tagids
+    global lineid numcommits
+
     if {$sha1string == {}
        || ([info exists currentid] && $sha1string == $currentid)} return
     if {[info exists tagids($sha1string)]} {
        set id $tagids($sha1string)
     } else {
        set id [string tolower $sha1string]
     if {$sha1string == {}
        || ([info exists currentid] && $sha1string == $currentid)} return
     if {[info exists tagids($sha1string)]} {
        set id $tagids($sha1string)
     } else {
        set id [string tolower $sha1string]
+       if {[regexp {^[0-9a-f]{4,39}$} $id]} {
+           set matches {}
+           for {set l 0} {$l < $numcommits} {incr l} {
+               if {[string match $id* $lineid($l)]} {
+                   lappend matches $lineid($l)
+               }
+           }
+           if {$matches ne {}} {
+               if {[llength $matches] > 1} {
+                   error_popup "Short SHA1 id $id is ambiguous"
+                   return
+               }
+               set id [lindex $matches 0]
+           }
+       }
     }
     if {[info exists idline($id)]} {
        selectline $idline($id)
        return
     }
     }
     if {[info exists idline($id)]} {
        selectline $idline($id)
        return
     }
-    if {[regexp {^[0-9a-fA-F]{40}$} $sha1string]} {
+    if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
        set type "SHA1 id"
     } else {
        set type "Tag"
        set type "SHA1 id"
     } else {
        set type "Tag"
@@ -1765,7 +2133,7 @@ proc rowmenu {x y id} {
 proc diffvssel {dirn} {
     global rowmenuid selectedline lineid
     global ctext cflist
 proc diffvssel {dirn} {
     global rowmenuid selectedline lineid
     global ctext cflist
-    global diffids commitinfo
+    global commitinfo
 
     if {![info exists selectedline]} return
     if {$dirn} {
 
     if {![info exists selectedline]} return
     if {$dirn} {
@@ -1789,8 +2157,7 @@ proc diffvssel {dirn} {
     $ctext conf -state disabled
     $ctext tag delete Comments
     $ctext tag remove found 1.0 end
     $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 {} {
 }
 
 proc mkpatch {} {
@@ -1806,22 +2173,22 @@ proc mkpatch {} {
     catch {destroy $top}
     toplevel $top
     label $top.title -text "Generate patch"
     catch {destroy $top}
     toplevel $top
     label $top.title -text "Generate patch"
-    grid $top.title -
+    grid $top.title - -pady 10
     label $top.from -text "From:"
     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
     $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:"
     $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
     $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
     $top.tohead insert 0 $newhead
     $top.tohead conf -state readonly
     grid x $top.tohead -sticky w
@@ -1831,7 +2198,7 @@ proc mkpatch {} {
     entry $top.fname -width 60
     $top.fname insert 0 [file normalize "patch$patchnum.patch"]
     incr patchnum
     entry $top.fname -width 60
     $top.fname insert 0 [file normalize "patch$patchnum.patch"]
     incr patchnum
-    grid $top.flab $top.fname
+    grid $top.flab $top.fname -sticky w
     frame $top.buts
     button $top.buts.gen -text "Generate" -command mkpatchgo
     button $top.buts.can -text "Cancel" -command mkpatchcan
     frame $top.buts
     button $top.buts.gen -text "Generate" -command mkpatchgo
     button $top.buts.can -text "Cancel" -command mkpatchcan
@@ -1839,6 +2206,7 @@ proc mkpatch {} {
     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
     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 mkpatchrev {} {
 }
 
 proc mkpatchrev {} {
@@ -1877,6 +2245,142 @@ proc mkpatchcan {} {
     unset patchtop
 }
 
     unset patchtop
 }
 
+proc mktag {} {
+    global rowmenuid mktagtop commitinfo
+
+    set top .maketag
+    set mktagtop $top
+    catch {destroy $top}
+    toplevel $top
+    label $top.title -text "Create tag"
+    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.tlab -text "Tag name:"
+    entry $top.tag -width 60
+    grid $top.tlab $top.tag -sticky w
+    frame $top.buts
+    button $top.buts.gen -text "Create" -command mktaggo
+    button $top.buts.can -text "Cancel" -command mktagcan
+    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.tag
+}
+
+proc domktag {} {
+    global mktagtop env tagids idtags
+    global idpos idline linehtag canv selectedline
+
+    set id [$mktagtop.sha1 get]
+    set tag [$mktagtop.tag get]
+    if {$tag == {}} {
+       error_popup "No tag name specified"
+       return
+    }
+    if {[info exists tagids($tag)]} {
+       error_popup "Tag \"$tag\" already exists"
+       return
+    }
+    if {[catch {
+       set dir ".git"
+       if {[info exists env(GIT_DIR)]} {
+           set dir $env(GIT_DIR)
+       }
+       set fname [file join $dir "refs/tags" $tag]
+       set f [open $fname w]
+       puts $f $id
+       close $f
+    } err]} {
+       error_popup "Error creating tag: $err"
+       return
+    }
+
+    set tagids($tag) $id
+    lappend idtags($id) $tag
+    $canv delete tag.$id
+    set xt [eval drawtags $id $idpos($id)]
+    $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
+    if {[info exists selectedline] && $selectedline == $idline($id)} {
+       selectline $selectedline
+    }
+}
+
+proc mktagcan {} {
+    global mktagtop
+
+    catch {destroy $mktagtop}
+    unset mktagtop
+}
+
+proc mktaggo {} {
+    domktag
+    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
 proc doquit {} {
     global stopped
     set stopped 100
@@ -1887,9 +2391,12 @@ proc doquit {} {
 set datemode 0
 set boldnames 0
 set diffopts "-U 5 -p"
 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 mainfont {Helvetica 9}
 set textfont {Courier 9}
+set findmergefiles 0
+set gaudydiff 0
 
 set colors {green red blue magenta darkgrey brown orange}
 
 
 set colors {green red blue magenta darkgrey brown orange}