Handle the rename cases reported by git-diff-tree -C correctly.
[git.git] / gitk
diff --git a/gitk b/gitk
index 779d71c..112c9c0 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -8,10 +8,20 @@ exec wish "$0" -- "${1+$@}"
 # either version 2, or (at your option) any later version.
 
 proc getcommits {rargs} {
 # either version 2, or (at your option) any later version.
 
 proc getcommits {rargs} {
-    global commits commfd phase canv mainfont
+    global commits commfd phase canv mainfont env
     global startmsecs nextupdate
     global ctext maincursor textcursor leftover
 
     global startmsecs nextupdate
     global ctext maincursor textcursor leftover
 
+    # check that we can find a .git directory somewhere...
+    if {[info exists env(GIT_DIR)]} {
+       set gitdir $env(GIT_DIR)
+    } else {
+       set gitdir ".git"
+    }
+    if {![file isdirectory $gitdir]} {
+       error_popup "Cannot find the git directory \"$gitdir\"."
+       exit 1
+    }
     set commits {}
     set phase getcommits
     set startmsecs [clock clicks -milliseconds]
     set commits {}
     set phase getcommits
     set startmsecs [clock clicks -milliseconds]
@@ -50,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
@@ -71,16 +81,21 @@ to allow selection of commits to be displayed.)}
     while 1 {
        set i [string first "\0" $stuff $start]
        if {$i < 0} {
     while 1 {
        set i [string first "\0" $stuff $start]
        if {$i < 0} {
-           set leftover [string range $stuff $start end]
+           append leftover [string range $stuff $start end]
            return
        }
        set cmit [string range $stuff $start [expr {$i - 1}]]
        if {$start == 0} {
            set cmit "$leftover$cmit"
            return
        }
        set cmit [string range $stuff $start [expr {$i - 1}]]
        if {$start == 0} {
            set cmit "$leftover$cmit"
+           set leftover {}
        }
        set start [expr {$i + 1}]
        if {![regexp {^([0-9a-f]{40})\n} $cmit match id]} {
        }
        set start [expr {$i + 1}]
        if {![regexp {^([0-9a-f]{40})\n} $cmit match id]} {
-           error_popup "Can't parse git-rev-list output: {$cmit}"
+           set shortcmit $cmit
+           if {[string length $shortcmit] > 80} {
+               set shortcmit "[string range $shortcmit 0 80]..."
+           }
+           error_popup "Can't parse git-rev-list output: {$shortcmit}"
            exit 1
        }
        set cmit [string range $cmit 41 end]
            exit 1
        }
        set cmit [string range $cmit 41 end]
@@ -255,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
@@ -327,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
@@ -346,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
@@ -382,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}
@@ -398,6 +423,7 @@ proc makewindow {} {
     bind . <Button-1> "click %W"
     bind $fstring <Key-Return> dofind
     bind $sha1entry <Key-Return> gotocommit
     bind . <Button-1> "click %W"
     bind $fstring <Key-Return> dofind
     bind $sha1entry <Key-Return> gotocommit
+    bind $sha1entry <<PasteSelection>> clearsha1
 
     set maincursor [. cget -cursor]
     set textcursor [$ctext cget -cursor]
 
     set maincursor [. cget -cursor]
     set textcursor [$ctext cget -cursor]
@@ -408,6 +434,9 @@ proc makewindow {} {
        -command {diffvssel 0}
     $rowctxmenu add command -label "Diff selected -> this" \
        -command {diffvssel 1}
        -command {diffvssel 0}
     $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
@@ -442,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]"
@@ -654,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
@@ -715,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]
@@ -769,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
@@ -883,11 +928,11 @@ proc drawslants {} {
     }
 }
 
     }
 }
 
-proc decidenext {} {
+proc decidenext {{noread 0}} {
     global parents children nchildren ncleft todo
     global canv canv2 canv3 mainfont namefont canvx0 canvy linespc
     global datemode cdate
     global parents children nchildren ncleft todo
     global canv canv2 canv3 mainfont namefont canvx0 canvy linespc
     global datemode cdate
-    global lineid linehtag linentag linedtag commitinfo
+    global commitinfo
     global currentparents oldlevel oldnlines oldtodo
     global lineno lthickness
 
     global currentparents oldlevel oldnlines oldtodo
     global lineno lthickness
 
@@ -905,6 +950,12 @@ proc decidenext {} {
        set p [lindex $todo $k]
        if {$ncleft($p) == 0} {
            if {$datemode} {
        set p [lindex $todo $k]
        if {$ncleft($p) == 0} {
            if {$datemode} {
+               if {![info exists commitinfo($p)]} {
+                   if {$noread} {
+                       return {}
+                   }
+                   readcommit $p
+               }
                if {$latest == {} || $cdate($p) > $latest} {
                    set level $k
                    set latest $cdate($p)
                if {$latest == {} || $cdate($p) > $latest} {
                    set level $k
                    set latest $cdate($p)
@@ -965,15 +1016,16 @@ proc drawcommit {id} {
            lappend todo $id
            lappend startcommits $id
        }
            lappend todo $id
            lappend startcommits $id
        }
-       set level [decidenext]
-       if {$id != [lindex $todo $level]} {
+       set level [decidenext 1]
+       if {$level == {} || $id != [lindex $todo $level]} {
            return
        }
        while 1 {
            drawslants
            drawcommitline $level
            if {[updatetodo $level $datemode]} {
            return
        }
        while 1 {
            drawslants
            drawcommitline $level
            if {[updatetodo $level $datemode]} {
-               set level [decidenext]
+               set level [decidenext 1]
+               if {$level == {}} break
            }
            set id [lindex $todo $level]
            if {![info exists commitlisted($id)]} {
            }
            set id [lindex $todo $level]
            if {![info exists commitlisted($id)]} {
@@ -990,18 +1042,18 @@ proc drawcommit {id} {
 proc finishcommits {} {
     global phase
     global startcommits
 proc finishcommits {} {
     global phase
     global startcommits
-    global ctext maincursor textcursor
+    global canv mainfont ctext maincursor textcursor
 
     if {$phase != "incrdraw"} {
        $canv delete all
        $canv create text 3 3 -anchor nw -text "No commits selected" \
            -font $mainfont -tags textitems
        set phase {}
 
     if {$phase != "incrdraw"} {
        $canv delete all
        $canv create text 3 3 -anchor nw -text "No commits selected" \
            -font $mainfont -tags textitems
        set phase {}
-       return
+    } else {
+       drawslants
+       set level [decidenext]
+       drawrest $level [llength $startcommits]
     }
     }
-    drawslants
-    set level [decidenext]
-    drawrest $level [llength $startcommits]
     . config -cursor $maincursor
     $ctext config -cursor $textcursor
 }
     . config -cursor $maincursor
     $ctext config -cursor $textcursor
 }
@@ -1096,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 {
@@ -1107,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)
@@ -1162,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
@@ -1197,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
+    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]
@@ -1215,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} {
@@ -1242,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
@@ -1296,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
@@ -1327,20 +1693,9 @@ proc selectline {l} {
     $cflist delete 0 end
     $cflist insert end "Comments"
     if {$nparents($id) == 1} {
     $cflist delete 0 end
     $cflist insert end "Comments"
     if {$nparents($id) == 1} {
-       startdiff
-    }
-    catch {unset seenfile}
-}
-
-proc startdiff {} {
-    global treediffs diffids treepending
-
-    if {![info exists treediffs($diffids)]} {
-       if {![info exists treepending]} {
-           gettreediffs $diffids
-       }
-    } else {
-       addtocflist $diffids
+       startdiff [concat $id $parents($id)]
+    } elseif {$nparents($id) > 1} {
+       mergediff $id
     }
 }
 
     }
 }
 
@@ -1352,12 +1707,117 @@ proc selnextline {dir} {
     selectline $l
 }
 
     selectline $l
 }
 
-proc addtocflist {ids} {
-    global diffids treediffs cflist
-    if {$ids != $diffids} {
-       gettreediffs $diffids
-       return
+proc mergediff {id} {
+    global parents diffmergeid diffmergegca mergefilelist diffpindex
+
+    set diffmergeid $id
+    set diffpindex -1
+    set diffmergegca [findgca $parents($id)]
+    if {[info exists mergefilelist($id)]} {
+       showmergediff
+    } else {
+       contmergediff {}
     }
     }
+}
+
+proc findgca {ids} {
+    set gca {}
+    foreach id $ids {
+       if {$gca eq {}} {
+           set gca $id
+       } else {
+           if {[catch {
+               set gca [exec git-merge-base $gca $id]
+           } err]} {
+               return {}
+           }
+       }
+    }
+    return $gca
+}
+
+proc contmergediff {ids} {
+    global diffmergeid diffpindex parents nparents diffmergegca
+    global treediffs mergefilelist diffids
+
+    # diff the child against each of the parents, and diff
+    # each of the parents against the GCA.
+    while 1 {
+       if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
+           set ids [list [lindex $ids 1] $diffmergegca]
+       } else {
+           if {[incr diffpindex] >= $nparents($diffmergeid)} break
+           set p [lindex $parents($diffmergeid) $diffpindex]
+           set ids [list $diffmergeid $p]
+       }
+       if {![info exists treediffs($ids)]} {
+           set diffids $ids
+           gettreediffs $ids
+           return
+       }
+    }
+
+    # If a file in some parent is different from the child and also
+    # different from the GCA, then it's interesting.
+    # If we don't have a GCA, then a file is interesting if it is
+    # different from the child in all the parents.
+    if {$diffmergegca ne {}} {
+       set files {}
+       foreach p $parents($diffmergeid) {
+           set gcadiffs $treediffs([list $p $diffmergegca])
+           foreach f $treediffs([list $diffmergeid $p]) {
+               if {[lsearch -exact $files $f] < 0
+                   && [lsearch -exact $gcadiffs $f] >= 0} {
+                   lappend files $f
+               }
+           }
+       }
+       set files [lsort $files]
+    } else {
+       set p [lindex $parents($diffmergeid) 0]
+       set files $treediffs([list $diffmergeid $p])
+       for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
+           set p [lindex $parents($diffmergeid) $i]
+           set df $treediffs([list $diffmergeid $p])
+           set nf {}
+           foreach f $files {
+               if {[lsearch -exact $df $f] >= 0} {
+                   lappend nf $f
+               }
+           }
+           set files $nf
+       }
+    }
+
+    set mergefilelist($diffmergeid) $files
+    showmergediff
+}
+
+proc showmergediff {} {
+    global cflist diffmergeid mergefilelist
+
+    set files $mergefilelist($diffmergeid)
+    foreach f $files {
+       $cflist insert end $f
+    }
+}
+
+proc startdiff {ids} {
+    global treediffs diffids treepending diffmergeid
+
+    set diffids $ids
+    catch {unset diffmergeid}
+    if {![info exists treediffs($ids)]} {
+       if {![info exists treepending]} {
+           gettreediffs $ids
+       }
+    } else {
+       addtocflist $ids
+    }
+}
+
+proc addtocflist {ids} {
+    global treediffs cflist
     foreach f $treediffs($ids) {
        $cflist insert end $f
     }
     foreach f $treediffs($ids) {
        $cflist insert end $f
     }
@@ -1365,55 +1825,67 @@ proc addtocflist {ids} {
 }
 
 proc gettreediffs {ids} {
 }
 
 proc gettreediffs {ids} {
-    global treediffs parents treepending
+    global treediff parents treepending
     set treepending $ids
     set treepending $ids
-    set treediffs($ids) {}
+    set treediff {}
     set id [lindex $ids 0]
     set p [lindex $ids 1]
     if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
     fconfigure $gdtf -blocking 0
     set id [lindex $ids 0]
     set p [lindex $ids 1]
     if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
     fconfigure $gdtf -blocking 0
-    fileevent $gdtf readable "gettreediffline $gdtf {$ids}"
+    fileevent $gdtf readable [list gettreediffline $gdtf $ids]
 }
 
 proc gettreediffline {gdtf ids} {
 }
 
 proc gettreediffline {gdtf ids} {
-    global treediffs treepending
+    global treediff treediffs treepending diffids diffmergeid
+
     set n [gets $gdtf line]
     if {$n < 0} {
        if {![eof $gdtf]} return
        close $gdtf
     set n [gets $gdtf line]
     if {$n < 0} {
        if {![eof $gdtf]} return
        close $gdtf
+       set treediffs($ids) $treediff
        unset treepending
        unset treepending
-       addtocflist $ids
+       if {$ids != $diffids} {
+           gettreediffs $diffids
+       } else {
+           if {[info exists diffmergeid]} {
+               contmergediff $ids
+           } else {
+               addtocflist $ids
+           }
+       }
        return
     }
     set file [lindex $line 5]
        return
     }
     set file [lindex $line 5]
-    lappend treediffs($ids) $file
+    lappend treediff $file
 }
 
 proc getblobdiffs {ids} {
 }
 
 proc getblobdiffs {ids} {
-    global diffopts blobdifffd env curdifftag curtagstart
-    global diffindex difffilestart nextupdate
+    global diffopts blobdifffd diffids env curdifftag curtagstart
+    global difffilestart nextupdate diffinhdr treediffs
 
     set id [lindex $ids 0]
     set p [lindex $ids 1]
     set env(GIT_DIFF_OPTS) $diffopts
 
     set id [lindex $ids 0]
     set p [lindex $ids 1]
     set env(GIT_DIFF_OPTS) $diffopts
-    if [catch {set bdf [open "|git-diff-tree -r -p $p $id" r]} err] {
+    set cmd [list | git-diff-tree -r -p -C $p $id]
+    if {[catch {set bdf [open $cmd r]} err]} {
        puts "error getting diffs: $err"
        return
     }
        puts "error getting diffs: $err"
        return
     }
+    set diffinhdr 0
     fconfigure $bdf -blocking 0
     set blobdifffd($ids) $bdf
     set curdifftag Comments
     set curtagstart 0.0
     fconfigure $bdf -blocking 0
     set blobdifffd($ids) $bdf
     set curdifftag Comments
     set curtagstart 0.0
-    set diffindex 0
     catch {unset difffilestart}
     catch {unset difffilestart}
-    fileevent $bdf readable "getblobdiffline $bdf {$ids}"
+    fileevent $bdf readable [list getblobdiffline $bdf $diffids]
     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 diffnexthead diffnextnote diffindex difffilestart
-    global nextupdate
+    global diffids blobdifffd ctext curdifftag curtagstart
+    global diffnexthead diffnextnote difffilestart
+    global nextupdate diffinhdr treediffs
+    global gaudydiff
 
     set n [gets $bdf line]
     if {$n < 0} {
 
     set n [gets $bdf line]
     if {$n < 0} {
@@ -1421,7 +1893,6 @@ 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
            }
        }
        return
            }
        }
        return
@@ -1430,60 +1901,61 @@ 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 newname]} {
        # 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 curtagstart [$ctext index "end - 1c"]
-       set header $fname
-       if {[info exists diffnexthead]} {
-           set fname $diffnexthead
-           set header "$diffnexthead ($diffnextnote)"
-           unset diffnexthead
-       }
+       set header $newname
        set here [$ctext index "end - 1c"]
        set here [$ctext index "end - 1c"]
-       set difffilestart($diffindex) $here
-       incr diffindex
-       # start mark names at fmark.1 for first file
-       $ctext mark set fmark.$diffindex $here
-       $ctext mark gravity fmark.$diffindex left
+       set i [lsearch -exact $treediffs($diffids) $fname]
+       if {$i >= 0} {
+           set difffilestart($i) $here
+           incr i
+           $ctext mark set fmark.$i $here
+           $ctext mark gravity fmark.$i left
+       }
+       if {$newname != $fname} {
+           set i [lsearch -exact $treediffs($diffids) $newname]
+           if {$i >= 0} {
+               set difffilestart($i) $here
+               incr i
+               $ctext mark set fmark.$i $here
+               $ctext mark gravity fmark.$i left
+           }
+       }
        set curdifftag "f:$fname"
        $ctext tag delete $curdifftag
        set l [expr {(78 - [string length $header]) / 2}]
        set pad [string range "----------------------------------------" 1 $l]
        $ctext insert end "$pad $header $pad\n" filesep
        set curdifftag "f:$fname"
        $ctext tag delete $curdifftag
        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 {
@@ -1491,7 +1963,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
            }
@@ -1512,14 +1983,19 @@ proc nextfile {} {
     set here [$ctext index @0,0]
     for {set i 0} {[info exists difffilestart($i)]} {incr i} {
        if {[$ctext compare $difffilestart($i) > $here]} {
     set here [$ctext index @0,0]
     for {set i 0} {[info exists difffilestart($i)]} {incr i} {
        if {[$ctext compare $difffilestart($i) > $here]} {
-           $ctext yview $difffilestart($i)
-           break
+           if {![info exists pos]
+               || [$ctext compare $difffilestart($i) < $pos]} {
+               set pos $difffilestart($i)
+           }
        }
     }
        }
     }
+    if {[info exists pos]} {
+       $ctext yview $pos
+    }
 }
 
 proc listboxsel {} {
 }
 
 proc listboxsel {} {
-    global ctext cflist currentid treediffs seenfile
+    global ctext cflist currentid
     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
@@ -1566,6 +2042,13 @@ proc incrfont {inc} {
     redisplay
 }
 
     redisplay
 }
 
+proc clearsha1 {} {
+    global sha1entry sha1string
+    if {[string length $sha1string] == 40} {
+       $sha1entry delete 0 end
+    }
+}
+
 proc sha1change {n1 n2 op} {
     global sha1string currentid sha1but
     if {$sha1string == {}
 proc sha1change {n1 n2 op} {
     global sha1string currentid sha1but
     if {$sha1string == {}
@@ -1584,18 +2067,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"
@@ -1726,6 +2226,7 @@ proc rowmenu {x y id} {
     }
     $rowctxmenu entryconfigure 0 -state $state
     $rowctxmenu entryconfigure 1 -state $state
     }
     $rowctxmenu entryconfigure 0 -state $state
     $rowctxmenu entryconfigure 1 -state $state
+    $rowctxmenu entryconfigure 2 -state $state
     set rowmenuid $id
     tk_popup $rowctxmenu $x $y
 }
     set rowmenuid $id
     tk_popup $rowctxmenu $x $y
 }
@@ -1733,7 +2234,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} {
@@ -1757,8 +2258,228 @@ 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 $newid [list $oldid]
+}
+
+proc mkpatch {} {
+    global rowmenuid currentid commitinfo patchtop patchnum
+
+    if {![info exists currentid]} return
+    set oldid $currentid
+    set oldhead [lindex $commitinfo($oldid) 0]
+    set newid $rowmenuid
+    set newhead [lindex $commitinfo($newid) 0]
+    set top .patch
+    set patchtop $top
+    catch {destroy $top}
+    toplevel $top
+    label $top.title -text "Generate patch"
+    grid $top.title - -pady 10
+    label $top.from -text "From:"
+    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 -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 -relief flat
+    $top.tosha1 insert 0 $newid
+    $top.tosha1 conf -state readonly
+    grid $top.to $top.tosha1 -sticky w
+    entry $top.tohead -width 60 -relief flat
+    $top.tohead insert 0 $newhead
+    $top.tohead conf -state readonly
+    grid x $top.tohead -sticky w
+    button $top.rev -text "Reverse" -command mkpatchrev -padx 5
+    grid $top.rev x -pady 10
+    label $top.flab -text "Output file:"
+    entry $top.fname -width 60
+    $top.fname insert 0 [file normalize "patch$patchnum.patch"]
+    incr patchnum
+    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
+    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 mkpatchrev {} {
+    global patchtop
+
+    set oldid [$patchtop.fromsha1 get]
+    set oldhead [$patchtop.fromhead get]
+    set newid [$patchtop.tosha1 get]
+    set newhead [$patchtop.tohead get]
+    foreach e [list fromsha1 fromhead tosha1 tohead] \
+           v [list $newid $newhead $oldid $oldhead] {
+       $patchtop.$e conf -state normal
+       $patchtop.$e delete 0 end
+       $patchtop.$e insert 0 $v
+       $patchtop.$e conf -state readonly
+    }
+}
+
+proc mkpatchgo {} {
+    global patchtop
+
+    set oldid [$patchtop.fromsha1 get]
+    set newid [$patchtop.tosha1 get]
+    set fname [$patchtop.fname get]
+    if {[catch {exec git-diff-tree -p $oldid $newid >$fname &} err]} {
+       error_popup "Error creating patch: $err"
+    }
+    catch {destroy $patchtop}
+    unset patchtop
+}
+
+proc mkpatchcan {} {
+    global patchtop
+
+    catch {destroy $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 {} {
 }
 
 proc doquit {} {
@@ -1771,9 +2492,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}
 
@@ -1799,6 +2523,7 @@ foreach arg $argv {
 set stopped 0
 set redisplaying 0
 set stuffsaved 0
 set stopped 0
 set redisplaying 0
 set stuffsaved 0
+set patchnum 0
 setcoords
 makewindow
 readrefs
 setcoords
 makewindow
 readrefs