2 # Tcl ignores the next line -*- tcl -*- \
3 if test "z$*" = zversion \
4 || test "z$*" = z--version; \
6 echo 'git-gui version @@GITGUI_VERSION@@'; \
10 exec wish "$argv0" -- "$@"
12 set appvers {@@GITGUI_VERSION@@}
13 set copyright [string map [list (c) \u00a9] {
14 Copyright (c) 2006-2010 Shawn Pearce, et. al.
16 This program is free software; you can redistribute it and/or modify
17 it under the terms of the GNU General Public License as published by
18 the Free Software Foundation; either version 2 of the License, or
19 (at your option) any later version.
21 This program is distributed in the hope that it will be useful,
22 but WITHOUT ANY WARRANTY; without even the implied warranty of
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 GNU General Public License for more details.
26 You should have received a copy of the GNU General Public License
27 along with this program; if not, see <http://www.gnu.org/licenses/>.}]
29 ######################################################################
31 ## Tcl/Tk sanity check
33 if {[catch {package require Tcl 8.5} err]
34 || [catch {package require Tk 8.5} err]
40 -title "git-gui: fatal error" \
45 catch {rename send {}} ; # What an evil concept...
47 ######################################################################
51 if { [info exists ::env(GIT_GUI_LIB_DIR) ] } {
52 set oguilib $::env(GIT_GUI_LIB_DIR)
54 set oguilib {@@GITGUI_LIBDIR@@}
56 set oguirel {@@GITGUI_RELATIVE@@}
57 if {$oguirel eq {1}} {
58 set oguilib [file dirname [file normalize $argv0]]
59 if {[file tail $oguilib] eq {git-core}} {
60 set oguilib [file dirname $oguilib]
62 set oguilib [file dirname $oguilib]
63 set oguilib [file join $oguilib share git-gui lib]
64 set oguimsg [file join $oguilib msgs]
65 } elseif {[string match @@* $oguirel]} {
66 set oguilib [file join [file dirname [file normalize $argv0]] lib]
67 set oguimsg [file join [file dirname [file normalize $argv0]] po]
69 set oguimsg [file join $oguilib msgs]
73 ######################################################################
75 ## enable verbose loading?
77 if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
79 rename auto_load real__auto_load
80 proc auto_load {name args} {
81 puts stderr "auto_load $name"
82 return [uplevel 1 real__auto_load $name $args]
84 rename source real__source
86 puts stderr "source $args"
87 uplevel 1 [linsert $args 0 real__source]
89 if {[tk windowingsystem] eq "win32"} { console show }
92 ######################################################################
94 ## Internationalization (i18n) through msgcat and gettext. See
95 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
97 package require msgcat
99 # Check for Windows 7 MUI language pack (missed by msgcat < 1.4.4)
100 if {[tk windowingsystem] eq "win32"
101 && [package vcompare [package provide msgcat] 1.4.4] < 0
103 proc _mc_update_locale {} {
104 set key {HKEY_CURRENT_USER\Control Panel\Desktop}
106 package require registry
107 set uilocale [registry get $key "PreferredUILanguages"]
108 msgcat::ConvertLocale [string map {- _} [lindex $uilocale 0]]
110 if {[string length $uilocale] > 0} {
111 msgcat::mclocale $uilocale
118 proc _mc_trim {fmt} {
119 set cmk [string first @@ $fmt]
121 return [string range $fmt 0 [expr {$cmk - 1}]]
126 proc mc {en_fmt args} {
127 set fmt [_mc_trim [::msgcat::mc $en_fmt]]
128 if {[catch {set msg [eval [list format $fmt] $args]} err]} {
129 set msg [eval [list format [_mc_trim $en_fmt]] $args]
135 return [join $args {}]
138 ::msgcat::mcload $oguimsg
141 ######################################################################
143 ## On Mac, bring the current Wish process window to front
145 if {[tk windowingsystem] eq "aqua"} {
147 exec osascript -e [format {
148 tell application "System Events"
149 set frontmost of processes whose unix id is %d to true
155 ######################################################################
159 set _appname {Git Gui}
168 set _shellpath {@@SHELL_PATH@@}
170 set _trace [lsearch -exact $argv --trace]
172 set argv [lreplace $argv $_trace $_trace]
174 if {[tk windowingsystem] eq "win32"} { console show }
179 # variable for the last merged branch (useful for a default when deleting
181 set _last_merged_branch {}
184 global _shellpath env
185 if {[string match @@* $_shellpath]} {
186 if {[info exists env(SHELL)]} {
205 return [eval [list file join $_gitdir] $args]
208 proc gitexec {args} {
210 if {$_gitexec eq {}} {
211 if {[catch {set _gitexec [git --exec-path]} err]} {
212 error "Git not installed?\n\n$err"
215 set _gitexec [exec cygpath \
220 set _gitexec [file normalize $_gitexec]
226 return [eval [list file join $_gitexec] $args]
229 proc githtmldir {args} {
231 if {$_githtmldir eq {}} {
232 if {[catch {set _githtmldir [git --html-path]}]} {
233 # Git not installed or option not yet supported
237 set _githtmldir [exec cygpath \
242 set _githtmldir [file normalize $_githtmldir]
248 return [eval [list file join $_githtmldir] $args]
256 if {[tk windowingsystem] eq {aqua}} {
263 if {$::tcl_platform(platform) eq {windows}} {
271 if {$_iscygwin eq {}} {
272 if {$::tcl_platform(platform) eq {windows}} {
273 if {[catch {set p [exec cygpath --windir]} err]} {
277 # Handle MSys2 which is only cygwin when MSYSTEM is MSYS.
278 if {[info exists ::env(MSYSTEM)] && $::env(MSYSTEM) ne "MSYS"} {
289 proc is_enabled {option} {
290 global enabled_options
291 if {[catch {set on $enabled_options($option)}]} {return 0}
295 proc enable_option {option} {
296 global enabled_options
297 set enabled_options($option) 1
300 proc disable_option {option} {
301 global enabled_options
302 set enabled_options($option) 0
305 ######################################################################
309 proc is_many_config {name} {
310 switch -glob -- $name {
320 proc is_config_true {name} {
322 if {[catch {set v $repo_config($name)}]} {
325 set v [string tolower $v]
326 if {$v eq {} || $v eq {true} || $v eq {1} || $v eq {yes} || $v eq {on}} {
333 proc is_config_false {name} {
335 if {[catch {set v $repo_config($name)}]} {
338 set v [string tolower $v]
339 if {$v eq {false} || $v eq {0} || $v eq {no} || $v eq {off}} {
346 proc get_config {name} {
348 if {[catch {set v $repo_config($name)}]} {
360 if {$_isbare eq {}} {
362 set _bare [git rev-parse --is-bare-repository]
364 true { set _isbare 1 }
365 false { set _isbare 0}
369 if {[is_config_true core.bare]
370 || ($_gitworktree eq {}
371 && [lindex [file split $_gitdir] end] ne {.git})} {
381 ######################################################################
385 proc _trace_exec {cmd} {
386 if {!$::_trace} return
392 if {[regexp {[ \t\r\n'"$?*]} $v]} {
400 #'" fix poor old emacs font-lock mode
402 proc _git_cmd {name} {
405 if {[catch {set v $_git_cmd_path($name)}]} {
409 --exec-path { return [list $::_git $name] }
412 set p [gitexec git-$name$::_search_exe]
413 if {[file exists $p]} {
415 } elseif {[is_Windows] && [file exists [gitexec git-$name]]} {
416 # Try to determine what sort of magic will make
417 # git-$name go and do its thing, because native
418 # Tcl on Windows doesn't know it.
420 set p [gitexec git-$name]
425 switch -glob -- [lindex $s 0] {
427 #!*perl { set i perl }
428 #!*python { set i python }
429 default { error "git-$name is not supported: $s" }
433 if {![info exists interp]} {
434 set interp [_which $i]
437 error "git-$name requires $i (not in PATH)"
439 set v [concat [list $interp] [lrange $s 1 end] [list $p]]
441 # Assume it is builtin to git somehow and we
442 # aren't actually able to see a file for it.
444 set v [list $::_git $name]
446 set _git_cmd_path($name) $v
451 proc _which {what args} {
452 global env _search_exe _search_path
454 if {$_search_path eq {}} {
455 if {[is_Cygwin] && [regexp {^(/|\.:)} $env(PATH)]} {
456 set _search_path [split [exec cygpath \
462 } elseif {[is_Windows]} {
463 set gitguidir [file dirname [info script]]
464 regsub -all ";" $gitguidir "\\;" gitguidir
465 set env(PATH) "$gitguidir;$env(PATH)"
466 set _search_path [split $env(PATH) {;}]
469 set _search_path [split $env(PATH) :]
474 if {[is_Windows] && [lsearch -exact $args -script] >= 0} {
477 set suffix $_search_exe
480 foreach p $_search_path {
481 set p [file join $p $what$suffix]
482 if {[file exists $p]} {
483 return [file normalize $p]
489 # Test a file for a hashbang to identify executable scripts on Windows.
490 proc is_shellscript {filename} {
491 if {![file exists $filename]} {return 0}
492 set f [open $filename r]
493 fconfigure $f -encoding binary
494 set magic [read $f 2]
496 return [expr {$magic eq "#!"}]
499 # Run a command connected via pipes on stdout.
500 # This is for use with textconv filters and uses sh -c "..." to allow it to
501 # contain a command with arguments. On windows we must check for shell
502 # scripts specifically otherwise just call the filter command.
503 proc open_cmd_pipe {cmd path} {
505 if {![file executable [shellpath]]} {
506 set exe [auto_execok [lindex $cmd 0]]
507 if {[is_shellscript [lindex $exe 0]]} {
508 set run [linsert [auto_execok sh] end -c "$cmd \"\$0\"" $path]
510 set run [concat $exe [lrange $cmd 1 end] $path]
513 set run [list [shellpath] -c "$cmd \"\$0\"" $path]
515 return [open |$run r]
518 proc _lappend_nice {cmd_var} {
522 if {![info exists _nice]} {
523 set _nice [_which nice]
524 if {[catch {exec $_nice git version}]} {
526 } elseif {[is_Windows] && [file dirname $_nice] ne [file dirname $::_git]} {
536 set fd [eval [list git_read] $args]
537 fconfigure $fd -translation binary -encoding utf-8
538 set result [string trimright [read $fd] "\n"]
541 puts stderr "< $result"
546 proc _open_stdout_stderr {cmd} {
549 set fd [open [concat [list | ] $cmd] r]
551 if { [lindex $cmd end] eq {2>@1}
552 && $err eq {can not find channel named "1"}
554 # Older versions of Tcl 8.4 don't have this 2>@1 IO
555 # redirect operator. Fallback to |& cat for those.
556 # The command was not actually started, so its safe
557 # to try to start it a second time.
559 set fd [open [concat \
561 [lrange $cmd 0 end-1] \
568 fconfigure $fd -eofchar {}
572 proc git_read {args} {
576 switch -- [lindex $args 0] {
591 set args [lrange $args 1 end]
594 set cmdp [_git_cmd [lindex $args 0]]
595 set args [lrange $args 1 end]
597 return [_open_stdout_stderr [concat $opt $cmdp $args]]
600 proc git_write {args} {
604 switch -- [lindex $args 0] {
615 set args [lrange $args 1 end]
618 set cmdp [_git_cmd [lindex $args 0]]
619 set args [lrange $args 1 end]
621 _trace_exec [concat $opt $cmdp $args]
622 return [open [concat [list | ] $opt $cmdp $args] w]
625 proc githook_read {hook_name args} {
626 set pchook [gitdir hooks $hook_name]
629 # On Windows [file executable] might lie so we need to ask
630 # the shell if the hook is executable. Yes that's annoying.
634 if {![info exists interp]} {
635 set interp [_which sh]
638 error "hook execution requires sh (not in PATH)"
641 set scr {if test -x "$1";then exec "$@";fi}
642 set sh_c [list $interp -c $scr $interp $pchook]
643 return [_open_stdout_stderr [concat $sh_c $args]]
646 if {[file executable $pchook]} {
647 return [_open_stdout_stderr [concat [list $pchook] $args]]
653 proc kill_file_process {fd} {
654 set process [pid $fd]
658 exec taskkill /pid $process
665 proc gitattr {path attr default} {
666 if {[catch {set r [git check-attr $attr -- $path]}]} {
669 set r [join [lrange [split $r :] 2 end] :]
672 if {$r eq {unspecified}} {
679 regsub -all ' $value "'\\''" value
683 proc load_current_branch {} {
684 global current_branch is_detached
686 set fd [open [gitdir HEAD] r]
687 fconfigure $fd -translation binary -encoding utf-8
688 if {[gets $fd ref] < 1} {
693 set pfx {ref: refs/heads/}
694 set len [string length $pfx]
695 if {[string equal -length $len $pfx $ref]} {
696 # We're on a branch. It might not exist. But
697 # HEAD looks good enough to be a branch.
699 set current_branch [string range $ref $len end]
702 # Assume this is a detached head.
704 set current_branch HEAD
709 auto_load tk_optionMenu
710 rename tk_optionMenu real__tkOptionMenu
711 proc tk_optionMenu {w varName args} {
712 set m [eval real__tkOptionMenu $w $varName $args]
713 $m configure -font font_ui
714 $w configure -font font_ui
718 proc rmsel_tag {text} {
720 -background [$text cget -background] \
721 -foreground [$text cget -foreground] \
723 $text tag conf in_sel\
724 -background $color::select_bg \
725 -foreground $color::select_fg
726 bind $text <Motion> break
732 bind . <Visibility> {
733 bind . <Visibility> {}
738 wm iconbitmap . -default $oguilib/git-gui.ico
739 set ::tk::AlwaysShowSelection 1
740 bind . <Control-F2> {console show}
742 # Spoof an X11 display for SSH
743 if {![info exists env(DISPLAY)]} {
744 set env(DISPLAY) :9999
748 image create photo gitlogo -width 16 -height 16
750 gitlogo put #33CC33 -to 7 0 9 2
751 gitlogo put #33CC33 -to 4 2 12 4
752 gitlogo put #33CC33 -to 7 4 9 6
753 gitlogo put #CC3333 -to 4 6 12 8
754 gitlogo put gray26 -to 4 9 6 10
755 gitlogo put gray26 -to 3 10 6 12
756 gitlogo put gray26 -to 8 9 13 11
757 gitlogo put gray26 -to 8 11 10 12
758 gitlogo put gray26 -to 11 11 13 14
759 gitlogo put gray26 -to 3 12 5 14
760 gitlogo put gray26 -to 5 13
761 gitlogo put gray26 -to 10 13
762 gitlogo put gray26 -to 4 14 12 15
763 gitlogo put gray26 -to 5 15 11 16
766 image create photo gitlogo32 -width 32 -height 32
767 gitlogo32 copy gitlogo -zoom 2 2
769 wm iconphoto . -default gitlogo gitlogo32
773 ######################################################################
779 if {[lsearch -exact [font names] TkDefaultFont] != -1} {
780 eval [linsert [font actual TkDefaultFont] 0 font configure font_ui]
781 eval [linsert [font actual TkFixedFont] 0 font create font_diff]
783 font create font_diff -family Courier -size 10
786 eval font configure font_ui [font actual [.dummy cget -font]]
791 font create font_uiitalic
792 font create font_uibold
793 font create font_diffbold
794 font create font_diffitalic
796 foreach class {Button Checkbutton Entry Label
797 Labelframe Listbox Message
798 Radiobutton Spinbox Text} {
799 option add *$class.font font_ui
802 option add *Menu.font font_ui
803 option add *Entry.borderWidth 1 startupFile
804 option add *Entry.relief sunken startupFile
805 option add *RadioButton.anchor w startupFile
809 if {[is_Windows] || [is_MacOSX]} {
810 option add *Menu.tearOff 0
821 proc bind_button3 {w cmd} {
822 bind $w <Any-Button-3> $cmd
824 # Mac OS X sends Button-2 on right click through three-button mouse,
825 # or through trackpad right-clicking (two-finger touch + click).
826 bind $w <Any-Button-2> $cmd
827 bind $w <Control-Button-1> $cmd
831 proc apply_config {} {
832 global repo_config font_descs
834 foreach option $font_descs {
835 set name [lindex $option 0]
836 set font [lindex $option 1]
839 foreach {cn cv} $repo_config(gui.$name) {
840 if {$cn eq {-weight}} {
843 font configure $font $cn $cv
846 font configure $font -weight normal
849 error_popup [strcat [mc "Invalid font specified in %s:" "gui.$name"] "\n\n$err"]
851 foreach {cn cv} [font configure $font] {
852 font configure ${font}bold $cn $cv
853 font configure ${font}italic $cn $cv
855 font configure ${font}bold -weight bold
856 font configure ${font}italic -slant italic
862 if {$repo_config(gui.usettk)} {
863 set use_ttk [package vsatisfies [package provide Tk] 8.5]
866 bind [winfo class .] <<ThemeChanged>> [list InitTheme]
868 color::sync_with_theme
873 set default_config(branch.autosetupmerge) true
874 set default_config(merge.tool) {}
875 set default_config(mergetool.keepbackup) true
876 set default_config(merge.diffstat) true
877 set default_config(merge.summary) false
878 set default_config(merge.verbosity) 2
879 set default_config(user.name) {}
880 set default_config(user.email) {}
882 set default_config(gui.encoding) [encoding system]
883 set default_config(gui.matchtrackingbranch) false
884 set default_config(gui.textconv) true
885 set default_config(gui.pruneduringfetch) false
886 set default_config(gui.trustmtime) false
887 set default_config(gui.fastcopyblame) false
888 set default_config(gui.maxrecentrepo) 10
889 set default_config(gui.copyblamethreshold) 40
890 set default_config(gui.blamehistoryctx) 7
891 set default_config(gui.diffcontext) 5
892 set default_config(gui.diffopts) {}
893 set default_config(gui.commitmsgwidth) 75
894 set default_config(gui.newbranchtemplate) {}
895 set default_config(gui.spellingdictionary) {}
896 set default_config(gui.fontui) [font configure font_ui]
897 set default_config(gui.fontdiff) [font configure font_diff]
898 # TODO: this option should be added to the git-config documentation
899 set default_config(gui.maxfilesdisplayed) 5000
900 set default_config(gui.usettk) 1
901 set default_config(gui.warndetachedcommit) 1
902 set default_config(gui.tabsize) 8
904 {fontui font_ui {mc "Main Font"}}
905 {fontdiff font_diff {mc "Diff/Console Font"}}
907 set default_config(gui.stageuntracked) ask
908 set default_config(gui.displayuntracked) true
910 ######################################################################
914 set _git [_which git]
916 catch {wm withdraw .}
920 -title [mc "git-gui: fatal error"] \
921 -message [mc "Cannot find git in PATH."]
925 ######################################################################
929 if {[catch {set _git_version [git --version]} err]} {
930 catch {wm withdraw .}
934 -title [mc "git-gui: fatal error"] \
935 -message "Cannot determine Git version:
939 [appname] requires Git 1.5.0 or later."
942 if {![regsub {^git version } $_git_version {} _git_version]} {
943 catch {wm withdraw .}
947 -title [mc "git-gui: fatal error"] \
948 -message [strcat [mc "Cannot parse Git version string:"] "\n\n$_git_version"]
952 proc get_trimmed_version {s} {
954 foreach x [split $s -._] {
955 if {[string is integer -strict $x]} {
963 set _real_git_version $_git_version
964 set _git_version [get_trimmed_version $_git_version]
966 if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} {
967 catch {wm withdraw .}
972 -title "[appname]: warning" \
973 -message [mc "Git version cannot be determined.
975 %s claims it is version '%s'.
977 %s requires at least Git 1.5.0 or later.
979 Assume '%s' is version 1.5.0?
980 " $_git $_real_git_version [appname] $_real_git_version]] eq {yes}} {
981 set _git_version 1.5.0
986 unset _real_git_version
988 proc git-version {args} {
991 switch [llength $args] {
997 set op [lindex $args 0]
998 set vr [lindex $args 1]
999 set cm [package vcompare $_git_version $vr]
1000 return [expr $cm $op 0]
1004 set type [lindex $args 0]
1005 set name [lindex $args 1]
1006 set parm [lindex $args 2]
1007 set body [lindex $args 3]
1009 if {($type ne {proc} && $type ne {method})} {
1010 error "Invalid arguments to git-version"
1012 if {[llength $body] < 2 || [lindex $body end-1] ne {default}} {
1013 error "Last arm of $type $name must be default"
1016 foreach {op vr cb} [lrange $body 0 end-2] {
1017 if {[git-version $op $vr]} {
1018 return [uplevel [list $type $name $parm $cb]]
1022 return [uplevel [list $type $name $parm [lindex $body end]]]
1026 error "git-version >= x"
1032 if {[git-version < 1.5]} {
1033 catch {wm withdraw .}
1037 -title [mc "git-gui: fatal error"] \
1038 -message "[appname] requires Git 1.5.0 or later.
1040 You are using [git-version]:
1046 ######################################################################
1048 ## configure our library
1050 set idx [file join $oguilib tclIndex]
1051 if {[catch {set fd [open $idx r]} err]} {
1052 catch {wm withdraw .}
1056 -title [mc "git-gui: fatal error"] \
1060 if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
1062 while {[gets $fd n] >= 0} {
1063 if {$n ne {} && ![string match #* $n]} {
1075 if {[lsearch -exact $loaded $p] >= 0} continue
1076 source [file join $oguilib $p]
1081 set auto_path [concat [list $oguilib] $auto_path]
1083 unset -nocomplain idx fd
1085 ######################################################################
1087 ## config file parsing
1089 git-version proc _parse_config {arr_name args} {
1096 [list git_read config] \
1098 [list --null --list]]
1099 fconfigure $fd_rc -translation binary -encoding utf-8
1100 set buf [read $fd_rc]
1103 foreach line [split $buf "\0"] {
1104 if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} {
1105 if {[is_many_config $name]} {
1106 lappend arr($name) $value
1108 set arr($name) $value
1110 } elseif {[regexp {^([^\n]+)$} $line line name]} {
1111 # no value given, but interpreting them as
1112 # boolean will be handled as true
1121 set fd_rc [eval [list git_read config --list] $args]
1122 while {[gets $fd_rc line] >= 0} {
1123 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
1124 if {[is_many_config $name]} {
1125 lappend arr($name) $value
1127 set arr($name) $value
1129 } elseif {[regexp {^([^=]+)$} $line line name]} {
1130 # no value given, but interpreting them as
1131 # boolean will be handled as true
1140 proc load_config {include_global} {
1141 global repo_config global_config system_config default_config
1143 if {$include_global} {
1144 _parse_config system_config --system
1145 _parse_config global_config --global
1147 _parse_config repo_config
1149 foreach name [array names default_config] {
1150 if {[catch {set v $system_config($name)}]} {
1151 set system_config($name) $default_config($name)
1154 foreach name [array names system_config] {
1155 if {[catch {set v $global_config($name)}]} {
1156 set global_config($name) $system_config($name)
1158 if {[catch {set v $repo_config($name)}]} {
1159 set repo_config($name) $system_config($name)
1164 ######################################################################
1166 ## feature option selection
1168 if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} {
1173 if {$subcommand eq {gui.sh}} {
1176 if {$subcommand eq {gui} && [llength $argv] > 0} {
1177 set subcommand [lindex $argv 0]
1178 set argv [lrange $argv 1 end]
1181 enable_option multicommit
1182 enable_option branch
1183 enable_option transport
1186 switch -- $subcommand {
1191 disable_option multicommit
1192 disable_option branch
1193 disable_option transport
1196 enable_option singlecommit
1197 enable_option retcode
1199 disable_option multicommit
1200 disable_option branch
1201 disable_option transport
1203 while {[llength $argv] > 0} {
1204 set a [lindex $argv 0]
1207 enable_option initialamend
1210 enable_option nocommit
1211 enable_option nocommitmsg
1214 disable_option nocommitmsg
1221 set argv [lrange $argv 1 end]
1226 ######################################################################
1228 ## execution environment
1230 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
1232 # Suggest our implementation of askpass, if none is set
1233 if {![info exists env(SSH_ASKPASS)]} {
1234 set env(SSH_ASKPASS) [gitexec git-gui--askpass]
1237 ######################################################################
1243 set _gitdir $env(GIT_DIR)
1247 # beware that from the .git dir this sets _gitdir to .
1248 # and _prefix to the empty string
1249 set _gitdir [git rev-parse --git-dir]
1250 set _prefix [git rev-parse --show-prefix]
1254 choose_repository::pick
1258 # we expand the _gitdir when it's just a single dot (i.e. when we're being
1259 # run from the .git dir itself) lest the routines to find the worktree
1261 if {$_gitdir eq "."} {
1265 if {![file isdirectory $_gitdir] && [is_Cygwin]} {
1266 catch {set _gitdir [exec cygpath --windows $_gitdir]}
1268 if {![file isdirectory $_gitdir]} {
1269 catch {wm withdraw .}
1270 error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"]
1273 # _gitdir exists, so try loading the config
1277 # v1.7.0 introduced --show-toplevel to return the canonical work-tree
1278 if {[package vcompare $_git_version 1.7.0] >= 0} {
1279 if { [is_Cygwin] } {
1280 catch {set _gitworktree [exec cygpath --windows [git rev-parse --show-toplevel]]}
1282 set _gitworktree [git rev-parse --show-toplevel]
1285 # try to set work tree from environment, core.worktree or use
1286 # cdup to obtain a relative path to the top of the worktree. If
1287 # run from the top, the ./ prefix ensures normalize expands pwd.
1288 if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} {
1289 set _gitworktree [get_config core.worktree]
1290 if {$_gitworktree eq ""} {
1291 set _gitworktree [file normalize ./[git rev-parse --show-cdup]]
1296 if {$_prefix ne {}} {
1297 if {$_gitworktree eq {}} {
1298 regsub -all {[^/]+/} $_prefix ../ cdup
1300 set cdup $_gitworktree
1302 if {[catch {cd $cdup} err]} {
1303 catch {wm withdraw .}
1304 error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"]
1307 set _gitworktree [pwd]
1309 } elseif {![is_enabled bare]} {
1311 catch {wm withdraw .}
1312 error_popup [strcat [mc "Cannot use bare repository:"] "\n\n$_gitdir"]
1315 if {$_gitworktree eq {}} {
1316 set _gitworktree [file dirname $_gitdir]
1318 if {[catch {cd $_gitworktree} err]} {
1319 catch {wm withdraw .}
1320 error_popup [strcat [mc "No working directory"] " $_gitworktree:\n\n$err"]
1323 set _gitworktree [pwd]
1325 set _reponame [file split [file normalize $_gitdir]]
1326 if {[lindex $_reponame end] eq {.git}} {
1327 set _reponame [lindex $_reponame end-1]
1329 set _reponame [lindex $_reponame end]
1332 set env(GIT_DIR) $_gitdir
1333 set env(GIT_WORK_TREE) $_gitworktree
1335 ######################################################################
1339 set current_diff_path {}
1340 set current_diff_side {}
1341 set diff_actions [list]
1345 set MERGE_HEAD [list]
1347 set commit_type_is_amend 0
1349 set current_branch {}
1351 set current_diff_path {}
1353 set is_submodule_diff 0
1354 set is_conflict_diff 0
1355 set diff_empty_count 0
1357 set last_revert_enc {}
1359 set nullid "0000000000000000000000000000000000000000"
1360 set nullid2 "0000000000000000000000000000000000000001"
1362 ######################################################################
1370 set disable_on_lock [list]
1371 set index_lock_type none
1373 proc lock_index {type} {
1374 global index_lock_type disable_on_lock
1376 if {$index_lock_type eq {none}} {
1377 set index_lock_type $type
1378 foreach w $disable_on_lock {
1379 uplevel #0 $w disabled
1382 } elseif {$index_lock_type eq "begin-$type"} {
1383 set index_lock_type $type
1389 proc unlock_index {} {
1390 global index_lock_type disable_on_lock
1392 set index_lock_type none
1393 foreach w $disable_on_lock {
1394 uplevel #0 $w normal
1398 ######################################################################
1402 proc repository_state {ctvar hdvar mhvar} {
1403 global current_branch
1404 upvar $ctvar ct $hdvar hd $mhvar mh
1409 if {[catch {set hd [git rev-parse --verify HEAD]}]} {
1415 set merge_head [gitdir MERGE_HEAD]
1416 if {[file exists $merge_head]} {
1418 set fd_mh [open $merge_head r]
1419 while {[gets $fd_mh line] >= 0} {
1430 global PARENT empty_tree
1432 set p [lindex $PARENT 0]
1436 if {$empty_tree eq {}} {
1437 set empty_tree [git mktree << {}]
1442 proc force_amend {} {
1443 global commit_type_is_amend
1444 global HEAD PARENT MERGE_HEAD commit_type
1446 repository_state newType newHEAD newMERGE_HEAD
1449 set MERGE_HEAD $newMERGE_HEAD
1450 set commit_type $newType
1452 set commit_type_is_amend 1
1453 do_select_commit_type
1456 proc rescan {after {honor_trustmtime 1}} {
1457 global HEAD PARENT MERGE_HEAD commit_type
1458 global ui_index ui_workdir ui_comm
1459 global rescan_active file_states
1462 if {$rescan_active > 0 || ![lock_index read]} return
1464 repository_state newType newHEAD newMERGE_HEAD
1465 if {[string match amend* $commit_type]
1466 && $newType eq {normal}
1467 && $newHEAD eq $HEAD} {
1471 set MERGE_HEAD $newMERGE_HEAD
1472 set commit_type $newType
1475 array unset file_states
1477 if {!$::GITGUI_BCK_exists &&
1478 (![$ui_comm edit modified]
1479 || [string trim [$ui_comm get 0.0 end]] eq {})} {
1480 if {[string match amend* $commit_type]} {
1481 } elseif {[load_message GITGUI_MSG utf-8]} {
1482 } elseif {[run_prepare_commit_msg_hook]} {
1483 } elseif {[load_message MERGE_MSG]} {
1484 } elseif {[load_message SQUASH_MSG]} {
1487 $ui_comm edit modified false
1490 if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
1491 rescan_stage2 {} $after
1494 ui_status [mc "Refreshing file status..."]
1495 set fd_rf [git_read update-index \
1501 fconfigure $fd_rf -blocking 0 -translation binary
1502 fileevent $fd_rf readable \
1503 [list rescan_stage2 $fd_rf $after]
1508 set is_git_info_exclude {}
1509 proc have_info_exclude {} {
1510 global is_git_info_exclude
1512 if {$is_git_info_exclude eq {}} {
1513 if {[catch {exec test -f [gitdir info exclude]}]} {
1514 set is_git_info_exclude 0
1516 set is_git_info_exclude 1
1519 return $is_git_info_exclude
1522 proc have_info_exclude {} {
1523 return [file readable [gitdir info exclude]]
1527 proc rescan_stage2 {fd after} {
1528 global rescan_active buf_rdi buf_rdf buf_rlo
1532 if {![eof $fd]} return
1536 if {[package vcompare $::_git_version 1.6.3] >= 0} {
1537 set ls_others [list --exclude-standard]
1539 set ls_others [list --exclude-per-directory=.gitignore]
1540 if {[have_info_exclude]} {
1541 lappend ls_others "--exclude-from=[gitdir info exclude]"
1543 set user_exclude [get_config core.excludesfile]
1544 if {$user_exclude ne {} && [file readable $user_exclude]} {
1545 lappend ls_others "--exclude-from=[file normalize $user_exclude]"
1554 ui_status [mc "Scanning for modified files ..."]
1555 if {[git-version >= "1.7.2"]} {
1556 set fd_di [git_read diff-index --cached --ignore-submodules=dirty -z [PARENT]]
1558 set fd_di [git_read diff-index --cached -z [PARENT]]
1560 set fd_df [git_read diff-files -z]
1562 fconfigure $fd_di -blocking 0 -translation binary -encoding binary
1563 fconfigure $fd_df -blocking 0 -translation binary -encoding binary
1565 fileevent $fd_di readable [list read_diff_index $fd_di $after]
1566 fileevent $fd_df readable [list read_diff_files $fd_df $after]
1568 if {[is_config_true gui.displayuntracked]} {
1569 set fd_lo [eval git_read ls-files --others -z $ls_others]
1570 fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
1571 fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
1576 proc load_message {file {encoding {}}} {
1579 set f [gitdir $file]
1580 if {[file isfile $f]} {
1581 if {[catch {set fd [open $f r]}]} {
1584 fconfigure $fd -eofchar {}
1585 if {$encoding ne {}} {
1586 fconfigure $fd -encoding $encoding
1588 set content [string trim [read $fd]]
1590 regsub -all -line {[ \r\t]+$} $content {} content
1591 $ui_comm delete 0.0 end
1592 $ui_comm insert end $content
1598 proc run_prepare_commit_msg_hook {} {
1601 # prepare-commit-msg requires PREPARE_COMMIT_MSG exist. From git-gui
1602 # it will be .git/MERGE_MSG (merge), .git/SQUASH_MSG (squash), or an
1603 # empty file but existent file.
1605 set fd_pcm [open [gitdir PREPARE_COMMIT_MSG] a]
1607 if {[file isfile [gitdir MERGE_MSG]]} {
1608 set pcm_source "merge"
1609 set fd_mm [open [gitdir MERGE_MSG] r]
1610 fconfigure $fd_mm -encoding utf-8
1611 puts -nonewline $fd_pcm [read $fd_mm]
1613 } elseif {[file isfile [gitdir SQUASH_MSG]]} {
1614 set pcm_source "squash"
1615 set fd_sm [open [gitdir SQUASH_MSG] r]
1616 fconfigure $fd_sm -encoding utf-8
1617 puts -nonewline $fd_pcm [read $fd_sm]
1625 set fd_ph [githook_read prepare-commit-msg \
1626 [gitdir PREPARE_COMMIT_MSG] $pcm_source]
1628 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1632 ui_status [mc "Calling prepare-commit-msg hook..."]
1635 fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
1636 fileevent $fd_ph readable \
1637 [list prepare_commit_msg_hook_wait $fd_ph]
1642 proc prepare_commit_msg_hook_wait {fd_ph} {
1645 append pch_error [read $fd_ph]
1646 fconfigure $fd_ph -blocking 1
1648 if {[catch {close $fd_ph}]} {
1649 ui_status [mc "Commit declined by prepare-commit-msg hook."]
1650 hook_failed_popup prepare-commit-msg $pch_error
1651 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1654 load_message PREPARE_COMMIT_MSG
1657 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1660 fconfigure $fd_ph -blocking 0
1661 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1664 proc read_diff_index {fd after} {
1667 append buf_rdi [read $fd]
1669 set n [string length $buf_rdi]
1671 set z1 [string first "\0" $buf_rdi $c]
1672 if {$z1 == -1} break
1674 set z2 [string first "\0" $buf_rdi $z1]
1675 if {$z2 == -1} break
1678 set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
1679 set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
1681 [encoding convertfrom utf-8 $p] \
1683 [list [lindex $i 0] [lindex $i 2]] \
1689 set buf_rdi [string range $buf_rdi $c end]
1694 rescan_done $fd buf_rdi $after
1697 proc read_diff_files {fd after} {
1700 append buf_rdf [read $fd]
1702 set n [string length $buf_rdf]
1704 set z1 [string first "\0" $buf_rdf $c]
1705 if {$z1 == -1} break
1707 set z2 [string first "\0" $buf_rdf $z1]
1708 if {$z2 == -1} break
1711 set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
1712 set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
1714 [encoding convertfrom utf-8 $p] \
1717 [list [lindex $i 0] [lindex $i 2]]
1722 set buf_rdf [string range $buf_rdf $c end]
1727 rescan_done $fd buf_rdf $after
1730 proc read_ls_others {fd after} {
1733 append buf_rlo [read $fd]
1734 set pck [split $buf_rlo "\0"]
1735 set buf_rlo [lindex $pck end]
1736 foreach p [lrange $pck 0 end-1] {
1737 set p [encoding convertfrom utf-8 $p]
1738 if {[string index $p end] eq {/}} {
1739 set p [string range $p 0 end-1]
1743 rescan_done $fd buf_rlo $after
1746 proc rescan_done {fd buf after} {
1747 global rescan_active current_diff_path
1748 global file_states repo_config
1751 if {![eof $fd]} return
1754 if {[incr rescan_active -1] > 0} return
1759 if {$current_diff_path ne {}} { reshow_diff $after }
1760 if {$current_diff_path eq {}} { select_first_diff $after }
1763 proc prune_selection {} {
1764 global file_states selected_paths
1766 foreach path [array names selected_paths] {
1767 if {[catch {set still_here $file_states($path)}]} {
1768 unset selected_paths($path)
1773 ######################################################################
1777 proc mapicon {w state path} {
1780 if {[catch {set r $all_icons($state$w)}]} {
1781 puts "error: no icon for $w state={$state} $path"
1787 proc mapdesc {state path} {
1790 if {[catch {set r $all_descs($state)}]} {
1791 puts "error: no desc for state={$state} $path"
1797 proc ui_status {msg} {
1799 if {[info exists main_status]} {
1800 $main_status show $msg
1806 if {[info exists main_status]} {
1807 $main_status show [mc "Ready."]
1811 proc escape_path {path} {
1812 regsub -all {\\} $path "\\\\" path
1813 regsub -all "\n" $path "\\n" path
1817 proc short_path {path} {
1818 return [escape_path [lindex [file split $path] end]]
1822 set null_sha1 [string repeat 0 40]
1824 proc merge_state {path new_state {head_info {}} {index_info {}}} {
1825 global file_states next_icon_id null_sha1
1827 set s0 [string index $new_state 0]
1828 set s1 [string index $new_state 1]
1830 if {[catch {set info $file_states($path)}]} {
1832 set icon n[incr next_icon_id]
1834 set state [lindex $info 0]
1835 set icon [lindex $info 1]
1836 if {$head_info eq {}} {set head_info [lindex $info 2]}
1837 if {$index_info eq {}} {set index_info [lindex $info 3]}
1840 if {$s0 eq {?}} {set s0 [string index $state 0]} \
1841 elseif {$s0 eq {_}} {set s0 _}
1843 if {$s1 eq {?}} {set s1 [string index $state 1]} \
1844 elseif {$s1 eq {_}} {set s1 _}
1846 if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
1847 set head_info [list 0 $null_sha1]
1848 } elseif {$s0 ne {_} && [string index $state 0] eq {_}
1849 && $head_info eq {}} {
1850 set head_info $index_info
1851 } elseif {$s0 eq {_} && [string index $state 0] ne {_}} {
1852 set index_info $head_info
1856 set file_states($path) [list $s0$s1 $icon \
1857 $head_info $index_info \
1862 proc display_file_helper {w path icon_name old_m new_m} {
1865 if {$new_m eq {_}} {
1866 set lno [lsearch -sorted -exact $file_lists($w) $path]
1868 set file_lists($w) [lreplace $file_lists($w) $lno $lno]
1870 $w conf -state normal
1871 $w delete $lno.0 [expr {$lno + 1}].0
1872 $w conf -state disabled
1874 } elseif {$old_m eq {_} && $new_m ne {_}} {
1875 lappend file_lists($w) $path
1876 set file_lists($w) [lsort -unique $file_lists($w)]
1877 set lno [lsearch -sorted -exact $file_lists($w) $path]
1879 $w conf -state normal
1880 $w image create $lno.0 \
1881 -align center -padx 5 -pady 1 \
1883 -image [mapicon $w $new_m $path]
1884 $w insert $lno.1 "[escape_path $path]\n"
1885 $w conf -state disabled
1886 } elseif {$old_m ne $new_m} {
1887 $w conf -state normal
1888 $w image conf $icon_name -image [mapicon $w $new_m $path]
1889 $w conf -state disabled
1893 proc display_file {path state} {
1894 global file_states selected_paths
1895 global ui_index ui_workdir
1897 set old_m [merge_state $path $state]
1898 set s $file_states($path)
1899 set new_m [lindex $s 0]
1900 set icon_name [lindex $s 1]
1902 set o [string index $old_m 0]
1903 set n [string index $new_m 0]
1910 display_file_helper $ui_index $path $icon_name $o $n
1912 if {[string index $old_m 0] eq {U}} {
1915 set o [string index $old_m 1]
1917 if {[string index $new_m 0] eq {U}} {
1920 set n [string index $new_m 1]
1922 display_file_helper $ui_workdir $path $icon_name $o $n
1924 if {$new_m eq {__}} {
1925 unset file_states($path)
1926 catch {unset selected_paths($path)}
1930 proc display_all_files_helper {w path icon_name m} {
1933 lappend file_lists($w) $path
1934 set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
1935 $w image create end \
1936 -align center -padx 5 -pady 1 \
1938 -image [mapicon $w $m $path]
1939 $w insert end "[escape_path $path]\n"
1943 proc display_all_files {} {
1944 global ui_index ui_workdir
1945 global file_states file_lists
1947 global files_warning
1949 $ui_index conf -state normal
1950 $ui_workdir conf -state normal
1952 $ui_index delete 0.0 end
1953 $ui_workdir delete 0.0 end
1956 set file_lists($ui_index) [list]
1957 set file_lists($ui_workdir) [list]
1959 set to_display [lsort [array names file_states]]
1960 set display_limit [get_config gui.maxfilesdisplayed]
1962 foreach path $to_display {
1963 set s $file_states($path)
1965 set icon_name [lindex $s 1]
1967 if {$displayed > $display_limit && [string index $m 1] eq {O} } {
1968 if {!$files_warning} {
1969 # do not repeatedly warn:
1971 info_popup [mc "Display limit (gui.maxfilesdisplayed = %s) reached, not showing all %s files." \
1972 $display_limit [llength $to_display]]
1977 set s [string index $m 0]
1978 if {$s ne {U} && $s ne {_}} {
1979 display_all_files_helper $ui_index $path \
1983 if {[string index $m 0] eq {U}} {
1986 set s [string index $m 1]
1989 display_all_files_helper $ui_workdir $path \
1995 $ui_index conf -state disabled
1996 $ui_workdir conf -state disabled
1999 ######################################################################
2004 #define mask_width 14
2005 #define mask_height 15
2006 static unsigned char mask_bits[] = {
2007 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
2008 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
2009 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
2012 image create bitmap file_plain -background white -foreground black -data {
2013 #define plain_width 14
2014 #define plain_height 15
2015 static unsigned char plain_bits[] = {
2016 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
2017 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
2018 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
2019 } -maskdata $filemask
2021 image create bitmap file_mod -background white -foreground blue -data {
2022 #define mod_width 14
2023 #define mod_height 15
2024 static unsigned char mod_bits[] = {
2025 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
2026 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
2027 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
2028 } -maskdata $filemask
2030 image create bitmap file_fulltick -background white -foreground "#007000" -data {
2031 #define file_fulltick_width 14
2032 #define file_fulltick_height 15
2033 static unsigned char file_fulltick_bits[] = {
2034 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
2035 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
2036 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
2037 } -maskdata $filemask
2039 image create bitmap file_question -background white -foreground black -data {
2040 #define file_question_width 14
2041 #define file_question_height 15
2042 static unsigned char file_question_bits[] = {
2043 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
2044 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
2045 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
2046 } -maskdata $filemask
2048 image create bitmap file_removed -background white -foreground red -data {
2049 #define file_removed_width 14
2050 #define file_removed_height 15
2051 static unsigned char file_removed_bits[] = {
2052 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
2053 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
2054 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
2055 } -maskdata $filemask
2057 image create bitmap file_merge -background white -foreground blue -data {
2058 #define file_merge_width 14
2059 #define file_merge_height 15
2060 static unsigned char file_merge_bits[] = {
2061 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
2062 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
2063 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
2064 } -maskdata $filemask
2066 image create bitmap file_statechange -background white -foreground green -data {
2067 #define file_statechange_width 14
2068 #define file_statechange_height 15
2069 static unsigned char file_statechange_bits[] = {
2070 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x62, 0x10,
2071 0x62, 0x10, 0xba, 0x11, 0xba, 0x11, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10,
2072 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
2073 } -maskdata $filemask
2075 set ui_index .vpane.files.index.list
2076 set ui_workdir .vpane.files.workdir.list
2078 set all_icons(_$ui_index) file_plain
2079 set all_icons(A$ui_index) file_plain
2080 set all_icons(M$ui_index) file_fulltick
2081 set all_icons(D$ui_index) file_removed
2082 set all_icons(U$ui_index) file_merge
2083 set all_icons(T$ui_index) file_statechange
2085 set all_icons(_$ui_workdir) file_plain
2086 set all_icons(M$ui_workdir) file_mod
2087 set all_icons(D$ui_workdir) file_question
2088 set all_icons(U$ui_workdir) file_merge
2089 set all_icons(O$ui_workdir) file_plain
2090 set all_icons(T$ui_workdir) file_statechange
2092 set max_status_desc 0
2094 {__ {mc "Unmodified"}}
2096 {_M {mc "Modified, not staged"}}
2097 {M_ {mc "Staged for commit"}}
2098 {MM {mc "Portions staged for commit"}}
2099 {MD {mc "Staged for commit, missing"}}
2101 {_T {mc "File type changed, not staged"}}
2102 {MT {mc "File type changed, old type staged for commit"}}
2103 {AT {mc "File type changed, old type staged for commit"}}
2104 {T_ {mc "File type changed, staged"}}
2105 {TM {mc "File type change staged, modification not staged"}}
2106 {TD {mc "File type change staged, file missing"}}
2108 {_O {mc "Untracked, not staged"}}
2109 {A_ {mc "Staged for commit"}}
2110 {AM {mc "Portions staged for commit"}}
2111 {AD {mc "Staged for commit, missing"}}
2114 {D_ {mc "Staged for removal"}}
2115 {DO {mc "Staged for removal, still present"}}
2117 {_U {mc "Requires merge resolution"}}
2118 {U_ {mc "Requires merge resolution"}}
2119 {UU {mc "Requires merge resolution"}}
2120 {UM {mc "Requires merge resolution"}}
2121 {UD {mc "Requires merge resolution"}}
2122 {UT {mc "Requires merge resolution"}}
2124 set text [eval [lindex $i 1]]
2125 if {$max_status_desc < [string length $text]} {
2126 set max_status_desc [string length $text]
2128 set all_descs([lindex $i 0]) $text
2132 ######################################################################
2136 proc scrollbar2many {list mode args} {
2137 foreach w $list {eval $w $mode $args}
2140 proc many2scrollbar {list mode sb top bottom} {
2141 $sb set $top $bottom
2142 foreach w $list {$w $mode moveto $top}
2145 proc incr_font_size {font {amt 1}} {
2146 set sz [font configure $font -size]
2148 font configure $font -size $sz
2149 font configure ${font}bold -size $sz
2150 font configure ${font}italic -size $sz
2153 ######################################################################
2157 proc do_gitk {revs {is_submodule false}} {
2158 global current_diff_path file_states current_diff_side ui_index
2159 global _gitdir _gitworktree
2161 # -- Always start gitk through whatever we were loaded with. This
2162 # lets us bypass using shell process on Windows systems.
2164 set exe [_which gitk -script]
2165 set cmd [list [info nameofexecutable] $exe]
2167 error_popup [mc "Couldn't find gitk in PATH"]
2173 if {!$is_submodule} {
2178 cd $current_diff_path
2179 if {$revs eq {--}} {
2180 set s $file_states($current_diff_path)
2183 switch -glob -- [lindex $s 0] {
2184 M_ { set old_sha1 [lindex [lindex $s 2] 1] }
2185 _M { set old_sha1 [lindex [lindex $s 3] 1] }
2187 if {$current_diff_side eq $ui_index} {
2188 set old_sha1 [lindex [lindex $s 2] 1]
2189 set new_sha1 [lindex [lindex $s 3] 1]
2191 set old_sha1 [lindex [lindex $s 3] 1]
2195 set revs $old_sha1...$new_sha1
2197 # GIT_DIR and GIT_WORK_TREE for the submodule are not the ones
2198 # we've been using for the main repository, so unset them.
2199 # TODO we could make life easier (start up faster?) for gitk
2200 # by setting these to the appropriate values to allow gitk
2201 # to skip the heuristics to find their proper value
2203 unset env(GIT_WORK_TREE)
2205 eval exec $cmd $revs "--" "--" &
2207 set env(GIT_DIR) $_gitdir
2208 set env(GIT_WORK_TREE) $_gitworktree
2211 if {[info exists main_status]} {
2212 set status_operation [$::main_status \
2214 [mc "Starting %s... please wait..." "gitk"]]
2216 after 3500 [list $status_operation stop]
2221 proc do_git_gui {} {
2222 global current_diff_path
2224 # -- Always start git gui through whatever we were loaded with. This
2225 # lets us bypass using shell process on Windows systems.
2227 set exe [list [_which git]]
2229 error_popup [mc "Couldn't find git gui in PATH"]
2232 global _gitdir _gitworktree
2234 # see note in do_gitk about unsetting these vars when
2235 # running tools in a submodule
2237 unset env(GIT_WORK_TREE)
2240 cd $current_diff_path
2242 eval exec $exe gui &
2244 set env(GIT_DIR) $_gitdir
2245 set env(GIT_WORK_TREE) $_gitworktree
2248 set status_operation [$::main_status \
2250 [mc "Starting %s... please wait..." "git-gui"]]
2252 after 3500 [list $status_operation stop]
2256 # Get the system-specific explorer app/command.
2257 proc get_explorer {} {
2258 if {[is_Cygwin] || [is_Windows]} {
2259 set explorer "explorer.exe"
2260 } elseif {[is_MacOSX]} {
2263 # freedesktop.org-conforming system is our best shot
2264 set explorer "xdg-open"
2269 proc do_explore {} {
2271 set explorer [get_explorer]
2272 eval exec $explorer [list [file nativename $_gitworktree]] &
2275 # Open file relative to the working tree by the default associated app.
2276 proc do_file_open {file} {
2278 set explorer [get_explorer]
2279 set full_file_path [file join $_gitworktree $file]
2280 exec $explorer [file nativename $full_file_path] &
2286 proc terminate_me {win} {
2288 if {$win ne {.}} return
2292 proc do_quit {{rc {1}}} {
2293 global ui_comm is_quitting repo_config commit_type
2294 global GITGUI_BCK_exists GITGUI_BCK_i
2295 global ui_comm_spell
2296 global ret_code use_ttk
2298 if {$is_quitting} return
2301 if {[winfo exists $ui_comm]} {
2302 # -- Stash our current commit buffer.
2304 set save [gitdir GITGUI_MSG]
2305 if {$GITGUI_BCK_exists && ![$ui_comm edit modified]} {
2306 file rename -force [gitdir GITGUI_BCK] $save
2307 set GITGUI_BCK_exists 0
2309 set msg [string trim [$ui_comm get 0.0 end]]
2310 regsub -all -line {[ \r\t]+$} $msg {} msg
2311 if {(![string match amend* $commit_type]
2312 || [$ui_comm edit modified])
2315 set fd [open $save w]
2316 fconfigure $fd -encoding utf-8
2317 puts -nonewline $fd $msg
2321 catch {file delete $save}
2325 # -- Cancel our spellchecker if its running.
2327 if {[info exists ui_comm_spell]} {
2331 # -- Remove our editor backup, its not needed.
2333 after cancel $GITGUI_BCK_i
2334 if {$GITGUI_BCK_exists} {
2335 catch {file delete [gitdir GITGUI_BCK]}
2338 # -- Stash our current window geometry into this repository.
2340 set cfg_wmstate [wm state .]
2341 if {[catch {set rc_wmstate $repo_config(gui.wmstate)}]} {
2344 if {$cfg_wmstate ne $rc_wmstate} {
2345 catch {git config gui.wmstate $cfg_wmstate}
2347 if {$cfg_wmstate eq {zoomed}} {
2348 # on Windows wm geometry will lie about window
2349 # position (but not size) when window is zoomed
2350 # restore the window before querying wm geometry
2353 set cfg_geometry [list]
2354 lappend cfg_geometry [wm geometry .]
2356 lappend cfg_geometry [.vpane sashpos 0]
2357 lappend cfg_geometry [.vpane.files sashpos 0]
2359 lappend cfg_geometry [lindex [.vpane sash coord 0] 0]
2360 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 1]
2362 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
2365 if {$cfg_geometry ne $rc_geometry} {
2366 catch {git config gui.geometry $cfg_geometry}
2372 # Briefly enable send again, working around Tk bug
2373 # http://sourceforge.net/tracker/?func=detail&atid=112997&aid=1821174&group_id=12997
2374 tk appname [appname]
2383 proc ui_do_rescan {} {
2384 rescan {force_first_diff ui_ready}
2391 proc next_diff {{after {}}} {
2392 global next_diff_p next_diff_w next_diff_i
2393 show_diff $next_diff_p $next_diff_w {} {} $after
2396 proc find_anchor_pos {lst name} {
2397 set lid [lsearch -sorted -exact $lst $name]
2401 foreach lname $lst {
2402 if {$lname >= $name} break
2410 proc find_file_from {flist idx delta path mmask} {
2413 set len [llength $flist]
2414 while {$idx >= 0 && $idx < $len} {
2415 set name [lindex $flist $idx]
2417 if {$name ne $path && [info exists file_states($name)]} {
2418 set state [lindex $file_states($name) 0]
2420 if {$mmask eq {} || [regexp $mmask $state]} {
2431 proc find_next_diff {w path {lno {}} {mmask {}}} {
2432 global next_diff_p next_diff_w next_diff_i
2433 global file_lists ui_index ui_workdir
2435 set flist $file_lists($w)
2437 set lno [find_anchor_pos $flist $path]
2442 if {$mmask ne {} && ![regexp {(^\^)|(\$$)} $mmask]} {
2443 if {$w eq $ui_index} {
2446 set mmask "$mmask\$"
2450 set idx [find_file_from $flist $lno 1 $path $mmask]
2453 set idx [find_file_from $flist $lno -1 $path $mmask]
2458 set next_diff_p [lindex $flist $idx]
2459 set next_diff_i [expr {$idx+1}]
2466 proc next_diff_after_action {w path {lno {}} {mmask {}}} {
2467 global current_diff_path
2469 if {$path ne $current_diff_path} {
2471 } elseif {[find_next_diff $w $path $lno $mmask]} {
2474 return {reshow_diff;}
2478 proc select_first_diff {after} {
2481 if {[find_next_diff $ui_workdir {} 1 {^_?U}] ||
2482 [find_next_diff $ui_workdir {} 1 {[^O]$}]} {
2489 proc force_first_diff {after} {
2490 global ui_workdir current_diff_path file_states
2492 if {[info exists file_states($current_diff_path)]} {
2493 set state [lindex $file_states($current_diff_path) 0]
2499 if {[string first {U} $state] >= 0} {
2500 # Already a conflict, do nothing
2501 } elseif {[find_next_diff $ui_workdir $current_diff_path {} {^_?U}]} {
2503 } elseif {[string index $state 1] ne {O}} {
2504 # Already a diff & no conflicts, do nothing
2505 } elseif {[find_next_diff $ui_workdir $current_diff_path {} {[^O]$}]} {
2516 proc toggle_or_diff {mode w args} {
2517 global file_states file_lists current_diff_path ui_index ui_workdir
2518 global last_clicked selected_paths file_lists_last_clicked
2520 if {$mode eq "click"} {
2521 foreach {x y} $args break
2522 set pos [split [$w index @$x,$y] .]
2523 foreach {lno col} $pos break
2525 if {$mode eq "toggle"} {
2526 if {$w eq $ui_workdir} {
2531 if {$w eq $ui_index} {
2532 do_unstage_selection
2538 if {$last_clicked ne {}} {
2539 set lno [lindex $last_clicked 1]
2541 if {![info exists file_lists]
2542 || ![info exists file_lists($w)]
2543 || [llength $file_lists($w)] == 0} {
2547 set lno [expr {int([lindex [$w tag ranges in_diff] 0])}]
2549 if {$mode eq "toggle"} {
2552 incr lno [expr {$mode eq "up" ? -1 : 1}]
2557 if {![info exists file_lists]
2558 || ![info exists file_lists($w)]
2559 || [llength $file_lists($w)] < $lno - 1} {
2562 set path [lindex $file_lists($w) [expr {$lno - 1}]]
2569 set last_clicked [list $w $lno]
2571 array unset selected_paths
2572 $ui_index tag remove in_sel 0.0 end
2573 $ui_workdir tag remove in_sel 0.0 end
2575 set file_lists_last_clicked($w) $path
2577 # Determine the state of the file
2578 if {[info exists file_states($path)]} {
2579 set state [lindex $file_states($path) 0]
2584 # Restage the file, or simply show the diff
2585 if {$col == 0 && $y > 1} {
2586 # Conflicts need special handling
2587 if {[string first {U} $state] >= 0} {
2588 # $w must always be $ui_workdir, but...
2589 if {$w ne $ui_workdir} { set lno {} }
2590 merge_stage_workdir $path $lno
2594 if {[string index $state 1] eq {O}} {
2600 set after [next_diff_after_action $w $path $lno $mmask]
2602 if {$w eq $ui_index} {
2604 "Unstaging [short_path $path] from commit" \
2606 [concat $after {ui_ready;}]
2607 } elseif {$w eq $ui_workdir} {
2609 "Adding [short_path $path]" \
2611 [concat $after {ui_ready;}]
2614 set selected_paths($path) 1
2615 show_diff $path $w $lno
2619 proc add_one_to_selection {w x y} {
2620 global file_lists last_clicked selected_paths
2622 set lno [lindex [split [$w index @$x,$y] .] 0]
2623 set path [lindex $file_lists($w) [expr {$lno - 1}]]
2629 if {$last_clicked ne {}
2630 && [lindex $last_clicked 0] ne $w} {
2631 array unset selected_paths
2632 [lindex $last_clicked 0] tag remove in_sel 0.0 end
2635 set last_clicked [list $w $lno]
2636 if {[catch {set in_sel $selected_paths($path)}]} {
2640 unset selected_paths($path)
2641 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
2643 set selected_paths($path) 1
2644 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
2648 proc add_range_to_selection {w x y} {
2649 global file_lists last_clicked selected_paths
2651 if {[lindex $last_clicked 0] ne $w} {
2652 toggle_or_diff click $w $x $y
2656 set lno [lindex [split [$w index @$x,$y] .] 0]
2657 set lc [lindex $last_clicked 1]
2666 foreach path [lrange $file_lists($w) \
2667 [expr {$begin - 1}] \
2668 [expr {$end - 1}]] {
2669 set selected_paths($path) 1
2671 $w tag add in_sel $begin.0 [expr {$end + 1}].0
2674 proc show_more_context {} {
2676 if {$repo_config(gui.diffcontext) < 99} {
2677 incr repo_config(gui.diffcontext)
2682 proc show_less_context {} {
2684 if {$repo_config(gui.diffcontext) > 1} {
2685 incr repo_config(gui.diffcontext) -1
2690 proc focus_widget {widget} {
2691 global file_lists last_clicked selected_paths
2692 global file_lists_last_clicked
2694 if {[llength $file_lists($widget)] > 0} {
2695 set path $file_lists_last_clicked($widget)
2696 set index [lsearch -sorted -exact $file_lists($widget) $path]
2699 set path [lindex $file_lists($widget) $index]
2703 set last_clicked [list $widget [expr $index + 1]]
2704 array unset selected_paths
2705 set selected_paths($path) 1
2706 show_diff $path $widget
2710 proc toggle_commit_type {} {
2711 global commit_type_is_amend
2712 set commit_type_is_amend [expr !$commit_type_is_amend]
2713 do_select_commit_type
2716 ######################################################################
2724 menu .mbar -tearoff 0
2726 # -- Apple Menu (Mac OS X only)
2728 .mbar add cascade -label Apple -menu .mbar.apple
2731 .mbar add cascade -label [mc Repository] -menu .mbar.repository
2732 .mbar add cascade -label [mc Edit] -menu .mbar.edit
2733 if {[is_enabled branch]} {
2734 .mbar add cascade -label [mc Branch] -menu .mbar.branch
2736 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2737 .mbar add cascade -label [mc Commit@@noun] -menu .mbar.commit
2739 if {[is_enabled transport]} {
2740 .mbar add cascade -label [mc Merge] -menu .mbar.merge
2741 .mbar add cascade -label [mc Remote] -menu .mbar.remote
2743 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2744 .mbar add cascade -label [mc Tools] -menu .mbar.tools
2747 # -- Repository Menu
2749 menu .mbar.repository
2752 .mbar.repository add command \
2753 -label [mc "Explore Working Copy"] \
2754 -command {do_explore}
2758 # Use /git-bash.exe if available
2759 set normalized [file normalize $::argv0]
2760 regsub "/mingw../libexec/git-core/git-gui$" \
2761 $normalized "/git-bash.exe" cmdLine
2762 if {$cmdLine != $normalized && [file exists $cmdLine]} {
2763 set cmdLine [list "Git Bash" $cmdLine &]
2765 set cmdLine [list "Git Bash" bash --login -l &]
2767 .mbar.repository add command \
2768 -label [mc "Git Bash"] \
2769 -command {eval exec [auto_execok start] $cmdLine}
2772 if {[is_Windows] || ![is_bare]} {
2773 .mbar.repository add separator
2776 .mbar.repository add command \
2777 -label [mc "Browse Current Branch's Files"] \
2778 -command {browser::new $current_branch}
2779 set ui_browse_current [.mbar.repository index last]
2780 .mbar.repository add command \
2781 -label [mc "Browse Branch Files..."] \
2782 -command browser_open::dialog
2783 .mbar.repository add separator
2785 .mbar.repository add command \
2786 -label [mc "Visualize Current Branch's History"] \
2787 -command {do_gitk $current_branch}
2788 set ui_visualize_current [.mbar.repository index last]
2789 .mbar.repository add command \
2790 -label [mc "Visualize All Branch History"] \
2791 -command {do_gitk --all}
2792 .mbar.repository add separator
2794 proc current_branch_write {args} {
2795 global current_branch
2796 .mbar.repository entryconf $::ui_browse_current \
2797 -label [mc "Browse %s's Files" $current_branch]
2798 .mbar.repository entryconf $::ui_visualize_current \
2799 -label [mc "Visualize %s's History" $current_branch]
2801 trace add variable current_branch write current_branch_write
2803 if {[is_enabled multicommit]} {
2804 .mbar.repository add command -label [mc "Database Statistics"] \
2807 .mbar.repository add command -label [mc "Compress Database"] \
2810 .mbar.repository add command -label [mc "Verify Database"] \
2811 -command do_fsck_objects
2813 .mbar.repository add separator
2816 .mbar.repository add command \
2817 -label [mc "Create Desktop Icon"] \
2818 -command do_cygwin_shortcut
2819 } elseif {[is_Windows]} {
2820 .mbar.repository add command \
2821 -label [mc "Create Desktop Icon"] \
2822 -command do_windows_shortcut
2823 } elseif {[is_MacOSX]} {
2824 .mbar.repository add command \
2825 -label [mc "Create Desktop Icon"] \
2826 -command do_macosx_app
2831 proc ::tk::mac::Quit {args} { do_quit }
2833 .mbar.repository add command -label [mc Quit] \
2841 .mbar.edit add command -label [mc Undo] \
2842 -command {catch {[focus] edit undo}} \
2844 .mbar.edit add command -label [mc Redo] \
2845 -command {catch {[focus] edit redo}} \
2847 .mbar.edit add separator
2848 .mbar.edit add command -label [mc Cut] \
2849 -command {catch {tk_textCut [focus]}} \
2851 .mbar.edit add command -label [mc Copy] \
2852 -command {catch {tk_textCopy [focus]}} \
2854 .mbar.edit add command -label [mc Paste] \
2855 -command {catch {tk_textPaste [focus]; [focus] see insert}} \
2857 .mbar.edit add command -label [mc Delete] \
2858 -command {catch {[focus] delete sel.first sel.last}} \
2860 .mbar.edit add separator
2861 .mbar.edit add command -label [mc "Select All"] \
2862 -command {catch {[focus] tag add sel 0.0 end}} \
2867 if {[is_enabled branch]} {
2870 .mbar.branch add command -label [mc "Create..."] \
2871 -command branch_create::dialog \
2873 lappend disable_on_lock [list .mbar.branch entryconf \
2874 [.mbar.branch index last] -state]
2876 .mbar.branch add command -label [mc "Checkout..."] \
2877 -command branch_checkout::dialog \
2879 lappend disable_on_lock [list .mbar.branch entryconf \
2880 [.mbar.branch index last] -state]
2882 .mbar.branch add command -label [mc "Rename..."] \
2883 -command branch_rename::dialog
2884 lappend disable_on_lock [list .mbar.branch entryconf \
2885 [.mbar.branch index last] -state]
2887 .mbar.branch add command -label [mc "Delete..."] \
2888 -command branch_delete::dialog
2889 lappend disable_on_lock [list .mbar.branch entryconf \
2890 [.mbar.branch index last] -state]
2892 .mbar.branch add command -label [mc "Reset..."] \
2893 -command merge::reset_hard
2894 lappend disable_on_lock [list .mbar.branch entryconf \
2895 [.mbar.branch index last] -state]
2900 proc commit_btn_caption {} {
2901 if {[is_enabled nocommit]} {
2904 return [mc Commit@@verb]
2908 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2911 if {![is_enabled nocommit]} {
2912 .mbar.commit add checkbutton \
2913 -label [mc "Amend Last Commit"] \
2914 -accelerator $M1T-E \
2915 -variable commit_type_is_amend \
2916 -command do_select_commit_type
2917 lappend disable_on_lock \
2918 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2920 .mbar.commit add separator
2923 .mbar.commit add command -label [mc Rescan] \
2924 -command ui_do_rescan \
2926 lappend disable_on_lock \
2927 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2929 .mbar.commit add command -label [mc "Stage To Commit"] \
2930 -command do_add_selection \
2932 lappend disable_on_lock \
2933 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2935 .mbar.commit add command -label [mc "Stage Changed Files To Commit"] \
2936 -command do_add_all \
2938 lappend disable_on_lock \
2939 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2941 .mbar.commit add command -label [mc "Unstage From Commit"] \
2942 -command do_unstage_selection \
2944 lappend disable_on_lock \
2945 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2947 .mbar.commit add command -label [mc "Revert Changes"] \
2948 -command do_revert_selection \
2950 lappend disable_on_lock \
2951 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2953 .mbar.commit add separator
2955 .mbar.commit add command -label [mc "Show Less Context"] \
2956 -command show_less_context \
2957 -accelerator $M1T-\-
2959 .mbar.commit add command -label [mc "Show More Context"] \
2960 -command show_more_context \
2963 .mbar.commit add separator
2965 if {![is_enabled nocommitmsg]} {
2966 .mbar.commit add command -label [mc "Sign Off"] \
2967 -command do_signoff \
2971 .mbar.commit add command -label [commit_btn_caption] \
2972 -command do_commit \
2973 -accelerator $M1T-Return
2974 lappend disable_on_lock \
2975 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2980 if {[is_enabled branch]} {
2982 .mbar.merge add command -label [mc "Local Merge..."] \
2983 -command merge::dialog \
2985 lappend disable_on_lock \
2986 [list .mbar.merge entryconf [.mbar.merge index last] -state]
2987 .mbar.merge add command -label [mc "Abort Merge..."] \
2988 -command merge::reset_hard
2989 lappend disable_on_lock \
2990 [list .mbar.merge entryconf [.mbar.merge index last] -state]
2995 if {[is_enabled transport]} {
2998 .mbar.remote add command \
2999 -label [mc "Add..."] \
3000 -command remote_add::dialog \
3002 .mbar.remote add command \
3003 -label [mc "Push..."] \
3004 -command do_push_anywhere \
3006 .mbar.remote add command \
3007 -label [mc "Delete Branch..."] \
3008 -command remote_branch_delete::dialog
3012 proc ::tk::mac::ShowPreferences {} {do_options}
3016 .mbar.edit add separator
3017 .mbar.edit add command -label [mc "Options..."] \
3023 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
3024 set tools_menubar .mbar.tools
3026 $tools_menubar add separator
3027 $tools_menubar add command -label [mc "Add..."] -command tools_add::dialog
3028 $tools_menubar add command -label [mc "Remove..."] -command tools_remove::dialog
3030 if {[array names repo_config guitool.*.cmd] ne {}} {
3037 .mbar add cascade -label [mc Help] -menu .mbar.help
3041 .mbar.apple add command -label [mc "About %s" [appname]] \
3043 .mbar.apple add separator
3045 .mbar.help add command -label [mc "About %s" [appname]] \
3048 . configure -menu .mbar
3050 set doc_path [githtmldir]
3051 if {$doc_path ne {}} {
3052 set doc_path [file join $doc_path index.html]
3055 set doc_path [exec cygpath --mixed $doc_path]
3059 if {[file isfile $doc_path]} {
3060 set doc_url "file:$doc_path"
3062 set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
3065 proc start_browser {url} {
3066 git "web--browse" $url
3069 .mbar.help add command -label [mc "Online Documentation"] \
3070 -command [list start_browser $doc_url]
3072 .mbar.help add command -label [mc "Show SSH Key"] \
3075 unset doc_path doc_url
3077 # -- Standard bindings
3079 wm protocol . WM_DELETE_WINDOW do_quit
3080 bind all <$M1B-Key-q> do_quit
3081 bind all <$M1B-Key-Q> do_quit
3084 set toplvl_win [winfo toplevel %W]
3086 # If we are destroying the main window, we should call do_quit to take
3087 # care of cleanup before exiting the program.
3088 if {$toplvl_win eq "."} {
3095 bind all <$M1B-Key-w> $m1b_w_script
3096 bind all <$M1B-Key-W> $m1b_w_script
3100 set subcommand_args {}
3102 set s "[mc usage:] $::argv0 $::subcommand $::subcommand_args"
3103 if {[tk windowingsystem] eq "win32"} {
3105 tk_messageBox -icon info -message $s \
3113 proc normalize_relpath {path} {
3115 foreach item [file split $path] {
3116 if {$item eq {.}} continue
3117 if {$item eq {..} && [llength $elements] > 0
3118 && [lindex $elements end] ne {..}} {
3119 set elements [lrange $elements 0 end-1]
3122 lappend elements $item
3124 return [eval file join $elements]
3127 # -- Not a normal commit type invocation? Do that instead!
3129 switch -- $subcommand {
3132 if {$subcommand eq "blame"} {
3133 set subcommand_args {[--line=<num>] rev? path}
3135 set subcommand_args {rev? path}
3137 if {$argv eq {}} usage
3143 set p [file join $_prefix $a]
3145 if {$is_path || [file exists $p]} {
3146 if {$path ne {}} usage
3147 set path [normalize_relpath $p]
3149 } elseif {$a eq {--}} {
3151 if {$head ne {}} usage
3156 } elseif {[regexp {^--line=(\d+)$} $a a lnum]} {
3157 if {$jump_spec ne {} || $head ne {}} usage
3158 set jump_spec [list $lnum]
3159 } elseif {$head eq {}} {
3160 if {$head ne {}} usage
3169 if {$head ne {} && $path eq {}} {
3170 if {[string index $head 0] eq {/}} {
3171 set path [normalize_relpath $head]
3174 set path [normalize_relpath $_prefix$head]
3182 if {[regexp {^[0-9a-f]{1,39}$} $head]} {
3184 set head [git rev-parse --verify $head]
3186 if {[tk windowingsystem] eq "win32"} {
3187 tk_messageBox -icon error -title [mc Error] -message $err
3194 set current_branch $head
3198 switch -- $subcommand {
3200 if {$jump_spec ne {}} usage
3202 if {$path ne {} && [file isdirectory $path]} {
3203 set head $current_branch
3209 browser::new $head $path
3212 if {$head eq {} && ![file exists $path]} {
3213 catch {wm withdraw .}
3217 -title [mc "git-gui: fatal error"] \
3218 -message [mc "fatal: cannot stat path %s: No such file or directory" $path]
3221 blame::new $head $path $jump_spec
3228 if {[llength $argv] != 0} {
3231 # fall through to setup UI for commits
3234 set err "[mc usage:] $argv0 \[{blame|browser|citool}\]"
3235 if {[tk windowingsystem] eq "win32"} {
3237 tk_messageBox -icon error -message $err \
3248 ${NS}::frame .branch
3249 if {!$use_ttk} {.branch configure -borderwidth 1 -relief sunken}
3250 ${NS}::label .branch.l1 \
3251 -text [mc "Current Branch:"] \
3254 ${NS}::label .branch.cb \
3255 -textvariable current_branch \
3258 pack .branch.l1 -side left
3259 pack .branch.cb -side left -fill x
3260 pack .branch -side top -fill x
3262 # -- Main Window Layout
3264 ${NS}::panedwindow .vpane -orient horizontal
3265 ${NS}::panedwindow .vpane.files -orient vertical
3267 .vpane add .vpane.files
3269 .vpane add .vpane.files -sticky nsew -height 100 -width 200
3271 pack .vpane -anchor n -side top -fill both -expand 1
3273 # -- Working Directory File List
3275 textframe .vpane.files.workdir -height 100 -width 200
3276 tlabel .vpane.files.workdir.title -text [mc "Unstaged Changes"] \
3277 -background lightsalmon -foreground black
3280 -width 20 -height 10 \
3282 -takefocus 1 -highlightthickness 1\
3283 -cursor $cursor_ptr \
3284 -xscrollcommand {.vpane.files.workdir.sx set} \
3285 -yscrollcommand {.vpane.files.workdir.sy set} \
3287 ${NS}::scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
3288 ${NS}::scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
3289 pack .vpane.files.workdir.title -side top -fill x
3290 pack .vpane.files.workdir.sx -side bottom -fill x
3291 pack .vpane.files.workdir.sy -side right -fill y
3292 pack $ui_workdir -side left -fill both -expand 1
3294 # -- Index File List
3296 textframe .vpane.files.index -height 100 -width 200
3297 tlabel .vpane.files.index.title \
3298 -text [mc "Staged Changes (Will Commit)"] \
3299 -background lightgreen -foreground black
3302 -width 20 -height 10 \
3304 -takefocus 1 -highlightthickness 1\
3305 -cursor $cursor_ptr \
3306 -xscrollcommand {.vpane.files.index.sx set} \
3307 -yscrollcommand {.vpane.files.index.sy set} \
3309 ${NS}::scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
3310 ${NS}::scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
3311 pack .vpane.files.index.title -side top -fill x
3312 pack .vpane.files.index.sx -side bottom -fill x
3313 pack .vpane.files.index.sy -side right -fill y
3314 pack $ui_index -side left -fill both -expand 1
3316 # -- Insert the workdir and index into the panes
3318 .vpane.files add .vpane.files.workdir
3319 .vpane.files add .vpane.files.index
3321 .vpane.files paneconfigure .vpane.files.workdir -sticky news
3322 .vpane.files paneconfigure .vpane.files.index -sticky news
3325 foreach i [list $ui_index $ui_workdir] {
3327 $i tag conf in_diff \
3328 -background $color::select_bg \
3329 -foreground $color::select_fg
3333 # -- Diff and Commit Area
3336 ${NS}::panedwindow .vpane.lower -orient vertical
3337 ${NS}::frame .vpane.lower.commarea
3338 ${NS}::frame .vpane.lower.diff -relief sunken -borderwidth 1 -height 500
3339 .vpane.lower add .vpane.lower.diff
3340 .vpane.lower add .vpane.lower.commarea
3341 .vpane add .vpane.lower
3343 .vpane.lower pane .vpane.lower.diff -weight 1
3344 .vpane.lower pane .vpane.lower.commarea -weight 0
3346 .vpane.lower paneconfigure .vpane.lower.diff -stretch always
3347 .vpane.lower paneconfigure .vpane.lower.commarea -stretch never
3350 frame .vpane.lower -height 300 -width 400
3351 frame .vpane.lower.commarea
3352 frame .vpane.lower.diff -relief sunken -borderwidth 1
3353 pack .vpane.lower.diff -fill both -expand 1
3354 pack .vpane.lower.commarea -side bottom -fill x
3355 .vpane add .vpane.lower
3356 .vpane paneconfigure .vpane.lower -sticky nsew
3359 # -- Commit Area Buttons
3361 ${NS}::frame .vpane.lower.commarea.buttons
3362 ${NS}::label .vpane.lower.commarea.buttons.l -text {} \
3365 pack .vpane.lower.commarea.buttons.l -side top -fill x
3366 pack .vpane.lower.commarea.buttons -side left -fill y
3368 ${NS}::button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \
3369 -command ui_do_rescan
3370 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
3371 lappend disable_on_lock \
3372 {.vpane.lower.commarea.buttons.rescan conf -state}
3374 ${NS}::button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \
3376 pack .vpane.lower.commarea.buttons.incall -side top -fill x
3377 lappend disable_on_lock \
3378 {.vpane.lower.commarea.buttons.incall conf -state}
3380 if {![is_enabled nocommitmsg]} {
3381 ${NS}::button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \
3383 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
3386 ${NS}::button .vpane.lower.commarea.buttons.commit -text [commit_btn_caption] \
3388 pack .vpane.lower.commarea.buttons.commit -side top -fill x
3389 lappend disable_on_lock \
3390 {.vpane.lower.commarea.buttons.commit conf -state}
3392 if {![is_enabled nocommit]} {
3393 ${NS}::button .vpane.lower.commarea.buttons.push -text [mc Push] \
3394 -command do_push_anywhere
3395 pack .vpane.lower.commarea.buttons.push -side top -fill x
3398 # -- Commit Message Buffer
3400 ${NS}::frame .vpane.lower.commarea.buffer
3401 ${NS}::frame .vpane.lower.commarea.buffer.header
3402 set ui_comm .vpane.lower.commarea.buffer.frame.t
3403 set ui_coml .vpane.lower.commarea.buffer.header.l
3405 if {![is_enabled nocommit]} {
3406 ${NS}::checkbutton .vpane.lower.commarea.buffer.header.amend \
3407 -text [mc "Amend Last Commit"] \
3408 -variable commit_type_is_amend \
3409 -command do_select_commit_type
3410 lappend disable_on_lock \
3411 [list .vpane.lower.commarea.buffer.header.amend conf -state]
3414 ${NS}::label $ui_coml \
3417 proc trace_commit_type {varname args} {
3418 global ui_coml commit_type
3419 switch -glob -- $commit_type {
3420 initial {set txt [mc "Initial Commit Message:"]}
3421 amend {set txt [mc "Amended Commit Message:"]}
3422 amend-initial {set txt [mc "Amended Initial Commit Message:"]}
3423 amend-merge {set txt [mc "Amended Merge Commit Message:"]}
3424 merge {set txt [mc "Merge Commit Message:"]}
3425 * {set txt [mc "Commit Message:"]}
3427 $ui_coml conf -text $txt
3429 trace add variable commit_type write trace_commit_type
3430 pack $ui_coml -side left -fill x
3432 if {![is_enabled nocommit]} {
3433 pack .vpane.lower.commarea.buffer.header.amend -side right
3436 textframe .vpane.lower.commarea.buffer.frame
3441 -autoseparators true \
3443 -highlightthickness 1 \
3445 -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \
3447 -xscrollcommand {.vpane.lower.commarea.buffer.frame.sbx set} \
3448 -yscrollcommand {.vpane.lower.commarea.buffer.frame.sby set}
3449 ${NS}::scrollbar .vpane.lower.commarea.buffer.frame.sbx \
3450 -orient horizontal \
3451 -command [list $ui_comm xview]
3452 ${NS}::scrollbar .vpane.lower.commarea.buffer.frame.sby \
3454 -command [list $ui_comm yview]
3456 pack .vpane.lower.commarea.buffer.frame.sbx -side bottom -fill x
3457 pack .vpane.lower.commarea.buffer.frame.sby -side right -fill y
3458 pack $ui_comm -side left -fill y
3459 pack .vpane.lower.commarea.buffer.header -side top -fill x
3460 pack .vpane.lower.commarea.buffer.frame -side left -fill y
3461 pack .vpane.lower.commarea.buffer -side left -fill y
3463 # -- Commit Message Buffer Context Menu
3465 set ctxm .vpane.lower.commarea.buffer.ctxm
3466 menu $ctxm -tearoff 0
3469 -command {tk_textCut $ui_comm}
3472 -command {tk_textCopy $ui_comm}
3475 -command {tk_textPaste $ui_comm}
3477 -label [mc Delete] \
3478 -command {catch {$ui_comm delete sel.first sel.last}}
3481 -label [mc "Select All"] \
3482 -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
3484 -label [mc "Copy All"] \
3486 $ui_comm tag add sel 0.0 end
3487 tk_textCopy $ui_comm
3488 $ui_comm tag remove sel 0.0 end
3492 -label [mc "Sign Off"] \
3494 set ui_comm_ctxm $ctxm
3498 proc trace_current_diff_path {varname args} {
3499 global current_diff_path diff_actions file_states
3500 if {$current_diff_path eq {}} {
3506 set p $current_diff_path
3507 set s [mapdesc [lindex $file_states($p) 0] $p]
3509 set p [escape_path $p]
3513 .vpane.lower.diff.header.status configure -text $s
3514 .vpane.lower.diff.header.file configure -text $f
3515 .vpane.lower.diff.header.path configure -text $p
3516 foreach w $diff_actions {
3520 trace add variable current_diff_path write trace_current_diff_path
3522 gold_frame .vpane.lower.diff.header
3523 tlabel .vpane.lower.diff.header.status \
3526 -width $max_status_desc \
3529 tlabel .vpane.lower.diff.header.file \
3534 tlabel .vpane.lower.diff.header.path \
3539 -font [eval font create [font configure font_ui] -underline 1] \
3541 pack .vpane.lower.diff.header.status -side left
3542 pack .vpane.lower.diff.header.file -side left
3543 pack .vpane.lower.diff.header.path -fill x
3544 set ctxm .vpane.lower.diff.header.ctxm
3545 menu $ctxm -tearoff 0
3553 -- $current_diff_path
3557 -command {do_file_open $current_diff_path}
3558 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3559 bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
3560 bind .vpane.lower.diff.header.path <Button-1> {do_file_open $current_diff_path}
3564 textframe .vpane.lower.diff.body
3565 set ui_diff .vpane.lower.diff.body.t
3568 -width 80 -height 5 -wrap none \
3570 -takefocus 1 -highlightthickness 1 \
3571 -xscrollcommand {.vpane.lower.diff.body.sbx set} \
3572 -yscrollcommand {.vpane.lower.diff.body.sby set} \
3574 catch {$ui_diff configure -tabstyle wordprocessor}
3575 ${NS}::scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
3576 -command [list $ui_diff xview]
3577 ${NS}::scrollbar .vpane.lower.diff.body.sby -orient vertical \
3578 -command [list $ui_diff yview]
3579 pack .vpane.lower.diff.body.sbx -side bottom -fill x
3580 pack .vpane.lower.diff.body.sby -side right -fill y
3581 pack $ui_diff -side left -fill both -expand 1
3582 pack .vpane.lower.diff.header -side top -fill x
3583 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
3585 foreach {n c} {0 black 1 red4 2 green4 3 yellow4 4 blue4 5 magenta4 6 cyan4 7 grey60} {
3586 $ui_diff tag configure clr4$n -background $c
3587 $ui_diff tag configure clri4$n -foreground $c
3588 $ui_diff tag configure clr3$n -foreground $c
3589 $ui_diff tag configure clri3$n -background $c
3591 $ui_diff tag configure clr1 -font font_diffbold
3592 $ui_diff tag configure clr4 -underline 1
3594 $ui_diff tag conf d_info -foreground blue -font font_diffbold
3596 $ui_diff tag conf d_cr -elide true
3597 $ui_diff tag conf d_@ -font font_diffbold
3598 $ui_diff tag conf d_+ -foreground {#00a000}
3599 $ui_diff tag conf d_- -foreground red
3601 $ui_diff tag conf d_++ -foreground {#00a000}
3602 $ui_diff tag conf d_-- -foreground red
3603 $ui_diff tag conf d_+s \
3604 -foreground {#00a000} \
3605 -background {#e2effa}
3606 $ui_diff tag conf d_-s \
3608 -background {#e2effa}
3609 $ui_diff tag conf d_s+ \
3610 -foreground {#00a000} \
3612 $ui_diff tag conf d_s- \
3616 $ui_diff tag conf d< \
3617 -foreground orange \
3619 $ui_diff tag conf d| \
3620 -foreground orange \
3622 $ui_diff tag conf d= \
3623 -foreground orange \
3625 $ui_diff tag conf d> \
3626 -foreground orange \
3629 $ui_diff tag raise sel
3631 # -- Diff Body Context Menu
3634 proc create_common_diff_popup {ctxm} {
3636 -label [mc Refresh] \
3637 -command reshow_diff
3638 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3641 -command {tk_textCopy $ui_diff}
3642 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3644 -label [mc "Select All"] \
3645 -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
3646 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3648 -label [mc "Copy All"] \
3650 $ui_diff tag add sel 0.0 end
3651 tk_textCopy $ui_diff
3652 $ui_diff tag remove sel 0.0 end
3654 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3657 -label [mc "Decrease Font Size"] \
3658 -command {incr_font_size font_diff -1}
3659 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3661 -label [mc "Increase Font Size"] \
3662 -command {incr_font_size font_diff 1}
3663 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3667 build_encoding_menu $emenu [list force_diff_encoding]
3669 -label [mc "Encoding"] \
3671 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3673 $ctxm add command -label [mc "Options..."] \
3677 set ctxm .vpane.lower.diff.body.ctxm
3678 menu $ctxm -tearoff 0
3680 -label [mc "Apply/Reverse Hunk"] \
3681 -command {apply_or_revert_hunk $cursorX $cursorY 0}
3682 set ui_diff_applyhunk [$ctxm index last]
3683 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
3685 -label [mc "Apply/Reverse Line"] \
3686 -command {apply_or_revert_range_or_line $cursorX $cursorY 0; do_rescan}
3687 set ui_diff_applyline [$ctxm index last]
3688 lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state]
3691 -label [mc "Revert Hunk"] \
3692 -command {apply_or_revert_hunk $cursorX $cursorY 1}
3693 set ui_diff_reverthunk [$ctxm index last]
3694 lappend diff_actions [list $ctxm entryconf $ui_diff_reverthunk -state]
3696 -label [mc "Revert Line"] \
3697 -command {apply_or_revert_range_or_line $cursorX $cursorY 1; do_rescan}
3698 set ui_diff_revertline [$ctxm index last]
3699 lappend diff_actions [list $ctxm entryconf $ui_diff_revertline -state]
3701 -label [mc "Undo Last Revert"] \
3702 -command {undo_last_revert; do_rescan}
3703 set ui_diff_undorevert [$ctxm index last]
3704 lappend diff_actions [list $ctxm entryconf $ui_diff_undorevert -state]
3707 -label [mc "Show Less Context"] \
3708 -command show_less_context
3709 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3711 -label [mc "Show More Context"] \
3712 -command show_more_context
3713 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3715 create_common_diff_popup $ctxm
3717 set ctxmmg .vpane.lower.diff.body.ctxmmg
3718 menu $ctxmmg -tearoff 0
3719 $ctxmmg add command \
3720 -label [mc "Run Merge Tool"] \
3721 -command {merge_resolve_tool}
3722 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3723 $ctxmmg add separator
3724 $ctxmmg add command \
3725 -label [mc "Use Remote Version"] \
3726 -command {merge_resolve_one 3}
3727 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3728 $ctxmmg add command \
3729 -label [mc "Use Local Version"] \
3730 -command {merge_resolve_one 2}
3731 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3732 $ctxmmg add command \
3733 -label [mc "Revert To Base"] \
3734 -command {merge_resolve_one 1}
3735 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3736 $ctxmmg add separator
3737 $ctxmmg add command \
3738 -label [mc "Show Less Context"] \
3739 -command show_less_context
3740 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3741 $ctxmmg add command \
3742 -label [mc "Show More Context"] \
3743 -command show_more_context
3744 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3745 $ctxmmg add separator
3746 create_common_diff_popup $ctxmmg
3748 set ctxmsm .vpane.lower.diff.body.ctxmsm
3749 menu $ctxmsm -tearoff 0
3750 $ctxmsm add command \
3751 -label [mc "Visualize These Changes In The Submodule"] \
3752 -command {do_gitk -- true}
3753 lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
3754 $ctxmsm add command \
3755 -label [mc "Visualize Current Branch History In The Submodule"] \
3756 -command {do_gitk {} true}
3757 lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
3758 $ctxmsm add command \
3759 -label [mc "Visualize All Branch History In The Submodule"] \
3760 -command {do_gitk --all true}
3761 lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
3762 $ctxmsm add separator
3763 $ctxmsm add command \
3764 -label [mc "Start git gui In The Submodule"] \
3765 -command {do_git_gui}
3766 lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
3767 $ctxmsm add separator
3768 create_common_diff_popup $ctxmsm
3770 proc has_textconv {path} {
3771 if {[is_config_false gui.textconv]} {
3774 set filter [gitattr $path diff set]
3775 set textconv [get_config [join [list diff $filter textconv] .]]
3776 if {$filter ne {set} && $textconv ne {}} {
3783 proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} {
3784 global current_diff_path file_states last_revert
3787 if {[info exists file_states($current_diff_path)]} {
3788 set state [lindex $file_states($current_diff_path) 0]
3792 if {[string first {U} $state] >= 0} {
3793 tk_popup $ctxmmg $X $Y
3794 } elseif {$::is_submodule_diff} {
3795 tk_popup $ctxmsm $X $Y
3797 set has_range [expr {[$::ui_diff tag nextrange sel 0.0] != {}}]
3798 set u [mc "Undo Last Revert"]
3799 if {$::ui_index eq $::current_diff_side} {
3800 set l [mc "Unstage Hunk From Commit"]
3801 set h [mc "Revert Hunk"]
3804 set t [mc "Unstage Lines From Commit"]
3805 set r [mc "Revert Lines"]
3807 set t [mc "Unstage Line From Commit"]
3808 set r [mc "Revert Line"]
3811 set l [mc "Stage Hunk For Commit"]
3812 set h [mc "Revert Hunk"]
3815 set t [mc "Stage Lines For Commit"]
3816 set r [mc "Revert Lines"]
3818 set t [mc "Stage Line For Commit"]
3819 set r [mc "Revert Line"]
3823 || $current_diff_path eq {}
3826 || [string match {?T} $state]
3827 || [string match {T?} $state]
3828 || [has_textconv $current_diff_path]} {
3830 set revert_state disabled
3834 # Only allow reverting changes in the working tree. If
3835 # the user wants to revert changes in the index, they
3836 # need to unstage those first.
3837 if {$::ui_workdir eq $::current_diff_side} {
3838 set revert_state normal
3840 set revert_state disabled
3844 if {$last_revert eq {}} {
3845 set undo_state disabled
3847 set undo_state normal
3850 $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
3851 $ctxm entryconf $::ui_diff_applyline -state $s -label $t
3852 $ctxm entryconf $::ui_diff_revertline -state $revert_state \
3854 $ctxm entryconf $::ui_diff_reverthunk -state $revert_state \
3856 $ctxm entryconf $::ui_diff_undorevert -state $undo_state \
3859 tk_popup $ctxm $X $Y
3862 bind_button3 $ui_diff [list popup_diff_menu $ctxm $ctxmmg $ctxmsm %x %y %X %Y]
3866 set main_status [::status_bar::new .status]
3867 pack .status -anchor w -side bottom -fill x
3868 $main_status show [mc "Initializing..."]
3872 proc on_ttk_pane_mapped {w pane pos} {
3874 after 0 [list after idle [list $w sashpos $pane $pos]]
3876 proc on_tk_pane_mapped {w pane x y} {
3878 after 0 [list after idle [list $w sash place $pane $x $y]]
3880 proc on_application_mapped {} {
3881 global repo_config use_ttk
3883 set gm $repo_config(gui.geometry)
3886 [list on_ttk_pane_mapped %W 0 [lindex $gm 1]]
3887 bind .vpane.files <Map> \
3888 [list on_ttk_pane_mapped %W 0 [lindex $gm 2]]
3891 [list on_tk_pane_mapped %W 0 \
3893 [lindex [.vpane sash coord 0] 1]]
3894 bind .vpane.files <Map> \
3895 [list on_tk_pane_mapped %W 0 \
3896 [lindex [.vpane.files sash coord 0] 0] \
3899 wm geometry . [lindex $gm 0]
3901 if {[info exists repo_config(gui.geometry)]} {
3902 bind . <Map> [list on_application_mapped]
3903 wm geometry . [lindex $repo_config(gui.geometry) 0]
3906 # -- Load window state
3908 if {[info exists repo_config(gui.wmstate)]} {
3909 catch {wm state . $repo_config(gui.wmstate)}
3914 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
3915 bind $ui_comm <$M1B-Key-t> {do_add_selection;break}
3916 bind $ui_comm <$M1B-Key-T> {do_add_selection;break}
3917 bind $ui_comm <$M1B-Key-u> {do_unstage_selection;break}
3918 bind $ui_comm <$M1B-Key-U> {do_unstage_selection;break}
3919 bind $ui_comm <$M1B-Key-j> {do_revert_selection;break}
3920 bind $ui_comm <$M1B-Key-J> {do_revert_selection;break}
3921 bind $ui_comm <$M1B-Key-i> {do_add_all;break}
3922 bind $ui_comm <$M1B-Key-I> {do_add_all;break}
3923 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
3924 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
3925 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
3926 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
3927 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
3928 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
3929 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3930 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3931 bind $ui_comm <$M1B-Key-minus> {show_less_context;break}
3932 bind $ui_comm <$M1B-Key-KP_Subtract> {show_less_context;break}
3933 bind $ui_comm <$M1B-Key-equal> {show_more_context;break}
3934 bind $ui_comm <$M1B-Key-plus> {show_more_context;break}
3935 bind $ui_comm <$M1B-Key-KP_Add> {show_more_context;break}
3936 bind $ui_comm <$M1B-Key-BackSpace> {event generate %W <Meta-Delete>;break}
3937 bind $ui_comm <$M1B-Key-Delete> {event generate %W <Meta-d>;break}
3939 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
3940 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
3941 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
3942 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
3943 bind $ui_diff <$M1B-Key-v> {break}
3944 bind $ui_diff <$M1B-Key-V> {break}
3945 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3946 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3947 bind $ui_diff <$M1B-Key-j> {do_revert_selection;break}
3948 bind $ui_diff <$M1B-Key-J> {do_revert_selection;break}
3949 bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break}
3950 bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break}
3951 bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break}
3952 bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break}
3953 bind $ui_diff <Key-k> {catch {%W yview scroll -1 units};break}
3954 bind $ui_diff <Key-j> {catch {%W yview scroll 1 units};break}
3955 bind $ui_diff <Key-h> {catch {%W xview scroll -1 units};break}
3956 bind $ui_diff <Key-l> {catch {%W xview scroll 1 units};break}
3957 bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
3958 bind $ui_diff <Control-Key-f> {catch {%W yview scroll 1 pages};break}
3959 bind $ui_diff <Button-1> {focus %W}
3961 if {[is_enabled branch]} {
3962 bind . <$M1B-Key-n> branch_create::dialog
3963 bind . <$M1B-Key-N> branch_create::dialog
3964 bind . <$M1B-Key-o> branch_checkout::dialog
3965 bind . <$M1B-Key-O> branch_checkout::dialog
3966 bind . <$M1B-Key-m> merge::dialog
3967 bind . <$M1B-Key-M> merge::dialog
3969 if {[is_enabled transport]} {
3970 bind . <$M1B-Key-p> do_push_anywhere
3971 bind . <$M1B-Key-P> do_push_anywhere
3974 bind . <Key-F5> ui_do_rescan
3975 bind . <$M1B-Key-r> ui_do_rescan
3976 bind . <$M1B-Key-R> ui_do_rescan
3977 bind . <$M1B-Key-s> do_signoff
3978 bind . <$M1B-Key-S> do_signoff
3979 bind . <$M1B-Key-t> { toggle_or_diff toggle %W }
3980 bind . <$M1B-Key-T> { toggle_or_diff toggle %W }
3981 bind . <$M1B-Key-u> { toggle_or_diff toggle %W }
3982 bind . <$M1B-Key-U> { toggle_or_diff toggle %W }
3983 bind . <$M1B-Key-j> do_revert_selection
3984 bind . <$M1B-Key-J> do_revert_selection
3985 bind . <$M1B-Key-i> do_add_all
3986 bind . <$M1B-Key-I> do_add_all
3987 bind . <$M1B-Key-e> toggle_commit_type
3988 bind . <$M1B-Key-E> toggle_commit_type
3989 bind . <$M1B-Key-minus> {show_less_context;break}
3990 bind . <$M1B-Key-KP_Subtract> {show_less_context;break}
3991 bind . <$M1B-Key-equal> {show_more_context;break}
3992 bind . <$M1B-Key-plus> {show_more_context;break}
3993 bind . <$M1B-Key-KP_Add> {show_more_context;break}
3994 bind . <$M1B-Key-Return> do_commit
3995 bind . <$M1B-Key-KP_Enter> do_commit
3996 foreach i [list $ui_index $ui_workdir] {
3997 bind $i <Button-1> { toggle_or_diff click %W %x %y; break }
3998 bind $i <$M1B-Button-1> { add_one_to_selection %W %x %y; break }
3999 bind $i <Shift-Button-1> { add_range_to_selection %W %x %y; break }
4000 bind $i <Key-Up> { toggle_or_diff up %W; break }
4001 bind $i <Key-Down> { toggle_or_diff down %W; break }
4005 bind . <Alt-Key-1> {focus_widget $::ui_workdir}
4006 bind . <Alt-Key-2> {focus_widget $::ui_index}
4007 bind . <Alt-Key-3> {focus $::ui_diff}
4008 bind . <Alt-Key-4> {focus $::ui_comm}
4010 set file_lists_last_clicked($ui_index) {}
4011 set file_lists_last_clicked($ui_workdir) {}
4013 set file_lists($ui_index) [list]
4014 set file_lists($ui_workdir) [list]
4016 wm title . "[appname] ([reponame]) [file normalize $_gitworktree]"
4017 focus -force $ui_comm
4019 # -- Warn the user about environmental problems. Cygwin's Tcl
4020 # does *not* pass its env array onto any processes it spawns.
4021 # This means that git processes get none of our environment.
4026 set msg [mc "Possible environment issues exist.
4028 The following environment variables are probably
4029 going to be ignored by any Git subprocess run
4033 foreach name [array names env] {
4034 switch -regexp -- $name {
4035 {^GIT_INDEX_FILE$} -
4036 {^GIT_OBJECT_DIRECTORY$} -
4037 {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
4039 {^GIT_EXTERNAL_DIFF$} -
4043 {^GIT_(AUTHOR|COMMITTER)_DATE$} {
4044 append msg " - $name\n"
4047 {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
4048 append msg " - $name\n"
4050 set suggest_user $name
4054 if {$ignored_env > 0} {
4056 This is due to a known issue with the
4057 Tcl binary distributed by Cygwin."]
4059 if {$suggest_user ne {}} {
4062 A good replacement for %s
4063 is placing values for the user.name and
4064 user.email settings into your personal
4070 unset ignored_env msg suggest_user name
4073 # -- Only initialize complex UI if we are going to stay running.
4075 if {[is_enabled transport]} {
4078 set n [.mbar.remote index end]
4079 populate_remotes_menu
4080 set n [expr {[.mbar.remote index end] - $n}]
4082 if {[.mbar.remote type 0] eq "tearoff"} { incr n }
4083 .mbar.remote insert $n separator
4088 if {[winfo exists $ui_comm]} {
4089 set GITGUI_BCK_exists [load_message GITGUI_BCK utf-8]
4091 # -- If both our backup and message files exist use the
4092 # newer of the two files to initialize the buffer.
4094 if {$GITGUI_BCK_exists} {
4095 set m [gitdir GITGUI_MSG]
4096 if {[file isfile $m]} {
4097 if {[file mtime [gitdir GITGUI_BCK]] > [file mtime $m]} {
4098 catch {file delete [gitdir GITGUI_MSG]}
4100 $ui_comm delete 0.0 end
4102 $ui_comm edit modified false
4103 catch {file delete [gitdir GITGUI_BCK]}
4104 set GITGUI_BCK_exists 0
4110 proc backup_commit_buffer {} {
4111 global ui_comm GITGUI_BCK_exists
4113 set m [$ui_comm edit modified]
4114 if {$m || $GITGUI_BCK_exists} {
4115 set msg [string trim [$ui_comm get 0.0 end]]
4116 regsub -all -line {[ \r\t]+$} $msg {} msg
4119 if {$GITGUI_BCK_exists} {
4120 catch {file delete [gitdir GITGUI_BCK]}
4121 set GITGUI_BCK_exists 0
4125 set fd [open [gitdir GITGUI_BCK] w]
4126 fconfigure $fd -encoding utf-8
4127 puts -nonewline $fd $msg
4129 set GITGUI_BCK_exists 1
4133 $ui_comm edit modified false
4136 set ::GITGUI_BCK_i [after 2000 backup_commit_buffer]
4139 backup_commit_buffer
4141 # -- If the user has aspell available we can drive it
4142 # in pipe mode to spellcheck the commit message.
4144 set spell_cmd [list |]
4145 set spell_dict [get_config gui.spellingdictionary]
4146 lappend spell_cmd aspell
4147 if {$spell_dict ne {}} {
4148 lappend spell_cmd --master=$spell_dict
4150 lappend spell_cmd --mode=none
4151 lappend spell_cmd --encoding=utf-8
4152 lappend spell_cmd pipe
4153 if {$spell_dict eq {none}
4154 || [catch {set spell_fd [open $spell_cmd r+]} spell_err]} {
4155 bind_button3 $ui_comm [list tk_popup $ui_comm_ctxm %X %Y]
4157 set ui_comm_spell [spellcheck::init \
4163 unset -nocomplain spell_cmd spell_fd spell_err spell_dict
4166 lock_index begin-read
4167 if {![winfo ismapped .]} {
4171 if {[is_enabled initialamend]} {
4177 if {[is_enabled nocommitmsg]} {
4178 $ui_comm configure -state disabled -background gray
4181 if {[is_enabled multicommit] && ![is_config_false gui.gcwarning]} {
4184 if {[is_enabled retcode]} {
4185 bind . <Destroy> {+terminate_me %W}
4187 if {$picked && [is_config_true gui.autoexplore]} {
4191 # Clear "Initializing..." status
4192 after 500 {$main_status show ""}
4196 # indent-tabs-mode: t