git-gui: Assume unfound commands are known by git wrapper
[gitweb.git] / git-gui.sh
index 88ef64337adb54f06bd8babd9d91ec9ab47e5cec..9b342f08464c39d7096c987f3444772770962370 100755 (executable)
@@ -117,6 +117,7 @@ set _gitdir {}
 set _gitexec {}
 set _reponame {}
 set _iscygwin {}
+set _search_path {}
 
 proc appname {} {
        global _appname
@@ -128,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} {
@@ -137,11 +138,19 @@ 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 {} {
@@ -237,7 +246,7 @@ proc load_config {include_global} {
        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]} {
@@ -253,7 +262,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]} {
@@ -280,11 +289,180 @@ 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   -
+               --exec-path { return [list $::_git $name] }
+               }
+
+               set p [gitexec git-$name$::_search_exe]
+               if {[file exists $p]} {
+                       set v [list $p]
+               } elseif {[is_Cygwin]} {
+                       # On Cygwin git is a proper Cygwin program and knows
+                       # how to properly restart the Cygwin environment and
+                       # spawn its non-.exe support program.
+                       #
+                       set v [list $::_git $name]
+               } elseif {[is_Windows]
+                       && $::_sh ne {}
+                       && [file exists [gitexec git-$name]]} {
+                       # Assume this is a UNIX shell script.  We can
+                       # probably execute it through a Bourne shell.
+                       #
+                       set v [list $::_sh [gitexec git-$name]]
+               } 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 git {args} {
-       return [eval exec git $args]
+       set opt [list exec]
+
+       while {1} {
+               switch -- [lindex $args 0] {
+               --nice {
+                       global _nice
+                       if {$_nice ne {}} {
+                               lappend opt $_nice
+                       }
+               }
+
+               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 current-branch {} {
+proc git_read {args} {
+       set opt [list |]
+
+       while {1} {
+               switch -- [lindex $args 0] {
+               --nice {
+                       global _nice
+                       if {$_nice ne {}} {
+                               lappend opt $_nice
+                       }
+               }
+
+               --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]
+
+       if {[catch {
+                       set fd [open [concat $opt $cmdp $args] r]
+               } err]} {
+               if {   [lindex $args 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 \
+                               $opt \
+                               $cmdp \
+                               [lrange $args 0 end-1] \
+                               [list |& cat] \
+                               ] r]
+               } else {
+                       error $err
+               }
+       }
+       return $fd
+}
+
+proc git_write {args} {
+       set opt [list |]
+
+       while {1} {
+               switch -- [lindex $args 0] {
+               --nice {
+                       global _nice
+                       if {$_nice ne {}} {
+                               lappend opt $_nice
+                       }
+               }
+
+               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 load_current_branch {} {
+       global current_branch is_detached
+
        set fd [open [gitdir HEAD] r]
        if {[gets $fd ref] < 1} {
                set ref {}
@@ -297,11 +475,13 @@ proc current-branch {} {
                # We're on a branch.  It might not exist.  But
                # HEAD looks good enough to be a branch.
                #
-               return [string range $ref $len end]
+               set current_branch [string range $ref $len end]
+               set is_detached 0
        } else {
                # Assume this is a detached head.
                #
-               return HEAD
+               set current_branch HEAD
+               set is_detached 1
        }
 }
 
@@ -314,6 +494,19 @@ proc tk_optionMenu {w varName args} {
        return $m
 }
 
+######################################################################
+##
+## find git
+
+set _git  [_which git]
+if {$_git eq {}} {
+       catch {wm withdraw .}
+       error_popup "Cannot find git in PATH."
+       exit 1
+}
+set _nice [_which nice]
+set _sh   [_which sh]
+
 ######################################################################
 ##
 ## version check
@@ -434,7 +627,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 {}
@@ -442,6 +634,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
 
@@ -491,7 +684,7 @@ proc repository_state {ctvar hdvar mhvar} {
 
        set mh [list]
 
-       set current_branch [current-branch]
+       load_current_branch
        if {[catch {set hd [git rev-parse --verify HEAD]}]} {
                set hd {}
                set ct initial
@@ -557,22 +750,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
                ui_status {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]
+               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]
@@ -588,8 +776,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"
@@ -601,9 +788,9 @@ proc rescan_stage2 {fd after} {
 
        set rescan_active 3
        ui_status {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]
+       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
@@ -761,13 +948,11 @@ proc mapdesc {state path} {
 }
 
 proc ui_status {msg} {
-       set ::ui_status_value $msg
+       $::main_status show $msg
 }
 
 proc ui_ready {{test {}}} {
-       if {$test eq {} || $::ui_status_value eq $test} {
-               ui_status Ready.
-       }
+       $::main_status show {Ready.} $test
 }
 
 proc escape_path {path} {
@@ -1121,24 +1306,16 @@ proc incr_font_size {font {amt 1}} {
 set starting_gitk_msg {Starting gitk... please wait...}
 
 proc do_gitk {revs} {
-       global env 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]]
-       set exe [gitexec gitk]
-       lappend cmd $exe
-       if {$revs ne {}} {
-               append cmd { }
-               append cmd $revs
-       }
-
+       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 {
-               eval exec $cmd &
-               ui_status $starting_gitk_msg
+               eval exec $cmd $revs &
+               ui_status $::starting_gitk_msg
                after 10000 {
                        ui_ready $starting_gitk_msg
                }
@@ -1519,6 +1696,12 @@ if {[is_enabled branch]} {
        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 \
@@ -1739,8 +1922,19 @@ switch -- $subcommand {
 browser {
        set subcommand_args {rev?}
        switch [llength $argv] {
-       0 { set current_branch [current-branch] }
-       1 { set current_branch [lindex $argv 0] }
+       0 { load_current_branch }
+       1 {
+               set current_branch [lindex $argv 0]
+               if {[regexp {^[0-9a-f]{1,39}$} $current_branch]} {
+                       if {[catch {
+                                       set current_branch \
+                                       [git rev-parse --verify $current_branch]
+                               } err]} {
+                               puts stderr $err
+                               exit 1
+                       }
+               }
+       }
        default usage
        }
        browser::new $current_branch
@@ -1773,8 +1967,16 @@ blame {
        unset is_path
 
        if {$head eq {}} {
-               set current_branch [current-branch]
+               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
        }
 
@@ -2182,12 +2384,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
 #
@@ -2240,6 +2439,8 @@ bind $ui_diff <Button-1>   {focus %W}
 if {[is_enabled 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
 }
 if {[is_enabled transport]} {
        bind . <$M1B-Key-p> do_push_anywhere
@@ -2326,9 +2527,7 @@ 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
 }