git-gui: update Japanese translation
[gitweb.git] / git-gui.sh
index 8996d2d6bc9ec9d2fe0f105a18c288d87734f7d3..11048c7a0e94f598b168de98d18fda9aea420c7d 100755 (executable)
@@ -10,8 +10,8 @@
  exec wish "$argv0" -- "$@"
 
 set appvers {@@GITGUI_VERSION@@}
-set copyright [encoding convertfrom utf-8 {
-Copyright © 2006, 2007 Shawn Pearce, et. al.
+set copyright [string map [list (c) \u00a9] {
+Copyright (c) 2006-2010 Shawn Pearce, et. al.
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -38,7 +38,7 @@ if {[catch {package require Tcl 8.4} err]
        tk_messageBox \
                -icon error \
                -type ok \
-               -title [mc "git-gui: fatal error"] \
+               -title "git-gui: fatal error" \
                -message $err
        exit 1
 }
@@ -49,7 +49,11 @@ catch {rename send {}} ; # What an evil concept...
 ##
 ## locate our library
 
-set oguilib {@@GITGUI_LIBDIR@@}
+if { [info exists ::env(GIT_GUI_LIB_DIR) ] } {
+       set oguilib $::env(GIT_GUI_LIB_DIR)
+} else {
+       set oguilib {@@GITGUI_LIBDIR@@}
+}
 set oguirel {@@GITGUI_RELATIVE@@}
 if {$oguirel eq {1}} {
        set oguilib [file dirname [file normalize $argv0]]
@@ -79,10 +83,11 @@ if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
                return [uplevel 1 real__auto_load $name $args]
        }
        rename source real__source
-       proc source {name} {
-               puts stderr "source    $name"
-               uplevel 1 real__source $name
+       proc source {args} {
+               puts stderr "source    $args"
+               uplevel 1 [linsert $args 0 real__source]
        }
+       if {[tk windowingsystem] eq "win32"} { console show }
 }
 
 ######################################################################
@@ -92,6 +97,25 @@ if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
 
 package require msgcat
 
+# Check for Windows 7 MUI language pack (missed by msgcat < 1.4.4)
+if {[tk windowingsystem] eq "win32"
+       && [package vcompare [package provide msgcat] 1.4.4] < 0
+} then {
+       proc _mc_update_locale {} {
+               set key {HKEY_CURRENT_USER\Control Panel\Desktop}
+               if {![catch {
+                       package require registry
+                       set uilocale [registry get $key "PreferredUILanguages"]
+                       msgcat::ConvertLocale [string map {- _} [lindex $uilocale 0]]
+               } uilocale]} {
+                       if {[string length $uilocale] > 0} {
+                               msgcat::mclocale $uilocale
+                       }
+               }
+       }
+       _mc_update_locale
+}
+
 proc _mc_trim {fmt} {
        set cmk [string first @@ $fmt]
        if {$cmk > 0} {
@@ -115,6 +139,20 @@ proc strcat {args} {
 ::msgcat::mcload $oguimsg
 unset oguimsg
 
+######################################################################
+##
+## On Mac, bring the current Wish process window to front
+
+if {[tk windowingsystem] eq "aqua"} {
+       catch {
+               exec osascript -e [format {
+                       tell application "System Events"
+                               set frontmost of processes whose unix id is %d to true
+                       end tell
+               } [pid]]
+       }
+}
+
 ######################################################################
 ##
 ## read only globals
@@ -128,15 +166,33 @@ set _githtmldir {}
 set _reponame {}
 set _iscygwin {}
 set _search_path {}
+set _shellpath {@@SHELL_PATH@@}
 
 set _trace [lsearch -exact $argv --trace]
 if {$_trace >= 0} {
        set argv [lreplace $argv $_trace $_trace]
        set _trace 1
+       if {[tk windowingsystem] eq "win32"} { console show }
 } else {
        set _trace 0
 }
 
+# variable for the last merged branch (useful for a default when deleting
+# branches).
+set _last_merged_branch {}
+
+proc shellpath {} {
+       global _shellpath env
+       if {[string match @@* $_shellpath]} {
+               if {[info exists env(SHELL)]} {
+                       return $env(SHELL)
+               } else {
+                       return /bin/sh
+               }
+       }
+       return $_shellpath
+}
+
 proc appname {} {
        global _appname
        return $_appname
@@ -262,7 +318,22 @@ proc is_config_true {name} {
        global repo_config
        if {[catch {set v $repo_config($name)}]} {
                return 0
-       } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
+       }
+       set v [string tolower $v]
+       if {$v eq {} || $v eq {true} || $v eq {1} || $v eq {yes} || $v eq {on}} {
+               return 1
+       } else {
+               return 0
+       }
+}
+
+proc is_config_false {name} {
+       global repo_config
+       if {[catch {set v $repo_config($name)}]} {
+               return 0
+       }
+       set v [string tolower $v]
+       if {$v eq {false} || $v eq {0} || $v eq {no} || $v eq {off}} {
                return 1
        } else {
                return 0
@@ -323,6 +394,8 @@ proc _trace_exec {cmd} {
        puts stderr $d
 }
 
+#'"  fix poor old emacs font-lock mode
+
 proc _git_cmd {name} {
        global _git_cmd_path
 
@@ -410,6 +483,35 @@ proc _which {what args} {
        return {}
 }
 
+# Test a file for a hashbang to identify executable scripts on Windows.
+proc is_shellscript {filename} {
+       if {![file exists $filename]} {return 0}
+       set f [open $filename r]
+       fconfigure $f -encoding binary
+       set magic [read $f 2]
+       close $f
+       return [expr {$magic eq "#!"}]
+}
+
+# Run a command connected via pipes on stdout.
+# This is for use with textconv filters and uses sh -c "..." to allow it to
+# contain a command with arguments. On windows we must check for shell
+# scripts specifically otherwise just call the filter command.
+proc open_cmd_pipe {cmd path} {
+       global env
+       if {![file executable [shellpath]]} {
+               set exe [auto_execok [lindex $cmd 0]]
+               if {[is_shellscript [lindex $exe 0]]} {
+                       set run [linsert [auto_execok sh] end -c "$cmd \"\$0\"" $path]
+               } else {
+                       set run [concat $exe [lrange $cmd 1 end] $path]
+               }
+       } else {
+               set run [list [shellpath] -c "$cmd \"\$0\"" $path]
+       }
+       return [open |$run r]
+}
+
 proc _lappend_nice {cmd_var} {
        global _nice
        upvar $cmd_var cmd
@@ -418,6 +520,8 @@ proc _lappend_nice {cmd_var} {
                set _nice [_which nice]
                if {[catch {exec $_nice git version}]} {
                        set _nice {}
+               } elseif {[is_Windows] && [file dirname $_nice] ne [file dirname $::_git]} {
+                       set _nice {}
                }
        }
        if {$_nice ne {}} {
@@ -566,9 +670,7 @@ proc kill_file_process {fd} {
 
        catch {
                if {[is_Windows]} {
-                       # Use a Cygwin-specific flag to allow killing
-                       # native Windows processes
-                       exec kill -f $process
+                       exec taskkill /pid $process
                } else {
                        exec kill $process
                }
@@ -637,6 +739,7 @@ proc rmsel_tag {text} {
        return $text
 }
 
+wm withdraw .
 set root_exists 0
 bind . <Visibility> {
        bind . <Visibility> {}
@@ -646,6 +749,7 @@ bind . <Visibility> {
 if {[is_Windows]} {
        wm iconbitmap . -default $oguilib/git-gui.ico
        set ::tk::AlwaysShowSelection 1
+       bind . <Control-F2> {console show}
 
        # Spoof an X11 display for SSH
        if {![info exists env(DISPLAY)]} {
@@ -671,7 +775,10 @@ if {[is_Windows]} {
                gitlogo put gray26  -to  5 15 11 16
                gitlogo redither
 
-               wm iconphoto . -default gitlogo
+               image create photo gitlogo32 -width 32 -height 32
+               gitlogo32 copy gitlogo -zoom 2 2
+
+               wm iconphoto . -default gitlogo gitlogo32
        }
 }
 
@@ -785,12 +892,15 @@ set default_config(user.email) {}
 
 set default_config(gui.encoding) [encoding system]
 set default_config(gui.matchtrackingbranch) false
+set default_config(gui.textconv) true
 set default_config(gui.pruneduringfetch) false
 set default_config(gui.trustmtime) false
 set default_config(gui.fastcopyblame) false
+set default_config(gui.maxrecentrepo) 10
 set default_config(gui.copyblamethreshold) 40
 set default_config(gui.blamehistoryctx) 7
 set default_config(gui.diffcontext) 5
+set default_config(gui.diffopts) {}
 set default_config(gui.commitmsgwidth) 75
 set default_config(gui.newbranchtemplate) {}
 set default_config(gui.spellingdictionary) {}
@@ -799,10 +909,14 @@ set default_config(gui.fontdiff) [font configure font_diff]
 # TODO: this option should be added to the git-config documentation
 set default_config(gui.maxfilesdisplayed) 5000
 set default_config(gui.usettk) 1
+set default_config(gui.warndetachedcommit) 1
+set default_config(gui.tabsize) 8
 set font_descs {
        {fontui   font_ui   {mc "Main Font"}}
        {fontdiff font_diff {mc "Diff/Console Font"}}
 }
+set default_config(gui.stageuntracked) ask
+set default_config(gui.displayuntracked) true
 
 ######################################################################
 ##
@@ -846,12 +960,19 @@ if {![regsub {^git version } $_git_version {} _git_version]} {
        exit 1
 }
 
+proc get_trimmed_version {s} {
+    set r {}
+    foreach x [split $s -._] {
+        if {[string is integer -strict $x]} {
+            lappend r $x
+        } else {
+            break
+        }
+    }
+    return [join $r .]
+}
 set _real_git_version $_git_version
-regsub -- {[\-\.]dirty$} $_git_version {} _git_version
-regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version
-regsub {\.[a-zA-Z]+\.?[0-9]+$} $_git_version {} _git_version
-regsub {\.GIT$} $_git_version {} _git_version
-regsub {\.[a-zA-Z]+\.?[0-9]+$} $_git_version {} _git_version
+set _git_version [get_trimmed_version $_git_version]
 
 if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} {
        catch {wm withdraw .}
@@ -997,6 +1118,10 @@ git-version proc _parse_config {arr_name args} {
                                } else {
                                        set arr($name) $value
                                }
+                       } elseif {[regexp {^([^\n]+)$} $line line name]} {
+                               # no value given, but interpreting them as
+                               # boolean will be handled as true
+                               set arr($name) {}
                        }
                }
        }
@@ -1012,6 +1137,10 @@ git-version proc _parse_config {arr_name args} {
                                        } else {
                                                set arr($name) $value
                                        }
+                               } elseif {[regexp {^([^=]+)$} $line line name]} {
+                                       # no value given, but interpreting them as
+                                       # boolean will be handled as true
+                                       set arr($name) {}
                                }
                        }
                        close $fd_rc
@@ -1155,10 +1284,26 @@ if {![file isdirectory $_gitdir]} {
 # _gitdir exists, so try loading the config
 load_config 0
 apply_config
-# try to set work tree from environment, falling back to core.worktree
-if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} {
-       set _gitworktree [get_config core.worktree]
+
+# v1.7.0 introduced --show-toplevel to return the canonical work-tree
+if {[package vcompare $_git_version 1.7.0] >= 0} {
+       if { [is_Cygwin] } {
+               catch {set _gitworktree [exec cygpath --windows [git rev-parse --show-toplevel]]}
+       } else {
+               set _gitworktree [git rev-parse --show-toplevel]
+       }
+} else {
+       # try to set work tree from environment, core.worktree or use
+       # cdup to obtain a relative path to the top of the worktree. If
+       # run from the top, the ./ prefix ensures normalize expands pwd.
+       if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} {
+               set _gitworktree [get_config core.worktree]
+               if {$_gitworktree eq ""} {
+                       set _gitworktree [file normalize ./[git rev-parse --show-cdup]]
+               }
+       }
 }
+
 if {$_prefix ne {}} {
        if {$_gitworktree eq {}} {
                regsub -all {[^/]+/} $_prefix ../ cdup
@@ -1342,7 +1487,7 @@ proc rescan {after {honor_trustmtime 1}} {
                (![$ui_comm edit modified]
                || [string trim [$ui_comm get 0.0 end]] eq {})} {
                if {[string match amend* $commit_type]} {
-               } elseif {[load_message GITGUI_MSG]} {
+               } elseif {[load_message GITGUI_MSG utf-8]} {
                } elseif {[run_prepare_commit_msg_hook]} {
                } elseif {[load_message MERGE_MSG]} {
                } elseif {[load_message SQUASH_MSG]} {
@@ -1397,34 +1542,47 @@ proc rescan_stage2 {fd after} {
                close $fd
        }
 
-       set ls_others [list --exclude-per-directory=.gitignore]
-       if {[have_info_exclude]} {
-               lappend ls_others "--exclude-from=[gitdir info exclude]"
-       }
-       set user_exclude [get_config core.excludesfile]
-       if {$user_exclude ne {} && [file readable $user_exclude]} {
-               lappend ls_others "--exclude-from=$user_exclude"
+       if {[package vcompare $::_git_version 1.6.3] >= 0} {
+               set ls_others [list --exclude-standard]
+       } else {
+               set ls_others [list --exclude-per-directory=.gitignore]
+               if {[have_info_exclude]} {
+                       lappend ls_others "--exclude-from=[gitdir info exclude]"
+               }
+               set user_exclude [get_config core.excludesfile]
+               if {$user_exclude ne {} && [file readable $user_exclude]} {
+                       lappend ls_others "--exclude-from=[file normalize $user_exclude]"
+               }
        }
 
        set buf_rdi {}
        set buf_rdf {}
        set buf_rlo {}
 
-       set rescan_active 3
+       set rescan_active 2
        ui_status [mc "Scanning for modified files ..."]
-       set fd_di [git_read diff-index --cached -z [PARENT]]
+       if {[git-version >= "1.7.2"]} {
+               set fd_di [git_read diff-index --cached --ignore-submodules=dirty -z [PARENT]]
+       } else {
+               set fd_di [git_read diff-index --cached -z [PARENT]]
+       }
        set fd_df [git_read diff-files -z]
-       set fd_lo [eval git_read ls-files --others -z $ls_others]
 
        fconfigure $fd_di -blocking 0 -translation binary -encoding binary
        fconfigure $fd_df -blocking 0 -translation binary -encoding binary
-       fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
+
        fileevent $fd_di readable [list read_diff_index $fd_di $after]
        fileevent $fd_df readable [list read_diff_files $fd_df $after]
-       fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
+
+       if {[is_config_true gui.displayuntracked]} {
+               set fd_lo [eval git_read ls-files --others -z $ls_others]
+               fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
+               fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
+               incr rescan_active
+       }
 }
 
-proc load_message {file} {
+proc load_message {file {encoding {}}} {
        global ui_comm
 
        set f [gitdir $file]
@@ -1433,6 +1591,9 @@ proc load_message {file} {
                        return 0
                }
                fconfigure $fd -eofchar {}
+               if {$encoding ne {}} {
+                       fconfigure $fd -encoding $encoding
+               }
                set content [string trim [read $fd]]
                close $fd
                regsub -all -line {[ \r\t]+$} $content {} content
@@ -1448,7 +1609,7 @@ proc run_prepare_commit_msg_hook {} {
 
        # prepare-commit-msg requires PREPARE_COMMIT_MSG exist.  From git-gui
        # it will be .git/MERGE_MSG (merge), .git/SQUASH_MSG (squash), or an
-       # empty file but existant file.
+       # empty file but existent file.
 
        set fd_pcm [open [gitdir PREPARE_COMMIT_MSG] a]
 
@@ -1804,20 +1965,22 @@ proc display_all_files {} {
 
        set to_display [lsort [array names file_states]]
        set display_limit [get_config gui.maxfilesdisplayed]
-       if {[llength $to_display] > $display_limit} {
-               if {!$files_warning} {
-                       # do not repeatedly warn:
-                       set files_warning 1
-                       info_popup [mc "Displaying only %s of %s files." \
-                               $display_limit [llength $to_display]]
-               }
-               set to_display [lrange $to_display 0 [expr {$display_limit-1}]]
-       }
+       set displayed 0
        foreach path $to_display {
                set s $file_states($path)
                set m [lindex $s 0]
                set icon_name [lindex $s 1]
 
+               if {$displayed > $display_limit && [string index $m 1] eq {O} } {
+                       if {!$files_warning} {
+                               # do not repeatedly warn:
+                               set files_warning 1
+                               info_popup [mc "Display limit (gui.maxfilesdisplayed = %s) reached, not showing all %s files." \
+                                       $display_limit [llength $to_display]]
+                       }
+                       continue
+               }
+
                set s [string index $m 0]
                if {$s ne {U} && $s ne {_}} {
                        display_all_files_helper $ui_index $path \
@@ -1832,6 +1995,7 @@ proc display_all_files {} {
                if {$s ne {_}} {
                        display_all_files_helper $ui_workdir $path \
                                $icon_name $s
+                       incr displayed
                }
        }
 
@@ -1907,8 +2071,8 @@ static unsigned char file_merge_bits[] = {
 } -maskdata $filemask
 
 image create bitmap file_statechange -background white -foreground green -data {
-#define file_merge_width 14
-#define file_merge_height 15
+#define file_statechange_width 14
+#define file_statechange_height 15
 static unsigned char file_statechange_bits[] = {
    0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x62, 0x10,
    0x62, 0x10, 0xba, 0x11, 0xba, 0x11, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10,
@@ -1942,7 +2106,11 @@ foreach i {
                {MD {mc "Staged for commit, missing"}}
 
                {_T {mc "File type changed, not staged"}}
+               {MT {mc "File type changed, old type staged for commit"}}
+               {AT {mc "File type changed, old type staged for commit"}}
                {T_ {mc "File type changed, staged"}}
+               {TM {mc "File type change staged, modification not staged"}}
+               {TD {mc "File type change staged, file missing"}}
 
                {_O {mc "Untracked, not staged"}}
                {A_ {mc "Staged for commit"}}
@@ -2101,7 +2269,7 @@ proc do_explore {} {
                # freedesktop.org-conforming system is our best shot
                set explorer "xdg-open"
        }
-       eval exec $explorer $_gitworktree &
+       eval exec $explorer [list [file nativename $_gitworktree]] &
 }
 
 set is_quitting 0
@@ -2137,6 +2305,7 @@ proc do_quit {{rc {1}}} {
                                && $msg ne {}} {
                                catch {
                                        set fd [open $save w]
+                                       fconfigure $fd -encoding utf-8
                                        puts -nonewline $fd $msg
                                        close $fd
                                }
@@ -2391,6 +2560,7 @@ proc toggle_or_diff {w x y} {
                                [concat $after [list ui_ready]]
                }
        } else {
+               set selected_paths($path) 1
                show_diff $path $w $lno
        }
 }
@@ -2505,6 +2675,16 @@ if {![is_bare]} {
        .mbar.repository add command \
                -label [mc "Explore Working Copy"] \
                -command {do_explore}
+}
+
+if {[is_Windows]} {
+       .mbar.repository add command \
+               -label [mc "Git Bash"] \
+               -command {eval exec [auto_execok start] \
+                                         [list "Git Bash" bash --login -l &]}
+}
+
+if {[is_Windows] || ![is_bare]} {
        .mbar.repository add separator
 }
 
@@ -2827,7 +3007,14 @@ bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
 
 set subcommand_args {}
 proc usage {} {
-       puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
+       set s "usage: $::argv0 $::subcommand $::subcommand_args"
+       if {[tk windowingsystem] eq "win32"} {
+               wm withdraw .
+               tk_messageBox -icon info -message $s \
+                       -title [mc "Usage"]
+       } else {
+               puts stderr $s
+       }
        exit 1
 }
 
@@ -2861,9 +3048,11 @@ blame {
        set jump_spec {}
        set is_path 0
        foreach a $argv {
-               if {$is_path || [file exists $_prefix$a]} {
+               set p [file join $_prefix $a]
+
+               if {$is_path || [file exists $p]} {
                        if {$path ne {}} usage
-                       set path [normalize_relpath $_prefix$a]
+                       set path [normalize_relpath $p]
                        break
                } elseif {$a eq {--}} {
                        if {$path ne {}} {
@@ -2886,8 +3075,13 @@ blame {
        unset is_path
 
        if {$head ne {} && $path eq {}} {
-               set path [normalize_relpath $_prefix$head]
-               set head {}
+               if {[string index $head 0] eq {/}} {
+                       set path [normalize_relpath $head]
+                       set head {}
+               } else {
+                       set path [normalize_relpath $_prefix$head]
+                       set head {}
+               }
        }
 
        if {$head eq {}} {
@@ -2897,13 +3091,18 @@ blame {
                        if {[catch {
                                        set head [git rev-parse --verify $head]
                                } err]} {
-                               puts stderr $err
+                               if {[tk windowingsystem] eq "win32"} {
+                                       tk_messageBox -icon error -title [mc Error] -message $err
+                               } else {
+                                       puts stderr $err
+                               }
                                exit 1
                        }
                }
                set current_branch $head
        }
 
+       wm deiconify .
        switch -- $subcommand {
        browser {
                if {$jump_spec ne {}} usage
@@ -2919,7 +3118,12 @@ blame {
        }
        blame   {
                if {$head eq {} && ![file exists $path]} {
-                       puts stderr [mc "fatal: cannot stat path %s: No such file or directory" $path]
+                       catch {wm withdraw .}
+                       tk_messageBox \
+                               -icon error \
+                               -type ok \
+                               -title [mc "git-gui: fatal error"] \
+                               -message [mc "fatal: cannot stat path %s: No such file or directory" $path]
                        exit 1
                }
                blame::new $head $path $jump_spec
@@ -2930,18 +3134,19 @@ blame {
 citool -
 gui {
        if {[llength $argv] != 0} {
-               puts -nonewline stderr "usage: $argv0"
-               if {$subcommand ne {gui}
-                       && [file tail $argv0] ne "git-$subcommand"} {
-                       puts -nonewline stderr " $subcommand"
-               }
-               puts stderr {}
-               exit 1
+               usage
        }
        # fall through to setup UI for commits
 }
 default {
-       puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
+       set err "usage: $argv0 \[{blame|browser|citool}\]"
+       if {[tk windowingsystem] eq "win32"} {
+               wm withdraw .
+               tk_messageBox -icon error -message $err \
+                       -title [mc "Usage"]
+       } else {
+               puts stderr $err
+       }
        exit 1
 }
 }
@@ -3029,13 +3234,29 @@ unset i
 
 # -- Diff and Commit Area
 #
-${NS}::frame .vpane.lower -height 300 -width 400
-${NS}::frame .vpane.lower.commarea
-${NS}::frame .vpane.lower.diff -relief sunken -borderwidth 1
-pack .vpane.lower.diff -fill both -expand 1
-pack .vpane.lower.commarea -side bottom -fill x
-.vpane add .vpane.lower
-if {!$use_ttk} {.vpane paneconfigure .vpane.lower -sticky nsew}
+if {$have_tk85} {
+       ${NS}::panedwindow .vpane.lower -orient vertical
+       ${NS}::frame .vpane.lower.commarea
+       ${NS}::frame .vpane.lower.diff -relief sunken -borderwidth 1 -height 500
+       .vpane.lower add .vpane.lower.diff
+       .vpane.lower add .vpane.lower.commarea
+       .vpane add .vpane.lower
+       if {$use_ttk} {
+               .vpane.lower pane .vpane.lower.diff -weight 1
+               .vpane.lower pane .vpane.lower.commarea -weight 0
+       } else {
+               .vpane.lower paneconfigure .vpane.lower.diff -stretch always
+               .vpane.lower paneconfigure .vpane.lower.commarea -stretch never
+       }
+} else {
+       frame .vpane.lower -height 300 -width 400
+       frame .vpane.lower.commarea
+       frame .vpane.lower.diff -relief sunken -borderwidth 1
+       pack .vpane.lower.diff -fill both -expand 1
+       pack .vpane.lower.commarea -side bottom -fill x
+       .vpane add .vpane.lower
+       .vpane paneconfigure .vpane.lower -sticky nsew
+}
 
 # -- Commit Area Buttons
 #
@@ -3243,6 +3464,7 @@ text $ui_diff -background white -foreground black \
        -xscrollcommand {.vpane.lower.diff.body.sbx set} \
        -yscrollcommand {.vpane.lower.diff.body.sby set} \
        -state disabled
+catch {$ui_diff configure -tabstyle wordprocessor}
 ${NS}::scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
        -command [list $ui_diff xview]
 ${NS}::scrollbar .vpane.lower.diff.body.sby -orient vertical \
@@ -3253,8 +3475,19 @@ pack $ui_diff -side left -fill both -expand 1
 pack .vpane.lower.diff.header -side top -fill x
 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
 
+foreach {n c} {0 black 1 red4 2 green4 3 yellow4 4 blue4 5 magenta4 6 cyan4 7 grey60} {
+       $ui_diff tag configure clr4$n -background $c
+       $ui_diff tag configure clri4$n -foreground $c
+       $ui_diff tag configure clr3$n -foreground $c
+       $ui_diff tag configure clri3$n -background $c
+}
+$ui_diff tag configure clr1 -font font_diffbold
+$ui_diff tag configure clr4 -underline 1
+
+$ui_diff tag conf d_info -foreground blue -font font_diffbold
+
 $ui_diff tag conf d_cr -elide true
-$ui_diff tag conf d_@ -foreground blue -font font_diffbold
+$ui_diff tag conf d_@ -font font_diffbold
 $ui_diff tag conf d_+ -foreground {#00a000}
 $ui_diff tag conf d_- -foreground red
 
@@ -3273,13 +3506,13 @@ $ui_diff tag conf d_s- \
        -foreground red \
        -background ivory1
 
-$ui_diff tag conf d<<<<<<< \
+$ui_diff tag conf d< \
        -foreground orange \
        -font font_diffbold
-$ui_diff tag conf d======= \
+$ui_diff tag conf d= \
        -foreground orange \
        -font font_diffbold
-$ui_diff tag conf d>>>>>>> \
+$ui_diff tag conf d> \
        -foreground orange \
        -font font_diffbold
 
@@ -3408,6 +3641,19 @@ lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
 $ctxmsm add separator
 create_common_diff_popup $ctxmsm
 
+proc has_textconv {path} {
+       if {[is_config_false gui.textconv]} {
+               return 0
+       }
+       set filter [gitattr $path diff set]
+       set textconv [get_config [join [list diff $filter textconv] .]]
+       if {$filter ne {set} && $textconv ne {}} {
+               return 1
+       } else {
+               return 0
+       }
+}
+
 proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} {
        global current_diff_path file_states
        set ::cursorX $x
@@ -3442,8 +3688,9 @@ proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} {
                        || $current_diff_path eq {}
                        || {__} eq $state
                        || {_O} eq $state
-                       || {_T} eq $state
-                       || {T_} eq $state} {
+                       || [string match {?T} $state]
+                       || [string match {T?} $state]
+                       || [has_textconv $current_diff_path]} {
                        set s disabled
                } else {
                        set s normal
@@ -3463,29 +3710,44 @@ $main_status show [mc "Initializing..."]
 
 # -- Load geometry
 #
-catch {
-set gm $repo_config(gui.geometry)
-wm geometry . [lindex $gm 0]
-if {$use_ttk} {
-       .vpane sashpos 0 [lindex $gm 1]
-       .vpane.files sashpos 0 [lindex $gm 2]
-} else {
-       .vpane sash place 0 \
-               [lindex $gm 1] \
-               [lindex [.vpane sash coord 0] 1]
-       .vpane.files sash place 0 \
-               [lindex [.vpane.files sash coord 0] 0] \
-               [lindex $gm 2]
+proc on_ttk_pane_mapped {w pane pos} {
+       bind $w <Map> {}
+       after 0 [list after idle [list $w sashpos $pane $pos]]
+}
+proc on_tk_pane_mapped {w pane x y} {
+       bind $w <Map> {}
+       after 0 [list after idle [list $w sash place $pane $x $y]]
+}
+proc on_application_mapped {} {
+       global repo_config use_ttk
+       bind . <Map> {}
+       set gm $repo_config(gui.geometry)
+       if {$use_ttk} {
+               bind .vpane <Map> \
+                   [list on_ttk_pane_mapped %W 0 [lindex $gm 1]]
+               bind .vpane.files <Map> \
+                   [list on_ttk_pane_mapped %W 0 [lindex $gm 2]]
+       } else {
+               bind .vpane <Map> \
+                   [list on_tk_pane_mapped %W 0 \
+                        [lindex $gm 1] \
+                        [lindex [.vpane sash coord 0] 1]]
+               bind .vpane.files <Map> \
+                   [list on_tk_pane_mapped %W 0 \
+                        [lindex [.vpane.files sash coord 0] 0] \
+                        [lindex $gm 2]]
+       }
+       wm geometry . [lindex $gm 0]
 }
-unset gm
+if {[info exists repo_config(gui.geometry)]} {
+       bind . <Map> [list on_application_mapped]
+       wm geometry . [lindex $repo_config(gui.geometry) 0]
 }
 
 # -- Load window state
 #
-catch {
-set gws $repo_config(gui.wmstate)
-wm state . $gws
-unset gws
+if {[info exists repo_config(gui.wmstate)]} {
+       catch {wm state . $repo_config(gui.wmstate)}
 }
 
 # -- Key Bindings
@@ -3521,6 +3783,8 @@ bind $ui_diff <$M1B-Key-v> {break}
 bind $ui_diff <$M1B-Key-V> {break}
 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
+bind $ui_diff <$M1B-Key-j> {do_revert_selection;break}
+bind $ui_diff <$M1B-Key-J> {do_revert_selection;break}
 bind $ui_diff <Key-Up>     {catch {%W yview scroll -1 units};break}
 bind $ui_diff <Key-Down>   {catch {%W yview scroll  1 units};break}
 bind $ui_diff <Key-Left>   {catch {%W xview scroll -1 units};break}
@@ -3553,6 +3817,8 @@ bind .   <$M1B-Key-s> do_signoff
 bind .   <$M1B-Key-S> do_signoff
 bind .   <$M1B-Key-t> do_add_selection
 bind .   <$M1B-Key-T> do_add_selection
+bind .   <$M1B-Key-u> do_unstage_selection
+bind .   <$M1B-Key-U> do_unstage_selection
 bind .   <$M1B-Key-j> do_revert_selection
 bind .   <$M1B-Key-J> do_revert_selection
 bind .   <$M1B-Key-i> do_add_all
@@ -3646,7 +3912,7 @@ if {[is_enabled transport]} {
 }
 
 if {[winfo exists $ui_comm]} {
-       set GITGUI_BCK_exists [load_message GITGUI_BCK]
+       set GITGUI_BCK_exists [load_message GITGUI_BCK utf-8]
 
        # -- If both our backup and message files exist use the
        #    newer of the two files to initialize the buffer.
@@ -3683,6 +3949,7 @@ if {[winfo exists $ui_comm]} {
                        } elseif {$m} {
                                catch {
                                        set fd [open [gitdir GITGUI_BCK] w]
+                                       fconfigure $fd -encoding utf-8
                                        puts -nonewline $fd $msg
                                        close $fd
                                        set GITGUI_BCK_exists 1
@@ -3737,7 +4004,7 @@ after 1 {
                $ui_comm configure -state disabled -background gray
        }
 }
-if {[is_enabled multicommit]} {
+if {[is_enabled multicommit] && ![is_config_false gui.gcwarning]} {
        after 1000 hint_gc
 }
 if {[is_enabled retcode]} {