revision traversal: '--simplify-by-decoration'
[git/dscho.git] / git-gui / git-gui.sh
blob12b496bec9233a86da18a45073d964bd559f059c
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 if test "z$*" = zversion \
4 || test "z$*" = z--version; \
5 then \
6 echo 'git-gui version @@GITGUI_VERSION@@'; \
7 exit; \
8 fi; \
9 argv0=$0; \
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]
36 } {
37 catch {wm withdraw .}
38 tk_messageBox \
39 -icon error \
40 -type ok \
41 -title [mc "git-gui: fatal error"] \
42 -message $err
43 exit 1
46 catch {rename send {}} ; # What an evil concept...
48 ######################################################################
50 ## locate our library
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]
65 } else {
66 set oguimsg [file join $oguilib msgs]
68 unset oguirel
70 ######################################################################
72 ## enable verbose loading?
74 if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
75 unset _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
82 proc source {name} {
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
95 proc _mc_trim {fmt} {
96 set cmk [string first @@ $fmt]
97 if {$cmk > 0} {
98 return [string range $fmt 0 [expr {$cmk - 1}]]
100 return $fmt
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]
108 return $msg
111 proc strcat {args} {
112 return [join $args {}]
115 ::msgcat::mcload $oguimsg
116 unset oguimsg
118 ######################################################################
120 ## read only globals
122 set _appname {Git Gui}
123 set _gitdir {}
124 set _gitexec {}
125 set _reponame {}
126 set _iscygwin {}
127 set _search_path {}
129 set _trace [lsearch -exact $argv --trace]
130 if {$_trace >= 0} {
131 set argv [lreplace $argv $_trace $_trace]
132 set _trace 1
133 } else {
134 set _trace 0
137 proc appname {} {
138 global _appname
139 return $_appname
142 proc gitdir {args} {
143 global _gitdir
144 if {$args eq {}} {
145 return $_gitdir
147 return [eval [list file join $_gitdir] $args]
150 proc gitexec {args} {
151 global _gitexec
152 if {$_gitexec eq {}} {
153 if {[catch {set _gitexec [git --exec-path]} err]} {
154 error "Git not installed?\n\n$err"
156 if {[is_Cygwin]} {
157 set _gitexec [exec cygpath \
158 --windows \
159 --absolute \
160 $_gitexec]
161 } else {
162 set _gitexec [file normalize $_gitexec]
165 if {$args eq {}} {
166 return $_gitexec
168 return [eval [list file join $_gitexec] $args]
171 proc reponame {} {
172 return $::_reponame
175 proc is_MacOSX {} {
176 if {[tk windowingsystem] eq {aqua}} {
177 return 1
179 return 0
182 proc is_Windows {} {
183 if {$::tcl_platform(platform) eq {windows}} {
184 return 1
186 return 0
189 proc is_Cygwin {} {
190 global _iscygwin
191 if {$_iscygwin eq {}} {
192 if {$::tcl_platform(platform) eq {windows}} {
193 if {[catch {set p [exec cygpath --windir]} err]} {
194 set _iscygwin 0
195 } else {
196 set _iscygwin 1
198 } else {
199 set _iscygwin 0
202 return $_iscygwin
205 proc is_enabled {option} {
206 global enabled_options
207 if {[catch {set on $enabled_options($option)}]} {return 0}
208 return $on
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 ######################################################################
223 ## config
225 proc is_many_config {name} {
226 switch -glob -- $name {
227 gui.recentrepo -
228 remote.*.fetch -
229 remote.*.push
230 {return 1}
232 {return 0}
236 proc is_config_true {name} {
237 global repo_config
238 if {[catch {set v $repo_config($name)}]} {
239 return 0
240 } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
241 return 1
242 } else {
243 return 0
247 proc get_config {name} {
248 global repo_config
249 if {[catch {set v $repo_config($name)}]} {
250 return {}
251 } else {
252 return $v
256 ######################################################################
258 ## handy utils
260 proc _trace_exec {cmd} {
261 if {!$::_trace} return
262 set d {}
263 foreach v $cmd {
264 if {$d ne {}} {
265 append d { }
267 if {[regexp {[ \t\r\n'"$?*]} $v]} {
268 set v [sq $v]
270 append d $v
272 puts stderr $d
275 proc _git_cmd {name} {
276 global _git_cmd_path
278 if {[catch {set v $_git_cmd_path($name)}]} {
279 switch -- $name {
280 version -
281 --version -
282 --exec-path { return [list $::_git $name] }
285 set p [gitexec git-$name$::_search_exe]
286 if {[file exists $p]} {
287 set v [list $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]
294 set f [open $p r]
295 set s [gets $f]
296 close $f
298 switch -glob -- [lindex $s 0] {
299 #!*sh { set i sh }
300 #!*perl { set i perl }
301 #!*python { set i python }
302 default { error "git-$name is not supported: $s" }
305 upvar #0 _$i interp
306 if {![info exists interp]} {
307 set interp [_which $i]
309 if {$interp eq {}} {
310 error "git-$name requires $i (not in PATH)"
312 set v [concat [list $interp] [lrange $s 1 end] [list $p]]
313 } else {
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
321 return $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 \
330 --windows \
331 --path \
332 --absolute \
333 $env(PATH)] {;}]
334 set _search_exe .exe
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) {;}]
340 set _search_exe .exe
341 } else {
342 set _search_path [split $env(PATH) :]
343 set _search_exe {}
347 if {[is_Windows] && [lsearch -exact $args -script] >= 0} {
348 set suffix {}
349 } else {
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]
359 return {}
362 proc _lappend_nice {cmd_var} {
363 global _nice
364 upvar $cmd_var cmd
366 if {![info exists _nice]} {
367 set _nice [_which nice]
369 if {$_nice ne {}} {
370 lappend cmd $_nice
374 proc git {args} {
375 set opt [list]
377 while {1} {
378 switch -- [lindex $args 0] {
379 --nice {
380 _lappend_nice opt
383 default {
384 break
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]
397 if {$::_trace} {
398 puts stderr "< $result"
400 return $result
403 proc _open_stdout_stderr {cmd} {
404 _trace_exec $cmd
405 if {[catch {
406 set fd [open [concat [list | ] $cmd] r]
407 } err]} {
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 \
417 [list | ] \
418 [lrange $cmd 0 end-1] \
419 [list |& cat] \
420 ] r]
421 } else {
422 error $err
425 fconfigure $fd -eofchar {}
426 return $fd
429 proc git_read {args} {
430 set opt [list]
432 while {1} {
433 switch -- [lindex $args 0] {
434 --nice {
435 _lappend_nice opt
438 --stderr {
439 lappend args 2>@1
442 default {
443 break
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} {
458 set opt [list]
460 while {1} {
461 switch -- [lindex $args 0] {
462 --nice {
463 _lappend_nice opt
466 default {
467 break
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]
484 lappend args 2>@1
486 # On Windows [file executable] might lie so we need to ask
487 # the shell if the hook is executable. Yes that's annoying.
489 if {[is_Windows]} {
490 upvar #0 _sh interp
491 if {![info exists interp]} {
492 set interp [_which sh]
494 if {$interp eq {}} {
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]]
507 return {}
510 proc kill_file_process {fd} {
511 set process [pid $fd]
513 catch {
514 if {[is_Windows]} {
515 # Use a Cygwin-specific flag to allow killing
516 # native Windows processes
517 exec kill -f $process
518 } else {
519 exec kill $process
524 proc gitattr {path attr default} {
525 if {[catch {set r [git check-attr $attr -- $path]}]} {
526 set r unspecified
527 } else {
528 set r [join [lrange [split $r :] 2 end] :]
529 regsub {^ } $r {} r
531 if {$r eq {unspecified}} {
532 return $default
534 return $r
537 proc sq {value} {
538 regsub -all ' $value "'\\''" value
539 return "'$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} {
547 set ref {}
549 close $fd
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]
558 set is_detached 0
559 } else {
560 # Assume this is a detached head.
562 set current_branch HEAD
563 set is_detached 1
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
573 return $m
576 proc rmsel_tag {text} {
577 $text tag conf sel \
578 -background [$text cget -background] \
579 -foreground [$text cget -foreground] \
580 -borderwidth 0
581 $text tag conf in_sel -background lightgray
582 bind $text <Motion> break
583 return $text
586 set root_exists 0
587 bind . <Visibility> {
588 bind . <Visibility> {}
589 set root_exists 1
592 if {[is_Windows]} {
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 ######################################################################
604 ## config defaults
606 set cursor_ptr arrow
607 font create font_diff -family Courier -size 10
608 font create font_ui
609 catch {
610 label .dummy
611 eval font configure font_ui [font actual [.dummy cget -font]]
612 destroy .dummy
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
625 unset class
627 if {[is_Windows] || [is_MacOSX]} {
628 option add *Menu.tearOff 0
631 if {[is_MacOSX]} {
632 set M1B M1
633 set M1T Cmd
634 } else {
635 set M1B Control
636 set M1T Ctrl
639 proc bind_button3 {w cmd} {
640 bind $w <Any-Button-3> $cmd
641 if {[is_MacOSX]} {
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]
655 if {[catch {
656 set need_weight 1
657 foreach {cn cv} $repo_config(gui.$name) {
658 if {$cn eq {-weight}} {
659 set need_weight 0
661 font configure $font $cn $cv
663 if {$need_weight} {
664 font configure $font -weight normal
666 } err]} {
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]
700 set font_descs {
701 {fontui font_ui {mc "Main Font"}}
702 {fontdiff font_diff {mc "Diff/Console Font"}}
705 ######################################################################
707 ## find git
709 set _git [_which git]
710 if {$_git eq {}} {
711 catch {wm withdraw .}
712 tk_messageBox \
713 -icon error \
714 -type ok \
715 -title [mc "git-gui: fatal error"] \
716 -message [mc "Cannot find git in PATH."]
717 exit 1
720 ######################################################################
722 ## version check
724 if {[catch {set _git_version [git --version]} err]} {
725 catch {wm withdraw .}
726 tk_messageBox \
727 -icon error \
728 -type ok \
729 -title [mc "git-gui: fatal error"] \
730 -message "Cannot determine Git version:
732 $err
734 [appname] requires Git 1.5.0 or later."
735 exit 1
737 if {![regsub {^git version } $_git_version {} _git_version]} {
738 catch {wm withdraw .}
739 tk_messageBox \
740 -icon error \
741 -type ok \
742 -title [mc "git-gui: fatal error"] \
743 -message [strcat [mc "Cannot parse Git version string:"] "\n\n$_git_version"]
744 exit 1
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 .}
756 if {[tk_messageBox \
757 -icon warning \
758 -type yesno \
759 -default no \
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
770 } else {
771 exit 1
774 unset _real_git_version
776 proc git-version {args} {
777 global _git_version
779 switch [llength $args] {
781 return $_git_version
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]]]
813 default {
814 error "git-version >= x"
820 if {[git-version < 1.5]} {
821 catch {wm withdraw .}
822 tk_messageBox \
823 -icon error \
824 -type ok \
825 -title [mc "git-gui: fatal error"] \
826 -message "[appname] requires Git 1.5.0 or later.
828 You are using [git-version]:
830 [git --version]"
831 exit 1
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 .}
841 tk_messageBox \
842 -icon error \
843 -type ok \
844 -title [mc "git-gui: fatal error"] \
845 -message $err
846 exit 1
848 if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
849 set idx [list]
850 while {[gets $fd n] >= 0} {
851 if {$n ne {} && ![string match #* $n]} {
852 lappend idx $n
855 } else {
856 set idx {}
858 close $fd
860 if {$idx ne {}} {
861 set loaded [list]
862 foreach p $idx {
863 if {[lsearch -exact $loaded $p] >= 0} continue
864 source [file join $oguilib $p]
865 lappend loaded $p
867 unset loaded p
868 } else {
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} {
878 >= 1.5.3 {
879 upvar $arr_name arr
880 array unset arr
881 set buf {}
882 catch {
883 set fd_rc [eval \
884 [list git_read config] \
885 $args \
886 [list --null --list]]
887 fconfigure $fd_rc -translation binary
888 set buf [read $fd_rc]
889 close $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
895 } else {
896 set arr($name) $value
901 default {
902 upvar $arr_name arr
903 array unset arr
904 catch {
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
910 } else {
911 set arr($name) $value
915 close $fd_rc
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]} {
943 unset _junk
944 } else {
945 set subcommand gui
947 if {$subcommand eq {gui.sh}} {
948 set subcommand gui
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
956 enable_option branch
957 enable_option transport
958 disable_option bare
960 switch -- $subcommand {
961 browser -
962 blame {
963 enable_option bare
965 disable_option multicommit
966 disable_option branch
967 disable_option transport
969 citool {
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]
979 switch -- $a {
980 --amend {
981 enable_option initialamend
983 --nocommit {
984 enable_option nocommit
985 enable_option nocommitmsg
987 --commitmsg {
988 disable_option nocommitmsg
990 default {
991 break
995 set argv [lrange $argv 1 end]
1000 ######################################################################
1002 ## repository setup
1004 set picked 0
1005 if {[catch {
1006 set _gitdir $env(GIT_DIR)
1007 set _prefix {}
1009 && [catch {
1010 set _gitdir [git rev-parse --git-dir]
1011 set _prefix [git rev-parse --show-prefix]
1012 } err]} {
1013 load_config 1
1014 apply_config
1015 choose_repository::pick
1016 set picked 1
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"]
1024 exit 1
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"]
1031 exit 1
1033 unset cdup
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"]
1038 exit 1
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"]
1043 exit 1
1046 set _reponame [file split [file normalize $_gitdir]]
1047 if {[lindex $_reponame end] eq {.git}} {
1048 set _reponame [lindex $_reponame end-1]
1049 } else {
1050 set _reponame [lindex $_reponame end]
1053 ######################################################################
1055 ## global init
1057 set current_diff_path {}
1058 set current_diff_side {}
1059 set diff_actions [list]
1061 set HEAD {}
1062 set PARENT {}
1063 set MERGE_HEAD [list]
1064 set commit_type {}
1065 set empty_tree {}
1066 set current_branch {}
1067 set is_detached 0
1068 set current_diff_path {}
1069 set is_3way_diff 0
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 ######################################################################
1087 ## task management
1089 set rescan_active 0
1090 set diff_active 0
1091 set last_clicked {}
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
1104 return 1
1105 } elseif {$index_lock_type eq "begin-$type"} {
1106 set index_lock_type $type
1107 return 1
1109 return 0
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 ######################################################################
1123 ## status
1125 proc repository_state {ctvar hdvar mhvar} {
1126 global current_branch
1127 upvar $ctvar ct $hdvar hd $mhvar mh
1129 set mh [list]
1131 load_current_branch
1132 if {[catch {set hd [git rev-parse --verify HEAD]}]} {
1133 set hd {}
1134 set ct initial
1135 return
1138 set merge_head [gitdir MERGE_HEAD]
1139 if {[file exists $merge_head]} {
1140 set ct merge
1141 set fd_mh [open $merge_head r]
1142 while {[gets $fd_mh line] >= 0} {
1143 lappend mh $line
1145 close $fd_mh
1146 return
1149 set ct normal
1152 proc PARENT {} {
1153 global PARENT empty_tree
1155 set p [lindex $PARENT 0]
1156 if {$p ne {}} {
1157 return $p
1159 if {$empty_tree eq {}} {
1160 set empty_tree [git mktree << {}]
1162 return $empty_tree
1165 proc force_amend {} {
1166 global selected_commit_type
1167 global HEAD PARENT MERGE_HEAD commit_type
1169 repository_state newType newHEAD newMERGE_HEAD
1170 set HEAD $newHEAD
1171 set PARENT $newHEAD
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
1183 global repo_config
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} {
1191 } else {
1192 set HEAD $newHEAD
1193 set PARENT $newHEAD
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]} {
1209 $ui_comm edit reset
1210 $ui_comm edit modified false
1213 if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
1214 rescan_stage2 {} $after
1215 } else {
1216 set rescan_active 1
1217 ui_status [mc "Refreshing file status..."]
1218 set fd_rf [git_read update-index \
1219 -q \
1220 --unmerged \
1221 --ignore-missing \
1222 --refresh \
1224 fconfigure $fd_rf -blocking 0 -translation binary
1225 fileevent $fd_rf readable \
1226 [list rescan_stage2 $fd_rf $after]
1230 if {[is_Cygwin]} {
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
1238 } else {
1239 set is_git_info_exclude 1
1242 return $is_git_info_exclude
1244 } else {
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
1253 if {$fd ne {}} {
1254 read $fd
1255 if {![eof $fd]} return
1256 close $fd
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"
1268 set buf_rdi {}
1269 set buf_rdf {}
1270 set buf_rlo {}
1272 set rescan_active 3
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} {
1287 global ui_comm
1289 set f [gitdir $file]
1290 if {[file isfile $f]} {
1291 if {[catch {set fd [open $f r]}]} {
1292 return 0
1294 fconfigure $fd -eofchar {}
1295 set content [string trim [read $fd]]
1296 close $fd
1297 regsub -all -line {[ \r\t]+$} $content {} content
1298 $ui_comm delete 0.0 end
1299 $ui_comm insert end $content
1300 return 1
1302 return 0
1305 proc run_prepare_commit_msg_hook {} {
1306 global pch_error
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]
1318 close $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]
1323 close $fd_sm
1324 } else {
1325 set pcm_source ""
1328 close $fd_pcm
1330 set fd_ph [githook_read prepare-commit-msg \
1331 [gitdir PREPARE_COMMIT_MSG] $pcm_source]
1332 if {$fd_ph eq {}} {
1333 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1334 return 0;
1337 ui_status [mc "Calling prepare-commit-msg hook..."]
1338 set pch_error {}
1340 fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
1341 fileevent $fd_ph readable \
1342 [list prepare_commit_msg_hook_wait $fd_ph]
1344 return 1;
1347 proc prepare_commit_msg_hook_wait {fd_ph} {
1348 global pch_error
1350 append pch_error [read $fd_ph]
1351 fconfigure $fd_ph -blocking 1
1352 if {[eof $fd_ph]} {
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]}
1357 exit 1
1358 } else {
1359 load_message PREPARE_COMMIT_MSG
1361 set pch_error {}
1362 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1363 return
1365 fconfigure $fd_ph -blocking 0
1366 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1369 proc read_diff_index {fd after} {
1370 global buf_rdi
1372 append buf_rdi [read $fd]
1373 set c 0
1374 set n [string length $buf_rdi]
1375 while {$c < $n} {
1376 set z1 [string first "\0" $buf_rdi $c]
1377 if {$z1 == -1} break
1378 incr z1
1379 set z2 [string first "\0" $buf_rdi $z1]
1380 if {$z2 == -1} break
1382 incr c
1383 set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
1384 set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
1385 merge_state \
1386 [encoding convertfrom $p] \
1387 [lindex $i 4]? \
1388 [list [lindex $i 0] [lindex $i 2]] \
1389 [list]
1390 set c $z2
1391 incr c
1393 if {$c < $n} {
1394 set buf_rdi [string range $buf_rdi $c end]
1395 } else {
1396 set buf_rdi {}
1399 rescan_done $fd buf_rdi $after
1402 proc read_diff_files {fd after} {
1403 global buf_rdf
1405 append buf_rdf [read $fd]
1406 set c 0
1407 set n [string length $buf_rdf]
1408 while {$c < $n} {
1409 set z1 [string first "\0" $buf_rdf $c]
1410 if {$z1 == -1} break
1411 incr z1
1412 set z2 [string first "\0" $buf_rdf $z1]
1413 if {$z2 == -1} break
1415 incr c
1416 set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
1417 set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
1418 merge_state \
1419 [encoding convertfrom $p] \
1420 ?[lindex $i 4] \
1421 [list] \
1422 [list [lindex $i 0] [lindex $i 2]]
1423 set c $z2
1424 incr c
1426 if {$c < $n} {
1427 set buf_rdf [string range $buf_rdf $c end]
1428 } else {
1429 set buf_rdf {}
1432 rescan_done $fd buf_rdf $after
1435 proc read_ls_others {fd after} {
1436 global buf_rlo
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]
1446 merge_state $p ?O
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
1454 upvar $buf to_clear
1456 if {![eof $fd]} return
1457 set to_clear {}
1458 close $fd
1459 if {[incr rescan_active -1] > 0} return
1461 prune_selection
1462 unlock_index
1463 display_all_files
1464 if {$current_diff_path ne {}} reshow_diff
1465 if {$current_diff_path eq {}} select_first_diff
1467 uplevel #0 $after
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 ######################################################################
1482 ## ui helpers
1484 proc mapicon {w state path} {
1485 global all_icons
1487 if {[catch {set r $all_icons($state$w)}]} {
1488 puts "error: no icon for $w state={$state} $path"
1489 return file_plain
1491 return $r
1494 proc mapdesc {state path} {
1495 global all_descs
1497 if {[catch {set r $all_descs($state)}]} {
1498 puts "error: no desc for state={$state} $path"
1499 return $state
1501 return $r
1504 proc ui_status {msg} {
1505 global main_status
1506 if {[info exists main_status]} {
1507 $main_status show $msg
1511 proc ui_ready {{test {}}} {
1512 global main_status
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
1521 return $path
1524 proc short_path {path} {
1525 return [escape_path [lindex [file split $path] end]]
1528 set next_icon_id 0
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)}]} {
1538 set state __
1539 set icon n[incr next_icon_id]
1540 } else {
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 \
1563 return $state
1566 proc display_file_helper {w path icon_name old_m new_m} {
1567 global file_lists
1569 if {$new_m eq {_}} {
1570 set lno [lsearch -sorted -exact $file_lists($w) $path]
1571 if {$lno >= 0} {
1572 set file_lists($w) [lreplace $file_lists($w) $lno $lno]
1573 incr 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]
1582 incr lno
1583 $w conf -state normal
1584 $w image create $lno.0 \
1585 -align center -padx 5 -pady 1 \
1586 -name $icon_name \
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]
1608 if {$o eq {U}} {
1609 set o _
1611 if {$n eq {U}} {
1612 set n _
1614 display_file_helper $ui_index $path $icon_name $o $n
1616 if {[string index $old_m 0] eq {U}} {
1617 set o U
1618 } else {
1619 set o [string index $old_m 1]
1621 if {[string index $new_m 0] eq {U}} {
1622 set n U
1623 } else {
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} {
1635 global file_lists
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 \
1641 -name $icon_name \
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
1649 global last_clicked
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
1656 set last_clicked {}
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)
1663 set m [lindex $s 0]
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 \
1669 $icon_name $s
1672 if {[string index $m 0] eq {U}} {
1673 set s U
1674 } else {
1675 set s [string index $m 1]
1677 if {$s ne {_}} {
1678 display_all_files_helper $ui_workdir $path \
1679 $icon_name $s
1683 $ui_index conf -state disabled
1684 $ui_workdir conf -state disabled
1687 ######################################################################
1689 ## icons
1691 set filemask {
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
1790 foreach i {
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"}}
1806 {_D {mc "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
1823 unset i
1825 ######################################################################
1827 ## util
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]
1840 incr sz $amt
1841 font configure $font -size $sz
1842 font configure ${font}bold -size $sz
1843 font configure ${font}italic -size $sz
1846 ######################################################################
1848 ## ui commands
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]
1858 if {$exe eq {}} {
1859 error_popup [mc "Couldn't find gitk in PATH"]
1860 } else {
1861 global env
1863 if {[info exists env(GIT_DIR)]} {
1864 set old_GIT_DIR $env(GIT_DIR)
1865 } else {
1866 set old_GIT_DIR {}
1869 set pwd [pwd]
1870 cd [file dirname [gitdir]]
1871 set env(GIT_DIR) [file tail [gitdir]]
1873 eval exec $cmd $revs &
1875 if {$old_GIT_DIR eq {}} {
1876 unset env(GIT_DIR)
1877 } else {
1878 set env(GIT_DIR) $old_GIT_DIR
1880 cd $pwd
1882 ui_status $::starting_gitk_msg
1883 after 10000 {
1884 ui_ready $starting_gitk_msg
1889 proc do_explore {} {
1890 set explorer {}
1891 if {[is_Cygwin] || [is_Windows]} {
1892 set explorer "explorer.exe"
1893 } elseif {[is_MacOSX]} {
1894 set explorer "open"
1895 } else {
1896 # freedesktop.org-conforming system is our best shot
1897 set explorer "xdg-open"
1899 eval exec $explorer [file dirname [gitdir]] &
1902 set is_quitting 0
1903 set ret_code 1
1905 proc terminate_me {win} {
1906 global ret_code
1907 if {$win ne {.}} return
1908 exit $ret_code
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
1915 global ret_code
1917 if {$is_quitting} return
1918 set is_quitting 1
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
1927 } else {
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])
1932 && $msg ne {}} {
1933 catch {
1934 set fd [open $save w]
1935 puts -nonewline $fd $msg
1936 close $fd
1938 } else {
1939 catch {file delete $save}
1943 # -- Cancel our spellchecker if its running.
1945 if {[info exists ui_comm_spell]} {
1946 $ui_comm_spell stop
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)}]} {
1963 set rc_geometry {}
1965 if {$cfg_geometry ne $rc_geometry} {
1966 catch {git config gui.geometry $cfg_geometry}
1970 set ret_code $rc
1971 destroy .
1974 proc do_rescan {} {
1975 rescan ui_ready
1978 proc ui_do_rescan {} {
1979 rescan {force_first_diff; ui_ready}
1982 proc do_commit {} {
1983 commit_tree
1986 proc next_diff {} {
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]
1994 if {$lid == -1} {
1995 set lid 0
1996 foreach lname $lst {
1997 if {$lname >= $name} break
1998 incr lid
2002 return $lid
2005 proc find_file_from {flist idx delta path mmask} {
2006 global file_states
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]} {
2016 return $idx
2020 incr idx $delta
2023 return {}
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)
2031 if {$lno eq {}} {
2032 set lno [find_anchor_pos $flist $path]
2033 } else {
2034 incr lno -1
2037 if {$mmask ne {} && ![regexp {(^\^)|(\$$)} $mmask]} {
2038 if {$w eq $ui_index} {
2039 set mmask "^$mmask"
2040 } else {
2041 set mmask "$mmask\$"
2045 set idx [find_file_from $flist $lno 1 $path $mmask]
2046 if {$idx eq {}} {
2047 incr lno -1
2048 set idx [find_file_from $flist $lno -1 $path $mmask]
2051 if {$idx ne {}} {
2052 set next_diff_w $w
2053 set next_diff_p [lindex $flist $idx]
2054 set next_diff_i [expr {$idx+1}]
2055 return 1
2056 } else {
2057 return 0
2061 proc next_diff_after_action {w path {lno {}} {mmask {}}} {
2062 global current_diff_path
2064 if {$path ne $current_diff_path} {
2065 return {}
2066 } elseif {[find_next_diff $w $path $lno $mmask]} {
2067 return {next_diff;}
2068 } else {
2069 return {reshow_diff;}
2073 proc select_first_diff {} {
2074 global ui_workdir
2076 if {[find_next_diff $ui_workdir {} 1 {^_?U}] ||
2077 [find_next_diff $ui_workdir {} 1 {[^O]$}]} {
2078 next_diff
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
2091 select_first_diff
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}]]
2102 if {$path eq {}} {
2103 set last_clicked {}
2104 return
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]
2115 } else {
2116 set state {__}
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
2126 return
2129 if {[string index $state 1] eq {O}} {
2130 set mmask {}
2131 } else {
2132 set mmask {[^O]}
2135 set after [next_diff_after_action $w $path $lno $mmask]
2137 if {$w eq $ui_index} {
2138 update_indexinfo \
2139 "Unstaging [short_path $path] from commit" \
2140 [list $path] \
2141 [concat $after [list ui_ready]]
2142 } elseif {$w eq $ui_workdir} {
2143 update_index \
2144 "Adding [short_path $path]" \
2145 [list $path] \
2146 [concat $after [list ui_ready]]
2148 } else {
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}]]
2158 if {$path eq {}} {
2159 set last_clicked {}
2160 return
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)}]} {
2171 set in_sel 0
2173 if {$in_sel} {
2174 unset selected_paths($path)
2175 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
2176 } else {
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
2187 return
2190 set lno [lindex [split [$w index @$x,$y] .] 0]
2191 set lc [lindex $last_clicked 1]
2192 if {$lc < $lno} {
2193 set begin $lc
2194 set end $lno
2195 } else {
2196 set begin $lno
2197 set end $lc
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 {} {
2209 global repo_config
2210 if {$repo_config(gui.diffcontext) < 99} {
2211 incr repo_config(gui.diffcontext)
2212 reshow_diff
2216 proc show_less_context {} {
2217 global repo_config
2218 if {$repo_config(gui.diffcontext) > 1} {
2219 incr repo_config(gui.diffcontext) -1
2220 reshow_diff
2224 ######################################################################
2226 ## ui construction
2228 load_config 0
2229 apply_config
2230 set ui_comm {}
2232 # -- Menu Bar
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"] \
2287 -command do_stats
2289 .mbar.repository add command -label [mc "Compress Database"] \
2290 -command do_gc
2292 .mbar.repository add command -label [mc "Verify Database"] \
2293 -command do_fsck_objects
2295 .mbar.repository add separator
2297 if {[is_Cygwin]} {
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
2312 if {[is_MacOSX]} {
2313 proc ::tk::mac::Quit {args} { do_quit }
2314 } else {
2315 .mbar.repository add command -label [mc Quit] \
2316 -command do_quit \
2317 -accelerator $M1T-Q
2320 # -- Edit Menu
2322 menu .mbar.edit
2323 .mbar.edit add command -label [mc Undo] \
2324 -command {catch {[focus] edit undo}} \
2325 -accelerator $M1T-Z
2326 .mbar.edit add command -label [mc Redo] \
2327 -command {catch {[focus] edit redo}} \
2328 -accelerator $M1T-Y
2329 .mbar.edit add separator
2330 .mbar.edit add command -label [mc Cut] \
2331 -command {catch {tk_textCut [focus]}} \
2332 -accelerator $M1T-X
2333 .mbar.edit add command -label [mc Copy] \
2334 -command {catch {tk_textCopy [focus]}} \
2335 -accelerator $M1T-C
2336 .mbar.edit add command -label [mc Paste] \
2337 -command {catch {tk_textPaste [focus]; [focus] see insert}} \
2338 -accelerator $M1T-V
2339 .mbar.edit add command -label [mc Delete] \
2340 -command {catch {[focus] delete sel.first sel.last}} \
2341 -accelerator Del
2342 .mbar.edit add separator
2343 .mbar.edit add command -label [mc "Select All"] \
2344 -command {catch {[focus] tag add sel 0.0 end}} \
2345 -accelerator $M1T-A
2347 # -- Branch Menu
2349 if {[is_enabled branch]} {
2350 menu .mbar.branch
2352 .mbar.branch add command -label [mc "Create..."] \
2353 -command branch_create::dialog \
2354 -accelerator $M1T-N
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 \
2360 -accelerator $M1T-O
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]
2380 # -- Commit Menu
2382 proc commit_btn_caption {} {
2383 if {[is_enabled nocommit]} {
2384 return [mc "Done"]
2385 } else {
2386 return [mc Commit@@verb]
2390 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2391 menu .mbar.commit
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 \
2398 -value new
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 \
2406 -value amend
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 \
2415 -accelerator F5
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 \
2421 -accelerator $M1T-T
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 \
2427 -accelerator $M1T-I
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 \
2449 -accelerator $M1T-=
2451 .mbar.commit add separator
2453 if {![is_enabled nocommitmsg]} {
2454 .mbar.commit add command -label [mc "Sign Off"] \
2455 -command do_signoff \
2456 -accelerator $M1T-S
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]
2466 # -- Merge Menu
2468 if {[is_enabled branch]} {
2469 menu .mbar.merge
2470 .mbar.merge add command -label [mc "Local Merge..."] \
2471 -command merge::dialog \
2472 -accelerator $M1T-M
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]
2481 # -- Transport Menu
2483 if {[is_enabled transport]} {
2484 menu .mbar.remote
2486 .mbar.remote add command \
2487 -label [mc "Add..."] \
2488 -command remote_add::dialog \
2489 -accelerator $M1T-A
2490 .mbar.remote add command \
2491 -label [mc "Push..."] \
2492 -command do_push_anywhere \
2493 -accelerator $M1T-P
2494 .mbar.remote add command \
2495 -label [mc "Delete Branch..."] \
2496 -command remote_branch_delete::dialog
2499 if {[is_MacOSX]} {
2500 # -- Apple Menu (Mac OS X only)
2502 .mbar add cascade -label Apple -menu .mbar.apple
2503 menu .mbar.apple
2505 .mbar.apple add command -label [mc "About %s" [appname]] \
2506 -command do_about
2507 .mbar.apple add separator
2508 .mbar.apple add command \
2509 -label [mc "Preferences..."] \
2510 -command do_options \
2511 -accelerator $M1T-,
2512 bind . <$M1B-,> do_options
2513 } else {
2514 # -- Edit Menu
2516 .mbar.edit add separator
2517 .mbar.edit add command -label [mc "Options..."] \
2518 -command do_options
2521 # -- Help Menu
2523 .mbar add cascade -label [mc Help] -menu .mbar.help
2524 menu .mbar.help
2526 if {![is_MacOSX]} {
2527 .mbar.help add command -label [mc "About %s" [appname]] \
2528 -command do_about
2532 set doc_path [file dirname [gitexec]]
2533 set doc_path [file join $doc_path Documentation index.html]
2535 if {[is_Cygwin]} {
2536 set doc_path [exec cygpath --mixed $doc_path]
2539 if {[file isfile $doc_path]} {
2540 set doc_url "file:$doc_path"
2541 } else {
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"] \
2553 -command do_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 {}
2566 proc usage {} {
2567 puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
2568 exit 1
2571 # -- Not a normal commit type invocation? Do that instead!
2573 switch -- $subcommand {
2574 browser -
2575 blame {
2576 if {$subcommand eq "blame"} {
2577 set subcommand_args {[--line=<num>] rev? path}
2578 } else {
2579 set subcommand_args {rev? path}
2581 if {$argv eq {}} usage
2582 set head {}
2583 set path {}
2584 set jump_spec {}
2585 set is_path 0
2586 foreach a $argv {
2587 if {$is_path || [file exists $_prefix$a]} {
2588 if {$path ne {}} usage
2589 set path $_prefix$a
2590 break
2591 } elseif {$a eq {--}} {
2592 if {$path ne {}} {
2593 if {$head ne {}} usage
2594 set head $path
2595 set path {}
2597 set is_path 1
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
2603 set head $a
2604 set is_path 1
2605 } else {
2606 usage
2609 unset is_path
2611 if {$head ne {} && $path eq {}} {
2612 set path $_prefix$head
2613 set head {}
2616 if {$head eq {}} {
2617 load_current_branch
2618 } else {
2619 if {[regexp {^[0-9a-f]{1,39}$} $head]} {
2620 if {[catch {
2621 set head [git rev-parse --verify $head]
2622 } err]} {
2623 puts stderr $err
2624 exit 1
2627 set current_branch $head
2630 switch -- $subcommand {
2631 browser {
2632 if {$jump_spec ne {}} usage
2633 if {$head eq {}} {
2634 if {$path ne {} && [file isdirectory $path]} {
2635 set head $current_branch
2636 } else {
2637 set head $path
2638 set path {}
2641 browser::new $head $path
2643 blame {
2644 if {$head eq {} && ![file exists $path]} {
2645 puts stderr [mc "fatal: cannot stat path %s: No such file or directory" $path]
2646 exit 1
2648 blame::new $head $path $jump_spec
2651 return
2653 citool -
2654 gui {
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"
2661 puts stderr {}
2662 exit 1
2664 # fall through to setup UI for commits
2666 default {
2667 puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
2668 exit 1
2672 # -- Branch Control
2674 frame .branch \
2675 -borderwidth 1 \
2676 -relief sunken
2677 label .branch.l1 \
2678 -text [mc "Current Branch:"] \
2679 -anchor w \
2680 -justify left
2681 label .branch.cb \
2682 -textvariable current_branch \
2683 -anchor w \
2684 -justify left
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 \
2702 -borderwidth 0 \
2703 -width 20 -height 10 \
2704 -wrap none \
2705 -cursor $cursor_ptr \
2706 -xscrollcommand {.vpane.files.index.sx set} \
2707 -yscrollcommand {.vpane.files.index.sy set} \
2708 -state disabled
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 \
2722 -borderwidth 0 \
2723 -width 20 -height 10 \
2724 -wrap none \
2725 -cursor $cursor_ptr \
2726 -xscrollcommand {.vpane.files.workdir.sx set} \
2727 -yscrollcommand {.vpane.files.workdir.sy set} \
2728 -state disabled
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] {
2740 rmsel_tag $i
2741 $i tag conf in_diff -background [$i tag cget in_sel -background]
2743 unset i
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 {} \
2758 -anchor w \
2759 -justify left
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"] \
2770 -command do_add_all
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"] \
2777 -command do_signoff
2778 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
2781 button .vpane.lower.commarea.buttons.commit -text [commit_btn_caption] \
2782 -command do_commit
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 \
2805 -value new
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 \
2812 -value amend
2813 lappend disable_on_lock \
2814 [list .vpane.lower.commarea.buffer.header.amend conf -state]
2817 label $ui_coml \
2818 -anchor w \
2819 -justify left
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 \
2841 -borderwidth 1 \
2842 -undo true \
2843 -maxundo 20 \
2844 -autoseparators true \
2845 -relief sunken \
2846 -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \
2847 -font font_diff \
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
2860 $ctxm add command \
2861 -label [mc Cut] \
2862 -command {tk_textCut $ui_comm}
2863 $ctxm add command \
2864 -label [mc Copy] \
2865 -command {tk_textCopy $ui_comm}
2866 $ctxm add command \
2867 -label [mc Paste] \
2868 -command {tk_textPaste $ui_comm}
2869 $ctxm add command \
2870 -label [mc Delete] \
2871 -command {$ui_comm delete sel.first sel.last}
2872 $ctxm add separator
2873 $ctxm add command \
2874 -label [mc "Select All"] \
2875 -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
2876 $ctxm add command \
2877 -label [mc "Copy All"] \
2878 -command {
2879 $ui_comm tag add sel 0.0 end
2880 tk_textCopy $ui_comm
2881 $ui_comm tag remove sel 0.0 end
2883 $ctxm add separator
2884 $ctxm add command \
2885 -label [mc "Sign Off"] \
2886 -command do_signoff
2887 set ui_comm_ctxm $ctxm
2889 # -- Diff Header
2891 proc trace_current_diff_path {varname args} {
2892 global current_diff_path diff_actions file_states
2893 if {$current_diff_path eq {}} {
2894 set s {}
2895 set f {}
2896 set p {}
2897 set o disabled
2898 } else {
2899 set p $current_diff_path
2900 set s [mapdesc [lindex $file_states($p) 0] $p]
2901 set f [mc "File:"]
2902 set p [escape_path $p]
2903 set o normal
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 {
2910 uplevel #0 $w $o
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 \
2917 -background gold \
2918 -foreground black \
2919 -width $max_status_desc \
2920 -anchor w \
2921 -justify left
2922 label .vpane.lower.diff.header.file \
2923 -background gold \
2924 -foreground black \
2925 -anchor w \
2926 -justify left
2927 label .vpane.lower.diff.header.path \
2928 -background gold \
2929 -foreground black \
2930 -anchor w \
2931 -justify left
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
2937 $ctxm add command \
2938 -label [mc Copy] \
2939 -command {
2940 clipboard clear
2941 clipboard append \
2942 -format STRING \
2943 -type STRING \
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"
2949 # -- Diff Body
2951 frame .vpane.lower.diff.body
2952 set ui_diff .vpane.lower.diff.body.t
2953 text $ui_diff -background white -foreground black \
2954 -borderwidth 0 \
2955 -width 80 -height 15 -wrap none \
2956 -font font_diff \
2957 -xscrollcommand {.vpane.lower.diff.body.sbx set} \
2958 -yscrollcommand {.vpane.lower.diff.body.sby set} \
2959 -state disabled
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 \
2981 -foreground red \
2982 -background {#e2effa}
2983 $ui_diff tag conf d_s+ \
2984 -foreground {#00a000} \
2985 -background ivory1
2986 $ui_diff tag conf d_s- \
2987 -foreground red \
2988 -background ivory1
2990 $ui_diff tag conf d<<<<<<< \
2991 -foreground orange \
2992 -font font_diffbold
2993 $ui_diff tag conf d======= \
2994 -foreground orange \
2995 -font font_diffbold
2996 $ui_diff tag conf d>>>>>>> \
2997 -foreground orange \
2998 -font font_diffbold
3000 $ui_diff tag raise sel
3002 # -- Diff Body Context Menu
3005 proc create_common_diff_popup {ctxm} {
3006 $ctxm add command \
3007 -label [mc "Show Less Context"] \
3008 -command show_less_context
3009 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3010 $ctxm add command \
3011 -label [mc "Show More Context"] \
3012 -command show_more_context
3013 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3014 $ctxm add separator
3015 $ctxm add command \
3016 -label [mc Refresh] \
3017 -command reshow_diff
3018 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3019 $ctxm add command \
3020 -label [mc Copy] \
3021 -command {tk_textCopy $ui_diff}
3022 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3023 $ctxm add command \
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]
3027 $ctxm add command \
3028 -label [mc "Copy All"] \
3029 -command {
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]
3035 $ctxm add separator
3036 $ctxm add command \
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]
3040 $ctxm add command \
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]
3044 $ctxm add separator
3045 set emenu $ctxm.enc
3046 menu $emenu
3047 build_encoding_menu $emenu [list force_diff_encoding]
3048 $ctxm add cascade \
3049 -label [mc "Encoding"] \
3050 -menu $emenu
3051 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3052 $ctxm add separator
3053 $ctxm add command -label [mc "Options..."] \
3054 -command do_options
3057 set ctxm .vpane.lower.diff.body.ctxm
3058 menu $ctxm -tearoff 0
3059 $ctxm add command \
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]
3064 $ctxm add command \
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]
3069 $ctxm add separator
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
3096 set ::cursorX $x
3097 set ::cursorY $y
3098 if {[info exists file_states($current_diff_path)]} {
3099 set state [lindex $file_states($current_diff_path) 0]
3100 } else {
3101 set state {__}
3103 if {[string first {U} $state] >= 0} {
3104 tk_popup $ctxmmg $X $Y
3105 } else {
3106 if {$::ui_index eq $::current_diff_side} {
3107 set l [mc "Unstage Hunk From Commit"]
3108 set t [mc "Unstage Line From Commit"]
3109 } else {
3110 set l [mc "Stage Hunk For Commit"]
3111 set t [mc "Stage Line For Commit"]
3113 if {$::is_3way_diff
3114 || $current_diff_path eq {}
3115 || {__} eq $state
3116 || {_O} eq $state
3117 || {_T} eq $state
3118 || {T_} eq $state} {
3119 set s disabled
3120 } else {
3121 set s normal
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]
3130 # -- Status Bar
3132 set main_status [::status_bar::new .status]
3133 pack .status -anchor w -side bottom -fill x
3134 $main_status show [mc "Initializing..."]
3136 # -- Load geometry
3138 catch {
3139 set gm $repo_config(gui.geometry)
3140 wm geometry . [lindex $gm 0]
3141 .vpane sash place 0 \
3142 [lindex $gm 1] \
3143 [lindex [.vpane sash coord 0] 1]
3144 .vpane.files sash place 0 \
3145 [lindex [.vpane.files sash coord 0] 0] \
3146 [lindex $gm 2]
3147 unset gm
3150 # -- Key Bindings
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"
3224 unset i
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.
3236 if {[is_Cygwin]} {
3237 set ignored_env 0
3238 set suggest_user {}
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
3243 by %s:
3245 " [appname]]
3246 foreach name [array names env] {
3247 switch -regexp -- $name {
3248 {^GIT_INDEX_FILE$} -
3249 {^GIT_OBJECT_DIRECTORY$} -
3250 {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
3251 {^GIT_DIFF_OPTS$} -
3252 {^GIT_EXTERNAL_DIFF$} -
3253 {^GIT_PAGER$} -
3254 {^GIT_TRACE$} -
3255 {^GIT_CONFIG$} -
3256 {^GIT_CONFIG_LOCAL$} -
3257 {^GIT_(AUTHOR|COMMITTER)_DATE$} {
3258 append msg " - $name\n"
3259 incr ignored_env
3261 {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
3262 append msg " - $name\n"
3263 incr ignored_env
3264 set suggest_user $name
3268 if {$ignored_env > 0} {
3269 append msg [mc "
3270 This is due to a known issue with the
3271 Tcl binary distributed by Cygwin."]
3273 if {$suggest_user ne {}} {
3274 append msg [mc "
3276 A good replacement for %s
3277 is placing values for the user.name and
3278 user.email settings into your personal
3279 ~/.gitconfig file.
3280 " $suggest_user]
3282 warn_popup $msg
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]} {
3290 load_all_remotes
3292 set n [.mbar.remote index end]
3293 populate_remotes_menu
3294 set n [expr {[.mbar.remote index end] - $n}]
3295 if {$n > 0} {
3296 if {[.mbar.remote type 0] eq "tearoff"} { incr n }
3297 .mbar.remote insert $n separator
3299 unset n
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]}
3313 } else {
3314 $ui_comm delete 0.0 end
3315 $ui_comm edit reset
3316 $ui_comm edit modified false
3317 catch {file delete [gitdir GITGUI_BCK]}
3318 set GITGUI_BCK_exists 0
3321 unset m
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
3332 if {$msg eq {}} {
3333 if {$GITGUI_BCK_exists} {
3334 catch {file delete [gitdir GITGUI_BCK]}
3335 set GITGUI_BCK_exists 0
3337 } elseif {$m} {
3338 catch {
3339 set fd [open [gitdir GITGUI_BCK] w]
3340 puts -nonewline $fd $msg
3341 close $fd
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]
3369 } else {
3370 set ui_comm_spell [spellcheck::init \
3371 $spell_fd \
3372 $ui_comm \
3373 $ui_comm_ctxm \
3376 unset -nocomplain spell_cmd spell_fd spell_err spell_dict
3379 lock_index begin-read
3380 if {![winfo ismapped .]} {
3381 wm deiconify .
3383 after 1 {
3384 if {[is_enabled initialamend]} {
3385 force_amend
3386 } else {
3387 do_rescan
3390 if {[is_enabled nocommitmsg]} {
3391 $ui_comm configure -state disabled -background gray
3394 if {[is_enabled multicommit]} {
3395 after 1000 hint_gc
3397 if {[is_enabled retcode]} {
3398 bind . <Destroy> {+terminate_me %W}
3400 if {$picked && [is_config_true gui.autoexplore]} {
3401 do_explore