summaryrefslogtreecommitdiff
path: root/git-gui/git-gui.sh
diff options
context:
space:
mode:
Diffstat (limited to 'git-gui/git-gui.sh')
-rwxr-xr-xgit-gui/git-gui.sh3028
1 files changed, 3028 insertions, 0 deletions
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
new file mode 100755
index 0000000000..ad65aaad5a
--- /dev/null
+++ b/git-gui/git-gui.sh
@@ -0,0 +1,3028 @@
+#!/bin/sh
+# Tcl ignores the next line -*- tcl -*- \
+ if test "z$*" = zversion \
+ || test "z$*" = z--version; \
+ then \
+ echo 'git-gui version @@GITGUI_VERSION@@'; \
+ exit; \
+ fi; \
+ argv0=$0; \
+ exec wish "$argv0" -- "$@"
+
+set appvers {@@GITGUI_VERSION@@}
+set copyright [encoding convertfrom utf-8 {
+Copyright © 2006, 2007 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
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA}]
+
+######################################################################
+##
+## Tcl/Tk sanity check
+
+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 [mc "git-gui: fatal error"] \
+ -message $err
+ exit 1
+}
+
+catch {rename send {}} ; # What an evil concept...
+
+######################################################################
+##
+## locate our library
+
+set oguilib {@@GITGUI_LIBDIR@@}
+set oguirel {@@GITGUI_RELATIVE@@}
+if {$oguirel eq {1}} {
+ set oguilib [file dirname [file normalize $argv0]]
+ if {[file tail $oguilib] eq {git-core}} {
+ set oguilib [file dirname $oguilib]
+ }
+ set oguilib [file dirname $oguilib]
+ set oguilib [file join $oguilib share git-gui lib]
+ set oguimsg [file join $oguilib msgs]
+} elseif {[string match @@* $oguirel]} {
+ set oguilib [file join [file dirname [file normalize $argv0]] lib]
+ set oguimsg [file join [file dirname [file normalize $argv0]] po]
+} else {
+ set oguimsg [file join $oguilib msgs]
+}
+unset oguirel
+
+######################################################################
+##
+## enable verbose loading?
+
+if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
+ unset _verbose
+ rename auto_load real__auto_load
+ proc auto_load {name args} {
+ puts stderr "auto_load $name"
+ return [uplevel 1 real__auto_load $name $args]
+ }
+ rename source real__source
+ proc source {name} {
+ puts stderr "source $name"
+ uplevel 1 real__source $name
+ }
+}
+
+######################################################################
+##
+## Internationalization (i18n) through msgcat and gettext. See
+## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
+
+package require msgcat
+
+proc _mc_trim {fmt} {
+ set cmk [string first @@ $fmt]
+ if {$cmk > 0} {
+ return [string range $fmt 0 [expr {$cmk - 1}]]
+ }
+ return $fmt
+}
+
+proc mc {en_fmt args} {
+ set fmt [_mc_trim [::msgcat::mc $en_fmt]]
+ if {[catch {set msg [eval [list format $fmt] $args]} err]} {
+ set msg [eval [list format [_mc_trim $en_fmt]] $args]
+ }
+ return $msg
+}
+
+proc strcat {args} {
+ return [join $args {}]
+}
+
+::msgcat::mcload $oguimsg
+unset oguimsg
+
+######################################################################
+##
+## read only globals
+
+set _appname {Git Gui}
+set _gitdir {}
+set _gitexec {}
+set _reponame {}
+set _iscygwin {}
+set _search_path {}
+
+set _trace [lsearch -exact $argv --trace]
+if {$_trace >= 0} {
+ set argv [lreplace $argv $_trace $_trace]
+ set _trace 1
+} else {
+ set _trace 0
+}
+
+proc appname {} {
+ global _appname
+ return $_appname
+}
+
+proc gitdir {args} {
+ global _gitdir
+ if {$args eq {}} {
+ return $_gitdir
+ }
+ return [eval [list file join $_gitdir] $args]
+}
+
+proc gitexec {args} {
+ global _gitexec
+ if {$_gitexec eq {}} {
+ 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 [list file join $_gitexec] $args]
+}
+
+proc reponame {} {
+ return $::_reponame
+}
+
+proc is_MacOSX {} {
+ if {[tk windowingsystem] eq {aqua}} {
+ return 1
+ }
+ return 0
+}
+
+proc is_Windows {} {
+ if {$::tcl_platform(platform) eq {windows}} {
+ return 1
+ }
+ return 0
+}
+
+proc is_Cygwin {} {
+ global _iscygwin
+ if {$_iscygwin eq {}} {
+ if {$::tcl_platform(platform) eq {windows}} {
+ if {[catch {set p [exec cygpath --windir]} err]} {
+ set _iscygwin 0
+ } else {
+ set _iscygwin 1
+ }
+ } else {
+ set _iscygwin 0
+ }
+ }
+ return $_iscygwin
+}
+
+proc is_enabled {option} {
+ global enabled_options
+ if {[catch {set on $enabled_options($option)}]} {return 0}
+ return $on
+}
+
+proc enable_option {option} {
+ global enabled_options
+ set enabled_options($option) 1
+}
+
+proc disable_option {option} {
+ global enabled_options
+ set enabled_options($option) 0
+}
+
+######################################################################
+##
+## config
+
+proc is_many_config {name} {
+ switch -glob -- $name {
+ gui.recentrepo -
+ remote.*.fetch -
+ remote.*.push
+ {return 1}
+ *
+ {return 0}
+ }
+}
+
+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}} {
+ return 1
+ } else {
+ return 0
+ }
+}
+
+proc get_config {name} {
+ global repo_config
+ if {[catch {set v $repo_config($name)}]} {
+ return {}
+ } else {
+ return $v
+ }
+}
+
+######################################################################
+##
+## handy utils
+
+proc _trace_exec {cmd} {
+ if {!$::_trace} return
+ set d {}
+ foreach v $cmd {
+ if {$d ne {}} {
+ append d { }
+ }
+ if {[regexp {[ \t\r\n'"$?*]} $v]} {
+ set v [sq $v]
+ }
+ append d $v
+ }
+ puts stderr $d
+}
+
+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 -- [lindex $s 0] {
+ #!*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 [concat [list $interp] [lrange $s 1 end] [list $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 args} {
+ global env _search_exe _search_path
+
+ if {$_search_path eq {}} {
+ if {[is_Cygwin] && [regexp {^(/|\.:)} $env(PATH)]} {
+ set _search_path [split [exec cygpath \
+ --windows \
+ --path \
+ --absolute \
+ $env(PATH)] {;}]
+ set _search_exe .exe
+ } elseif {[is_Windows]} {
+ set gitguidir [file dirname [info script]]
+ regsub -all ";" $gitguidir "\\;" gitguidir
+ set env(PATH) "$gitguidir;$env(PATH)"
+ set _search_path [split $env(PATH) {;}]
+ set _search_exe .exe
+ } else {
+ set _search_path [split $env(PATH) :]
+ set _search_exe {}
+ }
+ }
+
+ if {[is_Windows] && [lsearch -exact $args -script] >= 0} {
+ set suffix {}
+ } else {
+ set suffix $_search_exe
+ }
+
+ foreach p $_search_path {
+ set p [file join $p $what$suffix]
+ 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} {
+ 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]
+
+ _trace_exec [concat $opt $cmdp $args]
+ set result [eval exec $opt $cmdp $args]
+ if {$::_trace} {
+ puts stderr "< $result"
+ }
+ return $result
+}
+
+proc _open_stdout_stderr {cmd} {
+ _trace_exec $cmd
+ if {[catch {
+ set fd [open [concat [list | ] $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 \
+ [list | ] \
+ [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]
+
+ _trace_exec [concat $opt $cmdp $args]
+ return [open [concat [list | ] $opt $cmdp $args] w]
+}
+
+proc githook_read {hook_name args} {
+ set pchook [gitdir hooks $hook_name]
+ lappend args 2>@1
+
+ # On Windows [file executable] might lie so we need to ask
+ # the shell if the hook is executable. Yes that's annoying.
+ #
+ if {[is_Windows]} {
+ upvar #0 _sh interp
+ if {![info exists interp]} {
+ set interp [_which sh]
+ }
+ if {$interp eq {}} {
+ error "hook execution requires sh (not in PATH)"
+ }
+
+ set scr {if test -x "$1";then exec "$@";fi}
+ set sh_c [list $interp -c $scr $interp $pchook]
+ return [_open_stdout_stderr [concat $sh_c $args]]
+ }
+
+ if {[file executable $pchook]} {
+ return [_open_stdout_stderr [concat [list $pchook] $args]]
+ }
+
+ return {}
+}
+
+proc kill_file_process {fd} {
+ set process [pid $fd]
+
+ catch {
+ if {[is_Windows]} {
+ # Use a Cygwin-specific flag to allow killing
+ # native Windows processes
+ exec kill -f $process
+ } else {
+ exec kill $process
+ }
+ }
+}
+
+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
+rename tk_optionMenu real__tkOptionMenu
+proc tk_optionMenu {w varName args} {
+ set m [eval real__tkOptionMenu $w $varName $args]
+ $m configure -font font_ui
+ $w configure -font font_ui
+ return $m
+}
+
+proc rmsel_tag {text} {
+ $text tag conf sel \
+ -background [$text cget -background] \
+ -foreground [$text cget -foreground] \
+ -borderwidth 0
+ $text tag conf in_sel -background lightgray
+ bind $text <Motion> break
+ return $text
+}
+
+set root_exists 0
+bind . <Visibility> {
+ bind . <Visibility> {}
+ set root_exists 1
+}
+
+if {[is_Windows]} {
+ wm iconbitmap . -default $oguilib/git-gui.ico
+}
+
+######################################################################
+##
+## config defaults
+
+set cursor_ptr arrow
+font create font_diff -family Courier -size 10
+font create font_ui
+catch {
+ label .dummy
+ eval font configure font_ui [font actual [.dummy cget -font]]
+ 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 Spinbox Text} {
+ option add *$class.font font_ui
+}
+unset class
+
+if {[is_Windows] || [is_MacOSX]} {
+ option add *Menu.tearOff 0
+}
+
+if {[is_MacOSX]} {
+ set M1B M1
+ set M1T Cmd
+} else {
+ set M1B Control
+ set M1T Ctrl
+}
+
+proc bind_button3 {w cmd} {
+ bind $w <Any-Button-3> $cmd
+ if {[is_MacOSX]} {
+ # Mac OS X sends Button-2 on right click through three-button mouse,
+ # or through trackpad right-clicking (two-finger touch + click).
+ bind $w <Any-Button-2> $cmd
+ bind $w <Control-Button-1> $cmd
+ }
+}
+
+proc apply_config {} {
+ global repo_config font_descs
+
+ foreach option $font_descs {
+ set name [lindex $option 0]
+ set font [lindex $option 1]
+ if {[catch {
+ set need_weight 1
+ foreach {cn cv} $repo_config(gui.$name) {
+ if {$cn eq {-weight}} {
+ set need_weight 0
+ }
+ font configure $font $cn $cv
+ }
+ if {$need_weight} {
+ font configure $font -weight normal
+ }
+ } err]} {
+ error_popup [strcat [mc "Invalid font specified in %s:" "gui.$name"] "\n\n$err"]
+ }
+ 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(branch.autosetupmerge) true
+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.fastcopyblame) false
+set default_config(gui.copyblamethreshold) 40
+set default_config(gui.diffcontext) 5
+set default_config(gui.commitmsgwidth) 75
+set default_config(gui.newbranchtemplate) {}
+set default_config(gui.spellingdictionary) {}
+set default_config(gui.fontui) [font configure font_ui]
+set default_config(gui.fontdiff) [font configure font_diff]
+set font_descs {
+ {fontui font_ui {mc "Main Font"}}
+ {fontdiff font_diff {mc "Diff/Console Font"}}
+}
+
+######################################################################
+##
+## find git
+
+set _git [_which git]
+if {$_git eq {}} {
+ catch {wm withdraw .}
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [mc "git-gui: fatal error"] \
+ -message [mc "Cannot find git in PATH."]
+ exit 1
+}
+
+######################################################################
+##
+## version check
+
+if {[catch {set _git_version [git --version]} err]} {
+ catch {wm withdraw .}
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [mc "git-gui: fatal error"] \
+ -message "Cannot determine Git version:
+
+$err
+
+[appname] requires Git 1.5.0 or later."
+ exit 1
+}
+if {![regsub {^git version } $_git_version {} _git_version]} {
+ catch {wm withdraw .}
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [mc "git-gui: fatal error"] \
+ -message [strcat [mc "Cannot parse Git version string:"] "\n\n$_git_version"]
+ exit 1
+}
+
+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
+regsub {\.[a-zA-Z]+\.[0-9]+$} $_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 [mc "Git version cannot be determined.
+
+%s claims it is version '%s'.
+
+%s requires at least Git 1.5.0 or later.
+
+Assume '%s' is version 1.5.0?
+" $_git $_real_git_version [appname] $_real_git_version]] eq {yes}} {
+ set _git_version 1.5.0
+ } else {
+ exit 1
+ }
+}
+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 .}
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [mc "git-gui: fatal error"] \
+ -message "[appname] requires Git 1.5.0 or later.
+
+You are using [git-version]:
+
+[git --version]"
+ exit 1
+}
+
+######################################################################
+##
+## configure our library
+
+set idx [file join $oguilib tclIndex]
+if {[catch {set fd [open $idx r]} err]} {
+ catch {wm withdraw .}
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [mc "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 idx fd
+
+######################################################################
+##
+## config file parsing
+
+git-version proc _parse_config {arr_name args} {
+ >= 1.5.3 {
+ upvar $arr_name arr
+ array unset arr
+ set buf {}
+ catch {
+ set fd_rc [eval \
+ [list git_read config] \
+ $args \
+ [list --null --list]]
+ fconfigure $fd_rc -translation binary
+ set buf [read $fd_rc]
+ close $fd_rc
+ }
+ foreach line [split $buf "\0"] {
+ if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} {
+ if {[is_many_config $name]} {
+ lappend arr($name) $value
+ } else {
+ set arr($name) $value
+ }
+ }
+ }
+ }
+ default {
+ upvar $arr_name arr
+ array unset arr
+ catch {
+ set fd_rc [eval [list git_read config --list] $args]
+ while {[gets $fd_rc line] >= 0} {
+ if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
+ if {[is_many_config $name]} {
+ lappend arr($name) $value
+ } else {
+ set arr($name) $value
+ }
+ }
+ }
+ close $fd_rc
+ }
+ }
+}
+
+proc load_config {include_global} {
+ global repo_config global_config default_config
+
+ if {$include_global} {
+ _parse_config global_config --global
+ }
+ _parse_config repo_config
+
+ foreach name [array names default_config] {
+ if {[catch {set v $global_config($name)}]} {
+ set global_config($name) $default_config($name)
+ }
+ if {[catch {set v $repo_config($name)}]} {
+ set repo_config($name) $default_config($name)
+ }
+ }
+}
+
+######################################################################
+##
+## feature option selection
+
+if {[regexp {^git-(.+)$} [file tail $argv0] _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)
+ set _prefix {}
+ }]
+ && [catch {
+ set _gitdir [git rev-parse --git-dir]
+ set _prefix [git rev-parse --show-prefix]
+ } err]} {
+ load_config 1
+ apply_config
+ choose_repository::pick
+}
+if {![file isdirectory $_gitdir] && [is_Cygwin]} {
+ catch {set _gitdir [exec cygpath --windows $_gitdir]}
+}
+if {![file isdirectory $_gitdir]} {
+ catch {wm withdraw .}
+ error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"]
+ exit 1
+}
+if {$_prefix ne {}} {
+ regsub -all {[^/]+/} $_prefix ../ cdup
+ if {[catch {cd $cdup} err]} {
+ catch {wm withdraw .}
+ error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"]
+ exit 1
+ }
+ unset cdup
+} elseif {![is_enabled bare]} {
+ if {[lindex [file split $_gitdir] end] ne {.git}} {
+ catch {wm withdraw .}
+ error_popup [strcat [mc "Cannot use funny .git directory:"] "\n\n$_gitdir"]
+ exit 1
+ }
+ if {[catch {cd [file dirname $_gitdir]} err]} {
+ catch {wm withdraw .}
+ error_popup [strcat [mc "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]
+}
+
+######################################################################
+##
+## global init
+
+set current_diff_path {}
+set current_diff_side {}
+set diff_actions [list]
+
+set HEAD {}
+set PARENT {}
+set MERGE_HEAD [list]
+set commit_type {}
+set empty_tree {}
+set current_branch {}
+set is_detached 0
+set current_diff_path {}
+set is_3way_diff 0
+set selected_commit_type new
+
+######################################################################
+##
+## task management
+
+set rescan_active 0
+set diff_active 0
+set last_clicked {}
+
+set disable_on_lock [list]
+set index_lock_type none
+
+proc lock_index {type} {
+ global index_lock_type disable_on_lock
+
+ if {$index_lock_type eq {none}} {
+ set index_lock_type $type
+ foreach w $disable_on_lock {
+ uplevel #0 $w disabled
+ }
+ return 1
+ } elseif {$index_lock_type eq "begin-$type"} {
+ set index_lock_type $type
+ return 1
+ }
+ return 0
+}
+
+proc unlock_index {} {
+ global index_lock_type disable_on_lock
+
+ set index_lock_type none
+ foreach w $disable_on_lock {
+ uplevel #0 $w normal
+ }
+}
+
+######################################################################
+##
+## status
+
+proc repository_state {ctvar hdvar mhvar} {
+ global current_branch
+ upvar $ctvar ct $hdvar hd $mhvar mh
+
+ set mh [list]
+
+ load_current_branch
+ if {[catch {set hd [git rev-parse --verify HEAD]}]} {
+ set hd {}
+ set ct initial
+ return
+ }
+
+ set merge_head [gitdir MERGE_HEAD]
+ if {[file exists $merge_head]} {
+ set ct merge
+ set fd_mh [open $merge_head r]
+ while {[gets $fd_mh line] >= 0} {
+ lappend mh $line
+ }
+ close $fd_mh
+ return
+ }
+
+ set ct normal
+}
+
+proc PARENT {} {
+ global PARENT empty_tree
+
+ set p [lindex $PARENT 0]
+ if {$p ne {}} {
+ return $p
+ }
+ if {$empty_tree eq {}} {
+ set empty_tree [git mktree << {}]
+ }
+ return $empty_tree
+}
+
+proc rescan {after {honor_trustmtime 1}} {
+ global HEAD PARENT MERGE_HEAD commit_type
+ global ui_index ui_workdir ui_comm
+ global rescan_active file_states
+ global repo_config
+
+ if {$rescan_active > 0 || ![lock_index read]} return
+
+ repository_state newType newHEAD newMERGE_HEAD
+ if {[string match amend* $commit_type]
+ && $newType eq {normal}
+ && $newHEAD eq $HEAD} {
+ } else {
+ set HEAD $newHEAD
+ set PARENT $newHEAD
+ set MERGE_HEAD $newMERGE_HEAD
+ set commit_type $newType
+ }
+
+ array unset file_states
+
+ if {!$::GITGUI_BCK_exists &&
+ (![$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 MERGE_MSG]} {
+ } elseif {[load_message SQUASH_MSG]} {
+ }
+ $ui_comm edit reset
+ $ui_comm edit modified false
+ }
+
+ if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
+ rescan_stage2 {} $after
+ } else {
+ set rescan_active 1
+ ui_status [mc "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]
+ }
+}
+
+if {[is_Cygwin]} {
+ set is_git_info_exclude {}
+ proc have_info_exclude {} {
+ global is_git_info_exclude
+
+ if {$is_git_info_exclude eq {}} {
+ if {[catch {exec test -f [gitdir info exclude]}]} {
+ set is_git_info_exclude 0
+ } else {
+ set is_git_info_exclude 1
+ }
+ }
+ return $is_git_info_exclude
+ }
+} else {
+ proc have_info_exclude {} {
+ return [file readable [gitdir info exclude]]
+ }
+}
+
+proc rescan_stage2 {fd after} {
+ global rescan_active buf_rdi buf_rdf buf_rlo
+
+ if {$fd ne {}} {
+ read $fd
+ if {![eof $fd]} return
+ 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"
+ }
+
+ set buf_rdi {}
+ set buf_rdf {}
+ set buf_rlo {}
+
+ set rescan_active 3
+ ui_status [mc "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
+ 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]
+}
+
+proc load_message {file} {
+ global ui_comm
+
+ set f [gitdir $file]
+ if {[file isfile $f]} {
+ 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
+ $ui_comm delete 0.0 end
+ $ui_comm insert end $content
+ return 1
+ }
+ return 0
+}
+
+proc read_diff_index {fd after} {
+ global buf_rdi
+
+ append buf_rdi [read $fd]
+ set c 0
+ set n [string length $buf_rdi]
+ while {$c < $n} {
+ set z1 [string first "\0" $buf_rdi $c]
+ if {$z1 == -1} break
+ incr z1
+ set z2 [string first "\0" $buf_rdi $z1]
+ if {$z2 == -1} break
+
+ incr c
+ set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
+ set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
+ merge_state \
+ [encoding convertfrom $p] \
+ [lindex $i 4]? \
+ [list [lindex $i 0] [lindex $i 2]] \
+ [list]
+ set c $z2
+ incr c
+ }
+ if {$c < $n} {
+ set buf_rdi [string range $buf_rdi $c end]
+ } else {
+ set buf_rdi {}
+ }
+
+ rescan_done $fd buf_rdi $after
+}
+
+proc read_diff_files {fd after} {
+ global buf_rdf
+
+ append buf_rdf [read $fd]
+ set c 0
+ set n [string length $buf_rdf]
+ while {$c < $n} {
+ set z1 [string first "\0" $buf_rdf $c]
+ if {$z1 == -1} break
+ incr z1
+ set z2 [string first "\0" $buf_rdf $z1]
+ if {$z2 == -1} break
+
+ incr c
+ set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
+ set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
+ merge_state \
+ [encoding convertfrom $p] \
+ ?[lindex $i 4] \
+ [list] \
+ [list [lindex $i 0] [lindex $i 2]]
+ set c $z2
+ incr c
+ }
+ if {$c < $n} {
+ set buf_rdf [string range $buf_rdf $c end]
+ } else {
+ set buf_rdf {}
+ }
+
+ rescan_done $fd buf_rdf $after
+}
+
+proc read_ls_others {fd after} {
+ global buf_rlo
+
+ append buf_rlo [read $fd]
+ set pck [split $buf_rlo "\0"]
+ set buf_rlo [lindex $pck end]
+ foreach p [lrange $pck 0 end-1] {
+ set p [encoding convertfrom $p]
+ if {[string index $p end] eq {/}} {
+ set p [string range $p 0 end-1]
+ }
+ merge_state $p ?O
+ }
+ rescan_done $fd buf_rlo $after
+}
+
+proc rescan_done {fd buf after} {
+ global rescan_active current_diff_path
+ global file_states repo_config
+ upvar $buf to_clear
+
+ if {![eof $fd]} return
+ set to_clear {}
+ close $fd
+ if {[incr rescan_active -1] > 0} return
+
+ prune_selection
+ unlock_index
+ display_all_files
+ if {$current_diff_path ne {}} reshow_diff
+ uplevel #0 $after
+}
+
+proc prune_selection {} {
+ global file_states selected_paths
+
+ foreach path [array names selected_paths] {
+ if {[catch {set still_here $file_states($path)}]} {
+ unset selected_paths($path)
+ }
+ }
+}
+