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
2308 } elseif {[$ui_comm edit modified]} {
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]
2314 set fd [open $save w]
2315 fconfigure $fd -encoding utf-8
2316 puts -nonewline $fd $msg
2320 catch {file delete $save}
2324 # -- Cancel our spellchecker if its running.
2326 if {[info exists ui_comm_spell]} {
2330 # -- Remove our editor backup, its not needed.
2332 after cancel $GITGUI_BCK_i
2333 if {$GITGUI_BCK_exists} {
2334 catch {file delete [gitdir GITGUI_BCK]}
2337 # -- Stash our current window geometry into this repository.
2339 set cfg_wmstate [wm state .]
2340 if {[catch {set rc_wmstate $repo_config(gui.wmstate)}]} {
2343 if {$cfg_wmstate ne $rc_wmstate} {
2344 catch {git config gui.wmstate $cfg_wmstate}
2346 if {$cfg_wmstate eq {zoomed}} {
2347 # on Windows wm geometry will lie about window
2348 # position (but not size) when window is zoomed
2349 # restore the window before querying wm geometry
2352 set cfg_geometry [list]
2353 lappend cfg_geometry [wm geometry .]
2355 lappend cfg_geometry [.vpane sashpos 0]
2356 lappend cfg_geometry [.vpane.files sashpos 0]
2358 lappend cfg_geometry [lindex [.vpane sash coord 0] 0]
2359 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 1]
2361 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
2364 if {$cfg_geometry ne $rc_geometry} {
2365 catch {git config gui.geometry $cfg_geometry}
2371 # Briefly enable send again, working around Tk bug
2372 # http://sourceforge.net/tracker/?func=detail&atid=112997&aid=1821174&group_id=12997
2373 tk appname [appname]
2382 proc ui_do_rescan {} {
2383 rescan {force_first_diff ui_ready}
2390 proc next_diff {{after {}}} {
2391 global next_diff_p next_diff_w next_diff_i
2392 show_diff $next_diff_p $next_diff_w {} {} $after
2395 proc find_anchor_pos {lst name} {
2396 set lid [lsearch -sorted -exact $lst $name]
2400 foreach lname $lst {
2401 if {$lname >= $name} break
2409 proc find_file_from {flist idx delta path mmask} {
2412 set len [llength $flist]
2413 while {$idx >= 0 && $idx < $len} {
2414 set name [lindex $flist $idx]
2416 if {$name ne $path && [info exists file_states($name)]} {
2417 set state [lindex $file_states($name) 0]
2419 if {$mmask eq {} || [regexp $mmask $state]} {
2430 proc find_next_diff {w path {lno {}} {mmask {}}} {
2431 global next_diff_p next_diff_w next_diff_i
2432 global file_lists ui_index ui_workdir
2434 set flist $file_lists($w)
2436 set lno [find_anchor_pos $flist $path]
2441 if {$mmask ne {} && ![regexp {(^\^)|(\$$)} $mmask]} {
2442 if {$w eq $ui_index} {
2445 set mmask "$mmask\$"
2449 set idx [find_file_from $flist $lno 1 $path $mmask]
2452 set idx [find_file_from $flist $lno -1 $path $mmask]
2457 set next_diff_p [lindex $flist $idx]
2458 set next_diff_i [expr {$idx+1}]
2465 proc next_diff_after_action {w path {lno {}} {mmask {}}} {
2466 global current_diff_path
2468 if {$path ne $current_diff_path} {
2470 } elseif {[find_next_diff $w $path $lno $mmask]} {
2473 return {reshow_diff;}
2477 proc select_first_diff {after} {
2480 if {[find_next_diff $ui_workdir {} 1 {^_?U}] ||
2481 [find_next_diff $ui_workdir {} 1 {[^O]$}]} {
2488 proc force_first_diff {after} {
2489 global ui_workdir current_diff_path file_states
2491 if {[info exists file_states($current_diff_path)]} {
2492 set state [lindex $file_states($current_diff_path) 0]
2498 if {[string first {U} $state] >= 0} {
2499 # Already a conflict, do nothing
2500 } elseif {[find_next_diff $ui_workdir $current_diff_path {} {^_?U}]} {
2502 } elseif {[string index $state 1] ne {O}} {
2503 # Already a diff & no conflicts, do nothing
2504 } elseif {[find_next_diff $ui_workdir $current_diff_path {} {[^O]$}]} {
2515 proc toggle_or_diff {mode w args} {
2516 global file_states file_lists current_diff_path ui_index ui_workdir
2517 global last_clicked selected_paths file_lists_last_clicked
2519 if {$mode eq "click"} {
2520 foreach {x y} $args break
2521 set pos [split [$w index @$x,$y] .]
2522 foreach {lno col} $pos break
2524 if {$mode eq "toggle"} {
2525 if {$w eq $ui_workdir} {
2530 if {$w eq $ui_index} {
2531 do_unstage_selection
2537 if {$last_clicked ne {}} {
2538 set lno [lindex $last_clicked 1]
2540 if {![info exists file_lists]
2541 || ![info exists file_lists($w)]
2542 || [llength $file_lists($w)] == 0} {
2546 set lno [expr {int([lindex [$w tag ranges in_diff] 0])}]
2548 if {$mode eq "toggle"} {
2551 incr lno [expr {$mode eq "up" ? -1 : 1}]
2556 if {![info exists file_lists]
2557 || ![info exists file_lists($w)]
2558 || [llength $file_lists($w)] < $lno - 1} {
2561 set path [lindex $file_lists($w) [expr {$lno - 1}]]
2568 set last_clicked [list $w $lno]
2570 array unset selected_paths
2571 $ui_index tag remove in_sel 0.0 end
2572 $ui_workdir tag remove in_sel 0.0 end
2574 set file_lists_last_clicked($w) $path
2576 # Determine the state of the file
2577 if {[info exists file_states($path)]} {
2578 set state [lindex $file_states($path) 0]
2583 # Restage the file, or simply show the diff
2584 if {$col == 0 && $y > 1} {
2585 # Conflicts need special handling
2586 if {[string first {U} $state] >= 0} {
2587 # $w must always be $ui_workdir, but...
2588 if {$w ne $ui_workdir} { set lno {} }
2589 merge_stage_workdir $path $lno
2593 if {[string index $state 1] eq {O}} {
2599 set after [next_diff_after_action $w $path $lno $mmask]
2601 if {$w eq $ui_index} {
2603 "Unstaging [short_path $path] from commit" \
2605 [concat $after {ui_ready;}]
2606 } elseif {$w eq $ui_workdir} {
2608 "Adding [short_path $path]" \
2610 [concat $after {ui_ready;}]
2613 set selected_paths($path) 1
2614 show_diff $path $w $lno
2618 proc add_one_to_selection {w x y} {
2619 global file_lists last_clicked selected_paths
2621 set lno [lindex [split [$w index @$x,$y] .] 0]
2622 set path [lindex $file_lists($w) [expr {$lno - 1}]]
2628 if {$last_clicked ne {}
2629 && [lindex $last_clicked 0] ne $w} {
2630 array unset selected_paths
2631 [lindex $last_clicked 0] tag remove in_sel 0.0 end
2634 set last_clicked [list $w $lno]
2635 if {[catch {set in_sel $selected_paths($path)}]} {
2639 unset selected_paths($path)
2640 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
2642 set selected_paths($path) 1
2643 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
2647 proc add_range_to_selection {w x y} {
2648 global file_lists last_clicked selected_paths
2650 if {[lindex $last_clicked 0] ne $w} {
2651 toggle_or_diff click $w $x $y
2655 set lno [lindex [split [$w index @$x,$y] .] 0]
2656 set lc [lindex $last_clicked 1]
2665 foreach path [lrange $file_lists($w) \
2666 [expr {$begin - 1}] \
2667 [expr {$end - 1}]] {
2668 set selected_paths($path) 1
2670 $w tag add in_sel $begin.0 [expr {$end + 1}].0
2673 proc show_more_context {} {
2675 if {$repo_config(gui.diffcontext) < 99} {
2676 incr repo_config(gui.diffcontext)
2681 proc show_less_context {} {
2683 if {$repo_config(gui.diffcontext) > 1} {
2684 incr repo_config(gui.diffcontext) -1
2689 proc focus_widget {widget} {
2690 global file_lists last_clicked selected_paths
2691 global file_lists_last_clicked
2693 if {[llength $file_lists($widget)] > 0} {
2694 set path $file_lists_last_clicked($widget)
2695 set index [lsearch -sorted -exact $file_lists($widget) $path]
2698 set path [lindex $file_lists($widget) $index]
2702 set last_clicked [list $widget [expr $index + 1]]
2703 array unset selected_paths
2704 set selected_paths($path) 1
2705 show_diff $path $widget
2709 proc toggle_commit_type {} {
2710 global commit_type_is_amend
2711 set commit_type_is_amend [expr !$commit_type_is_amend]
2712 do_select_commit_type
2715 ######################################################################
2723 menu .mbar -tearoff 0
2725 # -- Apple Menu (Mac OS X only)
2727 .mbar add cascade -label Apple -menu .mbar.apple
2730 .mbar add cascade -label [mc Repository] -menu .mbar.repository
2731 .mbar add cascade -label [mc Edit] -menu .mbar.edit
2732 if {[is_enabled branch]} {
2733 .mbar add cascade -label [mc Branch] -menu .mbar.branch
2735 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2736 .mbar add cascade -label [mc Commit@@noun] -menu .mbar.commit
2738 if {[is_enabled transport]} {
2739 .mbar add cascade -label [mc Merge] -menu .mbar.merge
2740 .mbar add cascade -label [mc Remote] -menu .mbar.remote
2742 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2743 .mbar add cascade -label [mc Tools] -menu .mbar.tools
2746 # -- Repository Menu
2748 menu .mbar.repository
2751 .mbar.repository add command \
2752 -label [mc "Explore Working Copy"] \
2753 -command {do_explore}
2757 # Use /git-bash.exe if available
2758 set normalized [file normalize $::argv0]
2759 regsub "/mingw../libexec/git-core/git-gui$" \
2760 $normalized "/git-bash.exe" cmdLine
2761 if {$cmdLine != $normalized && [file exists $cmdLine]} {
2762 set cmdLine [list "Git Bash" $cmdLine &]
2764 set cmdLine [list "Git Bash" bash --login -l &]
2766 .mbar.repository add command \
2767 -label [mc "Git Bash"] \
2768 -command {eval exec [auto_execok start] $cmdLine}
2771 if {[is_Windows] || ![is_bare]} {
2772 .mbar.repository add separator
2775 .mbar.repository add command \
2776 -label [mc "Browse Current Branch's Files"] \
2777 -command {browser::new $current_branch}
2778 set ui_browse_current [.mbar.repository index last]
2779 .mbar.repository add command \
2780 -label [mc "Browse Branch Files..."] \
2781 -command browser_open::dialog
2782 .mbar.repository add separator
2784 .mbar.repository add command \
2785 -label [mc "Visualize Current Branch's History"] \
2786 -command {do_gitk $current_branch}
2787 set ui_visualize_current [.mbar.repository index last]
2788 .mbar.repository add command \
2789 -label [mc "Visualize All Branch History"] \
2790 -command {do_gitk --all}
2791 .mbar.repository add separator
2793 proc current_branch_write {args} {
2794 global current_branch
2795 .mbar.repository entryconf $::ui_browse_current \
2796 -label [mc "Browse %s's Files" $current_branch]
2797 .mbar.repository entryconf $::ui_visualize_current \
2798 -label [mc "Visualize %s's History" $current_branch]
2800 trace add variable current_branch write current_branch_write
2802 if {[is_enabled multicommit]} {
2803 .mbar.repository add command -label [mc "Database Statistics"] \
2806 .mbar.repository add command -label [mc "Compress Database"] \
2809 .mbar.repository add command -label [mc "Verify Database"] \
2810 -command do_fsck_objects
2812 .mbar.repository add separator
2815 .mbar.repository add command \
2816 -label [mc "Create Desktop Icon"] \
2817 -command do_cygwin_shortcut
2818 } elseif {[is_Windows]} {
2819 .mbar.repository add command \
2820 -label [mc "Create Desktop Icon"] \
2821 -command do_windows_shortcut
2822 } elseif {[is_MacOSX]} {
2823 .mbar.repository add command \
2824 -label [mc "Create Desktop Icon"] \
2825 -command do_macosx_app
2830 proc ::tk::mac::Quit {args} { do_quit }
2832 .mbar.repository add command -label [mc Quit] \
2840 .mbar.edit add command -label [mc Undo] \
2841 -command {catch {[focus] edit undo}} \
2843 .mbar.edit add command -label [mc Redo] \
2844 -command {catch {[focus] edit redo}} \
2846 .mbar.edit add separator
2847 .mbar.edit add command -label [mc Cut] \
2848 -command {catch {tk_textCut [focus]}} \
2850 .mbar.edit add command -label [mc Copy] \
2851 -command {catch {tk_textCopy [focus]}} \
2853 .mbar.edit add command -label [mc Paste] \
2854 -command {catch {tk_textPaste [focus]; [focus] see insert}} \
2856 .mbar.edit add command -label [mc Delete] \
2857 -command {catch {[focus] delete sel.first sel.last}} \
2859 .mbar.edit add separator
2860 .mbar.edit add command -label [mc "Select All"] \
2861 -command {catch {[focus] tag add sel 0.0 end}} \
2866 if {[is_enabled branch]} {
2869 .mbar.branch add command -label [mc "Create..."] \
2870 -command branch_create::dialog \
2872 lappend disable_on_lock [list .mbar.branch entryconf \
2873 [.mbar.branch index last] -state]
2875 .mbar.branch add command -label [mc "Checkout..."] \
2876 -command branch_checkout::dialog \
2878 lappend disable_on_lock [list .mbar.branch entryconf \
2879 [.mbar.branch index last] -state]
2881 .mbar.branch add command -label [mc "Rename..."] \
2882 -command branch_rename::dialog
2883 lappend disable_on_lock [list .mbar.branch entryconf \
2884 [.mbar.branch index last] -state]
2886 .mbar.branch add command -label [mc "Delete..."] \
2887 -command branch_delete::dialog
2888 lappend disable_on_lock [list .mbar.branch entryconf \
2889 [.mbar.branch index last] -state]
2891 .mbar.branch add command -label [mc "Reset..."] \
2892 -command merge::reset_hard
2893 lappend disable_on_lock [list .mbar.branch entryconf \
2894 [.mbar.branch index last] -state]
2899 proc commit_btn_caption {} {
2900 if {[is_enabled nocommit]} {
2903 return [mc Commit@@verb]
2907 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2910 if {![is_enabled nocommit]} {
2911 .mbar.commit add checkbutton \
2912 -label [mc "Amend Last Commit"] \
2913 -accelerator $M1T-E \
2914 -variable commit_type_is_amend \
2915 -command do_select_commit_type
2916 lappend disable_on_lock \
2917 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2919 .mbar.commit add separator
2922 .mbar.commit add command -label [mc Rescan] \
2923 -command ui_do_rescan \
2925 lappend disable_on_lock \
2926 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2928 .mbar.commit add command -label [mc "Stage To Commit"] \
2929 -command do_add_selection \
2931 lappend disable_on_lock \
2932 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2934 .mbar.commit add command -label [mc "Stage Changed Files To Commit"] \
2935 -command do_add_all \
2937 lappend disable_on_lock \
2938 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2940 .mbar.commit add command -label [mc "Unstage From Commit"] \
2941 -command do_unstage_selection \
2943 lappend disable_on_lock \
2944 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2946 .mbar.commit add command -label [mc "Revert Changes"] \
2947 -command do_revert_selection \
2949 lappend disable_on_lock \
2950 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2952 .mbar.commit add separator
2954 .mbar.commit add command -label [mc "Show Less Context"] \
2955 -command show_less_context \
2956 -accelerator $M1T-\-
2958 .mbar.commit add command -label [mc "Show More Context"] \
2959 -command show_more_context \
2962 .mbar.commit add separator
2964 if {![is_enabled nocommitmsg]} {
2965 .mbar.commit add command -label [mc "Sign Off"] \
2966 -command do_signoff \
2970 .mbar.commit add command -label [commit_btn_caption] \
2971 -command do_commit \
2972 -accelerator $M1T-Return
2973 lappend disable_on_lock \
2974 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2979 if {[is_enabled branch]} {
2981 .mbar.merge add command -label [mc "Local Merge..."] \
2982 -command merge::dialog \
2984 lappend disable_on_lock \
2985 [list .mbar.merge entryconf [.mbar.merge index last] -state]
2986 .mbar.merge add command -label [mc "Abort Merge..."] \
2987 -command merge::reset_hard
2988 lappend disable_on_lock \
2989 [list .mbar.merge entryconf [.mbar.merge index last] -state]
2994 if {[is_enabled transport]} {
2997 .mbar.remote add command \
2998 -label [mc "Add..."] \
2999 -command remote_add::dialog \
3001 .mbar.remote add command \
3002 -label [mc "Push..."] \
3003 -command do_push_anywhere \
3005 .mbar.remote add command \
3006 -label [mc "Delete Branch..."] \
3007 -command remote_branch_delete::dialog
3011 proc ::tk::mac::ShowPreferences {} {do_options}
3015 .mbar.edit add separator
3016 .mbar.edit add command -label [mc "Options..."] \
3022 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
3023 set tools_menubar .mbar.tools
3025 $tools_menubar add separator
3026 $tools_menubar add command -label [mc "Add..."] -command tools_add::dialog
3027 $tools_menubar add command -label [mc "Remove..."] -command tools_remove::dialog
3029 if {[array names repo_config guitool.*.cmd] ne {}} {
3036 .mbar add cascade -label [mc Help] -menu .mbar.help
3040 .mbar.apple add command -label [mc "About %s" [appname]] \
3042 .mbar.apple add separator
3044 .mbar.help add command -label [mc "About %s" [appname]] \
3047 . configure -menu .mbar
3049 set doc_path [githtmldir]
3050 if {$doc_path ne {}} {
3051 set doc_path [file join $doc_path index.html]
3054 set doc_path [exec cygpath --mixed $doc_path]
3058 if {[file isfile $doc_path]} {
3059 set doc_url "file:$doc_path"
3061 set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
3064 proc start_browser {url} {
3065 git "web--browse" $url
3068 .mbar.help add command -label [mc "Online Documentation"] \
3069 -command [list start_browser $doc_url]
3071 .mbar.help add command -label [mc "Show SSH Key"] \
3074 unset doc_path doc_url
3076 # -- Standard bindings
3078 wm protocol . WM_DELETE_WINDOW do_quit
3079 bind all <$M1B-Key-q> do_quit
3080 bind all <$M1B-Key-Q> do_quit
3083 set toplvl_win [winfo toplevel %W]
3085 # If we are destroying the main window, we should call do_quit to take
3086 # care of cleanup before exiting the program.
3087 if {$toplvl_win eq "."} {
3094 bind all <$M1B-Key-w> $m1b_w_script
3095 bind all <$M1B-Key-W> $m1b_w_script
3099 set subcommand_args {}
3101 set s "[mc usage:] $::argv0 $::subcommand $::subcommand_args"
3102 if {[tk windowingsystem] eq "win32"} {
3104 tk_messageBox -icon info -message $s \
3112 proc normalize_relpath {path} {
3114 foreach item [file split $path] {
3115 if {$item eq {.}} continue
3116 if {$item eq {..} && [llength $elements] > 0
3117 && [lindex $elements end] ne {..}} {
3118 set elements [lrange $elements 0 end-1]
3121 lappend elements $item
3123 return [eval file join $elements]
3126 # -- Not a normal commit type invocation? Do that instead!
3128 switch -- $subcommand {
3131 if {$subcommand eq "blame"} {
3132 set subcommand_args {[--line=<num>] rev? path}
3134 set subcommand_args {rev? path}
3136 if {$argv eq {}} usage
3142 set p [file join $_prefix $a]
3144 if {$is_path || [file exists $p]} {
3145 if {$path ne {}} usage
3146 set path [normalize_relpath $p]
3148 } elseif {$a eq {--}} {
3150 if {$head ne {}} usage
3155 } elseif {[regexp {^--line=(\d+)$} $a a lnum]} {
3156 if {$jump_spec ne {} || $head ne {}} usage
3157 set jump_spec [list $lnum]
3158 } elseif {$head eq {}} {
3159 if {$head ne {}} usage
3168 if {$head ne {} && $path eq {}} {
3169 if {[string index $head 0] eq {/}} {
3170 set path [normalize_relpath $head]
3173 set path [normalize_relpath $_prefix$head]
3181 if {[regexp {^[0-9a-f]{1,39}$} $head]} {
3183 set head [git rev-parse --verify $head]
3185 if {[tk windowingsystem] eq "win32"} {
3186 tk_messageBox -icon error -title [mc Error] -message $err
3193 set current_branch $head
3197 switch -- $subcommand {
3199 if {$jump_spec ne {}} usage
3201 if {$path ne {} && [file isdirectory $path]} {
3202 set head $current_branch
3208 browser::new $head $path
3211 if {$head eq {} && ![file exists $path]} {
3212 catch {wm withdraw .}
3216 -title [mc "git-gui: fatal error"] \
3217 -message [mc "fatal: cannot stat path %s: No such file or directory" $path]
3220 blame::new $head $path $jump_spec
3227 if {[llength $argv] != 0} {
3230 # fall through to setup UI for commits
3233 set err "[mc usage:] $argv0 \[{blame|browser|citool}\]"
3234 if {[tk windowingsystem] eq "win32"} {
3236 tk_messageBox -icon error -message $err \
3247 ${NS}::frame .branch
3248 if {!$use_ttk} {.branch configure -borderwidth 1 -relief sunken}
3249 ${NS}::label .branch.l1 \
3250 -text [mc "Current Branch:"] \
3253 ${NS}::label .branch.cb \
3254 -textvariable current_branch \
3257 pack .branch.l1 -side left
3258 pack .branch.cb -side left -fill x
3259 pack .branch -side top -fill x
3261 # -- Main Window Layout
3263 ${NS}::panedwindow .vpane -orient horizontal
3264 ${NS}::panedwindow .vpane.files -orient vertical
3266 .vpane add .vpane.files
3268 .vpane add .vpane.files -sticky nsew -height 100 -width 200
3270 pack .vpane -anchor n -side top -fill both -expand 1
3272 # -- Working Directory File List
3274 textframe .vpane.files.workdir -height 100 -width 200
3275 tlabel .vpane.files.workdir.title -text [mc "Unstaged Changes"] \
3276 -background lightsalmon -foreground black
3279 -width 20 -height 10 \
3281 -takefocus 1 -highlightthickness 1\
3282 -cursor $cursor_ptr \
3283 -xscrollcommand {.vpane.files.workdir.sx set} \
3284 -yscrollcommand {.vpane.files.workdir.sy set} \
3286 ${NS}::scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
3287 ${NS}::scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
3288 pack .vpane.files.workdir.title -side top -fill x
3289 pack .vpane.files.workdir.sx -side bottom -fill x
3290 pack .vpane.files.workdir.sy -side right -fill y
3291 pack $ui_workdir -side left -fill both -expand 1
3293 # -- Index File List
3295 textframe .vpane.files.index -height 100 -width 200
3296 tlabel .vpane.files.index.title \
3297 -text [mc "Staged Changes (Will Commit)"] \
3298 -background lightgreen -foreground black
3301 -width 20 -height 10 \
3303 -takefocus 1 -highlightthickness 1\
3304 -cursor $cursor_ptr \
3305 -xscrollcommand {.vpane.files.index.sx set} \
3306 -yscrollcommand {.vpane.files.index.sy set} \
3308 ${NS}::scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
3309 ${NS}::scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
3310 pack .vpane.files.index.title -side top -fill x
3311 pack .vpane.files.index.sx -side bottom -fill x
3312 pack .vpane.files.index.sy -side right -fill y
3313 pack $ui_index -side left -fill both -expand 1
3315 # -- Insert the workdir and index into the panes
3317 .vpane.files add .vpane.files.workdir
3318 .vpane.files add .vpane.files.index
3320 .vpane.files paneconfigure .vpane.files.workdir -sticky news
3321 .vpane.files paneconfigure .vpane.files.index -sticky news
3324 foreach i [list $ui_index $ui_workdir] {
3326 $i tag conf in_diff \
3327 -background $color::select_bg \
3328 -foreground $color::select_fg
3332 # -- Diff and Commit Area
3335 ${NS}::panedwindow .vpane.lower -orient vertical
3336 ${NS}::frame .vpane.lower.commarea
3337 ${NS}::frame .vpane.lower.diff -relief sunken -borderwidth 1 -height 500
3338 .vpane.lower add .vpane.lower.diff
3339 .vpane.lower add .vpane.lower.commarea
3340 .vpane add .vpane.lower
3342 .vpane.lower pane .vpane.lower.diff -weight 1
3343 .vpane.lower pane .vpane.lower.commarea -weight 0
3345 .vpane.lower paneconfigure .vpane.lower.diff -stretch always
3346 .vpane.lower paneconfigure .vpane.lower.commarea -stretch never
3349 frame .vpane.lower -height 300 -width 400
3350 frame .vpane.lower.commarea
3351 frame .vpane.lower.diff -relief sunken -borderwidth 1
3352 pack .vpane.lower.diff -fill both -expand 1
3353 pack .vpane.lower.commarea -side bottom -fill x
3354 .vpane add .vpane.lower
3355 .vpane paneconfigure .vpane.lower -sticky nsew
3358 # -- Commit Area Buttons
3360 ${NS}::frame .vpane.lower.commarea.buttons
3361 ${NS}::label .vpane.lower.commarea.buttons.l -text {} \
3364 pack .vpane.lower.commarea.buttons.l -side top -fill x
3365 pack .vpane.lower.commarea.buttons -side left -fill y
3367 ${NS}::button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \
3368 -command ui_do_rescan
3369 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
3370 lappend disable_on_lock \
3371 {.vpane.lower.commarea.buttons.rescan conf -state}
3373 ${NS}::button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \
3375 pack .vpane.lower.commarea.buttons.incall -side top -fill x
3376 lappend disable_on_lock \
3377 {.vpane.lower.commarea.buttons.incall conf -state}
3379 if {![is_enabled nocommitmsg]} {
3380 ${NS}::button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \
3382 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
3385 ${NS}::button .vpane.lower.commarea.buttons.commit -text [commit_btn_caption] \
3387 pack .vpane.lower.commarea.buttons.commit -side top -fill x
3388 lappend disable_on_lock \
3389 {.vpane.lower.commarea.buttons.commit conf -state}
3391 if {![is_enabled nocommit]} {
3392 ${NS}::button .vpane.lower.commarea.buttons.push -text [mc Push] \
3393 -command do_push_anywhere
3394 pack .vpane.lower.commarea.buttons.push -side top -fill x
3397 # -- Commit Message Buffer
3399 ${NS}::frame .vpane.lower.commarea.buffer
3400 ${NS}::frame .vpane.lower.commarea.buffer.header
3401 set ui_comm .vpane.lower.commarea.buffer.frame.t
3402 set ui_coml .vpane.lower.commarea.buffer.header.l
3404 if {![is_enabled nocommit]} {
3405 ${NS}::checkbutton .vpane.lower.commarea.buffer.header.amend \
3406 -text [mc "Amend Last Commit"] \
3407 -variable commit_type_is_amend \
3408 -command do_select_commit_type
3409 lappend disable_on_lock \
3410 [list .vpane.lower.commarea.buffer.header.amend conf -state]
3413 ${NS}::label $ui_coml \
3416 proc trace_commit_type {varname args} {
3417 global ui_coml commit_type
3418 switch -glob -- $commit_type {
3419 initial {set txt [mc "Initial Commit Message:"]}
3420 amend {set txt [mc "Amended Commit Message:"]}
3421 amend-initial {set txt [mc "Amended Initial Commit Message:"]}
3422 amend-merge {set txt [mc "Amended Merge Commit Message:"]}
3423 merge {set txt [mc "Merge Commit Message:"]}
3424 * {set txt [mc "Commit Message:"]}
3426 $ui_coml conf -text $txt
3428 trace add variable commit_type write trace_commit_type
3429 pack $ui_coml -side left -fill x
3431 if {![is_enabled nocommit]} {
3432 pack .vpane.lower.commarea.buffer.header.amend -side right
3435 textframe .vpane.lower.commarea.buffer.frame
3440 -autoseparators true \
3442 -highlightthickness 1 \
3444 -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \
3446 -xscrollcommand {.vpane.lower.commarea.buffer.frame.sbx set} \
3447 -yscrollcommand {.vpane.lower.commarea.buffer.frame.sby set}
3448 ${NS}::scrollbar .vpane.lower.commarea.buffer.frame.sbx \
3449 -orient horizontal \
3450 -command [list $ui_comm xview]
3451 ${NS}::scrollbar .vpane.lower.commarea.buffer.frame.sby \
3453 -command [list $ui_comm yview]
3455 pack .vpane.lower.commarea.buffer.frame.sbx -side bottom -fill x
3456 pack .vpane.lower.commarea.buffer.frame.sby -side right -fill y
3457 pack $ui_comm -side left -fill y
3458 pack .vpane.lower.commarea.buffer.header -side top -fill x
3459 pack .vpane.lower.commarea.buffer.frame -side left -fill y
3460 pack .vpane.lower.commarea.buffer -side left -fill y
3462 # -- Commit Message Buffer Context Menu
3464 set ctxm .vpane.lower.commarea.buffer.ctxm
3465 menu $ctxm -tearoff 0
3468 -command {tk_textCut $ui_comm}
3471 -command {tk_textCopy $ui_comm}
3474 -command {tk_textPaste $ui_comm}
3476 -label [mc Delete] \
3477 -command {catch {$ui_comm delete sel.first sel.last}}
3480 -label [mc "Select All"] \
3481 -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
3483 -label [mc "Copy All"] \
3485 $ui_comm tag add sel 0.0 end
3486 tk_textCopy $ui_comm
3487 $ui_comm tag remove sel 0.0 end
3491 -label [mc "Sign Off"] \
3493 set ui_comm_ctxm $ctxm
3497 proc trace_current_diff_path {varname args} {
3498 global current_diff_path diff_actions file_states
3499 if {$current_diff_path eq {}} {
3505 set p $current_diff_path
3506 set s [mapdesc [lindex $file_states($p) 0] $p]
3508 set p [escape_path $p]
3512 .vpane.lower.diff.header.status configure -text $s
3513 .vpane.lower.diff.header.file configure -text $f
3514 .vpane.lower.diff.header.path configure -text $p
3515 foreach w $diff_actions {
3519 trace add variable current_diff_path write trace_current_diff_path
3521 gold_frame .vpane.lower.diff.header
3522 tlabel .vpane.lower.diff.header.status \
3525 -width $max_status_desc \
3528 tlabel .vpane.lower.diff.header.file \
3533 tlabel .vpane.lower.diff.header.path \
3538 -font [eval font create [font configure font_ui] -underline 1] \
3540 pack .vpane.lower.diff.header.status -side left
3541 pack .vpane.lower.diff.header.file -side left
3542 pack .vpane.lower.diff.header.path -fill x
3543 set ctxm .vpane.lower.diff.header.ctxm
3544 menu $ctxm -tearoff 0
3552 -- $current_diff_path
3556 -command {do_file_open $current_diff_path}
3557 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3558 bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
3559 bind .vpane.lower.diff.header.path <Button-1> {do_file_open $current_diff_path}
3563 textframe .vpane.lower.diff.body
3564 set ui_diff .vpane.lower.diff.body.t
3567 -width 80 -height 5 -wrap none \
3569 -takefocus 1 -highlightthickness 1 \
3570 -xscrollcommand {.vpane.lower.diff.body.sbx set} \
3571 -yscrollcommand {.vpane.lower.diff.body.sby set} \
3573 catch {$ui_diff configure -tabstyle wordprocessor}
3574 ${NS}::scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
3575 -command [list $ui_diff xview]
3576 ${NS}::scrollbar .vpane.lower.diff.body.sby -orient vertical \
3577 -command [list $ui_diff yview]
3578 pack .vpane.lower.diff.body.sbx -side bottom -fill x
3579 pack .vpane.lower.diff.body.sby -side right -fill y
3580 pack $ui_diff -side left -fill both -expand 1
3581 pack .vpane.lower.diff.header -side top -fill x
3582 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
3584 foreach {n c} {0 black 1 red4 2 green4 3 yellow4 4 blue4 5 magenta4 6 cyan4 7 grey60} {
3585 $ui_diff tag configure clr4$n -background $c
3586 $ui_diff tag configure clri4$n -foreground $c
3587 $ui_diff tag configure clr3$n -foreground $c
3588 $ui_diff tag configure clri3$n -background $c
3590 $ui_diff tag configure clr1 -font font_diffbold
3591 $ui_diff tag configure clr4 -underline 1
3593 $ui_diff tag conf d_info -foreground blue -font font_diffbold
3595 $ui_diff tag conf d_cr -elide true
3596 $ui_diff tag conf d_@ -font font_diffbold
3597 $ui_diff tag conf d_+ -foreground {#00a000}
3598 $ui_diff tag conf d_- -foreground red
3600 $ui_diff tag conf d_++ -foreground {#00a000}
3601 $ui_diff tag conf d_-- -foreground red
3602 $ui_diff tag conf d_+s \
3603 -foreground {#00a000} \
3604 -background {#e2effa}
3605 $ui_diff tag conf d_-s \
3607 -background {#e2effa}
3608 $ui_diff tag conf d_s+ \
3609 -foreground {#00a000} \
3611 $ui_diff tag conf d_s- \
3615 $ui_diff tag conf d< \
3616 -foreground orange \
3618 $ui_diff tag conf d| \
3619 -foreground orange \
3621 $ui_diff tag conf d= \
3622 -foreground orange \
3624 $ui_diff tag conf d> \
3625 -foreground orange \
3628 $ui_diff tag raise sel
3630 # -- Diff Body Context Menu
3633 proc create_common_diff_popup {ctxm} {
3635 -label [mc Refresh] \
3636 -command reshow_diff
3637 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3640 -command {tk_textCopy $ui_diff}
3641 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3643 -label [mc "Select All"] \
3644 -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
3645 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3647 -label [mc "Copy All"] \
3649 $ui_diff tag add sel 0.0 end
3650 tk_textCopy $ui_diff
3651 $ui_diff tag remove sel 0.0 end
3653 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3656 -label [mc "Decrease Font Size"] \
3657 -command {incr_font_size font_diff -1}
3658 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3660 -label [mc "Increase Font Size"] \
3661 -command {incr_font_size font_diff 1}
3662 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3666 build_encoding_menu $emenu [list force_diff_encoding]
3668 -label [mc "Encoding"] \
3670 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3672 $ctxm add command -label [mc "Options..."] \
3676 set ctxm .vpane.lower.diff.body.ctxm
3677 menu $ctxm -tearoff 0
3679 -label [mc "Apply/Reverse Hunk"] \
3680 -command {apply_or_revert_hunk $cursorX $cursorY 0}
3681 set ui_diff_applyhunk [$ctxm index last]
3682 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
3684 -label [mc "Apply/Reverse Line"] \
3685 -command {apply_or_revert_range_or_line $cursorX $cursorY 0; do_rescan}
3686 set ui_diff_applyline [$ctxm index last]
3687 lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state]
3690 -label [mc "Revert Hunk"] \
3691 -command {apply_or_revert_hunk $cursorX $cursorY 1}
3692 set ui_diff_reverthunk [$ctxm index last]
3693 lappend diff_actions [list $ctxm entryconf $ui_diff_reverthunk -state]
3695 -label [mc "Revert Line"] \
3696 -command {apply_or_revert_range_or_line $cursorX $cursorY 1; do_rescan}
3697 set ui_diff_revertline [$ctxm index last]
3698 lappend diff_actions [list $ctxm entryconf $ui_diff_revertline -state]
3700 -label [mc "Undo Last Revert"] \
3701 -command {undo_last_revert; do_rescan}
3702 set ui_diff_undorevert [$ctxm index last]
3703 lappend diff_actions [list $ctxm entryconf $ui_diff_undorevert -state]
3706 -label [mc "Show Less Context"] \
3707 -command show_less_context
3708 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3710 -label [mc "Show More Context"] \
3711 -command show_more_context
3712 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3714 create_common_diff_popup $ctxm
3716 set ctxmmg .vpane.lower.diff.body.ctxmmg
3717 menu $ctxmmg -tearoff 0
3718 $ctxmmg add command \
3719 -label [mc "Run Merge Tool"] \
3720 -command {merge_resolve_tool}
3721 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3722 $ctxmmg add separator
3723 $ctxmmg add command \
3724 -label [mc "Use Remote Version"] \
3725 -command {merge_resolve_one 3}
3726 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3727 $ctxmmg add command \
3728 -label [mc "Use Local Version"] \
3729 -command {merge_resolve_one 2}
3730 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3731 $ctxmmg add command \
3732 -label [mc "Revert To Base"] \
3733 -command {merge_resolve_one 1}
3734 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3735 $ctxmmg add separator
3736 $ctxmmg add command \
3737 -label [mc "Show Less Context"] \
3738 -command show_less_context
3739 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3740 $ctxmmg add command \
3741 -label [mc "Show More Context"] \
3742 -command show_more_context
3743 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3744 $ctxmmg add separator
3745 create_common_diff_popup $ctxmmg
3747 set ctxmsm .vpane.lower.diff.body.ctxmsm
3748 menu $ctxmsm -tearoff 0
3749 $ctxmsm add command \
3750 -label [mc "Visualize These Changes In The Submodule"] \
3751 -command {do_gitk -- true}
3752 lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
3753 $ctxmsm add command \
3754 -label [mc "Visualize Current Branch History In The Submodule"] \
3755 -command {do_gitk {} true}
3756 lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
3757 $ctxmsm add command \
3758 -label [mc "Visualize All Branch History In The Submodule"] \
3759 -command {do_gitk --all true}
3760 lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
3761 $ctxmsm add separator
3762 $ctxmsm add command \
3763 -label [mc "Start git gui In The Submodule"] \
3764 -command {do_git_gui}
3765 lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
3766 $ctxmsm add separator
3767 create_common_diff_popup $ctxmsm
3769 proc has_textconv {path} {
3770 if {[is_config_false gui.textconv]} {
3773 set filter [gitattr $path diff set]
3774 set textconv [get_config [join [list diff $filter textconv] .]]
3775 if {$filter ne {set} && $textconv ne {}} {
3782 proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} {
3783 global current_diff_path file_states last_revert
3786 if {[info exists file_states($current_diff_path)]} {
3787 set state [lindex $file_states($current_diff_path) 0]
3791 if {[string first {U} $state] >= 0} {
3792 tk_popup $ctxmmg $X $Y
3793 } elseif {$::is_submodule_diff} {
3794 tk_popup $ctxmsm $X $Y
3796 set has_range [expr {[$::ui_diff tag nextrange sel 0.0] != {}}]
3797 set u [mc "Undo Last Revert"]
3798 if {$::ui_index eq $::current_diff_side} {
3799 set l [mc "Unstage Hunk From Commit"]
3800 set h [mc "Revert Hunk"]
3803 set t [mc "Unstage Lines From Commit"]
3804 set r [mc "Revert Lines"]
3806 set t [mc "Unstage Line From Commit"]
3807 set r [mc "Revert Line"]
3810 set l [mc "Stage Hunk For Commit"]
3811 set h [mc "Revert Hunk"]
3814 set t [mc "Stage Lines For Commit"]
3815 set r [mc "Revert Lines"]
3817 set t [mc "Stage Line For Commit"]
3818 set r [mc "Revert Line"]
3822 || $current_diff_path eq {}
3825 || [string match {?T} $state]
3826 || [string match {T?} $state]
3827 || [has_textconv $current_diff_path]} {
3829 set revert_state disabled
3833 # Only allow reverting changes in the working tree. If
3834 # the user wants to revert changes in the index, they
3835 # need to unstage those first.
3836 if {$::ui_workdir eq $::current_diff_side} {
3837 set revert_state normal
3839 set revert_state disabled
3843 if {$last_revert eq {}} {
3844 set undo_state disabled
3846 set undo_state normal
3849 $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
3850 $ctxm entryconf $::ui_diff_applyline -state $s -label $t
3851 $ctxm entryconf $::ui_diff_revertline -state $revert_state \
3853 $ctxm entryconf $::ui_diff_reverthunk -state $revert_state \
3855 $ctxm entryconf $::ui_diff_undorevert -state $undo_state \
3858 tk_popup $ctxm $X $Y
3861 bind_button3 $ui_diff [list popup_diff_menu $ctxm $ctxmmg $ctxmsm %x %y %X %Y]
3865 set main_status [::status_bar::new .status]
3866 pack .status -anchor w -side bottom -fill x
3867 $main_status show [mc "Initializing..."]
3871 proc on_ttk_pane_mapped {w pane pos} {
3873 after 0 [list after idle [list $w sashpos $pane $pos]]
3875 proc on_tk_pane_mapped {w pane x y} {
3877 after 0 [list after idle [list $w sash place $pane $x $y]]
3879 proc on_application_mapped {} {
3880 global repo_config use_ttk
3882 set gm $repo_config(gui.geometry)
3885 [list on_ttk_pane_mapped %W 0 [lindex $gm 1]]
3886 bind .vpane.files <Map> \
3887 [list on_ttk_pane_mapped %W 0 [lindex $gm 2]]
3890 [list on_tk_pane_mapped %W 0 \
3892 [lindex [.vpane sash coord 0] 1]]
3893 bind .vpane.files <Map> \
3894 [list on_tk_pane_mapped %W 0 \
3895 [lindex [.vpane.files sash coord 0] 0] \
3898 wm geometry . [lindex $gm 0]
3900 if {[info exists repo_config(gui.geometry)]} {
3901 bind . <Map> [list on_application_mapped]
3902 wm geometry . [lindex $repo_config(gui.geometry) 0]
3905 # -- Load window state
3907 if {[info exists repo_config(gui.wmstate)]} {
3908 catch {wm state . $repo_config(gui.wmstate)}
3913 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
3914 bind $ui_comm <$M1B-Key-t> {do_add_selection;break}
3915 bind $ui_comm <$M1B-Key-T> {do_add_selection;break}
3916 bind $ui_comm <$M1B-Key-u> {do_unstage_selection;break}
3917 bind $ui_comm <$M1B-Key-U> {do_unstage_selection;break}
3918 bind $ui_comm <$M1B-Key-j> {do_revert_selection;break}
3919 bind $ui_comm <$M1B-Key-J> {do_revert_selection;break}
3920 bind $ui_comm <$M1B-Key-i> {do_add_all;break}
3921 bind $ui_comm <$M1B-Key-I> {do_add_all;break}
3922 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
3923 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
3924 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
3925 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
3926 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
3927 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
3928 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3929 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3930 bind $ui_comm <$M1B-Key-minus> {show_less_context;break}
3931 bind $ui_comm <$M1B-Key-KP_Subtract> {show_less_context;break}
3932 bind $ui_comm <$M1B-Key-equal> {show_more_context;break}
3933 bind $ui_comm <$M1B-Key-plus> {show_more_context;break}
3934 bind $ui_comm <$M1B-Key-KP_Add> {show_more_context;break}
3935 bind $ui_comm <$M1B-Key-BackSpace> {event generate %W <Meta-Delete>;break}
3936 bind $ui_comm <$M1B-Key-Delete> {event generate %W <Meta-d>;break}
3938 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
3939 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
3940 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
3941 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
3942 bind $ui_diff <$M1B-Key-v> {break}
3943 bind $ui_diff <$M1B-Key-V> {break}
3944 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3945 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3946 bind $ui_diff <$M1B-Key-j> {do_revert_selection;break}
3947 bind $ui_diff <$M1B-Key-J> {do_revert_selection;break}
3948 bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break}
3949 bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break}
3950 bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break}
3951 bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break}
3952 bind $ui_diff <Key-k> {catch {%W yview scroll -1 units};break}
3953 bind $ui_diff <Key-j> {catch {%W yview scroll 1 units};break}
3954 bind $ui_diff <Key-h> {catch {%W xview scroll -1 units};break}
3955 bind $ui_diff <Key-l> {catch {%W xview scroll 1 units};break}
3956 bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
3957 bind $ui_diff <Control-Key-f> {catch {%W yview scroll 1 pages};break}
3958 bind $ui_diff <Button-1> {focus %W}
3960 if {[is_enabled branch]} {
3961 bind . <$M1B-Key-n> branch_create::dialog
3962 bind . <$M1B-Key-N> branch_create::dialog
3963 bind . <$M1B-Key-o> branch_checkout::dialog
3964 bind . <$M1B-Key-O> branch_checkout::dialog
3965 bind . <$M1B-Key-m> merge::dialog
3966 bind . <$M1B-Key-M> merge::dialog
3968 if {[is_enabled transport]} {
3969 bind . <$M1B-Key-p> do_push_anywhere
3970 bind . <$M1B-Key-P> do_push_anywhere
3973 bind . <Key-F5> ui_do_rescan
3974 bind . <$M1B-Key-r> ui_do_rescan
3975 bind . <$M1B-Key-R> ui_do_rescan
3976 bind . <$M1B-Key-s> do_signoff
3977 bind . <$M1B-Key-S> do_signoff
3978 bind . <$M1B-Key-t> { toggle_or_diff toggle %W }
3979 bind . <$M1B-Key-T> { toggle_or_diff toggle %W }
3980 bind . <$M1B-Key-u> { toggle_or_diff toggle %W }
3981 bind . <$M1B-Key-U> { toggle_or_diff toggle %W }
3982 bind . <$M1B-Key-j> do_revert_selection
3983 bind . <$M1B-Key-J> do_revert_selection
3984 bind . <$M1B-Key-i> do_add_all
3985 bind . <$M1B-Key-I> do_add_all
3986 bind . <$M1B-Key-e> toggle_commit_type
3987 bind . <$M1B-Key-E> toggle_commit_type
3988 bind . <$M1B-Key-minus> {show_less_context;break}
3989 bind . <$M1B-Key-KP_Subtract> {show_less_context;break}
3990 bind . <$M1B-Key-equal> {show_more_context;break}
3991 bind . <$M1B-Key-plus> {show_more_context;break}
3992 bind . <$M1B-Key-KP_Add> {show_more_context;break}
3993 bind . <$M1B-Key-Return> do_commit
3994 bind . <$M1B-Key-KP_Enter> do_commit
3995 foreach i [list $ui_index $ui_workdir] {
3996 bind $i <Button-1> { toggle_or_diff click %W %x %y; break }
3997 bind $i <$M1B-Button-1> { add_one_to_selection %W %x %y; break }
3998 bind $i <Shift-Button-1> { add_range_to_selection %W %x %y; break }
3999 bind $i <Key-Up> { toggle_or_diff up %W; break }
4000 bind $i <Key-Down> { toggle_or_diff down %W; break }
4004 bind . <Alt-Key-1> {focus_widget $::ui_workdir}
4005 bind . <Alt-Key-2> {focus_widget $::ui_index}
4006 bind . <Alt-Key-3> {focus $::ui_diff}
4007 bind . <Alt-Key-4> {focus $::ui_comm}
4009 set file_lists_last_clicked($ui_index) {}
4010 set file_lists_last_clicked($ui_workdir) {}
4012 set file_lists($ui_index) [list]
4013 set file_lists($ui_workdir) [list]
4015 wm title . "[appname] ([reponame]) [file normalize $_gitworktree]"
4016 focus -force $ui_comm
4018 # -- Warn the user about environmental problems. Cygwin's Tcl
4019 # does *not* pass its env array onto any processes it spawns.
4020 # This means that git processes get none of our environment.
4025 set msg [mc "Possible environment issues exist.
4027 The following environment variables are probably
4028 going to be ignored by any Git subprocess run
4032 foreach name [array names env] {
4033 switch -regexp -- $name {
4034 {^GIT_INDEX_FILE$} -
4035 {^GIT_OBJECT_DIRECTORY$} -
4036 {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
4038 {^GIT_EXTERNAL_DIFF$} -
4042 {^GIT_(AUTHOR|COMMITTER)_DATE$} {
4043 append msg " - $name\n"
4046 {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
4047 append msg " - $name\n"
4049 set suggest_user $name
4053 if {$ignored_env > 0} {
4055 This is due to a known issue with the
4056 Tcl binary distributed by Cygwin."]
4058 if {$suggest_user ne {}} {
4061 A good replacement for %s
4062 is placing values for the user.name and
4063 user.email settings into your personal
4069 unset ignored_env msg suggest_user name
4072 # -- Only initialize complex UI if we are going to stay running.
4074 if {[is_enabled transport]} {
4077 set n [.mbar.remote index end]
4078 populate_remotes_menu
4079 set n [expr {[.mbar.remote index end] - $n}]
4081 if {[.mbar.remote type 0] eq "tearoff"} { incr n }
4082 .mbar.remote insert $n separator
4087 if {[winfo exists $ui_comm]} {
4088 set GITGUI_BCK_exists [load_message GITGUI_BCK utf-8]
4090 # -- If both our backup and message files exist use the
4091 # newer of the two files to initialize the buffer.
4093 if {$GITGUI_BCK_exists} {
4094 set m [gitdir GITGUI_MSG]
4095 if {[file isfile $m]} {
4096 if {[file mtime [gitdir GITGUI_BCK]] > [file mtime $m]} {
4097 catch {file delete [gitdir GITGUI_MSG]}
4099 $ui_comm delete 0.0 end
4101 $ui_comm edit modified false
4102 catch {file delete [gitdir GITGUI_BCK]}
4103 set GITGUI_BCK_exists 0
4109 proc backup_commit_buffer {} {
4110 global ui_comm GITGUI_BCK_exists
4112 set m [$ui_comm edit modified]
4113 if {$m || $GITGUI_BCK_exists} {
4114 set msg [string trim [$ui_comm get 0.0 end]]
4115 regsub -all -line {[ \r\t]+$} $msg {} msg
4118 if {$GITGUI_BCK_exists} {
4119 catch {file delete [gitdir GITGUI_BCK]}
4120 set GITGUI_BCK_exists 0
4124 set fd [open [gitdir GITGUI_BCK] w]
4125 fconfigure $fd -encoding utf-8
4126 puts -nonewline $fd $msg
4128 set GITGUI_BCK_exists 1
4132 $ui_comm edit modified false
4135 set ::GITGUI_BCK_i [after 2000 backup_commit_buffer]
4138 backup_commit_buffer
4140 # -- If the user has aspell available we can drive it
4141 # in pipe mode to spellcheck the commit message.
4143 set spell_cmd [list |]
4144 set spell_dict [get_config gui.spellingdictionary]
4145 lappend spell_cmd aspell
4146 if {$spell_dict ne {}} {
4147 lappend spell_cmd --master=$spell_dict
4149 lappend spell_cmd --mode=none
4150 lappend spell_cmd --encoding=utf-8
4151 lappend spell_cmd pipe
4152 if {$spell_dict eq {none}
4153 || [catch {set spell_fd [open $spell_cmd r+]} spell_err]} {
4154 bind_button3 $ui_comm [list tk_popup $ui_comm_ctxm %X %Y]
4156 set ui_comm_spell [spellcheck::init \
4162 unset -nocomplain spell_cmd spell_fd spell_err spell_dict
4165 lock_index begin-read
4166 if {![winfo ismapped .]} {
4170 if {[is_enabled initialamend]} {
4176 if {[is_enabled nocommitmsg]} {
4177 $ui_comm configure -state disabled -background gray
4180 if {[is_enabled multicommit] && ![is_config_false gui.gcwarning]} {
4183 if {[is_enabled retcode]} {
4184 bind . <Destroy> {+terminate_me %W}
4186 if {$picked && [is_config_true gui.autoexplore]} {
4190 # Clear "Initializing..." status
4191 after 500 {$main_status show ""}
4195 # indent-tabs-mode: t