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" -- "$@"
 
-# 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.
@@ -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 {
-    global isonrunq runq
+    global isonrunq runq currunq
 
     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]
@@ -38,10 +38,10 @@ proc filerun {fd script} {
 }
 
 proc filereadable {fd script} {
-    global runq
+    global runq currunq
 
     fileevent $fd readable {}
-    if {$runq eq {}} {
+    if {$runq eq {} && ![info exists currunq]} {
        after idle dorunq
     }
     lappend runq [list $fd $script]
@@ -60,17 +60,19 @@ proc nukefile {fd} {
 }
 
 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 currunq [lindex $runq 0]
+       set runq [lrange $runq 1 end]
        set repeat [eval $script]
+       unset currunq
        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
@@ -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} {
-    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 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
 
-    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 \
-                        --boundary $commits "--" $viewfiles($view)] r]
+                        --boundary $args "--" $files] r]
     } 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 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
     }
-    filerun $fd [list getcommitlines $fd $i $view]
+    filerun $fd [list getcommitlines $fd $i $view 0]
     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} {
-    global commfd viewinstances leftover
+    global viewinstances
 
     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) {}
 }
 
-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
-    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 {} {
-    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 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} {
@@ -185,52 +462,60 @@ proc updatecommits {} {
        }
     }
     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 \
-                        --boundary $pos $neg $flags "--" $viewfiles($view)] r]
+                         --boundary $args "--" $vfilelimit($view)] r]
     } 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]
     }
-    set i [incr loginstance]
+    set i [reg_instance $fd]
     lappend viewinstances($view) $i
-    set commfd($i) $fd
-    set leftover($i) {}
     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
-    set pending_select $mainheadid
+    reset_pending_select {}
     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
-    global progresscoords targetid
+    global targetid
+
+    set selid {}
+    if {$selectedline ne {}} {
+       set selid $currentid
+    }
 
     if {!$viewcomplete($curview)} {
        stop_rev_list $curview
-       set progresscoords {0 0}
-       adjustprogress
     }
     resetvarcs $curview
-    catch {unset selectedline}
+    set selectedline {}
     catch {unset currentid}
     catch {unset thickerline}
     catch {unset treediffs}
@@ -262,7 +550,7 @@ proc reloadcommits {} {
     catch {unset cached_commitrow}
     catch {unset targetid}
     setcanvscroll
-    getcommits
+    getcommits $selid
     return 0
 }
 
@@ -321,14 +609,27 @@ proc resetvarcs {view} {
     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} {
-    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
-    if {[llength $children($vid)] == 0 || $datemode} {
+    if {[llength $children($vid)] == 0 || $vdatemode($view)} {
        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]
+    lappend vlastins($v) [lindex $vlastins($v) $oa]
     lset vdownptr($v) $oa $na
+    lset vlastins($v) $oa 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
     }
@@ -427,7 +729,7 @@ proc splitvarc {p v} {
 
 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 {}
@@ -463,7 +765,7 @@ proc renumbervarc {a v} {
                                      $children($v,$id)]
        }
        set oldtok [lindex $varctok($v) $a]
-       if {!$datemode} {
+       if {!$vdatemode($v)} {
            set tok {}
        } else {
            set tok $oldtok
@@ -513,6 +815,9 @@ proc renumbervarc {a v} {
            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 || \
@@ -551,6 +856,9 @@ proc renumbervarc {a v} {
     #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
 
@@ -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]
-    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
@@ -659,12 +965,10 @@ proc removefakerow {id} {
     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
-       unset selectedline
+       set selectedline {}
     }
     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)]]
 }
 
+# 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
 
+    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} {
@@ -732,6 +1046,14 @@ proc update_arcrows {v} {
     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 {}} {
@@ -750,23 +1072,12 @@ proc update_arcrows {v} {
        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 {$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)]
@@ -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 commitidx commitdata datemode
+    global commitidx commitdata vdatemode
     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...
@@ -976,7 +1313,7 @@ proc getcommitlines {fd inst view}  {
        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]
@@ -992,10 +1329,10 @@ proc getcommitlines {fd inst view}  {
            }
            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 \
-                       "  (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 {
@@ -1009,11 +1346,9 @@ proc getcommitlines {fd inst view}  {
            # appeared in the list
            closevarcs $view
            notbusy $view
-           set progresscoords {0 0}
-           adjustprogress
        }
        if {$view == $curview} {
-           run chewcommits $view
+           run chewcommits
        }
        return 0
     }
@@ -1066,6 +1401,26 @@ proc getcommitlines {fd inst view}  {
        }
        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
@@ -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 &&
-               (!$datemode ||
+               (!$vdatemode($view) ||
                 $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} {
-       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
        }
-       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
 }
 
-proc chewcommits {view} {
+proc chewcommits {} {
     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
            }
-           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
 }
@@ -1239,7 +1577,7 @@ proc parsecommit {id contents 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"] {
@@ -1273,6 +1611,7 @@ proc getcommit {id} {
 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}
@@ -1313,12 +1652,16 @@ proc readrefs {} {
     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]
-           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 rprogitem rprogcoord
+    global rprogitem rprogcoord rownumsel numcommits
     global have_tk85
 
     menu .bar
@@ -1537,6 +1880,18 @@ proc makewindow {} {
        -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
@@ -1608,6 +1963,7 @@ proc makewindow {} {
     }
     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
@@ -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
-    set ctext .bleft.ctext
+    set ctext .bleft.bottom.ctext
     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
     }
-    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.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
 
@@ -1711,9 +2074,17 @@ proc makewindow {} {
     .pwbottom add .bright
     .ctop add .pwbottom
 
-    # restore window position if known
+    # restore window width & height if known
     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}} {
@@ -1768,7 +2139,7 @@ proc makewindow {} {
     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}
@@ -1787,6 +2158,7 @@ proc makewindow {} {
     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
@@ -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 <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]
@@ -1840,6 +2213,10 @@ proc makewindow {} {
        -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
@@ -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} {
@@ -1941,9 +2329,10 @@ proc savestuff {w} {
     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 autoselect extdifftool
 
     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 autoselect $autoselect]
        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 extdifftool $extdifftool]
 
        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 $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
+               puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
            }
        }
        puts $f "}"
@@ -2068,7 +2459,7 @@ proc about {} {
     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
@@ -2315,7 +2706,7 @@ proc treeopendir {w dir} {
            $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]
@@ -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 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
 }
 
@@ -2533,6 +2930,134 @@ proc flist_hl {only} {
     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} {
@@ -2631,7 +3156,7 @@ proc shellsplit {str} {
 
 proc newview {ishighlight} {
     global nextviewnum newviewname newviewperm newishighlight
-    global newviewargs revtreeargs
+    global newviewargs revtreeargs viewargscmd newviewargscmd curview
 
     set newishighlight $ishighlight
     set top .gitkview
@@ -2639,16 +3164,17 @@ proc newview {ishighlight} {
        raise $top
        return
     }
-    set newviewname($nextviewnum) "View $nextviewnum"
+    set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
     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
-    global viewargs newviewargs
+    global viewargs newviewargs viewargscmd newviewargscmd
 
     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 newviewargscmd($curview) $viewargscmd($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 \
-       -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
+
+    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
@@ -2721,7 +3256,7 @@ proc allviewmenus {n op args} {
 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)]
@@ -2745,6 +3280,7 @@ proc newviewok {top n} {
        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
@@ -2761,9 +3297,11 @@ proc newviewok {top 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 viewargscmd($n) $newviewargscmd($n)
            if {$curview == $n} {
                run reloadcommits
            }
@@ -2795,7 +3333,7 @@ proc addviewmenu {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
@@ -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}]
-    if {[info exists selectedline]} {
+    if {$selectedline ne {}} {
        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)]} {
-       if {$selid ne {}} {
-           set pending_select $selid
-       }
-       getcommits
+       getcommits $selid
        return
     }
 
@@ -2879,18 +3414,18 @@ proc showview {n} {
     drawvisible
     if {$row ne {}} {
        selectline $row 0
-    } elseif {$mainheadid ne {} && [commitinview $mainheadid $curview]} {
-       selectline [rowofcommit $mainheadid] 1
     } elseif {!$viewcomplete($n)} {
-       if {$selid ne {}} {
-           set pending_select $selid
-       } else {
-           set pending_select $mainheadid
-       }
+       reset_pending_select $selid
     } 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)} {
@@ -2927,7 +3462,7 @@ proc bolden {row 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 \
@@ -2941,7 +3476,7 @@ proc bolden_name {row 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 \
@@ -3009,6 +3544,7 @@ proc vhighlightmore {} {
        }
     }
     set vhl_done $max
+    return 0
 }
 
 proc askvhighlight {row id} {
@@ -3369,7 +3905,7 @@ proc askrelhighlight {row id} {
     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"]} {
@@ -3507,15 +4043,19 @@ proc initlayout {} {
     set canvxmax [$canv cget -width]
     catch {unset colormap}
     catch {unset rowtextx}
+    setcanvscroll
 }
 
 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 lastscrollset [clock clicks -milliseconds]
+    set lastscrollrows $numcommits
 }
 
 proc visiblerows {} {
@@ -3539,39 +4079,25 @@ proc visiblerows {} {
 
 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
     }
-    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]} {
+       update
        selectline [rowofcommit $pending_select] 1
     }
+    drawvisible
 }
 
 proc doshowlocalchanges {} {
     global curview mainheadid
 
+    if {$mainheadid eq {}} return
     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
+    global isworktree
 
-    if {!$showlocalchanges} return
+    if {!$showlocalchanges || !$isworktree} return
     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
@@ -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...
-    close $fd
+    stop_instance $inst
 
     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
-    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
@@ -3639,7 +4168,7 @@ proc readdiffindex {fd serial} {
     return 0
 }
 
-proc readdifffiles {fd serial} {
+proc readdifffiles {fd serial inst} {
     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...
-    close $fd
+    stop_instance $inst
 
     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 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)
@@ -4398,8 +4928,10 @@ proc drawcmittext {id row col} {
        set ofill red
     } elseif {$id eq $nullid2} {
        set ofill green
+    } elseif {$id eq $mainheadid} {
+       set ofill yellow
     } 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]
@@ -4423,6 +4955,7 @@ proc drawcmittext {id row col} {
                   [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]]
@@ -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]
-    $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]
-    if {[info exists selectedline] && $selectedline == $row} {
+    if {$selectedline == $row} {
        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 {[info exists selectedline] &&
+    if {$selectedline ne {} &&
        $row <= $selectedline && $selectedline <= $endrow} {
        set targetrow $selectedline
-    } else {
+    } elseif {[info exists targetid]} {
        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
+    global linehtag linentag linedtag boldrows boldnamerows
 
     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}
@@ -4794,7 +5335,7 @@ proc bindline {t id} {
 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
@@ -4872,7 +5413,7 @@ proc drawtags {id x xt y1} {
        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
@@ -4970,7 +5511,7 @@ proc dofind {{dirn 1} {wrap 1}} {
     }
     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
@@ -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
-       if {[info exists selectedline] && $row == $selectedline} {
+       if {$row == $selectedline} {
            $canv raise $t secsel
        }
     }
@@ -5325,7 +5866,7 @@ proc appendrefs {pos ids var} {
 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
@@ -5339,7 +5880,7 @@ proc dispneartags {delay} {
 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]
@@ -5396,7 +5937,8 @@ proc selectline {l isnew} {
     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
@@ -5404,6 +5946,15 @@ proc selectline {l isnew} {
     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}]
@@ -5442,24 +5993,24 @@ proc selectline {l isnew} {
 
     make_secsel $l
 
-    set id [commitonrow $l]
     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 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
+    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"
@@ -5551,7 +6102,7 @@ proc sellastline {} {
 proc selnextline {dir} {
     global selectedline
     focus .
-    if {![info exists selectedline]} return
+    if {$selectedline eq {}} return
     set l [expr {$selectedline + $dir}]
     unmarkmatches
     selectline $l 1
@@ -5566,7 +6117,7 @@ proc selnextpage {dir} {
     }
     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
@@ -5580,7 +6131,7 @@ proc selnextpage {dir} {
 proc unselectline {} {
     global selectedline currentid
 
-    catch {unset selectedline}
+    set selectedline {}
     catch {unset currentid}
     allcanvs delete secsel
     rhighlight_none
@@ -5589,7 +6140,7 @@ proc unselectline {} {
 proc reselectline {} {
     global selectedline
 
-    if {[info exists selectedline]} {
+    if {$selectedline ne {}} {
        selectline $selectedline 0
     }
 }
@@ -5694,11 +6245,12 @@ proc gettreeline {gtf id} {
        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 sha1 [lindex $line 2]
            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]
            }
@@ -5782,14 +6334,14 @@ proc mergediff {id} {
     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]
-    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"
@@ -5958,16 +6510,17 @@ proc diffcmd {ids flags} {
 proc gettreediffs {ids} {
     global treediff treepending
 
+    if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
+
     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
-    global cmitmode viewfiles curview limitdiffs
+    global cmitmode vfilelimit curview limitdiffs
 
     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
-    if {$limitdiffs && $viewfiles($curview) ne {}} {
+    if {$limitdiffs && $vfilelimit($curview) ne {}} {
        set flist {}
        foreach f $treediff {
-           if {[path_filter $viewfiles($curview) $f]} {
+           if {[path_filter $vfilelimit($curview) $f]} {
                lappend flist $f
            }
        }
@@ -6033,14 +6586,14 @@ proc getblobdiffs {ids} {
     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"
     }
-    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"
@@ -6180,26 +6733,44 @@ proc changediffdisp {} {
     $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 {} {
-    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]} {
-           $ctext yview $prev
+           highlightfile $prev $prevline
            return
        }
        set prev $loc
+       incr prevline
     }
-    $ctext yview $prev
+    highlightfile $prev $prevline
 }
 
 proc nextfile {} {
-    global difffilestart ctext
+    global difffilestart ctext cmitmode
+
+    if {$cmitmode eq "tree"} return
     set here [$ctext index @0,0]
+    set line 1
     foreach loc $difffilestart {
+       incr line
        if {[$ctext compare $loc > $here]} {
-           $ctext yview $loc
+           highlightfile $loc $line
            return
        }
     }
@@ -6352,7 +6923,7 @@ proc searchmarkvisible {doall} {
 proc scrolltext {f0 f1} {
     global searchstring
 
-    .bleft.sb set $f0 $f1
+    .bleft.bottom.sb set $f0 $f1
     if {$searchstring ne {}} {
        searchmarkvisible 0
     }
@@ -6382,7 +6953,7 @@ proc redisplay {} {
     setcanvscroll
     allcanvs yview moveto [lindex $span 0]
     drawvisible
-    if {[info exists selectedline]} {
+    if {$selectedline ne {}} {
        selectline $selectedline 0
        allcanvs yview moveto [lindex $span 0]
     }
@@ -6703,15 +7274,18 @@ proc rowmenu {x y 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
-       $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
     }
@@ -6724,7 +7298,7 @@ proc rowmenu {x y id} {
 proc diffvssel {dirn} {
     global rowmenuid selectedline
 
-    if {![info exists selectedline]} return
+    if {$selectedline eq {}} return
     if {$dirn} {
        set oldid [commitonrow $selectedline]
        set newid $rowmenuid
@@ -6908,12 +7482,18 @@ proc domktag {} {
 }
 
 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 {$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]
@@ -7051,7 +7631,7 @@ proc mkbrgo {top} {
 }
 
 proc cherrypick {} {
-    global rowmenuid curview viewincl
+    global rowmenuid curview
     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
-           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
@@ -7132,7 +7706,7 @@ proc resethead {} {
     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
@@ -7190,29 +7764,48 @@ proc headmenu {x y id head} {
 }
 
 proc cobranch {} {
-    global headmenuid headmenuhead mainhead headids
+    global headmenuid headmenuhead headids
     global showlocalchanges mainheadid
 
     # check the tree is clean first??
-    set oldmainhead $mainhead
     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
+       if {$showlocalchanges} {
+           dodiffindex
+       }
     } 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
     }
@@ -8510,12 +9103,14 @@ proc rereadrefs {} {
                        [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
        }
     }
+    if {$oldmainhead ne $mainheadid} {
+       redrawtags $oldmainhead
+       redrawtags $mainheadid
+    }
     run refill_reflist
 }
 
@@ -8564,9 +9159,15 @@ proc showtag {tag isnew} {
 
 proc doquit {} {
     global stopped
+    global gitktmpdir
+
     set stopped 100
     savestuff .
     destroy .
+
+    if {[info exists gitktmpdir]} {
+       catch {file delete -force $gitktmpdir}
+    }
 }
 
 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 tabstop limitdiffs
+    global tabstop limitdiffs autoselect extdifftool
 
     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
+    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
@@ -8742,15 +9348,24 @@ proc doprefs {} {
     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 \
-       -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 \
-       -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 \
@@ -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 \
-       -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"]
@@ -8789,6 +9404,15 @@ proc doprefs {} {
     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
 
@@ -9180,7 +9804,6 @@ if {[catch {package require Tk 8.4} err]} {
 }
 
 # defaults...
-set datemode 0
 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 autoselect 1
+
+set extdifftool "meld"
 
 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 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.
@@ -9270,22 +9905,26 @@ if {![file isdirectory $gitdir]} {
     exit 1
 }
 
-set mergeonly 0
+set selecthead {}
+set selectheadid {}
+
 set revtreeargs {}
 set cmdline_files {}
 set i 0
+set revtreeargscmd {}
 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
        }
+       "--select-commit=*" {
+           set selecthead [string range $arg 16 end]
+       }
+       "--argscmd=*" {
+           set revtreeargscmd [string range $arg 10 end]
+       }
        default {
            lappend revtreeargs $arg
        }
@@ -9293,8 +9932,12 @@ foreach arg $argv {
     incr i
 }
 
+if {$selecthead eq "HEAD"} {
+    set selecthead {}
+}
+
 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"]
@@ -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 nullfile "/dev/null"
 
 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 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 isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
 setcoords
 makewindow
 # wait for the window to become visible
@@ -9401,7 +10015,7 @@ tkwait visibility .
 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
@@ -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 viewargscmd(1) $revtreeargscmd
     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
@@ -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 viewargscmd($n) [lindex $v 3]
        set viewperm($n) 1
        addviewmenu $n
     }
 }
-getcommits
+getcommits {}