gitk: Force the focus to the main window on Windows
[gitweb.git] / gitk
diff --git a/gitk b/gitk
index 00037976970d57888e2db09eec77d836b2215bbb..dc2a439618ffd92a92d9ca954a8597ba31875dab 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -155,18 +155,16 @@ proc parseviewargs {n arglist} {
                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=*" {
+               # These request or affect diff output, which we don't want.
+               # Some could be used to set our defaults for diff display.
                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" -
@@ -174,36 +172,34 @@ proc parseviewargs {n arglist} {
            "--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
            "--timestamp" - "relative-date" - "--date=*" - "--stdin" -
            "--objects" - "--objects-edge" - "--reverse" {
+               # These cause our parsing of git log's output to fail, or else
+               # they're options we want to set ourselves, so ignore them.
            }
-           # 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=*" {
+               # These are harmless, and some are even useful
                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" - {
+           "-S*" - "--pickaxe-all" - "--pickaxe-regex" {
+               # These mean that we get a subset of the commits
                set filtered 1
                lappend glflags $arg
            }
-           # This appears to be the only one that has a value as a
-           # separate word following it
            "-n" {
+               # This appears to be the only one that has a value as a
+               # separate word following it
                set filtered 1
                set nextisval 1
                lappend glflags $arg
            }
-           "--not" {
-               set notflag [expr {!$notflag}]
-               lappend revargs $arg
-           }
-           "--all" {
+           "--not" - "--all" {
                lappend revargs $arg
            }
            "--merge" {
@@ -211,8 +207,8 @@ proc parseviewargs {n arglist} {
                # git rev-parse doesn't understand --merge
                lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
            }
-           # Other flag arguments including -<n>
            "-*" {
+               # Other flag arguments including -<n>
                if {[string is digit -strict [string range $arg 1 end]]} {
                    set filtered 1
                } else {
@@ -222,8 +218,8 @@ proc parseviewargs {n arglist} {
                }
                lappend glflags $arg
            }
-           # Non-flag arguments specify commits or ranges of commits
            default {
+               # Non-flag arguments specify commits or ranges of commits
                if {[string match "*...*" $arg]} {
                    lappend revargs --gitk-symmetric-diff-marker
                }
@@ -309,7 +305,7 @@ proc start_rev_list {view} {
     global viewargs viewargscmd viewfiles vfilelimit
     global showlocalchanges
     global viewactive viewinstances vmergeonly
-    global mainheadid
+    global mainheadid viewmainheadid viewmainheadid_orig
     global vcanopt vflags vrevs vorigargs
 
     set startmsecs [clock clicks -milliseconds]
@@ -367,8 +363,13 @@ proc start_rev_list {view} {
     }
     set i [reg_instance $fd]
     set viewinstances($view) [list $i]
-    if {$showlocalchanges && $mainheadid ne {}} {
-       interestedin $mainheadid dodiffindex
+    set viewmainheadid($view) $mainheadid
+    set viewmainheadid_orig($view) $mainheadid
+    if {$files ne {} && $mainheadid ne {}} {
+       get_viewmainhead $view
+    }
+    if {$showlocalchanges && $viewmainheadid($view) ne {}} {
+       interestedin $viewmainheadid($view) dodiffindex
     }
     fconfigure $fd -blocking 0 -translation lf -eofchar {}
     if {$tclencoding != {}} {
@@ -446,22 +447,26 @@ proc updatecommits {} {
     global curview vcanopt vorigargs vfilelimit viewinstances
     global viewactive viewcomplete tclencoding
     global startmsecs showneartags showlocalchanges
-    global mainheadid pending_select
+    global mainheadid viewmainheadid viewmainheadid_orig 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} {
-       if {$mainheadid ne $oldmainid} {
+    set view $curview
+    if {$mainheadid ne $viewmainheadid_orig($view)} {
+       if {$showlocalchanges} {
            dohidelocalchanges
        }
-       if {[commitinview $mainheadid $curview]} {
-           dodiffindex
+       set viewmainheadid($view) $mainheadid
+       set viewmainheadid_orig($view) $mainheadid
+       if {$vfilelimit($view) ne {}} {
+           get_viewmainhead $view
        }
     }
-    set view $curview
+    if {$showlocalchanges} {
+       doshowlocalchanges
+    }
     if {$vcanopt($view)} {
        set oldpos $vposids($view)
        set oldneg $vnegids($view)
@@ -1555,9 +1560,27 @@ proc chewcommits {} {
     return 0
 }
 
+proc do_readcommit {id} {
+    global tclencoding
+
+    # Invoke git-log to handle automatic encoding conversion
+    set fd [open [concat | git log --no-color --pretty=raw -1 $id] r]
+    # Read the results using i18n.logoutputencoding
+    fconfigure $fd -translation lf -eofchar {}
+    if {$tclencoding != {}} {
+       fconfigure $fd -encoding $tclencoding
+    }
+    set contents [read $fd]
+    close $fd
+    # Remove the heading line
+    regsub {^commit [0-9a-f]+\n} $contents {} contents
+
+    return $contents
+}
+
 proc readcommit {id} {
-    if {[catch {set contents [exec git cat-file commit $id]}]} return
-    parsecommit $id $contents 0
+    if {[catch {set contents [do_readcommit $id]}]} return
+    parsecommit $id $contents 1
 }
 
 proc parsecommit {id contents listed} {
@@ -1578,13 +1601,14 @@ proc parsecommit {id contents listed} {
     set header [string range $contents 0 [expr {$hdrend - 1}]]
     set comment [string range $contents [expr {$hdrend + 2}] end]
     foreach line [split $header "\n"] {
+       set line [split $line " "]
        set tag [lindex $line 0]
        if {$tag == "author"} {
            set audate [lindex $line end-1]
-           set auname [lrange $line 1 end-2]
+           set auname [join [lrange $line 1 end-2] " "]
        } elseif {$tag == "committer"} {
            set comdate [lindex $line end-1]
-           set comname [lrange $line 1 end-2]
+           set comname [join [lrange $line 1 end-2] " "]
        }
     }
     set headline {}
@@ -1739,6 +1763,24 @@ proc removehead {id name} {
     unset headids($name)
 }
 
+proc make_transient {window origin} {
+    global have_tk85
+
+    # In MacOS Tk 8.4 transient appears to work by setting
+    # overrideredirect, which is utterly useless, since the
+    # windows get no border, and are not even kept above
+    # the parent.
+    if {!$have_tk85 && [tk windowingsystem] eq {aqua}} return
+
+    wm transient $window $origin
+
+    # Windows fails to place transient windows normally, so
+    # schedule a callback to center them on the parent.
+    if {[tk windowingsystem] eq {win32}} {
+       after idle [list tk::PlaceWindow $window widget $origin]
+    }
+}
+
 proc show_error {w top msg} {
     message $w.m -text $msg -justify center -aspect 400
     pack $w.m -side top -fill x -padx 20 -pady 20
@@ -1746,22 +1788,24 @@ proc show_error {w top msg} {
     pack $w.ok -side bottom -fill x
     bind $top <Visibility> "grab $top; focus $top"
     bind $top <Key-Return> "destroy $top"
+    bind $top <Key-space>  "destroy $top"
+    bind $top <Key-Escape> "destroy $top"
     tkwait window $top
 }
 
-proc error_popup msg {
+proc error_popup {msg {owner .}} {
     set w .error
     toplevel $w
-    wm transient $w .
+    make_transient $w $owner
     show_error $w $w $msg
 }
 
-proc confirm_popup msg {
+proc confirm_popup {msg {owner .}} {
     global confirm_ok
     set confirm_ok 0
     set w .confirm
     toplevel $w
-    wm transient $w .
+    make_transient $w $owner
     message $w.m -text $msg -justify center -aspect 400
     pack $w.m -side top -fill x -padx 20 -pady 20
     button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
@@ -1769,6 +1813,9 @@ proc confirm_popup msg {
     button $w.cancel -text [mc Cancel] -command "destroy $w"
     pack $w.cancel -side right -fill x
     bind $w <Visibility> "grab $w; focus $w"
+    bind $w <Key-Return> "set confirm_ok 1; destroy $w"
+    bind $w <Key-space>  "set confirm_ok 1; destroy $w"
+    bind $w <Key-Escape> "destroy $w"
     tkwait window $w
     return $confirm_ok
 }
@@ -1796,6 +1843,11 @@ proc setoptions {} {
 # command to invoke for command, or {variable value} for radiobutton
 proc makemenu {m items} {
     menu $m
+    if {[tk windowingsystem] eq {aqua}} {
+       set Meta1 Cmd
+    } else {
+       set Meta1 Ctrl
+    }
     foreach i $items {
        set name [mc [lindex $i 1]]
        set type [lindex $i 2]
@@ -1821,7 +1873,9 @@ proc makemenu {m items} {
                    -value [lindex $thing 1]
            }
        }
-       eval $m add $params [lrange $i 4 end]
+       set tail [lrange $i 4 end]
+       regsub -all {\yMeta1\y} $tail $Meta1 tail
+       eval $m add $params $tail
        if {$type eq "cascade"} {
            makemenu $m.$submenu $thing
        }
@@ -1855,17 +1909,20 @@ proc makewindow {} {
     makemenu .bar {
        {mc "File" cascade {
            {mc "Update" command updatecommits -accelerator F5}
-           {mc "Reload" command reloadcommits}
+           {mc "Reload" command reloadcommits -accelerator Meta1-F5}
            {mc "Reread references" command rereadrefs}
-           {mc "List references" command showrefs}
-           {mc "Quit" command doquit}
+           {mc "List references" command showrefs -accelerator F2}
+           {xx "" separator}
+           {mc "Start git gui" command {exec git gui &}}
+           {xx "" separator}
+           {mc "Quit" command doquit -accelerator Meta1-Q}
        }}
        {mc "Edit" cascade {
            {mc "Preferences" command doprefs}
        }}
        {mc "View" cascade {
-           {mc "New view..." command {newview 0}}
-           {mc "Edit view..." command editview -state disabled}
+           {mc "New view..." command {newview 0} -accelerator Shift-F4}
+           {mc "Edit view..." command editview -state disabled -accelerator F4}
            {mc "Delete view" command delview -state disabled}
            {xx "" separator}
            {mc "All files" radiobutton {selectedview 0} -command {showview 0}}
@@ -2098,7 +2155,7 @@ proc makewindow {} {
     $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
     $ctext tag conf hunksep -fore [lindex $diffcolors 2]
     $ctext tag conf d0 -fore [lindex $diffcolors 0]
-    $ctext tag conf d1 -fore [lindex $diffcolors 1]
+    $ctext tag conf dresult -fore [lindex $diffcolors 1]
     $ctext tag conf m0 -fore red
     $ctext tag conf m1 -fore blue
     $ctext tag conf m2 -fore green
@@ -2223,11 +2280,16 @@ proc makewindow {} {
     bindkey b prevfile
     bindkey d "$ctext yview scroll 18 units"
     bindkey u "$ctext yview scroll -18 units"
-    bindkey / {dofind 1 1}
+    bindkey / {focus $fstring}
     bindkey <Key-Return> {dofind 1 1}
     bindkey ? {dofind -1 1}
     bindkey f nextfile
-    bindkey <F5> updatecommits
+    bind . <F5> updatecommits
+    bind . <$M1B-F5> reloadcommits
+    bind . <F2> showrefs
+    bind . <Shift-F4> {newview 0}
+    catch { bind . <Shift-Key-XF86_Switch_VT_4> {newview 0} }
+    bind . <F4> edit_or_newview
     bind . <$M1B-q> doquit
     bind . <$M1B-f> {dofind 1 1}
     bind . <$M1B-g> {dofind 1 0}
@@ -2296,6 +2358,7 @@ proc makewindow {} {
     global diff_menu
     set diff_menu .diffctxmenu
     makemenu $diff_menu {
+       {mc "Show origin of this line" command show_line_source}
        {mc "Run git gui blame on this line" command {external_blame_diff}}
     }
     $diff_menu configure -tearoff 0
@@ -2414,7 +2477,7 @@ proc savestuff {w} {
     global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
     global cmitmode wrapcomment datetimeformat limitdiffs
     global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
-    global autoselect extdifftool perfile_attrs
+    global autoselect extdifftool perfile_attrs markbgcolor
 
     if {$stuffsaved} return
     if {![winfo viewable .]} return
@@ -2438,6 +2501,7 @@ proc savestuff {w} {
        puts $f [list set fgcolor $fgcolor]
        puts $f [list set colors $colors]
        puts $f [list set diffcolors $diffcolors]
+       puts $f [list set markbgcolor $markbgcolor]
        puts $f [list set diffcontext $diffcontext]
        puts $f [list set selectbgcolor $selectbgcolor]
        puts $f [list set extdifftool $extdifftool]
@@ -2539,6 +2603,7 @@ proc about {} {
     }
     toplevel $w
     wm title $w [mc "About gitk"]
+    make_transient $w .
     message $w.m -text [mc "
 Gitk - a commit viewer for git
 
@@ -2567,6 +2632,7 @@ proc keys {} {
     }
     toplevel $w
     wm title $w [mc "Gitk key bindings"]
+    make_transient $w .
     message $w.m -text "
 [mc "Gitk key bindings:"]
 
@@ -2595,7 +2661,7 @@ proc keys {} {
 [mc "<%s-F>            Find" $M1T]
 [mc "<%s-G>            Move to next find hit" $M1T]
 [mc "<Return>  Move to next find hit"]
-[mc "/         Move to next find hit, or redo find"]
+[mc "/         Focus the search box"]
 [mc "?         Move to previous find hit"]
 [mc "f         Scroll diff view to next file"]
 [mc "<%s-S>            Search for next hit in diff view" $M1T]
@@ -2609,6 +2675,7 @@ proc keys {} {
            -justify left -bg white -border 2 -relief groove
     pack $w.m -side top -fill both -padx 2 -pady 2
     button $w.ok -text [mc "Close"] -command "destroy $w" -default active
+    bind $w <Key-Escape> [list destroy $w]
     pack $w.ok -side bottom
     bind $w <Visibility> "focus $w.ok"
     bind $w <Key-Escape> "destroy $w"
@@ -2830,9 +2897,15 @@ proc treeclick {w x y} {
 }
 
 proc setfilelist {id} {
-    global treefilelist cflist
+    global treefilelist cflist jump_to_here
 
     treeview $cflist $treefilelist($id) 0
+    if {$jump_to_here ne {}} {
+       set f [lindex $jump_to_here 0]
+       if {[lsearch -exact $treefilelist($id) $f] >= 0} {
+           showfile $f
+       }
+    }
 }
 
 image create bitmap tri-rt -background black -foreground blue -data {
@@ -3019,9 +3092,13 @@ proc pop_diff_menu {w X Y x y} {
     global diff_menu_txtpos diff_menu_line
     global diff_menu_filebase
 
-    stopfinding
     set diff_menu_txtpos [split [$w index "@$x,$y"] "."]
     set diff_menu_line [lindex $diff_menu_txtpos 0]
+    # don't pop up the menu on hunk-separator or file-separator lines
+    if {[lsearch -glob [$ctext tag names $diff_menu_line.0] "*sep"] >= 0} {
+       return
+    }
+    stopfinding
     set f [find_ctext_fileinfo $diff_menu_line]
     if {$f eq {}} return
     set flist_menu_file [lindex $f 0]
@@ -3159,57 +3236,59 @@ proc find_hunk_blamespec {base line} {
     }
 
     # Now scan the lines to determine offset within the hunk
-    set parent {}
     set max_parent [expr {[llength $base_lines]-2}]
     set dline 0
     set s_lno [lindex [split $s_lix "."] 0]
 
-    for {set i $line} {$i > $s_lno} {incr i -1} {
-       set c_line [$ctext get $i.0 "$i.0 + 1 lines"]
-       # Determine if the line is removed
-       set chunk [string range $c_line 0 $max_parent]
+    # Determine if the line is removed
+    set chunk [$ctext get $line.0 "$line.1 + $max_parent chars"]
+    if {[string match {[-+ ]*} $chunk]} {
        set removed_idx [string first "-" $chunk]
        # Choose a parent index
-       if {$parent eq {}} {
-           if {$removed_idx >= 0} {
-               set parent $removed_idx
+       if {$removed_idx >= 0} {
+           set parent $removed_idx
+       } else {
+           set unchanged_idx [string first " " $chunk]
+           if {$unchanged_idx >= 0} {
+               set parent $unchanged_idx
            } else {
-               set unchanged_idx [string first " " $chunk]
-               if {$unchanged_idx >= 0} {
-                   set parent $unchanged_idx
-               } else {
-                   # blame the current commit
-                   set parent -1
-               }
+               # blame the current commit
+               set parent -1
            }
        }
        # then count other lines that belong to it
-       if {$parent >= 0} {
-           set code [string index $c_line $parent]
-           if {$code eq "-" || ($removed_idx < 0 && $code ne "+")} {
-               incr dline
-           }
-       } else {
-           if {$removed_idx < 0} {
-               incr dline
+       for {set i $line} {[incr i -1] > $s_lno} {} {
+           set chunk [$ctext get $i.0 "$i.1 + $max_parent chars"]
+           # Determine if the line is removed
+           set removed_idx [string first "-" $chunk]
+           if {$parent >= 0} {
+               set code [string index $chunk $parent]
+               if {$code eq "-" || ($removed_idx < 0 && $code ne "+")} {
+                   incr dline
+               }
+           } else {
+               if {$removed_idx < 0} {
+                   incr dline
+               }
            }
        }
+       incr parent
+    } else {
+       set parent 0
     }
 
-    if {$parent eq {}} { set parent -1 }
-    incr parent
     incr dline [lindex $base_lines $parent]
     return [list $parent $dline]
 }
 
 proc external_blame_diff {} {
-    global currentid diffmergeid cmitmode
+    global currentid cmitmode
     global diff_menu_txtpos diff_menu_line
     global diff_menu_filebase flist_menu_file
 
     if {$cmitmode eq "tree"} {
        set parent_idx 0
-       set line [expr {$diff_menu_line - $diff_menu_filebase - 1}]
+       set line [expr {$diff_menu_line - $diff_menu_filebase}]
     } else {
        set hinfo [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
        if {$hinfo ne {}} {
@@ -3224,8 +3303,43 @@ proc external_blame_diff {} {
     external_blame $parent_idx $line
 }
 
+# Find the SHA1 ID of the blob for file $fname in the index
+# at stage 0 or 2
+proc index_sha1 {fname} {
+    set f [open [list | git ls-files -s $fname] r]
+    while {[gets $f line] >= 0} {
+       set info [lindex [split $line "\t"] 0]
+       set stage [lindex $info 2]
+       if {$stage eq "0" || $stage eq "2"} {
+           close $f
+           return [lindex $info 1]
+       }
+    }
+    close $f
+    return {}
+}
+
+# Turn an absolute path into one relative to the current directory
+proc make_relative {f} {
+    set elts [file split $f]
+    set here [file split [pwd]]
+    set ei 0
+    set hi 0
+    set res {}
+    foreach d $here {
+       if {$ei < $hi || $ei >= [llength $elts] || [lindex $elts $ei] ne $d} {
+           lappend res ".."
+       } else {
+           incr ei
+       }
+       incr hi
+    }
+    set elts [concat $res [lrange $elts $ei end]]
+    return [eval file join $elts]
+}
+
 proc external_blame {parent_idx {line {}}} {
-    global flist_menu_file
+    global flist_menu_file gitdir
     global nullid nullid2
     global parentlist selectedline currentid
 
@@ -3244,12 +3358,149 @@ proc external_blame {parent_idx {line {}}} {
     if {$line ne {} && $line > 1} {
        lappend cmdline "--line=$line"
     }
-    lappend cmdline $base_commit $flist_menu_file
+    set f [file join [file dirname $gitdir] $flist_menu_file]
+    # Unfortunately it seems git gui blame doesn't like
+    # being given an absolute path...
+    set f [make_relative $f]
+    lappend cmdline $base_commit $f
+    puts "cmdline={$cmdline}"
     if {[catch {eval exec $cmdline &} err]} {
        error_popup "[mc "git gui blame: command failed:"] $err"
     }
 }
 
+proc show_line_source {} {
+    global cmitmode currentid parents curview blamestuff blameinst
+    global diff_menu_line diff_menu_filebase flist_menu_file
+    global nullid nullid2 gitdir
+
+    set from_index {}
+    if {$cmitmode eq "tree"} {
+       set id $currentid
+       set line [expr {$diff_menu_line - $diff_menu_filebase}]
+    } else {
+       set h [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
+       if {$h eq {}} return
+       set pi [lindex $h 0]
+       if {$pi == 0} {
+           mark_ctext_line $diff_menu_line
+           return
+       }
+       incr pi -1
+       if {$currentid eq $nullid} {
+           if {$pi > 0} {
+               # must be a merge in progress...
+               if {[catch {
+                   # get the last line from .git/MERGE_HEAD
+                   set f [open [file join $gitdir MERGE_HEAD] r]
+                   set id [lindex [split [read $f] "\n"] end-1]
+                   close $f
+               } err]} {
+                   error_popup [mc "Couldn't read merge head: %s" $err]
+                   return
+               }
+           } elseif {$parents($curview,$currentid) eq $nullid2} {
+               # need to do the blame from the index
+               if {[catch {
+                   set from_index [index_sha1 $flist_menu_file]
+               } err]} {
+                   error_popup [mc "Error reading index: %s" $err]
+                   return
+               }
+           } else {
+               set id $parents($curview,$currentid)
+           }
+       } else {
+           set id [lindex $parents($curview,$currentid) $pi]
+       }
+       set line [lindex $h 1]
+    }
+    set blameargs {}
+    if {$from_index ne {}} {
+       lappend blameargs | git cat-file blob $from_index
+    }
+    lappend blameargs | git blame -p -L$line,+1
+    if {$from_index ne {}} {
+       lappend blameargs --contents -
+    } else {
+       lappend blameargs $id
+    }
+    lappend blameargs -- [file join [file dirname $gitdir] $flist_menu_file]
+    if {[catch {
+       set f [open $blameargs r]
+    } err]} {
+       error_popup [mc "Couldn't start git blame: %s" $err]
+       return
+    }
+    nowbusy blaming [mc "Searching"]
+    fconfigure $f -blocking 0
+    set i [reg_instance $f]
+    set blamestuff($i) {}
+    set blameinst $i
+    filerun $f [list read_line_source $f $i]
+}
+
+proc stopblaming {} {
+    global blameinst
+
+    if {[info exists blameinst]} {
+       stop_instance $blameinst
+       unset blameinst
+       notbusy blaming
+    }
+}
+
+proc read_line_source {fd inst} {
+    global blamestuff curview commfd blameinst nullid nullid2
+
+    while {[gets $fd line] >= 0} {
+       lappend blamestuff($inst) $line
+    }
+    if {![eof $fd]} {
+       return 1
+    }
+    unset commfd($inst)
+    unset blameinst
+    notbusy blaming
+    fconfigure $fd -blocking 1
+    if {[catch {close $fd} err]} {
+       error_popup [mc "Error running git blame: %s" $err]
+       return 0
+    }
+
+    set fname {}
+    set line [split [lindex $blamestuff($inst) 0] " "]
+    set id [lindex $line 0]
+    set lnum [lindex $line 1]
+    if {[string length $id] == 40 && [string is xdigit $id] &&
+       [string is digit -strict $lnum]} {
+       # look for "filename" line
+       foreach l $blamestuff($inst) {
+           if {[string match "filename *" $l]} {
+               set fname [string range $l 9 end]
+               break
+           }
+       }
+    }
+    if {$fname ne {}} {
+       # all looks good, select it
+       if {$id eq $nullid} {
+           # blame uses all-zeroes to mean not committed,
+           # which would mean a change in the index
+           set id $nullid2
+       }
+       if {[commitinview $id $curview]} {
+           selectline [rowofcommit $id] 1 [list $fname $lnum]
+       } else {
+           error_popup [mc "That line comes from commit %s, \
+                            which is not in this view" [shortids $id]]
+       }
+    } else {
+       puts "oops couldn't parse git blame output"
+    }
+    return 0
+}
+
 # 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} {}
@@ -3360,8 +3611,8 @@ proc shellsplit {str} {
 # Code to implement multiple views
 
 proc newview {ishighlight} {
-    global nextviewnum newviewname newviewperm newishighlight
-    global newviewargs revtreeargs viewargscmd newviewargscmd curview
+    global nextviewnum newviewname newishighlight
+    global revtreeargs viewargscmd newviewopts curview
 
     set newishighlight $ishighlight
     set top .gitkview
@@ -3370,58 +3621,183 @@ proc newview {ishighlight} {
        return
     }
     set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
-    set newviewperm($nextviewnum) 0
-    set newviewargs($nextviewnum) [shellarglist $revtreeargs]
-    set newviewargscmd($nextviewnum) $viewargscmd($curview)
+    set newviewopts($nextviewnum,perm) 0
+    set newviewopts($nextviewnum,cmd)  $viewargscmd($curview)
+    decode_view_opts $nextviewnum $revtreeargs
     vieweditor $top $nextviewnum [mc "Gitk view definition"]
 }
 
+set known_view_options {
+    {perm    b    . {}               {mc "Remember this view"}}
+    {args    t50= + {}               {mc "Commits to include (arguments to git log):"}}
+    {all     b    * "--all"          {mc "Use all refs"}}
+    {dorder  b    . {"--date-order" "-d"}      {mc "Strictly sort by date"}}
+    {lright  b    . "--left-right"   {mc "Mark branch sides"}}
+    {since   t15  + {"--since=*" "--after=*"}  {mc "Since date:"}}
+    {until   t15  . {"--until=*" "--before=*"} {mc "Until date:"}}
+    {limit   t10  + "--max-count=*"  {mc "Max count:"}}
+    {skip    t10  . "--skip=*"       {mc "Skip:"}}
+    {first   b    . "--first-parent" {mc "Limit to first parent"}}
+    {cmd     t50= + {}               {mc "Command to generate more commits to include:"}}
+    }
+
+proc encode_view_opts {n} {
+    global known_view_options newviewopts
+
+    set rargs [list]
+    foreach opt $known_view_options {
+       set patterns [lindex $opt 3]
+       if {$patterns eq {}} continue
+       set pattern [lindex $patterns 0]
+
+       set val $newviewopts($n,[lindex $opt 0])
+       
+       if {[lindex $opt 1] eq "b"} {
+           if {$val} {
+               lappend rargs $pattern
+           }
+       } else {
+           set val [string trim $val]
+           if {$val ne {}} {
+               set pfix [string range $pattern 0 end-1]
+               lappend rargs $pfix$val
+           }
+       }
+    }
+    return [concat $rargs [shellsplit $newviewopts($n,args)]]
+}
+
+proc decode_view_opts {n view_args} {
+    global known_view_options newviewopts
+
+    foreach opt $known_view_options {
+       if {[lindex $opt 1] eq "b"} {
+           set val 0
+       } else {
+           set val {}
+       }
+       set newviewopts($n,[lindex $opt 0]) $val
+    }
+    set oargs [list]
+    foreach arg $view_args {
+       if {[regexp -- {^-([0-9]+)$} $arg arg cnt]
+           && ![info exists found(limit)]} {
+           set newviewopts($n,limit) $cnt
+           set found(limit) 1
+           continue
+       }
+       catch { unset val }
+       foreach opt $known_view_options {
+           set id [lindex $opt 0]
+           if {[info exists found($id)]} continue
+           foreach pattern [lindex $opt 3] {
+               if {![string match $pattern $arg]} continue
+               if {[lindex $opt 1] ne "b"} {
+                   set size [string length $pattern]
+                   set val [string range $arg [expr {$size-1}] end]
+               } else {
+                   set val 1
+               }
+               set newviewopts($n,$id) $val
+               set found($id) 1
+               break
+           }
+           if {[info exists val]} break
+       }
+       if {[info exists val]} continue
+       lappend oargs $arg
+    }
+    set newviewopts($n,args) [shellarglist $oargs]
+}
+
+proc edit_or_newview {} {
+    global curview
+
+    if {$curview > 0} {
+       editview
+    } else {
+       newview 0
+    }
+}
+
 proc editview {} {
     global curview
-    global viewname viewperm newviewname newviewperm
-    global viewargs newviewargs viewargscmd newviewargscmd
+    global viewname viewperm newviewname newviewopts
+    global viewargs viewargscmd
 
     set top .gitkvedit-$curview
     if {[winfo exists $top]} {
        raise $top
        return
     }
-    set newviewname($curview) $viewname($curview)
-    set newviewperm($curview) $viewperm($curview)
-    set newviewargs($curview) [shellarglist $viewargs($curview)]
-    set newviewargscmd($curview) $viewargscmd($curview)
+    set newviewname($curview)      $viewname($curview)
+    set newviewopts($curview,perm) $viewperm($curview)
+    set newviewopts($curview,cmd)  $viewargscmd($curview)
+    decode_view_opts $curview $viewargs($curview)
     vieweditor $top $curview "Gitk: edit view $viewname($curview)"
 }
 
 proc vieweditor {top n title} {
-    global newviewname newviewperm viewfiles bgcolor
+    global newviewname newviewopts viewfiles bgcolor
+    global known_view_options
 
     toplevel $top
     wm title $top $title
+    make_transient $top .
+
+    # View name
+    frame $top.nfr
     label $top.nl -text [mc "Name"]
     entry $top.name -width 20 -textvariable newviewname($n)
-    grid $top.nl $top.name -sticky w -pady 5
-    checkbutton $top.perm -text [mc "Remember this view"] \
-       -variable newviewperm($n)
-    grid $top.perm - -pady 5 -sticky w
-    message $top.al -aspect 1000 \
-       -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 \
+    pack $top.nfr -in $top -fill x -pady 5 -padx 3
+    pack $top.nl -in $top.nfr -side left -padx {0 30}
+    pack $top.name -in $top.nfr -side left
+
+    # View options
+    set cframe $top.nfr
+    set cexpand 0
+    set cnt 0
+    foreach opt $known_view_options {
+       set id [lindex $opt 0]
+       set type [lindex $opt 1]
+       set flags [lindex $opt 2]
+       set title [eval [lindex $opt 4]]
+       set lxpad 0
+
+       if {$flags eq "+" || $flags eq "*"} {
+           set cframe $top.fr$cnt
+           incr cnt
+           frame $cframe
+           pack $cframe -in $top -fill x -pady 3 -padx 3
+           set cexpand [expr {$flags eq "*"}]
+       } else {
+           set lxpad 5
+       }
+
+       if {$type eq "b"} {
+           checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id)
+           pack $cframe.c_$id -in $cframe -side left \
+               -padx [list $lxpad 0] -expand $cexpand -anchor w
+       } elseif {[regexp {^t(\d+)$} $type type sz]} {
+           message $cframe.l_$id -aspect 1500 -text $title
+           entry $cframe.e_$id -width $sz -background $bgcolor \
+               -textvariable newviewopts($n,$id)
+           pack $cframe.l_$id -in $cframe -side left -padx [list $lxpad 0]
+           pack $cframe.e_$id -in $cframe -side left -expand 1 -fill x
+       } elseif {[regexp {^t(\d+)=$} $type type sz]} {
+           message $cframe.l_$id -aspect 1500 -text $title
+           entry $cframe.e_$id -width $sz -background $bgcolor \
+               -textvariable newviewopts($n,$id)
+           pack $cframe.l_$id -in $cframe -side top -pady [list 3 0] -anchor w
+           pack $cframe.e_$id -in $cframe -side top -fill x
+       }
+    }
+
+    # Path list
+    message $top.l -aspect 1500 \
        -text [mc "Enter files and directories to include, one per line:"]
-    grid $top.l - -sticky w
-    text $top.t -width 40 -height 10 -background $bgcolor -font uifont
+    pack $top.l -in $top -side top -pady [list 7 0] -anchor w -padx 3
+    text $top.t -width 40 -height 5 -background $bgcolor -font uifont
     if {[info exists viewfiles($n)]} {
        foreach f $viewfiles($n) {
            $top.t insert end $f
@@ -3430,14 +3806,19 @@ proc vieweditor {top n title} {
        $top.t delete {end - 1c} end
        $top.t mark set insert 0.0
     }
-    grid $top.t - -sticky ew -padx 5
+    pack $top.t -in $top -side top -pady [list 0 5] -fill both -expand 1 -padx 3
     frame $top.buts
     button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
+    button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1]
     button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
-    grid $top.buts.ok $top.buts.can
+    bind $top <Control-Return> [list newviewok $top $n]
+    bind $top <F5> [list newviewok $top $n 1]
+    bind $top <Escape> [list destroy $top]
+    grid $top.buts.ok $top.buts.apply $top.buts.can
     grid columnconfigure $top.buts 0 -weight 1 -uniform a
     grid columnconfigure $top.buts 1 -weight 1 -uniform a
-    grid $top.buts - -pady 10 -sticky ew
+    grid columnconfigure $top.buts 2 -weight 1 -uniform a
+    pack $top.buts -in $top -side top -fill x
     focus $top.t
 }
 
@@ -3458,17 +3839,15 @@ proc allviewmenus {n op args} {
     # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
 }
 
-proc newviewok {top n} {
+proc newviewok {top n {apply 0}} {
     global nextviewnum newviewperm newviewname newishighlight
     global viewname viewfiles viewperm selectedview curview
-    global viewargs newviewargs viewargscmd newviewargscmd viewhlmenu
+    global viewargs viewargscmd newviewopts viewhlmenu
 
     if {[catch {
-       set newargs [shellsplit $newviewargs($n)]
+       set newargs [encode_view_opts $n]
     } err]} {
-       error_popup "[mc "Error in commit selection arguments:"] $err"
-       wm raise $top
-       focus $top
+       error_popup "[mc "Error in commit selection arguments:"] $err" $top
        return
     }
     set files {}
@@ -3482,10 +3861,10 @@ proc newviewok {top n} {
        # creating a new view
        incr nextviewnum
        set viewname($n) $newviewname($n)
-       set viewperm($n) $newviewperm($n)
+       set viewperm($n) $newviewopts($n,perm)
        set viewfiles($n) $files
        set viewargs($n) $newargs
-       set viewargscmd($n) $newviewargscmd($n)
+       set viewargscmd($n) $newviewopts($n,cmd)
        addviewmenu $n
        if {!$newishighlight} {
            run showview $n
@@ -3494,7 +3873,7 @@ proc newviewok {top n} {
        }
     } else {
        # editing an existing view
-       set viewperm($n) $newviewperm($n)
+       set viewperm($n) $newviewopts($n,perm)
        if {$newviewname($n) ne $viewname($n)} {
            set viewname($n) $newviewname($n)
            doviewmenu .bar.view 5 [list showview $n] \
@@ -3503,15 +3882,16 @@ proc newviewok {top n} {
                # entryconf [list -label $viewname($n) -value $viewname($n)]
        }
        if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
-               $newviewargscmd($n) ne $viewargscmd($n)} {
+               $newviewopts($n,cmd) ne $viewargscmd($n)} {
            set viewfiles($n) $files
            set viewargs($n) $newargs
-           set viewargscmd($n) $newviewargscmd($n)
+           set viewargscmd($n) $newviewopts($n,cmd)
            if {$curview == $n} {
                run reloadcommits
            }
        }
     }
+    if {$apply} return
     catch {destroy $top}
 }
 
@@ -3662,28 +4042,31 @@ proc ishighlighted {id} {
     return 0
 }
 
-proc bolden {row font} {
-    global canv linehtag selectedline boldrows
+proc bolden {id font} {
+    global canv linehtag currentid boldids need_redisplay
 
-    lappend boldrows $row
-    $canv itemconf $linehtag($row) -font $font
-    if {$row == $selectedline} {
+    # need_redisplay = 1 means the display is stale and about to be redrawn
+    if {$need_redisplay} return
+    lappend boldids $id
+    $canv itemconf $linehtag($id) -font $font
+    if {[info exists currentid] && $id eq $currentid} {
        $canv delete secsel
-       set t [eval $canv create rect [$canv bbox $linehtag($row)] \
+       set t [eval $canv create rect [$canv bbox $linehtag($id)] \
                   -outline {{}} -tags secsel \
                   -fill [$canv cget -selectbackground]]
        $canv lower $t
     }
 }
 
-proc bolden_name {row font} {
-    global canv2 linentag selectedline boldnamerows
+proc bolden_name {id font} {
+    global canv2 linentag currentid boldnameids need_redisplay
 
-    lappend boldnamerows $row
-    $canv2 itemconf $linentag($row) -font $font
-    if {$row == $selectedline} {
+    if {$need_redisplay} return
+    lappend boldnameids $id
+    $canv2 itemconf $linentag($id) -font $font
+    if {[info exists currentid] && $id eq $currentid} {
        $canv2 delete secsel
-       set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
+       set t [eval $canv2 create rect [$canv2 bbox $linentag($id)] \
                   -outline {{}} -tags secsel \
                   -fill [$canv2 cget -selectbackground]]
        $canv2 lower $t
@@ -3691,17 +4074,17 @@ proc bolden_name {row font} {
 }
 
 proc unbolden {} {
-    global boldrows
+    global boldids
 
     set stillbold {}
-    foreach row $boldrows {
-       if {![ishighlighted [commitonrow $row]]} {
-           bolden $row mainfont
+    foreach id $boldids {
+       if {![ishighlighted $id]} {
+           bolden $id mainfont
        } else {
-           lappend stillbold $row
+           lappend stillbold $id
        }
     }
-    set boldrows $stillbold
+    set boldids $stillbold
 }
 
 proc addvhighlight {n} {
@@ -3742,7 +4125,7 @@ proc vhighlightmore {} {
            set row [rowofcommit $id]
            if {$r0 <= $row && $row <= $r1} {
                if {![highlighted $row]} {
-                   bolden $row mainfontbold
+                   bolden $id mainfontbold
                }
                set vhighlights($id) 1
            }
@@ -3757,7 +4140,7 @@ proc askvhighlight {row id} {
 
     if {[commitinview $id $hlview]} {
        if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
-           bolden $row mainfontbold
+           bolden $id mainfontbold
        }
        set vhighlights($id) 1
     } else {
@@ -3767,7 +4150,7 @@ proc askvhighlight {row id} {
 
 proc hfiles_change {} {
     global highlight_files filehighlight fhighlights fh_serial
-    global highlight_paths gdttype
+    global highlight_paths
 
     if {[info exists filehighlight]} {
        # delete previous highlights
@@ -3825,15 +4208,15 @@ proc find_change {name ix op} {
 }
 
 proc findcom_change args {
-    global nhighlights boldnamerows
+    global nhighlights boldnameids
     global findpattern findtype findstring gdttype
 
     stopfinding
     # delete previous highlights, if any
-    foreach row $boldnamerows {
-       bolden_name $row mainfont
+    foreach id $boldnameids {
+       bolden_name $id mainfont
     }
-    set boldnamerows {}
+    set boldnameids {}
     catch {unset nhighlights}
     unbolden
     unmarkmatches
@@ -3922,9 +4305,8 @@ proc readfhighlight {} {
        set fhl_list [lrange $fhl_list [expr {$i+1}] end]
        if {$line eq {}} continue
        if {![commitinview $line $curview]} continue
-       set row [rowofcommit $line]
        if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
-           bolden $row mainfontbold
+           bolden $line mainfontbold
        }
        set fhighlights($line) 1
     }
@@ -3976,9 +4358,9 @@ proc askfindhighlight {row id} {
     }
     if {$isbold && [info exists iddrawn($id)]} {
        if {![ishighlighted $id]} {
-           bolden $row mainfontbold
+           bolden $id mainfontbold
            if {$isbold > 1} {
-               bolden_name $row mainfontbold
+               bolden_name $id mainfontbold
            }
        }
        if {$markingmatches} {
@@ -3998,15 +4380,15 @@ proc markrowmatches {row id} {
     if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
        set m [findmatches $headline]
        if {$m ne {}} {
-           markmatches $canv $row $headline $linehtag($row) $m \
-               [$canv itemcget $linehtag($row) -font] $row
+           markmatches $canv $row $headline $linehtag($id) $m \
+               [$canv itemcget $linehtag($id) -font] $row
        }
     }
     if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
        set m [findmatches $author]
        if {$m ne {}} {
-           markmatches $canv2 $row $author $linentag($row) $m \
-               [$canv2 itemcget $linentag($row) -font] $row
+           markmatches $canv2 $row $author $linentag($id) $m \
+               [$canv2 itemcget $linentag($id) -font] $row
        }
     }
 }
@@ -4131,7 +4513,7 @@ proc askrelhighlight {row id} {
     }
     if {[info exists iddrawn($id)]} {
        if {$isbold && ![ishighlighted $id]} {
-           bolden $row mainfontbold
+           bolden $id mainfontbold
        }
     }
     set rhighlights($id) $isbold
@@ -4299,14 +4681,56 @@ proc layoutmore {} {
     drawvisible
 }
 
+# With path limiting, we mightn't get the actual HEAD commit,
+# so ask git rev-list what is the first ancestor of HEAD that
+# touches a file in the path limit.
+proc get_viewmainhead {view} {
+    global viewmainheadid vfilelimit viewinstances mainheadid
+
+    catch {
+       set rfd [open [concat | git rev-list -1 $mainheadid \
+                          -- $vfilelimit($view)] r]
+       set j [reg_instance $rfd]
+       lappend viewinstances($view) $j
+       fconfigure $rfd -blocking 0
+       filerun $rfd [list getviewhead $rfd $j $view]
+       set viewmainheadid($curview) {}
+    }
+}
+
+# git rev-list should give us just 1 line to use as viewmainheadid($view)
+proc getviewhead {fd inst view} {
+    global viewmainheadid commfd curview viewinstances showlocalchanges
+
+    set id {}
+    if {[gets $fd line] < 0} {
+       if {![eof $fd]} {
+           return 1
+       }
+    } elseif {[string length $line] == 40 && [string is xdigit $line]} {
+       set id $line
+    }
+    set viewmainheadid($view) $id
+    close $fd
+    unset commfd($inst)
+    set i [lsearch -exact $viewinstances($view) $inst]
+    if {$i >= 0} {
+       set viewinstances($view) [lreplace $viewinstances($view) $i $i]
+    }
+    if {$showlocalchanges && $id ne {} && $view == $curview} {
+       doshowlocalchanges
+    }
+    return 0
+}
+
 proc doshowlocalchanges {} {
-    global curview mainheadid
+    global curview viewmainheadid
 
-    if {$mainheadid eq {}} return
-    if {[commitinview $mainheadid $curview]} {
+    if {$viewmainheadid($curview) eq {}} return
+    if {[commitinview $viewmainheadid($curview) $curview]} {
        dodiffindex
     } else {
-       interestedin $mainheadid dodiffindex
+       interestedin $viewmainheadid($curview) dodiffindex
     }
 }
 
@@ -4324,19 +4748,24 @@ proc dohidelocalchanges {} {
 
 # spawn off a process to do git diff-index --cached HEAD
 proc dodiffindex {} {
-    global lserial showlocalchanges
+    global lserial showlocalchanges vfilelimit curview
     global isworktree
 
     if {!$showlocalchanges || !$isworktree} return
     incr lserial
-    set fd [open "|git diff-index --cached HEAD" r]
+    set cmd "|git diff-index --cached HEAD"
+    if {$vfilelimit($curview) ne {}} {
+       set cmd [concat $cmd -- $vfilelimit($curview)]
+    }
+    set fd [open $cmd r]
     fconfigure $fd -blocking 0
     set i [reg_instance $fd]
     filerun $fd [list readdiffindex $fd $lserial $i]
 }
 
 proc readdiffindex {fd serial inst} {
-    global mainheadid nullid nullid2 curview commitinfo commitdata lserial
+    global viewmainheadid nullid nullid2 curview commitinfo commitdata lserial
+    global vfilelimit
 
     set isdiff 1
     if {[gets $fd line] < 0} {
@@ -4353,7 +4782,11 @@ proc readdiffindex {fd serial inst} {
     }
 
     # now see if there are any local changes not checked in to the index
-    set fd [open "|git diff-files" r]
+    set cmd "|git diff-files"
+    if {$vfilelimit($curview) ne {}} {
+       set cmd [concat $cmd -- $vfilelimit($curview)]
+    }
+    set fd [open $cmd r]
     fconfigure $fd -blocking 0
     set i [reg_instance $fd]
     filerun $fd [list readdifffiles $fd $serial $i]
@@ -4366,15 +4799,18 @@ proc readdiffindex {fd serial inst} {
        if {[commitinview $nullid $curview]} {
            removefakerow $nullid
        }
-       insertfakerow $nullid2 $mainheadid
+       insertfakerow $nullid2 $viewmainheadid($curview)
     } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
+       if {[commitinview $nullid $curview]} {
+           removefakerow $nullid
+       }
        removefakerow $nullid2
     }
     return 0
 }
 
 proc readdifffiles {fd serial inst} {
-    global mainheadid nullid nullid2 curview
+    global viewmainheadid nullid nullid2 curview
     global commitinfo commitdata lserial
 
     set isdiff 1
@@ -4399,7 +4835,7 @@ proc readdifffiles {fd serial inst} {
        if {[commitinview $nullid2 $curview]} {
            set p $nullid2
        } else {
-           set p $mainheadid
+           set p $viewmainheadid($curview)
        }
        insertfakerow $nullid $p
     } elseif {!$isdiff && [commitinview $nullid $curview]} {
@@ -5124,7 +5560,7 @@ 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
+    global canvxmax boldids boldnameids 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
@@ -5189,22 +5625,22 @@ proc drawcmittext {id row col} {
     set nfont mainfont
     set isbold [ishighlighted $id]
     if {$isbold > 0} {
-       lappend boldrows $row
+       lappend boldids $id
        set font mainfontbold
        if {$isbold > 1} {
-           lappend boldnamerows $row
+           lappend boldnameids $id
            set nfont mainfontbold
        }
     }
-    set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
-                           -text $headline -font $font -tags text]
-    $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 linehtag($id) [$canv create text $xt $y -anchor w -fill $fgcolor \
+                          -text $headline -font $font -tags text]
+    $canv bind $linehtag($id) $ctxbut "rowmenu %X %Y $id"
+    set linentag($id) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
+                          -text $name -font $nfont -tags text]
+    set linedtag($id) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
+                          -text $date -font mainfont -tags text]
     if {$selectedline == $row} {
-       make_secsel $row
+       make_secsel $id
     }
     set xr [expr {$xt + [font measure $font $headline]}]
     if {$xr > $canvxmax} {
@@ -5412,7 +5848,7 @@ proc drawvisible {} {
 proc clear_display {} {
     global iddrawn linesegs need_redisplay nrows_drawn
     global vhighlights fhighlights nhighlights rhighlights
-    global linehtag linentag linedtag boldrows boldnamerows
+    global linehtag linentag linedtag boldids boldnameids
 
     allcanvs delete all
     catch {unset iddrawn}
@@ -5420,8 +5856,8 @@ proc clear_display {} {
     catch {unset linehtag}
     catch {unset linentag}
     catch {unset linedtag}
-    set boldrows {}
-    set boldnamerows {}
+    set boldids {}
+    set boldnameids {}
     catch {unset vhighlights}
     catch {unset fhighlights}
     catch {unset nhighlights}
@@ -5742,6 +6178,7 @@ proc stopfinding {} {
        set fprogcoord 0
        adjustprogress
     }
+    stopblaming
 }
 
 proc findmore {} {
@@ -5878,10 +6315,11 @@ proc findmore {} {
 proc findselectline {l} {
     global findloc commentend ctext findcurline markingmatches gdttype
 
-    set markingmatches 1
+    set markingmatches [expr {$gdttype eq [mc "containing:"]}]
     set findcurline $l
     selectline $l 1
-    if {$findloc == [mc "All fields"] || $findloc == [mc "Comments"]} {
+    if {$markingmatches &&
+       ($findloc eq [mc "All fields"] || $findloc eq [mc "Comments"])} {
        # highlight the matches in the comments
        set f [$ctext get 1.0 $commentend]
        set matches [findmatches $f]
@@ -6128,25 +6566,25 @@ proc dispnexttag {} {
     }
 }
 
-proc make_secsel {l} {
+proc make_secsel {id} {
     global linehtag linentag linedtag canv canv2 canv3
 
-    if {![info exists linehtag($l)]} return
+    if {![info exists linehtag($id)]} return
     $canv delete secsel
-    set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
+    set t [eval $canv create rect [$canv bbox $linehtag($id)] -outline {{}} \
               -tags secsel -fill [$canv cget -selectbackground]]
     $canv lower $t
     $canv2 delete secsel
-    set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
+    set t [eval $canv2 create rect [$canv2 bbox $linentag($id)] -outline {{}} \
               -tags secsel -fill [$canv2 cget -selectbackground]]
     $canv2 lower $t
     $canv3 delete secsel
-    set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
+    set t [eval $canv3 create rect [$canv3 bbox $linedtag($id)] -outline {{}} \
               -tags secsel -fill [$canv3 cget -selectbackground]]
     $canv3 lower $t
 }
 
-proc selectline {l isnew} {
+proc selectline {l isnew {desired_loc {}}} {
     global canv ctext commitinfo selectedline
     global canvy0 linespc parents children curview
     global currentid sha1entry
@@ -6154,7 +6592,7 @@ proc selectline {l isnew} {
     global mergemax numcommits pending_select
     global cmitmode showneartags allcommits
     global targetrow targetid lastscrollrows
-    global autoselect
+    global autoselect jump_to_here
 
     catch {unset pending_select}
     $canv delete hover
@@ -6207,7 +6645,7 @@ proc selectline {l isnew} {
        drawvisible
     }
 
-    make_secsel $l
+    make_secsel $id
 
     if {$isnew} {
        addtohistory [list selbyid $id]
@@ -6293,6 +6731,7 @@ proc selectline {l isnew} {
     $ctext conf -state disabled
     set commentend [$ctext index "end - 1c"]
 
+    set jump_to_here $desired_loc
     init_flist [mc "Comments"]
     if {$cmitmode eq "tree"} {
        gettree $id
@@ -6540,121 +6979,43 @@ proc getblobline {bf id} {
        $ctext insert end "$line\n"
     }
     if {[eof $bf]} {
+       global jump_to_here ctext_file_names commentend
+
        # delete last newline
        $ctext delete "end - 2c" "end - 1c"
        close $bf
+       if {$jump_to_here ne {} &&
+           [lindex $jump_to_here 0] eq [lindex $ctext_file_names 0]} {
+           set lnum [expr {[lindex $jump_to_here 1] +
+                           [lindex [split $commentend .] 0]}]
+           mark_ctext_line $lnum
+       }
        return 0
     }
     $ctext config -state disabled
     return [expr {$nl >= 1000? 2: 1}]
 }
 
+proc mark_ctext_line {lnum} {
+    global ctext markbgcolor
+
+    $ctext tag delete omark
+    $ctext tag add omark $lnum.0 "$lnum.0 + 1 line"
+    $ctext tag conf omark -background $markbgcolor
+    $ctext see $lnum.0
+}
+
 proc mergediff {id} {
-    global diffmergeid mdifffd
+    global diffmergeid
     global diffids treediffs
-    global parents
-    global diffcontext
-    global diffencoding
-    global limitdiffs vfilelimit curview
+    global parents curview
 
     set diffmergeid $id
     set diffids $id
     set treediffs($id) {}
-    # this doesn't seem to actually affect anything...
-    set cmd [concat | git diff-tree --no-commit-id --cc -U$diffcontext $id]
-    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"
-       return
-    }
-    fconfigure $mdf -blocking 0 -encoding binary
-    set mdifffd($id) $mdf
     set np [llength $parents($curview,$id)]
-    set diffencoding [get_path_encoding {}]
     settabs $np
-    filerun $mdf [list getmergediffline $mdf $id $np]
-}
-
-proc getmergediffline {mdf id np} {
-    global diffmergeid ctext cflist mergemax
-    global difffilestart mdifffd treediffs
-    global ctext_file_names ctext_file_lines
-    global diffencoding
-
-    $ctext conf -state normal
-    set nr 0
-    while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
-       if {![info exists diffmergeid] || $id != $diffmergeid
-           || $mdf != $mdifffd($id)} {
-           close $mdf
-           return 0
-       }
-       if {[regexp {^diff --cc (.*)} $line match fname]} {
-           # start of a new file
-           set fname [encoding convertfrom $fname]
-           $ctext insert end "\n"
-           set here [$ctext index "end - 1c"]
-           lappend difffilestart $here
-           lappend treediffs($id) $fname
-           add_flist [list $fname]
-           lappend ctext_file_names $fname
-           lappend ctext_file_lines [lindex [split $here "."] 0]
-           set diffencoding [get_path_encoding $fname]
-           set l [expr {(78 - [string length $fname]) / 2}]
-           set pad [string range "----------------------------------------" 1 $l]
-           $ctext insert end "$pad $fname $pad\n" filesep
-       } elseif {[regexp {^@@} $line]} {
-           set line [encoding convertfrom $diffencoding $line]
-           $ctext insert end "$line\n" hunksep
-       } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
-           # do nothing
-       } else {
-           set line [encoding convertfrom $diffencoding $line]
-           # parse the prefix - one ' ', '-' or '+' for each parent
-           set spaces {}
-           set minuses {}
-           set pluses {}
-           set isbad 0
-           for {set j 0} {$j < $np} {incr j} {
-               set c [string range $line $j $j]
-               if {$c == " "} {
-                   lappend spaces $j
-               } elseif {$c == "-"} {
-                   lappend minuses $j
-               } elseif {$c == "+"} {
-                   lappend pluses $j
-               } else {
-                   set isbad 1
-                   break
-               }
-           }
-           set tags {}
-           set num {}
-           if {!$isbad && $minuses ne {} && $pluses eq {}} {
-               # line doesn't appear in result, parents in $minuses have the line
-               set num [lindex $minuses 0]
-           } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
-               # line appears in result, parents in $pluses don't have the line
-               lappend tags mresult
-               set num [lindex $spaces 0]
-           }
-           if {$num ne {}} {
-               if {$num >= $mergemax} {
-                   set num "max"
-               }
-               lappend tags m$num
-           }
-           $ctext insert end "$line\n" $tags
-       }
-    }
-    $ctext conf -state disabled
-    if {[eof $mdf]} {
-       close $mdf
-       return 0
-    }
-    return [expr {$nr >= 1000? 2: 1}]
+    getblobdiffs $id
 }
 
 proc startdiff {ids} {
@@ -6773,8 +7134,10 @@ proc gettreediffline {gdtf ids} {
                set file [lindex $file 0]
            }
            set file [encoding convertfrom $file]
-           lappend treediff $file
-           lappend sublist $file
+           if {$file ne [lindex $treediff end]} {
+               lappend treediff $file
+               lappend sublist $file
+           }
        }
     }
     if {$perfile_attrs} {
@@ -6796,7 +7159,7 @@ proc gettreediffline {gdtf ids} {
        set treediffs($ids) $treediff
     }
     unset treepending
-    if {$cmitmode eq "tree"} {
+    if {$cmitmode eq "tree" && [llength $diffids] == 1} {
        gettree $diffids
     } elseif {$ids != $diffids} {
        if {![info exists diffmergeid]} {
@@ -6834,9 +7197,9 @@ proc getblobdiffs {ids} {
     global diffcontext
     global ignorespace
     global limitdiffs vfilelimit curview
-    global diffencoding
+    global diffencoding targetline diffnparents
 
-    set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
+    set cmd [diffcmd $ids "-p -C --cc --no-commit-id -U$diffcontext"]
     if {$ignorespace} {
        append cmd " -w"
     }
@@ -6844,9 +7207,11 @@ proc getblobdiffs {ids} {
        set cmd [concat $cmd -- $vfilelimit($curview)]
     }
     if {[catch {set bdf [open $cmd r]} err]} {
-       puts "error getting diffs: $err"
+       error_popup [mc "Error getting diffs: %s" $err]
        return
     }
+    set targetline {}
+    set diffnparents 0
     set diffinhdr 0
     set diffencoding [get_path_encoding {}]
     fconfigure $bdf -blocking 0 -encoding binary
@@ -6868,25 +7233,32 @@ proc setinlist {var i val} {
 }
 
 proc makediffhdr {fname ids} {
-    global ctext curdiffstart treediffs
-    global ctext_file_names
+    global ctext curdiffstart treediffs diffencoding
+    global ctext_file_names jump_to_here targetline diffline
 
+    set fname [encoding convertfrom $fname]
+    set diffencoding [get_path_encoding $fname]
     set i [lsearch -exact $treediffs($ids) $fname]
     if {$i >= 0} {
        setinlist difffilestart $i $curdiffstart
     }
-    set ctext_file_names [lreplace $ctext_file_names end end $fname]
+    lset ctext_file_names end $fname
     set l [expr {(78 - [string length $fname]) / 2}]
     set pad [string range "----------------------------------------" 1 $l]
     $ctext insert $curdiffstart "$pad $fname $pad" filesep
+    set targetline {}
+    if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
+       set targetline [lindex $jump_to_here 1]
+    }
+    set diffline 0
 }
 
 proc getblobdiffline {bdf ids} {
     global diffids blobdifffd ctext curdiffstart
     global diffnexthead diffnextnote difffilestart
     global ctext_file_names ctext_file_lines
-    global diffinhdr treediffs
-    global diffencoding
+    global diffinhdr treediffs mergemax diffnparents
+    global diffencoding jump_to_here targetline diffline
 
     set nr 0
     $ctext conf -state normal
@@ -6895,45 +7267,74 @@ proc getblobdiffline {bdf ids} {
            close $bdf
            return 0
        }
-       if {![string compare -length 11 "diff --git " $line]} {
-           # trim off "diff --git "
-           set line [string range $line 11 end]
-           set diffinhdr 1
+       if {![string compare -length 5 "diff " $line]} {
+           if {![regexp {^diff (--cc|--git) } $line m type]} {
+               set line [encoding convertfrom $line]
+               $ctext insert end "$line\n" hunksep
+               continue
+           }
            # start of a new file
+           set diffinhdr 1
            $ctext insert end "\n"
            set curdiffstart [$ctext index "end - 1c"]
            lappend ctext_file_names ""
            lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
            $ctext insert end "\n" filesep
-           # If the name hasn't changed the length will be odd,
-           # the middle char will be a space, and the two bits either
-           # side will be a/name and b/name, or "a/name" and "b/name".
-           # If the name has changed we'll get "rename from" and
-           # "rename to" or "copy from" and "copy to" lines following this,
-           # and we'll use them to get the filenames.
-           # This complexity is necessary because spaces in the filename(s)
-           # don't get escaped.
-           set l [string length $line]
-           set i [expr {$l / 2}]
-           if {!(($l & 1) && [string index $line $i] eq " " &&
-                 [string range $line 2 [expr {$i - 1}]] eq \
-                     [string range $line [expr {$i + 3}] end])} {
-               continue
-           }
-           # unescape if quoted and chop off the a/ from the front
-           if {[string index $line 0] eq "\""} {
-               set fname [string range [lindex $line 0] 2 end]
+
+           if {$type eq "--cc"} {
+               # start of a new file in a merge diff
+               set fname [string range $line 10 end]
+               if {[lsearch -exact $treediffs($ids) $fname] < 0} {
+                   lappend treediffs($ids) $fname
+                   add_flist [list $fname]
+               }
+
            } else {
-               set fname [string range $line 2 [expr {$i - 1}]]
+               set line [string range $line 11 end]
+               # If the name hasn't changed the length will be odd,
+               # the middle char will be a space, and the two bits either
+               # side will be a/name and b/name, or "a/name" and "b/name".
+               # If the name has changed we'll get "rename from" and
+               # "rename to" or "copy from" and "copy to" lines following
+               # this, and we'll use them to get the filenames.
+               # This complexity is necessary because spaces in the
+               # filename(s) don't get escaped.
+               set l [string length $line]
+               set i [expr {$l / 2}]
+               if {!(($l & 1) && [string index $line $i] eq " " &&
+                     [string range $line 2 [expr {$i - 1}]] eq \
+                         [string range $line [expr {$i + 3}] end])} {
+                   continue
+               }
+               # unescape if quoted and chop off the a/ from the front
+               if {[string index $line 0] eq "\""} {
+                   set fname [string range [lindex $line 0] 2 end]
+               } else {
+                   set fname [string range $line 2 [expr {$i - 1}]]
+               }
            }
-           set fname [encoding convertfrom $fname]
-           set diffencoding [get_path_encoding $fname]
            makediffhdr $fname $ids
 
-       } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
-                      $line match f1l f1c f2l f2c rest]} {
+       } elseif {![string compare -length 16 "* Unmerged path " $line]} {
+           set fname [encoding convertfrom [string range $line 16 end]]
+           $ctext insert end "\n"
+           set curdiffstart [$ctext index "end - 1c"]
+           lappend ctext_file_names $fname
+           lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
+           $ctext insert end "$line\n" filesep
+           set i [lsearch -exact $treediffs($ids) $fname]
+           if {$i >= 0} {
+               setinlist difffilestart $i $curdiffstart
+           }
+
+       } elseif {![string compare -length 2 "@@" $line]} {
+           regexp {^@@+} $line ats
            set line [encoding convertfrom $diffencoding $line]
            $ctext insert end "$line\n" hunksep
+           if {[regexp { \+(\d+),\d+ @@} $line m nl]} {
+               set diffline $nl
+           }
+           set diffnparents [expr {[string length $ats] - 1}]
            set diffinhdr 0
 
        } elseif {$diffinhdr} {
@@ -6953,8 +7354,6 @@ proc getblobdiffline {bdf ids} {
                if {[string index $fname 0] eq "\""} {
                    set fname [lindex $fname 0]
                }
-               set fname [encoding convertfrom $fname]
-               set diffencoding [get_path_encoding $fname]
                makediffhdr $fname $ids
            } elseif {[string compare -length 3 $line "---"] == 0} {
                # do nothing
@@ -6967,12 +7366,43 @@ proc getblobdiffline {bdf ids} {
 
        } else {
            set line [encoding convertfrom $diffencoding $line]
-           set x [string range $line 0 0]
-           if {$x == "-" || $x == "+"} {
-               set tag [expr {$x == "+"}]
-               $ctext insert end "$line\n" d$tag
-           } elseif {$x == " "} {
-               $ctext insert end "$line\n"
+           # parse the prefix - one ' ', '-' or '+' for each parent
+           set prefix [string range $line 0 [expr {$diffnparents - 1}]]
+           set tag [expr {$diffnparents > 1? "m": "d"}]
+           if {[string trim $prefix " -+"] eq {}} {
+               # prefix only has " ", "-" and "+" in it: normal diff line
+               set num [string first "-" $prefix]
+               if {$num >= 0} {
+                   # removed line, first parent with line is $num
+                   if {$num >= $mergemax} {
+                       set num "max"
+                   }
+                   $ctext insert end "$line\n" $tag$num
+               } else {
+                   set tags {}
+                   if {[string first "+" $prefix] >= 0} {
+                       # added line
+                       lappend tags ${tag}result
+                       if {$diffnparents > 1} {
+                           set num [string first " " $prefix]
+                           if {$num >= 0} {
+                               if {$num >= $mergemax} {
+                                   set num "max"
+                               }
+                               lappend tags m$num
+                           }
+                       }
+                   }
+                   if {$targetline ne {}} {
+                       if {$diffline == $targetline} {
+                           set seehere [$ctext index "end - 1 chars"]
+                           set targetline {}
+                       } else {
+                           incr diffline
+                       }
+                   }
+                   $ctext insert end "$line\n" $tags
+               }
            } else {
                # "\ No newline at end of file",
                # or something else we don't recognize
@@ -6980,6 +7410,9 @@ proc getblobdiffline {bdf ids} {
            }
        }
     }
+    if {[info exists seehere]} {
+       mark_ctext_line [lindex [split $seehere .] 0]
+    }
     $ctext conf -state disabled
     if {[eof $bdf]} {
        close $bdf
@@ -6992,7 +7425,7 @@ proc changediffdisp {} {
     global ctext diffelide
 
     $ctext tag conf d0 -elide [lindex $diffelide 0]
-    $ctext tag conf d1 -elide [lindex $diffelide 1]
+    $ctext tag conf dresult -elide [lindex $diffelide 1]
 }
 
 proc highlightfile {loc cline} {
@@ -7547,7 +7980,7 @@ proc rowmenu {x y id} {
     if {$id ne $nullid && $id ne $nullid2} {
        set menu $rowctxmenu
        if {$mainhead ne {}} {
-           $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
+           $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead] -state normal
        } else {
            $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
        }
@@ -7610,6 +8043,7 @@ proc mkpatch {} {
     set patchtop $top
     catch {destroy $top}
     toplevel $top
+    make_transient $top .
     label $top.title -text [mc "Generate patch"]
     grid $top.title - -pady 10
     label $top.from -text [mc "From:"]
@@ -7640,6 +8074,8 @@ proc mkpatch {} {
     frame $top.buts
     button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
     button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
+    bind $top <Key-Return> mkpatchgo
+    bind $top <Key-Escape> mkpatchcan
     grid $top.buts.gen $top.buts.can
     grid columnconfigure $top.buts 0 -weight 1 -uniform a
     grid columnconfigure $top.buts 1 -weight 1 -uniform a
@@ -7674,7 +8110,7 @@ proc mkpatchgo {} {
     set cmd [lrange $cmd 1 end]
     lappend cmd >$fname &
     if {[catch {eval exec $cmd} err]} {
-       error_popup "[mc "Error creating patch:"] $err"
+       error_popup "[mc "Error creating patch:"] $err" $patchtop
     }
     catch {destroy $patchtop}
     unset patchtop
@@ -7694,6 +8130,7 @@ proc mktag {} {
     set mktagtop $top
     catch {destroy $top}
     toplevel $top
+    make_transient $top .
     label $top.title -text [mc "Create tag"]
     grid $top.title - -pady 10
     label $top.id -text [mc "ID:"]
@@ -7711,6 +8148,8 @@ proc mktag {} {
     frame $top.buts
     button $top.buts.gen -text [mc "Create"] -command mktaggo
     button $top.buts.can -text [mc "Cancel"] -command mktagcan
+    bind $top <Key-Return> mktaggo
+    bind $top <Key-Escape> mktagcan
     grid $top.buts.gen $top.buts.can
     grid columnconfigure $top.buts 0 -weight 1 -uniform a
     grid columnconfigure $top.buts 1 -weight 1 -uniform a
@@ -7724,18 +8163,18 @@ proc domktag {} {
     set id [$mktagtop.sha1 get]
     set tag [$mktagtop.tag get]
     if {$tag == {}} {
-       error_popup [mc "No tag name specified"]
-       return
+       error_popup [mc "No tag name specified"] $mktagtop
+       return 0
     }
     if {[info exists tagids($tag)]} {
-       error_popup [mc "Tag \"%s\" already exists" $tag]
-       return
+       error_popup [mc "Tag \"%s\" already exists" $tag] $mktagtop
+       return 0
     }
     if {[catch {
        exec git tag $tag $id
     } err]} {
-       error_popup "[mc "Error creating tag:"] $err"
-       return
+       error_popup "[mc "Error creating tag:"] $err" $mktagtop
+       return 0
     }
 
     set tagids($tag) $id
@@ -7744,6 +8183,7 @@ proc domktag {} {
     addedtag $id
     dispneartags 0
     run refill_reflist
+    return 1
 }
 
 proc redrawtags {id} {
@@ -7761,16 +8201,16 @@ proc redrawtags {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]
-    set text [$canv itemcget $linehtag($row) -text]
-    set font [$canv itemcget $linehtag($row) -font]
+    $canv coords $linehtag($id) $xt [lindex $idpos($id) 2]
+    set text [$canv itemcget $linehtag($id) -text]
+    set font [$canv itemcget $linehtag($id) -font]
     set xr [expr {$xt + [font measure $font $text]}]
     if {$xr > $canvxmax} {
        set canvxmax $xr
        setcanvscroll
     }
     if {[info exists currentid] && $currentid == $id} {
-       make_secsel $row
+       make_secsel $id
     }
 }
 
@@ -7782,7 +8222,7 @@ proc mktagcan {} {
 }
 
 proc mktaggo {} {
-    domktag
+    if {![domktag]} return
     mktagcan
 }
 
@@ -7793,6 +8233,7 @@ proc writecommit {} {
     set wrcomtop $top
     catch {destroy $top}
     toplevel $top
+    make_transient $top .
     label $top.title -text [mc "Write commit to file"]
     grid $top.title - -pady 10
     label $top.id -text [mc "ID:"]
@@ -7814,6 +8255,8 @@ proc writecommit {} {
     frame $top.buts
     button $top.buts.gen -text [mc "Write"] -command wrcomgo
     button $top.buts.can -text [mc "Cancel"] -command wrcomcan
+    bind $top <Key-Return> wrcomgo
+    bind $top <Key-Escape> wrcomcan
     grid $top.buts.gen $top.buts.can
     grid columnconfigure $top.buts 0 -weight 1 -uniform a
     grid columnconfigure $top.buts 1 -weight 1 -uniform a
@@ -7828,7 +8271,7 @@ proc wrcomgo {} {
     set cmd "echo $id | [$wrcomtop.cmd get]"
     set fname [$wrcomtop.fname get]
     if {[catch {exec sh -c $cmd >$fname &} err]} {
-       error_popup "[mc "Error writing commit:"] $err"
+       error_popup "[mc "Error writing commit:"] $err" $wrcomtop
     }
     catch {destroy $wrcomtop}
     unset wrcomtop
@@ -7847,6 +8290,7 @@ proc mkbranch {} {
     set top .makebranch
     catch {destroy $top}
     toplevel $top
+    make_transient $top .
     label $top.title -text [mc "Create new branch"]
     grid $top.title - -pady 10
     label $top.id -text [mc "ID:"]
@@ -7856,11 +8300,12 @@ proc mkbranch {} {
     grid $top.id $top.sha1 -sticky w
     label $top.nlab -text [mc "Name:"]
     entry $top.name -width 40
-    bind $top.name <Key-Return> "[list mkbrgo $top]"
     grid $top.nlab $top.name -sticky w
     frame $top.buts
     button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
     button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
+    bind $top <Key-Return> [list mkbrgo $top]
+    bind $top <Key-Escape> "catch {destroy $top}"
     grid $top.buts.go $top.buts.can
     grid columnconfigure $top.buts 0 -weight 1 -uniform a
     grid columnconfigure $top.buts 1 -weight 1 -uniform a
@@ -7876,12 +8321,12 @@ proc mkbrgo {top} {
     set cmdargs {}
     set old_id {}
     if {$name eq {}} {
-       error_popup [mc "Please specify a name for the new branch"]
+       error_popup [mc "Please specify a name for the new branch"] $top
        return
     }
     if {[info exists headids($name)]} {
        if {![confirm_popup [mc \
-               "Branch '%s' already exists. Overwrite?" $name]]} {
+               "Branch '%s' already exists. Overwrite?" $name] $top]} {
            return
        }
        set old_id $headids($name)
@@ -7914,6 +8359,32 @@ proc mkbrgo {top} {
     }
 }
 
+proc exec_citool {tool_args {baseid {}}} {
+    global commitinfo env
+
+    set save_env [array get env GIT_AUTHOR_*]
+
+    if {$baseid ne {}} {
+       if {![info exists commitinfo($baseid)]} {
+           getcommit $baseid
+       }
+       set author [lindex $commitinfo($baseid) 1]
+       set date [lindex $commitinfo($baseid) 2]
+       if {[regexp {^\s*(\S.*\S|\S)\s*<(.*)>\s*$} \
+                   $author author name email]
+           && $date ne {}} {
+           set env(GIT_AUTHOR_NAME) $name
+           set env(GIT_AUTHOR_EMAIL) $email
+           set env(GIT_AUTHOR_DATE) $date
+       }
+    }
+
+    eval exec git citool $tool_args &
+
+    array unset env GIT_AUTHOR_*
+    array set env $save_env
+}
+
 proc cherrypick {} {
     global rowmenuid curview
     global mainhead mainheadid
@@ -7932,7 +8403,26 @@ proc cherrypick {} {
     # no error occurs, and exec takes that as an indication of error...
     if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
        notbusy cherrypick
-       error_popup $err
+       if {[regexp -line \
+                {Entry '(.*)' (would be overwritten by merge|not uptodate)} \
+                $err msg fname]} {
+           error_popup [mc "Cherry-pick failed because of local changes\
+                       to file '%s'.\nPlease commit, reset or stash\
+                       your changes and try again." $fname]
+       } elseif {[regexp -line \
+                      {^(CONFLICT \(.*\):|Automatic cherry-pick failed)} \
+                      $err]} {
+           if {[confirm_popup [mc "Cherry-pick failed because of merge\
+                       conflict.\nDo you wish to run git citool to\
+                       resolve it?"]]} {
+               # Force citool to read MERGE_MSG
+               file delete [file join [gitdir] "GITGUI_MSG"]
+               exec_citool {} $rowmenuid
+           }
+       } else {
+           error_popup $err
+       }
+       run updatecommits
        return
     }
     set newhead [exec git rev-parse HEAD]
@@ -7943,6 +8433,7 @@ proc cherrypick {} {
     }
     addnewchild $newhead $oldhead
     if {[commitinview $oldhead $curview]} {
+       # XXX this isn't right if we have a path limit...
        insertrow $newhead $oldhead $curview
        if {$mainhead ne {}} {
            movehead $newhead $mainhead
@@ -7962,7 +8453,7 @@ proc resethead {} {
     set confirm_ok 0
     set w ".confirmreset"
     toplevel $w
-    wm transient $w .
+    make_transient $w .
     wm title $w [mc "Confirm reset"]
     message $w.m -text \
        [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \
@@ -7985,6 +8476,7 @@ proc resethead {} {
     button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
     pack $w.ok -side left -fill x -padx 20 -pady 20
     button $w.cancel -text [mc Cancel] -command "destroy $w"
+    bind $w <Key-Escape> [list destroy $w]
     pack $w.cancel -side right -fill x -padx 20 -pady 20
     bind $w <Visibility> "grab $w; focus $w"
     tkwait window $w
@@ -8049,7 +8541,7 @@ proc headmenu {x y id head} {
 
 proc cobranch {} {
     global headmenuid headmenuhead headids
-    global showlocalchanges mainheadid
+    global showlocalchanges
 
     # check the tree is clean first??
     nowbusy checkout [mc "Checking out"]
@@ -8070,6 +8562,7 @@ proc cobranch {} {
 
 proc readcheckoutstat {fd newhead newheadid} {
     global mainhead mainheadid headids showlocalchanges progresscoords
+    global viewmainheadid curview
 
     if {[gets $fd line] >= 0} {
        if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
@@ -8087,6 +8580,7 @@ proc readcheckoutstat {fd newhead newheadid} {
     set oldmainid $mainheadid
     set mainhead $newhead
     set mainheadid $newheadid
+    set viewmainheadid($curview) $newheadid
     redrawtags $oldmainid
     redrawtags $newheadid
     selbyid $newheadid
@@ -8141,6 +8635,7 @@ proc showrefs {} {
     }
     toplevel $top
     wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
+    make_transient $top .
     text $top.list -background $bgcolor -foreground $fgcolor \
        -selectbackground $selectbgcolor -font mainfont \
        -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
@@ -8162,6 +8657,7 @@ proc showrefs {} {
     pack $top.f.l -side left
     grid $top.f - -sticky ew -pady 2
     button $top.close -command [list destroy $top] -text [mc "Close"]
+    bind $top <Key-Escape> [list destroy $top]
     grid $top.close -
     grid columnconfigure $top 0 -weight 1
     grid rowconfigure $top 0 -weight 1
@@ -9467,6 +9963,7 @@ proc mkfontdisp {font top which} {
 
 proc choosefont {font which} {
     global fontparam fontlist fonttop fontattr
+    global prefstop
 
     set fontparam(which) $which
     set fontparam(font) $font
@@ -9480,6 +9977,7 @@ proc choosefont {font which} {
        font create sample
        eval font config sample [font actual $font]
        toplevel $top
+       make_transient $top $prefstop
        wm title $top [mc "Gitk font chooser"]
        label $top.l -textvariable fontparam(which)
        pack $top.l -side top
@@ -9513,6 +10011,8 @@ proc choosefont {font which} {
        frame $top.buts
        button $top.buts.ok -text [mc "OK"] -command fontok -default active
        button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
+       bind $top <Key-Return> fontok
+       bind $top <Key-Escape> fontcan
        grid $top.buts.ok $top.buts.can
        grid columnconfigure $top.buts 0 -weight 1 -uniform a
        grid columnconfigure $top.buts 1 -weight 1 -uniform a
@@ -9579,7 +10079,7 @@ proc chg_fontparam {v sub op} {
 proc doprefs {} {
     global maxwidth maxgraphpct
     global oldprefs prefstop showneartags showlocalchanges
-    global bgcolor fgcolor ctext diffcolors selectbgcolor
+    global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
     global tabstop limitdiffs autoselect extdifftool perfile_attrs
 
     set top .gitkprefs
@@ -9594,6 +10094,7 @@ proc doprefs {} {
     }
     toplevel $top
     wm title $top [mc "Gitk preferences"]
+    make_transient $top .
     label $top.ldisp -text [mc "Commit list display options"]
     grid $top.ldisp - -sticky w -pady 10
     label $top.spacer -text " "
@@ -9605,15 +10106,11 @@ proc doprefs {} {
        -font optionfont
     spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
     grid x $top.maxpctl $top.maxpct -sticky w
-    frame $top.showlocal
-    label $top.showlocal.l -text [mc "Show local changes"] -font optionfont
-    checkbutton $top.showlocal.b -variable showlocalchanges
-    pack $top.showlocal.b $top.showlocal.l -side left
+    checkbutton $top.showlocal -text [mc "Show local changes"] \
+       -font optionfont -variable showlocalchanges
     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
+    checkbutton $top.autoselect -text [mc "Auto-select SHA1"] \
+       -font optionfont -variable autoselect
     grid x $top.autoselect -sticky w
 
     label $top.ddisp -text [mc "Diff display options"]
@@ -9621,20 +10118,14 @@ proc doprefs {} {
     label $top.tabstopl -text [mc "Tab spacing"] -font optionfont
     spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
     grid x $top.tabstopl $top.tabstop -sticky w
-    frame $top.ntag
-    label $top.ntag.l -text [mc "Display nearby tags"] -font optionfont
-    checkbutton $top.ntag.b -variable showneartags
-    pack $top.ntag.b $top.ntag.l -side left
+    checkbutton $top.ntag -text [mc "Display nearby tags"] \
+       -font optionfont -variable showneartags
     grid x $top.ntag -sticky w
-    frame $top.ldiff
-    label $top.ldiff.l -text [mc "Limit diffs to listed paths"] -font optionfont
-    checkbutton $top.ldiff.b -variable limitdiffs
-    pack $top.ldiff.b $top.ldiff.l -side left
+    checkbutton $top.ldiff -text [mc "Limit diffs to listed paths"] \
+       -font optionfont -variable limitdiffs
     grid x $top.ldiff -sticky w
-    frame $top.lattr
-    label $top.lattr.l -text [mc "Support per-file encodings"] -font optionfont
-    checkbutton $top.lattr.b -variable perfile_attrs
-    pack $top.lattr.b $top.lattr.l -side left
+    checkbutton $top.lattr -text [mc "Support per-file encodings"] \
+       -font optionfont -variable perfile_attrs
     grid x $top.lattr -sticky w
 
     entry $top.extdifft -textvariable extdifftool
@@ -9650,31 +10141,37 @@ proc doprefs {} {
     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 {} $top.bg background setbg]
+       -command [list choosecolor bgcolor {} $top.bg [mc "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 {} $top.fg foreground setfg]
+       -command [list choosecolor fgcolor {} $top.fg [mc "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 \
-       -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
+       -command [list choosecolor diffcolors 0 $top.diffold [mc "diff old lines"] \
                      [list $ctext tag conf d0 -foreground]]
     grid x $top.diffoldbut $top.diffold -sticky w
     label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
     button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \
-       -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
-                     [list $ctext tag conf d1 -foreground]]
+       -command [list choosecolor diffcolors 1 $top.diffnew [mc "diff new lines"] \
+                     [list $ctext tag conf dresult -foreground]]
     grid x $top.diffnewbut $top.diffnew -sticky w
     label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
     button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
        -command [list choosecolor diffcolors 2 $top.hunksep \
-                     "diff hunk header" \
+                     [mc "diff hunk header"] \
                      [list $ctext tag conf hunksep -foreground]]
     grid x $top.hunksepbut $top.hunksep -sticky w
+    label $top.markbgsep -padx 40 -relief sunk -background $markbgcolor
+    button $top.markbgbut -text [mc "Marked line bg"] -font optionfont \
+       -command [list choosecolor markbgcolor {} $top.markbgsep \
+                     [mc "marked line background"] \
+                     [list $ctext tag conf omark -background]]
+    grid x $top.markbgbut $top.markbgsep -sticky w
     label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
     button $top.selbgbut -text [mc "Select bg"] -font optionfont \
-       -command [list choosecolor selectbgcolor {} $top.selbgsep background setselbg]
+       -command [list choosecolor selectbgcolor {} $top.selbgsep [mc "background"] setselbg]
     grid x $top.selbgbut $top.selbgsep -sticky w
 
     label $top.cfont -text [mc "Fonts: press to choose"]
@@ -9686,6 +10183,8 @@ proc doprefs {} {
     frame $top.buts
     button $top.buts.ok -text [mc "OK"] -command prefsok -default active
     button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
+    bind $top <Key-Return> prefsok
+    bind $top <Key-Escape> prefscan
     grid $top.buts.ok $top.buts.can
     grid columnconfigure $top.buts 0 -weight 1 -uniform a
     grid columnconfigure $top.buts 1 -weight 1 -uniform a
@@ -10164,6 +10663,9 @@ set gitencoding {}
 catch {
     set gitencoding [exec git config --get i18n.commitencoding]
 }
+catch {
+    set gitencoding [exec git config --get i18n.logoutputencoding]
+}
 if {$gitencoding == ""} {
     set gitencoding "utf-8"
 }
@@ -10217,6 +10719,7 @@ set diffcolors {red "#00a000" blue}
 set diffcontext 3
 set ignorespace 0
 set selectbgcolor gray85
+set markbgcolor "#e0e0ff"
 
 set circlecolors {white blue gray blue blue}
 
@@ -10347,8 +10850,8 @@ set nhl_names {}
 set highlight_paths {}
 set findpattern {}
 set searchdirn -forwards
-set boldrows {}
-set boldnamerows {}
+set boldids {}
+set boldnameids {}
 set diffelide {0 0}
 set markingmatches 0
 set linkentercount 0
@@ -10411,4 +10914,9 @@ if {[info exists permviews]} {
        addviewmenu $n
     }
 }
+
+if {[tk windowingsystem] eq "win32"} {
+    focus -force .
+}
+
 getcommits {}