gitk: Accelerators for the main menu
[gitweb.git] / gitk
diff --git a/gitk b/gitk
index 90764e8948f1f95aaddfb381664ecede328bc863..2028b554f48798c87852cdb3c5ab6b558ad6722e 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -294,6 +294,8 @@ proc parseviewrevs {view revs} {
 
     if {$revs eq {}} {
        set revs HEAD
+    } elseif {[lsearch -exact $revs --all] >= 0} {
+       lappend revs HEAD
     }
     if {[catch {set ids [eval exec git rev-parse $revs]} err]} {
        # we get stdout followed by stderr in $err
@@ -445,7 +447,7 @@ proc stop_instance {inst} {
        set pid [pid $fd]
 
        if {$::tcl_platform(platform) eq {windows}} {
-           exec kill -f $pid
+           exec taskkill /pid $pid
        } else {
            exec kill $pid
        }
@@ -598,18 +600,18 @@ proc reloadcommits {} {
     }
     resetvarcs $curview
     set selectedline {}
-    catch {unset currentid}
-    catch {unset thickerline}
-    catch {unset treediffs}
+    unset -nocomplain currentid
+    unset -nocomplain thickerline
+    unset -nocomplain treediffs
     readrefs
     changedrefs
     if {$showneartags} {
        getallcommits
     }
     clear_display
-    catch {unset commitinterest}
-    catch {unset cached_commitrow}
-    catch {unset targetid}
+    unset -nocomplain commitinterest
+    unset -nocomplain cached_commitrow
+    unset -nocomplain targetid
     setcanvscroll
     getcommits $selid
     return 0
@@ -671,7 +673,7 @@ proc resetvarcs {view} {
     foreach vd [array names vseedcount $view,*] {
        unset vseedcount($vd)
     }
-    catch {unset ordertok}
+    unset -nocomplain ordertok
 }
 
 # returns a list of the commits with no children
@@ -964,7 +966,7 @@ proc insertrow {id p v} {
     set vp $v,$p
     if {[llength [lappend children($vp) $id]] > 1} {
        set children($vp) [lsort -command [list vtokcmp $v] $children($vp)]
-       catch {unset ordertok}
+       unset -nocomplain ordertok
     }
     fix_reversal $p $a $v
     incr commitidx($v)
@@ -1134,7 +1136,7 @@ proc update_arcrows {v} {
            set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
            set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
        }
-       catch {unset cached_commitrow}
+       unset -nocomplain cached_commitrow
     }
     set narctot [expr {[llength $varctok($v)] - 1}]
     set a $varcmod($v)
@@ -1440,7 +1442,7 @@ proc getcommitlines {fd inst view updating}  {
            if {[string range $err 0 4] == "usage"} {
                set err "Gitk: error reading commits$fv:\
                        bad arguments to git log."
-               if {$viewname($view) eq "Command line"} {
+               if {$viewname($view) eq [mc "Command line"]} {
                    append err \
                        "  (Note: arguments to gitk are passed to git log\
                         to allow selection of commits to be displayed.)"
@@ -1577,7 +1579,7 @@ proc getcommitlines {fd inst view updating}  {
                    [vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
                    set children($vp) [lsort -command [list vtokcmp $view] \
                                           $children($vp)]
-                   catch {unset ordertok}
+                   unset -nocomplain ordertok
                }
                if {[info exists varcid($view,$p)]} {
                    fix_reversal $p $a $view
@@ -1776,7 +1778,7 @@ proc readrefs {} {
     global hideremotes
 
     foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
-       catch {unset $v}
+       unset -nocomplain $v
     }
     set refd [open [list | git show-ref -d] r]
     while {[gets $refd line] >= 0} {
@@ -1892,13 +1894,13 @@ proc make_transient {window origin} {
     }
 }
 
-proc show_error {w top msg {mc mc}} {
+proc show_error {w top msg} {
     global NS
     if {![info exists NS]} {set NS ""}
     if {[wm state $top] eq "withdrawn"} { wm deiconify $top }
     message $w.m -text $msg -justify center -aspect 400
     pack $w.m -side top -fill x -padx 20 -pady 20
-    ${NS}::button $w.ok -default active -text [$mc OK] -command "destroy $top"
+    ${NS}::button $w.ok -default active -text [mc OK] -command "destroy $top"
     pack $w.ok -side bottom -fill x
     bind $top <Visibility> "grab $top; focus $top"
     bind $top <Key-Return> "destroy $top"
@@ -2063,33 +2065,33 @@ proc makewindow {} {
     # The "mc" arguments here are purely so that xgettext
     # sees the following string as needing to be translated
     set file {
-       mc "File" cascade {
-           {mc "Update" command updatecommits -accelerator F5}
-           {mc "Reload" command reloadcommits -accelerator Shift-F5}
-           {mc "Reread references" command rereadrefs}
-           {mc "List references" command showrefs -accelerator F2}
+       mc "&File" cascade {
+           {mc "&Update" command updatecommits -accelerator F5}
+           {mc "&Reload" command reloadcommits -accelerator Shift-F5}
+           {mc "Reread re&ferences" command rereadrefs}
+           {mc "&List references" command showrefs -accelerator F2}
            {xx "" separator}
-           {mc "Start git gui" command {exec git gui &}}
+           {mc "Start git &gui" command {exec git gui &}}
            {xx "" separator}
-           {mc "Quit" command doquit -accelerator Meta1-Q}
+           {mc "&Quit" command doquit -accelerator Meta1-Q}
        }}
     set edit {
-       mc "Edit" cascade {
-           {mc "Preferences" command doprefs}
+       mc "&Edit" cascade {
+           {mc "&Preferences" command doprefs}
        }}
     set view {
-       mc "View" cascade {
-           {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}
+       mc "&View" cascade {
+           {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}}
+           {mc "&All files" radiobutton {selectedview 0} -command {showview 0}}
        }}
     if {[tk windowingsystem] ne "aqua"} {
        set help {
-       mc "Help" cascade {
-           {mc "About gitk" command about}
-           {mc "Key bindings" command keys}
+       mc "&Help" cascade {
+           {mc "&About gitk" command about}
+           {mc "&Key bindings" command keys}
        }}
        set bar [list $file $edit $view $help]
     } else {
@@ -2097,13 +2099,13 @@ proc makewindow {} {
        proc ::tk::mac::Quit {} {doquit}
        lset file end [lreplace [lindex $file end] end-1 end]
        set apple {
-       xx "Apple" cascade {
-           {mc "About gitk" command about}
+       xx "&Apple" cascade {
+           {mc "&About gitk" command about}
            {xx "" separator}
        }}
        set help {
-       mc "Help" cascade {
-           {mc "Key bindings" command keys}
+       mc "&Help" cascade {
+           {mc "&Key bindings" command keys}
        }}
        set bar [list $apple $file $view $help]
     }
@@ -2514,6 +2516,13 @@ proc makewindow {} {
     } else {
        bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
        bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
+       bind $ctext <Button> {
+           if {"%b" eq 6} {
+               $ctext xview scroll -5 units
+           } elseif {"%b" eq 7} {
+               $ctext xview scroll 5 units
+           }
+       }
         if {[tk windowingsystem] eq "aqua"} {
             bindall <MouseWheel> {
                 set delta [expr {- (%D)}]
@@ -2559,6 +2568,7 @@ proc makewindow {} {
     bindkey b prevfile
     bindkey d "$ctext yview scroll 18 units"
     bindkey u "$ctext yview scroll -18 units"
+    bindkey g {$sha1entry delete 0 end; focus $sha1entry}
     bindkey / {focus $fstring}
     bindkey <Key-KP_Divide> {focus $fstring}
     bindkey <Key-Return> {dofind 1 1}
@@ -2585,6 +2595,7 @@ proc makewindow {} {
     bind $fstring <Key-Return> {dofind 1 1}
     bind $sha1entry <Key-Return> {gotocommit; break}
     bind $sha1entry <<PasteSelection>> clearsha1
+    bind $sha1entry <<Paste>> clearsha1
     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}
@@ -2593,6 +2604,9 @@ proc makewindow {} {
     bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y}
     bind $ctext <Button-1> {focus %W}
     bind $ctext <<Selection>> rehighlight_search_results
+    for {set i 1} {$i < 10} {incr i} {
+       bind . <$M1B-Key-$i> [list go_to_parent $i]
+    }
 
     set maincursor [. cget -cursor]
     set textcursor [$ctext cget -cursor]
@@ -2604,6 +2618,7 @@ proc makewindow {} {
        {mc "Diff selected -> this" command {diffvssel 1}}
        {mc "Make patch" command mkpatch}
        {mc "Create tag" command mktag}
+       {mc "Copy commit summary" command copysummary}
        {mc "Write commit to file" command writecommit}
        {mc "Create new branch" command mkbranch}
        {mc "Cherry-pick this commit" command cherrypick}
@@ -2632,6 +2647,7 @@ proc makewindow {} {
     makemenu $headctxmenu {
        {mc "Check out this branch" command cobranch}
        {mc "Remove this branch" command rmbranch}
+       {mc "Copy branch name" command {clipboard clear; clipboard append $headmenuhead}}
     }
     $headctxmenu configure -tearoff 0
 
@@ -2642,6 +2658,7 @@ proc makewindow {} {
        {mc "Highlight this only" command {flist_hl 1}}
        {mc "External diff" command {external_diff}}
        {mc "Blame parent commit" command {external_blame 1}}
+       {mc "Copy path" command {clipboard clear; clipboard append $flist_menu_file}}
     }
     $flist_menu configure -tearoff 0
 
@@ -2770,93 +2787,87 @@ proc doprogupdate {} {
     }
 }
 
+proc config_check_tmp_exists {tries_left} {
+    global config_file_tmp
+
+    if {[file exists $config_file_tmp]} {
+       incr tries_left -1
+       if {$tries_left > 0} {
+           after 100 [list config_check_tmp_exists $tries_left]
+       } else {
+           error_popup "There appears to be a stale $config_file_tmp\
+ file, which will prevent gitk from saving its configuration on exit.\
+ Please remove it if it is not being used by any existing gitk process."
+       }
+    }
+}
+
+proc config_init_trace {name} {
+    global config_variable_changed config_variable_original
+
+    upvar #0 $name var
+    set config_variable_changed($name) 0
+    set config_variable_original($name) $var
+}
+
+proc config_variable_change_cb {name name2 op} {
+    global config_variable_changed config_variable_original
+
+    upvar #0 $name var
+    if {$op eq "write" &&
+       (![info exists config_variable_original($name)] ||
+        $config_variable_original($name) ne $var)} {
+       set config_variable_changed($name) 1
+    }
+}
+
 proc savestuff {w} {
-    global canv canv2 canv3 mainfont textfont uifont tabstop
-    global stuffsaved findmergefiles maxgraphpct
-    global maxwidth showneartags showlocalchanges
-    global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
-    global cmitmode wrapcomment datetimeformat limitdiffs
-    global colors uicolor bgcolor fgcolor diffcolors diffcontext selectbgcolor
-    global uifgcolor uifgdisabledcolor
-    global headbgcolor headfgcolor headoutlinecolor remotebgcolor
-    global tagbgcolor tagfgcolor tagoutlinecolor
-    global reflinecolor filesepbgcolor filesepfgcolor
-    global mergecolors foundbgcolor currentsearchhitbgcolor
-    global linehoverbgcolor linehoverfgcolor linehoveroutlinecolor circlecolors
-    global mainheadcirclecolor workingfilescirclecolor indexcirclecolor
-    global linkfgcolor circleoutlinecolor
-    global autoselect autosellen extdifftool perfile_attrs markbgcolor use_ttk
-    global hideremotes want_ttk maxrefs
+    global stuffsaved
     global config_file config_file_tmp
+    global config_variables config_variable_changed
+    global viewchanged
+
+    upvar #0 viewname current_viewname
+    upvar #0 viewfiles current_viewfiles
+    upvar #0 viewargs current_viewargs
+    upvar #0 viewargscmd current_viewargscmd
+    upvar #0 viewperm current_viewperm
+    upvar #0 nextviewnum current_nextviewnum
+    upvar #0 use_ttk current_use_ttk
 
     if {$stuffsaved} return
     if {![winfo viewable .]} return
-    catch {
-       if {[file exists $config_file_tmp]} {
-           file delete -force $config_file_tmp
+    set remove_tmp 0
+    if {[catch {
+       set try_count 0
+       while {[catch {set f [open $config_file_tmp {WRONLY CREAT EXCL}]}]} {
+           if {[incr try_count] > 50} {
+               error "Unable to write config file: $config_file_tmp exists"
+           }
+           after 100
        }
-       set f [open $config_file_tmp w]
+       set remove_tmp 1
        if {$::tcl_platform(platform) eq {windows}} {
            file attributes $config_file_tmp -hidden true
        }
-       puts $f [list set mainfont $mainfont]
-       puts $f [list set textfont $textfont]
-       puts $f [list set uifont $uifont]
-       puts $f [list set tabstop $tabstop]
-       puts $f [list set findmergefiles $findmergefiles]
-       puts $f [list set maxgraphpct $maxgraphpct]
-       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 autosellen $autosellen]
-       puts $f [list set showneartags $showneartags]
-       puts $f [list set maxrefs $maxrefs]
-       puts $f [list set hideremotes $hideremotes]
-       puts $f [list set showlocalchanges $showlocalchanges]
-       puts $f [list set datetimeformat $datetimeformat]
-       puts $f [list set limitdiffs $limitdiffs]
-       puts $f [list set uicolor $uicolor]
-       puts $f [list set want_ttk $want_ttk]
-       puts $f [list set bgcolor $bgcolor]
-       puts $f [list set fgcolor $fgcolor]
-       puts $f [list set uifgcolor $uifgcolor]
-       puts $f [list set uifgdisabledcolor $uifgdisabledcolor]
-       puts $f [list set colors $colors]
-       puts $f [list set diffcolors $diffcolors]
-       puts $f [list set mergecolors $mergecolors]
-       puts $f [list set markbgcolor $markbgcolor]
-       puts $f [list set diffcontext $diffcontext]
-       puts $f [list set selectbgcolor $selectbgcolor]
-       puts $f [list set foundbgcolor $foundbgcolor]
-       puts $f [list set currentsearchhitbgcolor $currentsearchhitbgcolor]
-       puts $f [list set extdifftool $extdifftool]
-       puts $f [list set perfile_attrs $perfile_attrs]
-       puts $f [list set headbgcolor $headbgcolor]
-       puts $f [list set headfgcolor $headfgcolor]
-       puts $f [list set headoutlinecolor $headoutlinecolor]
-       puts $f [list set remotebgcolor $remotebgcolor]
-       puts $f [list set tagbgcolor $tagbgcolor]
-       puts $f [list set tagfgcolor $tagfgcolor]
-       puts $f [list set tagoutlinecolor $tagoutlinecolor]
-       puts $f [list set reflinecolor $reflinecolor]
-       puts $f [list set filesepbgcolor $filesepbgcolor]
-       puts $f [list set filesepfgcolor $filesepfgcolor]
-       puts $f [list set linehoverbgcolor $linehoverbgcolor]
-       puts $f [list set linehoverfgcolor $linehoverfgcolor]
-       puts $f [list set linehoveroutlinecolor $linehoveroutlinecolor]
-       puts $f [list set mainheadcirclecolor $mainheadcirclecolor]
-       puts $f [list set workingfilescirclecolor $workingfilescirclecolor]
-       puts $f [list set indexcirclecolor $indexcirclecolor]
-       puts $f [list set circlecolors $circlecolors]
-       puts $f [list set linkfgcolor $linkfgcolor]
-       puts $f [list set circleoutlinecolor $circleoutlinecolor]
+       if {[file exists $config_file]} {
+           source $config_file
+       }
+       foreach var_name $config_variables {
+           upvar #0 $var_name var
+           upvar 0 $var_name old_var
+           if {!$config_variable_changed($var_name) && [info exists old_var]} {
+               puts $f [list set $var_name $old_var]
+           } else {
+               puts $f [list set $var_name $var]
+           }
+       }
 
        puts $f "set geometry(main) [wm geometry .]"
        puts $f "set geometry(state) [wm state .]"
        puts $f "set geometry(topwidth) [winfo width .tf]"
        puts $f "set geometry(topheight) [winfo height .tf]"
-       if {$use_ttk} {
+       if {$current_use_ttk} {
            puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sashpos 0] 1\""
            puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sashpos 1] 1\""
        } else {
@@ -2866,15 +2877,43 @@ proc savestuff {w} {
        puts $f "set geometry(botwidth) [winfo width .bleft]"
        puts $f "set geometry(botheight) [winfo height .bleft]"
 
+       array set view_save {}
+       array set views {}
+       if {![info exists permviews]} { set permviews {} }
+       foreach view $permviews {
+           set view_save([lindex $view 0]) 1
+           set views([lindex $view 0]) $view
+       }
        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) $viewargscmd($v)]}"
+       for {set v 1} {$v < $current_nextviewnum} {incr v} {
+           if {$viewchanged($v)} {
+               if {$current_viewperm($v)} {
+                   set views($current_viewname($v)) [list $current_viewname($v) $current_viewfiles($v) $current_viewargs($v) $current_viewargscmd($v)]
+               } else {
+                   set view_save($current_viewname($v)) 0
+               }
+           }
+       }
+       # write old and updated view to their places and append remaining to the end
+       foreach view $permviews {
+           set view_name [lindex $view 0]
+           if {$view_save($view_name)} {
+               puts $f "{$views($view_name)}"
            }
+           unset views($view_name)
+       }
+       foreach view_name [array names views] {
+           puts $f "{$views($view_name)}"
        }
        puts $f "}"
        close $f
        file rename -force $config_file_tmp $config_file
+       set remove_tmp 0
+    } err]} {
+        puts "Error saving config: $err"
+    }
+    if {$remove_tmp} {
+       file delete -force $config_file_tmp
     }
     set stuffsaved 1
 }
@@ -3015,6 +3054,7 @@ proc keys {} {
 [mc "<Down>, n, j      Move down one commit"]
 [mc "<Left>, z, h      Go back in history list"]
 [mc "<Right>, x, l     Go forward in history list"]
+[mc "<%s-n>    Go to n-th parent of current commit in history list" $M1T]
 [mc "<PageUp>  Move up one page in commit list"]
 [mc "<PageDown>        Move down one page in commit list"]
 [mc "<%s-Home> Scroll to top of commit list" $M1T]
@@ -3033,6 +3073,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 "g         Go to commit"]
 [mc "/         Focus the search box"]
 [mc "?         Move to previous find hit"]
 [mc "f         Scroll diff view to next file"]
@@ -3353,7 +3394,7 @@ proc init_flist {first} {
        set cflist_top 1
        $cflist tag add highlight 1.0 "1.0 lineend"
     } else {
-       catch {unset cflist_top}
+       unset -nocomplain cflist_top
     }
     $cflist conf -state disabled
     set difffilestart {}
@@ -3492,10 +3533,20 @@ proc flist_hl {only} {
 }
 
 proc gitknewtmpdir {} {
-    global diffnum gitktmpdir gitdir
+    global diffnum gitktmpdir gitdir env
 
     if {![info exists gitktmpdir]} {
-       set gitktmpdir [file join $gitdir [format ".gitk-tmp.%s" [pid]]]
+       if {[info exists env(GITK_TMPDIR)]} {
+           set tmpdir $env(GITK_TMPDIR)
+       } elseif {[info exists env(TMPDIR)]} {
+           set tmpdir $env(TMPDIR)
+       } else {
+           set tmpdir $gitdir
+       }
+       set gitktmpformat [file join $tmpdir ".gitk-tmp.XXXXXX"]
+       if {[catch {set gitktmpdir [exec mktemp -d $gitktmpformat]}]} {
+           set gitktmpdir [file join $gitdir [format ".gitk-tmp.%s" [pid]]]
+       }
        if {[catch {file mkdir $gitktmpdir} err]} {
            error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
            unset gitktmpdir
@@ -3870,7 +3921,7 @@ proc read_line_source {fd inst} {
            set id $nullid2
        }
        if {[commitinview $id $curview]} {
-           selectline [rowofcommit $id] 1 [list $fname $lnum]
+           selectline [rowofcommit $id] 1 [list $fname $lnum] 1
        } else {
            error_popup [mc "That line comes from commit %s, \
                             which is not in this view" [shortids $id]]
@@ -3988,6 +4039,19 @@ proc shellsplit {str} {
     return $l
 }
 
+proc set_window_title {} {
+    global appname curview viewname vrevs
+    set rev [mc "All files"]
+    if {$curview ne 0} {
+       if {$viewname($curview) eq [mc "Command line"]} {
+           set rev [string map {"--gitk-symmetric-diff-marker" "--merge"} $vrevs($curview)]
+       } else {
+           set rev $viewname($curview)
+       }
+    }
+    wm title . "[reponame]: $rev - $appname"
+}
+
 # Code to implement multiple views
 
 proc newview {ishighlight} {
@@ -4020,6 +4084,7 @@ set known_view_options {
     {committer t15  .  "--committer=*"  {mc "Committer:"}}
     {loginfo   t15  .. "--grep=*"       {mc "Commit Message:"}}
     {allmatch  b    .. "--all-match"    {mc "Matches all Commit Info criteria"}}
+    {igrep     b    .. "--invert-grep"  {mc "Matches no Commit Info criteria"}}
     {changes_l l    +  {}               {mc "Changes to Files:"}}
     {pickaxe_s r0   .  {}               {mc "Fixed String"}}
     {pickaxe_t r1   .  "--pickaxe-regex"  {mc "Regular Expression"}}
@@ -4281,7 +4346,7 @@ proc allviewmenus {n op args} {
 
 proc newviewok {top n {apply 0}} {
     global nextviewnum newviewperm newviewname newishighlight
-    global viewname viewfiles viewperm selectedview curview
+    global viewname viewfiles viewperm viewchanged selectedview curview
     global viewargs viewargscmd newviewopts viewhlmenu
 
     if {[catch {
@@ -4302,6 +4367,7 @@ proc newviewok {top n {apply 0}} {
        incr nextviewnum
        set viewname($n) $newviewname($n)
        set viewperm($n) $newviewopts($n,perm)
+       set viewchanged($n) 1
        set viewfiles($n) $files
        set viewargs($n) $newargs
        set viewargscmd($n) $newviewopts($n,cmd)
@@ -4314,6 +4380,7 @@ proc newviewok {top n {apply 0}} {
     } else {
        # editing an existing view
        set viewperm($n) $newviewopts($n,perm)
+       set viewchanged($n) 1
        if {$newviewname($n) ne $viewname($n)} {
            set viewname($n) $newviewname($n)
            doviewmenu .bar.view 5 [list showview $n] \
@@ -4336,7 +4403,7 @@ proc newviewok {top n {apply 0}} {
 }
 
 proc delview {} {
-    global curview viewperm hlview selectedhlview
+    global curview viewperm hlview selectedhlview viewchanged
 
     if {$curview == 0} return
     if {[info exists hlview] && $hlview == $curview} {
@@ -4345,6 +4412,7 @@ proc delview {} {
     }
     allviewmenus $curview delete
     set viewperm($curview) 0
+    set viewchanged($curview) 1
     showview 0
 }
 
@@ -4388,20 +4456,20 @@ proc showview {n} {
     }
     unselectline
     normalline
-    catch {unset treediffs}
+    unset -nocomplain treediffs
     clear_display
     if {[info exists hlview] && $hlview == $n} {
        unset hlview
        set selectedhlview [mc "None"]
     }
-    catch {unset commitinterest}
-    catch {unset cached_commitrow}
-    catch {unset ordertok}
+    unset -nocomplain commitinterest
+    unset -nocomplain cached_commitrow
+    unset -nocomplain ordertok
 
     set curview $n
     set selectedview $n
-    .bar.view entryconf [mca "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
-    .bar.view entryconf [mca "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
+    .bar.view entryconf [mca "&Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
+    .bar.view entryconf [mca "&Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
 
     run refill_reflist
     if {![info exists viewcomplete($n)]} {
@@ -4416,8 +4484,8 @@ proc showview {n} {
     set rowfinal {}
     set numcommits $commitidx($n)
 
-    catch {unset colormap}
-    catch {unset rowtextx}
+    unset -nocomplain colormap
+    unset -nocomplain rowtextx
     set nextcolor 0
     set canvxmax [$canv cget -width]
     set curview $n
@@ -4460,6 +4528,7 @@ proc showview {n} {
     } elseif {$numcommits == 0} {
        show_status [mc "No commits selected"]
     }
+    set_window_title
 }
 
 # Stuff relating to the highlighting facility
@@ -4551,7 +4620,7 @@ proc delvhighlight {} {
 
     if {![info exists hlview]} return
     unset hlview
-    catch {unset vhighlights}
+    unset -nocomplain vhighlights
     unbolden
 }
 
@@ -4599,7 +4668,7 @@ proc hfiles_change {} {
        # delete previous highlights
        catch {close $filehighlight}
        unset filehighlight
-       catch {unset fhighlights}
+       unset -nocomplain fhighlights
        unbolden
        unhighlight_filelist
     }
@@ -4660,7 +4729,7 @@ proc findcom_change args {
        bolden_name $id mainfont
     }
     set boldnameids {}
-    catch {unset nhighlights}
+    unset -nocomplain nhighlights
     unbolden
     unmarkmatches
     if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
@@ -4863,9 +4932,9 @@ proc rhighlight_sel {a} {
     global descendent desc_todo ancestor anc_todo
     global highlight_related
 
-    catch {unset descendent}
+    unset -nocomplain descendent
     set desc_todo [list $a]
-    catch {unset ancestor}
+    unset -nocomplain ancestor
     set anc_todo [list $a]
     if {$highlight_related ne [mc "None"]} {
        rhighlight_none
@@ -4876,7 +4945,7 @@ proc rhighlight_sel {a} {
 proc rhighlight_none {} {
     global rhighlights
 
-    catch {unset rhighlights}
+    unset -nocomplain rhighlights
     unbolden
 }
 
@@ -5084,8 +5153,8 @@ proc initlayout {} {
     set rowisopt {}
     set rowfinal {}
     set canvxmax [$canv cget -width]
-    catch {unset colormap}
-    catch {unset rowtextx}
+    unset -nocomplain colormap
+    unset -nocomplain rowtextx
     setcanvscroll
 }
 
@@ -5205,11 +5274,15 @@ proc dohidelocalchanges {} {
 # spawn off a process to do git diff-index --cached HEAD
 proc dodiffindex {} {
     global lserial showlocalchanges vfilelimit curview
-    global hasworktree
+    global hasworktree git_version
 
     if {!$showlocalchanges || !$hasworktree} return
     incr lserial
-    set cmd "|git diff-index --cached HEAD"
+    if {[package vcompare $git_version "1.7.2"] >= 0} {
+       set cmd "|git diff-index --cached --ignore-submodules=dirty HEAD"
+    } else {
+       set cmd "|git diff-index --cached HEAD"
+    }
     if {$vfilelimit($curview) ne {}} {
        set cmd [concat $cmd -- $vfilelimit($curview)]
     }
@@ -6314,17 +6387,17 @@ proc clear_display {} {
     global linehtag linentag linedtag boldids boldnameids
 
     allcanvs delete all
-    catch {unset iddrawn}
-    catch {unset linesegs}
-    catch {unset linehtag}
-    catch {unset linentag}
-    catch {unset linedtag}
+    unset -nocomplain iddrawn
+    unset -nocomplain linesegs
+    unset -nocomplain linehtag
+    unset -nocomplain linentag
+    unset -nocomplain linedtag
     set boldids {}
     set boldnameids {}
-    catch {unset vhighlights}
-    catch {unset fhighlights}
-    catch {unset nhighlights}
-    catch {unset rhighlights}
+    unset -nocomplain vhighlights
+    unset -nocomplain fhighlights
+    unset -nocomplain nhighlights
+    unset -nocomplain rhighlights
     set need_redisplay 0
     set nrows_drawn 0
 }
@@ -6596,6 +6669,7 @@ proc show_status {msg} {
     global canv fgcolor
 
     clear_display
+    set_window_title
     $canv create text 3 3 -anchor nw -text $msg -font mainfont \
        -tags text -fill $fgcolor
 }
@@ -7020,7 +7094,7 @@ proc viewnextline {dir} {
 # add a list of tag or branch names at position pos
 # returns the number of names inserted
 proc appendrefs {pos ids var} {
-    global ctext linknum curview $var maxrefs mainheadid
+    global ctext linknum curview $var maxrefs visiblerefs mainheadid
 
     if {[catch {$ctext index $pos}]} {
        return 0
@@ -7041,14 +7115,14 @@ proc appendrefs {pos ids var} {
     if {[llength $tags] > $maxrefs} {
        # If we are displaying heads, and there are too many,
        # see if there are some important heads to display.
-       # Currently this means "master" and the current head.
+       # Currently that are the current head and heads listed in $visiblerefs option
        set itags {}
        if {$var eq "idheads"} {
            set utags {}
            foreach ti $tags {
                set hname [lindex $ti 0]
                set id [lindex $ti 1]
-               if {($hname eq "master" || $id eq $mainheadid) &&
+               if {([lsearch -exact $visiblerefs $hname] != -1 || $id eq $mainheadid) &&
                    [llength $itags] < $maxrefs} {
                    lappend itags $ti
                } else {
@@ -7161,7 +7235,7 @@ proc make_idmark {id} {
     $canv raise $t
 }
 
-proc selectline {l isnew {desired_loc {}}} {
+proc selectline {l isnew {desired_loc {}} {switch_to_patch 0}} {
     global canv ctext commitinfo selectedline
     global canvy0 linespc parents children curview
     global currentid sha1entry
@@ -7172,7 +7246,7 @@ proc selectline {l isnew {desired_loc {}}} {
     global autoselect autosellen jump_to_here
     global vinlinediff
 
-    catch {unset pending_select}
+    unset -nocomplain pending_select
     $canv delete hover
     normalline
     unsel_reflist
@@ -7187,6 +7261,10 @@ proc selectline {l isnew {desired_loc {}}} {
        setcanvscroll
     }
 
+    if {$cmitmode ne "patch" && $switch_to_patch} {
+        set cmitmode "patch"
+    }
+
     set y [expr {$canvy0 + $l * $linespc}]
     set ymax [lindex [$canv cget -scrollregion] 3]
     set ytop [expr {$y - $linespc - 1}]
@@ -7366,7 +7444,7 @@ proc unselectline {} {
     global selectedline currentid
 
     set selectedline {}
-    catch {unset currentid}
+    unset -nocomplain currentid
     allcanvs delete secsel
     rhighlight_none
 }
@@ -7422,7 +7500,7 @@ proc unset_posvars {} {
     if {[info exists last_posvars]} {
        foreach {var val} $last_posvars {
            global $var
-           catch {unset $var}
+           unset -nocomplain $var
        }
        unset last_posvars
     }
@@ -7477,12 +7555,20 @@ proc goforw {} {
     }
 }
 
+proc go_to_parent {i} {
+    global parents curview targetid
+    set ps $parents($curview,$targetid)
+    if {[llength $ps] >= $i} {
+       selbyid [lindex $ps [expr $i - 1]]
+    }
+}
+
 proc gettree {id} {
     global treefilelist treeidlist diffids diffmergeid treepending
     global nullid nullid2
 
     set diffids $id
-    catch {unset diffmergeid}
+    unset -nocomplain diffmergeid
     if {![info exists treefilelist($id)]} {
        if {![info exists treepending]} {
            if {$id eq $nullid} {
@@ -7638,7 +7724,7 @@ proc startdiff {ids} {
 
     settabs 1
     set diffids $ids
-    catch {unset diffmergeid}
+    unset -nocomplain diffmergeid
     if {![info exists treediffs($ids)] ||
        [lsearch -exact $ids $nullid] >= 0 ||
        [lsearch -exact $ids $nullid2] >= 0} {
@@ -7705,7 +7791,7 @@ proc addtocflist {ids} {
 }
 
 proc diffcmd {ids flags} {
-    global log_showroot nullid nullid2
+    global log_showroot nullid nullid2 git_version
 
     set i [lsearch -exact $ids $nullid]
     set j [lsearch -exact $ids $nullid2]
@@ -7726,6 +7812,9 @@ proc diffcmd {ids flags} {
            }
        }
     } elseif {$j >= 0} {
+       if {[package vcompare $git_version "1.7.2"] >= 0} {
+           set flags "$flags --ignore-submodules=dirty"
+       }
        set cmd [concat | git diff-index --cached $flags]
        if {[llength $ids] > 1} {
            # comparing index with specific revision
@@ -8256,7 +8345,7 @@ proc clear_ctext {{first 1.0}} {
     }
     $ctext delete $first end
     if {$first eq "1.0"} {
-       catch {unset pendinglinks}
+       unset -nocomplain pendinglinks
     }
     set ctext_file_names {}
     set ctext_file_lines {}
@@ -8432,7 +8521,7 @@ proc scrolltext {f0 f1} {
        highlightfile_for_scrollpos $topidx
     }
 
-    catch {unset suppress_highlighting_file_for_this_scrollpos}
+    unset -nocomplain suppress_highlighting_file_for_this_scrollpos
 
     .bleft.bottom.sb set $f0 $f1
     if {$searchstring ne {}} {
@@ -8807,13 +8896,13 @@ 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] -state normal
+           $menu entryconfigure 8 -label [mc "Reset %s branch to here" $mainhead] -state normal
        } else {
-           $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
+           $menu entryconfigure 8 -label [mc "Detached head: can't reset" $mainhead] -state disabled
        }
-       $menu entryconfigure 9 -state $mstate
        $menu entryconfigure 10 -state $mstate
        $menu entryconfigure 11 -state $mstate
+       $menu entryconfigure 12 -state $mstate
     } else {
        set menu $fakerowmenu
     }
@@ -9272,6 +9361,20 @@ proc mktaggo {} {
     mktagcan
 }
 
+proc copysummary {} {
+    global rowmenuid autosellen
+
+    set format "%h (\"%s\", %ad)"
+    set cmd [list git show -s --pretty=format:$format --date=short]
+    if {$autosellen < 40} {
+        lappend cmd --abbrev=$autosellen
+    }
+    set summary [eval exec $cmd $rowmenuid]
+
+    clipboard clear
+    clipboard append $summary
+}
+
 proc writecommit {} {
     global rowmenuid wrcomtop commitinfo wrcomcmd NS
 
@@ -9750,8 +9853,10 @@ proc showrefs {} {
        -width 30 -height 20 -cursor $maincursor \
        -spacing1 1 -spacing3 1 -state disabled
     $top.list tag configure highlight -background $selectbgcolor
-    lappend bglist $top.list
-    lappend fglist $top.list
+    if {![lsearch -exact $bglist $top.list]} {
+       lappend bglist $top.list
+       lappend fglist $top.list
+    }
     ${NS}::scrollbar $top.ysb -command "$top.list yview" -orient vertical
     ${NS}::scrollbar $top.xsb -command "$top.list xview" -orient horizontal
     grid $top.list $top.ysb -sticky nsew
@@ -10034,9 +10139,9 @@ proc getallclines {fd} {
     }
     if {$nid > 0} {
        global cached_dheads cached_dtags cached_atags
-       catch {unset cached_dheads}
-       catch {unset cached_dtags}
-       catch {unset cached_atags}
+       unset -nocomplain cached_dheads
+       unset -nocomplain cached_dtags
+       unset -nocomplain cached_atags
     }
     if {![eof $fd]} {
        return [expr {$nid >= 1000? 2: 1}]
@@ -10276,7 +10381,7 @@ proc dropcache {err} {
     foreach v {arcnos arcout arcids arcstart arcend growing \
                   arctags archeads allparents allchildren} {
        global $v
-       catch {unset $v}
+       unset -nocomplain $v
     }
     set allcwait 0
     set nextarc 0
@@ -10927,8 +11032,8 @@ proc addedtag {id} {
     if {![info exists arcout($id)]} {
        recalcarc [lindex $arcnos($id) 0]
     }
-    catch {unset cached_dtags}
-    catch {unset cached_atags}
+    unset -nocomplain cached_dtags
+    unset -nocomplain cached_atags
 }
 
 proc addedhead {hid head} {
@@ -10938,13 +11043,13 @@ proc addedhead {hid head} {
     if {![info exists arcout($hid)]} {
        recalcarc [lindex $arcnos($hid) 0]
     }
-    catch {unset cached_dheads}
+    unset -nocomplain cached_dheads
 }
 
 proc removedhead {hid head} {
     global cached_dheads
 
-    catch {unset cached_dheads}
+    unset -nocomplain cached_dheads
 }
 
 proc movedhead {hid head} {
@@ -10954,7 +11059,7 @@ proc movedhead {hid head} {
     if {![info exists arcout($hid)]} {
        recalcarc [lindex $arcnos($hid) 0]
     }
-    catch {unset cached_dheads}
+    unset -nocomplain cached_dheads
 }
 
 proc changedrefs {} {
@@ -10970,10 +11075,10 @@ proc changedrefs {} {
            }
        }
     }
-    catch {unset cached_tagcontent}
-    catch {unset cached_dtags}
-    catch {unset cached_atags}
-    catch {unset cached_dheads}
+    unset -nocomplain cached_tagcontent
+    unset -nocomplain cached_dtags
+    unset -nocomplain cached_atags
+    unset -nocomplain cached_dheads
 }
 
 proc rereadrefs {} {
@@ -11261,6 +11366,7 @@ proc prefspage_general {notebook} {
     ${NS}::label $page.maxwidthl -text [mc "Maximum graph width (lines)"]
     spinbox $page.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
     grid $page.spacer $page.maxwidthl $page.maxwidth -sticky w
+                                         #xgettext:no-tcl-format
     ${NS}::label $page.maxpctl -text [mc "Maximum graph width (% of pane)"]
     spinbox $page.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
     grid x $page.maxpctl $page.maxpct -sticky w
@@ -11462,7 +11568,9 @@ proc choosecolor {v vi w x cmd} {
 proc setselbg {c} {
     global bglist cflist
     foreach w $bglist {
-       $w configure -selectbackground $c
+       if {[winfo exists $w]} {
+           $w configure -selectbackground $c
+       }
     }
     $cflist tag configure highlight \
        -background [$cflist cget -selectbackground]
@@ -11488,7 +11596,9 @@ proc setbg {c} {
     global bglist
 
     foreach w $bglist {
-       $w conf -background $c
+       if {[winfo exists $w]} {
+           $w conf -background $c
+       }
     }
 }
 
@@ -11496,7 +11606,9 @@ proc setfg {c} {
     global fglist canv
 
     foreach w $fglist {
-       $w conf -foreground $c
+       if {[winfo exists $w]} {
+           $w conf -foreground $c
+       }
     }
     allcanvs itemconf text -fill $c
     $canv itemconf circle -outline $c
@@ -11558,7 +11670,7 @@ proc prefsok {} {
        ($perfile_attrs && !$oldprefs(perfile_attrs))} {
        # treediffs elements are limited by path;
        # won't have encodings cached if perfile_attrs was just turned on
-       catch {unset treediffs}
+       unset -nocomplain treediffs
     }
     if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
        || $maxgraphpct != $oldprefs(maxgraphpct)} {
@@ -11575,7 +11687,29 @@ proc prefsok {} {
 proc formatdate {d} {
     global datetimeformat
     if {$d ne {}} {
-       set d [clock format [lindex $d 0] -format $datetimeformat]
+       # If $datetimeformat includes a timezone, display in the
+       # timezone of the argument.  Otherwise, display in local time.
+       if {[string match {*%[zZ]*} $datetimeformat]} {
+           if {[catch {set d [clock format [lindex $d 0] -timezone [lindex $d 1] -format $datetimeformat]}]} {
+               # Tcl < 8.5 does not support -timezone.  Emulate it by
+               # setting TZ (e.g. TZ=<-0430>+04:30).
+               global env
+               if {[info exists env(TZ)]} {
+                   set savedTZ $env(TZ)
+               }
+               set zone [lindex $d 1]
+               set sign [string map {+ - - +} [string index $zone 0]]
+               set env(TZ) <$zone>$sign[string range $zone 1 2]:[string range $zone 3 4]
+               set d [clock format [lindex $d 0] -format $datetimeformat]
+               if {[info exists savedTZ]} {
+                   set env(TZ) $savedTZ
+               } else {
+                   unset env(TZ)
+               }
+           }
+       } else {
+           set d [clock format [lindex $d 0] -format $datetimeformat]
+       }
     }
     return $d
 }
@@ -11916,10 +12050,29 @@ proc get_path_encoding {path} {
     return $tcl_enc
 }
 
+## For msgcat loading, first locate the installation location.
+if { [info exists ::env(GITK_MSGSDIR)] } {
+    ## Msgsdir was manually set in the environment.
+    set gitk_msgsdir $::env(GITK_MSGSDIR)
+} else {
+    ## Let's guess the prefix from argv0.
+    set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
+    set gitk_libdir [file join $gitk_prefix share gitk lib]
+    set gitk_msgsdir [file join $gitk_libdir msgs]
+    unset gitk_prefix
+}
+
+## Internationalization (i18n) through msgcat and gettext. See
+## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
+package require msgcat
+namespace import ::msgcat::mc
+## And eventually load the actual message catalog
+::msgcat::mcload $gitk_msgsdir
+
 # First check that Tcl/Tk is recent enough
 if {[catch {package require Tk 8.4} err]} {
-    show_error {} . "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
-                    Gitk requires at least Tcl/Tk 8.4." list
+    show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
+                        Gitk requires at least Tcl/Tk 8.4."]
     exit 1
 }
 
@@ -11938,7 +12091,7 @@ if { [info exists ::env(GIT_TRACE)] } {
 }
 
 # defaults...
-set wrcomcmd "git diff-tree --stdin -p --pretty"
+set wrcomcmd "git diff-tree --stdin -p --pretty=email"
 
 set gitencoding {}
 catch {
@@ -12001,6 +12154,7 @@ set wrapcomment "none"
 set showneartags 1
 set hideremotes 0
 set maxrefs 20
+set visiblerefs {"master"}
 set maxlinelen 200
 set showlocalchanges 1
 set limitdiffs 1
@@ -12068,25 +12222,6 @@ if {[tk windowingsystem] eq "aqua"} {
     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.
-    set gitk_msgsdir $::env(GITK_MSGSDIR)
-} else {
-    ## Let's guess the prefix from argv0.
-    set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
-    set gitk_libdir [file join $gitk_prefix share gitk lib]
-    set gitk_msgsdir [file join $gitk_libdir msgs]
-    unset gitk_prefix
-}
-
-## Internationalization (i18n) through msgcat and gettext. See
-## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
-package require msgcat
-namespace import ::msgcat::mc
-## And eventually load the actual message catalog
-::msgcat::mcload $gitk_msgsdir
-
 catch {
     # follow the XDG base directory specification by default. See
     # http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
@@ -12110,6 +12245,24 @@ catch {
     }
     source $config_file
 }
+config_check_tmp_exists 50
+
+set config_variables {
+    mainfont textfont uifont tabstop findmergefiles maxgraphpct maxwidth
+    cmitmode wrapcomment autoselect autosellen showneartags maxrefs visiblerefs
+    hideremotes showlocalchanges datetimeformat limitdiffs uicolor want_ttk
+    bgcolor fgcolor uifgcolor uifgdisabledcolor colors diffcolors mergecolors
+    markbgcolor diffcontext selectbgcolor foundbgcolor currentsearchhitbgcolor
+    extdifftool perfile_attrs headbgcolor headfgcolor headoutlinecolor
+    remotebgcolor tagbgcolor tagfgcolor tagoutlinecolor reflinecolor
+    filesepbgcolor filesepfgcolor linehoverbgcolor linehoverfgcolor
+    linehoveroutlinecolor mainheadcirclecolor workingfilescirclecolor
+    indexcirclecolor circlecolors linkfgcolor circleoutlinecolor
+}
+foreach var $config_variables {
+    config_init_trace $var
+    trace add variable $var write config_variable_change_cb
+}
 
 parsefont mainfont $mainfont
 eval font create mainfont [fontflags mainfont]
@@ -12237,6 +12390,7 @@ set highlight_related [mc "None"]
 set highlight_files {}
 set viewfiles(0) {}
 set viewperm(0) 0
+set viewchanged(0) 0
 set viewargs(0) {}
 set viewargscmd(0) {}
 
@@ -12281,7 +12435,7 @@ catch {
 }
 # wait for the window to become visible
 tkwait visibility .
-wm title . "$appname: [reponame]"
+set_window_title
 update
 readrefs
 
@@ -12295,6 +12449,7 @@ if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
     set viewargs(1) $revtreeargs
     set viewargscmd(1) $revtreeargscmd
     set viewperm(1) 0
+    set viewchanged(1) 0
     set vdatemode(1) 0
     addviewmenu 1
     .bar.view entryconf [mca "Edit view..."] -state normal
@@ -12310,6 +12465,7 @@ if {[info exists permviews]} {
        set viewargs($n) [lindex $v 2]
        set viewargscmd($n) [lindex $v 3]
        set viewperm($n) 1
+       set viewchanged($n) 0
        addviewmenu $n
     }
 }