Code

gitk: Add untranslated error messages to translation
[git.git] / gitk
diff --git a/gitk b/gitk
index 1dd73f7d8563dd14014c037938bff9358665020b..dce17e9f1705c835719af5d5cbaaf7bd64333a69 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -2,7 +2,7 @@
 # Tcl ignores the next line -*- tcl -*- \
 exec wish "$0" -- "$@"
 
 # Tcl ignores the next line -*- tcl -*- \
 exec wish "$0" -- "$@"
 
-# Copyright (C) 2005-2006 Paul Mackerras.  All rights reserved.
+# Copyright © 2005-2008 Paul Mackerras.  All rights reserved.
 # This program is free software; it may be used, copied, modified
 # and distributed under the terms of the GNU General Public Licence,
 # either version 2, or (at your option) any later version.
 # This program is free software; it may be used, copied, modified
 # and distributed under the terms of the GNU General Public Licence,
 # either version 2, or (at your option) any later version.
@@ -22,11 +22,11 @@ proc gitdir {} {
 # run before X event handlers, so reading from a fast source can
 # make the GUI completely unresponsive.
 proc run args {
 # run before X event handlers, so reading from a fast source can
 # make the GUI completely unresponsive.
 proc run args {
-    global isonrunq runq
+    global isonrunq runq currunq
 
     set script $args
     if {[info exists isonrunq($script)]} return
 
     set script $args
     if {[info exists isonrunq($script)]} return
-    if {$runq eq {}} {
+    if {$runq eq {} && ![info exists currunq]} {
        after idle dorunq
     }
     lappend runq [list {} $script]
        after idle dorunq
     }
     lappend runq [list {} $script]
@@ -38,10 +38,10 @@ proc filerun {fd script} {
 }
 
 proc filereadable {fd script} {
 }
 
 proc filereadable {fd script} {
-    global runq
+    global runq currunq
 
     fileevent $fd readable {}
 
     fileevent $fd readable {}
-    if {$runq eq {}} {
+    if {$runq eq {} && ![info exists currunq]} {
        after idle dorunq
     }
     lappend runq [list $fd $script]
        after idle dorunq
     }
     lappend runq [list $fd $script]
@@ -60,17 +60,19 @@ proc nukefile {fd} {
 }
 
 proc dorunq {} {
 }
 
 proc dorunq {} {
-    global isonrunq runq
+    global isonrunq runq currunq
 
     set tstart [clock clicks -milliseconds]
     set t0 $tstart
     while {[llength $runq] > 0} {
        set fd [lindex $runq 0 0]
        set script [lindex $runq 0 1]
 
     set tstart [clock clicks -milliseconds]
     set t0 $tstart
     while {[llength $runq] > 0} {
        set fd [lindex $runq 0 0]
        set script [lindex $runq 0 1]
+       set currunq [lindex $runq 0]
+       set runq [lrange $runq 1 end]
        set repeat [eval $script]
        set repeat [eval $script]
+       unset currunq
        set t1 [clock clicks -milliseconds]
        set t [expr {$t1 - $t0}]
        set t1 [clock clicks -milliseconds]
        set t [expr {$t1 - $t0}]
-       set runq [lrange $runq 1 end]
        if {$repeat ne {} && $repeat} {
            if {$fd eq {} || $repeat == 2} {
                # script returns 1 if it wants to be readded
        if {$repeat ne {} && $repeat} {
            if {$fd eq {} || $repeat == 2} {
                # script returns 1 if it wants to be readded
@@ -90,90 +92,365 @@ proc dorunq {} {
     }
 }
 
     }
 }
 
-# Start off a git rev-list process and arrange to read its output
+proc reg_instance {fd} {
+    global commfd leftover loginstance
+
+    set i [incr loginstance]
+    set commfd($i) $fd
+    set leftover($i) {}
+    return $i
+}
+
+proc unmerged_files {files} {
+    global nr_unmerged
+
+    # find the list of unmerged files
+    set mlist {}
+    set nr_unmerged 0
+    if {[catch {
+       set fd [open "| git ls-files -u" r]
+    } err]} {
+       show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
+       exit 1
+    }
+    while {[gets $fd line] >= 0} {
+       set i [string first "\t" $line]
+       if {$i < 0} continue
+       set fname [string range $line [expr {$i+1}] end]
+       if {[lsearch -exact $mlist $fname] >= 0} continue
+       incr nr_unmerged
+       if {$files eq {} || [path_filter $files $fname]} {
+           lappend mlist $fname
+       }
+    }
+    catch {close $fd}
+    return $mlist
+}
+
+proc parseviewargs {n arglist} {
+    global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs
+
+    set vdatemode($n) 0
+    set vmergeonly($n) 0
+    set glflags {}
+    set diffargs {}
+    set nextisval 0
+    set revargs {}
+    set origargs $arglist
+    set allknown 1
+    set filtered 0
+    set i -1
+    foreach arg $arglist {
+       incr i
+       if {$nextisval} {
+           lappend glflags $arg
+           set nextisval 0
+           continue
+       }
+       switch -glob -- $arg {
+           "-d" -
+           "--date-order" {
+               set vdatemode($n) 1
+               # remove from origargs in case we hit an unknown option
+               set origargs [lreplace $origargs $i $i]
+               incr i -1
+           }
+           # These request or affect diff output, which we don't want.
+           # Some could be used to set our defaults for diff display.
+           "-[puabwcrRBMC]" -
+           "--no-renames" - "--full-index" - "--binary" - "--abbrev=*" -
+           "--find-copies-harder" - "-l*" - "--ext-diff" - "--no-ext-diff" -
+           "--src-prefix=*" - "--dst-prefix=*" - "--no-prefix" -
+           "-O*" - "--text" - "--full-diff" - "--ignore-space-at-eol" -
+           "--ignore-space-change" - "-U*" - "--unified=*" {
+               lappend diffargs $arg
+           }
+           # These cause our parsing of git log's output to fail, or else
+           # they're options we want to set ourselves, so ignore them.
+           "--raw" - "--patch-with-raw" - "--patch-with-stat" -
+           "--name-only" - "--name-status" - "--color" - "--color-words" -
+           "--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" -
+           "--cc" - "-z" - "--header" - "--parents" - "--boundary" -
+           "--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
+           "--timestamp" - "relative-date" - "--date=*" - "--stdin" -
+           "--objects" - "--objects-edge" - "--reverse" {
+           }
+           # These are harmless, and some are even useful
+           "--stat=*" - "--numstat" - "--shortstat" - "--summary" -
+           "--check" - "--exit-code" - "--quiet" - "--topo-order" -
+           "--full-history" - "--dense" - "--sparse" -
+           "--follow" - "--left-right" - "--encoding=*" {
+               lappend glflags $arg
+           }
+           # These mean that we get a subset of the commits
+           "--diff-filter=*" - "--no-merges" - "--unpacked" -
+           "--max-count=*" - "--skip=*" - "--since=*" - "--after=*" -
+           "--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" -
+           "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
+           "--remove-empty" - "--first-parent" - "--cherry-pick" -
+           "-S*" - "--pickaxe-all" - "--pickaxe-regex" - {
+               set filtered 1
+               lappend glflags $arg
+           }
+           # This appears to be the only one that has a value as a
+           # separate word following it
+           "-n" {
+               set filtered 1
+               set nextisval 1
+               lappend glflags $arg
+           }
+           "--not" {
+               set notflag [expr {!$notflag}]
+               lappend revargs $arg
+           }
+           "--all" {
+               lappend revargs $arg
+           }
+           "--merge" {
+               set vmergeonly($n) 1
+               # git rev-parse doesn't understand --merge
+               lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
+           }
+           # Other flag arguments including -<n>
+           "-*" {
+               if {[string is digit -strict [string range $arg 1 end]]} {
+                   set filtered 1
+               } else {
+                   # a flag argument that we don't recognize;
+                   # that means we can't optimize
+                   set allknown 0
+               }
+               lappend glflags $arg
+           }
+           # Non-flag arguments specify commits or ranges of commits
+           default {
+               if {[string match "*...*" $arg]} {
+                   lappend revargs --gitk-symmetric-diff-marker
+               }
+               lappend revargs $arg
+           }
+       }
+    }
+    set vdflags($n) $diffargs
+    set vflags($n) $glflags
+    set vrevs($n) $revargs
+    set vfiltered($n) $filtered
+    set vorigargs($n) $origargs
+    return $allknown
+}
+
+proc parseviewrevs {view revs} {
+    global vposids vnegids
+
+    if {$revs eq {}} {
+       set revs HEAD
+    }
+    if {[catch {set ids [eval exec git rev-parse $revs]} err]} {
+       # we get stdout followed by stderr in $err
+       # for an unknown rev, git rev-parse echoes it and then errors out
+       set errlines [split $err "\n"]
+       set badrev {}
+       for {set l 0} {$l < [llength $errlines]} {incr l} {
+           set line [lindex $errlines $l]
+           if {!([string length $line] == 40 && [string is xdigit $line])} {
+               if {[string match "fatal:*" $line]} {
+                   if {[string match "fatal: ambiguous argument*" $line]
+                       && $badrev ne {}} {
+                       if {[llength $badrev] == 1} {
+                           set err "unknown revision $badrev"
+                       } else {
+                           set err "unknown revisions: [join $badrev ", "]"
+                       }
+                   } else {
+                       set err [join [lrange $errlines $l end] "\n"]
+                   }
+                   break
+               }
+               lappend badrev $line
+           }
+       }                   
+       error_popup "[mc "Error parsing revisions:"] $err"
+       return {}
+    }
+    set ret {}
+    set pos {}
+    set neg {}
+    set sdm 0
+    foreach id [split $ids "\n"] {
+       if {$id eq "--gitk-symmetric-diff-marker"} {
+           set sdm 4
+       } elseif {[string match "^*" $id]} {
+           if {$sdm != 1} {
+               lappend ret $id
+               if {$sdm == 3} {
+                   set sdm 0
+               }
+           }
+           lappend neg [string range $id 1 end]
+       } else {
+           if {$sdm != 2} {
+               lappend ret $id
+           } else {
+               lset ret end [lindex $ret end]...$id
+           }
+           lappend pos $id
+       }
+       incr sdm -1
+    }
+    set vposids($view) $pos
+    set vnegids($view) $neg
+    return $ret
+}
+
+# Start off a git log process and arrange to read its output
 proc start_rev_list {view} {
 proc start_rev_list {view} {
-    global startmsecs
-    global commfd leftover tclencoding datemode
-    global viewargs viewfiles commitidx viewcomplete vnextroot
-    global showlocalchanges commitinterest mainheadid
-    global progressdirn progresscoords proglastnc curview
-    global viewincl viewactive loginstance viewinstances
-    global pending_select mainheadid
+    global startmsecs commitidx viewcomplete curview
+    global tclencoding
+    global viewargs viewargscmd viewfiles vfilelimit
+    global showlocalchanges commitinterest
+    global viewactive viewinstances vmergeonly
+    global mainheadid
+    global vcanopt vflags vrevs vorigargs
 
     set startmsecs [clock clicks -milliseconds]
     set commitidx($view) 0
 
     set startmsecs [clock clicks -milliseconds]
     set commitidx($view) 0
-    set viewcomplete($view) 0
-    set viewactive($view) 1
-    set vnextroot($view) 0
+    # these are set this way for the error exits
+    set viewcomplete($view) 1
+    set viewactive($view) 0
     varcinit $view
 
     varcinit $view
 
-    set commits [eval exec git rev-parse --default HEAD --revs-only \
-                    $viewargs($view)]
-    set viewincl($view) {}
-    foreach c $commits {
-       if {[regexp {^[0-9a-fA-F]{40}$} $c]} {
-           lappend viewincl($view) $c
+    set args $viewargs($view)
+    if {$viewargscmd($view) ne {}} {
+       if {[catch {
+           set str [exec sh -c $viewargscmd($view)]
+       } err]} {
+           error_popup "[mc "Error executing --argscmd command:"] $err"
+           return 0
        }
        }
+       set args [concat $args [split $str "\n"]]
     }
     }
+    set vcanopt($view) [parseviewargs $view $args]
+
+    set files $viewfiles($view)
+    if {$vmergeonly($view)} {
+       set files [unmerged_files $files]
+       if {$files eq {}} {
+           global nr_unmerged
+           if {$nr_unmerged == 0} {
+               error_popup [mc "No files selected: --merge specified but\
+                            no files are unmerged."]
+           } else {
+               error_popup [mc "No files selected: --merge specified but\
+                            no unmerged files are within file limit."]
+           }
+           return 0
+       }
+    }
+    set vfilelimit($view) $files
+
+    if {$vcanopt($view)} {
+       set revs [parseviewrevs $view $vrevs($view)]
+       if {$revs eq {}} {
+           return 0
+       }
+       set args [concat $vflags($view) $revs]
+    } else {
+       set args $vorigargs($view)
+    }
+
     if {[catch {
        set fd [open [concat | git log --no-color -z --pretty=raw --parents \
     if {[catch {
        set fd [open [concat | git log --no-color -z --pretty=raw --parents \
-                        --boundary $commits "--" $viewfiles($view)] r]
+                        --boundary $args "--" $files] r]
     } err]} {
        error_popup "[mc "Error executing git log:"] $err"
     } err]} {
        error_popup "[mc "Error executing git log:"] $err"
-       exit 1
+       return 0
     }
     }
-    set i [incr loginstance]
+    set i [reg_instance $fd]
     set viewinstances($view) [list $i]
     set viewinstances($view) [list $i]
-    set commfd($i) $fd
-    set leftover($i) {}
-    if {$showlocalchanges} {
+    if {$showlocalchanges && $mainheadid ne {}} {
        lappend commitinterest($mainheadid) {dodiffindex}
     }
     fconfigure $fd -blocking 0 -translation lf -eofchar {}
     if {$tclencoding != {}} {
        fconfigure $fd -encoding $tclencoding
     }
        lappend commitinterest($mainheadid) {dodiffindex}
     }
     fconfigure $fd -blocking 0 -translation lf -eofchar {}
     if {$tclencoding != {}} {
        fconfigure $fd -encoding $tclencoding
     }
-    filerun $fd [list getcommitlines $fd $i $view]
+    filerun $fd [list getcommitlines $fd $i $view 0]
     nowbusy $view [mc "Reading"]
     nowbusy $view [mc "Reading"]
-    if {$view == $curview} {
-       set progressdirn 1
-       set progresscoords {0 0}
-       set proglastnc 0
-       set pending_select $mainheadid
+    set viewcomplete($view) 0
+    set viewactive($view) 1
+    return 1
+}
+
+proc stop_instance {inst} {
+    global commfd leftover
+
+    set fd $commfd($inst)
+    catch {
+       set pid [pid $fd]
+
+       if {$::tcl_platform(platform) eq {windows}} {
+           exec kill -f $pid
+       } else {
+           exec kill $pid
+       }
+    }
+    catch {close $fd}
+    nukefile $fd
+    unset commfd($inst)
+    unset leftover($inst)
+}
+
+proc stop_backends {} {
+    global commfd
+
+    foreach inst [array names commfd] {
+       stop_instance $inst
     }
 }
 
 proc stop_rev_list {view} {
     }
 }
 
 proc stop_rev_list {view} {
-    global commfd viewinstances leftover
+    global viewinstances
 
     foreach inst $viewinstances($view) {
 
     foreach inst $viewinstances($view) {
-       set fd $commfd($inst)
-       catch {
-           set pid [pid $fd]
-           exec kill $pid
-       }
-       catch {close $fd}
-       nukefile $fd
-       unset commfd($inst)
-       unset leftover($inst)
+       stop_instance $inst
     }
     set viewinstances($view) {}
 }
 
     }
     set viewinstances($view) {}
 }
 
-proc getcommits {} {
-    global canv curview
+proc reset_pending_select {selid} {
+    global pending_select mainheadid selectheadid
+
+    if {$selid ne {}} {
+       set pending_select $selid
+    } elseif {$selectheadid ne {}} {
+       set pending_select $selectheadid
+    } else {
+       set pending_select $mainheadid
+    }
+}
+
+proc getcommits {selid} {
+    global canv curview need_redisplay viewactive
 
     initlayout
 
     initlayout
-    start_rev_list $curview
-    show_status [mc "Reading commits..."]
+    if {[start_rev_list $curview]} {
+       reset_pending_select $selid
+       show_status [mc "Reading commits..."]
+       set need_redisplay 1
+    } else {
+       show_status [mc "No commits selected"]
+    }
 }
 
 proc updatecommits {} {
 }
 
 proc updatecommits {} {
-    global curview viewargs viewfiles viewincl viewinstances
-    global viewactive viewcomplete loginstance tclencoding mainheadid
-    global varcid startmsecs commfd showneartags showlocalchanges leftover
+    global curview vcanopt vorigargs vfilelimit viewinstances
+    global viewactive viewcomplete tclencoding
+    global startmsecs showneartags showlocalchanges
     global mainheadid pending_select
     global mainheadid pending_select
+    global isworktree
+    global varcid vposids vnegids vflags vrevs
 
 
+    set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
     set oldmainid $mainheadid
     rereadrefs
     if {$showlocalchanges} {
     set oldmainid $mainheadid
     rereadrefs
     if {$showlocalchanges} {
@@ -185,52 +462,60 @@ proc updatecommits {} {
        }
     }
     set view $curview
        }
     }
     set view $curview
-    set commits [exec git rev-parse --default HEAD --revs-only \
-                    $viewargs($view)]
-    set pos {}
-    set neg {}
-    set flags {}
-    foreach c $commits {
-       if {[string match "^*" $c]} {
-           lappend neg $c
-       } elseif {[regexp {^[0-9a-fA-F]{40}$} $c]} {
-           if {!([info exists varcid($view,$c)] ||
-                 [lsearch -exact $viewincl($view) $c] >= 0)} {
-               lappend pos $c
+    if {$vcanopt($view)} {
+       set oldpos $vposids($view)
+       set oldneg $vnegids($view)
+       set revs [parseviewrevs $view $vrevs($view)]
+       if {$revs eq {}} {
+           return
+       }
+       # note: getting the delta when negative refs change is hard,
+       # and could require multiple git log invocations, so in that
+       # case we ask git log for all the commits (not just the delta)
+       if {$oldneg eq $vnegids($view)} {
+           set newrevs {}
+           set npos 0
+           # take out positive refs that we asked for before or
+           # that we have already seen
+           foreach rev $revs {
+               if {[string length $rev] == 40} {
+                   if {[lsearch -exact $oldpos $rev] < 0
+                       && ![info exists varcid($view,$rev)]} {
+                       lappend newrevs $rev
+                       incr npos
+                   }
+               } else {
+                   lappend $newrevs $rev
+               }
            }
            }
-       } else {
-           lappend flags $c
+           if {$npos == 0} return
+           set revs $newrevs
+           set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
        }
        }
+       set args [concat $vflags($view) $revs --not $oldpos]
+    } else {
+       set args $vorigargs($view)
     }
     }
-    if {$pos eq {}} {
-       return
-    }
-    foreach id $viewincl($view) {
-       lappend neg "^$id"
-    }
-    set viewincl($view) [concat $viewincl($view) $pos]
     if {[catch {
        set fd [open [concat | git log --no-color -z --pretty=raw --parents \
     if {[catch {
        set fd [open [concat | git log --no-color -z --pretty=raw --parents \
-                        --boundary $pos $neg $flags "--" $viewfiles($view)] r]
+                         --boundary $args "--" $vfilelimit($view)] r]
     } err]} {
     } err]} {
-       error_popup "Error executing git log: $err"
-       exit 1
+       error_popup "[mc "Error executing git log:"] $err"
+       return
     }
     if {$viewactive($view) == 0} {
        set startmsecs [clock clicks -milliseconds]
     }
     }
     if {$viewactive($view) == 0} {
        set startmsecs [clock clicks -milliseconds]
     }
-    set i [incr loginstance]
+    set i [reg_instance $fd]
     lappend viewinstances($view) $i
     lappend viewinstances($view) $i
-    set commfd($i) $fd
-    set leftover($i) {}
     fconfigure $fd -blocking 0 -translation lf -eofchar {}
     if {$tclencoding != {}} {
        fconfigure $fd -encoding $tclencoding
     }
     fconfigure $fd -blocking 0 -translation lf -eofchar {}
     if {$tclencoding != {}} {
        fconfigure $fd -encoding $tclencoding
     }
-    filerun $fd [list getcommitlines $fd $i $view]
+    filerun $fd [list getcommitlines $fd $i $view 1]
     incr viewactive($view)
     set viewcomplete($view) 0
     incr viewactive($view)
     set viewcomplete($view) 0
-    set pending_select $mainheadid
+    reset_pending_select {}
     nowbusy $view "Reading"
     if {$showneartags} {
        getallcommits
     nowbusy $view "Reading"
     if {$showneartags} {
        getallcommits
@@ -240,15 +525,18 @@ proc updatecommits {} {
 proc reloadcommits {} {
     global curview viewcomplete selectedline currentid thickerline
     global showneartags treediffs commitinterest cached_commitrow
 proc reloadcommits {} {
     global curview viewcomplete selectedline currentid thickerline
     global showneartags treediffs commitinterest cached_commitrow
-    global progresscoords targetid
+    global targetid
+
+    set selid {}
+    if {$selectedline ne {}} {
+       set selid $currentid
+    }
 
     if {!$viewcomplete($curview)} {
        stop_rev_list $curview
 
     if {!$viewcomplete($curview)} {
        stop_rev_list $curview
-       set progresscoords {0 0}
-       adjustprogress
     }
     resetvarcs $curview
     }
     resetvarcs $curview
-    catch {unset selectedline}
+    set selectedline {}
     catch {unset currentid}
     catch {unset thickerline}
     catch {unset treediffs}
     catch {unset currentid}
     catch {unset thickerline}
     catch {unset treediffs}
@@ -262,7 +550,7 @@ proc reloadcommits {} {
     catch {unset cached_commitrow}
     catch {unset targetid}
     setcanvscroll
     catch {unset cached_commitrow}
     catch {unset targetid}
     setcanvscroll
-    getcommits
+    getcommits $selid
     return 0
 }
 
     return 0
 }
 
@@ -321,14 +609,27 @@ proc resetvarcs {view} {
     catch {unset ordertok}
 }
 
     catch {unset ordertok}
 }
 
+# returns a list of the commits with no children
+proc seeds {v} {
+    global vdownptr vleftptr varcstart
+
+    set ret {}
+    set a [lindex $vdownptr($v) 0]
+    while {$a != 0} {
+       lappend ret [lindex $varcstart($v) $a]
+       set a [lindex $vleftptr($v) $a]
+    }
+    return $ret
+}
+
 proc newvarc {view id} {
 proc newvarc {view id} {
-    global varcid varctok parents children datemode
+    global varcid varctok parents children vdatemode
     global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
     global commitdata commitinfo vseedcount varccommits vlastins
 
     set a [llength $varctok($view)]
     set vid $view,$id
     global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
     global commitdata commitinfo vseedcount varccommits vlastins
 
     set a [llength $varctok($view)]
     set vid $view,$id
-    if {[llength $children($vid)] == 0 || $datemode} {
+    if {[llength $children($vid)] == 0 || $vdatemode($view)} {
        if {![info exists commitinfo($id)]} {
            parsecommit $id $commitdata($id) 1
        }
        if {![info exists commitinfo($id)]} {
            parsecommit $id $commitdata($id) 1
        }
@@ -415,11 +716,12 @@ proc splitvarc {p v} {
        set varcid($v,$id) $na
     }
     lappend vdownptr($v) [lindex $vdownptr($v) $oa]
        set varcid($v,$id) $na
     }
     lappend vdownptr($v) [lindex $vdownptr($v) $oa]
+    lappend vlastins($v) [lindex $vlastins($v) $oa]
     lset vdownptr($v) $oa $na
     lset vdownptr($v) $oa $na
+    lset vlastins($v) $oa 0
     lappend vupptr($v) $oa
     lappend vleftptr($v) 0
     lappend vbackptr($v) 0
     lappend vupptr($v) $oa
     lappend vleftptr($v) 0
     lappend vbackptr($v) 0
-    lappend vlastins($v) 0
     for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
        lset vupptr($v) $b $na
     }
     for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
        lset vupptr($v) $b $na
     }
@@ -427,7 +729,7 @@ proc splitvarc {p v} {
 
 proc renumbervarc {a v} {
     global parents children varctok varcstart varccommits
 
 proc renumbervarc {a v} {
     global parents children varctok varcstart varccommits
-    global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod datemode
+    global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode
 
     set t1 [clock clicks -milliseconds]
     set todo {}
 
     set t1 [clock clicks -milliseconds]
     set todo {}
@@ -463,7 +765,7 @@ proc renumbervarc {a v} {
                                      $children($v,$id)]
        }
        set oldtok [lindex $varctok($v) $a]
                                      $children($v,$id)]
        }
        set oldtok [lindex $varctok($v) $a]
-       if {!$datemode} {
+       if {!$vdatemode($v)} {
            set tok {}
        } else {
            set tok $oldtok
            set tok {}
        } else {
            set tok $oldtok
@@ -513,6 +815,9 @@ proc renumbervarc {a v} {
            if {$d != 0} {
                lset vbackptr($v) $d $c
            }
            if {$d != 0} {
                lset vbackptr($v) $d $c
            }
+           if {[lindex $vlastins($v) $b] == $a} {
+               lset vlastins($v) $b $c
+           }
            lset vupptr($v) $a $ka
            set c [lindex $vlastins($v) $ka]
            if {$c == 0 || \
            lset vupptr($v) $a $ka
            set c [lindex $vlastins($v) $ka]
            if {$c == 0 || \
@@ -551,6 +856,9 @@ proc renumbervarc {a v} {
     #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
 }
 
     #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
 }
 
+# Fix up the graph after we have found out that in view $v,
+# $p (a commit that we have already seen) is actually the parent
+# of the last commit in arc $a.
 proc fix_reversal {p a v} {
     global varcid varcstart varctok vupptr
 
 proc fix_reversal {p a v} {
     global varcid varcstart varctok vupptr
 
@@ -620,9 +928,7 @@ proc insertfakerow {id p} {
     set numcommits [incr commitidx($v)]
     # note we deliberately don't update varcstart($v) even if $i == 0
     set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
     set numcommits [incr commitidx($v)]
     # note we deliberately don't update varcstart($v) even if $i == 0
     set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
-    if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
-       modify_arc $v $a $i
-    }
+    modify_arc $v $a $i
     if {[info exists targetid]} {
        if {![comes_before $targetid $p]} {
            incr targetrow
     if {[info exists targetid]} {
        if {![comes_before $targetid $p]} {
            incr targetrow
@@ -659,12 +965,10 @@ proc removefakerow {id} {
     if {$j >= 0} {
        set children($v,$p) [lreplace $children($v,$p) $j $j]
     }
     if {$j >= 0} {
        set children($v,$p) [lreplace $children($v,$p) $j $j]
     }
-    if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
-       modify_arc $v $a $i
-    }
+    modify_arc $v $a $i
     if {[info exist currentid] && $id eq $currentid} {
        unset currentid
     if {[info exist currentid] && $id eq $currentid} {
        unset currentid
-       unset selectedline
+       set selectedline {}
     }
     if {[info exists targetid] && $targetid eq $id} {
        set targetid $p
     }
     if {[info exists targetid] && $targetid eq $id} {
        set targetid $p
@@ -704,9 +1008,19 @@ proc vtokcmp {v a b} {
                [lindex $varctok($v) $varcid($v,$b)]]
 }
 
                [lindex $varctok($v) $varcid($v,$b)]]
 }
 
+# This assumes that if lim is not given, the caller has checked that
+# arc a's token is less than $vtokmod($v)
 proc modify_arc {v a {lim {}}} {
     global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
 
 proc modify_arc {v a {lim {}}} {
     global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
 
+    if {$lim ne {}} {
+       set c [string compare [lindex $varctok($v) $a] $vtokmod($v)]
+       if {$c > 0} return
+       if {$c == 0} {
+           set r [lindex $varcrow($v) $a]
+           if {$r ne {} && $vrowmod($v) <= $r + $lim} return
+       }
+    }
     set vtokmod($v) [lindex $varctok($v) $a]
     set varcmod($v) $a
     if {$v == $curview} {
     set vtokmod($v) [lindex $varctok($v) $a]
     set varcmod($v) $a
     if {$v == $curview} {
@@ -732,6 +1046,14 @@ proc update_arcrows {v} {
     global vupptr vdownptr vleftptr varctok
     global displayorder parentlist curview cached_commitrow
 
     global vupptr vdownptr vleftptr varctok
     global displayorder parentlist curview cached_commitrow
 
+    if {$vrowmod($v) == $commitidx($v)} return
+    if {$v == $curview} {
+       if {[llength $displayorder] > $vrowmod($v)} {
+           set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
+           set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
+       }
+       catch {unset cached_commitrow}
+    }
     set narctot [expr {[llength $varctok($v)] - 1}]
     set a $varcmod($v)
     while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
     set narctot [expr {[llength $varctok($v)] - 1}]
     set a $varcmod($v)
     while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
@@ -750,23 +1072,12 @@ proc update_arcrows {v} {
        set row 0
     } else {
        set arcn [lindex $varcix($v) $a]
        set row 0
     } else {
        set arcn [lindex $varcix($v) $a]
-       # see if a is the last arc; if so, nothing to do
-       if {$arcn == $narctot - 1} {
-           return
-       }
        if {[llength $vrownum($v)] > $arcn + 1} {
            set vrownum($v) [lrange $vrownum($v) 0 $arcn]
            set varcorder($v) [lrange $varcorder($v) 0 $arcn]
        }
        set row [lindex $varcrow($v) $a]
     }
        if {[llength $vrownum($v)] > $arcn + 1} {
            set vrownum($v) [lrange $vrownum($v) 0 $arcn]
            set varcorder($v) [lrange $varcorder($v) 0 $arcn]
        }
        set row [lindex $varcrow($v) $a]
     }
-    if {$v == $curview} {
-       if {[llength $displayorder] > $vrowmod($v)} {
-           set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
-           set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
-       }
-       catch {unset cached_commitrow}
-    }
     while {1} {
        set p $a
        incr row [llength $varccommits($v,$a)]
     while {1} {
        set p $a
        incr row [llength $varccommits($v,$a)]
@@ -960,12 +1271,38 @@ proc closevarcs {v} {
     }
 }
 
     }
 }
 
-proc getcommitlines {fd inst view}  {
+# Use $rwid as a substitute for $id, i.e. reparent $id's children to $rwid
+# Assumes we already have an arc for $rwid.
+proc rewrite_commit {v id rwid} {
+    global children parents varcid varctok vtokmod varccommits
+
+    foreach ch $children($v,$id) {
+       # make $rwid be $ch's parent in place of $id
+       set i [lsearch -exact $parents($v,$ch) $id]
+       if {$i < 0} {
+           puts "oops rewrite_commit didn't find $id in parent list for $ch"
+       }
+       set parents($v,$ch) [lreplace $parents($v,$ch) $i $i $rwid]
+       # add $ch to $rwid's children and sort the list if necessary
+       if {[llength [lappend children($v,$rwid) $ch]] > 1} {
+           set children($v,$rwid) [lsort -command [list vtokcmp $v] \
+                                       $children($v,$rwid)]
+       }
+       # fix the graph after joining $id to $rwid
+       set a $varcid($v,$ch)
+       fix_reversal $rwid $a $v
+       # parentlist is wrong for the last element of arc $a
+       # even if displayorder is right, hence the 3rd arg here
+       modify_arc $v $a [expr {[llength $varccommits($v,$a)] - 1}]
+    }
+}
+
+proc getcommitlines {fd inst view updating}  {
     global cmitlisted commitinterest leftover
     global cmitlisted commitinterest leftover
-    global commitidx commitdata datemode
+    global commitidx commitdata vdatemode
     global parents children curview hlview
     global parents children curview hlview
-    global vnextroot idpending ordertok
-    global varccommits varcid varctok vtokmod
+    global idpending ordertok
+    global varccommits varcid varctok vtokmod vfilelimit
 
     set stuff [read $fd 500000]
     # git log doesn't terminate the last commit with a null...
 
     set stuff [read $fd 500000]
     # git log doesn't terminate the last commit with a null...
@@ -976,7 +1313,7 @@ proc getcommitlines {fd inst view}  {
        if {![eof $fd]} {
            return 1
        }
        if {![eof $fd]} {
            return 1
        }
-       global commfd viewcomplete viewactive viewname progresscoords
+       global commfd viewcomplete viewactive viewname
        global viewinstances
        unset commfd($inst)
        set i [lsearch -exact $viewinstances($view) $inst]
        global viewinstances
        unset commfd($inst)
        set i [lsearch -exact $viewinstances($view) $inst]
@@ -992,10 +1329,10 @@ proc getcommitlines {fd inst view}  {
            }
            if {[string range $err 0 4] == "usage"} {
                set err "Gitk: error reading commits$fv:\
            }
            if {[string range $err 0 4] == "usage"} {
                set err "Gitk: error reading commits$fv:\
-                       bad arguments to git rev-list."
+                       bad arguments to git log."
                if {$viewname($view) eq "Command line"} {
                    append err \
                if {$viewname($view) eq "Command line"} {
                    append err \
-                       "  (Note: arguments to gitk are passed to git rev-list\
+                       "  (Note: arguments to gitk are passed to git log\
                         to allow selection of commits to be displayed.)"
                }
            } else {
                         to allow selection of commits to be displayed.)"
                }
            } else {
@@ -1009,11 +1346,9 @@ proc getcommitlines {fd inst view}  {
            # appeared in the list
            closevarcs $view
            notbusy $view
            # appeared in the list
            closevarcs $view
            notbusy $view
-           set progresscoords {0 0}
-           adjustprogress
        }
        if {$view == $curview} {
        }
        if {$view == $curview} {
-           run chewcommits $view
+           run chewcommits
        }
        return 0
     }
        }
        return 0
     }
@@ -1066,6 +1401,26 @@ proc getcommitlines {fd inst view}  {
        }
        set id [lindex $ids 0]
        set vid $view,$id
        }
        set id [lindex $ids 0]
        set vid $view,$id
+
+       if {!$listed && $updating && ![info exists varcid($vid)] &&
+           $vfilelimit($view) ne {}} {
+           # git log doesn't rewrite parents for unlisted commits
+           # when doing path limiting, so work around that here
+           # by working out the rewritten parent with git rev-list
+           # and if we already know about it, using the rewritten
+           # parent as a substitute parent for $id's children.
+           if {![catch {
+               set rwid [exec git rev-list --first-parent --max-count=1 \
+                             $id -- $vfilelimit($view)]
+           }]} {
+               if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
+                   # use $rwid in place of $id
+                   rewrite_commit $view $id $rwid
+                   continue
+               }
+           }
+       }
+
        set a 0
        if {[info exists varcid($vid)]} {
            if {$cmitlisted($vid) || !$listed} continue
        set a 0
        if {[info exists varcid($vid)]} {
            if {$cmitlisted($vid) || !$listed} continue
@@ -1084,7 +1439,7 @@ proc getcommitlines {fd inst view}  {
        } elseif {$a == 0 && [llength $children($vid)] == 1} {
            set k [lindex $children($vid) 0]
            if {[llength $parents($view,$k)] == 1 &&
        } elseif {$a == 0 && [llength $children($vid)] == 1} {
            set k [lindex $children($vid) 0]
            if {[llength $parents($view,$k)] == 1 &&
-               (!$datemode ||
+               (!$vdatemode($view) ||
                 $varcid($view,$k) == [llength $varctok($view)] - 1)} {
                set a $varcid($view,$k)
            }
                 $varcid($view,$k) == [llength $varctok($view)] - 1)} {
                set a $varcid($view,$k)
            }
@@ -1128,68 +1483,51 @@ proc getcommitlines {fd inst view}  {
        set gotsome 1
     }
     if {$gotsome} {
        set gotsome 1
     }
     if {$gotsome} {
-       run chewcommits $view
+       global numcommits hlview
+
+       if {$view == $curview} {
+           set numcommits $commitidx($view)
+           run chewcommits
+       }
+       if {[info exists hlview] && $view == $hlview} {
+           # we never actually get here...
+           run vhighlightmore
+       }
        foreach s $scripts {
            eval $s
        }
        foreach s $scripts {
            eval $s
        }
-       if {$view == $curview} {
-           # update progress bar
-           global progressdirn progresscoords proglastnc
-           set inc [expr {($commitidx($view) - $proglastnc) * 0.0002}]
-           set proglastnc $commitidx($view)
-           set l [lindex $progresscoords 0]
-           set r [lindex $progresscoords 1]
-           if {$progressdirn} {
-               set r [expr {$r + $inc}]
-               if {$r >= 1.0} {
-                   set r 1.0
-                   set progressdirn 0
-               }
-               if {$r > 0.2} {
-                   set l [expr {$r - 0.2}]
-               }
-           } else {
-               set l [expr {$l - $inc}]
-               if {$l <= 0.0} {
-                   set l 0.0
-                   set progressdirn 1
-               }
-               set r [expr {$l + 0.2}]
-           }
-           set progresscoords [list $l $r]
-           adjustprogress
-       }
     }
     return 2
 }
 
     }
     return 2
 }
 
-proc chewcommits {view} {
+proc chewcommits {} {
     global curview hlview viewcomplete
     global pending_select
 
     global curview hlview viewcomplete
     global pending_select
 
-    if {$view == $curview} {
-       layoutmore
-       if {$viewcomplete($view)} {
-           global commitidx varctok
-           global numcommits startmsecs
-           global mainheadid commitinfo nullid
+    layoutmore
+    if {$viewcomplete($curview)} {
+       global commitidx varctok
+       global numcommits startmsecs
 
 
-           if {[info exists pending_select]} {
+       if {[info exists pending_select]} {
+           update
+           reset_pending_select {}
+
+           if {[commitinview $pending_select $curview]} {
+               selectline [rowofcommit $pending_select] 1
+           } else {
                set row [first_real_row]
                selectline $row 1
            }
                set row [first_real_row]
                selectline $row 1
            }
-           if {$commitidx($curview) > 0} {
-               #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
-               #puts "overall $ms ms for $numcommits commits"
-               #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
-           } else {
-               show_status [mc "No commits selected"]
-           }
-           notbusy layout
        }
        }
-    }
-    if {[info exists hlview] && $view == $hlview} {
-       vhighlightmore
+       if {$commitidx($curview) > 0} {
+           #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
+           #puts "overall $ms ms for $numcommits commits"
+           #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
+       } else {
+           show_status [mc "No commits selected"]
+       }
+       notbusy layout
     }
     return 0
 }
     }
     return 0
 }
@@ -1239,7 +1577,7 @@ proc parsecommit {id contents listed} {
        set headline [string trimright [string range $headline 0 $i]]
     }
     if {!$listed} {
        set headline [string trimright [string range $headline 0 $i]]
     }
     if {!$listed} {
-       # git rev-list indents the comment by 4 spaces;
+       # git log indents the comment by 4 spaces;
        # if we got this via git cat-file, add the indentation
        set newcomment {}
        foreach line [split $comment "\n"] {
        # if we got this via git cat-file, add the indentation
        set newcomment {}
        foreach line [split $comment "\n"] {
@@ -1273,6 +1611,7 @@ proc getcommit {id} {
 proc readrefs {} {
     global tagids idtags headids idheads tagobjid
     global otherrefids idotherrefs mainhead mainheadid
 proc readrefs {} {
     global tagids idtags headids idheads tagobjid
     global otherrefids idotherrefs mainhead mainheadid
+    global selecthead selectheadid
 
     foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
        catch {unset $v}
 
     foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
        catch {unset $v}
@@ -1313,12 +1652,16 @@ proc readrefs {} {
     set mainhead {}
     set mainheadid {}
     catch {
     set mainhead {}
     set mainheadid {}
     catch {
+       set mainheadid [exec git rev-parse HEAD]
        set thehead [exec git symbolic-ref HEAD]
        if {[string match "refs/heads/*" $thehead]} {
            set mainhead [string range $thehead 11 end]
        set thehead [exec git symbolic-ref HEAD]
        if {[string match "refs/heads/*" $thehead]} {
            set mainhead [string range $thehead 11 end]
-           if {[info exists headids($mainhead)]} {
-               set mainheadid $headids($mainhead)
-           }
+       }
+    }
+    set selectheadid {}
+    if {$selecthead ne {}} {
+       catch {
+           set selectheadid [exec git rev-parse --verify $selecthead]
        }
     }
 }
        }
     }
 }
@@ -1421,7 +1764,7 @@ proc makewindow {} {
     global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
     global headctxmenu progresscanv progressitem progresscoords statusw
     global fprogitem fprogcoord lastprogupdate progupdatepending
     global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
     global headctxmenu progresscanv progressitem progresscoords statusw
     global fprogitem fprogcoord lastprogupdate progupdatepending
-    global rprogitem rprogcoord
+    global rprogitem rprogcoord rownumsel numcommits
     global have_tk85
 
     menu .bar
     global have_tk85
 
     menu .bar
@@ -1537,6 +1880,18 @@ proc makewindow {} {
        -state disabled -width 26
     pack .tf.bar.rightbut -side left -fill y
 
        -state disabled -width 26
     pack .tf.bar.rightbut -side left -fill y
 
+    label .tf.bar.rowlabel -text [mc "Row"]
+    set rownumsel {}
+    label .tf.bar.rownum -width 7 -font textfont -textvariable rownumsel \
+       -relief sunken -anchor e
+    label .tf.bar.rowlabel2 -text "/"
+    label .tf.bar.numcommits -width 7 -font textfont -textvariable numcommits \
+       -relief sunken -anchor e
+    pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
+       -side left
+    global selectedline
+    trace add variable selectedline write selectedline_change
+
     # Status label and progress bar
     set statusw .tf.bar.status
     label $statusw -width 15 -relief sunken
     # Status label and progress bar
     set statusw .tf.bar.status
     label $statusw -width 15 -relief sunken
@@ -1608,6 +1963,7 @@ proc makewindow {} {
     }
     frame .bleft.top
     frame .bleft.mid
     }
     frame .bleft.top
     frame .bleft.mid
+    frame .bleft.bottom
 
     button .bleft.top.search -text [mc "Search"] -command dosearch
     pack .bleft.top.search -side left -padx 5
 
     button .bleft.top.search -text [mc "Search"] -command dosearch
     pack .bleft.top.search -side left -padx 5
@@ -1635,18 +1991,25 @@ proc makewindow {} {
     checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
        -command changeignorespace -variable ignorespace
     pack .bleft.mid.ignspace -side left -padx 5
     checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
        -command changeignorespace -variable ignorespace
     pack .bleft.mid.ignspace -side left -padx 5
-    set ctext .bleft.ctext
+    set ctext .bleft.bottom.ctext
     text $ctext -background $bgcolor -foreground $fgcolor \
        -state disabled -font textfont \
     text $ctext -background $bgcolor -foreground $fgcolor \
        -state disabled -font textfont \
-       -yscrollcommand scrolltext -wrap none
+       -yscrollcommand scrolltext -wrap none \
+       -xscrollcommand ".bleft.bottom.sbhorizontal set"
     if {$have_tk85} {
        $ctext conf -tabstyle wordprocessor
     }
     if {$have_tk85} {
        $ctext conf -tabstyle wordprocessor
     }
-    scrollbar .bleft.sb -command "$ctext yview"
+    scrollbar .bleft.bottom.sb -command "$ctext yview"
+    scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \
+       -width 10
     pack .bleft.top -side top -fill x
     pack .bleft.mid -side top -fill x
     pack .bleft.top -side top -fill x
     pack .bleft.mid -side top -fill x
-    pack .bleft.sb -side right -fill y
-    pack $ctext -side left -fill both -expand 1
+    grid $ctext .bleft.bottom.sb -sticky nsew
+    grid .bleft.bottom.sbhorizontal -sticky ew
+    grid columnconfigure .bleft.bottom 0 -weight 1
+    grid rowconfigure .bleft.bottom 0 -weight 1
+    grid rowconfigure .bleft.bottom 1 -weight 0
+    pack .bleft.bottom -side top -fill both -expand 1
     lappend bglist $ctext
     lappend fglist $ctext
 
     lappend bglist $ctext
     lappend fglist $ctext
 
@@ -1711,9 +2074,17 @@ proc makewindow {} {
     .pwbottom add .bright
     .ctop add .pwbottom
 
     .pwbottom add .bright
     .ctop add .pwbottom
 
-    # restore window position if known
+    # restore window width & height if known
     if {[info exists geometry(main)]} {
     if {[info exists geometry(main)]} {
-        wm geometry . "$geometry(main)"
+       if {[scan $geometry(main) "%dx%d" w h] >= 2} {
+           if {$w > [winfo screenwidth .]} {
+               set w [winfo screenwidth .]
+           }
+           if {$h > [winfo screenheight .]} {
+               set h [winfo screenheight .]
+           }
+           wm geometry . "${w}x$h"
+       }
     }
 
     if {[tk windowingsystem] eq {aqua}} {
     }
 
     if {[tk windowingsystem] eq {aqua}} {
@@ -1768,7 +2139,7 @@ proc makewindow {} {
     bindkey k "selnextline 1"
     bindkey j "goback"
     bindkey l "goforw"
     bindkey k "selnextline 1"
     bindkey j "goback"
     bindkey l "goforw"
-    bindkey b "$ctext yview scroll -1 pages"
+    bindkey b prevfile
     bindkey d "$ctext yview scroll 18 units"
     bindkey u "$ctext yview scroll -18 units"
     bindkey / {dofind 1 1}
     bindkey d "$ctext yview scroll 18 units"
     bindkey u "$ctext yview scroll -18 units"
     bindkey / {dofind 1 1}
@@ -1787,6 +2158,7 @@ proc makewindow {} {
     bind . <$M1B-minus> {incrfont -1}
     bind . <$M1B-KP_Subtract> {incrfont -1}
     wm protocol . WM_DELETE_WINDOW doquit
     bind . <$M1B-minus> {incrfont -1}
     bind . <$M1B-KP_Subtract> {incrfont -1}
     wm protocol . WM_DELETE_WINDOW doquit
+    bind . <Destroy> {stop_backends}
     bind . <Button-1> "click %W"
     bind $fstring <Key-Return> {dofind 1 1}
     bind $sha1entry <Key-Return> gotocommit
     bind . <Button-1> "click %W"
     bind $fstring <Key-Return> {dofind 1 1}
     bind $sha1entry <Key-Return> gotocommit
@@ -1794,7 +2166,8 @@ proc makewindow {} {
     bind $cflist <1> {sel_flist %W %x %y; break}
     bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
     bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
     bind $cflist <1> {sel_flist %W %x %y; break}
     bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
     bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
-    bind $cflist <Button-3> {pop_flist_menu %W %X %Y %x %y}
+    global ctxbut
+    bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
 
     set maincursor [. cget -cursor]
     set textcursor [$ctext cget -cursor]
 
     set maincursor [. cget -cursor]
     set textcursor [$ctext cget -cursor]
@@ -1840,6 +2213,10 @@ proc makewindow {} {
        -command {flist_hl 0}
     $flist_menu add command -label [mc "Highlight this only"] \
        -command {flist_hl 1}
        -command {flist_hl 0}
     $flist_menu add command -label [mc "Highlight this only"] \
        -command {flist_hl 1}
+    $flist_menu add command -label [mc "External diff"] \
+        -command {external_diff}
+    $flist_menu add command -label [mc "Blame parent commit"] \
+        -command {external_blame 1}
 }
 
 # Windows sends all mouse wheel events to the current focused window, not
 }
 
 # Windows sends all mouse wheel events to the current focused window, not
@@ -1860,6 +2237,17 @@ proc windows_mousewheel_redirector {W X Y D} {
     }
 }
 
     }
 }
 
+# Update row number label when selectedline changes
+proc selectedline_change {n1 n2 op} {
+    global selectedline rownumsel
+
+    if {$selectedline eq {}} {
+       set rownumsel {}
+    } else {
+       set rownumsel [expr {$selectedline + 1}]
+    }
+}
+
 # mouse-2 makes all windows scan vertically, but only the one
 # the cursor is in scans horizontally
 proc canvscan {op w x y} {
 # mouse-2 makes all windows scan vertically, but only the one
 # the cursor is in scans horizontally
 proc canvscan {op w x y} {
@@ -1941,9 +2329,10 @@ proc savestuff {w} {
     global canv canv2 canv3 mainfont textfont uifont tabstop
     global stuffsaved findmergefiles maxgraphpct
     global maxwidth showneartags showlocalchanges
     global canv canv2 canv3 mainfont textfont uifont tabstop
     global stuffsaved findmergefiles maxgraphpct
     global maxwidth showneartags showlocalchanges
-    global viewname viewfiles viewargs viewperm nextviewnum
+    global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
     global cmitmode wrapcomment datetimeformat limitdiffs
     global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
     global cmitmode wrapcomment datetimeformat limitdiffs
     global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
+    global autoselect extdifftool
 
     if {$stuffsaved} return
     if {![winfo viewable .]} return
 
     if {$stuffsaved} return
     if {![winfo viewable .]} return
@@ -1958,6 +2347,7 @@ proc savestuff {w} {
        puts $f [list set maxwidth $maxwidth]
        puts $f [list set cmitmode $cmitmode]
        puts $f [list set wrapcomment $wrapcomment]
        puts $f [list set maxwidth $maxwidth]
        puts $f [list set cmitmode $cmitmode]
        puts $f [list set wrapcomment $wrapcomment]
+       puts $f [list set autoselect $autoselect]
        puts $f [list set showneartags $showneartags]
        puts $f [list set showlocalchanges $showlocalchanges]
        puts $f [list set datetimeformat $datetimeformat]
        puts $f [list set showneartags $showneartags]
        puts $f [list set showlocalchanges $showlocalchanges]
        puts $f [list set datetimeformat $datetimeformat]
@@ -1968,6 +2358,7 @@ proc savestuff {w} {
        puts $f [list set diffcolors $diffcolors]
        puts $f [list set diffcontext $diffcontext]
        puts $f [list set selectbgcolor $selectbgcolor]
        puts $f [list set diffcolors $diffcolors]
        puts $f [list set diffcontext $diffcontext]
        puts $f [list set selectbgcolor $selectbgcolor]
+       puts $f [list set extdifftool $extdifftool]
 
        puts $f "set geometry(main) [wm geometry .]"
        puts $f "set geometry(topwidth) [winfo width .tf]"
 
        puts $f "set geometry(main) [wm geometry .]"
        puts $f "set geometry(topwidth) [winfo width .tf]"
@@ -1980,7 +2371,7 @@ proc savestuff {w} {
        puts -nonewline $f "set permviews {"
        for {set v 0} {$v < $nextviewnum} {incr v} {
            if {$viewperm($v)} {
        puts -nonewline $f "set permviews {"
        for {set v 0} {$v < $nextviewnum} {incr v} {
            if {$viewperm($v)} {
-               puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
+               puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
            }
        }
        puts $f "}"
            }
        }
        puts $f "}"
@@ -2068,7 +2459,7 @@ proc about {} {
     message $w.m -text [mc "
 Gitk - a commit viewer for git
 
     message $w.m -text [mc "
 Gitk - a commit viewer for git
 
-Copyright © 2005-2006 Paul Mackerras
+Copyright © 2005-2008 Paul Mackerras
 
 Use and redistribute under the terms of the GNU General Public License"] \
            -justify center -aspect 400 -border 2 -bg white -relief groove
 
 Use and redistribute under the terms of the GNU General Public License"] \
            -justify center -aspect 400 -border 2 -bg white -relief groove
@@ -2315,7 +2706,7 @@ proc treeopendir {w dir} {
            $w insert e:$ix $e [highlight_tag $de]
        }
     }
            $w insert e:$ix $e [highlight_tag $de]
        }
     }
-    $w mark gravity e:$ix left
+    $w mark gravity e:$ix right
     $w conf -state disabled
     set treediropen($dir) 1
     set top [lindex [split [$w index @0,0] .] 0]
     $w conf -state disabled
     set treediropen($dir) 1
     set top [lindex [split [$w index @0,0] .] 0]
@@ -2518,6 +2909,12 @@ proc pop_flist_menu {w X Y x y} {
        set e [lindex $treediffs($diffids) [expr {$l-2}]]
     }
     set flist_menu_file $e
        set e [lindex $treediffs($diffids) [expr {$l-2}]]
     }
     set flist_menu_file $e
+    set xdiffstate "normal"
+    if {$cmitmode eq "tree"} {
+       set xdiffstate "disabled"
+    }
+    # Disable "External diff" item in tree mode
+    $flist_menu entryconf 2 -state $xdiffstate
     tk_popup $flist_menu $X $Y
 }
 
     tk_popup $flist_menu $X $Y
 }
 
@@ -2533,6 +2930,134 @@ proc flist_hl {only} {
     set gdttype [mc "touching paths:"]
 }
 
     set gdttype [mc "touching paths:"]
 }
 
+proc save_file_from_commit {filename output what} {
+    global nullfile
+
+    if {[catch {exec git show $filename -- > $output} err]} {
+       if {[string match "fatal: bad revision *" $err]} {
+           return $nullfile
+       }
+       error_popup "[mc "Error getting \"%s\" from %s:" $filename $what] $err"
+       return {}
+    }
+    return $output
+}
+
+proc external_diff_get_one_file {diffid filename diffdir} {
+    global nullid nullid2 nullfile
+    global gitdir
+
+    if {$diffid == $nullid} {
+        set difffile [file join [file dirname $gitdir] $filename]
+       if {[file exists $difffile]} {
+           return $difffile
+       }
+       return $nullfile
+    }
+    if {$diffid == $nullid2} {
+        set difffile [file join $diffdir "\[index\] [file tail $filename]"]
+        return [save_file_from_commit :$filename $difffile index]
+    }
+    set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
+    return [save_file_from_commit $diffid:$filename $difffile \
+              "revision $diffid"]
+}
+
+proc external_diff {} {
+    global gitktmpdir nullid nullid2
+    global flist_menu_file
+    global diffids
+    global diffnum
+    global gitdir extdifftool
+
+    if {[llength $diffids] == 1} {
+        # no reference commit given
+        set diffidto [lindex $diffids 0]
+        if {$diffidto eq $nullid} {
+            # diffing working copy with index
+            set diffidfrom $nullid2
+        } elseif {$diffidto eq $nullid2} {
+            # diffing index with HEAD
+            set diffidfrom "HEAD"
+        } else {
+            # use first parent commit
+            global parentlist selectedline
+            set diffidfrom [lindex $parentlist $selectedline 0]
+        }
+    } else {
+        set diffidfrom [lindex $diffids 0]
+        set diffidto [lindex $diffids 1]
+    }
+
+    # make sure that several diffs wont collide
+    if {![info exists gitktmpdir]} {
+       set gitktmpdir [file join [file dirname $gitdir] \
+                           [format ".gitk-tmp.%s" [pid]]]
+       if {[catch {file mkdir $gitktmpdir} err]} {
+           error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
+           unset gitktmpdir
+           return
+       }
+       set diffnum 0
+    }
+    incr diffnum
+    set diffdir [file join $gitktmpdir $diffnum]
+    if {[catch {file mkdir $diffdir} err]} {
+       error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err"
+       return
+    }
+
+    # gather files to diff
+    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
+    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
+
+    if {$difffromfile ne {} && $difftofile ne {}} {
+        set cmd [concat | [shellsplit $extdifftool] \
+                    [list $difffromfile $difftofile]]
+        if {[catch {set fl [open $cmd r]} err]} {
+            file delete -force $diffdir
+            error_popup "$extdifftool: [mc "command failed:"] $err"
+        } else {
+            fconfigure $fl -blocking 0
+            filerun $fl [list delete_at_eof $fl $diffdir]
+        }
+    }
+}
+
+proc external_blame {parent_idx} {
+    global flist_menu_file
+    global nullid nullid2
+    global parentlist selectedline currentid
+
+    if {$parent_idx > 0} {
+       set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]]
+    } else {
+       set base_commit $currentid
+    }
+
+    if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} {
+       error_popup [mc "No such commit"]
+       return
+    }
+
+    if {[catch {exec git gui blame $base_commit $flist_menu_file &} err]} {
+       error_popup "[mc "git gui blame: command failed:"] $err"
+    }
+}
+
+# delete $dir when we see eof on $f (presumably because the child has exited)
+proc delete_at_eof {f dir} {
+    while {[gets $f line] >= 0} {}
+    if {[eof $f]} {
+       if {[catch {close $f} err]} {
+           error_popup "[mc "External diff viewer failed:"] $err"
+       }
+       file delete -force $dir
+       return 0
+    }
+    return 1
+}
+
 # Functions for adding and removing shell-type quoting
 
 proc shellquote {str} {
 # Functions for adding and removing shell-type quoting
 
 proc shellquote {str} {
@@ -2631,7 +3156,7 @@ proc shellsplit {str} {
 
 proc newview {ishighlight} {
     global nextviewnum newviewname newviewperm newishighlight
 
 proc newview {ishighlight} {
     global nextviewnum newviewname newviewperm newishighlight
-    global newviewargs revtreeargs
+    global newviewargs revtreeargs viewargscmd newviewargscmd curview
 
     set newishighlight $ishighlight
     set top .gitkview
 
     set newishighlight $ishighlight
     set top .gitkview
@@ -2639,16 +3164,17 @@ proc newview {ishighlight} {
        raise $top
        return
     }
        raise $top
        return
     }
-    set newviewname($nextviewnum) "View $nextviewnum"
+    set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
     set newviewperm($nextviewnum) 0
     set newviewargs($nextviewnum) [shellarglist $revtreeargs]
     set newviewperm($nextviewnum) 0
     set newviewargs($nextviewnum) [shellarglist $revtreeargs]
+    set newviewargscmd($nextviewnum) $viewargscmd($curview)
     vieweditor $top $nextviewnum [mc "Gitk view definition"]
 }
 
 proc editview {} {
     global curview
     global viewname viewperm newviewname newviewperm
     vieweditor $top $nextviewnum [mc "Gitk view definition"]
 }
 
 proc editview {} {
     global curview
     global viewname viewperm newviewname newviewperm
-    global viewargs newviewargs
+    global viewargs newviewargs viewargscmd newviewargscmd
 
     set top .gitkvedit-$curview
     if {[winfo exists $top]} {
 
     set top .gitkvedit-$curview
     if {[winfo exists $top]} {
@@ -2658,6 +3184,7 @@ proc editview {} {
     set newviewname($curview) $viewname($curview)
     set newviewperm($curview) $viewperm($curview)
     set newviewargs($curview) [shellarglist $viewargs($curview)]
     set newviewname($curview) $viewname($curview)
     set newviewperm($curview) $viewperm($curview)
     set newviewargs($curview) [shellarglist $viewargs($curview)]
+    set newviewargscmd($curview) $viewargscmd($curview)
     vieweditor $top $curview "Gitk: edit view $viewname($curview)"
 }
 
     vieweditor $top $curview "Gitk: edit view $viewname($curview)"
 }
 
@@ -2673,11 +3200,19 @@ proc vieweditor {top n title} {
        -variable newviewperm($n)
     grid $top.perm - -pady 5 -sticky w
     message $top.al -aspect 1000 \
        -variable newviewperm($n)
     grid $top.perm - -pady 5 -sticky w
     message $top.al -aspect 1000 \
-       -text [mc "Commits to include (arguments to git rev-list):"]
+       -text [mc "Commits to include (arguments to git log):"]
     grid $top.al - -sticky w -pady 5
     entry $top.args -width 50 -textvariable newviewargs($n) \
        -background $bgcolor
     grid $top.args - -sticky ew -padx 5
     grid $top.al - -sticky w -pady 5
     entry $top.args -width 50 -textvariable newviewargs($n) \
        -background $bgcolor
     grid $top.args - -sticky ew -padx 5
+
+    message $top.ac -aspect 1000 \
+       -text [mc "Command to generate more commits to include:"]
+    grid $top.ac - -sticky w -pady 5
+    entry $top.argscmd -width 50 -textvariable newviewargscmd($n) \
+       -background white
+    grid $top.argscmd - -sticky ew -padx 5
+
     message $top.l -aspect 1000 \
        -text [mc "Enter files and directories to include, one per line:"]
     grid $top.l - -sticky w
     message $top.l -aspect 1000 \
        -text [mc "Enter files and directories to include, one per line:"]
     grid $top.l - -sticky w
@@ -2721,7 +3256,7 @@ proc allviewmenus {n op args} {
 proc newviewok {top n} {
     global nextviewnum newviewperm newviewname newishighlight
     global viewname viewfiles viewperm selectedview curview
 proc newviewok {top n} {
     global nextviewnum newviewperm newviewname newishighlight
     global viewname viewfiles viewperm selectedview curview
-    global viewargs newviewargs viewhlmenu
+    global viewargs newviewargs viewargscmd newviewargscmd viewhlmenu
 
     if {[catch {
        set newargs [shellsplit $newviewargs($n)]
 
     if {[catch {
        set newargs [shellsplit $newviewargs($n)]
@@ -2745,6 +3280,7 @@ proc newviewok {top n} {
        set viewperm($n) $newviewperm($n)
        set viewfiles($n) $files
        set viewargs($n) $newargs
        set viewperm($n) $newviewperm($n)
        set viewfiles($n) $files
        set viewargs($n) $newargs
+       set viewargscmd($n) $newviewargscmd($n)
        addviewmenu $n
        if {!$newishighlight} {
            run showview $n
        addviewmenu $n
        if {!$newishighlight} {
            run showview $n
@@ -2761,9 +3297,11 @@ proc newviewok {top n} {
            # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
                # entryconf [list -label $viewname($n) -value $viewname($n)]
        }
            # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
                # entryconf [list -label $viewname($n) -value $viewname($n)]
        }
-       if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
+       if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
+               $newviewargscmd($n) ne $viewargscmd($n)} {
            set viewfiles($n) $files
            set viewargs($n) $newargs
            set viewfiles($n) $files
            set viewargs($n) $newargs
+           set viewargscmd($n) $newviewargscmd($n)
            if {$curview == $n} {
                run reloadcommits
            }
            if {$curview == $n} {
                run reloadcommits
            }
@@ -2795,7 +3333,7 @@ proc addviewmenu {n} {
 }
 
 proc showview {n} {
 }
 
 proc showview {n} {
-    global curview viewfiles cached_commitrow ordertok
+    global curview cached_commitrow ordertok
     global displayorder parentlist rowidlist rowisopt rowfinal
     global colormap rowtextx nextcolor canvxmax
     global numcommits viewcomplete
     global displayorder parentlist rowidlist rowisopt rowfinal
     global colormap rowtextx nextcolor canvxmax
     global numcommits viewcomplete
@@ -2813,7 +3351,7 @@ proc showview {n} {
     set ytop [expr {[lindex $span 0] * $ymax}]
     set ybot [expr {[lindex $span 1] * $ymax}]
     set yscreen [expr {($ybot - $ytop) / 2}]
     set ytop [expr {[lindex $span 0] * $ymax}]
     set ybot [expr {[lindex $span 1] * $ymax}]
     set yscreen [expr {($ybot - $ytop) / 2}]
-    if {[info exists selectedline]} {
+    if {$selectedline ne {}} {
        set selid $currentid
        set y [yc $selectedline]
        if {$ytop < $y && $y < $ybot} {
        set selid $currentid
        set y [yc $selectedline]
        if {$ytop < $y && $y < $ybot} {
@@ -2842,10 +3380,7 @@ proc showview {n} {
 
     run refill_reflist
     if {![info exists viewcomplete($n)]} {
 
     run refill_reflist
     if {![info exists viewcomplete($n)]} {
-       if {$selid ne {}} {
-           set pending_select $selid
-       }
-       getcommits
+       getcommits $selid
        return
     }
 
        return
     }
 
@@ -2879,18 +3414,18 @@ proc showview {n} {
     drawvisible
     if {$row ne {}} {
        selectline $row 0
     drawvisible
     if {$row ne {}} {
        selectline $row 0
-    } elseif {$mainheadid ne {} && [commitinview $mainheadid $curview]} {
-       selectline [rowofcommit $mainheadid] 1
     } elseif {!$viewcomplete($n)} {
     } elseif {!$viewcomplete($n)} {
-       if {$selid ne {}} {
-           set pending_select $selid
-       } else {
-           set pending_select $mainheadid
-       }
+       reset_pending_select $selid
     } else {
     } else {
-       set row [first_real_row]
-       if {$row < $numcommits} {
-           selectline $row 0
+       reset_pending_select {}
+
+       if {[commitinview $pending_select $curview]} {
+           selectline [rowofcommit $pending_select] 1
+       } else {
+           set row [first_real_row]
+           if {$row < $numcommits} {
+               selectline $row 0
+           }
        }
     }
     if {!$viewcomplete($n)} {
        }
     }
     if {!$viewcomplete($n)} {
@@ -2927,7 +3462,7 @@ proc bolden {row font} {
 
     lappend boldrows $row
     $canv itemconf $linehtag($row) -font $font
 
     lappend boldrows $row
     $canv itemconf $linehtag($row) -font $font
-    if {[info exists selectedline] && $row == $selectedline} {
+    if {$row == $selectedline} {
        $canv delete secsel
        set t [eval $canv create rect [$canv bbox $linehtag($row)] \
                   -outline {{}} -tags secsel \
        $canv delete secsel
        set t [eval $canv create rect [$canv bbox $linehtag($row)] \
                   -outline {{}} -tags secsel \
@@ -2941,7 +3476,7 @@ proc bolden_name {row font} {
 
     lappend boldnamerows $row
     $canv2 itemconf $linentag($row) -font $font
 
     lappend boldnamerows $row
     $canv2 itemconf $linentag($row) -font $font
-    if {[info exists selectedline] && $row == $selectedline} {
+    if {$row == $selectedline} {
        $canv2 delete secsel
        set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
                   -outline {{}} -tags secsel \
        $canv2 delete secsel
        set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
                   -outline {{}} -tags secsel \
@@ -3009,6 +3544,7 @@ proc vhighlightmore {} {
        }
     }
     set vhl_done $max
        }
     }
     set vhl_done $max
+    return 0
 }
 
 proc askvhighlight {row id} {
 }
 
 proc askvhighlight {row id} {
@@ -3369,7 +3905,7 @@ proc askrelhighlight {row id} {
     global descendent highlight_related iddrawn rhighlights
     global selectedline ancestor
 
     global descendent highlight_related iddrawn rhighlights
     global selectedline ancestor
 
-    if {![info exists selectedline]} return
+    if {$selectedline eq {}} return
     set isbold 0
     if {$highlight_related eq [mc "Descendant"] ||
        $highlight_related eq [mc "Not descendant"]} {
     set isbold 0
     if {$highlight_related eq [mc "Descendant"] ||
        $highlight_related eq [mc "Not descendant"]} {
@@ -3507,15 +4043,19 @@ proc initlayout {} {
     set canvxmax [$canv cget -width]
     catch {unset colormap}
     catch {unset rowtextx}
     set canvxmax [$canv cget -width]
     catch {unset colormap}
     catch {unset rowtextx}
+    setcanvscroll
 }
 
 proc setcanvscroll {} {
     global canv canv2 canv3 numcommits linespc canvxmax canvy0
 }
 
 proc setcanvscroll {} {
     global canv canv2 canv3 numcommits linespc canvxmax canvy0
+    global lastscrollset lastscrollrows
 
     set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
     $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
     $canv2 conf -scrollregion [list 0 0 0 $ymax]
     $canv3 conf -scrollregion [list 0 0 0 $ymax]
 
     set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
     $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
     $canv2 conf -scrollregion [list 0 0 0 $ymax]
     $canv3 conf -scrollregion [list 0 0 0 $ymax]
+    set lastscrollset [clock clicks -milliseconds]
+    set lastscrollrows $numcommits
 }
 
 proc visiblerows {} {
 }
 
 proc visiblerows {} {
@@ -3539,39 +4079,25 @@ proc visiblerows {} {
 
 proc layoutmore {} {
     global commitidx viewcomplete curview
 
 proc layoutmore {} {
     global commitidx viewcomplete curview
-    global numcommits pending_select selectedline curview
-    global lastscrollset commitinterest
-
-    set canshow $commitidx($curview)
-    if {$canshow <= $numcommits && !$viewcomplete($curview)} return
-    if {$numcommits == 0} {
-       allcanvs delete all
-    }
-    set r0 $numcommits
-    set prev $numcommits
-    set numcommits $canshow
-    set t [clock clicks -milliseconds]
-    if {$prev < 100 || $viewcomplete($curview) || $t - $lastscrollset > 500} {
-       set lastscrollset $t
+    global numcommits pending_select curview
+    global lastscrollset lastscrollrows commitinterest
+
+    if {$lastscrollrows < 100 || $viewcomplete($curview) ||
+       [clock clicks -milliseconds] - $lastscrollset > 500} {
        setcanvscroll
     }
        setcanvscroll
     }
-    set rows [visiblerows]
-    set r1 [lindex $rows 1]
-    if {$r1 >= $canshow} {
-       set r1 [expr {$canshow - 1}]
-    }
-    if {$r0 <= $r1} {
-       drawcommits $r0 $r1
-    }
     if {[info exists pending_select] &&
        [commitinview $pending_select $curview]} {
     if {[info exists pending_select] &&
        [commitinview $pending_select $curview]} {
+       update
        selectline [rowofcommit $pending_select] 1
     }
        selectline [rowofcommit $pending_select] 1
     }
+    drawvisible
 }
 
 proc doshowlocalchanges {} {
     global curview mainheadid
 
 }
 
 proc doshowlocalchanges {} {
     global curview mainheadid
 
+    if {$mainheadid eq {}} return
     if {[commitinview $mainheadid $curview]} {
        dodiffindex
     } else {
     if {[commitinview $mainheadid $curview]} {
        dodiffindex
     } else {
@@ -3594,15 +4120,17 @@ proc dohidelocalchanges {} {
 # spawn off a process to do git diff-index --cached HEAD
 proc dodiffindex {} {
     global lserial showlocalchanges
 # spawn off a process to do git diff-index --cached HEAD
 proc dodiffindex {} {
     global lserial showlocalchanges
+    global isworktree
 
 
-    if {!$showlocalchanges} return
+    if {!$showlocalchanges || !$isworktree} return
     incr lserial
     set fd [open "|git diff-index --cached HEAD" r]
     fconfigure $fd -blocking 0
     incr lserial
     set fd [open "|git diff-index --cached HEAD" r]
     fconfigure $fd -blocking 0
-    filerun $fd [list readdiffindex $fd $lserial]
+    set i [reg_instance $fd]
+    filerun $fd [list readdiffindex $fd $lserial $i]
 }
 
 }
 
-proc readdiffindex {fd serial} {
+proc readdiffindex {fd serial inst} {
     global mainheadid nullid nullid2 curview commitinfo commitdata lserial
 
     set isdiff 1
     global mainheadid nullid nullid2 curview commitinfo commitdata lserial
 
     set isdiff 1
@@ -3613,7 +4141,7 @@ proc readdiffindex {fd serial} {
        set isdiff 0
     }
     # we only need to see one line and we don't really care what it says...
        set isdiff 0
     }
     # we only need to see one line and we don't really care what it says...
-    close $fd
+    stop_instance $inst
 
     if {$serial != $lserial} {
        return 0
 
     if {$serial != $lserial} {
        return 0
@@ -3622,7 +4150,8 @@ proc readdiffindex {fd serial} {
     # now see if there are any local changes not checked in to the index
     set fd [open "|git diff-files" r]
     fconfigure $fd -blocking 0
     # now see if there are any local changes not checked in to the index
     set fd [open "|git diff-files" r]
     fconfigure $fd -blocking 0
-    filerun $fd [list readdifffiles $fd $serial]
+    set i [reg_instance $fd]
+    filerun $fd [list readdifffiles $fd $serial $i]
 
     if {$isdiff && ![commitinview $nullid2 $curview]} {
        # add the line for the changes in the index to the graph
 
     if {$isdiff && ![commitinview $nullid2 $curview]} {
        # add the line for the changes in the index to the graph
@@ -3639,7 +4168,7 @@ proc readdiffindex {fd serial} {
     return 0
 }
 
     return 0
 }
 
-proc readdifffiles {fd serial} {
+proc readdifffiles {fd serial inst} {
     global mainheadid nullid nullid2 curview
     global commitinfo commitdata lserial
 
     global mainheadid nullid nullid2 curview
     global commitinfo commitdata lserial
 
@@ -3651,7 +4180,7 @@ proc readdifffiles {fd serial} {
        set isdiff 0
     }
     # we only need to see one line and we don't really care what it says...
        set isdiff 0
     }
     # we only need to see one line and we don't really care what it says...
-    close $fd
+    stop_instance $inst
 
     if {$serial != $lserial} {
        return 0
 
     if {$serial != $lserial} {
        return 0
@@ -4390,7 +4919,8 @@ proc drawcmittext {id row col} {
     global cmitlisted commitinfo rowidlist parentlist
     global rowtextx idpos idtags idheads idotherrefs
     global linehtag linentag linedtag selectedline
     global cmitlisted commitinfo rowidlist parentlist
     global rowtextx idpos idtags idheads idotherrefs
     global linehtag linentag linedtag selectedline
-    global canvxmax boldrows boldnamerows fgcolor nullid nullid2
+    global canvxmax boldrows boldnamerows fgcolor
+    global mainheadid nullid nullid2 circleitem circlecolors ctxbut
 
     # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
     set listed $cmitlisted($curview,$id)
 
     # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
     set listed $cmitlisted($curview,$id)
@@ -4398,8 +4928,10 @@ proc drawcmittext {id row col} {
        set ofill red
     } elseif {$id eq $nullid2} {
        set ofill green
        set ofill red
     } elseif {$id eq $nullid2} {
        set ofill green
+    } elseif {$id eq $mainheadid} {
+       set ofill yellow
     } else {
     } else {
-       set ofill [expr {$listed != 0 ? $listed == 2 ? "gray" : "blue" : "white"}]
+       set ofill [lindex $circlecolors $listed]
     }
     set x [xc $row $col]
     set y [yc $row]
     }
     set x [xc $row $col]
     set y [yc $row]
@@ -4423,6 +4955,7 @@ proc drawcmittext {id row col} {
                   [expr {$x - $orad}] [expr {$y + $orad - 1}] \
                   -fill $ofill -outline $fgcolor -width 1 -tags circle]
     }
                   [expr {$x - $orad}] [expr {$y + $orad - 1}] \
                   -fill $ofill -outline $fgcolor -width 1 -tags circle]
     }
+    set circleitem($row) $t
     $canv raise $t
     $canv bind $t <1> {selcanvline {} %x %y}
     set rmx [llength [lindex $rowidlist $row]]
     $canv raise $t
     $canv bind $t <1> {selcanvline {} %x %y}
     set rmx [llength [lindex $rowidlist $row]]
@@ -4460,12 +4993,12 @@ proc drawcmittext {id row col} {
     }
     set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
                            -text $headline -font $font -tags text]
     }
     set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
                            -text $headline -font $font -tags text]
-    $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
+    $canv bind $linehtag($row) $ctxbut "rowmenu %X %Y $id"
     set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
                            -text $name -font $nfont -tags text]
     set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
                            -text $date -font mainfont -tags text]
     set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
                            -text $name -font $nfont -tags text]
     set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
                            -text $date -font mainfont -tags text]
-    if {[info exists selectedline] && $selectedline == $row} {
+    if {$selectedline == $row} {
        make_secsel $row
     }
     set xr [expr {$xt + [font measure $font $headline]}]
        make_secsel $row
     }
     set xr [expr {$xt + [font measure $font $headline]}]
@@ -4656,26 +5189,34 @@ proc drawvisible {} {
     if {$endrow >= $vrowmod($curview)} {
        update_arcrows $curview
     }
     if {$endrow >= $vrowmod($curview)} {
        update_arcrows $curview
     }
-    if {[info exists selectedline] &&
+    if {$selectedline ne {} &&
        $row <= $selectedline && $selectedline <= $endrow} {
        set targetrow $selectedline
        $row <= $selectedline && $selectedline <= $endrow} {
        set targetrow $selectedline
-    } else {
+    } elseif {[info exists targetid]} {
        set targetrow [expr {int(($row + $endrow) / 2)}]
     }
        set targetrow [expr {int(($row + $endrow) / 2)}]
     }
-    if {$targetrow >= $numcommits} {
-       set targetrow [expr {$numcommits - 1}]
+    if {[info exists targetrow]} {
+       if {$targetrow >= $numcommits} {
+           set targetrow [expr {$numcommits - 1}]
+       }
+       set targetid [commitonrow $targetrow]
     }
     }
-    set targetid [commitonrow $targetrow]
     drawcommits $row $endrow
 }
 
 proc clear_display {} {
     global iddrawn linesegs need_redisplay nrows_drawn
     global vhighlights fhighlights nhighlights rhighlights
     drawcommits $row $endrow
 }
 
 proc clear_display {} {
     global iddrawn linesegs need_redisplay nrows_drawn
     global vhighlights fhighlights nhighlights rhighlights
+    global linehtag linentag linedtag boldrows boldnamerows
 
     allcanvs delete all
     catch {unset iddrawn}
     catch {unset linesegs}
 
     allcanvs delete all
     catch {unset iddrawn}
     catch {unset linesegs}
+    catch {unset linehtag}
+    catch {unset linentag}
+    catch {unset linedtag}
+    set boldrows {}
+    set boldnamerows {}
     catch {unset vhighlights}
     catch {unset fhighlights}
     catch {unset nhighlights}
     catch {unset vhighlights}
     catch {unset fhighlights}
     catch {unset nhighlights}
@@ -4794,7 +5335,7 @@ proc bindline {t id} {
 proc drawtags {id x xt y1} {
     global idtags idheads idotherrefs mainhead
     global linespc lthickness
 proc drawtags {id x xt y1} {
     global idtags idheads idotherrefs mainhead
     global linespc lthickness
-    global canv rowtextx curview fgcolor bgcolor
+    global canv rowtextx curview fgcolor bgcolor ctxbut
 
     set marks {}
     set ntags 0
 
     set marks {}
     set ntags 0
@@ -4872,7 +5413,7 @@ proc drawtags {id x xt y1} {
        if {$ntags >= 0} {
            $canv bind $t <1> [list showtag $tag 1]
        } elseif {$nheads >= 0} {
        if {$ntags >= 0} {
            $canv bind $t <1> [list showtag $tag 1]
        } elseif {$nheads >= 0} {
-           $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
+           $canv bind $t $ctxbut [list headmenu %X %Y $id $tag]
        }
     }
     return $xt
        }
     }
     return $xt
@@ -4970,7 +5511,7 @@ proc dofind {{dirn 1} {wrap 1}} {
     }
     focus .
     if {$findstring eq {} || $numcommits == 0} return
     }
     focus .
     if {$findstring eq {} || $numcommits == 0} return
-    if {![info exists selectedline]} {
+    if {$selectedline eq {}} {
        set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
     } else {
        set findstartline $selectedline
        set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
     } else {
        set findstartline $selectedline
@@ -5166,7 +5707,7 @@ proc markmatches {canv l str tag matches font row} {
                   [expr {$x0+$xlen+2}] $y1 \
                   -outline {} -tags [list match$l matches] -fill yellow]
        $canv lower $t
                   [expr {$x0+$xlen+2}] $y1 \
                   -outline {} -tags [list match$l matches] -fill yellow]
        $canv lower $t
-       if {[info exists selectedline] && $row == $selectedline} {
+       if {$row == $selectedline} {
            $canv raise $t secsel
        }
     }
            $canv raise $t secsel
        }
     }
@@ -5325,7 +5866,7 @@ proc appendrefs {pos ids var} {
 proc dispneartags {delay} {
     global selectedline currentid showneartags tagphase
 
 proc dispneartags {delay} {
     global selectedline currentid showneartags tagphase
 
-    if {![info exists selectedline] || !$showneartags} return
+    if {$selectedline eq {} || !$showneartags} return
     after cancel dispnexttag
     if {$delay} {
        after 200 dispnexttag
     after cancel dispnexttag
     if {$delay} {
        after 200 dispnexttag
@@ -5339,7 +5880,7 @@ proc dispneartags {delay} {
 proc dispnexttag {} {
     global selectedline currentid showneartags tagphase ctext
 
 proc dispnexttag {} {
     global selectedline currentid showneartags tagphase ctext
 
-    if {![info exists selectedline] || !$showneartags} return
+    if {$selectedline eq {} || !$showneartags} return
     switch -- $tagphase {
        0 {
            set dtags [desctags $currentid]
     switch -- $tagphase {
        0 {
            set dtags [desctags $currentid]
@@ -5396,7 +5937,8 @@ proc selectline {l isnew} {
     global commentend idtags linknum
     global mergemax numcommits pending_select
     global cmitmode showneartags allcommits
     global commentend idtags linknum
     global mergemax numcommits pending_select
     global cmitmode showneartags allcommits
-    global targetrow targetid
+    global targetrow targetid lastscrollrows
+    global autoselect
 
     catch {unset pending_select}
     $canv delete hover
 
     catch {unset pending_select}
     $canv delete hover
@@ -5404,6 +5946,15 @@ proc selectline {l isnew} {
     unsel_reflist
     stopfinding
     if {$l < 0 || $l >= $numcommits} return
     unsel_reflist
     stopfinding
     if {$l < 0 || $l >= $numcommits} return
+    set id [commitonrow $l]
+    set targetid $id
+    set targetrow $l
+    set selectedline $l
+    set currentid $id
+    if {$lastscrollrows < $numcommits} {
+       setcanvscroll
+    }
+
     set y [expr {$canvy0 + $l * $linespc}]
     set ymax [lindex [$canv cget -scrollregion] 3]
     set ytop [expr {$y - $linespc - 1}]
     set y [expr {$canvy0 + $l * $linespc}]
     set ymax [lindex [$canv cget -scrollregion] 3]
     set ytop [expr {$y - $linespc - 1}]
@@ -5442,24 +5993,24 @@ proc selectline {l isnew} {
 
     make_secsel $l
 
 
     make_secsel $l
 
-    set id [commitonrow $l]
     if {$isnew} {
        addtohistory [list selbyid $id]
     }
 
     if {$isnew} {
        addtohistory [list selbyid $id]
     }
 
-    set selectedline $l
-    set currentid $id
-    set targetid $id
-    set targetrow $l
     $sha1entry delete 0 end
     $sha1entry insert 0 $id
     $sha1entry delete 0 end
     $sha1entry insert 0 $id
-    $sha1entry selection from 0
-    $sha1entry selection to end
+    if {$autoselect} {
+       $sha1entry selection from 0
+       $sha1entry selection to end
+    }
     rhighlight_sel $id
 
     $ctext conf -state normal
     clear_ctext
     set linknum 0
     rhighlight_sel $id
 
     $ctext conf -state normal
     clear_ctext
     set linknum 0
+    if {![info exists commitinfo($id)]} {
+       getcommit $id
+    }
     set info $commitinfo($id)
     set date [formatdate [lindex $info 2]]
     $ctext insert end "[mc "Author"]: [lindex $info 1]  $date\n"
     set info $commitinfo($id)
     set date [formatdate [lindex $info 2]]
     $ctext insert end "[mc "Author"]: [lindex $info 1]  $date\n"
@@ -5551,7 +6102,7 @@ proc sellastline {} {
 proc selnextline {dir} {
     global selectedline
     focus .
 proc selnextline {dir} {
     global selectedline
     focus .
-    if {![info exists selectedline]} return
+    if {$selectedline eq {}} return
     set l [expr {$selectedline + $dir}]
     unmarkmatches
     selectline $l 1
     set l [expr {$selectedline + $dir}]
     unmarkmatches
     selectline $l 1
@@ -5566,7 +6117,7 @@ proc selnextpage {dir} {
     }
     allcanvs yview scroll [expr {$dir * $lpp}] units
     drawvisible
     }
     allcanvs yview scroll [expr {$dir * $lpp}] units
     drawvisible
-    if {![info exists selectedline]} return
+    if {$selectedline eq {}} return
     set l [expr {$selectedline + $dir * $lpp}]
     if {$l < 0} {
        set l 0
     set l [expr {$selectedline + $dir * $lpp}]
     if {$l < 0} {
        set l 0
@@ -5580,7 +6131,7 @@ proc selnextpage {dir} {
 proc unselectline {} {
     global selectedline currentid
 
 proc unselectline {} {
     global selectedline currentid
 
-    catch {unset selectedline}
+    set selectedline {}
     catch {unset currentid}
     allcanvs delete secsel
     rhighlight_none
     catch {unset currentid}
     allcanvs delete secsel
     rhighlight_none
@@ -5589,7 +6140,7 @@ proc unselectline {} {
 proc reselectline {} {
     global selectedline
 
 proc reselectline {} {
     global selectedline
 
-    if {[info exists selectedline]} {
+    if {$selectedline ne {}} {
        selectline $selectedline 0
     }
 }
        selectline $selectedline 0
     }
 }
@@ -5694,11 +6245,12 @@ proc gettreeline {gtf id} {
        if {$diffids eq $nullid} {
            set fname $line
        } else {
        if {$diffids eq $nullid} {
            set fname $line
        } else {
-           if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
            set i [string first "\t" $line]
            if {$i < 0} continue
            set i [string first "\t" $line]
            if {$i < 0} continue
-           set sha1 [lindex $line 2]
            set fname [string range $line [expr {$i+1}] end]
            set fname [string range $line [expr {$i+1}] end]
+           set line [string range $line 0 [expr {$i-1}]]
+           if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
+           set sha1 [lindex $line 2]
            if {[string index $fname 0] eq "\""} {
                set fname [lindex $fname 0]
            }
            if {[string index $fname 0] eq "\""} {
                set fname [lindex $fname 0]
            }
@@ -5782,14 +6334,14 @@ proc mergediff {id} {
     global diffids
     global parents
     global diffcontext
     global diffids
     global parents
     global diffcontext
-    global limitdiffs viewfiles curview
+    global limitdiffs vfilelimit curview
 
     set diffmergeid $id
     set diffids $id
     # this doesn't seem to actually affect anything...
     set cmd [concat | git diff-tree --no-commit-id --cc -U$diffcontext $id]
 
     set diffmergeid $id
     set diffids $id
     # this doesn't seem to actually affect anything...
     set cmd [concat | git diff-tree --no-commit-id --cc -U$diffcontext $id]
-    if {$limitdiffs && $viewfiles($curview) ne {}} {
-       set cmd [concat $cmd -- $viewfiles($curview)]
+    if {$limitdiffs && $vfilelimit($curview) ne {}} {
+       set cmd [concat $cmd -- $vfilelimit($curview)]
     }
     if {[catch {set mdf [open $cmd r]} err]} {
        error_popup "[mc "Error getting merge diffs:"] $err"
     }
     if {[catch {set mdf [open $cmd r]} err]} {
        error_popup "[mc "Error getting merge diffs:"] $err"
@@ -5958,16 +6510,17 @@ proc diffcmd {ids flags} {
 proc gettreediffs {ids} {
     global treediff treepending
 
 proc gettreediffs {ids} {
     global treediff treepending
 
+    if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
+
     set treepending $ids
     set treediff {}
     set treepending $ids
     set treediff {}
-    if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
     fconfigure $gdtf -blocking 0
     filerun $gdtf [list gettreediffline $gdtf $ids]
 }
 
 proc gettreediffline {gdtf ids} {
     global treediff treediffs treepending diffids diffmergeid
     fconfigure $gdtf -blocking 0
     filerun $gdtf [list gettreediffline $gdtf $ids]
 }
 
 proc gettreediffline {gdtf ids} {
     global treediff treediffs treepending diffids diffmergeid
-    global cmitmode viewfiles curview limitdiffs
+    global cmitmode vfilelimit curview limitdiffs
 
     set nr 0
     while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
 
     set nr 0
     while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
@@ -5984,10 +6537,10 @@ proc gettreediffline {gdtf ids} {
        return [expr {$nr >= 1000? 2: 1}]
     }
     close $gdtf
        return [expr {$nr >= 1000? 2: 1}]
     }
     close $gdtf
-    if {$limitdiffs && $viewfiles($curview) ne {}} {
+    if {$limitdiffs && $vfilelimit($curview) ne {}} {
        set flist {}
        foreach f $treediff {
        set flist {}
        foreach f $treediff {
-           if {[path_filter $viewfiles($curview) $f]} {
+           if {[path_filter $vfilelimit($curview) $f]} {
                lappend flist $f
            }
        }
                lappend flist $f
            }
        }
@@ -6033,14 +6586,14 @@ proc getblobdiffs {ids} {
     global diffinhdr treediffs
     global diffcontext
     global ignorespace
     global diffinhdr treediffs
     global diffcontext
     global ignorespace
-    global limitdiffs viewfiles curview
+    global limitdiffs vfilelimit curview
 
     set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
     if {$ignorespace} {
        append cmd " -w"
     }
 
     set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
     if {$ignorespace} {
        append cmd " -w"
     }
-    if {$limitdiffs && $viewfiles($curview) ne {}} {
-       set cmd [concat $cmd -- $viewfiles($curview)]
+    if {$limitdiffs && $vfilelimit($curview) ne {}} {
+       set cmd [concat $cmd -- $vfilelimit($curview)]
     }
     if {[catch {set bdf [open $cmd r]} err]} {
        puts "error getting diffs: $err"
     }
     if {[catch {set bdf [open $cmd r]} err]} {
        puts "error getting diffs: $err"
@@ -6180,26 +6733,44 @@ proc changediffdisp {} {
     $ctext tag conf d1 -elide [lindex $diffelide 1]
 }
 
     $ctext tag conf d1 -elide [lindex $diffelide 1]
 }
 
+proc highlightfile {loc cline} {
+    global ctext cflist cflist_top
+
+    $ctext yview $loc
+    $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
+    $cflist tag add highlight $cline.0 "$cline.0 lineend"
+    $cflist see $cline.0
+    set cflist_top $cline
+}
+
 proc prevfile {} {
 proc prevfile {} {
-    global difffilestart ctext
-    set prev [lindex $difffilestart 0]
+    global difffilestart ctext cmitmode
+
+    if {$cmitmode eq "tree"} return
+    set prev 0.0
+    set prevline 1
     set here [$ctext index @0,0]
     foreach loc $difffilestart {
        if {[$ctext compare $loc >= $here]} {
     set here [$ctext index @0,0]
     foreach loc $difffilestart {
        if {[$ctext compare $loc >= $here]} {
-           $ctext yview $prev
+           highlightfile $prev $prevline
            return
        }
        set prev $loc
            return
        }
        set prev $loc
+       incr prevline
     }
     }
-    $ctext yview $prev
+    highlightfile $prev $prevline
 }
 
 proc nextfile {} {
 }
 
 proc nextfile {} {
-    global difffilestart ctext
+    global difffilestart ctext cmitmode
+
+    if {$cmitmode eq "tree"} return
     set here [$ctext index @0,0]
     set here [$ctext index @0,0]
+    set line 1
     foreach loc $difffilestart {
     foreach loc $difffilestart {
+       incr line
        if {[$ctext compare $loc > $here]} {
        if {[$ctext compare $loc > $here]} {
-           $ctext yview $loc
+           highlightfile $loc $line
            return
        }
     }
            return
        }
     }
@@ -6352,7 +6923,7 @@ proc searchmarkvisible {doall} {
 proc scrolltext {f0 f1} {
     global searchstring
 
 proc scrolltext {f0 f1} {
     global searchstring
 
-    .bleft.sb set $f0 $f1
+    .bleft.bottom.sb set $f0 $f1
     if {$searchstring ne {}} {
        searchmarkvisible 0
     }
     if {$searchstring ne {}} {
        searchmarkvisible 0
     }
@@ -6382,7 +6953,7 @@ proc redisplay {} {
     setcanvscroll
     allcanvs yview moveto [lindex $span 0]
     drawvisible
     setcanvscroll
     allcanvs yview moveto [lindex $span 0]
     drawvisible
-    if {[info exists selectedline]} {
+    if {$selectedline ne {}} {
        selectline $selectedline 0
        allcanvs yview moveto [lindex $span 0]
     }
        selectline $selectedline 0
        allcanvs yview moveto [lindex $span 0]
     }
@@ -6703,15 +7274,18 @@ proc rowmenu {x y id} {
 
     stopfinding
     set rowmenuid $id
 
     stopfinding
     set rowmenuid $id
-    if {![info exists selectedline]
-       || [rowofcommit $id] eq $selectedline} {
+    if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} {
        set state disabled
     } else {
        set state normal
     }
     if {$id ne $nullid && $id ne $nullid2} {
        set menu $rowctxmenu
        set state disabled
     } else {
        set state normal
     }
     if {$id ne $nullid && $id ne $nullid2} {
        set menu $rowctxmenu
-       $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
+       if {$mainhead ne {}} {
+           $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
+       } else {
+           $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
+       }
     } else {
        set menu $fakerowmenu
     }
     } else {
        set menu $fakerowmenu
     }
@@ -6724,7 +7298,7 @@ proc rowmenu {x y id} {
 proc diffvssel {dirn} {
     global rowmenuid selectedline
 
 proc diffvssel {dirn} {
     global rowmenuid selectedline
 
-    if {![info exists selectedline]} return
+    if {$selectedline eq {}} return
     if {$dirn} {
        set oldid [commitonrow $selectedline]
        set newid $rowmenuid
     if {$dirn} {
        set oldid [commitonrow $selectedline]
        set newid $rowmenuid
@@ -6908,12 +7482,18 @@ proc domktag {} {
 }
 
 proc redrawtags {id} {
 }
 
 proc redrawtags {id} {
-    global canv linehtag idpos currentid curview
-    global canvxmax iddrawn
+    global canv linehtag idpos currentid curview cmitlisted
+    global canvxmax iddrawn circleitem mainheadid circlecolors
 
     if {![commitinview $id $curview]} return
     if {![info exists iddrawn($id)]} return
     set row [rowofcommit $id]
 
     if {![commitinview $id $curview]} return
     if {![info exists iddrawn($id)]} return
     set row [rowofcommit $id]
+    if {$id eq $mainheadid} {
+       set ofill yellow
+    } else {
+       set ofill [lindex $circlecolors $cmitlisted($curview,$id)]
+    }
+    $canv itemconf $circleitem($row) -fill $ofill
     $canv delete tag.$id
     set xt [eval drawtags $id $idpos($id)]
     $canv coords $linehtag($row) $xt [lindex $idpos($id) 2]
     $canv delete tag.$id
     set xt [eval drawtags $id $idpos($id)]
     $canv coords $linehtag($row) $xt [lindex $idpos($id) 2]
@@ -7051,7 +7631,7 @@ proc mkbrgo {top} {
 }
 
 proc cherrypick {} {
 }
 
 proc cherrypick {} {
-    global rowmenuid curview viewincl
+    global rowmenuid curview
     global mainhead mainheadid
 
     set oldhead [exec git rev-parse HEAD]
     global mainhead mainheadid
 
     set oldhead [exec git rev-parse HEAD]
@@ -7083,14 +7663,8 @@ proc cherrypick {} {
        if {$mainhead ne {}} {
            movehead $newhead $mainhead
            movedhead $newhead $mainhead
        if {$mainhead ne {}} {
            movehead $newhead $mainhead
            movedhead $newhead $mainhead
-           set mainheadid $newhead
-       }
-       # remove oldhead from viewincl and add newhead
-       set i [lsearch -exact $viewincl($curview) $oldhead]
-       if {$i >= 0} {
-           set viewincl($curview) [lreplace $viewincl($curview) $i $i]
        }
        }
-       lappend viewincl($curview) $newhead
+       set mainheadid $newhead
        redrawtags $oldhead
        redrawtags $newhead
        selbyid $newhead
        redrawtags $oldhead
        redrawtags $newhead
        selbyid $newhead
@@ -7132,7 +7706,7 @@ proc resethead {} {
     tkwait window $w
     if {!$confirm_ok} return
     if {[catch {set fd [open \
     tkwait window $w
     if {!$confirm_ok} return
     if {[catch {set fd [open \
-           [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} {
+           [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
        error_popup $err
     } else {
        dohidelocalchanges
        error_popup $err
     } else {
        dohidelocalchanges
@@ -7190,29 +7764,48 @@ proc headmenu {x y id head} {
 }
 
 proc cobranch {} {
 }
 
 proc cobranch {} {
-    global headmenuid headmenuhead mainhead headids
+    global headmenuid headmenuhead headids
     global showlocalchanges mainheadid
 
     # check the tree is clean first??
     global showlocalchanges mainheadid
 
     # check the tree is clean first??
-    set oldmainhead $mainhead
     nowbusy checkout [mc "Checking out"]
     update
     dohidelocalchanges
     if {[catch {
     nowbusy checkout [mc "Checking out"]
     update
     dohidelocalchanges
     if {[catch {
-       exec git checkout -q $headmenuhead
+       set fd [open [list | git checkout $headmenuhead 2>@1] r]
     } err]} {
        notbusy checkout
        error_popup $err
     } err]} {
        notbusy checkout
        error_popup $err
+       if {$showlocalchanges} {
+           dodiffindex
+       }
     } else {
     } else {
-       notbusy checkout
-       set mainhead $headmenuhead
-       set mainheadid $headmenuid
-       if {[info exists headids($oldmainhead)]} {
-           redrawtags $headids($oldmainhead)
+       filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
+    }
+}
+
+proc readcheckoutstat {fd newhead newheadid} {
+    global mainhead mainheadid headids showlocalchanges progresscoords
+
+    if {[gets $fd line] >= 0} {
+       if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
+           set progresscoords [list 0 [expr {1.0 * $m / $n}]]
+           adjustprogress
        }
        }
-       redrawtags $headmenuid
-       selbyid $headmenuid
+       return 1
+    }
+    set progresscoords {0 0}
+    adjustprogress
+    notbusy checkout
+    if {[catch {close $fd} err]} {
+       error_popup $err
     }
     }
+    set oldmainid $mainheadid
+    set mainhead $newhead
+    set mainheadid $newheadid
+    redrawtags $oldmainid
+    redrawtags $newheadid
+    selbyid $newheadid
     if {$showlocalchanges} {
        dodiffindex
     }
     if {$showlocalchanges} {
        dodiffindex
     }
@@ -8510,12 +9103,14 @@ proc rereadrefs {} {
                        [array names idheads] [array names idotherrefs]]]
     foreach id $refids {
        set v [listrefs $id]
                        [array names idheads] [array names idotherrefs]]]
     foreach id $refids {
        set v [listrefs $id]
-       if {![info exists ref($id)] || $ref($id) != $v ||
-           ($id eq $oldmainhead && $id ne $mainheadid) ||
-           ($id eq $mainheadid && $id ne $oldmainhead)} {
+       if {![info exists ref($id)] || $ref($id) != $v} {
            redrawtags $id
        }
     }
            redrawtags $id
        }
     }
+    if {$oldmainhead ne $mainheadid} {
+       redrawtags $oldmainhead
+       redrawtags $mainheadid
+    }
     run refill_reflist
 }
 
     run refill_reflist
 }
 
@@ -8564,9 +9159,15 @@ proc showtag {tag isnew} {
 
 proc doquit {} {
     global stopped
 
 proc doquit {} {
     global stopped
+    global gitktmpdir
+
     set stopped 100
     savestuff .
     destroy .
     set stopped 100
     savestuff .
     destroy .
+
+    if {[info exists gitktmpdir]} {
+       catch {file delete -force $gitktmpdir}
+    }
 }
 
 proc mkfontdisp {font top which} {
 }
 
 proc mkfontdisp {font top which} {
@@ -8695,7 +9296,7 @@ proc doprefs {} {
     global maxwidth maxgraphpct
     global oldprefs prefstop showneartags showlocalchanges
     global bgcolor fgcolor ctext diffcolors selectbgcolor
     global maxwidth maxgraphpct
     global oldprefs prefstop showneartags showlocalchanges
     global bgcolor fgcolor ctext diffcolors selectbgcolor
-    global tabstop limitdiffs
+    global tabstop limitdiffs autoselect extdifftool
 
     set top .gitkprefs
     set prefstop $top
 
     set top .gitkprefs
     set prefstop $top
@@ -8725,6 +9326,11 @@ proc doprefs {} {
     checkbutton $top.showlocal.b -variable showlocalchanges
     pack $top.showlocal.b $top.showlocal.l -side left
     grid x $top.showlocal -sticky w
     checkbutton $top.showlocal.b -variable showlocalchanges
     pack $top.showlocal.b $top.showlocal.l -side left
     grid x $top.showlocal -sticky w
+    frame $top.autoselect
+    label $top.autoselect.l -text [mc "Auto-select SHA1"] -font optionfont
+    checkbutton $top.autoselect.b -variable autoselect
+    pack $top.autoselect.b $top.autoselect.l -side left
+    grid x $top.autoselect -sticky w
 
     label $top.ddisp -text [mc "Diff display options"]
     grid $top.ddisp - -sticky w -pady 10
 
     label $top.ddisp -text [mc "Diff display options"]
     grid $top.ddisp - -sticky w -pady 10
@@ -8742,15 +9348,24 @@ proc doprefs {} {
     pack $top.ldiff.b $top.ldiff.l -side left
     grid x $top.ldiff -sticky w
 
     pack $top.ldiff.b $top.ldiff.l -side left
     grid x $top.ldiff -sticky w
 
+    entry $top.extdifft -textvariable extdifftool
+    frame $top.extdifff
+    label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \
+       -padx 10
+    button $top.extdifff.b -text [mc "Choose..."] -font optionfont \
+       -command choose_extdiff
+    pack $top.extdifff.l $top.extdifff.b -side left
+    grid x $top.extdifff $top.extdifft -sticky w
+
     label $top.cdisp -text [mc "Colors: press to choose"]
     grid $top.cdisp - -sticky w -pady 10
     label $top.bg -padx 40 -relief sunk -background $bgcolor
     button $top.bgbut -text [mc "Background"] -font optionfont \
     label $top.cdisp -text [mc "Colors: press to choose"]
     grid $top.cdisp - -sticky w -pady 10
     label $top.bg -padx 40 -relief sunk -background $bgcolor
     button $top.bgbut -text [mc "Background"] -font optionfont \
-       -command [list choosecolor bgcolor 0 $top.bg background setbg]
+       -command [list choosecolor bgcolor {} $top.bg background setbg]
     grid x $top.bgbut $top.bg -sticky w
     label $top.fg -padx 40 -relief sunk -background $fgcolor
     button $top.fgbut -text [mc "Foreground"] -font optionfont \
     grid x $top.bgbut $top.bg -sticky w
     label $top.fg -padx 40 -relief sunk -background $fgcolor
     button $top.fgbut -text [mc "Foreground"] -font optionfont \
-       -command [list choosecolor fgcolor 0 $top.fg foreground setfg]
+       -command [list choosecolor fgcolor {} $top.fg foreground setfg]
     grid x $top.fgbut $top.fg -sticky w
     label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
     button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
     grid x $top.fgbut $top.fg -sticky w
     label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
     button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
@@ -8770,7 +9385,7 @@ proc doprefs {} {
     grid x $top.hunksepbut $top.hunksep -sticky w
     label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
     button $top.selbgbut -text [mc "Select bg"] -font optionfont \
     grid x $top.hunksepbut $top.hunksep -sticky w
     label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
     button $top.selbgbut -text [mc "Select bg"] -font optionfont \
-       -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg]
+       -command [list choosecolor selectbgcolor {} $top.selbgsep background setselbg]
     grid x $top.selbgbut $top.selbgsep -sticky w
 
     label $top.cfont -text [mc "Fonts: press to choose"]
     grid x $top.selbgbut $top.selbgsep -sticky w
 
     label $top.cfont -text [mc "Fonts: press to choose"]
@@ -8789,6 +9404,15 @@ proc doprefs {} {
     bind $top <Visibility> "focus $top.buts.ok"
 }
 
     bind $top <Visibility> "focus $top.buts.ok"
 }
 
+proc choose_extdiff {} {
+    global extdifftool
+
+    set prog [tk_getOpenFile -title "External diff tool" -multiple false]
+    if {$prog ne {}} {
+       set extdifftool $prog
+    }
+}
+
 proc choosecolor {v vi w x cmd} {
     global $v
 
 proc choosecolor {v vi w x cmd} {
     global $v
 
@@ -9180,7 +9804,6 @@ if {[catch {package require Tk 8.4} err]} {
 }
 
 # defaults...
 }
 
 # defaults...
-set datemode 0
 set wrcomcmd "git diff-tree --stdin -p --pretty"
 
 set gitencoding {}
 set wrcomcmd "git diff-tree --stdin -p --pretty"
 
 set gitencoding {}
@@ -9215,6 +9838,9 @@ set maxlinelen 200
 set showlocalchanges 1
 set limitdiffs 1
 set datetimeformat "%Y-%m-%d %H:%M:%S"
 set showlocalchanges 1
 set limitdiffs 1
 set datetimeformat "%Y-%m-%d %H:%M:%S"
+set autoselect 1
+
+set extdifftool "meld"
 
 set colors {green red blue magenta darkgrey brown orange}
 set bgcolor white
 
 set colors {green red blue magenta darkgrey brown orange}
 set bgcolor white
@@ -9224,6 +9850,15 @@ set diffcontext 3
 set ignorespace 0
 set selectbgcolor gray85
 
 set ignorespace 0
 set selectbgcolor gray85
 
+set circlecolors {white blue gray blue blue}
+
+# button for popping up context menus
+if {[tk windowingsystem] eq "aqua"} {
+    set ctxbut <Button-2>
+} else {
+    set ctxbut <Button-3>
+}
+
 ## For msgcat loading, first locate the installation location.
 if { [info exists ::env(GITK_MSGSDIR)] } {
     ## Msgsdir was manually set in the environment.
 ## For msgcat loading, first locate the installation location.
 if { [info exists ::env(GITK_MSGSDIR)] } {
     ## Msgsdir was manually set in the environment.
@@ -9270,22 +9905,26 @@ if {![file isdirectory $gitdir]} {
     exit 1
 }
 
     exit 1
 }
 
-set mergeonly 0
+set selecthead {}
+set selectheadid {}
+
 set revtreeargs {}
 set cmdline_files {}
 set i 0
 set revtreeargs {}
 set cmdline_files {}
 set i 0
+set revtreeargscmd {}
 foreach arg $argv {
 foreach arg $argv {
-    switch -- $arg {
+    switch -glob -- $arg {
        "" { }
        "" { }
-       "-d" { set datemode 1 }
-       "--merge" {
-           set mergeonly 1
-           lappend revtreeargs $arg
-       }
        "--" {
            set cmdline_files [lrange $argv [expr {$i + 1}] end]
            break
        }
        "--" {
            set cmdline_files [lrange $argv [expr {$i + 1}] end]
            break
        }
+       "--select-commit=*" {
+           set selecthead [string range $arg 16 end]
+       }
+       "--argscmd=*" {
+           set revtreeargscmd [string range $arg 10 end]
+       }
        default {
            lappend revtreeargs $arg
        }
        default {
            lappend revtreeargs $arg
        }
@@ -9293,8 +9932,12 @@ foreach arg $argv {
     incr i
 }
 
     incr i
 }
 
+if {$selecthead eq "HEAD"} {
+    set selecthead {}
+}
+
 if {$i >= [llength $argv] && $revtreeargs ne {}} {
 if {$i >= [llength $argv] && $revtreeargs ne {}} {
-    # no -- on command line, but some arguments (other than -d)
+    # no -- on command line, but some arguments (other than --argscmd)
     if {[catch {
        set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
        set cmdline_files [split $f "\n"]
     if {[catch {
        set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
        set cmdline_files [split $f "\n"]
@@ -9322,42 +9965,9 @@ if {$i >= [llength $argv] && $revtreeargs ne {}} {
     }
 }
 
     }
 }
 
-if {$mergeonly} {
-    # find the list of unmerged files
-    set mlist {}
-    set nr_unmerged 0
-    if {[catch {
-       set fd [open "| git ls-files -u" r]
-    } err]} {
-       show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
-       exit 1
-    }
-    while {[gets $fd line] >= 0} {
-       set i [string first "\t" $line]
-       if {$i < 0} continue
-       set fname [string range $line [expr {$i+1}] end]
-       if {[lsearch -exact $mlist $fname] >= 0} continue
-       incr nr_unmerged
-       if {$cmdline_files eq {} || [path_filter $cmdline_files $fname]} {
-           lappend mlist $fname
-       }
-    }
-    catch {close $fd}
-    if {$mlist eq {}} {
-       if {$nr_unmerged == 0} {
-           show_error {} . [mc "No files selected: --merge specified but\
-                            no files are unmerged."]
-       } else {
-           show_error {} . [mc "No files selected: --merge specified but\
-                            no unmerged files are within file limit."]
-       }
-       exit 1
-    }
-    set cmdline_files $mlist
-}
-
 set nullid "0000000000000000000000000000000000000000"
 set nullid2 "0000000000000000000000000000000000000001"
 set nullid "0000000000000000000000000000000000000000"
 set nullid2 "0000000000000000000000000000000000000001"
+set nullfile "/dev/null"
 
 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
 
 
 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
 
@@ -9387,13 +9997,17 @@ set highlight_files {}
 set viewfiles(0) {}
 set viewperm(0) 0
 set viewargs(0) {}
 set viewfiles(0) {}
 set viewperm(0) 0
 set viewargs(0) {}
+set viewargscmd(0) {}
 
 
+set selectedline {}
+set numcommits 0
 set loginstance 0
 set cmdlineok 0
 set stopped 0
 set stuffsaved 0
 set patchnum 0
 set lserial 0
 set loginstance 0
 set cmdlineok 0
 set stopped 0
 set stuffsaved 0
 set patchnum 0
 set lserial 0
+set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
 setcoords
 makewindow
 # wait for the window to become visible
 setcoords
 makewindow
 # wait for the window to become visible
@@ -9401,7 +10015,7 @@ tkwait visibility .
 wm title . "[file tail $argv0]: [file tail [pwd]]"
 readrefs
 
 wm title . "[file tail $argv0]: [file tail [pwd]]"
 readrefs
 
-if {$cmdline_files ne {} || $revtreeargs ne {}} {
+if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
     # create a view for the files/dirs specified on the command line
     set curview 1
     set selectedview 1
     # create a view for the files/dirs specified on the command line
     set curview 1
     set selectedview 1
@@ -9409,7 +10023,9 @@ if {$cmdline_files ne {} || $revtreeargs ne {}} {
     set viewname(1) [mc "Command line"]
     set viewfiles(1) $cmdline_files
     set viewargs(1) $revtreeargs
     set viewname(1) [mc "Command line"]
     set viewfiles(1) $cmdline_files
     set viewargs(1) $revtreeargs
+    set viewargscmd(1) $revtreeargscmd
     set viewperm(1) 0
     set viewperm(1) 0
+    set vdatemode(1) 0
     addviewmenu 1
     .bar.view entryconf [mc "Edit view..."] -state normal
     .bar.view entryconf [mc "Delete view"] -state normal
     addviewmenu 1
     .bar.view entryconf [mc "Edit view..."] -state normal
     .bar.view entryconf [mc "Delete view"] -state normal
@@ -9422,8 +10038,9 @@ if {[info exists permviews]} {
        set viewname($n) [lindex $v 0]
        set viewfiles($n) [lindex $v 1]
        set viewargs($n) [lindex $v 2]
        set viewname($n) [lindex $v 0]
        set viewfiles($n) [lindex $v 1]
        set viewargs($n) [lindex $v 2]
+       set viewargscmd($n) [lindex $v 3]
        set viewperm($n) 1
        addviewmenu $n
     }
 }
        set viewperm($n) 1
        addviewmenu $n
     }
 }
-getcommits
+getcommits {}