git-gui: Automatically backup the user's commit buffer
[gitweb.git] / git-gui.sh
index 67f993aa4bfc355aa2b581e5f850e765f3c7c7f4..d85d7076e28905d9cd6a8d668ab3b6b2066763e9 100755 (executable)
@@ -1,6 +1,12 @@
 #!/bin/sh
 # Tcl ignores the next line -*- tcl -*- \
-exec wish "$0" -- "$@"
+ if test "z$*" = zversion \
+ || test "z$*" = z--version; \
+ then \
+       echo 'git-gui version @@GITGUI_VERSION@@'; \
+       exit; \
+ fi; \
+ exec wish "$0" -- "$@"
 
 set appvers {@@GITGUI_VERSION@@}
 set copyright {
@@ -22,13 +28,23 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA}
 
 ######################################################################
 ##
-## configure our library
+## Tcl/Tk sanity check
 
-set oguilib {@@GITGUI_LIBDIR@@}
-if {[string match @@* $oguilib]} {
-       set oguilib [file join [file dirname [file normalize $argv0]] lib]
+if {[catch {package require Tcl 8.4} err]
+ || [catch {package require Tk  8.4} err]
+} {
+       catch {wm withdraw .}
+       tk_messageBox \
+               -icon error \
+               -type ok \
+               -title "git-gui: fatal error" \
+               -message $err
+       exit 1
 }
-set auto_path [concat [list $oguilib] $auto_path]
+
+######################################################################
+##
+## enable verbose loading?
 
 if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
        unset _verbose
@@ -44,6 +60,54 @@ if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
        }
 }
 
+######################################################################
+##
+## configure our library
+
+set oguilib {@@GITGUI_LIBDIR@@}
+set oguirel {@@GITGUI_RELATIVE@@}
+if {$oguirel eq {1}} {
+       set oguilib [file dirname [file dirname [file normalize $argv0]]]
+       set oguilib [file join $oguilib share git-gui lib]
+} elseif {[string match @@* $oguirel]} {
+       set oguilib [file join [file dirname [file normalize $argv0]] lib]
+}
+
+set idx [file join $oguilib tclIndex]
+if {[catch {set fd [open $idx r]} err]} {
+       catch {wm withdraw .}
+       tk_messageBox \
+               -icon error \
+               -type ok \
+               -title "git-gui: fatal error" \
+               -message $err
+       exit 1
+}
+if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
+       set idx [list]
+       while {[gets $fd n] >= 0} {
+               if {$n ne {} && ![string match #* $n]} {
+                       lappend idx $n
+               }
+       }
+} else {
+       set idx {}
+}
+close $fd
+
+if {$idx ne {}} {
+       set loaded [list]
+       foreach p $idx {
+               if {[lsearch -exact $loaded $p] >= 0} continue
+               source [file join $oguilib $p]
+               lappend loaded $p
+       }
+       unset loaded p
+} else {
+       set auto_path [concat [list $oguilib] $auto_path]
+}
+unset -nocomplain oguirel idx fd
+
 ######################################################################
 ##
 ## read only globals
@@ -53,6 +117,7 @@ set _gitdir {}
 set _gitexec {}
 set _reponame {}
 set _iscygwin {}
+set _search_path {}
 
 proc appname {} {
        global _appname
@@ -64,7 +129,7 @@ proc gitdir {args} {
        if {$args eq {}} {
                return $_gitdir
        }
-       return [eval [concat [list file join $_gitdir] $args]]
+       return [eval [list file join $_gitdir] $args]
 }
 
 proc gitexec {args} {
@@ -73,20 +138,26 @@ proc gitexec {args} {
                if {[catch {set _gitexec [git --exec-path]} err]} {
                        error "Git not installed?\n\n$err"
                }
+               if {[is_Cygwin]} {
+                       set _gitexec [exec cygpath \
+                               --windows \
+                               --absolute \
+                               $_gitexec]
+               } else {
+                       set _gitexec [file normalize $_gitexec]
+               }
        }
        if {$args eq {}} {
                return $_gitexec
        }
-       return [eval [concat [list file join $_gitexec] $args]]
+       return [eval [list file join $_gitexec] $args]
 }
 
 proc reponame {} {
-       global _reponame
-       return $_reponame
+       return $::_reponame
 }
 
 proc is_MacOSX {} {
-       global tcl_platform tk_library
        if {[tk windowingsystem] eq {aqua}} {
                return 1
        }
@@ -94,17 +165,16 @@ proc is_MacOSX {} {
 }
 
 proc is_Windows {} {
-       global tcl_platform
-       if {$tcl_platform(platform) eq {windows}} {
+       if {$::tcl_platform(platform) eq {windows}} {
                return 1
        }
        return 0
 }
 
 proc is_Cygwin {} {
-       global tcl_platform _iscygwin
+       global _iscygwin
        if {$_iscygwin eq {}} {
-               if {$tcl_platform(platform) eq {windows}} {
+               if {$::tcl_platform(platform) eq {windows}} {
                        if {[catch {set p [exec cygpath --windir]} err]} {
                                set _iscygwin 0
                        } else {
@@ -158,13 +228,22 @@ proc is_config_true {name} {
        }
 }
 
+proc get_config {name} {
+       global repo_config
+       if {[catch {set v $repo_config($name)}]} {
+               return {}
+       } else {
+               return $v
+       }
+}
+
 proc load_config {include_global} {
        global repo_config global_config default_config
 
        array unset global_config
        if {$include_global} {
                catch {
-                       set fd_rc [open "| git config --global --list" r]
+                       set fd_rc [git_read config --global --list]
                        while {[gets $fd_rc line] >= 0} {
                                if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
                                        if {[is_many_config $name]} {
@@ -180,7 +259,7 @@ proc load_config {include_global} {
 
        array unset repo_config
        catch {
-               set fd_rc [open "| git config --list" r]
+               set fd_rc [git_read config --list]
                while {[gets $fd_rc line] >= 0} {
                        if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
                                if {[is_many_config $name]} {
@@ -207,8 +286,224 @@ proc load_config {include_global} {
 ##
 ## handy utils
 
+proc _git_cmd {name} {
+       global _git_cmd_path
+
+       if {[catch {set v $_git_cmd_path($name)}]} {
+               switch -- $name {
+                 version   -
+               --version   -
+               --exec-path { return [list $::_git $name] }
+               }
+
+               set p [gitexec git-$name$::_search_exe]
+               if {[file exists $p]} {
+                       set v [list $p]
+               } elseif {[is_Windows] && [file exists [gitexec git-$name]]} {
+                       # Try to determine what sort of magic will make
+                       # git-$name go and do its thing, because native
+                       # Tcl on Windows doesn't know it.
+                       #
+                       set p [gitexec git-$name]
+                       set f [open $p r]
+                       set s [gets $f]
+                       close $f
+
+                       switch -glob -- $s {
+                       #!*sh     { set i sh     }
+                       #!*perl   { set i perl   }
+                       #!*python { set i python }
+                       default   { error "git-$name is not supported: $s" }
+                       }
+
+                       upvar #0 _$i interp
+                       if {![info exists interp]} {
+                               set interp [_which $i]
+                       }
+                       if {$interp eq {}} {
+                               error "git-$name requires $i (not in PATH)"
+                       }
+                       set v [list $interp $p]
+               } else {
+                       # Assume it is builtin to git somehow and we
+                       # aren't actually able to see a file for it.
+                       #
+                       set v [list $::_git $name]
+               }
+               set _git_cmd_path($name) $v
+       }
+       return $v
+}
+
+proc _which {what} {
+       global env _search_exe _search_path
+
+       if {$_search_path eq {}} {
+               if {[is_Cygwin]} {
+                       set _search_path [split [exec cygpath \
+                               --windows \
+                               --path \
+                               --absolute \
+                               $env(PATH)] {;}]
+                       set _search_exe .exe
+               } elseif {[is_Windows]} {
+                       set _search_path [split $env(PATH) {;}]
+                       set _search_exe .exe
+               } else {
+                       set _search_path [split $env(PATH) :]
+                       set _search_exe {}
+               }
+       }
+
+       foreach p $_search_path {
+               set p [file join $p $what$_search_exe]
+               if {[file exists $p]} {
+                       return [file normalize $p]
+               }
+       }
+       return {}
+}
+
+proc _lappend_nice {cmd_var} {
+       global _nice
+       upvar $cmd_var cmd
+
+       if {![info exists _nice]} {
+               set _nice [_which nice]
+       }
+       if {$_nice ne {}} {
+               lappend cmd $_nice
+       }
+}
+
 proc git {args} {
-       return [eval exec git $args]
+       set opt [list exec]
+
+       while {1} {
+               switch -- [lindex $args 0] {
+               --nice {
+                       _lappend_nice opt
+               }
+
+               default {
+                       break
+               }
+
+               }
+
+               set args [lrange $args 1 end]
+       }
+
+       set cmdp [_git_cmd [lindex $args 0]]
+       set args [lrange $args 1 end]
+
+       return [eval $opt $cmdp $args]
+}
+
+proc _open_stdout_stderr {cmd} {
+       if {[catch {
+                       set fd [open $cmd r]
+               } err]} {
+               if {   [lindex $cmd end] eq {2>@1}
+                   && $err eq {can not find channel named "1"}
+                       } {
+                       # Older versions of Tcl 8.4 don't have this 2>@1 IO
+                       # redirect operator.  Fallback to |& cat for those.
+                       # The command was not actually started, so its safe
+                       # to try to start it a second time.
+                       #
+                       set fd [open [concat \
+                               [lrange $cmd 0 end-1] \
+                               [list |& cat] \
+                               ] r]
+               } else {
+                       error $err
+               }
+       }
+       fconfigure $fd -eofchar {}
+       return $fd
+}
+
+proc git_read {args} {
+       set opt [list |]
+
+       while {1} {
+               switch -- [lindex $args 0] {
+               --nice {
+                       _lappend_nice opt
+               }
+
+               --stderr {
+                       lappend args 2>@1
+               }
+
+               default {
+                       break
+               }
+
+               }
+
+               set args [lrange $args 1 end]
+       }
+
+       set cmdp [_git_cmd [lindex $args 0]]
+       set args [lrange $args 1 end]
+
+       return [_open_stdout_stderr [concat $opt $cmdp $args]]
+}
+
+proc git_write {args} {
+       set opt [list |]
+
+       while {1} {
+               switch -- [lindex $args 0] {
+               --nice {
+                       _lappend_nice opt
+               }
+
+               default {
+                       break
+               }
+
+               }
+
+               set args [lrange $args 1 end]
+       }
+
+       set cmdp [_git_cmd [lindex $args 0]]
+       set args [lrange $args 1 end]
+
+       return [open [concat $opt $cmdp $args] w]
+}
+
+proc sq {value} {
+       regsub -all ' $value "'\\''" value
+       return "'$value'"
+}
+
+proc load_current_branch {} {
+       global current_branch is_detached
+
+       set fd [open [gitdir HEAD] r]
+       if {[gets $fd ref] < 1} {
+               set ref {}
+       }
+       close $fd
+
+       set pfx {ref: refs/heads/}
+       set len [string length $pfx]
+       if {[string equal -length $len $pfx $ref]} {
+               # We're on a branch.  It might not exist.  But
+               # HEAD looks good enough to be a branch.
+               #
+               set current_branch [string range $ref $len end]
+               set is_detached 0
+       } else {
+               # Assume this is a detached head.
+               #
+               set current_branch HEAD
+               set is_detached 1
+       }
 }
 
 auto_load tk_optionMenu
@@ -222,47 +517,168 @@ proc tk_optionMenu {w varName args} {
 
 ######################################################################
 ##
-## version check
+## find git
 
-if {{--version} eq $argv || {version} eq $argv} {
-       puts "git-gui version $appvers"
-       exit
+set _git  [_which git]
+if {$_git eq {}} {
+       catch {wm withdraw .}
+       error_popup "Cannot find git in PATH."
+       exit 1
 }
 
-set req_maj 1
-set req_min 5
+######################################################################
+##
+## version check
 
-if {[catch {set v [git --version]} err]} {
+if {[catch {set _git_version [git --version]} err]} {
        catch {wm withdraw .}
        error_popup "Cannot determine Git version:
 
 $err
 
-[appname] requires Git $req_maj.$req_min or later."
+[appname] requires Git 1.5.0 or later."
+       exit 1
+}
+if {![regsub {^git version } $_git_version {} _git_version]} {
+       catch {wm withdraw .}
+       error_popup "Cannot parse Git version string:\n\n$_git_version"
        exit 1
 }
-if {[regexp {^git version (\d+)\.(\d+)} $v _junk act_maj act_min]} {
-       if {$act_maj < $req_maj
-               || ($act_maj == $req_maj && $act_min < $req_min)} {
-               catch {wm withdraw .}
-               error_popup "[appname] requires Git $req_maj.$req_min or later.
 
-You are using $v."
+set _real_git_version $_git_version
+regsub -- {-dirty$} $_git_version {} _git_version
+regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version
+regsub {\.rc[0-9]+$} $_git_version {} _git_version
+regsub {\.GIT$} $_git_version {} _git_version
+
+if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} {
+       catch {wm withdraw .}
+       if {[tk_messageBox \
+               -icon warning \
+               -type yesno \
+               -default no \
+               -title "[appname]: warning" \
+               -message "Git version cannot be determined.
+
+$_git claims it is version '$_real_git_version'.
+
+[appname] requires at least Git 1.5.0 or later.
+
+Assume '$_real_git_version' is version 1.5.0?
+"] eq {yes}} {
+               set _git_version 1.5.0
+       } else {
                exit 1
        }
-} else {
+}
+unset _real_git_version
+
+proc git-version {args} {
+       global _git_version
+
+       switch [llength $args] {
+       0 {
+               return $_git_version
+       }
+
+       2 {
+               set op [lindex $args 0]
+               set vr [lindex $args 1]
+               set cm [package vcompare $_git_version $vr]
+               return [expr $cm $op 0]
+       }
+
+       4 {
+               set type [lindex $args 0]
+               set name [lindex $args 1]
+               set parm [lindex $args 2]
+               set body [lindex $args 3]
+
+               if {($type ne {proc} && $type ne {method})} {
+                       error "Invalid arguments to git-version"
+               }
+               if {[llength $body] < 2 || [lindex $body end-1] ne {default}} {
+                       error "Last arm of $type $name must be default"
+               }
+
+               foreach {op vr cb} [lrange $body 0 end-2] {
+                       if {[git-version $op $vr]} {
+                               return [uplevel [list $type $name $parm $cb]]
+                       }
+               }
+
+               return [uplevel [list $type $name $parm [lindex $body end]]]
+       }
+
+       default {
+               error "git-version >= x"
+       }
+
+       }
+}
+
+if {[git-version < 1.5]} {
        catch {wm withdraw .}
-       error_popup "Cannot parse Git version string:\n\n$v"
+       error_popup "[appname] requires Git 1.5.0 or later.
+
+You are using [git-version]:
+
+[git --version]"
        exit 1
 }
-unset -nocomplain v _junk act_maj act_min req_maj req_min
+
+######################################################################
+##
+## feature option selection
+
+if {[regexp {^git-(.+)$} [appname] _junk subcommand]} {
+       unset _junk
+} else {
+       set subcommand gui
+}
+if {$subcommand eq {gui.sh}} {
+       set subcommand gui
+}
+if {$subcommand eq {gui} && [llength $argv] > 0} {
+       set subcommand [lindex $argv 0]
+       set argv [lrange $argv 1 end]
+}
+
+enable_option multicommit
+enable_option branch
+enable_option transport
+disable_option bare
+
+switch -- $subcommand {
+browser -
+blame {
+       enable_option bare
+
+       disable_option multicommit
+       disable_option branch
+       disable_option transport
+}
+citool {
+       enable_option singlecommit
+
+       disable_option multicommit
+       disable_option branch
+       disable_option transport
+}
+}
 
 ######################################################################
 ##
 ## repository setup
 
-if {   [catch {set _gitdir $env(GIT_DIR)}]
-       && [catch {set _gitdir [git rev-parse --git-dir]} err]} {
+if {[catch {
+               set _gitdir $env(GIT_DIR)
+               set _prefix {}
+               }]
+       && [catch {
+               set _gitdir [git rev-parse --git-dir]
+               set _prefix [git rev-parse --show-prefix]
+       } err]} {
        catch {wm withdraw .}
        error_popup "Cannot find the git directory:\n\n$err"
        exit 1
@@ -275,19 +691,24 @@ if {![file isdirectory $_gitdir]} {
        error_popup "Git directory not found:\n\n$_gitdir"
        exit 1
 }
-if {[lindex [file split $_gitdir] end] ne {.git}} {
-       catch {wm withdraw .}
-       error_popup "Cannot use funny .git directory:\n\n$_gitdir"
-       exit 1
+if {![is_enabled bare]} {
+       if {[lindex [file split $_gitdir] end] ne {.git}} {
+               catch {wm withdraw .}
+               error_popup "Cannot use funny .git directory:\n\n$_gitdir"
+               exit 1
+       }
+       if {[catch {cd [file dirname $_gitdir]} err]} {
+               catch {wm withdraw .}
+               error_popup "No working directory [file dirname $_gitdir]:\n\n$err"
+               exit 1
+       }
 }
-if {[catch {cd [file dirname $_gitdir]} err]} {
-       catch {wm withdraw .}
-       error_popup "No working directory [file dirname $_gitdir]:\n\n$err"
-       exit 1
+set _reponame [file split [file normalize $_gitdir]]
+if {[lindex $_reponame end] eq {.git}} {
+       set _reponame [lindex $_reponame end-1]
+} else {
+       set _reponame [lindex $_reponame end]
 }
-set _reponame [lindex [file split \
-       [file normalize [file dirname $_gitdir]]] \
-       end]
 
 ######################################################################
 ##
@@ -296,7 +717,6 @@ set _reponame [lindex [file split \
 set current_diff_path {}
 set current_diff_side {}
 set diff_actions [list]
-set ui_status_value {Initializing...}
 
 set HEAD {}
 set PARENT {}
@@ -304,6 +724,7 @@ set MERGE_HEAD [list]
 set commit_type {}
 set empty_tree {}
 set current_branch {}
+set is_detached 0
 set current_diff_path {}
 set selected_commit_type new
 
@@ -353,15 +774,7 @@ proc repository_state {ctvar hdvar mhvar} {
 
        set mh [list]
 
-       if {[catch {set current_branch [git symbolic-ref HEAD]}]} {
-               set current_branch {}
-       } else {
-               regsub ^refs/((heads|tags|remotes)/)? \
-                       $current_branch \
-                       {} \
-                       current_branch
-       }
-
+       load_current_branch
        if {[catch {set hd [git rev-parse --verify HEAD]}]} {
                set hd {}
                set ct initial
@@ -397,7 +810,7 @@ proc PARENT {} {
 
 proc rescan {after {honor_trustmtime 1}} {
        global HEAD PARENT MERGE_HEAD commit_type
-       global ui_index ui_workdir ui_status_value ui_comm
+       global ui_index ui_workdir ui_comm
        global rescan_active file_states
        global repo_config
 
@@ -418,7 +831,8 @@ proc rescan {after {honor_trustmtime 1}} {
 
        if {![$ui_comm edit modified]
                || [string trim [$ui_comm get 0.0 end]] eq {}} {
-               if {[load_message GITGUI_MSG]} {
+               if {[string match amend* $commit_type]} {
+               } elseif {[load_message GITGUI_MSG]} {
                } elseif {[load_message MERGE_MSG]} {
                } elseif {[load_message SQUASH_MSG]} {
                }
@@ -426,22 +840,17 @@ proc rescan {after {honor_trustmtime 1}} {
                $ui_comm edit modified false
        }
 
-       if {[is_enabled branch]} {
-               load_all_heads
-               populate_branch_menu
-       }
-
        if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
                rescan_stage2 {} $after
        } else {
                set rescan_active 1
-               set ui_status_value {Refreshing file status...}
-               set cmd [list git update-index]
-               lappend cmd -q
-               lappend cmd --unmerged
-               lappend cmd --ignore-missing
-               lappend cmd --refresh
-               set fd_rf [open "| $cmd" r]
+               ui_status {Refreshing file status...}
+               set fd_rf [git_read update-index \
+                       -q \
+                       --unmerged \
+                       --ignore-missing \
+                       --refresh \
+                       ]
                fconfigure $fd_rf -blocking 0 -translation binary
                fileevent $fd_rf readable \
                        [list rescan_stage2 $fd_rf $after]
@@ -449,7 +858,6 @@ proc rescan {after {honor_trustmtime 1}} {
 }
 
 proc rescan_stage2 {fd after} {
-       global ui_status_value
        global rescan_active buf_rdi buf_rdf buf_rlo
 
        if {$fd ne {}} {
@@ -458,8 +866,7 @@ proc rescan_stage2 {fd after} {
                close $fd
        }
 
-       set ls_others [list | git ls-files --others -z \
-               --exclude-per-directory=.gitignore]
+       set ls_others [list --exclude-per-directory=.gitignore]
        set info_exclude [gitdir info exclude]
        if {[file readable $info_exclude]} {
                lappend ls_others "--exclude-from=$info_exclude"
@@ -470,10 +877,10 @@ proc rescan_stage2 {fd after} {
        set buf_rlo {}
 
        set rescan_active 3
-       set ui_status_value {Scanning for modified files ...}
-       set fd_di [open "| git diff-index --cached -z [PARENT]" r]
-       set fd_df [open "| git diff-files -z" r]
-       set fd_lo [open $ls_others r]
+       ui_status {Scanning for modified files ...}
+       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
@@ -491,6 +898,7 @@ proc load_message {file} {
                if {[catch {set fd [open $f r]}]} {
                        return 0
                }
+               fconfigure $fd -eofchar {}
                set content [string trim [read $fd]]
                close $fd
                regsub -all -line {[ \r\t]+$} $content {} content
@@ -630,6 +1038,14 @@ proc mapdesc {state path} {
        return $r
 }
 
+proc ui_status {msg} {
+       $::main_status show $msg
+}
+
+proc ui_ready {{test {}}} {
+       $::main_status show {Ready.} $test
+}
+
 proc escape_path {path} {
        regsub -all {\\} $path "\\\\" path
        regsub -all "\n" $path "\\n" path
@@ -971,6 +1387,7 @@ proc incr_font_size {font {amt 1}} {
        incr sz $amt
        font configure $font -size $sz
        font configure ${font}bold -size $sz
+       font configure ${font}italic -size $sz
 }
 
 ######################################################################
@@ -980,26 +1397,18 @@ proc incr_font_size {font {amt 1}} {
 set starting_gitk_msg {Starting gitk... please wait...}
 
 proc do_gitk {revs} {
-       global env ui_status_value starting_gitk_msg
-
        # -- Always start gitk through whatever we were loaded with.  This
        #    lets us bypass using shell process on Windows systems.
        #
-       set cmd [list [info nameofexecutable]]
-       lappend cmd [gitexec gitk]
-       if {$revs ne {}} {
-               append cmd { }
-               append cmd $revs
-       }
-
-       if {[catch {eval exec $cmd &} err]} {
-               error_popup "Failed to start gitk:\n\n$err"
+       set exe [file join [file dirname $::_git] gitk]
+       set cmd [list [info nameofexecutable] $exe]
+       if {! [file exists $exe]} {
+               error_popup "Unable to start gitk:\n\n$exe does not exist"
        } else {
-               set ui_status_value $starting_gitk_msg
+               eval exec $cmd $revs &
+               ui_status $::starting_gitk_msg
                after 10000 {
-                       if {$ui_status_value eq $starting_gitk_msg} {
-                               set ui_status_value {Ready.}
-                       }
+                       ui_ready $starting_gitk_msg
                }
        }
 }
@@ -1008,6 +1417,7 @@ set is_quitting 0
 
 proc do_quit {} {
        global ui_comm is_quitting repo_config commit_type
+       global GITGUI_BCK_exists GITGUI_BCK_i
 
        if {$is_quitting} return
        set is_quitting 1
@@ -1016,18 +1426,30 @@ proc do_quit {} {
                # -- Stash our current commit buffer.
                #
                set save [gitdir GITGUI_MSG]
-               set msg [string trim [$ui_comm get 0.0 end]]
-               regsub -all -line {[ \r\t]+$} $msg {} msg
-               if {(![string match amend* $commit_type]
-                       || [$ui_comm edit modified])
-                       && $msg ne {}} {
-                       catch {
-                               set fd [open $save w]
-                               puts -nonewline $fd $msg
-                               close $fd
-                       }
+               if {$GITGUI_BCK_exists && ![$ui_comm edit modified]} {
+                       file rename -force [gitdir GITGUI_BCK] $save
+                       set GITGUI_BCK_exists 0
                } else {
-                       catch {file delete $save}
+                       set msg [string trim [$ui_comm get 0.0 end]]
+                       regsub -all -line {[ \r\t]+$} $msg {} msg
+                       if {(![string match amend* $commit_type]
+                               || [$ui_comm edit modified])
+                               && $msg ne {}} {
+                               catch {
+                                       set fd [open $save w]
+                                       puts -nonewline $fd $msg
+                                       close $fd
+                               }
+                       } else {
+                               catch {file delete $save}
+                       }
+               }
+
+               # -- Remove our editor backup, its not needed.
+               #
+               after cancel $GITGUI_BCK_i
+               if {$GITGUI_BCK_exists} {
+                       catch {file delete [gitdir GITGUI_BCK]}
                }
 
                # -- Stash our current window geometry into this repository.
@@ -1048,7 +1470,7 @@ proc do_quit {} {
 }
 
 proc do_rescan {} {
-       rescan {set ui_status_value {Ready.}}
+       rescan ui_ready
 }
 
 proc do_commit {} {
@@ -1083,12 +1505,12 @@ proc toggle_or_diff {w x y} {
                        update_indexinfo \
                                "Unstaging [short_path $path] from commit" \
                                [list $path] \
-                               [concat $after {set ui_status_value {Ready.}}]
+                               [concat $after [list ui_ready]]
                } elseif {$w eq $ui_workdir} {
                        update_index \
                                "Adding [short_path $path]" \
                                [list $path] \
-                               [concat $after {set ui_status_value {Ready.}}]
+                               [concat $after [list ui_ready]]
                }
        } else {
                show_diff $path $w $lno
@@ -1163,25 +1585,28 @@ catch {
        destroy .dummy
 }
 
+font create font_uiitalic
 font create font_uibold
 font create font_diffbold
+font create font_diffitalic
 
 foreach class {Button Checkbutton Entry Label
                Labelframe Listbox Menu Message
-               Radiobutton Text} {
+               Radiobutton Spinbox Text} {
        option add *$class.font font_ui
 }
 unset class
 
-if {[is_Windows]} {
-       set M1B Control
-       set M1T Ctrl
-} elseif {[is_MacOSX]} {
+if {[is_Windows] || [is_MacOSX]} {
+       option add *Menu.tearOff 0
+}
+
+if {[is_MacOSX]} {
        set M1B M1
        set M1T Cmd
 } else {
-       set M1B M1
-       set M1T M1
+       set M1B Control
+       set M1T Ctrl
 }
 
 proc apply_config {} {
@@ -1199,16 +1624,21 @@ proc apply_config {} {
                }
                foreach {cn cv} [font configure $font] {
                        font configure ${font}bold $cn $cv
+                       font configure ${font}italic $cn $cv
                }
                font configure ${font}bold -weight bold
+               font configure ${font}italic -slant italic
        }
 }
 
+set default_config(merge.diffstat) true
 set default_config(merge.summary) false
 set default_config(merge.verbosity) 2
 set default_config(user.name) {}
 set default_config(user.email) {}
 
+set default_config(gui.matchtrackingbranch) false
+set default_config(gui.pruneduringfetch) false
 set default_config(gui.trustmtime) false
 set default_config(gui.diffcontext) 5
 set default_config(gui.newbranchtemplate) {}
@@ -1221,43 +1651,6 @@ set font_descs {
 load_config 0
 apply_config
 
-######################################################################
-##
-## feature option selection
-
-if {[regexp {^git-(.+)$} [appname] _junk subcommand]} {
-       unset _junk
-} else {
-       set subcommand gui
-}
-if {$subcommand eq {gui.sh}} {
-       set subcommand gui
-}
-if {$subcommand eq {gui} && [llength $argv] > 0} {
-       set subcommand [lindex $argv 0]
-       set argv [lrange $argv 1 end]
-}
-
-enable_option multicommit
-enable_option branch
-enable_option transport
-
-switch -- $subcommand {
-browser -
-blame {
-       disable_option multicommit
-       disable_option branch
-       disable_option transport
-}
-citool {
-       enable_option singlecommit
-
-       disable_option multicommit
-       disable_option branch
-       disable_option transport
-}
-}
-
 ######################################################################
 ##
 ## ui construction
@@ -1287,17 +1680,20 @@ if {[is_enabled transport]} {
 menu .mbar.repository
 
 .mbar.repository add command \
-       -label {Browse Current Branch} \
+       -label {Browse Current Branch's Files} \
        -command {browser::new $current_branch}
-trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#"
+trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch's Files\" ;#"
+.mbar.repository add command \
+       -label {Browse Branch Files...} \
+       -command browser_open::dialog
 .mbar.repository add separator
 
 .mbar.repository add command \
-       -label {Visualize Current Branch} \
+       -label {Visualize Current Branch's History} \
        -command {do_gitk $current_branch}
-trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch\" ;#"
+trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch's History\" ;#"
 .mbar.repository add command \
-       -label {Visualize All Branches} \
+       -label {Visualize All Branch History} \
        -command {do_gitk --all}
 .mbar.repository add separator
 
@@ -1365,13 +1761,24 @@ if {[is_enabled branch]} {
        menu .mbar.branch
 
        .mbar.branch add command -label {Create...} \
-               -command do_create_branch \
+               -command branch_create::dialog \
                -accelerator $M1T-N
        lappend disable_on_lock [list .mbar.branch entryconf \
                [.mbar.branch index last] -state]
 
+       .mbar.branch add command -label {Checkout...} \
+               -command branch_checkout::dialog \
+               -accelerator $M1T-O
+       lappend disable_on_lock [list .mbar.branch entryconf \
+               [.mbar.branch index last] -state]
+
+       .mbar.branch add command -label {Rename...} \
+               -command branch_rename::dialog
+       lappend disable_on_lock [list .mbar.branch entryconf \
+               [.mbar.branch index last] -state]
+
        .mbar.branch add command -label {Delete...} \
-               -command do_delete_branch
+               -command branch_delete::dialog
        lappend disable_on_lock [list .mbar.branch entryconf \
                [.mbar.branch index last] -state]
 
@@ -1449,14 +1856,14 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
 if {[is_enabled branch]} {
        menu .mbar.merge
        .mbar.merge add command -label {Local Merge...} \
-               -command merge::dialog
+               -command merge::dialog \
+               -accelerator $M1T-M
        lappend disable_on_lock \
                [list .mbar.merge entryconf [.mbar.merge index last] -state]
        .mbar.merge add command -label {Abort Merge...} \
                -command merge::reset_hard
        lappend disable_on_lock \
                [list .mbar.merge entryconf [.mbar.merge index last] -state]
-
 }
 
 # -- Transport Menu
@@ -1466,7 +1873,10 @@ if {[is_enabled transport]} {
 
        menu .mbar.push
        .mbar.push add command -label {Push...} \
-               -command do_push_anywhere
+               -command do_push_anywhere \
+               -accelerator $M1T-P
+       .mbar.push add command -label {Delete...} \
+               -command remote_branch_delete::dialog
 }
 
 if {[is_MacOSX]} {
@@ -1488,23 +1898,23 @@ if {[is_MacOSX]} {
 
        # -- Tools Menu
        #
-       if {[file exists /usr/local/miga/lib/gui-miga]
-               && [file exists .pvcsrc]} {
+       if {[is_Cygwin]
+               && [is_enabled multicommit]
+               && [file exists /usr/local/miga/lib/gui-miga]} {
        proc do_miga {} {
-               global ui_status_value
                if {![lock_index update]} return
                set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""]
                set miga_fd [open "|$cmd" r]
                fconfigure $miga_fd -blocking 0
                fileevent $miga_fd readable [list miga_done $miga_fd]
-               set ui_status_value {Running miga...}
+               ui_status {Running miga...}
        }
        proc miga_done {fd} {
                read $fd 512
                if {[eof $fd]} {
                        close $fd
                        unlock_index
-                       rescan [list set ui_status_value {Ready.}]
+                       rescan ui_ready
                }
        }
        .mbar add cascade -label Tools -menu .mbar.tools
@@ -1566,31 +1976,89 @@ unset browser doc_path doc_url
 
 # -- Standard bindings
 #
-bind .   <Destroy> do_quit
+wm protocol . WM_DELETE_WINDOW do_quit
 bind all <$M1B-Key-q> do_quit
 bind all <$M1B-Key-Q> do_quit
 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
 
+set subcommand_args {}
+proc usage {} {
+       puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
+       exit 1
+}
+
 # -- Not a normal commit type invocation?  Do that instead!
 #
 switch -- $subcommand {
-browser {
-       if {[llength $argv] != 1} {
-               puts stderr "usage: $argv0 browser commit"
-               exit 1
-       }
-       set current_branch [lindex $argv 0]
-       browser::new $current_branch
-       return
-}
+browser -
 blame {
-       if {[llength $argv] != 2} {
-               puts stderr "usage: $argv0 blame commit path"
-               exit 1
+       set subcommand_args {rev? path}
+       if {$argv eq {}} usage
+       set head {}
+       set path {}
+       set is_path 0
+       foreach a $argv {
+               if {$is_path || [file exists $_prefix$a]} {
+                       if {$path ne {}} usage
+                       set path $_prefix$a
+                       break
+               } elseif {$a eq {--}} {
+                       if {$path ne {}} {
+                               if {$head ne {}} usage
+                               set head $path
+                               set path {}
+                       }
+                       set is_path 1
+               } elseif {$head eq {}} {
+                       if {$head ne {}} usage
+                       set head $a
+                       set is_path 1
+               } else {
+                       usage
+               }
+       }
+       unset is_path
+
+       if {$head ne {} && $path eq {}} {
+               set path $_prefix$head
+               set head {}
+       }
+
+       if {$head eq {}} {
+               load_current_branch
+       } else {
+               if {[regexp {^[0-9a-f]{1,39}$} $head]} {
+                       if {[catch {
+                                       set head [git rev-parse --verify $head]
+                               } err]} {
+                               puts stderr $err
+                               exit 1
+                       }
+               }
+               set current_branch $head
+       }
+
+       switch -- $subcommand {
+       browser {
+               if {$head eq {}} {
+                       if {$path ne {} && [file isdirectory $path]} {
+                               set head $current_branch
+                       } else {
+                               set head $path
+                               set path {}
+                       }
+               }
+               browser::new $head $path
+       }
+       blame   {
+               if {$head eq {} && ![file exists $path]} {
+                       puts stderr "fatal: cannot stat path $path: No such file or directory"
+                       exit 1
+               }
+               blame::new $head $path
+       }
        }
-       set current_branch [lindex $argv 0]
-       show_blame $current_branch [lindex $argv 1]
        return
 }
 citool -
@@ -1639,7 +2107,7 @@ pack .vpane -anchor n -side top -fill both -expand 1
 #
 frame .vpane.files.index -height 100 -width 200
 label .vpane.files.index.title -text {Staged Changes (Will Be Committed)} \
-       -background green
+       -background lightgreen
 text $ui_index -background white -borderwidth 0 \
        -width 20 -height 10 \
        -wrap none \
@@ -1659,7 +2127,7 @@ pack $ui_index -side left -fill both -expand 1
 #
 frame .vpane.files.workdir -height 100 -width 200
 label .vpane.files.workdir.title -text {Unstaged Changes (Will Not Be Committed)} \
-       -background red
+       -background lightsalmon
 text $ui_workdir -background white -borderwidth 0 \
        -width 20 -height 10 \
        -wrap none \
@@ -1676,10 +2144,8 @@ pack $ui_workdir -side left -fill both -expand 1
 .vpane.files add .vpane.files.workdir -sticky nsew
 
 foreach i [list $ui_index $ui_workdir] {
-       $i tag conf in_diff -font font_uibold
-       $i tag conf in_sel \
-               -background [$i cget -foreground] \
-               -foreground [$i cget -background]
+       $i tag conf in_diff -background lightgray
+       $i tag conf in_sel  -background lightgray
 }
 unset i
 
@@ -1723,6 +2189,10 @@ pack .vpane.lower.commarea.buttons.commit -side top -fill x
 lappend disable_on_lock \
        {.vpane.lower.commarea.buttons.commit conf -state}
 
+button .vpane.lower.commarea.buttons.push -text {Push} \
+       -command do_push_anywhere
+pack .vpane.lower.commarea.buttons.push -side top -fill x
+
 # -- Commit Message Buffer
 #
 frame .vpane.lower.commarea.buffer
@@ -1837,18 +2307,18 @@ proc trace_current_diff_path {varname args} {
 }
 trace add variable current_diff_path write trace_current_diff_path
 
-frame .vpane.lower.diff.header -background orange
+frame .vpane.lower.diff.header -background gold
 label .vpane.lower.diff.header.status \
-       -background orange \
+       -background gold \
        -width $max_status_desc \
        -anchor w \
        -justify left
 label .vpane.lower.diff.header.file \
-       -background orange \
+       -background gold \
        -anchor w \
        -justify left
 label .vpane.lower.diff.header.path \
-       -background orange \
+       -background gold \
        -anchor w \
        -justify left
 pack .vpane.lower.diff.header.status -side left
@@ -1962,17 +2432,17 @@ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
 $ctxm add separator
 $ctxm add command \
        -label {Show Less Context} \
-       -command {if {$repo_config(gui.diffcontext) >= 2} {
+       -command {if {$repo_config(gui.diffcontext) >= 1} {
                incr repo_config(gui.diffcontext) -1
                reshow_diff
        }}
 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
 $ctxm add command \
        -label {Show More Context} \
-       -command {
+       -command {if {$repo_config(gui.diffcontext) < 99} {
                incr repo_config(gui.diffcontext)
                reshow_diff
-       }
+       }}
 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
 $ctxm add separator
 $ctxm add command -label {Options...} \
@@ -1991,12 +2461,9 @@ unset ui_diff_applyhunk
 
 # -- Status Bar
 #
-label .status -textvariable ui_status_value \
-       -anchor w \
-       -justify left \
-       -borderwidth 1 \
-       -relief sunken
+set main_status [::status_bar::new .status]
 pack .status -anchor w -side bottom -fill x
+$main_status show {Initializing...}
 
 # -- Load geometry
 #
@@ -2047,13 +2514,21 @@ bind $ui_diff <Control-Key-f> {catch {%W yview scroll  1 pages};break}
 bind $ui_diff <Button-1>   {focus %W}
 
 if {[is_enabled branch]} {
-       bind . <$M1B-Key-n> do_create_branch
-       bind . <$M1B-Key-N> do_create_branch
+       bind . <$M1B-Key-n> branch_create::dialog
+       bind . <$M1B-Key-N> branch_create::dialog
+       bind . <$M1B-Key-o> branch_checkout::dialog
+       bind . <$M1B-Key-O> branch_checkout::dialog
+       bind . <$M1B-Key-m> merge::dialog
+       bind . <$M1B-Key-M> merge::dialog
+}
+if {[is_enabled transport]} {
+       bind . <$M1B-Key-p> do_push_anywhere
+       bind . <$M1B-Key-P> do_push_anywhere
 }
 
-bind all <Key-F5> do_rescan
-bind all <$M1B-Key-r> do_rescan
-bind all <$M1B-Key-R> do_rescan
+bind .   <Key-F5>     do_rescan
+bind .   <$M1B-Key-r> do_rescan
+bind .   <$M1B-Key-R> do_rescan
 bind .   <$M1B-Key-s> do_signoff
 bind .   <$M1B-Key-S> do_signoff
 bind .   <$M1B-Key-i> do_add_all
@@ -2131,31 +2606,69 @@ user.email settings into your personal
 #
 if {[is_enabled transport]} {
        load_all_remotes
-       load_all_heads
 
-       populate_branch_menu
        populate_fetch_menu
        populate_push_menu
 }
 
-# -- Only suggest a gc run if we are going to stay running.
-#
-if {[is_enabled multicommit]} {
-       set object_limit 2000
-       if {[is_Windows]} {set object_limit 200}
-       regexp {^([0-9]+) objects,} [git count-objects] _junk objects_current
-       if {$objects_current >= $object_limit} {
-               if {[ask_popup \
-                       "This repository currently has $objects_current loose objects.
+if {[winfo exists $ui_comm]} {
+       set GITGUI_BCK_exists [load_message GITGUI_BCK]
+
+       # -- If both our backup and message files exist use the
+       #    newer of the two files to initialize the buffer.
+       #
+       if {$GITGUI_BCK_exists} {
+               set m [gitdir GITGUI_MSG]
+               if {[file isfile $m]} {
+                       if {[file mtime [gitdir GITGUI_BCK]] > [file mtime $m]} {
+                               catch {file delete [gitdir GITGUI_MSG]}
+                       } else {
+                               $ui_comm delete 0.0 end
+                               $ui_comm edit reset
+                               $ui_comm edit modified false
+                               catch {file delete [gitdir GITGUI_BCK]}
+                               set GITGUI_BCK_exists 0
+                       }
+               }
+               unset m
+       }
 
-To maintain optimal performance it is strongly recommended that you compress the database when more than $object_limit loose objects exist.
+       proc backup_commit_buffer {} {
+               global ui_comm GITGUI_BCK_exists
 
-Compress the database now?"] eq yes} {
-                       do_gc
+               set m [$ui_comm edit modified]
+               if {$m || $GITGUI_BCK_exists} {
+                       set msg [string trim [$ui_comm get 0.0 end]]
+                       regsub -all -line {[ \r\t]+$} $msg {} msg
+
+                       if {$msg eq {}} {
+                               if {$GITGUI_BCK_exists} {
+                                       catch {file delete [gitdir GITGUI_BCK]}
+                                       set GITGUI_BCK_exists 0
+                               }
+                       } elseif {$m} {
+                               catch {
+                                       set fd [open [gitdir GITGUI_BCK] w]
+                                       puts -nonewline $fd $msg
+                                       close $fd
+                                       set GITGUI_BCK_exists 1
+                               }
+                       }
+
+                       $ui_comm edit modified false
                }
+
+               set ::GITGUI_BCK_i [after 2000 backup_commit_buffer]
        }
-       unset object_limit _junk objects_current
+
+       backup_commit_buffer
 }
 
 lock_index begin-read
+if {![winfo ismapped .]} {
+       wm deiconify .
+}
 after 1 do_rescan
+if {[is_enabled multicommit]} {
+       after 1000 hint_gc
+}