[PATCH] git-ssh-pull: commit-id consistency
[git.git] / gitk
diff --git a/gitk b/gitk
index 15d9cf0..faaffe1 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -7,79 +7,126 @@ exec wish "$0" -- "${1+$@}"
 # and distributed under the terms of the GNU General Public Licence,
 # either version 2, or (at your option) any later version.
 
 # and distributed under the terms of the GNU General Public Licence,
 # either version 2, or (at your option) any later version.
 
-# CVS $Revision: 1.18 $
+# CVS $Revision: 1.24 $
 
 proc getcommits {rargs} {
     global commits commfd phase canv mainfont
 
 proc getcommits {rargs} {
     global commits commfd phase canv mainfont
-    if {$rargs == {}} {
-       set rargs HEAD
-    }
+    global startmsecs nextupdate
+    global ctext maincursor textcursor leftover
+
     set commits {}
     set phase getcommits
     set commits {}
     set phase getcommits
-    if [catch {set commfd [open "|git-rev-tree $rargs" r]} err] {
-       puts stderr "Error executing git-rev-tree: $err"
+    set startmsecs [clock clicks -milliseconds]
+    set nextupdate [expr $startmsecs + 100]
+    if [catch {
+       set parse_args [concat --default HEAD $rargs]
+       set parsed_args [split [eval exec git-rev-parse $parse_args] "\n"]
+    }] {
+       # if git-rev-parse failed for some reason...
+       if {$rargs == {}} {
+           set rargs HEAD
+       }
+       set parsed_args $rargs
+    }
+    if [catch {
+       set commfd [open "|git-rev-list --header --merge-order $parsed_args" r]
+    } err] {
+       puts stderr "Error executing git-rev-list: $err"
        exit 1
     }
        exit 1
     }
-    fconfigure $commfd -blocking 0
-    fileevent $commfd readable "getcommitline $commfd"
+    set leftover {}
+    fconfigure $commfd -blocking 0 -translation binary
+    fileevent $commfd readable "getcommitlines $commfd"
     $canv delete all
     $canv create text 3 3 -anchor nw -text "Reading commits..." \
        -font $mainfont -tags textitems
     $canv delete all
     $canv create text 3 3 -anchor nw -text "Reading commits..." \
        -font $mainfont -tags textitems
+    . config -cursor watch
+    $ctext config -cursor watch
 }
 
 }
 
-proc getcommitline {commfd}  {
-    global commits parents cdate nparents children nchildren
-    set n [gets $commfd line]
-    if {$n < 0} {
+proc getcommitlines {commfd}  {
+    global commits parents cdate children nchildren
+    global commitlisted phase commitinfo nextupdate
+    global stopped redisplaying leftover
+
+    set stuff [read $commfd]
+    if {$stuff == {}} {
        if {![eof $commfd]} return
        # this works around what is apparently a bug in Tcl...
        fconfigure $commfd -blocking 1
        if {![catch {close $commfd} err]} {
        if {![eof $commfd]} return
        # this works around what is apparently a bug in Tcl...
        fconfigure $commfd -blocking 1
        if {![catch {close $commfd} err]} {
-           after idle drawgraph
+           after idle finishcommits
            return
        }
        if {[string range $err 0 4] == "usage"} {
            return
        }
        if {[string range $err 0 4] == "usage"} {
-           set err "\
-Gitk: error reading commits: bad arguments to git-rev-tree.\n\
-(Note: arguments to gitk are passed to git-rev-tree\
-to allow selection of commits to be displayed.)"
+           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.)}
        } else {
            set err "Error reading commits: $err"
        }
        error_popup $err
        exit 1
     }
        } else {
            set err "Error reading commits: $err"
        }
        error_popup $err
        exit 1
     }
-
-    set i 0
-    set cid {}
-    foreach f $line {
-       if {$i == 0} {
-           set d $f
-       } else {
-           set id [lindex [split $f :] 0]
-           if {![info exists nchildren($id)]} {
-               set children($id) {}
-               set nchildren($id) 0
-           }
-           if {$i == 1} {
-               set cid $id
-               lappend commits $id
-               set parents($id) {}
-               set cdate($id) $d
-               set nparents($id) 0
-           } else {
-               lappend parents($cid) $id
-               incr nparents($cid)
-               incr nchildren($id)
-               lappend children($id) $cid
+    set start 0
+    while 1 {
+       set i [string first "\0" $stuff $start]
+       if {$i < 0} {
+           set leftover [string range $stuff $start end]
+           return
+       }
+       set cmit [string range $stuff $start [expr {$i - 1}]]
+       if {$start == 0} {
+           set cmit "$leftover$cmit"
+       }
+       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}"
+           exit 1
+       }
+       set cmit [string range $cmit 41 end]
+       lappend commits $id
+       set commitlisted($id) 1
+       parsecommit $id $cmit 1
+       drawcommit $id
+       if {[clock clicks -milliseconds] >= $nextupdate} {
+           doupdate
+       }
+       while {$redisplaying} {
+           set redisplaying 0
+           if {$stopped == 1} {
+               set stopped 0
+               set phase "getcommits"
+               foreach id $commits {
+                   drawcommit $id
+                   if {$stopped} break
+                   if {[clock clicks -milliseconds] >= $nextupdate} {
+                       doupdate
+                   }
+               }
            }
        }
            }
        }
-       incr i
     }
 }
 
     }
 }
 
+proc doupdate {} {
+    global commfd nextupdate
+
+    incr nextupdate 100
+    fileevent $commfd readable {}
+    update
+    fileevent $commfd readable "getcommitlines $commfd"
+}
+
 proc readcommit {id} {
 proc readcommit {id} {
-    global commitinfo
+    if [catch {set contents [exec git-cat-file commit $id]}] return
+    parsecommit $id $contents 0
+}
+
+proc parsecommit {id contents listed} {
+    global commitinfo children nchildren parents nparents cdate ncleft
+
     set inhdr 1
     set comment {}
     set headline {}
     set inhdr 1
     set comment {}
     set headline {}
@@ -87,14 +134,35 @@ proc readcommit {id} {
     set audate {}
     set comname {}
     set comdate {}
     set audate {}
     set comname {}
     set comdate {}
-    if [catch {set contents [exec git-cat-file commit $id]}] return
+    if {![info exists nchildren($id)]} {
+       set children($id) {}
+       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]
     foreach line [split $contents "\n"] {
        if {$inhdr} {
            if {$line == {}} {
                set inhdr 0
            } else {
                set tag [lindex $line 0]
-               if {$tag == "author"} {
+               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}]]
                    set x [expr {[llength $line] - 2}]
                    set audate [lindex $line $x]
                    set auname [lrange $line 1 [expr {$x - 1}]]
@@ -106,10 +174,15 @@ proc readcommit {id} {
            }
        } else {
            if {$comment == {}} {
            }
        } else {
            if {$comment == {}} {
-               set headline $line
+               set headline [string trim $line]
            } else {
                append comment "\n"
            }
            } 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
        }
     }
            append comment $line
        }
     }
@@ -117,6 +190,7 @@ proc readcommit {id} {
        set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
     }
     if {$comdate != {}} {
        set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
     }
     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 \
        set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
     }
     set commitinfo($id) [list $headline $auname $audate \
@@ -124,13 +198,16 @@ proc readcommit {id} {
 }
 
 proc readrefs {} {
 }
 
 proc readrefs {} {
-    global tagids idtags
+    global tagids idtags headids idheads
     set tags [glob -nocomplain -types f .git/refs/tags/*]
     foreach f $tags {
        catch {
            set fd [open $f r]
            set line [read $fd]
            if {[regexp {^[0-9a-f]{40}} $line id]} {
     set tags [glob -nocomplain -types f .git/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 contents [split [exec git-cat-file tag $id] "\n"]
                set obj {}
                set type {}
@@ -148,6 +225,20 @@ proc readrefs {} {
                    lappend idtags($obj) $tag
                }
            }
                    lappend idtags($obj) $tag
                }
            }
+           close $fd
+       }
+    }
+    set heads [glob -nocomplain -types f .git/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
        }
     }
 }
        }
     }
 }
@@ -168,6 +259,8 @@ proc makewindow {} {
     global canv canv2 canv3 linespc charspc ctext cflist textfont
     global findtype findloc findstring fstring geometry
     global entries sha1entry sha1string sha1but
     global canv canv2 canv3 linespc charspc ctext cflist textfont
     global findtype findloc findstring fstring geometry
     global entries sha1entry sha1string sha1but
+    global maincursor textcursor
+    global linectxmenu
 
     menu .bar
     .bar add cascade -label "File" -menu .bar.file
 
     menu .bar
     .bar add cascade -label "File" -menu .bar.file
@@ -275,22 +368,22 @@ proc makewindow {} {
 
     bindall <1> {selcanvline %x %y}
     bindall <B1-Motion> {selcanvline %x %y}
 
     bindall <1> {selcanvline %x %y}
     bindall <B1-Motion> {selcanvline %x %y}
-    bindall <ButtonRelease-4> "allcanvs yview scroll -5 u"
-    bindall <ButtonRelease-5> "allcanvs yview scroll 5 u"
+    bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
+    bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
     bindall <2> "allcanvs scan mark 0 %y"
     bindall <B2-Motion> "allcanvs scan dragto 0 %y"
     bind . <Key-Up> "selnextline -1"
     bind . <Key-Down> "selnextline 1"
     bindall <2> "allcanvs scan mark 0 %y"
     bindall <B2-Motion> "allcanvs scan dragto 0 %y"
     bind . <Key-Up> "selnextline -1"
     bind . <Key-Down> "selnextline 1"
-    bind . <Key-Prior> "allcanvs yview scroll -1 p"
-    bind . <Key-Next> "allcanvs yview scroll 1 p"
-    bindkey <Key-Delete> "$ctext yview scroll -1 p"
-    bindkey <Key-BackSpace> "$ctext yview scroll -1 p"
-    bindkey <Key-space> "$ctext yview scroll 1 p"
+    bind . <Key-Prior> "allcanvs yview scroll -1 pages"
+    bind . <Key-Next> "allcanvs yview scroll 1 pages"
+    bindkey <Key-Delete> "$ctext yview scroll -1 pages"
+    bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
+    bindkey <Key-space> "$ctext yview scroll 1 pages"
     bindkey p "selnextline -1"
     bindkey n "selnextline 1"
     bindkey p "selnextline -1"
     bindkey n "selnextline 1"
-    bindkey b "$ctext yview scroll -1 p"
-    bindkey d "$ctext yview scroll 18 u"
-    bindkey u "$ctext yview scroll -18 u"
+    bindkey b "$ctext yview scroll -1 pages"
+    bindkey d "$ctext yview scroll 18 units"
+    bindkey u "$ctext yview scroll -18 units"
     bindkey / findnext
     bindkey ? findprev
     bindkey f nextfile
     bindkey / findnext
     bindkey ? findprev
     bindkey f nextfile
@@ -307,6 +400,13 @@ 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
+
+    set maincursor [. cget -cursor]
+    set textcursor [$ctext cget -cursor]
+
+    set linectxmenu .linectxmenu
+    menu $linectxmenu -tearoff 0
+    $linectxmenu add command -label "Select" -command lineselect
 }
 
 # when we make a key binding for the toplevel, make sure
 }
 
 # when we make a key binding for the toplevel, make sure
@@ -436,66 +536,62 @@ proc about {} {
     toplevel $w
     wm title $w "About gitk"
     message $w.m -text {
     toplevel $w
     wm title $w "About gitk"
     message $w.m -text {
-Gitk version 1.0
+Gitk version 1.1
 
 Copyright © 2005 Paul Mackerras
 
 Use and redistribute under the terms of the GNU General Public License
 
 
 Copyright © 2005 Paul Mackerras
 
 Use and redistribute under the terms of the GNU General Public License
 
-(CVS $Revision: 1.18 $)} \
+(CVS $Revision: 1.24 $)} \
            -justify center -aspect 400
     pack $w.m -side top -fill x -padx 20 -pady 20
     button $w.ok -text Close -command "destroy $w"
     pack $w.ok -side bottom
 }
 
            -justify center -aspect 400
     pack $w.m -side top -fill x -padx 20 -pady 20
     button $w.ok -text Close -command "destroy $w"
     pack $w.ok -side bottom
 }
 
-proc truncatetofit {str width font} {
-    if {[font measure $font $str] <= $width} {
-       return $str
-    }
-    set best 0
-    set bad [string length $str]
-    set tmp $str
-    while {$best < $bad - 1} {
-       set try [expr {int(($best + $bad) / 2)}]
-       set tmp "[string range $str 0 [expr $try-1]]..."
-       if {[font measure $font $tmp] <= $width} {
-           set best $try
-       } else {
-           set bad $try
-       }
-    }
-    return $tmp
-}
-
 proc assigncolor {id} {
     global commitinfo colormap commcolors colors nextcolor
 proc assigncolor {id} {
     global commitinfo colormap commcolors colors nextcolor
-    global colorbycommitter
     global parents nparents children nchildren
     global parents nparents children nchildren
+    global cornercrossings crossings
+
     if [info exists colormap($id)] return
     set ncolors [llength $colors]
     if [info exists colormap($id)] return
     set ncolors [llength $colors]
-    if {$colorbycommitter} {
-       if {![info exists commitinfo($id)]} {
-           readcommit $id
+    if {$nparents($id) <= 1 && $nchildren($id) == 1} {
+       set child [lindex $children($id) 0]
+       if {[info exists colormap($child)]
+           && $nparents($child) == 1} {
+           set colormap($id) $colormap($child)
+           return
        }
        }
-       set comm [lindex $commitinfo($id) 3]
-       if {![info exists commcolors($comm)]} {
-           set commcolors($comm) [lindex $colors $nextcolor]
-           if {[incr nextcolor] >= $ncolors} {
-               set nextcolor 0
+    }
+    set badcolors {}
+    if {[info exists cornercrossings($id)]} {
+       foreach x $cornercrossings($id) {
+           if {[info exists colormap($x)]
+               && [lsearch -exact $badcolors $colormap($x)] < 0} {
+               lappend badcolors $colormap($x)
            }
        }
            }
        }
-       set colormap($id) $commcolors($comm)
-    } else {
-       if {$nparents($id) == 1 && $nchildren($id) == 1} {
-           set child [lindex $children($id) 0]
-           if {[info exists colormap($child)]
-               && $nparents($child) == 1} {
-               set colormap($id) $colormap($child)
-               return
+       if {[llength $badcolors] >= $ncolors} {
+           set badcolors {}
+       }
+    }
+    set origbad $badcolors
+    if {[llength $badcolors] < $ncolors - 1} {
+       if {[info exists crossings($id)]} {
+           foreach x $crossings($id) {
+               if {[info exists colormap($x)]
+                   && [lsearch -exact $badcolors $colormap($x)] < 0} {
+                   lappend badcolors $colormap($x)
+               }
+           }
+           if {[llength $badcolors] >= $ncolors} {
+               set badcolors $origbad
            }
        }
            }
        }
-       set badcolors {}
+       set origbad $badcolors
+    }
+    if {[llength $badcolors] < $ncolors - 1} {
        foreach child $children($id) {
            if {[info exists colormap($child)]
                && [lsearch -exact $badcolors $colormap($child)] < 0} {
        foreach child $children($id) {
            if {[info exists colormap($child)]
                && [lsearch -exact $badcolors $colormap($child)] < 0} {
@@ -511,270 +607,455 @@ proc assigncolor {id} {
            }
        }
        if {[llength $badcolors] >= $ncolors} {
            }
        }
        if {[llength $badcolors] >= $ncolors} {
-           set badcolors {}
+           set badcolors $origbad
        }
        }
-       for {set i 0} {$i <= $ncolors} {incr i} {
-           set c [lindex $colors $nextcolor]
-           if {[incr nextcolor] >= $ncolors} {
-               set nextcolor 0
-           }
-           if {[lsearch -exact $badcolors $c]} break
+    }
+    for {set i 0} {$i <= $ncolors} {incr i} {
+       set c [lindex $colors $nextcolor]
+       if {[incr nextcolor] >= $ncolors} {
+           set nextcolor 0
        }
        }
-       set colormap($id) $c
+       if {[lsearch -exact $badcolors $c]} break
     }
     }
+    set colormap($id) $c
 }
 
 }
 
-proc drawgraph {} {
-    global parents children nparents nchildren commits
-    global canv canv2 canv3 mainfont namefont canvx0 canvy0 canvy linespc
-    global datemode cdate
-    global lineid linehtag linentag linedtag commitinfo
-    global nextcolor colormap numcommits
-    global stopped phase redisplaying selectedline idtags idline
+proc initgraph {} {
+    global canvy canvy0 lineno numcommits lthickness nextcolor linespc
+    global mainline sidelines
+    global nchildren ncleft
 
     allcanvs delete all
 
     allcanvs delete all
-    set start {}
+    set nextcolor 0
+    set canvy $canvy0
+    set lineno -1
+    set numcommits 0
+    set lthickness [expr {int($linespc / 9) + 1}]
+    catch {unset mainline}
+    catch {unset sidelines}
     foreach id [array names nchildren] {
     foreach id [array names nchildren] {
-       if {$nchildren($id) == 0} {
-           lappend start $id
-       }
        set ncleft($id) $nchildren($id)
        set ncleft($id) $nchildren($id)
-       if {![info exists nparents($id)]} {
-           set nparents($id) 0
-       }
-    }
-    if {$start == {}} {
-       error_popup "Gitk: ERROR: No starting commits found"
-       exit 1
     }
     }
+}
 
 
-    set nextcolor 0
-    foreach id $start {
-       assigncolor $id
-    }
-    set todo $start
-    set level [expr [llength $todo] - 1]
-    set y2 $canvy0
-    set nullentry -1
-    set lineno -1
-    set numcommits 0
-    set phase drawgraph
-    set lthickness [expr {($linespc / 9) + 1}]
-    while 1 {
-       set canvy $y2
-       allcanvs conf -scrollregion \
-           [list 0 0 0 [expr $canvy + 0.5 * $linespc + 2]]
-       update
-       if {$stopped} break
-       incr numcommits
-       incr lineno
-       set nlines [llength $todo]
-       set id [lindex $todo $level]
-       set lineid($lineno) $id
-       set idline($id) $lineno
-       set actualparents {}
-       if {[info exists parents($id)]} {
-           foreach p $parents($id) {
-               incr ncleft($p) -1
-               if {![info exists commitinfo($p)]} {
-                   readcommit $p
-                   if {![info exists commitinfo($p)]} continue
-               }
-               lappend actualparents $p
-           }
-       }
+proc bindline {t id} {
+    global canv
+
+    $canv bind $t <Button-3> "linemenu %X %Y $id"
+    $canv bind $t <Enter> "lineenter %x %y $id"
+    $canv bind $t <Motion> "linemotion %x %y $id"
+    $canv bind $t <Leave> "lineleave $id"
+}
+
+proc drawcommitline {level} {
+    global parents children nparents nchildren todo
+    global canv canv2 canv3 mainfont namefont canvx0 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
+
+    incr numcommits
+    incr lineno
+    set id [lindex $todo $level]
+    set lineid($lineno) $id
+    set idline($id) $lineno
+    set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
+    if {![info exists commitinfo($id)]} {
+       readcommit $id
        if {![info exists commitinfo($id)]} {
        if {![info exists commitinfo($id)]} {
-           readcommit $id
-           if {![info exists commitinfo($id)]} {
-               set commitinfo($id) {"No commit information available"}
+           set commitinfo($id) {"No commit information available"}
+           set nparents($id) 0
+       }
+    }
+    assigncolor $id
+    set currentparents {}
+    set dupparents {}
+    if {[info exists commitlisted($id)] && [info exists parents($id)]} {
+       foreach p $parents($id) {
+           if {[lsearch -exact $currentparents $p] < 0} {
+               lappend currentparents $p
+           } else {
+               # remember that this parent was listed twice
+               lappend dupparents $p
            }
        }
            }
        }
-       set x [expr $canvx0 + $level * $linespc]
-       set y2 [expr $canvy + $linespc]
-       if {[info exists linestarty($level)] && $linestarty($level) < $canvy} {
-           set t [$canv create line $x $linestarty($level) $x $canvy \
-                      -width $lthickness -fill $colormap($id)]
+    }
+    set x [expr $canvx0 + $level * $linespc]
+    set y1 $canvy
+    set canvy [expr $canvy + $linespc]
+    allcanvs conf -scrollregion \
+       [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
            $canv lower $t
+           bindline $t $id
        }
        }
-       set linestarty($level) $canvy
-       set ofill [expr {[info exists parents($id)]? "blue": "white"}]
-       set orad [expr {$linespc / 3}]
-       set t [$canv create oval [expr $x - $orad] [expr $canvy - $orad] \
-                  [expr $x + $orad - 1] [expr $canvy + $orad - 1] \
-                  -fill $ofill -outline black -width 1]
-       $canv raise $t
-       set xt [expr $canvx0 + $nlines * $linespc]
-       if {$nparents($id) > 2} {
-           set xt [expr {$xt + ($nparents($id) - 2) * $linespc}]
+    }
+    set orad [expr {$linespc / 3}]
+    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
+    set xt [expr $canvx0 + [llength $todo] * $linespc]
+    if {[llength $currentparents] > 2} {
+       set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
+    }
+    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}]
        }
        }
-       if {[info exists idtags($id)] && $idtags($id) != {}} {
-           set delta [expr {int(0.5 * ($linespc - $lthickness))}]
-           set yt [expr $canvy - 0.5 * $linespc]
-           set yb [expr $yt + $linespc - 1]
-           set xvals {}
-           set wvals {}
-           foreach tag $idtags($id) {
-               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 $canvy [lindex $xvals end] $canvy \
-                      -width $lthickness -fill black]
-           $canv lower $t
-           foreach tag $idtags($id) x $xvals wid $wvals {
-               set xl [expr $x + $delta]
-               set xr [expr $x + $delta + $wid + $lthickness]
+       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
                $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
-               $canv create text $xl $canvy -anchor w -text $tag \
-                   -font $mainfont
+           } 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 headline [lindex $commitinfo($id) 0]
-       set name [lindex $commitinfo($id) 1]
-       set date [lindex $commitinfo($id) 2]
-       set linehtag($lineno) [$canv create text $xt $canvy -anchor w \
-                                  -text $headline -font $mainfont ]
-       set linentag($lineno) [$canv2 create text 3 $canvy -anchor w \
-                                  -text $name -font $namefont]
-       set linedtag($lineno) [$canv3 create text 3 $canvy -anchor w \
-                                -text $date -font $mainfont]
-       if {!$datemode && [llength $actualparents] == 1} {
-           set p [lindex $actualparents 0]
-           if {$ncleft($p) == 0 && [lsearch -exact $todo $p] < 0} {
-               assigncolor $p
-               set todo [lreplace $todo $level $level $p]
-               continue
-           }
+    }
+    set headline [lindex $commitinfo($id) 0]
+    set name [lindex $commitinfo($id) 1]
+    set date [lindex $commitinfo($id) 2]
+    set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
+                              -text $headline -font $mainfont ]
+    set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
+                              -text $name -font $namefont]
+    set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
+                              -text $date -font $mainfont]
+}
+
+proc updatetodo {level noshortcut} {
+    global currentparents ncleft todo
+    global mainline oldlevel oldtodo oldnlines
+    global canvx0 canvy linespc mainline
+    global commitinfo
+
+    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 [expr $canvx0 + $level * $linespc]
+           set y [expr $canvy - $linespc]
+           set mainline($p) [list $x $y]
+           set todo [lreplace $todo $level $level $p]
+           return 0
+       }
+    }
+
+    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
        }
        }
+    }
+    return 1
+}
+
+proc notecrossings {id lo hi corner} {
+    global oldtodo crossings cornercrossings
 
 
-       set oldtodo $todo
-       set oldlevel $level
-       set lines {}
-       for {set i 0} {$i < $nlines} {incr i} {
-           if {[lindex $todo $i] == {}} continue
-           if {[info exists linestarty($i)]} {
-               set oldstarty($i) $linestarty($i)
-               unset linestarty($i)
+    for {set i $lo} {[incr i] < $hi} {} {
+       set p [lindex $oldtodo $i]
+       if {$p == {}} continue
+       if {$i == $corner} {
+           if {![info exists cornercrossings($id)]
+               || [lsearch -exact $cornercrossings($id) $p] < 0} {
+               lappend cornercrossings($id) $p
            }
            }
-           if {$i != $level} {
-               lappend lines [list $i [lindex $todo $i]]
+           if {![info exists cornercrossings($p)]
+               || [lsearch -exact $cornercrossings($p) $id] < 0} {
+               lappend cornercrossings($p) $id
            }
            }
-       }
-       if {$nullentry >= 0} {
-           set todo [lreplace $todo $nullentry $nullentry]
-           if {$nullentry < $level} {
-               incr level -1
+       } else {
+           if {![info exists crossings($id)]
+               || [lsearch -exact $crossings($id) $p] < 0} {
+               lappend crossings($id) $p
+           }
+           if {![info exists crossings($p)]
+               || [lsearch -exact $crossings($p) $id] < 0} {
+               lappend crossings($p) $id
            }
        }
            }
        }
+    }
+}
 
 
-       set todo [lreplace $todo $level $level]
-       if {$nullentry > $level} {
-           incr nullentry -1
-       }
-       set i $level
-       foreach p $actualparents {
-           set k [lsearch -exact $todo $p]
-           if {$k < 0} {
-               assigncolor $p
-               set todo [linsert $todo $i $p]
-               if {$nullentry >= $i} {
-                   incr nullentry
+proc drawslants {} {
+    global canv mainline sidelines canvx0 canvy linespc
+    global oldlevel oldtodo todo currentparents dupparents
+    global lthickness linespc canvy colormap
+
+    set y1 [expr $canvy - $linespc]
+    set y2 $canvy
+    set i -1
+    foreach id $oldtodo {
+       incr i
+       if {$id == {}} continue
+       set xi [expr {$canvx0 + $i * $linespc}]
+       if {$i == $oldlevel} {
+           foreach p $currentparents {
+               set j [lsearch -exact $todo $p]
+               set coords [list $xi $y1]
+               set xj [expr {$canvx0 + $j * $linespc}]
+               if {$j < $i - 1} {
+                   lappend coords [expr $xj + $linespc] $y1
+                   notecrossings $p $j $i [expr {$j + 1}]
+               } elseif {$j > $i + 1} {
+                   lappend coords [expr $xj - $linespc] $y1
+                   notecrossings $p $i $j [expr {$j - 1}]
+               }
+               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]
+                   if {![info exists mainline($p)]} {
+                       set mainline($p) [list $xj $y2]
+                   }
+               } else {
+                   # normal case, no parent duplicated
+                   if {![info exists mainline($p)]} {
+                       if {$i != $j} {
+                           lappend coords $xj $y2
+                       }
+                       set mainline($p) $coords
+                   } else {
+                       lappend coords $xj $y2
+                       lappend sidelines($p) [list $coords 1]
+                   }
                }
            }
                }
            }
-           lappend lines [list $oldlevel $p]
+       } elseif {[lindex $todo $i] != $id} {
+           set j [lsearch -exact $todo $id]
+           set xj [expr {$canvx0 + $j * $linespc}]
+           lappend mainline($id) $xi $y1 $xj $y2
        }
        }
+    }
+}
 
 
-       # choose which one to do next time around
-       set todol [llength $todo]
-       set level -1
-       set latest {}
-       for {set k $todol} {[incr k -1] >= 0} {} {
-           set p [lindex $todo $k]
-           if {$p == {}} continue
-           if {$ncleft($p) == 0} {
-               if {$datemode} {
-                   if {$latest == {} || $cdate($p) > $latest} {
-                       set level $k
-                       set latest $cdate($p)
-                   }
-               } else {
+proc decidenext {} {
+    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 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]
+    set level -1
+    set latest {}
+    for {set k $todol} {[incr k -1] >= 0} {} {
+       set p [lindex $todo $k]
+       if {$ncleft($p) == 0} {
+           if {$datemode} {
+               if {$latest == {} || $cdate($p) > $latest} {
                    set level $k
                    set level $k
-                   break
+                   set latest $cdate($p)
                }
                }
+           } else {
+               set level $k
+               break
            }
        }
            }
        }
-       if {$level < 0} {
-           if {$todo != {}} {
-               puts "ERROR: none of the pending commits can be done yet:"
-               foreach p $todo {
-                   puts "  $p"
-               }
+    }
+    if {$level < 0} {
+       if {$todo != {}} {
+           puts "ERROR: none of the pending commits can be done yet:"
+           foreach p $todo {
+               puts "  $p ($ncleft($p))"
            }
            }
-           break
        }
        }
+       return -1
+    }
 
 
-       # If we are reducing, put in a null entry
-       if {$todol < $nlines} {
-           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 nullentry -1
-           } else {
-               set nullentry $i
-               set todo [linsert $todo $nullentry {}]
-               if {$level >= $i} {
-                   incr level
-               }
+    # 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 {
            }
        } else {
-           set nullentry -1
+           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
+
+    if {$phase != "incrdraw"} {
+       set phase incrdraw
+       set todo $id
+       set startcommits $id
+       initgraph
+       drawcommitline 0
+       updatetodo 0 $datemode
+    } else {
+       if {$nchildren($id) == 0} {
+           lappend todo $id
+           lappend startcommits $id
+       }
+       set level [decidenext]
+       if {$id != [lindex $todo $level]} {
+           return
+       }
+       while 1 {
+           drawslants
+           drawcommitline $level
+           if {[updatetodo $level $datemode]} {
+               set level [decidenext]
+           }
+           set id [lindex $todo $level]
+           if {![info exists commitlisted($id)]} {
+               break
+           }
+           if {[clock clicks -milliseconds] >= $nextupdate} {
+               doupdate
+               if {$stopped} break
+           }
        }
        }
+    }
+}
+
+proc finishcommits {} {
+    global phase
+    global startcommits
+    global ctext maincursor textcursor
 
 
-       foreach l $lines {
-           set i [lindex $l 0]
-           set dst [lindex $l 1]
-           set j [lsearch -exact $todo $dst]
-           if {$i == $j} {
-               if {[info exists oldstarty($i)]} {
-                   set linestarty($i) $oldstarty($i)
+    if {$phase != "incrdraw"} {
+       $canv delete all
+       $canv create text 3 3 -anchor nw -text "No commits selected" \
+           -font $mainfont -tags textitems
+       set phase {}
+       return
+    }
+    drawslants
+    set level [decidenext]
+    drawrest $level [llength $startcommits]
+    . config -cursor $maincursor
+    $ctext config -cursor $textcursor
+}
+
+proc drawgraph {} {
+    global nextupdate startmsecs startcommits todo
+
+    if {$startcommits == {}} return
+    set startmsecs [clock clicks -milliseconds]
+    set nextupdate [expr $startmsecs + 100]
+    initgraph
+    set todo [lindex $startcommits 0]
+    drawrest 0 1
+}
+
+proc drawrest {level startix} {
+    global phase stopped redisplaying selectedline
+    global datemode currentparents todo
+    global numcommits
+    global nextupdate startmsecs startcommits idline
+
+    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
+           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)
                }
                }
-               continue
-           }
-           set xi [expr {$canvx0 + $i * $linespc}]
-           set xj [expr {$canvx0 + $j * $linespc}]
-           set coords {}
-           if {[info exists oldstarty($i)] && $oldstarty($i) < $canvy} {
-               lappend coords $xi $oldstarty($i)
            }
            }
-           lappend coords $xi $canvy
-           if {$j < $i - 1} {
-               lappend coords [expr $xj + $linespc] $canvy
-           } elseif {$j > $i + 1} {
-               lappend coords [expr $xj - $linespc] $canvy
+           if {$hard} {
+               set level [decidenext]
+               if {$level < 0} break
+               drawslants
            }
            }
-           lappend coords $xj $y2
-           set t [$canv create line $coords -width $lthickness \
-                      -fill $colormap($dst)]
-           $canv lower $t
-           if {![info exists linestarty($j)]} {
-               set linestarty($j) $y2
+           if {[clock clicks -milliseconds] >= $nextupdate} {
+               update
+               incr nextupdate 100
            }
        }
     }
     set phase {}
            }
        }
     }
     set phase {}
+    set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
+    #puts "overall $drawmsecs ms for $numcommits commits"
     if {$redisplaying} {
        if {$stopped == 0 && [info exists selectedline]} {
            selectline $selectedline
     if {$redisplaying} {
        if {$stopped == 0 && [info exists selectedline]} {
            selectline $selectedline
@@ -812,7 +1093,7 @@ proc dofind {} {
     global findtype findloc findstring markedmatches commitinfo
     global numcommits lineid linehtag linentag linedtag
     global mainfont namefont canv canv2 canv3 selectedline
     global findtype findloc findstring markedmatches commitinfo
     global numcommits lineid linehtag linentag linedtag
     global mainfont namefont canv canv2 canv3 selectedline
-    global matchinglines foundstring foundstrlen idtags
+    global matchinglines foundstring foundstrlen
     unmarkmatches
     focus .
     set matchinglines {}
     unmarkmatches
     focus .
     set matchinglines {}
@@ -941,6 +1222,7 @@ proc selcanvline {x y} {
     global canv canvy0 ctext linespc selectedline
     global lineid linehtag linentag linedtag
     set ymax [lindex [$canv cget -scrollregion] 3]
     global canv canvy0 ctext linespc selectedline
     global lineid linehtag linentag linedtag
     set ymax [lindex [$canv cget -scrollregion] 3]
+    if {$ymax == {}} return
     set yfrac [lindex [$canv yview] 0]
     set y [expr {$y + $yfrac * $ymax}]
     set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
     set yfrac [lindex [$canv yview] 0]
     set y [expr {$y + $yfrac * $ymax}]
     set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
@@ -957,7 +1239,8 @@ proc selectline {l} {
     global lineid linehtag linentag linedtag
     global canvy0 linespc nparents treepending
     global cflist treediffs currentid sha1entry
     global lineid linehtag linentag linedtag
     global canvy0 linespc nparents treepending
     global cflist treediffs currentid sha1entry
-    global commentend seenfile numcommits idtags
+    global commentend seenfile idtags
+    $canv delete hover
     if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
     $canv delete secsel
     set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
     if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
     $canv delete secsel
     set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
@@ -1088,11 +1371,8 @@ proc gettreediffline {gdtf id} {
        addtocflist $id
        return
     }
        addtocflist $id
        return
     }
-    set type [lindex $line 1]
-    set file [lindex $line 3]
-    if {$type == "blob"} {
-       lappend treediffs($id) $file
-    }
+    set file [lindex $line 5]
+    lappend treediffs($id) $file
 }
 
 proc getblobdiffs {id} {
 }
 
 proc getblobdiffs {id} {
@@ -1248,7 +1528,7 @@ proc redisplay {} {
     if {$stopped > 1} return
     if {$phase == "getcommits"} return
     set redisplaying 1
     if {$stopped > 1} return
     if {$phase == "getcommits"} return
     set redisplaying 1
-    if {$phase == "drawgraph"} {
+    if {$phase == "drawgraph" || $phase == "incrdraw"} {
        set stopped 1
     } else {
        drawgraph
        set stopped 1
     } else {
        drawgraph
@@ -1257,7 +1537,7 @@ proc redisplay {} {
 
 proc incrfont {inc} {
     global mainfont namefont textfont selectedline ctext canv phase
 
 proc incrfont {inc} {
     global mainfont namefont textfont selectedline ctext canv phase
-    global stopped
+    global stopped entries
     unmarkmatches
     set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
     set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
     unmarkmatches
     set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
     set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
@@ -1311,6 +1591,82 @@ proc gotocommit {} {
     error_popup "$type $sha1string is not known"
 }
 
     error_popup "$type $sha1string is not known"
 }
 
+proc linemenu {x y id} {
+    global linectxmenu linemenuid
+    set linemenuid $id
+    $linectxmenu post $x $y
+}
+
+proc lineselect {} {
+    global linemenuid idline
+    if {[info exists linemenuid] && [info exists idline($linemenuid)]} {
+       selectline $idline($linemenuid)
+    }
+}
+
+proc lineenter {x y id} {
+    global hoverx hovery hoverid hovertimer
+    global commitinfo canv
+
+    if {![info exists commitinfo($id)]} return
+    set hoverx $x
+    set hovery $y
+    set hoverid $id
+    if {[info exists hovertimer]} {
+       after cancel $hovertimer
+    }
+    set hovertimer [after 500 linehover]
+    $canv delete hover
+}
+
+proc linemotion {x y id} {
+    global hoverx hovery hoverid hovertimer
+
+    if {[info exists hoverid] && $id == $hoverid} {
+       set hoverx $x
+       set hovery $y
+       if {[info exists hovertimer]} {
+           after cancel $hovertimer
+       }
+       set hovertimer [after 500 linehover]
+    }
+}
+
+proc lineleave {id} {
+    global hoverid hovertimer canv
+
+    if {[info exists hoverid] && $id == $hoverid} {
+       $canv delete hover
+       if {[info exists hovertimer]} {
+           after cancel $hovertimer
+           unset hovertimer
+       }
+       unset hoverid
+    }
+}
+
+proc linehover {} {
+    global hoverx hovery hoverid hovertimer
+    global canv linespc lthickness
+    global commitinfo mainfont
+
+    set text [lindex $commitinfo($hoverid) 0]
+    set ymax [lindex [$canv cget -scrollregion] 3]
+    if {$ymax == {}} return
+    set yfrac [lindex [$canv yview] 0]
+    set x [expr {$hoverx + 2 * $linespc}]
+    set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
+    set x0 [expr {$x - 2 * $lthickness}]
+    set y0 [expr {$y - 2 * $lthickness}]
+    set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
+    set y1 [expr {$y + $linespc + 2 * $lthickness}]
+    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]
+    $canv raise $t
+}
+
 proc doquit {} {
     global stopped
     set stopped 100
 proc doquit {} {
     global stopped
     set stopped 100
@@ -1326,7 +1682,6 @@ set mainfont {Helvetica 9}
 set textfont {Courier 9}
 
 set colors {green red blue magenta darkgrey brown orange}
 set textfont {Courier 9}
 
 set colors {green red blue magenta darkgrey brown orange}
-set colorbycommitter false
 
 catch {source ~/.gitk}
 
 
 catch {source ~/.gitk}
 
@@ -1340,12 +1695,7 @@ foreach arg $argv {
     switch -regexp -- $arg {
        "^$" { }
        "^-b" { set boldnames 1 }
     switch -regexp -- $arg {
        "^$" { }
        "^-b" { set boldnames 1 }
-       "^-c" { set colorbycommitter 1 }
        "^-d" { set datemode 1 }
        "^-d" { set datemode 1 }
-       "^-.*" {
-           puts stderr "unrecognized option $arg"
-           exit 1
-       }
        default {
            lappend revtreeargs $arg
        }
        default {
            lappend revtreeargs $arg
        }