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 [encoding convertfrom utf-8 {
14 Copyright © 2006, 2007 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, write to the Free Software
28 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA}]
30 ######################################################################
32 ## Tcl/Tk sanity check
34 if {[catch {package require Tcl 8.4} err]
35 || [catch {package require Tk 8.4} err]
41 -title [mc "git-gui: fatal error"] \
46 catch {rename send {}} ; # What an evil concept...
48 ######################################################################
52 set oguilib {@@GITGUI_LIBDIR@@}
53 set oguirel {@@GITGUI_RELATIVE@@}
54 if {$oguirel eq {1}} {
55 set oguilib [file dirname [file normalize $argv0]]
56 if {[file tail $oguilib] eq {git-core}} {
57 set oguilib [file dirname $oguilib]
59 set oguilib [file dirname $oguilib]
60 set oguilib [file join $oguilib share git-gui lib]
61 set oguimsg [file join $oguilib msgs]
62 } elseif {[string match @@* $oguirel]} {
63 set oguilib [file join [file dirname [file normalize $argv0]] lib]
64 set oguimsg [file join [file dirname [file normalize $argv0]] po]
66 set oguimsg [file join $oguilib msgs]
70 ######################################################################
72 ## enable verbose loading?
74 if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
76 rename auto_load real__auto_load
77 proc auto_load {name args} {
78 puts stderr "auto_load $name"
79 return [uplevel 1 real__auto_load $name $args]
81 rename source real__source
83 puts stderr "source $name"
84 uplevel 1 real__source $name
88 ######################################################################
90 ## Internationalization (i18n) through msgcat and gettext. See
91 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
93 package require msgcat
96 set cmk [string first @@ $fmt]
98 return [string range $fmt 0 [expr {$cmk - 1}]]
103 proc mc {en_fmt args} {
104 set fmt [_mc_trim [::msgcat::mc $en_fmt]]
105 if {[catch {set msg [eval [list format $fmt] $args]} err]} {
106 set msg [eval [list format [_mc_trim $en_fmt]] $args]
112 return [join $args {}]
115 ::msgcat::mcload $oguimsg
118 ######################################################################
122 set _appname {Git Gui}
129 set _trace [lsearch -exact $argv --trace]
131 set argv [lreplace $argv $_trace $_trace]
147 return [eval [list file join $_gitdir] $args]
150 proc gitexec {args} {
152 if {$_gitexec eq {}} {
153 if {[catch {set _gitexec [git --exec-path]} err]} {
154 error "Git not installed?\n\n$err"
157 set _gitexec [exec cygpath \
162 set _gitexec [file normalize $_gitexec]
168 return [eval [list file join $_gitexec] $args]
176 if {[tk windowingsystem] eq {aqua}} {
183 if {$::tcl_platform(platform) eq {windows}} {
191 if {$_iscygwin eq {}} {
192 if {$::tcl_platform(platform) eq {windows}} {
193 if {[catch {set p [exec cygpath --windir]} err]} {
205 proc is_enabled {option} {
206 global enabled_options
207 if {[catch {set on $enabled_options($option)}]} {return 0}
211 proc enable_option {option} {
212 global enabled_options
213 set enabled_options($option) 1
216 proc disable_option {option} {
217 global enabled_options
218 set enabled_options($option) 0
221 ######################################################################
225 proc is_many_config {name} {
226 switch -glob -- $name {
236 proc is_config_true {name} {
238 if {[catch {set v $repo_config($name)}]} {
240 } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
247 proc get_config {name} {
249 if {[catch {set v $repo_config($name)}]} {
256 ######################################################################
260 proc _trace_exec {cmd} {
261 if {!$::_trace} return
267 if {[regexp {[ \t\r\n'"$?*]} $v]} {
275 proc _git_cmd {name} {
278 if {[catch {set v $_git_cmd_path($name)}]} {
282 --exec-path { return [list $::_git $name] }
285 set p [gitexec git-$name$::_search_exe]
286 if {[file exists $p]} {
288 } elseif {[is_Windows] && [file exists [gitexec git-$name]]} {
289 # Try to determine what sort of magic will make
290 # git-$name go and do its thing, because native
291 # Tcl on Windows doesn't know it.
293 set p [gitexec git-$name]
298 switch -glob -- [lindex $s 0] {
300 #!*perl { set i perl }
301 #!*python { set i python }
302 default { error "git-$name is not supported: $s" }
306 if {![info exists interp]} {
307 set interp [_which $i]
310 error "git-$name requires $i (not in PATH)"
312 set v [concat [list $interp] [lrange $s 1 end] [list $p]]
314 # Assume it is builtin to git somehow and we
315 # aren't actually able to see a file for it.
317 set v [list $::_git $name]
319 set _git_cmd_path($name) $v
324 proc _which {what args} {
325 global env _search_exe _search_path
327 if {$_search_path eq {}} {
328 if {[is_Cygwin] && [regexp {^(/|\.:)} $env(PATH)]} {
329 set _search_path [split [exec cygpath \
335 } elseif {[is_Windows]} {
336 set gitguidir [file dirname [info script]]
337 regsub -all ";" $gitguidir "\\;" gitguidir
338 set env(PATH) "$gitguidir;$env(PATH)"
339 set _search_path [split $env(PATH) {;}]
342 set _search_path [split $env(PATH) :]
347 if {[is_Windows] && [lsearch -exact $args -script] >= 0} {
350 set suffix $_search_exe
353 foreach p $_search_path {
354 set p [file join $p $what$suffix]
355 if {[file exists $p]} {
356 return [file normalize $p]
362 proc _lappend_nice {cmd_var} {
366 if {![info exists _nice]} {
367 set _nice [_which nice]
378 switch -- [lindex $args 0] {
389 set args [lrange $args 1 end]
392 set cmdp [_git_cmd [lindex $args 0]]
393 set args [lrange $args 1 end]
395 _trace_exec [concat $opt $cmdp $args]
396 set result [eval exec $opt $cmdp $args]
398 puts stderr "< $result"
403 proc _open_stdout_stderr {cmd} {
406 set fd [open [concat [list | ] $cmd] r]
408 if { [lindex $cmd end] eq {2>@1}
409 && $err eq {can not find channel named "1"}
411 # Older versions of Tcl 8.4 don't have this 2>@1 IO
412 # redirect operator. Fallback to |& cat for those.
413 # The command was not actually started, so its safe
414 # to try to start it a second time.
416 set fd [open [concat \
418 [lrange $cmd 0 end-1] \
425 fconfigure $fd -eofchar {}
429 proc git_read {args} {
433 switch -- [lindex $args 0] {
448 set args [lrange $args 1 end]
451 set cmdp [_git_cmd [lindex $args 0]]
452 set args [lrange $args 1 end]
454 return [_open_stdout_stderr [concat $opt $cmdp $args]]
457 proc git_write {args} {
461 switch -- [lindex $args 0] {
472 set args [lrange $args 1 end]
475 set cmdp [_git_cmd [lindex $args 0]]
476 set args [lrange $args 1 end]
478 _trace_exec [concat $opt $cmdp $args]
479 return [open [concat [list | ] $opt $cmdp $args] w]
482 proc githook_read {hook_name args} {
483 set pchook [gitdir hooks $hook_name]
486 # On Windows [file executable] might lie so we need to ask
487 # the shell if the hook is executable. Yes that's annoying.
491 if {![info exists interp]} {
492 set interp [_which sh]
495 error "hook execution requires sh (not in PATH)"
498 set scr {if test -x "$1";then exec "$@";fi}
499 set sh_c [list $interp -c $scr $interp $pchook]
500 return [_open_stdout_stderr [concat $sh_c $args]]
503 if {[file executable $pchook]} {
504 return [_open_stdout_stderr [concat [list $pchook] $args]]
510 proc kill_file_process {fd} {
511 set process [pid $fd]
515 # Use a Cygwin-specific flag to allow killing
516 # native Windows processes
517 exec kill -f $process
524 proc gitattr {path attr default} {
525 if {[catch {set r [git check-attr $attr -- $path]}]} {
528 set r [join [lrange [split $r :] 2 end] :]
531 if {$r eq {unspecified}} {
538 regsub -all ' $value "'\\''" value
542 proc load_current_branch {} {
543 global current_branch is_detached
545 set fd [open [gitdir HEAD] r]
546 if {[gets $fd ref] < 1} {
551 set pfx {ref: refs/heads/}
552 set len [string length $pfx]
553 if {[string equal -length $len $pfx $ref]} {
554 # We're on a branch. It might not exist. But
555 # HEAD looks good enough to be a branch.
557 set current_branch [string range $ref $len end]
560 # Assume this is a detached head.
562 set current_branch HEAD
567 auto_load tk_optionMenu
568 rename tk_optionMenu real__tkOptionMenu
569 proc tk_optionMenu {w varName args} {
570 set m [eval real__tkOptionMenu $w $varName $args]
571 $m configure -font font_ui
572 $w configure -font font_ui
576 proc rmsel_tag {text} {
578 -background [$text cget -background] \
579 -foreground [$text cget -foreground] \
581 $text tag conf in_sel -background lightgray
582 bind $text <Motion> break
587 bind . <Visibility> {
588 bind . <Visibility> {}
593 wm iconbitmap . -default $oguilib/git-gui.ico
594 set ::tk::AlwaysShowSelection 1
596 # Spoof an X11 display for SSH
597 if {![info exists env(DISPLAY)]} {
598 set env(DISPLAY) :9999
602 ######################################################################
607 font create font_diff -family Courier -size 10
611 eval font configure font_ui [font actual [.dummy cget -font]]
615 font create font_uiitalic
616 font create font_uibold
617 font create font_diffbold
618 font create font_diffitalic
620 foreach class {Button Checkbutton Entry Label
621 Labelframe Listbox Menu Message
622 Radiobutton Spinbox Text} {
623 option add *$class.font font_ui
627 if {[is_Windows] || [is_MacOSX]} {
628 option add *Menu.tearOff 0
639 proc bind_button3 {w cmd} {
640 bind $w <Any-Button-3> $cmd
642 # Mac OS X sends Button-2 on right click through three-button mouse,
643 # or through trackpad right-clicking (two-finger touch + click).
644 bind $w <Any-Button-2> $cmd
645 bind $w <Control-Button-1> $cmd
649 proc apply_config {} {
650 global repo_config font_descs
652 foreach option $font_descs {
653 set name [lindex $option 0]
654 set font [lindex $option 1]
657 foreach {cn cv} $repo_config(gui.$name) {
658 if {$cn eq {-weight}} {
661 font configure $font $cn $cv
664 font configure $font -weight normal
667 error_popup [strcat [mc "Invalid font specified in %s:" "gui.$name"] "\n\n$err"]
669 foreach {cn cv} [font configure $font] {
670 font configure ${font}bold $cn $cv
671 font configure ${font}italic $cn $cv
673 font configure ${font}bold -weight bold
674 font configure ${font}italic -slant italic
678 set default_config(branch.autosetupmerge) true
679 set default_config(merge.tool) {}
680 set default_config(merge.keepbackup) true
681 set default_config(merge.diffstat) true
682 set default_config(merge.summary) false
683 set default_config(merge.verbosity) 2
684 set default_config(user.name) {}
685 set default_config(user.email) {}
687 set default_config(gui.encoding) [encoding system]
688 set default_config(gui.matchtrackingbranch) false
689 set default_config(gui.pruneduringfetch) false
690 set default_config(gui.trustmtime) false
691 set default_config(gui.fastcopyblame) false
692 set default_config(gui.copyblamethreshold) 40
693 set default_config(gui.blamehistoryctx) 7
694 set default_config(gui.diffcontext) 5
695 set default_config(gui.commitmsgwidth) 75
696 set default_config(gui.newbranchtemplate) {}
697 set default_config(gui.spellingdictionary) {}
698 set default_config(gui.fontui) [font configure font_ui]
699 set default_config(gui.fontdiff) [font configure font_diff]
701 {fontui font_ui {mc "Main Font"}}
702 {fontdiff font_diff {mc "Diff/Console Font"}}
705 ######################################################################
709 set _git [_which git]
711 catch {wm withdraw .}
715 -title [mc "git-gui: fatal error"] \
716 -message [mc "Cannot find git in PATH."]
720 ######################################################################
724 if {[catch {set _git_version [git --version]} err]} {
725 catch {wm withdraw .}
729 -title [mc "git-gui: fatal error"] \
730 -message "Cannot determine Git version:
734 [appname] requires Git 1.5.0 or later."
737 if {![regsub {^git version } $_git_version {} _git_version]} {
738 catch {wm withdraw .}
742 -title [mc "git-gui: fatal error"] \
743 -message [strcat [mc "Cannot parse Git version string:"] "\n\n$_git_version"]
747 set _real_git_version $_git_version
748 regsub -- {[\-\.]dirty$} $_git_version {} _git_version
749 regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version
750 regsub {\.rc[0-9]+$} $_git_version {} _git_version
751 regsub {\.GIT$} $_git_version {} _git_version
752 regsub {\.[a-zA-Z]+\.[0-9]+$} $_git_version {} _git_version
754 if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} {
755 catch {wm withdraw .}
760 -title "[appname]: warning" \
761 -message [mc "Git version cannot be determined.
763 %s claims it is version '%s'.
765 %s requires at least Git 1.5.0 or later.
767 Assume '%s' is version 1.5.0?
768 " $_git $_real_git_version [appname] $_real_git_version]] eq {yes}} {
769 set _git_version 1.5.0
774 unset _real_git_version
776 proc git-version {args} {
779 switch [llength $args] {
785 set op [lindex $args 0]
786 set vr [lindex $args 1]
787 set cm [package vcompare $_git_version $vr]
788 return [expr $cm $op 0]
792 set type [lindex $args 0]
793 set name [lindex $args 1]
794 set parm [lindex $args 2]
795 set body [lindex $args 3]
797 if {($type ne {proc} && $type ne {method})} {
798 error "Invalid arguments to git-version"
800 if {[llength $body] < 2 || [lindex $body end-1] ne {default}} {
801 error "Last arm of $type $name must be default"
804 foreach {op vr cb} [lrange $body 0 end-2] {
805 if {[git-version $op $vr]} {
806 return [uplevel [list $type $name $parm $cb]]
810 return [uplevel [list $type $name $parm [lindex $body end]]]
814 error "git-version >= x"
820 if {[git-version < 1.5]} {
821 catch {wm withdraw .}
825 -title [mc "git-gui: fatal error"] \
826 -message "[appname] requires Git 1.5.0 or later.
828 You are using [git-version]:
834 ######################################################################
836 ## configure our library
838 set idx [file join $oguilib tclIndex]
839 if {[catch {set fd [open $idx r]} err]} {
840 catch {wm withdraw .}
844 -title [mc "git-gui: fatal error"] \
848 if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
850 while {[gets $fd n] >= 0} {
851 if {$n ne {} && ![string match #* $n]} {
863 if {[lsearch -exact $loaded $p] >= 0} continue
864 source [file join $oguilib $p]
869 set auto_path [concat [list $oguilib] $auto_path]
871 unset -nocomplain idx fd
873 ######################################################################
875 ## config file parsing
877 git-version proc _parse_config {arr_name args} {
884 [list git_read config] \
886 [list --null --list]]
887 fconfigure $fd_rc -translation binary
888 set buf [read $fd_rc]
891 foreach line [split $buf "\0"] {
892 if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} {
893 if {[is_many_config $name]} {
894 lappend arr($name) $value
896 set arr($name) $value
905 set fd_rc [eval [list git_read config --list] $args]
906 while {[gets $fd_rc line] >= 0} {
907 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
908 if {[is_many_config $name]} {
909 lappend arr($name) $value
911 set arr($name) $value
920 proc load_config {include_global} {
921 global repo_config global_config default_config
923 if {$include_global} {
924 _parse_config global_config --global
926 _parse_config repo_config
928 foreach name [array names default_config] {
929 if {[catch {set v $global_config($name)}]} {
930 set global_config($name) $default_config($name)
932 if {[catch {set v $repo_config($name)}]} {
933 set repo_config($name) $default_config($name)
938 ######################################################################
940 ## feature option selection
942 if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} {
947 if {$subcommand eq {gui.sh}} {
950 if {$subcommand eq {gui} && [llength $argv] > 0} {
951 set subcommand [lindex $argv 0]
952 set argv [lrange $argv 1 end]
955 enable_option multicommit
957 enable_option transport
960 switch -- $subcommand {
965 disable_option multicommit
966 disable_option branch
967 disable_option transport
970 enable_option singlecommit
971 enable_option retcode
973 disable_option multicommit
974 disable_option branch
975 disable_option transport
977 while {[llength $argv] > 0} {
978 set a [lindex $argv 0]
981 enable_option initialamend
984 enable_option nocommit
985 enable_option nocommitmsg
988 disable_option nocommitmsg
995 set argv [lrange $argv 1 end]
1000 ######################################################################
1006 set _gitdir $env(GIT_DIR)
1010 set _gitdir [git rev-parse --git-dir]
1011 set _prefix [git rev-parse --show-prefix]
1015 choose_repository::pick
1018 if {![file isdirectory $_gitdir] && [is_Cygwin]} {
1019 catch {set _gitdir [exec cygpath --windows $_gitdir]}
1021 if {![file isdirectory $_gitdir]} {
1022 catch {wm withdraw .}
1023 error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"]
1026 if {$_prefix ne {}} {
1027 regsub -all {[^/]+/} $_prefix ../ cdup
1028 if {[catch {cd $cdup} err]} {
1029 catch {wm withdraw .}
1030 error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"]
1034 } elseif {![is_enabled bare]} {
1035 if {[lindex [file split $_gitdir] end] ne {.git}} {
1036 catch {wm withdraw .}
1037 error_popup [strcat [mc "Cannot use funny .git directory:"] "\n\n$_gitdir"]
1040 if {[catch {cd [file dirname $_gitdir]} err]} {
1041 catch {wm withdraw .}
1042 error_popup [strcat [mc "No working directory"] " [file dirname $_gitdir]:\n\n$err"]
1046 set _reponame [file split [file normalize $_gitdir]]
1047 if {[lindex $_reponame end] eq {.git}} {
1048 set _reponame [lindex $_reponame end-1]
1050 set _reponame [lindex $_reponame end]
1053 ######################################################################
1057 set current_diff_path {}
1058 set current_diff_side {}
1059 set diff_actions [list]
1063 set MERGE_HEAD [list]
1066 set current_branch {}
1068 set current_diff_path {}
1070 set is_conflict_diff 0
1071 set selected_commit_type new
1073 set nullid "0000000000000000000000000000000000000000"
1074 set nullid2 "0000000000000000000000000000000000000001"
1076 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
1078 ######################################################################
1080 # Suggest our implementation of askpass, if none is set
1081 if {![info exists env(SSH_ASKPASS)]} {
1082 set env(SSH_ASKPASS) [gitexec git-gui--askpass]
1085 ######################################################################
1093 set disable_on_lock [list]
1094 set index_lock_type none
1096 proc lock_index {type} {
1097 global index_lock_type disable_on_lock
1099 if {$index_lock_type eq {none}} {
1100 set index_lock_type $type
1101 foreach w $disable_on_lock {
1102 uplevel #0 $w disabled
1105 } elseif {$index_lock_type eq "begin-$type"} {
1106 set index_lock_type $type
1112 proc unlock_index {} {
1113 global index_lock_type disable_on_lock
1115 set index_lock_type none
1116 foreach w $disable_on_lock {
1117 uplevel #0 $w normal
1121 ######################################################################
1125 proc repository_state {ctvar hdvar mhvar} {
1126 global current_branch
1127 upvar $ctvar ct $hdvar hd $mhvar mh
1132 if {[catch {set hd [git rev-parse --verify HEAD]}]} {
1138 set merge_head [gitdir MERGE_HEAD]
1139 if {[file exists $merge_head]} {
1141 set fd_mh [open $merge_head r]
1142 while {[gets $fd_mh line] >= 0} {
1153 global PARENT empty_tree
1155 set p [lindex $PARENT 0]
1159 if {$empty_tree eq {}} {
1160 set empty_tree [git mktree << {}]
1165 proc force_amend {} {
1166 global selected_commit_type
1167 global HEAD PARENT MERGE_HEAD commit_type
1169 repository_state newType newHEAD newMERGE_HEAD
1172 set MERGE_HEAD $newMERGE_HEAD
1173 set commit_type $newType
1175 set selected_commit_type amend
1176 do_select_commit_type
1179 proc rescan {after {honor_trustmtime 1}} {
1180 global HEAD PARENT MERGE_HEAD commit_type
1181 global ui_index ui_workdir ui_comm
1182 global rescan_active file_states
1185 if {$rescan_active > 0 || ![lock_index read]} return
1187 repository_state newType newHEAD newMERGE_HEAD
1188 if {[string match amend* $commit_type]
1189 && $newType eq {normal}
1190 && $newHEAD eq $HEAD} {
1194 set MERGE_HEAD $newMERGE_HEAD
1195 set commit_type $newType
1198 array unset file_states
1200 if {!$::GITGUI_BCK_exists &&
1201 (![$ui_comm edit modified]
1202 || [string trim [$ui_comm get 0.0 end]] eq {})} {
1203 if {[string match amend* $commit_type]} {
1204 } elseif {[load_message GITGUI_MSG]} {
1205 } elseif {[run_prepare_commit_msg_hook]} {
1206 } elseif {[load_message MERGE_MSG]} {
1207 } elseif {[load_message SQUASH_MSG]} {
1210 $ui_comm edit modified false
1213 if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
1214 rescan_stage2 {} $after
1217 ui_status [mc "Refreshing file status..."]
1218 set fd_rf [git_read update-index \
1224 fconfigure $fd_rf -blocking 0 -translation binary
1225 fileevent $fd_rf readable \
1226 [list rescan_stage2 $fd_rf $after]
1231 set is_git_info_exclude {}
1232 proc have_info_exclude {} {
1233 global is_git_info_exclude
1235 if {$is_git_info_exclude eq {}} {
1236 if {[catch {exec test -f [gitdir info exclude]}]} {
1237 set is_git_info_exclude 0
1239 set is_git_info_exclude 1
1242 return $is_git_info_exclude
1245 proc have_info_exclude {} {
1246 return [file readable [gitdir info exclude]]
1250 proc rescan_stage2 {fd after} {
1251 global rescan_active buf_rdi buf_rdf buf_rlo
1255 if {![eof $fd]} return
1259 set ls_others [list --exclude-per-directory=.gitignore]
1260 if {[have_info_exclude]} {
1261 lappend ls_others "--exclude-from=[gitdir info exclude]"
1263 set user_exclude [get_config core.excludesfile]
1264 if {$user_exclude ne {} && [file readable $user_exclude]} {
1265 lappend ls_others "--exclude-from=$user_exclude"
1273 ui_status [mc "Scanning for modified files ..."]
1274 set fd_di [git_read diff-index --cached -z [PARENT]]
1275 set fd_df [git_read diff-files -z]
1276 set fd_lo [eval git_read ls-files --others -z $ls_others]
1278 fconfigure $fd_di -blocking 0 -translation binary -encoding binary
1279 fconfigure $fd_df -blocking 0 -translation binary -encoding binary
1280 fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
1281 fileevent $fd_di readable [list read_diff_index $fd_di $after]
1282 fileevent $fd_df readable [list read_diff_files $fd_df $after]
1283 fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
1286 proc load_message {file} {
1289 set f [gitdir $file]
1290 if {[file isfile $f]} {
1291 if {[catch {set fd [open $f r]}]} {
1294 fconfigure $fd -eofchar {}
1295 set content [string trim [read $fd]]
1297 regsub -all -line {[ \r\t]+$} $content {} content
1298 $ui_comm delete 0.0 end
1299 $ui_comm insert end $content
1305 proc run_prepare_commit_msg_hook {} {
1308 # prepare-commit-msg requires PREPARE_COMMIT_MSG exist. From git-gui
1309 # it will be .git/MERGE_MSG (merge), .git/SQUASH_MSG (squash), or an
1310 # empty file but existant file.
1312 set fd_pcm [open [gitdir PREPARE_COMMIT_MSG] a]
1314 if {[file isfile [gitdir MERGE_MSG]]} {
1315 set pcm_source "merge"
1316 set fd_mm [open [gitdir MERGE_MSG] r]
1317 puts -nonewline $fd_pcm [read $fd_mm]
1319 } elseif {[file isfile [gitdir SQUASH_MSG]]} {
1320 set pcm_source "squash"
1321 set fd_sm [open [gitdir SQUASH_MSG] r]
1322 puts -nonewline $fd_pcm [read $fd_sm]
1330 set fd_ph [githook_read prepare-commit-msg \
1331 [gitdir PREPARE_COMMIT_MSG] $pcm_source]
1333 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1337 ui_status [mc "Calling prepare-commit-msg hook..."]
1340 fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
1341 fileevent $fd_ph readable \
1342 [list prepare_commit_msg_hook_wait $fd_ph]
1347 proc prepare_commit_msg_hook_wait {fd_ph} {
1350 append pch_error [read $fd_ph]
1351 fconfigure $fd_ph -blocking 1
1353 if {[catch {close $fd_ph}]} {
1354 ui_status [mc "Commit declined by prepare-commit-msg hook."]
1355 hook_failed_popup prepare-commit-msg $pch_error
1356 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1359 load_message PREPARE_COMMIT_MSG
1362 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1365 fconfigure $fd_ph -blocking 0
1366 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1369 proc read_diff_index {fd after} {
1372 append buf_rdi [read $fd]
1374 set n [string length $buf_rdi]
1376 set z1 [string first "\0" $buf_rdi $c]
1377 if {$z1 == -1} break
1379 set z2 [string first "\0" $buf_rdi $z1]
1380 if {$z2 == -1} break
1383 set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
1384 set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
1386 [encoding convertfrom $p] \
1388 [list [lindex $i 0] [lindex $i 2]] \
1394 set buf_rdi [string range $buf_rdi $c end]
1399 rescan_done $fd buf_rdi $after
1402 proc read_diff_files {fd after} {
1405 append buf_rdf [read $fd]
1407 set n [string length $buf_rdf]
1409 set z1 [string first "\0" $buf_rdf $c]
1410 if {$z1 == -1} break
1412 set z2 [string first "\0" $buf_rdf $z1]
1413 if {$z2 == -1} break
1416 set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
1417 set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
1419 [encoding convertfrom $p] \
1422 [list [lindex $i 0] [lindex $i 2]]
1427 set buf_rdf [string range $buf_rdf $c end]
1432 rescan_done $fd buf_rdf $after
1435 proc read_ls_others {fd after} {
1438 append buf_rlo [read $fd]
1439 set pck [split $buf_rlo "\0"]
1440 set buf_rlo [lindex $pck end]
1441 foreach p [lrange $pck 0 end-1] {
1442 set p [encoding convertfrom $p]
1443 if {[string index $p end] eq {/}} {
1444 set p [string range $p 0 end-1]
1448 rescan_done $fd buf_rlo $after
1451 proc rescan_done {fd buf after} {
1452 global rescan_active current_diff_path
1453 global file_states repo_config
1456 if {![eof $fd]} return
1459 if {[incr rescan_active -1] > 0} return
1464 if {$current_diff_path ne {}} reshow_diff
1465 if {$current_diff_path eq {}} select_first_diff
1470 proc prune_selection {} {
1471 global file_states selected_paths
1473 foreach path [array names selected_paths] {
1474 if {[catch {set still_here $file_states($path)}]} {
1475 unset selected_paths($path)
1480 ######################################################################
1484 proc mapicon {w state path} {
1487 if {[catch {set r $all_icons($state$w)}]} {
1488 puts "error: no icon for $w state={$state} $path"
1494 proc mapdesc {state path} {
1497 if {[catch {set r $all_descs($state)}]} {
1498 puts "error: no desc for state={$state} $path"
1504 proc ui_status {msg} {
1506 if {[info exists main_status]} {
1507 $main_status show $msg
1511 proc ui_ready {{test {}}} {
1513 if {[info exists main_status]} {
1514 $main_status show [mc "Ready."] $test
1518 proc escape_path {path} {
1519 regsub -all {\\} $path "\\\\" path
1520 regsub -all "\n" $path "\\n" path
1524 proc short_path {path} {
1525 return [escape_path [lindex [file split $path] end]]
1529 set null_sha1 [string repeat 0 40]
1531 proc merge_state {path new_state {head_info {}} {index_info {}}} {
1532 global file_states next_icon_id null_sha1
1534 set s0 [string index $new_state 0]
1535 set s1 [string index $new_state 1]
1537 if {[catch {set info $file_states($path)}]} {
1539 set icon n[incr next_icon_id]
1541 set state [lindex $info 0]
1542 set icon [lindex $info 1]
1543 if {$head_info eq {}} {set head_info [lindex $info 2]}
1544 if {$index_info eq {}} {set index_info [lindex $info 3]}
1547 if {$s0 eq {?}} {set s0 [string index $state 0]} \
1548 elseif {$s0 eq {_}} {set s0 _}
1550 if {$s1 eq {?}} {set s1 [string index $state 1]} \
1551 elseif {$s1 eq {_}} {set s1 _}
1553 if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
1554 set head_info [list 0 $null_sha1]
1555 } elseif {$s0 ne {_} && [string index $state 0] eq {_}
1556 && $head_info eq {}} {
1557 set head_info $index_info
1560 set file_states($path) [list $s0$s1 $icon \
1561 $head_info $index_info \
1566 proc display_file_helper {w path icon_name old_m new_m} {
1569 if {$new_m eq {_}} {
1570 set lno [lsearch -sorted -exact $file_lists($w) $path]
1572 set file_lists($w) [lreplace $file_lists($w) $lno $lno]
1574 $w conf -state normal
1575 $w delete $lno.0 [expr {$lno + 1}].0
1576 $w conf -state disabled
1578 } elseif {$old_m eq {_} && $new_m ne {_}} {
1579 lappend file_lists($w) $path
1580 set file_lists($w) [lsort -unique $file_lists($w)]
1581 set lno [lsearch -sorted -exact $file_lists($w) $path]
1583 $w conf -state normal
1584 $w image create $lno.0 \
1585 -align center -padx 5 -pady 1 \
1587 -image [mapicon $w $new_m $path]
1588 $w insert $lno.1 "[escape_path $path]\n"
1589 $w conf -state disabled
1590 } elseif {$old_m ne $new_m} {
1591 $w conf -state normal
1592 $w image conf $icon_name -image [mapicon $w $new_m $path]
1593 $w conf -state disabled
1597 proc display_file {path state} {
1598 global file_states selected_paths
1599 global ui_index ui_workdir
1601 set old_m [merge_state $path $state]
1602 set s $file_states($path)
1603 set new_m [lindex $s 0]
1604 set icon_name [lindex $s 1]
1606 set o [string index $old_m 0]
1607 set n [string index $new_m 0]
1614 display_file_helper $ui_index $path $icon_name $o $n
1616 if {[string index $old_m 0] eq {U}} {
1619 set o [string index $old_m 1]
1621 if {[string index $new_m 0] eq {U}} {
1624 set n [string index $new_m 1]
1626 display_file_helper $ui_workdir $path $icon_name $o $n
1628 if {$new_m eq {__}} {
1629 unset file_states($path)
1630 catch {unset selected_paths($path)}
1634 proc display_all_files_helper {w path icon_name m} {
1637 lappend file_lists($w) $path
1638 set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
1639 $w image create end \
1640 -align center -padx 5 -pady 1 \
1642 -image [mapicon $w $m $path]
1643 $w insert end "[escape_path $path]\n"
1646 proc display_all_files {} {
1647 global ui_index ui_workdir
1648 global file_states file_lists
1651 $ui_index conf -state normal
1652 $ui_workdir conf -state normal
1654 $ui_index delete 0.0 end
1655 $ui_workdir delete 0.0 end
1658 set file_lists($ui_index) [list]
1659 set file_lists($ui_workdir) [list]
1661 foreach path [lsort [array names file_states]] {
1662 set s $file_states($path)
1664 set icon_name [lindex $s 1]
1666 set s [string index $m 0]
1667 if {$s ne {U} && $s ne {_}} {
1668 display_all_files_helper $ui_index $path \
1672 if {[string index $m 0] eq {U}} {
1675 set s [string index $m 1]
1678 display_all_files_helper $ui_workdir $path \
1683 $ui_index conf -state disabled
1684 $ui_workdir conf -state disabled
1687 ######################################################################
1692 #define mask_width 14
1693 #define mask_height 15
1694 static unsigned char mask_bits[] = {
1695 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1696 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1697 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1700 image create bitmap file_plain -background white -foreground black -data {
1701 #define plain_width 14
1702 #define plain_height 15
1703 static unsigned char plain_bits[] = {
1704 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1705 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1706 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1707 } -maskdata $filemask
1709 image create bitmap file_mod -background white -foreground blue -data {
1710 #define mod_width 14
1711 #define mod_height 15
1712 static unsigned char mod_bits[] = {
1713 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1714 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1715 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1716 } -maskdata $filemask
1718 image create bitmap file_fulltick -background white -foreground "#007000" -data {
1719 #define file_fulltick_width 14
1720 #define file_fulltick_height 15
1721 static unsigned char file_fulltick_bits[] = {
1722 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1723 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1724 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1725 } -maskdata $filemask
1727 image create bitmap file_parttick -background white -foreground "#005050" -data {
1728 #define parttick_width 14
1729 #define parttick_height 15
1730 static unsigned char parttick_bits[] = {
1731 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1732 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1733 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1734 } -maskdata $filemask
1736 image create bitmap file_question -background white -foreground black -data {
1737 #define file_question_width 14
1738 #define file_question_height 15
1739 static unsigned char file_question_bits[] = {
1740 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1741 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1742 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1743 } -maskdata $filemask
1745 image create bitmap file_removed -background white -foreground red -data {
1746 #define file_removed_width 14
1747 #define file_removed_height 15
1748 static unsigned char file_removed_bits[] = {
1749 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1750 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1751 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1752 } -maskdata $filemask
1754 image create bitmap file_merge -background white -foreground blue -data {
1755 #define file_merge_width 14
1756 #define file_merge_height 15
1757 static unsigned char file_merge_bits[] = {
1758 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1759 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1760 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1761 } -maskdata $filemask
1763 image create bitmap file_statechange -background white -foreground green -data {
1764 #define file_merge_width 14
1765 #define file_merge_height 15
1766 static unsigned char file_statechange_bits[] = {
1767 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x62, 0x10,
1768 0x62, 0x10, 0xba, 0x11, 0xba, 0x11, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10,
1769 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1770 } -maskdata $filemask
1772 set ui_index .vpane.files.index.list
1773 set ui_workdir .vpane.files.workdir.list
1775 set all_icons(_$ui_index) file_plain
1776 set all_icons(A$ui_index) file_fulltick
1777 set all_icons(M$ui_index) file_fulltick
1778 set all_icons(D$ui_index) file_removed
1779 set all_icons(U$ui_index) file_merge
1780 set all_icons(T$ui_index) file_statechange
1782 set all_icons(_$ui_workdir) file_plain
1783 set all_icons(M$ui_workdir) file_mod
1784 set all_icons(D$ui_workdir) file_question
1785 set all_icons(U$ui_workdir) file_merge
1786 set all_icons(O$ui_workdir) file_plain
1787 set all_icons(T$ui_workdir) file_statechange
1789 set max_status_desc 0
1791 {__ {mc "Unmodified"}}
1793 {_M {mc "Modified, not staged"}}
1794 {M_ {mc "Staged for commit"}}
1795 {MM {mc "Portions staged for commit"}}
1796 {MD {mc "Staged for commit, missing"}}
1798 {_T {mc "File type changed, not staged"}}
1799 {T_ {mc "File type changed, staged"}}
1801 {_O {mc "Untracked, not staged"}}
1802 {A_ {mc "Staged for commit"}}
1803 {AM {mc "Portions staged for commit"}}
1804 {AD {mc "Staged for commit, missing"}}
1807 {D_ {mc "Staged for removal"}}
1808 {DO {mc "Staged for removal, still present"}}
1810 {_U {mc "Requires merge resolution"}}
1811 {U_ {mc "Requires merge resolution"}}
1812 {UU {mc "Requires merge resolution"}}
1813 {UM {mc "Requires merge resolution"}}
1814 {UD {mc "Requires merge resolution"}}
1815 {UT {mc "Requires merge resolution"}}
1817 set text [eval [lindex $i 1]]
1818 if {$max_status_desc < [string length $text]} {
1819 set max_status_desc [string length $text]
1821 set all_descs([lindex $i 0]) $text
1825 ######################################################################
1829 proc scrollbar2many {list mode args} {
1830 foreach w $list {eval $w $mode $args}
1833 proc many2scrollbar {list mode sb top bottom} {
1834 $sb set $top $bottom
1835 foreach w $list {$w $mode moveto $top}
1838 proc incr_font_size {font {amt 1}} {
1839 set sz [font configure $font -size]
1841 font configure $font -size $sz
1842 font configure ${font}bold -size $sz
1843 font configure ${font}italic -size $sz
1846 ######################################################################
1850 set starting_gitk_msg [mc "Starting gitk... please wait..."]
1852 proc do_gitk {revs} {
1853 # -- Always start gitk through whatever we were loaded with. This
1854 # lets us bypass using shell process on Windows systems.
1856 set exe [_which gitk -script]
1857 set cmd [list [info nameofexecutable] $exe]
1859 error_popup [mc "Couldn't find gitk in PATH"]
1863 if {[info exists env(GIT_DIR)]} {
1864 set old_GIT_DIR $env(GIT_DIR)
1870 cd [file dirname [gitdir]]
1871 set env(GIT_DIR) [file tail [gitdir]]
1873 eval exec $cmd $revs &
1875 if {$old_GIT_DIR eq {}} {
1878 set env(GIT_DIR) $old_GIT_DIR
1882 ui_status $::starting_gitk_msg
1884 ui_ready $starting_gitk_msg
1889 proc do_explore {} {
1891 if {[is_Cygwin] || [is_Windows]} {
1892 set explorer "explorer.exe"
1893 } elseif {[is_MacOSX]} {
1896 # freedesktop.org-conforming system is our best shot
1897 set explorer "xdg-open"
1899 eval exec $explorer [file dirname [gitdir]] &
1905 proc terminate_me {win} {
1907 if {$win ne {.}} return
1911 proc do_quit {{rc {1}}} {
1912 global ui_comm is_quitting repo_config commit_type
1913 global GITGUI_BCK_exists GITGUI_BCK_i
1914 global ui_comm_spell
1917 if {$is_quitting} return
1920 if {[winfo exists $ui_comm]} {
1921 # -- Stash our current commit buffer.
1923 set save [gitdir GITGUI_MSG]
1924 if {$GITGUI_BCK_exists && ![$ui_comm edit modified]} {
1925 file rename -force [gitdir GITGUI_BCK] $save
1926 set GITGUI_BCK_exists 0
1928 set msg [string trim [$ui_comm get 0.0 end]]
1929 regsub -all -line {[ \r\t]+$} $msg {} msg
1930 if {(![string match amend* $commit_type]
1931 || [$ui_comm edit modified])
1934 set fd [open $save w]
1935 puts -nonewline $fd $msg
1939 catch {file delete $save}
1943 # -- Cancel our spellchecker if its running.
1945 if {[info exists ui_comm_spell]} {
1949 # -- Remove our editor backup, its not needed.
1951 after cancel $GITGUI_BCK_i
1952 if {$GITGUI_BCK_exists} {
1953 catch {file delete [gitdir GITGUI_BCK]}
1956 # -- Stash our current window geometry into this repository.
1958 set cfg_geometry [list]
1959 lappend cfg_geometry [wm geometry .]
1960 lappend cfg_geometry [lindex [.vpane sash coord 0] 0]
1961 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 1]
1962 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1965 if {$cfg_geometry ne $rc_geometry} {
1966 catch {git config gui.geometry $cfg_geometry}
1978 proc ui_do_rescan {} {
1979 rescan {force_first_diff; ui_ready}
1987 global next_diff_p next_diff_w next_diff_i
1988 show_diff $next_diff_p $next_diff_w {}
1991 proc find_anchor_pos {lst name} {
1992 set lid [lsearch -sorted -exact $lst $name]
1996 foreach lname $lst {
1997 if {$lname >= $name} break
2005 proc find_file_from {flist idx delta path mmask} {
2008 set len [llength $flist]
2009 while {$idx >= 0 && $idx < $len} {
2010 set name [lindex $flist $idx]
2012 if {$name ne $path && [info exists file_states($name)]} {
2013 set state [lindex $file_states($name) 0]
2015 if {$mmask eq {} || [regexp $mmask $state]} {
2026 proc find_next_diff {w path {lno {}} {mmask {}}} {
2027 global next_diff_p next_diff_w next_diff_i
2028 global file_lists ui_index ui_workdir
2030 set flist $file_lists($w)
2032 set lno [find_anchor_pos $flist $path]
2037 if {$mmask ne {} && ![regexp {(^\^)|(\$$)} $mmask]} {
2038 if {$w eq $ui_index} {
2041 set mmask "$mmask\$"
2045 set idx [find_file_from $flist $lno 1 $path $mmask]
2048 set idx [find_file_from $flist $lno -1 $path $mmask]
2053 set next_diff_p [lindex $flist $idx]
2054 set next_diff_i [expr {$idx+1}]
2061 proc next_diff_after_action {w path {lno {}} {mmask {}}} {
2062 global current_diff_path
2064 if {$path ne $current_diff_path} {
2066 } elseif {[find_next_diff $w $path $lno $mmask]} {
2069 return {reshow_diff;}
2073 proc select_first_diff {} {
2076 if {[find_next_diff $ui_workdir {} 1 {^_?U}] ||
2077 [find_next_diff $ui_workdir {} 1 {[^O]$}]} {
2082 proc force_first_diff {} {
2083 global current_diff_path
2085 if {[info exists file_states($current_diff_path)]} {
2086 set state [lindex $file_states($current_diff_path) 0]
2088 if {[string index $state 1] ne {O}} return
2094 proc toggle_or_diff {w x y} {
2095 global file_states file_lists current_diff_path ui_index ui_workdir
2096 global last_clicked selected_paths
2098 set pos [split [$w index @$x,$y] .]
2099 set lno [lindex $pos 0]
2100 set col [lindex $pos 1]
2101 set path [lindex $file_lists($w) [expr {$lno - 1}]]
2107 set last_clicked [list $w $lno]
2108 array unset selected_paths
2109 $ui_index tag remove in_sel 0.0 end
2110 $ui_workdir tag remove in_sel 0.0 end
2112 # Determine the state of the file
2113 if {[info exists file_states($path)]} {
2114 set state [lindex $file_states($path) 0]
2119 # Restage the file, or simply show the diff
2120 if {$col == 0 && $y > 1} {
2121 # Conflicts need special handling
2122 if {[string first {U} $state] >= 0} {
2123 # $w must always be $ui_workdir, but...
2124 if {$w ne $ui_workdir} { set lno {} }
2125 merge_stage_workdir $path $lno
2129 if {[string index $state 1] eq {O}} {
2135 set after [next_diff_after_action $w $path $lno $mmask]
2137 if {$w eq $ui_index} {
2139 "Unstaging [short_path $path] from commit" \
2141 [concat $after [list ui_ready]]
2142 } elseif {$w eq $ui_workdir} {
2144 "Adding [short_path $path]" \
2146 [concat $after [list ui_ready]]
2149 show_diff $path $w $lno
2153 proc add_one_to_selection {w x y} {
2154 global file_lists last_clicked selected_paths
2156 set lno [lindex [split [$w index @$x,$y] .] 0]
2157 set path [lindex $file_lists($w) [expr {$lno - 1}]]
2163 if {$last_clicked ne {}
2164 && [lindex $last_clicked 0] ne $w} {
2165 array unset selected_paths
2166 [lindex $last_clicked 0] tag remove in_sel 0.0 end
2169 set last_clicked [list $w $lno]
2170 if {[catch {set in_sel $selected_paths($path)}]} {
2174 unset selected_paths($path)
2175 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
2177 set selected_paths($path) 1
2178 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
2182 proc add_range_to_selection {w x y} {
2183 global file_lists last_clicked selected_paths
2185 if {[lindex $last_clicked 0] ne $w} {
2186 toggle_or_diff $w $x $y
2190 set lno [lindex [split [$w index @$x,$y] .] 0]
2191 set lc [lindex $last_clicked 1]
2200 foreach path [lrange $file_lists($w) \
2201 [expr {$begin - 1}] \
2202 [expr {$end - 1}]] {
2203 set selected_paths($path) 1
2205 $w tag add in_sel $begin.0 [expr {$end + 1}].0
2208 proc show_more_context {} {
2210 if {$repo_config(gui.diffcontext) < 99} {
2211 incr repo_config(gui.diffcontext)
2216 proc show_less_context {} {
2218 if {$repo_config(gui.diffcontext) > 1} {
2219 incr repo_config(gui.diffcontext) -1
2224 ######################################################################
2234 menu .mbar -tearoff 0
2235 .mbar add cascade -label [mc Repository] -menu .mbar.repository
2236 .mbar add cascade -label [mc Edit] -menu .mbar.edit
2237 if {[is_enabled branch]} {
2238 .mbar add cascade -label [mc Branch] -menu .mbar.branch
2240 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2241 .mbar add cascade -label [mc Commit@@noun] -menu .mbar.commit
2243 if {[is_enabled transport]} {
2244 .mbar add cascade -label [mc Merge] -menu .mbar.merge
2245 .mbar add cascade -label [mc Remote] -menu .mbar.remote
2247 . configure -menu .mbar
2249 # -- Repository Menu
2251 menu .mbar.repository
2253 .mbar.repository add command \
2254 -label [mc "Explore Working Copy"] \
2255 -command {do_explore}
2256 .mbar.repository add separator
2258 .mbar.repository add command \
2259 -label [mc "Browse Current Branch's Files"] \
2260 -command {browser::new $current_branch}
2261 set ui_browse_current [.mbar.repository index last]
2262 .mbar.repository add command \
2263 -label [mc "Browse Branch Files..."] \
2264 -command browser_open::dialog
2265 .mbar.repository add separator
2267 .mbar.repository add command \
2268 -label [mc "Visualize Current Branch's History"] \
2269 -command {do_gitk $current_branch}
2270 set ui_visualize_current [.mbar.repository index last]
2271 .mbar.repository add command \
2272 -label [mc "Visualize All Branch History"] \
2273 -command {do_gitk --all}
2274 .mbar.repository add separator
2276 proc current_branch_write {args} {
2277 global current_branch
2278 .mbar.repository entryconf $::ui_browse_current \
2279 -label [mc "Browse %s's Files" $current_branch]
2280 .mbar.repository entryconf $::ui_visualize_current \
2281 -label [mc "Visualize %s's History" $current_branch]
2283 trace add variable current_branch write current_branch_write
2285 if {[is_enabled multicommit]} {
2286 .mbar.repository add command -label [mc "Database Statistics"] \
2289 .mbar.repository add command -label [mc "Compress Database"] \
2292 .mbar.repository add command -label [mc "Verify Database"] \
2293 -command do_fsck_objects
2295 .mbar.repository add separator
2298 .mbar.repository add command \
2299 -label [mc "Create Desktop Icon"] \
2300 -command do_cygwin_shortcut
2301 } elseif {[is_Windows]} {
2302 .mbar.repository add command \
2303 -label [mc "Create Desktop Icon"] \
2304 -command do_windows_shortcut
2305 } elseif {[is_MacOSX]} {
2306 .mbar.repository add command \
2307 -label [mc "Create Desktop Icon"] \
2308 -command do_macosx_app
2313 proc ::tk::mac::Quit {args} { do_quit }
2315 .mbar.repository add command -label [mc Quit] \
2323 .mbar.edit add command -label [mc Undo] \
2324 -command {catch {[focus] edit undo}} \
2326 .mbar.edit add command -label [mc Redo] \
2327 -command {catch {[focus] edit redo}} \
2329 .mbar.edit add separator
2330 .mbar.edit add command -label [mc Cut] \
2331 -command {catch {tk_textCut [focus]}} \
2333 .mbar.edit add command -label [mc Copy] \
2334 -command {catch {tk_textCopy [focus]}} \
2336 .mbar.edit add command -label [mc Paste] \
2337 -command {catch {tk_textPaste [focus]; [focus] see insert}} \
2339 .mbar.edit add command -label [mc Delete] \
2340 -command {catch {[focus] delete sel.first sel.last}} \
2342 .mbar.edit add separator
2343 .mbar.edit add command -label [mc "Select All"] \
2344 -command {catch {[focus] tag add sel 0.0 end}} \
2349 if {[is_enabled branch]} {
2352 .mbar.branch add command -label [mc "Create..."] \
2353 -command branch_create::dialog \
2355 lappend disable_on_lock [list .mbar.branch entryconf \
2356 [.mbar.branch index last] -state]
2358 .mbar.branch add command -label [mc "Checkout..."] \
2359 -command branch_checkout::dialog \
2361 lappend disable_on_lock [list .mbar.branch entryconf \
2362 [.mbar.branch index last] -state]
2364 .mbar.branch add command -label [mc "Rename..."] \
2365 -command branch_rename::dialog
2366 lappend disable_on_lock [list .mbar.branch entryconf \
2367 [.mbar.branch index last] -state]
2369 .mbar.branch add command -label [mc "Delete..."] \
2370 -command branch_delete::dialog
2371 lappend disable_on_lock [list .mbar.branch entryconf \
2372 [.mbar.branch index last] -state]
2374 .mbar.branch add command -label [mc "Reset..."] \
2375 -command merge::reset_hard
2376 lappend disable_on_lock [list .mbar.branch entryconf \
2377 [.mbar.branch index last] -state]
2382 proc commit_btn_caption {} {
2383 if {[is_enabled nocommit]} {
2386 return [mc Commit@@verb]
2390 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2393 if {![is_enabled nocommit]} {
2394 .mbar.commit add radiobutton \
2395 -label [mc "New Commit"] \
2396 -command do_select_commit_type \
2397 -variable selected_commit_type \
2399 lappend disable_on_lock \
2400 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2402 .mbar.commit add radiobutton \
2403 -label [mc "Amend Last Commit"] \
2404 -command do_select_commit_type \
2405 -variable selected_commit_type \
2407 lappend disable_on_lock \
2408 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2410 .mbar.commit add separator
2413 .mbar.commit add command -label [mc Rescan] \
2414 -command ui_do_rescan \
2416 lappend disable_on_lock \
2417 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2419 .mbar.commit add command -label [mc "Stage To Commit"] \
2420 -command do_add_selection \
2422 lappend disable_on_lock \
2423 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2425 .mbar.commit add command -label [mc "Stage Changed Files To Commit"] \
2426 -command do_add_all \
2428 lappend disable_on_lock \
2429 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2431 .mbar.commit add command -label [mc "Unstage From Commit"] \
2432 -command do_unstage_selection
2433 lappend disable_on_lock \
2434 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2436 .mbar.commit add command -label [mc "Revert Changes"] \
2437 -command do_revert_selection
2438 lappend disable_on_lock \
2439 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2441 .mbar.commit add separator
2443 .mbar.commit add command -label [mc "Show Less Context"] \
2444 -command show_less_context \
2445 -accelerator $M1T-\-
2447 .mbar.commit add command -label [mc "Show More Context"] \
2448 -command show_more_context \
2451 .mbar.commit add separator
2453 if {![is_enabled nocommitmsg]} {
2454 .mbar.commit add command -label [mc "Sign Off"] \
2455 -command do_signoff \
2459 .mbar.commit add command -label [commit_btn_caption] \
2460 -command do_commit \
2461 -accelerator $M1T-Return
2462 lappend disable_on_lock \
2463 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2468 if {[is_enabled branch]} {
2470 .mbar.merge add command -label [mc "Local Merge..."] \
2471 -command merge::dialog \
2473 lappend disable_on_lock \
2474 [list .mbar.merge entryconf [.mbar.merge index last] -state]
2475 .mbar.merge add command -label [mc "Abort Merge..."] \
2476 -command merge::reset_hard
2477 lappend disable_on_lock \
2478 [list .mbar.merge entryconf [.mbar.merge index last] -state]
2483 if {[is_enabled transport]} {
2486 .mbar.remote add command \
2487 -label [mc "Add..."] \
2488 -command remote_add::dialog \
2490 .mbar.remote add command \
2491 -label [mc "Push..."] \
2492 -command do_push_anywhere \
2494 .mbar.remote add command \
2495 -label [mc "Delete Branch..."] \
2496 -command remote_branch_delete::dialog
2500 # -- Apple Menu (Mac OS X only)
2502 .mbar add cascade -label Apple -menu .mbar.apple
2505 .mbar.apple add command -label [mc "About %s" [appname]] \
2507 .mbar.apple add separator
2508 .mbar.apple add command \
2509 -label [mc "Preferences..."] \
2510 -command do_options \
2512 bind . <$M1B-,> do_options
2516 .mbar.edit add separator
2517 .mbar.edit add command -label [mc "Options..."] \
2523 .mbar add cascade -label [mc Help] -menu .mbar.help
2527 .mbar.help add command -label [mc "About %s" [appname]] \
2532 set doc_path [file dirname [gitexec]]
2533 set doc_path [file join $doc_path Documentation index.html]
2536 set doc_path [exec cygpath --mixed $doc_path]
2539 if {[file isfile $doc_path]} {
2540 set doc_url "file:$doc_path"
2542 set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
2545 proc start_browser {url} {
2546 git "web--browse" $url
2549 .mbar.help add command -label [mc "Online Documentation"] \
2550 -command [list start_browser $doc_url]
2552 .mbar.help add command -label [mc "Show SSH Key"] \
2555 unset doc_path doc_url
2557 # -- Standard bindings
2559 wm protocol . WM_DELETE_WINDOW do_quit
2560 bind all <$M1B-Key-q> do_quit
2561 bind all <$M1B-Key-Q> do_quit
2562 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
2563 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
2565 set subcommand_args {}
2567 puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
2571 # -- Not a normal commit type invocation? Do that instead!
2573 switch -- $subcommand {
2576 if {$subcommand eq "blame"} {
2577 set subcommand_args {[--line=<num>] rev? path}
2579 set subcommand_args {rev? path}
2581 if {$argv eq {}} usage
2587 if {$is_path || [file exists $_prefix$a]} {
2588 if {$path ne {}} usage
2591 } elseif {$a eq {--}} {
2593 if {$head ne {}} usage
2598 } elseif {[regexp {^--line=(\d+)$} $a a lnum]} {
2599 if {$jump_spec ne {} || $head ne {}} usage
2600 set jump_spec [list $lnum]
2601 } elseif {$head eq {}} {
2602 if {$head ne {}} usage
2611 if {$head ne {} && $path eq {}} {
2612 set path $_prefix$head
2619 if {[regexp {^[0-9a-f]{1,39}$} $head]} {
2621 set head [git rev-parse --verify $head]
2627 set current_branch $head
2630 switch -- $subcommand {
2632 if {$jump_spec ne {}} usage
2634 if {$path ne {} && [file isdirectory $path]} {
2635 set head $current_branch
2641 browser::new $head $path
2644 if {$head eq {} && ![file exists $path]} {
2645 puts stderr [mc "fatal: cannot stat path %s: No such file or directory" $path]
2648 blame::new $head $path $jump_spec
2655 if {[llength $argv] != 0} {
2656 puts -nonewline stderr "usage: $argv0"
2657 if {$subcommand ne {gui}
2658 && [file tail $argv0] ne "git-$subcommand"} {
2659 puts -nonewline stderr " $subcommand"
2664 # fall through to setup UI for commits
2667 puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
2678 -text [mc "Current Branch:"] \
2682 -textvariable current_branch \
2685 pack .branch.l1 -side left
2686 pack .branch.cb -side left -fill x
2687 pack .branch -side top -fill x
2689 # -- Main Window Layout
2691 panedwindow .vpane -orient horizontal
2692 panedwindow .vpane.files -orient vertical
2693 .vpane add .vpane.files -sticky nsew -height 100 -width 200
2694 pack .vpane -anchor n -side top -fill both -expand 1
2696 # -- Index File List
2698 frame .vpane.files.index -height 100 -width 200
2699 label .vpane.files.index.title -text [mc "Staged Changes (Will Commit)"] \
2700 -background lightgreen -foreground black
2701 text $ui_index -background white -foreground black \
2703 -width 20 -height 10 \
2705 -cursor $cursor_ptr \
2706 -xscrollcommand {.vpane.files.index.sx set} \
2707 -yscrollcommand {.vpane.files.index.sy set} \
2709 scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
2710 scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
2711 pack .vpane.files.index.title -side top -fill x
2712 pack .vpane.files.index.sx -side bottom -fill x
2713 pack .vpane.files.index.sy -side right -fill y
2714 pack $ui_index -side left -fill both -expand 1
2716 # -- Working Directory File List
2718 frame .vpane.files.workdir -height 100 -width 200
2719 label .vpane.files.workdir.title -text [mc "Unstaged Changes"] \
2720 -background lightsalmon -foreground black
2721 text $ui_workdir -background white -foreground black \
2723 -width 20 -height 10 \
2725 -cursor $cursor_ptr \
2726 -xscrollcommand {.vpane.files.workdir.sx set} \
2727 -yscrollcommand {.vpane.files.workdir.sy set} \
2729 scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
2730 scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
2731 pack .vpane.files.workdir.title -side top -fill x
2732 pack .vpane.files.workdir.sx -side bottom -fill x
2733 pack .vpane.files.workdir.sy -side right -fill y
2734 pack $ui_workdir -side left -fill both -expand 1
2736 .vpane.files add .vpane.files.workdir -sticky nsew
2737 .vpane.files add .vpane.files.index -sticky nsew
2739 foreach i [list $ui_index $ui_workdir] {
2741 $i tag conf in_diff -background [$i tag cget in_sel -background]
2745 # -- Diff and Commit Area
2747 frame .vpane.lower -height 300 -width 400
2748 frame .vpane.lower.commarea
2749 frame .vpane.lower.diff -relief sunken -borderwidth 1
2750 pack .vpane.lower.diff -fill both -expand 1
2751 pack .vpane.lower.commarea -side bottom -fill x
2752 .vpane add .vpane.lower -sticky nsew
2754 # -- Commit Area Buttons
2756 frame .vpane.lower.commarea.buttons
2757 label .vpane.lower.commarea.buttons.l -text {} \
2760 pack .vpane.lower.commarea.buttons.l -side top -fill x
2761 pack .vpane.lower.commarea.buttons -side left -fill y
2763 button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \
2764 -command ui_do_rescan
2765 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
2766 lappend disable_on_lock \
2767 {.vpane.lower.commarea.buttons.rescan conf -state}
2769 button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \
2771 pack .vpane.lower.commarea.buttons.incall -side top -fill x
2772 lappend disable_on_lock \
2773 {.vpane.lower.commarea.buttons.incall conf -state}
2775 if {![is_enabled nocommitmsg]} {
2776 button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \
2778 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
2781 button .vpane.lower.commarea.buttons.commit -text [commit_btn_caption] \
2783 pack .vpane.lower.commarea.buttons.commit -side top -fill x
2784 lappend disable_on_lock \
2785 {.vpane.lower.commarea.buttons.commit conf -state}
2787 if {![is_enabled nocommit]} {
2788 button .vpane.lower.commarea.buttons.push -text [mc Push] \
2789 -command do_push_anywhere
2790 pack .vpane.lower.commarea.buttons.push -side top -fill x
2793 # -- Commit Message Buffer
2795 frame .vpane.lower.commarea.buffer
2796 frame .vpane.lower.commarea.buffer.header
2797 set ui_comm .vpane.lower.commarea.buffer.t
2798 set ui_coml .vpane.lower.commarea.buffer.header.l
2800 if {![is_enabled nocommit]} {
2801 radiobutton .vpane.lower.commarea.buffer.header.new \
2802 -text [mc "New Commit"] \
2803 -command do_select_commit_type \
2804 -variable selected_commit_type \
2806 lappend disable_on_lock \
2807 [list .vpane.lower.commarea.buffer.header.new conf -state]
2808 radiobutton .vpane.lower.commarea.buffer.header.amend \
2809 -text [mc "Amend Last Commit"] \
2810 -command do_select_commit_type \
2811 -variable selected_commit_type \
2813 lappend disable_on_lock \
2814 [list .vpane.lower.commarea.buffer.header.amend conf -state]
2820 proc trace_commit_type {varname args} {
2821 global ui_coml commit_type
2822 switch -glob -- $commit_type {
2823 initial {set txt [mc "Initial Commit Message:"]}
2824 amend {set txt [mc "Amended Commit Message:"]}
2825 amend-initial {set txt [mc "Amended Initial Commit Message:"]}
2826 amend-merge {set txt [mc "Amended Merge Commit Message:"]}
2827 merge {set txt [mc "Merge Commit Message:"]}
2828 * {set txt [mc "Commit Message:"]}
2830 $ui_coml conf -text $txt
2832 trace add variable commit_type write trace_commit_type
2833 pack $ui_coml -side left -fill x
2835 if {![is_enabled nocommit]} {
2836 pack .vpane.lower.commarea.buffer.header.amend -side right
2837 pack .vpane.lower.commarea.buffer.header.new -side right
2840 text $ui_comm -background white -foreground black \
2844 -autoseparators true \
2846 -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \
2848 -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
2849 scrollbar .vpane.lower.commarea.buffer.sby \
2850 -command [list $ui_comm yview]
2851 pack .vpane.lower.commarea.buffer.header -side top -fill x
2852 pack .vpane.lower.commarea.buffer.sby -side right -fill y
2853 pack $ui_comm -side left -fill y
2854 pack .vpane.lower.commarea.buffer -side left -fill y
2856 # -- Commit Message Buffer Context Menu
2858 set ctxm .vpane.lower.commarea.buffer.ctxm
2859 menu $ctxm -tearoff 0
2862 -command {tk_textCut $ui_comm}
2865 -command {tk_textCopy $ui_comm}
2868 -command {tk_textPaste $ui_comm}
2870 -label [mc Delete] \
2871 -command {$ui_comm delete sel.first sel.last}
2874 -label [mc "Select All"] \
2875 -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
2877 -label [mc "Copy All"] \
2879 $ui_comm tag add sel 0.0 end
2880 tk_textCopy $ui_comm
2881 $ui_comm tag remove sel 0.0 end
2885 -label [mc "Sign Off"] \
2887 set ui_comm_ctxm $ctxm
2891 proc trace_current_diff_path {varname args} {
2892 global current_diff_path diff_actions file_states
2893 if {$current_diff_path eq {}} {
2899 set p $current_diff_path
2900 set s [mapdesc [lindex $file_states($p) 0] $p]
2902 set p [escape_path $p]
2906 .vpane.lower.diff.header.status configure -text $s
2907 .vpane.lower.diff.header.file configure -text $f
2908 .vpane.lower.diff.header.path configure -text $p
2909 foreach w $diff_actions {
2913 trace add variable current_diff_path write trace_current_diff_path
2915 frame .vpane.lower.diff.header -background gold
2916 label .vpane.lower.diff.header.status \
2919 -width $max_status_desc \
2922 label .vpane.lower.diff.header.file \
2927 label .vpane.lower.diff.header.path \
2932 pack .vpane.lower.diff.header.status -side left
2933 pack .vpane.lower.diff.header.file -side left
2934 pack .vpane.lower.diff.header.path -fill x
2935 set ctxm .vpane.lower.diff.header.ctxm
2936 menu $ctxm -tearoff 0
2944 -- $current_diff_path
2946 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2947 bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
2951 frame .vpane.lower.diff.body
2952 set ui_diff .vpane.lower.diff.body.t
2953 text $ui_diff -background white -foreground black \
2955 -width 80 -height 15 -wrap none \
2957 -xscrollcommand {.vpane.lower.diff.body.sbx set} \
2958 -yscrollcommand {.vpane.lower.diff.body.sby set} \
2960 scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
2961 -command [list $ui_diff xview]
2962 scrollbar .vpane.lower.diff.body.sby -orient vertical \
2963 -command [list $ui_diff yview]
2964 pack .vpane.lower.diff.body.sbx -side bottom -fill x
2965 pack .vpane.lower.diff.body.sby -side right -fill y
2966 pack $ui_diff -side left -fill both -expand 1
2967 pack .vpane.lower.diff.header -side top -fill x
2968 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
2970 $ui_diff tag conf d_cr -elide true
2971 $ui_diff tag conf d_@ -foreground blue -font font_diffbold
2972 $ui_diff tag conf d_+ -foreground {#00a000}
2973 $ui_diff tag conf d_- -foreground red
2975 $ui_diff tag conf d_++ -foreground {#00a000}
2976 $ui_diff tag conf d_-- -foreground red
2977 $ui_diff tag conf d_+s \
2978 -foreground {#00a000} \
2979 -background {#e2effa}
2980 $ui_diff tag conf d_-s \
2982 -background {#e2effa}
2983 $ui_diff tag conf d_s+ \
2984 -foreground {#00a000} \
2986 $ui_diff tag conf d_s- \
2990 $ui_diff tag conf d<<<<<<< \
2991 -foreground orange \
2993 $ui_diff tag conf d======= \
2994 -foreground orange \
2996 $ui_diff tag conf d>>>>>>> \
2997 -foreground orange \
3000 $ui_diff tag raise sel
3002 # -- Diff Body Context Menu
3005 proc create_common_diff_popup {ctxm} {
3007 -label [mc "Show Less Context"] \
3008 -command show_less_context
3009 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3011 -label [mc "Show More Context"] \
3012 -command show_more_context
3013 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3016 -label [mc Refresh] \
3017 -command reshow_diff
3018 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3021 -command {tk_textCopy $ui_diff}
3022 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3024 -label [mc "Select All"] \
3025 -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
3026 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3028 -label [mc "Copy All"] \
3030 $ui_diff tag add sel 0.0 end
3031 tk_textCopy $ui_diff
3032 $ui_diff tag remove sel 0.0 end
3034 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3037 -label [mc "Decrease Font Size"] \
3038 -command {incr_font_size font_diff -1}
3039 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3041 -label [mc "Increase Font Size"] \
3042 -command {incr_font_size font_diff 1}
3043 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3047 build_encoding_menu $emenu [list force_diff_encoding]
3049 -label [mc "Encoding"] \
3051 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3053 $ctxm add command -label [mc "Options..."] \
3057 set ctxm .vpane.lower.diff.body.ctxm
3058 menu $ctxm -tearoff 0
3060 -label [mc "Apply/Reverse Hunk"] \
3061 -command {apply_hunk $cursorX $cursorY}
3062 set ui_diff_applyhunk [$ctxm index last]
3063 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
3065 -label [mc "Apply/Reverse Line"] \
3066 -command {apply_line $cursorX $cursorY; do_rescan}
3067 set ui_diff_applyline [$ctxm index last]
3068 lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state]
3070 create_common_diff_popup $ctxm
3072 set ctxmmg .vpane.lower.diff.body.ctxmmg
3073 menu $ctxmmg -tearoff 0
3074 $ctxmmg add command \
3075 -label [mc "Run Merge Tool"] \
3076 -command {merge_resolve_tool}
3077 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3078 $ctxmmg add separator
3079 $ctxmmg add command \
3080 -label [mc "Use Remote Version"] \
3081 -command {merge_resolve_one 3}
3082 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3083 $ctxmmg add command \
3084 -label [mc "Use Local Version"] \
3085 -command {merge_resolve_one 2}
3086 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3087 $ctxmmg add command \
3088 -label [mc "Revert To Base"] \
3089 -command {merge_resolve_one 1}
3090 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3091 $ctxmmg add separator
3092 create_common_diff_popup $ctxmmg
3094 proc popup_diff_menu {ctxm ctxmmg x y X Y} {
3095 global current_diff_path file_states
3098 if {[info exists file_states($current_diff_path)]} {
3099 set state [lindex $file_states($current_diff_path) 0]
3103 if {[string first {U} $state] >= 0} {
3104 tk_popup $ctxmmg $X $Y
3106 if {$::ui_index eq $::current_diff_side} {
3107 set l [mc "Unstage Hunk From Commit"]
3108 set t [mc "Unstage Line From Commit"]
3110 set l [mc "Stage Hunk For Commit"]
3111 set t [mc "Stage Line For Commit"]
3114 || $current_diff_path eq {}
3118 || {T_} eq $state} {
3123 $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
3124 $ctxm entryconf $::ui_diff_applyline -state $s -label $t
3125 tk_popup $ctxm $X $Y
3128 bind_button3 $ui_diff [list popup_diff_menu $ctxm $ctxmmg %x %y %X %Y]
3132 set main_status [::status_bar::new .status]
3133 pack .status -anchor w -side bottom -fill x
3134 $main_status show [mc "Initializing..."]
3139 set gm $repo_config(gui.geometry)
3140 wm geometry . [lindex $gm 0]
3141 .vpane sash place 0 \
3143 [lindex [.vpane sash coord 0] 1]
3144 .vpane.files sash place 0 \
3145 [lindex [.vpane.files sash coord 0] 0] \
3152 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
3153 bind $ui_comm <$M1B-Key-t> {do_add_selection;break}
3154 bind $ui_comm <$M1B-Key-T> {do_add_selection;break}
3155 bind $ui_comm <$M1B-Key-i> {do_add_all;break}
3156 bind $ui_comm <$M1B-Key-I> {do_add_all;break}
3157 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
3158 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
3159 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
3160 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
3161 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
3162 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
3163 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3164 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3165 bind $ui_comm <$M1B-Key-minus> {show_less_context;break}
3166 bind $ui_comm <$M1B-Key-KP_Subtract> {show_less_context;break}
3167 bind $ui_comm <$M1B-Key-equal> {show_more_context;break}
3168 bind $ui_comm <$M1B-Key-plus> {show_more_context;break}
3169 bind $ui_comm <$M1B-Key-KP_Add> {show_more_context;break}
3171 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
3172 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
3173 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
3174 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
3175 bind $ui_diff <$M1B-Key-v> {break}
3176 bind $ui_diff <$M1B-Key-V> {break}
3177 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3178 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3179 bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break}
3180 bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break}
3181 bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break}
3182 bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break}
3183 bind $ui_diff <Key-k> {catch {%W yview scroll -1 units};break}
3184 bind $ui_diff <Key-j> {catch {%W yview scroll 1 units};break}
3185 bind $ui_diff <Key-h> {catch {%W xview scroll -1 units};break}
3186 bind $ui_diff <Key-l> {catch {%W xview scroll 1 units};break}
3187 bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
3188 bind $ui_diff <Control-Key-f> {catch {%W yview scroll 1 pages};break}
3189 bind $ui_diff <Button-1> {focus %W}
3191 if {[is_enabled branch]} {
3192 bind . <$M1B-Key-n> branch_create::dialog
3193 bind . <$M1B-Key-N> branch_create::dialog
3194 bind . <$M1B-Key-o> branch_checkout::dialog
3195 bind . <$M1B-Key-O> branch_checkout::dialog
3196 bind . <$M1B-Key-m> merge::dialog
3197 bind . <$M1B-Key-M> merge::dialog
3199 if {[is_enabled transport]} {
3200 bind . <$M1B-Key-p> do_push_anywhere
3201 bind . <$M1B-Key-P> do_push_anywhere
3204 bind . <Key-F5> ui_do_rescan
3205 bind . <$M1B-Key-r> ui_do_rescan
3206 bind . <$M1B-Key-R> ui_do_rescan
3207 bind . <$M1B-Key-s> do_signoff
3208 bind . <$M1B-Key-S> do_signoff
3209 bind . <$M1B-Key-t> do_add_selection
3210 bind . <$M1B-Key-T> do_add_selection
3211 bind . <$M1B-Key-i> do_add_all
3212 bind . <$M1B-Key-I> do_add_all
3213 bind . <$M1B-Key-minus> {show_less_context;break}
3214 bind . <$M1B-Key-KP_Subtract> {show_less_context;break}
3215 bind . <$M1B-Key-equal> {show_more_context;break}
3216 bind . <$M1B-Key-plus> {show_more_context;break}
3217 bind . <$M1B-Key-KP_Add> {show_more_context;break}
3218 bind . <$M1B-Key-Return> do_commit
3219 foreach i [list $ui_index $ui_workdir] {
3220 bind $i <Button-1> "toggle_or_diff $i %x %y; break"
3221 bind $i <$M1B-Button-1> "add_one_to_selection $i %x %y; break"
3222 bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
3226 set file_lists($ui_index) [list]
3227 set file_lists($ui_workdir) [list]
3229 wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
3230 focus -force $ui_comm
3232 # -- Warn the user about environmental problems. Cygwin's Tcl
3233 # does *not* pass its env array onto any processes it spawns.
3234 # This means that git processes get none of our environment.
3239 set msg [mc "Possible environment issues exist.
3241 The following environment variables are probably
3242 going to be ignored by any Git subprocess run
3246 foreach name [array names env] {
3247 switch -regexp -- $name {
3248 {^GIT_INDEX_FILE$} -
3249 {^GIT_OBJECT_DIRECTORY$} -
3250 {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
3252 {^GIT_EXTERNAL_DIFF$} -
3256 {^GIT_CONFIG_LOCAL$} -
3257 {^GIT_(AUTHOR|COMMITTER)_DATE$} {
3258 append msg " - $name\n"
3261 {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
3262 append msg " - $name\n"
3264 set suggest_user $name
3268 if {$ignored_env > 0} {
3270 This is due to a known issue with the
3271 Tcl binary distributed by Cygwin."]
3273 if {$suggest_user ne {}} {
3276 A good replacement for %s
3277 is placing values for the user.name and
3278 user.email settings into your personal
3284 unset ignored_env msg suggest_user name
3287 # -- Only initialize complex UI if we are going to stay running.
3289 if {[is_enabled transport]} {
3292 set n [.mbar.remote index end]
3293 populate_remotes_menu
3294 set n [expr {[.mbar.remote index end] - $n}]
3296 if {[.mbar.remote type 0] eq "tearoff"} { incr n }
3297 .mbar.remote insert $n separator
3302 if {[winfo exists $ui_comm]} {
3303 set GITGUI_BCK_exists [load_message GITGUI_BCK]
3305 # -- If both our backup and message files exist use the
3306 # newer of the two files to initialize the buffer.
3308 if {$GITGUI_BCK_exists} {
3309 set m [gitdir GITGUI_MSG]
3310 if {[file isfile $m]} {
3311 if {[file mtime [gitdir GITGUI_BCK]] > [file mtime $m]} {
3312 catch {file delete [gitdir GITGUI_MSG]}
3314 $ui_comm delete 0.0 end
3316 $ui_comm edit modified false
3317 catch {file delete [gitdir GITGUI_BCK]}
3318 set GITGUI_BCK_exists 0
3324 proc backup_commit_buffer {} {
3325 global ui_comm GITGUI_BCK_exists
3327 set m [$ui_comm edit modified]
3328 if {$m || $GITGUI_BCK_exists} {
3329 set msg [string trim [$ui_comm get 0.0 end]]
3330 regsub -all -line {[ \r\t]+$} $msg {} msg
3333 if {$GITGUI_BCK_exists} {
3334 catch {file delete [gitdir GITGUI_BCK]}
3335 set GITGUI_BCK_exists 0
3339 set fd [open [gitdir GITGUI_BCK] w]
3340 puts -nonewline $fd $msg
3342 set GITGUI_BCK_exists 1
3346 $ui_comm edit modified false
3349 set ::GITGUI_BCK_i [after 2000 backup_commit_buffer]
3352 backup_commit_buffer
3354 # -- If the user has aspell available we can drive it
3355 # in pipe mode to spellcheck the commit message.
3357 set spell_cmd [list |]
3358 set spell_dict [get_config gui.spellingdictionary]
3359 lappend spell_cmd aspell
3360 if {$spell_dict ne {}} {
3361 lappend spell_cmd --master=$spell_dict
3363 lappend spell_cmd --mode=none
3364 lappend spell_cmd --encoding=utf-8
3365 lappend spell_cmd pipe
3366 if {$spell_dict eq {none}
3367 || [catch {set spell_fd [open $spell_cmd r+]} spell_err]} {
3368 bind_button3 $ui_comm [list tk_popup $ui_comm_ctxm %X %Y]
3370 set ui_comm_spell [spellcheck::init \
3376 unset -nocomplain spell_cmd spell_fd spell_err spell_dict
3379 lock_index begin-read
3380 if {![winfo ismapped .]} {
3384 if {[is_enabled initialamend]} {
3390 if {[is_enabled nocommitmsg]} {
3391 $ui_comm configure -state disabled -background gray
3394 if {[is_enabled multicommit]} {
3397 if {[is_enabled retcode]} {
3398 bind . <Destroy> {+terminate_me %W}
3400 if {$picked && [is_config_true gui.autoexplore]} {