ls-tree: --name-only
[gitweb.git] / gitk
diff --git a/gitk b/gitk
index 59cdd853e2649c4f1ce38acad366016a11b4f884..a847ef69c7ae87c9035aff23af359e1832abceea 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -1,6 +1,6 @@
 #!/bin/sh
 # Tcl ignores the next line -*- tcl -*- \
-exec wish "$0" -- "${1+$@}"
+exec wish "$0" -- "$@"
 
 # Copyright (C) 2005 Paul Mackerras.  All rights reserved.
 # This program is free software; it may be used, copied, modified
@@ -18,8 +18,8 @@ proc gitdir {} {
 
 proc getcommits {rargs} {
     global commits commfd phase canv mainfont env
-    global startmsecs nextupdate
-    global ctext maincursor textcursor leftover
+    global startmsecs nextupdate ncmupdate
+    global ctext maincursor textcursor leftover gitencoding
 
     # check that we can find a .git directory somewhere...
     set gitdir [gitdir]
@@ -30,7 +30,8 @@ proc getcommits {rargs} {
     set commits {}
     set phase getcommits
     set startmsecs [clock clicks -milliseconds]
-    set nextupdate [expr $startmsecs + 100]
+    set nextupdate [expr {$startmsecs + 100}]
+    set ncmupdate 1
     if [catch {
        set parse_args [concat --default HEAD $rargs]
        set parsed_args [split [eval exec git-rev-parse $parse_args] "\n"]
@@ -42,24 +43,24 @@ proc getcommits {rargs} {
        set parsed_args $rargs
     }
     if [catch {
-       set commfd [open "|git-rev-list --header --topo-order $parsed_args" r]
+       set commfd [open "|git-rev-list --header --topo-order --parents $parsed_args" r]
     } err] {
        puts stderr "Error executing git-rev-list: $err"
        exit 1
     }
     set leftover {}
-    fconfigure $commfd -blocking 0 -translation binary
-    fileevent $commfd readable "getcommitlines $commfd"
+    fconfigure $commfd -blocking 0 -translation lf -encoding $gitencoding
+    fileevent $commfd readable [list getcommitlines $commfd]
     $canv delete all
     $canv create text 3 3 -anchor nw -text "Reading commits..." \
        -font $mainfont -tags textitems
     . config -cursor watch
-    $ctext config -cursor watch
+    settextcursor watch
 }
 
 proc getcommitlines {commfd}  {
-    global commits parents cdate children nchildren
-    global commitlisted phase commitinfo nextupdate
+    global commits parents cdate children
+    global commitlisted phase nextupdate
     global stopped redisplaying leftover
 
     set stuff [read $commfd]
@@ -73,9 +74,9 @@ proc getcommitlines {commfd}  {
        }
        if {[string range $err 0 4] == "usage"} {
            set err \
-{Gitk: error reading commits: bad arguments to git-rev-list.
-(Note: arguments to gitk are passed to git-rev-list
-to allow selection of commits to be displayed.)}
+               "Gitk: error reading commits: bad arguments to git-rev-list.\
+               (Note: arguments to gitk are passed to git-rev-list\
+               to allow selection of commits to be displayed.)"
        } else {
            set err "Error reading commits: $err"
        }
@@ -95,7 +96,19 @@ to allow selection of commits to be displayed.)}
            set leftover {}
        }
        set start [expr {$i + 1}]
-       if {![regexp {^([0-9a-f]{40})\n} $cmit match id]} {
+       set j [string first "\n" $cmit]
+       set ok 0
+       if {$j >= 0} {
+           set ids [string range $cmit 0 [expr {$j - 1}]]
+           set ok 1
+           foreach id $ids {
+               if {![regexp {^[0-9a-f]{40}$} $id]} {
+                   set ok 0
+                   break
+               }
+           }
+       }
+       if {!$ok} {
            set shortcmit $cmit
            if {[string length $shortcmit] > 80} {
                set shortcmit "[string range $shortcmit 0 80]..."
@@ -103,13 +116,15 @@ to allow selection of commits to be displayed.)}
            error_popup "Can't parse git-rev-list output: {$shortcmit}"
            exit 1
        }
-       set cmit [string range $cmit 41 end]
+       set id [lindex $ids 0]
+       set olds [lrange $ids 1 end]
+       set cmit [string range $cmit [expr {$j + 1}] end]
        lappend commits $id
        set commitlisted($id) 1
-       parsecommit $id $cmit 1
+       parsecommit $id $cmit 1 [lrange $ids 1 end]
        drawcommit $id
        if {[clock clicks -milliseconds] >= $nextupdate} {
-           doupdate
+           doupdate 1
        }
        while {$redisplaying} {
            set redisplaying 0
@@ -120,7 +135,7 @@ to allow selection of commits to be displayed.)}
                    drawcommit $id
                    if {$stopped} break
                    if {[clock clicks -milliseconds] >= $nextupdate} {
-                       doupdate
+                       doupdate 1
                    }
                }
            }
@@ -128,21 +143,32 @@ to allow selection of commits to be displayed.)}
     }
 }
 
-proc doupdate {} {
-    global commfd nextupdate
+proc doupdate {reading} {
+    global commfd nextupdate numcommits ncmupdate
 
-    incr nextupdate 100
-    fileevent $commfd readable {}
+    if {$reading} {
+       fileevent $commfd readable {}
+    }
     update
-    fileevent $commfd readable "getcommitlines $commfd"
+    set nextupdate [expr {[clock clicks -milliseconds] + 100}]
+    if {$numcommits < 100} {
+       set ncmupdate [expr {$numcommits + 1}]
+    } elseif {$numcommits < 10000} {
+       set ncmupdate [expr {$numcommits + 10}]
+    } else {
+       set ncmupdate [expr {$numcommits + 100}]
+    }
+    if {$reading} {
+       fileevent $commfd readable [list getcommitlines $commfd]
+    }
 }
 
 proc readcommit {id} {
     if [catch {set contents [exec git-cat-file commit $id]}] return
-    parsecommit $id $contents 0
+    parsecommit $id $contents 0 {}
 }
 
-proc parsecommit {id contents listed} {
+proc parsecommit {id contents listed olds} {
     global commitinfo children nchildren parents nparents cdate ncleft
 
     set inhdr 1
@@ -157,108 +183,101 @@ proc parsecommit {id contents listed} {
        set nchildren($id) 0
        set ncleft($id) 0
     }
-    set parents($id) {}
-    set nparents($id) 0
-    foreach line [split $contents "\n"] {
-       if {$inhdr} {
-           if {$line == {}} {
-               set inhdr 0
-           } else {
-               set tag [lindex $line 0]
-               if {$tag == "parent"} {
-                   set p [lindex $line 1]
-                   if {![info exists nchildren($p)]} {
-                       set children($p) {}
-                       set nchildren($p) 0
-                       set ncleft($p) 0
-                   }
-                   lappend parents($id) $p
-                   incr nparents($id)
-                   # sometimes we get a commit that lists a parent twice...
-                   if {$listed && [lsearch -exact $children($p) $id] < 0} {
-                       lappend children($p) $id
-                       incr nchildren($p)
-                       incr ncleft($p)
-                   }
-               } elseif {$tag == "author"} {
-                   set x [expr {[llength $line] - 2}]
-                   set audate [lindex $line $x]
-                   set auname [lrange $line 1 [expr {$x - 1}]]
-               } elseif {$tag == "committer"} {
-                   set x [expr {[llength $line] - 2}]
-                   set comdate [lindex $line $x]
-                   set comname [lrange $line 1 [expr {$x - 1}]]
-               }
-           }
-       } else {
-           if {$comment == {}} {
-               set headline [string trim $line]
-           } else {
-               append comment "\n"
-           }
-           if {!$listed} {
-               # git-rev-list indents the comment by 4 spaces;
-               # if we got this via git-cat-file, add the indentation
-               append comment "    "
-           }
-           append comment $line
+    set parents($id) $olds
+    set nparents($id) [llength $olds]
+    foreach p $olds {
+       if {![info exists nchildren($p)]} {
+           set children($p) [list $id]
+           set nchildren($p) 1
+           set ncleft($p) 1
+       } elseif {[lsearch -exact $children($p) $id] < 0} {
+           lappend children($p) $id
+           incr nchildren($p)
+           incr ncleft($p)
+       }
+    }
+    set hdrend [string first "\n\n" $contents]
+    if {$hdrend < 0} {
+       # should never happen...
+       set hdrend [string length $contents]
+    }
+    set header [string range $contents 0 [expr {$hdrend - 1}]]
+    set comment [string range $contents [expr {$hdrend + 2}] end]
+    foreach line [split $header "\n"] {
+       set tag [lindex $line 0]
+       if {$tag == "author"} {
+           set audate [lindex $line end-1]
+           set auname [lrange $line 1 end-2]
+       } elseif {$tag == "committer"} {
+           set comdate [lindex $line end-1]
+           set comname [lrange $line 1 end-2]
        }
     }
-    if {$audate != {}} {
-       set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
+    set headline {}
+    # take the first line of the comment as the headline
+    set i [string first "\n" $comment]
+    if {$i >= 0} {
+       set headline [string trim [string range $comment 0 $i]]
+    } else {
+       set headline $comment
+    }
+    if {!$listed} {
+       # git-rev-list indents the comment by 4 spaces;
+       # if we got this via git-cat-file, add the indentation
+       set newcomment {}
+       foreach line [split $comment "\n"] {
+           append newcomment "    "
+           append newcomment $line
+           append newcomment "\n"
+       }
+       set comment $newcomment
     }
     if {$comdate != {}} {
        set cdate($id) $comdate
-       set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
     }
     set commitinfo($id) [list $headline $auname $audate \
                             $comname $comdate $comment]
 }
 
 proc readrefs {} {
-    global tagids idtags headids idheads
-    set tags [glob -nocomplain -types f [gitdir]/refs/tags/*]
-    foreach f $tags {
-       catch {
-           set fd [open $f r]
-           set line [read $fd]
-           if {[regexp {^[0-9a-f]{40}} $line id]} {
-               set direct [file tail $f]
-               set tagids($direct) $id
-               lappend idtags($id) $direct
-               set contents [split [exec git-cat-file tag $id] "\n"]
-               set obj {}
-               set type {}
-               set tag {}
-               foreach l $contents {
-                   if {$l == {}} break
-                   switch -- [lindex $l 0] {
-                       "object" {set obj [lindex $l 1]}
-                       "type" {set type [lindex $l 1]}
-                       "tag" {set tag [string range $l 4 end]}
-                   }
-               }
-               if {$obj != {} && $type == "commit" && $tag != {}} {
-                   set tagids($tag) $obj
-                   lappend idtags($obj) $tag
+    global tagids idtags headids idheads tagcontents
+    global otherrefids idotherrefs
+
+    set refd [open [list | git-ls-remote [gitdir]] r]
+    while {0 <= [set n [gets $refd line]]} {
+       if {![regexp {^([0-9a-f]{40})   refs/([^^]*)$} $line \
+           match id path]} {
+           continue
+       }
+       if {![regexp {^(tags|heads)/(.*)$} $path match type name]} {
+           set type others
+           set name $path
+       }
+       if {$type == "tags"} {
+           set tagids($name) $id
+           lappend idtags($id) $name
+           set obj {}
+           set type {}
+           set tag {}
+           catch {
+               set commit [exec git-rev-parse "$id^0"]
+               if {"$commit" != "$id"} {
+                   set tagids($name) $commit
+                   lappend idtags($commit) $name
                }
+           }           
+           catch {
+               set tagcontents($name) [exec git-cat-file tag "$id"]
            }
-           close $fd
-       }
-    }
-    set heads [glob -nocomplain -types f [gitdir]/refs/heads/*]
-    foreach f $heads {
-       catch {
-           set fd [open $f r]
-           set line [read $fd 40]
-           if {[regexp {^[0-9a-f]{40}} $line id]} {
-               set head [file tail $f]
-               set headids($head) $line
-               lappend idheads($line) $head
-           }
-           close $fd
+       } elseif { $type == "heads" } {
+           set headids($name) $id
+           lappend idheads($id) $name
+       } else {
+           set otherrefids($name) $id
+           lappend idotherrefs($id) $name
        }
     }
+    close $refd
 }
 
 proc error_popup msg {
@@ -277,23 +296,27 @@ proc makewindow {} {
     global canv canv2 canv3 linespc charspc ctext cflist textfont
     global findtype findtypemenu findloc findstring fstring geometry
     global entries sha1entry sha1string sha1but
-    global maincursor textcursor
-    global rowctxmenu gaudydiff mergemax
+    global maincursor textcursor curtextcursor
+    global rowctxmenu mergemax
 
     menu .bar
     .bar add cascade -label "File" -menu .bar.file
     menu .bar.file
+    .bar.file add command -label "Reread references" -command rereadrefs
     .bar.file add command -label "Quit" -command doquit
+    menu .bar.edit
+    .bar add cascade -label "Edit" -menu .bar.edit
+    .bar.edit add command -label "Preferences" -command doprefs
     menu .bar.help
     .bar add cascade -label "Help" -menu .bar.help
     .bar.help add command -label "About gitk" -command about
     . configure -menu .bar
 
     if {![info exists geometry(canv1)]} {
-       set geometry(canv1) [expr 45 * $charspc]
-       set geometry(canv2) [expr 30 * $charspc]
-       set geometry(canv3) [expr 15 * $charspc]
-       set geometry(canvh) [expr 25 * $linespc + 4]
+       set geometry(canv1) [expr {45 * $charspc}]
+       set geometry(canv2) [expr {30 * $charspc}]
+       set geometry(canv3) [expr {15 * $charspc}]
+       set geometry(canvh) [expr {25 * $linespc + 4}]
        set geometry(ctextw) 80
        set geometry(ctexth) 30
        set geometry(cflistw) 30
@@ -387,32 +410,26 @@ proc makewindow {} {
     set ctext .ctop.cdet.left.ctext
     text $ctext -bg white -state disabled -font $textfont \
        -width $geometry(ctextw) -height $geometry(ctexth) \
-       -yscrollcommand ".ctop.cdet.left.sb set"
+       -yscrollcommand ".ctop.cdet.left.sb set" -wrap none
     scrollbar .ctop.cdet.left.sb -command "$ctext yview"
     pack .ctop.cdet.left.sb -side right -fill y
     pack $ctext -side left -fill both -expand 1
     .ctop.cdet add .ctop.cdet.left
 
     $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 m0 -fore red
-       $ctext tag conf m1 -fore blue
-       $ctext tag conf m2 -fore green
-       $ctext tag conf m3 -fore purple
-       $ctext tag conf m4 -fore brown
-       $ctext tag conf mmax -fore darkgrey
-       set mergemax 5
-       $ctext tag conf mresult -font [concat $textfont bold]
-       $ctext tag conf msep -font [concat $textfont bold]
-       $ctext tag conf found -back yellow
-    }
+    $ctext tag conf hunksep -fore blue
+    $ctext tag conf d0 -fore red
+    $ctext tag conf d1 -fore "#00a000"
+    $ctext tag conf m0 -fore red
+    $ctext tag conf m1 -fore blue
+    $ctext tag conf m2 -fore green
+    $ctext tag conf m3 -fore purple
+    $ctext tag conf m4 -fore brown
+    $ctext tag conf mmax -fore darkgrey
+    set mergemax 5
+    $ctext tag conf mresult -font [concat $textfont bold]
+    $ctext tag conf msep -font [concat $textfont bold]
+    $ctext tag conf found -back yellow
 
     frame .ctop.cdet.right
     set cflist .ctop.cdet.right.cfiles
@@ -434,6 +451,8 @@ proc makewindow {} {
     bindall <B2-Motion> "allcanvs scan dragto 0 %y"
     bind . <Key-Up> "selnextline -1"
     bind . <Key-Down> "selnextline 1"
+    bind . <Key-Right> "goforw"
+    bind . <Key-Left> "goback"
     bind . <Key-Prior> "allcanvs yview scroll -1 pages"
     bind . <Key-Next> "allcanvs yview scroll 1 pages"
     bindkey <Key-Delete> "$ctext yview scroll -1 pages"
@@ -441,6 +460,12 @@ proc makewindow {} {
     bindkey <Key-space> "$ctext yview scroll 1 pages"
     bindkey p "selnextline -1"
     bindkey n "selnextline 1"
+    bindkey z "goback"
+    bindkey x "goforw"
+    bindkey i "selnextline -1"
+    bindkey k "selnextline 1"
+    bindkey j "goback"
+    bindkey l "goforw"
     bindkey b "$ctext yview scroll -1 pages"
     bindkey d "$ctext yview scroll 18 units"
     bindkey u "$ctext yview scroll -18 units"
@@ -465,6 +490,7 @@ proc makewindow {} {
 
     set maincursor [. cget -cursor]
     set textcursor [$ctext cget -cursor]
+    set curtextcursor $textcursor
 
     set rowctxmenu .rowctxmenu
     menu $rowctxmenu -tearoff 0
@@ -504,7 +530,8 @@ proc click {w} {
 
 proc savestuff {w} {
     global canv canv2 canv3 ctext cflist mainfont textfont
-    global stuffsaved findmergefiles gaudydiff maxgraphpct
+    global stuffsaved findmergefiles maxgraphpct
+    global maxwidth
 
     if {$stuffsaved} return
     if {![winfo viewable .]} return
@@ -513,14 +540,14 @@ proc savestuff {w} {
        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 [list set maxgraphpct $maxgraphpct]
+       puts $f [list set maxwidth $maxwidth]
        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(canv2) [expr [winfo width $canv2]-2]"
-       puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
-       puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
+       puts $f "set geometry(canv1) [expr {[winfo width $canv]-2}]"
+       puts $f "set geometry(canv2) [expr {[winfo width $canv2]-2}]"
+       puts $f "set geometry(canv3) [expr {[winfo width $canv3]-2}]"
+       puts $f "set geometry(canvh) [expr {[winfo height $canv]-2}]"
        set wid [expr {([winfo width $ctext] - 8) \
                           / [font measure $textfont "0"]}]
        puts $f "set geometry(ctextw) $wid"
@@ -549,12 +576,12 @@ proc resizeclistpanes {win w} {
                set sash0 30
            }
            if {$sash1 < $sash0 + 20} {
-               set sash1 [expr $sash0 + 20]
+               set sash1 [expr {$sash0 + 20}]
            }
            if {$sash1 > $w - 10} {
-               set sash1 [expr $w - 10]
+               set sash1 [expr {$w - 10}]
                if {$sash0 > $sash1 - 20} {
-                   set sash0 [expr $sash1 - 20]
+                   set sash0 [expr {$sash1 - 20}]
                }
            }
        }
@@ -577,7 +604,7 @@ proc resizecdetpanes {win w} {
                set sash0 45
            }
            if {$sash0 > $w - 15} {
-               set sash0 [expr $w - 15]
+               set sash0 [expr {$w - 15}]
            }
        }
        $win sash place 0 $sash0 [lindex $s0 1]
@@ -620,7 +647,7 @@ Use and redistribute under the terms of the GNU General Public License} \
 }
 
 proc assigncolor {id} {
-    global commitinfo colormap commcolors colors nextcolor
+    global colormap commcolors colors nextcolor
     global parents nparents children nchildren
     global cornercrossings crossings
 
@@ -691,21 +718,24 @@ proc assigncolor {id} {
 }
 
 proc initgraph {} {
-    global canvy canvy0 lineno numcommits lthickness nextcolor linespc
-    global mainline sidelines
+    global canvy canvy0 lineno numcommits nextcolor linespc
+    global mainline mainlinearrow sidelines
     global nchildren ncleft
+    global displist nhyperspace
 
     allcanvs delete all
     set nextcolor 0
     set canvy $canvy0
     set lineno -1
     set numcommits 0
-    set lthickness [expr {int($linespc / 9) + 1}]
     catch {unset mainline}
+    catch {unset mainlinearrow}
     catch {unset sidelines}
     foreach id [array names nchildren] {
        set ncleft($id) $nchildren($id)
     }
+    set displist {}
+    set nhyperspace 0
 }
 
 proc bindline {t id} {
@@ -714,22 +744,52 @@ proc bindline {t id} {
     $canv bind $t <Enter> "lineenter %x %y $id"
     $canv bind $t <Motion> "linemotion %x %y $id"
     $canv bind $t <Leave> "lineleave $id"
-    $canv bind $t <Button-1> "lineclick %x %y $id"
+    $canv bind $t <Button-1> "lineclick %x %y $id 1"
+}
+
+proc drawlines {id xtra delold} {
+    global mainline mainlinearrow sidelines lthickness colormap canv
+
+    if {$delold} {
+       $canv delete lines.$id
+    }
+    if {[info exists mainline($id)]} {
+       set t [$canv create line $mainline($id) \
+                  -width [expr {($xtra + 1) * $lthickness}] \
+                  -fill $colormap($id) -tags lines.$id \
+                  -arrow $mainlinearrow($id)]
+       $canv lower $t
+       bindline $t $id
+    }
+    if {[info exists sidelines($id)]} {
+       foreach ls $sidelines($id) {
+           set coords [lindex $ls 0]
+           set thick [lindex $ls 1]
+           set arrow [lindex $ls 2]
+           set t [$canv create line $coords -fill $colormap($id) \
+                      -width [expr {($thick + $xtra) * $lthickness}] \
+                      -arrow $arrow -tags lines.$id]
+           $canv lower $t
+           bindline $t $id
+       }
+    }
 }
 
+# level here is an index in displist
 proc drawcommitline {level} {
-    global parents children nparents nchildren todo
+    global parents children nparents displist
     global canv canv2 canv3 mainfont namefont canvy linespc
     global lineid linehtag linentag linedtag commitinfo
     global colormap numcommits currentparents dupparents
-    global oldlevel oldnlines oldtodo
-    global idtags idline idheads
-    global lineno lthickness mainline sidelines
-    global commitlisted rowtextx idpos
+    global idtags idline idheads idotherrefs
+    global lineno lthickness mainline mainlinearrow sidelines
+    global commitlisted rowtextx idpos lastuse displist
+    global oldnlines olddlevel olddisplist
 
     incr numcommits
     incr lineno
-    set id [lindex $todo $level]
+    set id [lindex $displist $level]
+    set lastuse($id) $lineno
     set lineid($lineno) $id
     set idline($id) $lineno
     set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
@@ -755,44 +815,36 @@ proc drawcommitline {level} {
     }
     set x [xcoord $level $level $lineno]
     set y1 $canvy
-    set canvy [expr $canvy + $linespc]
+    set canvy [expr {$canvy + $linespc}]
     allcanvs conf -scrollregion \
-       [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
+       [list 0 0 0 [expr {$y1 + 0.5 * $linespc + 2}]]
     if {[info exists mainline($id)]} {
        lappend mainline($id) $x $y1
-       set t [$canv create line $mainline($id) \
-                  -width $lthickness -fill $colormap($id)]
-       $canv lower $t
-       bindline $t $id
-    }
-    if {[info exists sidelines($id)]} {
-       foreach ls $sidelines($id) {
-           set coords [lindex $ls 0]
-           set thick [lindex $ls 1]
-           set t [$canv create line $coords -fill $colormap($id) \
-                      -width [expr {$thick * $lthickness}]]
-           $canv lower $t
-           bindline $t $id
+       if {$mainlinearrow($id) ne "none"} {
+           set mainline($id) [trimdiagstart $mainline($id)]
        }
     }
+    drawlines $id 0 0
     set orad [expr {$linespc / 3}]
-    set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
-              [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
+    set t [$canv create oval [expr {$x - $orad}] [expr {$y1 - $orad}] \
+              [expr {$x + $orad - 1}] [expr {$y1 + $orad - 1}] \
               -fill $ofill -outline black -width 1]
     $canv raise $t
     $canv bind $t <1> {selcanvline {} %x %y}
-    set xt [xcoord [llength $todo] $level $lineno]
+    set xt [xcoord [llength $displist] $level $lineno]
     if {[llength $currentparents] > 2} {
        set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
     }
     set rowtextx($lineno) $xt
     set idpos($id) [list $x $xt $y1]
-    if {[info exists idtags($id)] || [info exists idheads($id)]} {
+    if {[info exists idtags($id)] || [info exists idheads($id)]
+       || [info exists idotherrefs($id)]} {
        set xt [drawtags $id $x $xt $y1]
     }
     set headline [lindex $commitinfo($id) 0]
     set name [lindex $commitinfo($id) 1]
     set date [lindex $commitinfo($id) 2]
+    set date [formatdate $date]
     set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
                               -text $headline -font $mainfont ]
     $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
@@ -800,29 +852,38 @@ proc drawcommitline {level} {
                               -text $name -font $namefont]
     set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
                               -text $date -font $mainfont]
+
+    set olddlevel $level
+    set olddisplist $displist
+    set oldnlines [llength $displist]
 }
 
 proc drawtags {id x xt y1} {
-    global idtags idheads
+    global idtags idheads idotherrefs
     global linespc lthickness
-    global canv mainfont
+    global canv mainfont idline rowtextx
 
     set marks {}
     set ntags 0
+    set nheads 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)]
+       set nheads [llength $idheads($id)]
+    }
+    if {[info exists idotherrefs($id)]} {
+       set marks [concat $marks $idotherrefs($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 yt [expr {$y1 - 0.5 * $linespc}]
+    set yb [expr {$yt + $linespc - 1}]
     set xvals {}
     set wvals {}
     foreach tag $marks {
@@ -835,65 +896,40 @@ proc drawtags {id x xt 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]
+       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
+           set t [$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]
+           $canv bind $t <1> [list showtag $tag 1]
+           set rowtextx($idline($id)) [expr {$xr + $linespc}]
        } else {
-           # draw a head
-           set xl [expr $xl - $delta/2]
+           # draw a head or other ref
+           if {[incr nheads -1] >= 0} {
+               set col green
+           } else {
+               set col "#ddddff"
+           }
+           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
-    global canvy linespc mainline
-    global commitinfo lineno xspc1
-
-    set oldlevel $level
-    set oldtodo $todo
-    set oldnlines [llength $todo]
-    if {!$noshortcut && [llength $currentparents] == 1} {
-       set p [lindex $currentparents 0]
-       if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
-           set ncleft($p) 0
-           set x [xcoord $level $level $lineno]
-           set y [expr $canvy - $linespc]
-           set mainline($p) [list $x $y]
-           set todo [lreplace $todo $level $level $p]
-           set xspc1([expr {$lineno + 1}]) $xspc1($lineno)
-           return 0
+               -width 1 -outline black -fill $col -tags tag.$id
        }
-    }
-
-    set todo [lreplace $todo $level $level]
-    set i $level
-    foreach p $currentparents {
-       incr ncleft($p) -1
-       set k [lsearch -exact $todo $p]
-       if {$k < 0} {
-           set todo [linsert $todo $i $p]
-           incr i
+       set t [$canv create text $xl $y1 -anchor w -text $tag \
+                  -font $mainfont -tags tag.$id]
+       if {$ntags >= 0} {
+           $canv bind $t <1> [list showtag $tag 1]
        }
     }
-    return 1
+    return $xt
 }
 
 proc notecrossings {id lo hi corner} {
-    global oldtodo crossings cornercrossings
+    global olddisplist crossings cornercrossings
 
     for {set i $lo} {[incr i] < $hi} {} {
-       set p [lindex $oldtodo $i]
+       set p [lindex $olddisplist $i]
        if {$p == {}} continue
        if {$i == $corner} {
            if {![info exists cornercrossings($id)]
@@ -929,37 +965,219 @@ proc xcoord {i level ln} {
     return $x
 }
 
-proc drawslants {level} {
-    global canv mainline sidelines canvx0 canvy xspc1 xspc2 lthickness
-    global oldlevel oldtodo todo currentparents dupparents
+# it seems Tk can't draw arrows on the end of diagonal line segments...
+proc trimdiagend {line} {
+    while {[llength $line] > 4} {
+       set x1 [lindex $line end-3]
+       set y1 [lindex $line end-2]
+       set x2 [lindex $line end-1]
+       set y2 [lindex $line end]
+       if {($x1 == $x2) != ($y1 == $y2)} break
+       set line [lreplace $line end-1 end]
+    }
+    return $line
+}
+
+proc trimdiagstart {line} {
+    while {[llength $line] > 4} {
+       set x1 [lindex $line 0]
+       set y1 [lindex $line 1]
+       set x2 [lindex $line 2]
+       set y2 [lindex $line 3]
+       if {($x1 == $x2) != ($y1 == $y2)} break
+       set line [lreplace $line 0 1]
+    }
+    return $line
+}
+
+proc drawslants {id needonscreen nohs} {
+    global canv mainline mainlinearrow sidelines
+    global canvx0 canvy xspc1 xspc2 lthickness
+    global currentparents dupparents
     global lthickness linespc canvy colormap lineno geometry
-    global maxgraphpct
+    global maxgraphpct maxwidth
+    global displist onscreen lastuse
+    global parents commitlisted
+    global oldnlines olddlevel olddisplist
+    global nhyperspace numcommits nnewparents
+
+    if {$lineno < 0} {
+       lappend displist $id
+       set onscreen($id) 1
+       return 0
+    }
+
+    set y1 [expr {$canvy - $linespc}]
+    set y2 $canvy
+
+    # work out what we need to get back on screen
+    set reins {}
+    if {$onscreen($id) < 0} {
+       # next to do isn't displayed, better get it on screen...
+       lappend reins [list $id 0]
+    }
+    # make sure all the previous commits's parents are on the screen
+    foreach p $currentparents {
+       if {$onscreen($p) < 0} {
+           lappend reins [list $p 0]
+       }
+    }
+    # bring back anything requested by caller
+    if {$needonscreen ne {}} {
+       lappend reins $needonscreen
+    }
+
+    # try the shortcut
+    if {$currentparents == $id && $onscreen($id) == 0 && $reins eq {}} {
+       set dlevel $olddlevel
+       set x [xcoord $dlevel $dlevel $lineno]
+       set mainline($id) [list $x $y1]
+       set mainlinearrow($id) none
+       set lastuse($id) $lineno
+       set displist [lreplace $displist $dlevel $dlevel $id]
+       set onscreen($id) 1
+       set xspc1([expr {$lineno + 1}]) $xspc1($lineno)
+       return $dlevel
+    }
+
+    # update displist
+    set displist [lreplace $displist $olddlevel $olddlevel]
+    set j $olddlevel
+    foreach p $currentparents {
+       set lastuse($p) $lineno
+       if {$onscreen($p) == 0} {
+           set displist [linsert $displist $j $p]
+           set onscreen($p) 1
+           incr j
+       }
+    }
+    if {$onscreen($id) == 0} {
+       lappend displist $id
+       set onscreen($id) 1
+    }
+
+    # remove the null entry if present
+    set nullentry [lsearch -exact $displist {}]
+    if {$nullentry >= 0} {
+       set displist [lreplace $displist $nullentry $nullentry]
+    }
+
+    # bring back the ones we need now (if we did it earlier
+    # it would change displist and invalidate olddlevel)
+    foreach pi $reins {
+       # test again in case of duplicates in reins
+       set p [lindex $pi 0]
+       if {$onscreen($p) < 0} {
+           set onscreen($p) 1
+           set lastuse($p) $lineno
+           set displist [linsert $displist [lindex $pi 1] $p]
+           incr nhyperspace -1
+       }
+    }
+
+    set lastuse($id) $lineno
+
+    # see if we need to make any lines jump off into hyperspace
+    set displ [llength $displist]
+    if {$displ > $maxwidth} {
+       set ages {}
+       foreach x $displist {
+           lappend ages [list $lastuse($x) $x]
+       }
+       set ages [lsort -integer -index 0 $ages]
+       set k 0
+       while {$displ > $maxwidth} {
+           set use [lindex $ages $k 0]
+           set victim [lindex $ages $k 1]
+           if {$use >= $lineno - 5} break
+           incr k
+           if {[lsearch -exact $nohs $victim] >= 0} continue
+           set i [lsearch -exact $displist $victim]
+           set displist [lreplace $displist $i $i]
+           set onscreen($victim) -1
+           incr nhyperspace
+           incr displ -1
+           if {$i < $nullentry} {
+               incr nullentry -1
+           }
+           set x [lindex $mainline($victim) end-1]
+           lappend mainline($victim) $x $y1
+           set line [trimdiagend $mainline($victim)]
+           set arrow "last"
+           if {$mainlinearrow($victim) ne "none"} {
+               set line [trimdiagstart $line]
+               set arrow "both"
+           }
+           lappend sidelines($victim) [list $line 1 $arrow]
+           unset mainline($victim)
+       }
+    }
+
+    set dlevel [lsearch -exact $displist $id]
+
+    # If we are reducing, put in a null entry
+    if {$displ < $oldnlines} {
+       # does the next line look like a merge?
+       # i.e. does it have > 1 new parent?
+       if {$nnewparents($id) > 1} {
+           set i [expr {$dlevel + 1}]
+       } elseif {$nnewparents([lindex $olddisplist $olddlevel]) == 0} {
+           set i $olddlevel
+           if {$nullentry >= 0 && $nullentry < $i} {
+               incr i -1
+           }
+       } elseif {$nullentry >= 0} {
+           set i $nullentry
+           while {$i < $displ
+                  && [lindex $olddisplist $i] == [lindex $displist $i]} {
+               incr i
+           }
+       } else {
+           set i $olddlevel
+           if {$dlevel >= $i} {
+               incr i
+           }
+       }
+       if {$i < $displ} {
+           set displist [linsert $displist $i {}]
+           incr displ
+           if {$dlevel >= $i} {
+               incr dlevel
+           }
+       }
+    }
 
     # decide on the line spacing for the next line
     set lj [expr {$lineno + 1}]
     set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}]
-    set n [llength $todo]
-    if {$n <= 1 || $canvx0 + $n * $xspc2 <= $maxw} {
+    if {$displ <= 1 || $canvx0 + $displ * $xspc2 <= $maxw} {
        set xspc1($lj) $xspc2
     } else {
-       set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($n - 1)}]
+       set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($displ - 1)}]
        if {$xspc1($lj) < $lthickness} {
            set xspc1($lj) $lthickness
        }
     }
-    
-    set y1 [expr $canvy - $linespc]
-    set y2 $canvy
+
+    foreach idi $reins {
+       set id [lindex $idi 0]
+       set j [lsearch -exact $displist $id]
+       set xj [xcoord $j $dlevel $lj]
+       set mainline($id) [list $xj $y2]
+       set mainlinearrow($id) first
+    }
+
     set i -1
-    foreach id $oldtodo {
+    foreach id $olddisplist {
        incr i
        if {$id == {}} continue
-       set xi [xcoord $i $oldlevel $lineno]
-       if {$i == $oldlevel} {
+       if {$onscreen($id) <= 0} continue
+       set xi [xcoord $i $olddlevel $lineno]
+       if {$i == $olddlevel} {
            foreach p $currentparents {
-               set j [lsearch -exact $todo $p]
+               set j [lsearch -exact $displist $p]
                set coords [list $xi $y1]
-               set xj [xcoord $j $level $lj]
+               set xj [xcoord $j $dlevel $lj]
                if {$xj < $xi - $linespc} {
                    lappend coords [expr {$xj + $linespc}] $y1
                    notecrossings $p $j $i [expr {$j + 1}]
@@ -970,9 +1188,10 @@ proc drawslants {level} {
                if {[lsearch -exact $dupparents $p] >= 0} {
                    # draw a double-width line to indicate the doubled parent
                    lappend coords $xj $y2
-                   lappend sidelines($p) [list $coords 2]
+                   lappend sidelines($p) [list $coords 2 none]
                    if {![info exists mainline($p)]} {
                        set mainline($p) [list $xj $y2]
+                       set mainlinearrow($p) none
                    }
                } else {
                    # normal case, no parent duplicated
@@ -986,48 +1205,175 @@ proc drawslants {level} {
                            lappend coords $xj $yb
                        }
                        set mainline($p) $coords
+                       set mainlinearrow($p) none
                    } else {
                        lappend coords $xj $yb
                        if {$yb < $y2} {
                            lappend coords $xj $y2
                        }
-                       lappend sidelines($p) [list $coords 1]
+                       lappend sidelines($p) [list $coords 1 none]
                    }
                }
            }
        } else {
            set j $i
-           if {[lindex $todo $i] != $id} {
-               set j [lsearch -exact $todo $id]
+           if {[lindex $displist $i] != $id} {
+               set j [lsearch -exact $displist $id]
            }
            if {$j != $i || $xspc1($lineno) != $xspc1($lj)
-               || ($oldlevel <= $i && $i <= $level)
-               || ($level <= $i && $i <= $oldlevel)} {
-               set xj [xcoord $j $level $lj]
-               set dx [expr {abs($xi - $xj)}]
-               set yb $y2
-               if {0 && $dx < $linespc} {
-                   set yb [expr {$y1 + $dx}]
+               || ($olddlevel < $i && $i < $dlevel)
+               || ($dlevel < $i && $i < $olddlevel)} {
+               set xj [xcoord $j $dlevel $lj]
+               lappend mainline($id) $xi $y1 $xj $y2
+           }
+       }
+    }
+    return $dlevel
+}
+
+# search for x in a list of lists
+proc llsearch {llist x} {
+    set i 0
+    foreach l $llist {
+       if {$l == $x || [lsearch -exact $l $x] >= 0} {
+           return $i
+       }
+       incr i
+    }
+    return -1
+}
+
+proc drawmore {reading} {
+    global displayorder numcommits ncmupdate nextupdate
+    global stopped nhyperspace parents commitlisted
+    global maxwidth onscreen displist currentparents olddlevel
+
+    set n [llength $displayorder]
+    while {$numcommits < $n} {
+       set id [lindex $displayorder $numcommits]
+       set ctxend [expr {$numcommits + 10}]
+       if {!$reading && $ctxend > $n} {
+           set ctxend $n
+       }
+       set dlist {}
+       if {$numcommits > 0} {
+           set dlist [lreplace $displist $olddlevel $olddlevel]
+           set i $olddlevel
+           foreach p $currentparents {
+               if {$onscreen($p) == 0} {
+                   set dlist [linsert $dlist $i $p]
+                   incr i
+               }
+           }
+       }
+       set nohs {}
+       set reins {}
+       set isfat [expr {[llength $dlist] > $maxwidth}]
+       if {$nhyperspace > 0 || $isfat} {
+           if {$ctxend > $n} break
+           # work out what to bring back and
+           # what we want to don't want to send into hyperspace
+           set room 1
+           for {set k $numcommits} {$k < $ctxend} {incr k} {
+               set x [lindex $displayorder $k]
+               set i [llsearch $dlist $x]
+               if {$i < 0} {
+                   set i [llength $dlist]
+                   lappend dlist $x
+               }
+               if {[lsearch -exact $nohs $x] < 0} {
+                   lappend nohs $x
+               }
+               if {$reins eq {} && $onscreen($x) < 0 && $room} {
+                   set reins [list $x $i]
+               }
+               set newp {}
+               if {[info exists commitlisted($x)]} {
+                   set right 0
+                   foreach p $parents($x) {
+                       if {[llsearch $dlist $p] < 0} {
+                           lappend newp $p
+                           if {[lsearch -exact $nohs $p] < 0} {
+                               lappend nohs $p
+                           }
+                           if {$reins eq {} && $onscreen($p) < 0 && $room} {
+                               set reins [list $p [expr {$i + $right}]]
+                           }
+                       }
+                       set right 1
+                   }
+               }
+               set l [lindex $dlist $i]
+               if {[llength $l] == 1} {
+                   set l $newp
+               } else {
+                   set j [lsearch -exact $l $x]
+                   set l [concat [lreplace $l $j $j] $newp]
                }
-               lappend mainline($id) $xi $y1 $xj $yb
+               set dlist [lreplace $dlist $i $i $l]
+               if {$room && $isfat && [llength $newp] <= 1} {
+                   set room 0
+               }
+           }
+       }
+
+       set dlevel [drawslants $id $reins $nohs]
+       drawcommitline $dlevel
+       if {[clock clicks -milliseconds] >= $nextupdate
+           && $numcommits >= $ncmupdate} {
+           doupdate $reading
+           if {$stopped} break
+       }
+    }
+}
+
+# level here is an index in todo
+proc updatetodo {level noshortcut} {
+    global ncleft todo nnewparents
+    global commitlisted parents onscreen
+
+    set id [lindex $todo $level]
+    set olds {}
+    if {[info exists commitlisted($id)]} {
+       foreach p $parents($id) {
+           if {[lsearch -exact $olds $p] < 0} {
+               lappend olds $p
            }
        }
     }
+    if {!$noshortcut && [llength $olds] == 1} {
+       set p [lindex $olds 0]
+       if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
+           set ncleft($p) 0
+           set todo [lreplace $todo $level $level $p]
+           set onscreen($p) 0
+           set nnewparents($id) 1
+           return 0
+       }
+    }
+
+    set todo [lreplace $todo $level $level]
+    set i $level
+    set n 0
+    foreach p $olds {
+       incr ncleft($p) -1
+       set k [lsearch -exact $todo $p]
+       if {$k < 0} {
+           set todo [linsert $todo $i $p]
+           set onscreen($p) 0
+           incr i
+           incr n
+       }
+    }
+    set nnewparents($id) $n
+
+    return 1
 }
 
 proc decidenext {{noread 0}} {
-    global parents children nchildren ncleft todo
-    global canv canv2 canv3 mainfont namefont canvy linespc
+    global ncleft todo
     global datemode cdate
     global commitinfo
-    global currentparents oldlevel oldnlines oldtodo
-    global lineno lthickness
-
-    # remove the null entry if present
-    set nullentry [lsearch -exact $todo {}]
-    if {$nullentry >= 0} {
-       set todo [lreplace $todo $nullentry $nullentry]
-    }
 
     # choose which one to do next time around
     set todol [llength $todo]
@@ -1063,53 +1409,38 @@ proc decidenext {{noread 0}} {
        return -1
     }
 
-    # If we are reducing, put in a null entry
-    if {$todol < $oldnlines} {
-       if {$nullentry >= 0} {
-           set i $nullentry
-           while {$i < $todol
-                  && [lindex $oldtodo $i] == [lindex $todo $i]} {
-               incr i
-           }
-       } else {
-           set i $oldlevel
-           if {$level >= $i} {
-               incr i
-           }
-       }
-       if {$i < $todol} {
-           set todo [linsert $todo $i {}]
-           if {$level >= $i} {
-               incr level
-           }
-       }
-    }
     return $level
 }
 
 proc drawcommit {id} {
-    global phase todo nchildren datemode nextupdate
-    global startcommits
+    global phase todo nchildren datemode nextupdate revlistorder
+    global numcommits ncmupdate displayorder todo onscreen parents
 
     if {$phase != "incrdraw"} {
        set phase incrdraw
-       set todo $id
-       set startcommits $id
+       set displayorder {}
+       set todo {}
        initgraph
-       drawcommitline 0
-       updatetodo 0 $datemode
-    } else {
-       if {$nchildren($id) == 0} {
-           lappend todo $id
-           lappend startcommits $id
+    }
+    if {$nchildren($id) == 0} {
+       lappend todo $id
+       set onscreen($id) 0
+    }
+    if {$revlistorder} {
+       set level [lsearch -exact $todo $id]
+       if {$level < 0} {
+           error_popup "oops, $id isn't in todo"
+           return
        }
+       lappend displayorder $id
+       updatetodo $level 0
+    } else {
        set level [decidenext 1]
        if {$level == {} || $id != [lindex $todo $level]} {
            return
        }
        while 1 {
-           drawslants $level
-           drawcommitline $level
+           lappend displayorder [lindex $todo $level]
            if {[updatetodo $level $datemode]} {
                set level [decidenext 1]
                if {$level == {}} break
@@ -1118,17 +1449,13 @@ proc drawcommit {id} {
            if {![info exists commitlisted($id)]} {
                break
            }
-           if {[clock clicks -milliseconds] >= $nextupdate} {
-               doupdate
-               if {$stopped} break
-           }
        }
     }
+    drawmore 1
 }
 
 proc finishcommits {} {
     global phase
-    global startcommits
     global canv mainfont ctext maincursor textcursor
 
     if {$phase != "incrdraw"} {
@@ -1137,65 +1464,59 @@ proc finishcommits {} {
            -font $mainfont -tags textitems
        set phase {}
     } else {
-       set level [decidenext]
-       drawslants $level
-       drawrest $level [llength $startcommits]
+       drawrest
     }
     . config -cursor $maincursor
-    $ctext config -cursor $textcursor
+    settextcursor $textcursor
+}
+
+# Don't change the text pane cursor if it is currently the hand cursor,
+# showing that we are over a sha1 ID link.
+proc settextcursor {c} {
+    global ctext curtextcursor
+
+    if {[$ctext cget -cursor] == $curtextcursor} {
+       $ctext config -cursor $c
+    }
+    set curtextcursor $c
 }
 
 proc drawgraph {} {
-    global nextupdate startmsecs startcommits todo
+    global nextupdate startmsecs ncmupdate
+    global displayorder onscreen
 
-    if {$startcommits == {}} return
+    if {$displayorder == {}} return
     set startmsecs [clock clicks -milliseconds]
-    set nextupdate [expr $startmsecs + 100]
+    set nextupdate [expr {$startmsecs + 100}]
+    set ncmupdate 1
     initgraph
-    set todo [lindex $startcommits 0]
-    drawrest 0 1
+    foreach id $displayorder {
+       set onscreen($id) 0
+    }
+    drawmore 0
 }
 
-proc drawrest {level startix} {
+proc drawrest {} {
     global phase stopped redisplaying selectedline
-    global datemode currentparents todo
-    global numcommits
-    global nextupdate startmsecs startcommits idline
+    global datemode todo displayorder
+    global numcommits ncmupdate
+    global nextupdate startmsecs revlistorder
 
+    set level [decidenext]
     if {$level >= 0} {
        set phase drawgraph
-       set startid [lindex $startcommits $startix]
-       set startline -1
-       if {$startid != {}} {
-           set startline $idline($startid)
-       }
        while 1 {
-           if {$stopped} break
-           drawcommitline $level
+           lappend displayorder [lindex $todo $level]
            set hard [updatetodo $level $datemode]
-           if {$numcommits == $startline} {
-               lappend todo $startid
-               set hard 1
-               incr startix
-               set startid [lindex $startcommits $startix]
-               set startline -1
-               if {$startid != {}} {
-                   set startline $idline($startid)
-               }
-           }
            if {$hard} {
                set level [decidenext]
                if {$level < 0} break
-               drawslants $level
-           }
-           if {[clock clicks -milliseconds] >= $nextupdate} {
-               update
-               incr nextupdate 100
            }
        }
     }
+    drawmore 0
     set phase {}
-    set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
+    set drawmsecs [expr {[clock clicks -milliseconds] - $startmsecs}]
     #puts "overall $drawmsecs ms for $numcommits commits"
     if {$redisplaying} {
        if {$stopped == 0 && [info exists selectedline]} {
@@ -1223,8 +1544,8 @@ proc findmatches {f} {
        set matches {}
        set i 0
        while {[set j [string first $foundstring $str $i]] >= 0} {
-           lappend matches [list $j [expr $j+$foundstrlen-1]]
-           set i [expr $j + $foundstrlen]
+           lappend matches [list $j [expr {$j+$foundstrlen-1}]]
+           set i [expr {$j + $foundstrlen}]
        }
     }
     return $matches
@@ -1305,7 +1626,7 @@ proc findselectline {l} {
        set matches [findmatches $f]
        foreach match $matches {
            set start [lindex $match 0]
-           set end [expr [lindex $match 1] + 1]
+           set end [expr {[lindex $match 1] + 1}]
            $ctext tag add found "1.0 + $start c" "1.0 + $end c"
        }
     }
@@ -1377,7 +1698,7 @@ proc stopfindproc {{done 0}} {
        unset findinprogress
        if {$phase != "incrdraw"} {
            . config -cursor $maincursor
-           $ctext config -cursor $textcursor
+           settextcursor $textcursor
        }
     }
 }
@@ -1420,7 +1741,7 @@ proc findpatches {} {
     fileevent $f readable readfindproc
     set finddidsel 0
     . config -cursor watch
-    $ctext config -cursor watch
+    settextcursor watch
     set findinprogress 1
 }
 
@@ -1525,7 +1846,7 @@ proc findfiles {} {
     set id $lineid($l)
     set p [lindex $parents($id) 0]
     . config -cursor watch
-    $ctext config -cursor watch
+    settextcursor watch
     set findinprogress 1
     findcont [list $id $p]
     update
@@ -1659,9 +1980,10 @@ proc markmatches {canv l str tag matches font} {
        set start [lindex $match 0]
        set end [lindex $match 1]
        if {$start > $end} continue
-       set xoff [font measure $font [string range $str 0 [expr $start-1]]]
-       set xlen [font measure $font [string range $str 0 [expr $end]]]
-       set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
+       set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
+       set xlen [font measure $font [string range $str 0 [expr {$end}]]]
+       set t [$canv create rect [expr {$x0+$xoff}] $y0 \
+                  [expr {$x0+$xlen+2}] $y1 \
                   -outline {} -tags matches -fill yellow]
        $canv lower $t
     }
@@ -1675,7 +1997,7 @@ proc unmarkmatches {} {
 }
 
 proc selcanvline {w x y} {
-    global canv canvy0 ctext linespc selectedline
+    global canv canvy0 ctext linespc
     global lineid linehtag linentag linedtag rowtextx
     set ymax [lindex [$canv cget -scrollregion] 3]
     if {$ymax == {}} return
@@ -1692,15 +2014,49 @@ proc selcanvline {w x y} {
     selectline $l 1
 }
 
+proc commit_descriptor {p} {
+    global commitinfo
+    set l "..."
+    if {[info exists commitinfo($p)]} {
+       set l [lindex $commitinfo($p) 0]
+    }
+    return "$p ($l)"
+}
+
+# append some text to the ctext widget, and make any SHA1 ID
+# that we know about be a clickable link.
+proc appendwithlinks {text} {
+    global ctext idline linknum
+
+    set start [$ctext index "end - 1c"]
+    $ctext insert end $text
+    $ctext insert end "\n"
+    set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
+    foreach l $links {
+       set s [lindex $l 0]
+       set e [lindex $l 1]
+       set linkid [string range $text $s $e]
+       if {![info exists idline($linkid)]} continue
+       incr e
+       $ctext tag add link "$start + $s c" "$start + $e c"
+       $ctext tag add link$linknum "$start + $s c" "$start + $e c"
+       $ctext tag bind link$linknum <1> [list selectline $idline($linkid) 1]
+       incr linknum
+    }
+    $ctext tag conf link -foreground blue -underline 1
+    $ctext tag bind link <Enter> { %W configure -cursor hand2 }
+    $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
+}
+
 proc selectline {l isnew} {
     global canv canv2 canv3 ctext commitinfo selectedline
     global lineid linehtag linentag linedtag
-    global canvy0 linespc parents nparents
+    global canvy0 linespc parents nparents children
     global cflist currentid sha1entry
-    global commentend idtags idline
-    global history historyindex
+    global commentend idtags idline linknum
 
     $canv delete hover
+    normalline
     if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
     $canv delete secsel
     set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
@@ -1719,8 +2075,8 @@ proc selectline {l isnew} {
     set ytop [expr {$y - $linespc - 1}]
     set ybot [expr {$y + $linespc + 1}]
     set wnow [$canv yview]
-    set wtop [expr [lindex $wnow 0] * $ymax]
-    set wbot [expr [lindex $wnow 1] * $ymax]
+    set wtop [expr {[lindex $wnow 0] * $ymax}]
+    set wbot [expr {[lindex $wnow 1] * $ymax}]
     set wh [expr {$wbot - $wtop}]
     set newtop $wtop
     if {$ytop < $wtop} {
@@ -1746,22 +2102,11 @@ proc selectline {l isnew} {
        if {$newtop < 0} {
            set newtop 0
        }
-       allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
+       allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
     }
 
-    if {$isnew && (![info exists selectedline] || $selectedline != $l)} {
-       if {$historyindex < [llength $history]} {
-           set history [lreplace $history $historyindex end $l]
-       } else {
-           lappend history $l
-       }
-       incr historyindex
-       if {$historyindex > 1} {
-           .ctop.top.bar.leftbut conf -state normal
-       } else {
-           .ctop.top.bar.leftbut conf -state disabled
-       }
-       .ctop.top.bar.rightbut conf -state disabled
+    if {$isnew} {
+       addtohistory [list selectline $l 0]
     }
 
     set selectedline $l
@@ -1775,11 +2120,14 @@ proc selectline {l isnew} {
 
     $ctext conf -state normal
     $ctext delete 0.0 end
+    set linknum 0
     $ctext mark set fmark.0 0.0
     $ctext mark gravity fmark.0 left
     set info $commitinfo($id)
-    $ctext insert end "Author: [lindex $info 1]  [lindex $info 2]\n"
-    $ctext insert end "Committer: [lindex $info 3]  [lindex $info 4]\n"
+    set date [formatdate [lindex $info 2]]
+    $ctext insert end "Author: [lindex $info 1]  $date\n"
+    set date [formatdate [lindex $info 4]]
+    $ctext insert end "Committer: [lindex $info 3]  $date\n"
     if {[info exists idtags($id)]} {
        $ctext insert end "Tags:"
        foreach tag $idtags($id) {
@@ -1787,25 +2135,23 @@ proc selectline {l isnew} {
        }
        $ctext insert end "\n"
     }
-    $ctext insert end "\n"
-    set commentstart [$ctext index "end - 1c"]
-    set comment [lindex $info 5]
-    $ctext insert end $comment
-    $ctext insert end "\n"
+    set comment {}
+    if {[info exists parents($id)]} {
+       foreach p $parents($id) {
+           append comment "Parent: [commit_descriptor $p]\n"
+       }
+    }
+    if {[info exists children($id)]} {
+       foreach c $children($id) {
+           append comment "Child:  [commit_descriptor $c]\n"
+       }
+    }
+    append comment "\n"
+    append comment [lindex $info 5]
 
     # make anything that looks like a SHA1 ID be a clickable link
-    set links [regexp -indices -all -inline {[0-9a-f]{40}} $comment]
-    set i 0
-    foreach l $links {
-       set s [lindex $l 0]
-       set e [lindex $l 1]
-       set linkid [string range $comment $s $e]
-       if {![info exists idline($linkid)]} continue
-       incr e
-       $ctext tag conf link$i -foreground blue -underline 1
-       $ctext tag add link$i "$commentstart + $s c" "$commentstart + $e c"
-       $ctext tag bind link$i <1> [list selectline $idline($linkid) 1]
-    }
+    appendwithlinks $comment
 
     $ctext tag delete Comments
     $ctext tag remove found 1.0 end
@@ -1815,7 +2161,7 @@ proc selectline {l isnew} {
     $cflist delete 0 end
     $cflist insert end "Comments"
     if {$nparents($id) == 1} {
-       startdiff [concat $id $parents($id)]
+       startdiff $id
     } elseif {$nparents($id) > 1} {
        mergediff $id
     }
@@ -1824,17 +2170,47 @@ proc selectline {l isnew} {
 proc selnextline {dir} {
     global selectedline
     if {![info exists selectedline]} return
-    set l [expr $selectedline + $dir]
+    set l [expr {$selectedline + $dir}]
     unmarkmatches
     selectline $l 1
 }
 
+proc unselectline {} {
+    global selectedline
+
+    catch {unset selectedline}
+    allcanvs delete secsel
+}
+
+proc addtohistory {cmd} {
+    global history historyindex
+
+    if {$historyindex > 0
+       && [lindex $history [expr {$historyindex - 1}]] == $cmd} {
+       return
+    }
+
+    if {$historyindex < [llength $history]} {
+       set history [lreplace $history $historyindex end $cmd]
+    } else {
+       lappend history $cmd
+    }
+    incr historyindex
+    if {$historyindex > 1} {
+       .ctop.top.bar.leftbut conf -state normal
+    } else {
+       .ctop.top.bar.leftbut conf -state disabled
+    }
+    .ctop.top.bar.rightbut conf -state disabled
+}
+
 proc goback {} {
     global history historyindex
 
     if {$historyindex > 1} {
        incr historyindex -1
-       selectline [lindex $history [expr {$historyindex - 1}]] 0
+       set cmd [lindex $history [expr {$historyindex - 1}]]
+       eval $cmd
        .ctop.top.bar.rightbut conf -state normal
     }
     if {$historyindex <= 1} {
@@ -1846,9 +2222,9 @@ proc goforw {} {
     global history historyindex
 
     if {$historyindex < [llength $history]} {
-       set l [lindex $history $historyindex]
+       set cmd [lindex $history $historyindex]
        incr historyindex
-       selectline $l 0
+       eval $cmd
        .ctop.top.bar.leftbut conf -state normal
     }
     if {$historyindex >= [llength $history]} {
@@ -1894,12 +2270,12 @@ proc contmergediff {ids} {
     # 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]
+       if {[lindex $ids 1] == $diffmergeid && $diffmergegca ne {}} {
+           set ids [list $diffmergegca [lindex $ids 0]]
        } else {
            if {[incr diffpindex] >= $nparents($diffmergeid)} break
            set p [lindex $parents($diffmergeid) $diffpindex]
-           set ids [list $diffmergeid $p]
+           set ids [list $p $diffmergeid]
        }
        if {![info exists treediffs($ids)]} {
            set diffids $ids
@@ -1917,8 +2293,8 @@ proc contmergediff {ids} {
     if {$diffmergegca ne {}} {
        set files {}
        foreach p $parents($diffmergeid) {
-           set gcadiffs $treediffs([list $p $diffmergegca])
-           foreach f $treediffs([list $diffmergeid $p]) {
+           set gcadiffs $treediffs([list $diffmergegca $p])
+           foreach f $treediffs([list $p $diffmergeid]) {
                if {[lsearch -exact $files $f] < 0
                    && [lsearch -exact $gcadiffs $f] >= 0} {
                    lappend files $f
@@ -1931,7 +2307,7 @@ proc contmergediff {ids} {
        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 df $treediffs([list $p $diffmergeid])
            set nf {}
            foreach f $files {
                if {[lsearch -exact $df $f] >= 0} {
@@ -2408,9 +2784,7 @@ proc gettreediffs {ids} {
     global treediff parents treepending
     set treepending $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
+    if [catch {set gdtf [open [concat | git-diff-tree --no-commit-id -r $ids] r]}] return
     fconfigure $gdtf -blocking 0
     fileevent $gdtf readable [list gettreediffline $gdtf $ids]
 }
@@ -2443,10 +2817,8 @@ proc getblobdiffs {ids} {
     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 cmd [list | git-diff-tree -r -p -C $p $id]
+    set cmd [concat | git-diff-tree --no-commit-id -r -p -C $ids]
     if {[catch {set bdf [open $cmd r]} err]} {
        puts "error getting diffs: $err"
        return
@@ -2465,7 +2837,6 @@ proc getblobdiffline {bdf ids} {
     global diffids blobdifffd ctext curdifftag curtagstart
     global diffnexthead diffnextnote difffilestart
     global nextupdate diffinhdr treediffs
-    global gaudydiff
 
     set n [gets $bdf line]
     if {$n < 0} {
@@ -2514,26 +2885,14 @@ proc getblobdiffline {bdf ids} {
        set diffinhdr 0
     } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
                   $line match f1l f1c f2l f2c rest]} {
-       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
-       }
+       $ctext insert end "$line\n" hunksep
        set diffinhdr 0
     } else {
        set x [string range $line 0 0]
        if {$x == "-" || $x == "+"} {
            set tag [expr {$x == "+"}]
-           if {$gaudydiff} {
-               set line [string range $line 1 end]
-           }
            $ctext insert end "$line\n" d$tag
        } elseif {$x == " "} {
-           if {$gaudydiff} {
-               set line [string range $line 1 end]
-           }
            $ctext insert end "$line\n"
        } elseif {$diffinhdr || $x == "\\"} {
            # e.g. "\ No newline at end of file"
@@ -2585,18 +2944,19 @@ proc listboxsel {} {
 
 proc setcoords {} {
     global linespc charspc canvx0 canvy0 mainfont
-    global xspc1 xspc2
+    global xspc1 xspc2 lthickness
 
     set linespc [font metrics $mainfont -linespace]
     set charspc [font measure $mainfont "m"]
-    set canvy0 [expr 3 + 0.5 * $linespc]
-    set canvx0 [expr 3 + 0.5 * $linespc]
+    set canvy0 [expr {3 + 0.5 * $linespc}]
+    set canvx0 [expr {3 + 0.5 * $linespc}]
+    set lthickness [expr {int($linespc / 9) + 1}]
     set xspc1(0) $linespc
     set xspc2 $linespc
 }
 
 proc redisplay {} {
-    global selectedline stopped redisplaying phase
+    global stopped redisplaying phase
     if {$stopped > 1} return
     if {$phase == "getcommits"} return
     set redisplaying 1
@@ -2608,7 +2968,7 @@ proc redisplay {} {
 }
 
 proc incrfont {inc} {
-    global mainfont namefont textfont selectedline ctext canv phase
+    global mainfont namefont textfont ctext canv phase
     global stopped entries
     unmarkmatches
     set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
@@ -2746,38 +3106,135 @@ proc linehover {} {
     set t [$canv create rectangle $x0 $y0 $x1 $y1 \
               -fill \#ffff80 -outline black -width 1 -tags hover]
     $canv raise $t
-    set t [$canv create text $x $y -anchor nw -text $text -tags hover]
+    set t [$canv create text $x $y -anchor nw -text $text -tags hover -font $mainfont]
     $canv raise $t
 }
 
-proc lineclick {x y id} {
-    global ctext commitinfo children cflist canv
+proc clickisonarrow {id y} {
+    global mainline mainlinearrow sidelines lthickness
+
+    set thresh [expr {2 * $lthickness + 6}]
+    if {[info exists mainline($id)]} {
+       if {$mainlinearrow($id) ne "none"} {
+           if {abs([lindex $mainline($id) 1] - $y) < $thresh} {
+               return "up"
+           }
+       }
+    }
+    if {[info exists sidelines($id)]} {
+       foreach ls $sidelines($id) {
+           set coords [lindex $ls 0]
+           set arrow [lindex $ls 2]
+           if {$arrow eq "first" || $arrow eq "both"} {
+               if {abs([lindex $coords 1] - $y) < $thresh} {
+                   return "up"
+               }
+           }
+           if {$arrow eq "last" || $arrow eq "both"} {
+               if {abs([lindex $coords end] - $y) < $thresh} {
+                   return "down"
+               }
+           }
+       }
+    }
+    return {}
+}
+
+proc arrowjump {id dirn y} {
+    global mainline sidelines canv canv2 canv3
+
+    set yt {}
+    if {$dirn eq "down"} {
+       if {[info exists mainline($id)]} {
+           set y1 [lindex $mainline($id) 1]
+           if {$y1 > $y} {
+               set yt $y1
+           }
+       }
+       if {[info exists sidelines($id)]} {
+           foreach ls $sidelines($id) {
+               set y1 [lindex $ls 0 1]
+               if {$y1 > $y && ($yt eq {} || $y1 < $yt)} {
+                   set yt $y1
+               }
+           }
+       }
+    } else {
+       if {[info exists sidelines($id)]} {
+           foreach ls $sidelines($id) {
+               set y1 [lindex $ls 0 end]
+               if {$y1 < $y && ($yt eq {} || $y1 > $yt)} {
+                   set yt $y1
+               }
+           }
+       }
+    }
+    if {$yt eq {}} return
+    set ymax [lindex [$canv cget -scrollregion] 3]
+    if {$ymax eq {} || $ymax <= 0} return
+    set view [$canv yview]
+    set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
+    set yfrac [expr {$yt / $ymax - $yspan / 2}]
+    if {$yfrac < 0} {
+       set yfrac 0
+    }
+    $canv yview moveto $yfrac
+    $canv2 yview moveto $yfrac
+    $canv3 yview moveto $yfrac
+}
+
+proc lineclick {x y id isnew} {
+    global ctext commitinfo children cflist canv thickerline
 
     unmarkmatches
+    unselectline
+    normalline
     $canv delete hover
+    # draw this line thicker than normal
+    drawlines $id 1 1
+    set thickerline $id
+    if {$isnew} {
+       set ymax [lindex [$canv cget -scrollregion] 3]
+       if {$ymax eq {}} return
+       set yfrac [lindex [$canv yview] 0]
+       set y [expr {$y + $yfrac * $ymax}]
+    }
+    set dirn [clickisonarrow $id $y]
+    if {$dirn ne {}} {
+       arrowjump $id $dirn $y
+       return
+    }
+
+    if {$isnew} {
+       addtohistory [list lineclick $x $y $id 0]
+    }
     # fill the details pane with info about this line
     $ctext conf -state normal
     $ctext delete 0.0 end
-    $ctext insert end "Parent:\n "
-    catch {destroy $ctext.$id}
-    button $ctext.$id -text "Go:" -command "selbyid $id" \
-       -padx 4 -pady 0
-    $ctext window create end -window $ctext.$id -align center
+    $ctext tag conf link -foreground blue -underline 1
+    $ctext tag bind link <Enter> { %W configure -cursor hand2 }
+    $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
+    $ctext insert end "Parent:\t"
+    $ctext insert end $id [list link link0]
+    $ctext tag bind link0 <1> [list selbyid $id]
     set info $commitinfo($id)
-    $ctext insert end "\t[lindex $info 0]\n"
+    $ctext insert end "\n\t[lindex $info 0]\n"
     $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
-    $ctext insert end "\tDate:\t[lindex $info 2]\n"
-    $ctext insert end "\tID:\t$id\n"
+    set date [formatdate [lindex $info 2]]
+    $ctext insert end "\tDate:\t$date\n"
     if {[info exists children($id)]} {
        $ctext insert end "\nChildren:"
+       set i 0
        foreach child $children($id) {
-           $ctext insert end "\n "
-           catch {destroy $ctext.$child}
-           button $ctext.$child -text "Go:" -command "selbyid $child" \
-               -padx 4 -pady 0
-           $ctext window create end -window $ctext.$child -align center
+           incr i
            set info $commitinfo($child)
-           $ctext insert end "\t[lindex $info 0]"
+           $ctext insert end "\n\t"
+           $ctext insert end $child [list link link$i]
+           $ctext tag bind link$i <1> [list selbyid $child]
+           $ctext insert end "\n\t[lindex $info 0]"
+           $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
+           set date [formatdate [lindex $info 2]]
+           $ctext insert end "\n\tDate:\t$date\n"
        }
     }
     $ctext conf -state disabled
@@ -2785,6 +3242,14 @@ proc lineclick {x y id} {
     $cflist delete 0 end
 }
 
+proc normalline {} {
+    global thickerline
+    if {[info exists thickerline]} {
+       drawlines $thickerline 0 1
+       unset thickerline
+    }
+}
+
 proc selbyid {id} {
     global idline
     if {[info exists idline($id)]} {
@@ -2817,8 +3282,6 @@ proc rowmenu {x y id} {
 
 proc diffvssel {dirn} {
     global rowmenuid selectedline lineid
-    global ctext cflist
-    global commitinfo
 
     if {![info exists selectedline]} return
     if {$dirn} {
@@ -2828,21 +3291,38 @@ proc diffvssel {dirn} {
        set oldid $rowmenuid
        set newid $lineid($selectedline)
     }
+    addtohistory [list doseldiff $oldid $newid]
+    doseldiff $oldid $newid
+}
+
+proc doseldiff {oldid newid} {
+    global ctext cflist
+    global commitinfo
+
     $ctext conf -state normal
     $ctext delete 0.0 end
     $ctext mark set fmark.0 0.0
     $ctext mark gravity fmark.0 left
     $cflist delete 0 end
     $cflist insert end "Top"
-    $ctext insert end "From $oldid\n     "
+    $ctext insert end "From "
+    $ctext tag conf link -foreground blue -underline 1
+    $ctext tag bind link <Enter> { %W configure -cursor hand2 }
+    $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
+    $ctext tag bind link0 <1> [list selbyid $oldid]
+    $ctext insert end $oldid [list link link0]
+    $ctext insert end "\n     "
     $ctext insert end [lindex $commitinfo($oldid) 0]
-    $ctext insert end "\n\nTo   $newid\n     "
+    $ctext insert end "\n\nTo   "
+    $ctext tag bind link1 <1> [list selbyid $newid]
+    $ctext insert end $newid [list link link1]
+    $ctext insert end "\n     "
     $ctext insert end [lindex $commitinfo($newid) 0]
     $ctext insert end "\n"
     $ctext conf -state disabled
     $ctext tag delete Comments
     $ctext tag remove found 1.0 end
-    startdiff [list $newid $oldid]
+    startdiff [list $oldid $newid]
 }
 
 proc mkpatch {} {
@@ -2963,7 +3443,6 @@ proc mktag {} {
 
 proc domktag {} {
     global mktagtop env tagids idtags
-    global idpos idline linehtag canv selectedline
 
     set id [$mktagtop.sha1 get]
     set tag [$mktagtop.tag get]
@@ -2988,6 +3467,13 @@ proc domktag {} {
 
     set tagids($tag) $id
     lappend idtags($id) $tag
+    redrawtags $id
+}
+
+proc redrawtags {id} {
+    global canv linehtag idline idpos selectedline
+
+    if {![info exists idline($id)]} return
     $canv delete tag.$id
     set xt [eval drawtags $id $idpos($id)]
     $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
@@ -3063,39 +3549,181 @@ proc wrcomcan {} {
     unset wrcomtop
 }
 
+proc listrefs {id} {
+    global idtags idheads idotherrefs
+
+    set x {}
+    if {[info exists idtags($id)]} {
+       set x $idtags($id)
+    }
+    set y {}
+    if {[info exists idheads($id)]} {
+       set y $idheads($id)
+    }
+    set z {}
+    if {[info exists idotherrefs($id)]} {
+       set z $idotherrefs($id)
+    }
+    return [list $x $y $z]
+}
+
+proc rereadrefs {} {
+    global idtags idheads idotherrefs
+    global tagids headids otherrefids
+
+    set refids [concat [array names idtags] \
+                   [array names idheads] [array names idotherrefs]]
+    foreach id $refids {
+       if {![info exists ref($id)]} {
+           set ref($id) [listrefs $id]
+       }
+    }
+    foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
+       catch {unset $v}
+    }
+    readrefs
+    set refids [lsort -unique [concat $refids [array names idtags] \
+                       [array names idheads] [array names idotherrefs]]]
+    foreach id $refids {
+       set v [listrefs $id]
+       if {![info exists ref($id)] || $ref($id) != $v} {
+           redrawtags $id
+       }
+    }
+}
+
+proc showtag {tag isnew} {
+    global ctext cflist tagcontents tagids linknum
+
+    if {$isnew} {
+       addtohistory [list showtag $tag 0]
+    }
+    $ctext conf -state normal
+    $ctext delete 0.0 end
+    set linknum 0
+    if {[info exists tagcontents($tag)]} {
+       set text $tagcontents($tag)
+    } else {
+       set text "Tag: $tag\nId:  $tagids($tag)"
+    }
+    appendwithlinks $text
+    $ctext conf -state disabled
+    $cflist delete 0 end
+}
+
 proc doquit {} {
     global stopped
     set stopped 100
     destroy .
 }
 
+proc doprefs {} {
+    global maxwidth maxgraphpct diffopts findmergefiles
+    global oldprefs prefstop
+
+    set top .gitkprefs
+    set prefstop $top
+    if {[winfo exists $top]} {
+       raise $top
+       return
+    }
+    foreach v {maxwidth maxgraphpct diffopts findmergefiles} {
+       set oldprefs($v) [set $v]
+    }
+    toplevel $top
+    wm title $top "Gitk preferences"
+    label $top.ldisp -text "Commit list display options"
+    grid $top.ldisp - -sticky w -pady 10
+    label $top.spacer -text " "
+    label $top.maxwidthl -text "Maximum graph width (lines)" \
+       -font optionfont
+    spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
+    grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
+    label $top.maxpctl -text "Maximum graph width (% of pane)" \
+       -font optionfont
+    spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
+    grid x $top.maxpctl $top.maxpct -sticky w
+    checkbutton $top.findm -variable findmergefiles
+    label $top.findml -text "Include merges for \"Find\" in \"Files\"" \
+       -font optionfont
+    grid $top.findm $top.findml - -sticky w
+    label $top.ddisp -text "Diff display options"
+    grid $top.ddisp - -sticky w -pady 10
+    label $top.diffoptl -text "Options for diff program" \
+       -font optionfont
+    entry $top.diffopt -width 20 -textvariable diffopts
+    grid x $top.diffoptl $top.diffopt -sticky w
+    frame $top.buts
+    button $top.buts.ok -text "OK" -command prefsok
+    button $top.buts.can -text "Cancel" -command prefscan
+    grid $top.buts.ok $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
+}
+
+proc prefscan {} {
+    global maxwidth maxgraphpct diffopts findmergefiles
+    global oldprefs prefstop
+
+    foreach v {maxwidth maxgraphpct diffopts findmergefiles} {
+       set $v $oldprefs($v)
+    }
+    catch {destroy $prefstop}
+    unset prefstop
+}
+
+proc prefsok {} {
+    global maxwidth maxgraphpct
+    global oldprefs prefstop
+
+    catch {destroy $prefstop}
+    unset prefstop
+    if {$maxwidth != $oldprefs(maxwidth)
+       || $maxgraphpct != $oldprefs(maxgraphpct)} {
+       redisplay
+    }
+}
+
+proc formatdate {d} {
+    return [clock format $d -format "%Y-%m-%d %H:%M:%S"]
+}
+
 # defaults...
 set datemode 0
-set boldnames 0
 set diffopts "-U 5 -p"
 set wrcomcmd "git-diff-tree --stdin -p --pretty"
 
+set gitencoding ""
+catch {
+    set gitencoding [exec git-repo-config --get i18n.commitencoding]
+}
+if {$gitencoding == ""} {
+       set gitencoding "utf-8"
+}
+
 set mainfont {Helvetica 9}
 set textfont {Courier 9}
 set findmergefiles 0
-set gaudydiff 0
 set maxgraphpct 50
+set maxwidth 16
+set revlistorder 0
+set fastdate 0
 
 set colors {green red blue magenta darkgrey brown orange}
 
 catch {source ~/.gitk}
 
 set namefont $mainfont
-if {$boldnames} {
-    lappend namefont bold
-}
+
+font create optionfont -family sans-serif -size -12
 
 set revtreeargs {}
 foreach arg $argv {
     switch -regexp -- $arg {
        "^$" { }
-       "^-b" { set boldnames 1 }
        "^-d" { set datemode 1 }
+       "^-r" { set revlistorder 1 }
        default {
            lappend revtreeargs $arg
        }