mingw: Don't ask the user yes/no questions if they can't see the question.
[git/dscho.git] / git-gui / git-gui.sh
blob0d36b56d18b2f17721d4923f6cce35b92cba9604
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 [string map [list (c) \u00a9] {
14 Copyright (c) 2006-2010 Shawn Pearce, et. al.
16 This program is free software; you can redistribute it and/or modify
17 it under the terms of the GNU General Public License as published by
18 the Free Software Foundation; either version 2 of the License, or
19 (at your option) any later version.
21 This program is distributed in the hope that it will be useful,
22 but WITHOUT ANY WARRANTY; without even the implied warranty of
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 GNU General Public License for more details.
26 You should have received a copy of the GNU General Public License
27 along with this program; if not, 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 "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 _gitworktree {}
125 set _isbare {}
126 set _gitexec {}
127 set _githtmldir {}
128 set _reponame {}
129 set _iscygwin {}
130 set _search_path {}
131 set _shellpath {@@SHELL_PATH@@}
133 set _trace [lsearch -exact $argv --trace]
134 if {$_trace >= 0} {
135 set argv [lreplace $argv $_trace $_trace]
136 set _trace 1
137 } else {
138 set _trace 0
141 proc shellpath {} {
142 global _shellpath env
143 if {[string match @@* $_shellpath]} {
144 if {[info exists env(SHELL)]} {
145 return $env(SHELL)
146 } else {
147 return /bin/sh
150 return $_shellpath
153 proc appname {} {
154 global _appname
155 return $_appname
158 proc gitdir {args} {
159 global _gitdir
160 if {$args eq {}} {
161 return $_gitdir
163 return [eval [list file join $_gitdir] $args]
166 proc gitexec {args} {
167 global _gitexec
168 if {$_gitexec eq {}} {
169 if {[catch {set _gitexec [git --exec-path]} err]} {
170 error "Git not installed?\n\n$err"
172 if {[is_Cygwin]} {
173 set _gitexec [exec cygpath \
174 --windows \
175 --absolute \
176 $_gitexec]
177 } else {
178 set _gitexec [file normalize $_gitexec]
181 if {$args eq {}} {
182 return $_gitexec
184 return [eval [list file join $_gitexec] $args]
187 proc githtmldir {args} {
188 global _githtmldir
189 if {$_githtmldir eq {}} {
190 if {[catch {set _githtmldir [git --html-path]}]} {
191 # Git not installed or option not yet supported
192 return {}
194 if {[is_Cygwin]} {
195 set _githtmldir [exec cygpath \
196 --windows \
197 --absolute \
198 $_githtmldir]
199 } else {
200 set _githtmldir [file normalize $_githtmldir]
203 if {$args eq {}} {
204 return $_githtmldir
206 return [eval [list file join $_githtmldir] $args]
209 proc reponame {} {
210 return $::_reponame
213 proc is_MacOSX {} {
214 if {[tk windowingsystem] eq {aqua}} {
215 return 1
217 return 0
220 proc is_Windows {} {
221 if {$::tcl_platform(platform) eq {windows}} {
222 return 1
224 return 0
227 proc is_Cygwin {} {
228 global _iscygwin
229 if {$_iscygwin eq {}} {
230 if {$::tcl_platform(platform) eq {windows}} {
231 if {[catch {set p [exec cygpath --windir]} err]} {
232 set _iscygwin 0
233 } else {
234 set _iscygwin 1
236 } else {
237 set _iscygwin 0
240 return $_iscygwin
243 proc is_enabled {option} {
244 global enabled_options
245 if {[catch {set on $enabled_options($option)}]} {return 0}
246 return $on
249 proc enable_option {option} {
250 global enabled_options
251 set enabled_options($option) 1
254 proc disable_option {option} {
255 global enabled_options
256 set enabled_options($option) 0
259 ######################################################################
261 ## config
263 proc is_many_config {name} {
264 switch -glob -- $name {
265 gui.recentrepo -
266 remote.*.fetch -
267 remote.*.push
268 {return 1}
270 {return 0}
274 proc is_config_true {name} {
275 global repo_config
276 if {[catch {set v $repo_config($name)}]} {
277 return 0
278 } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
279 return 1
280 } else {
281 return 0
285 proc is_config_false {name} {
286 global repo_config
287 if {[catch {set v $repo_config($name)}]} {
288 return 0
289 } elseif {$v eq {false} || $v eq {0} || $v eq {no}} {
290 return 1
291 } else {
292 return 0
296 proc get_config {name} {
297 global repo_config
298 if {[catch {set v $repo_config($name)}]} {
299 return {}
300 } else {
301 return $v
305 proc is_bare {} {
306 global _isbare
307 global _gitdir
308 global _gitworktree
310 if {$_isbare eq {}} {
311 if {[catch {
312 set _bare [git rev-parse --is-bare-repository]
313 switch -- $_bare {
314 true { set _isbare 1 }
315 false { set _isbare 0}
316 default { throw }
318 }]} {
319 if {[is_config_true core.bare]
320 || ($_gitworktree eq {}
321 && [lindex [file split $_gitdir] end] ne {.git})} {
322 set _isbare 1
323 } else {
324 set _isbare 0
328 return $_isbare
331 ######################################################################
333 ## handy utils
335 proc _trace_exec {cmd} {
336 if {!$::_trace} return
337 set d {}
338 foreach v $cmd {
339 if {$d ne {}} {
340 append d { }
342 if {[regexp {[ \t\r\n'"$?*]} $v]} {
343 set v [sq $v]
345 append d $v
347 puts stderr $d
350 #'" fix poor old emacs font-lock mode
352 proc _git_cmd {name} {
353 global _git_cmd_path
355 if {[catch {set v $_git_cmd_path($name)}]} {
356 switch -- $name {
357 version -
358 --version -
359 --exec-path { return [list $::_git $name] }
362 set p [gitexec git-$name$::_search_exe]
363 if {[file exists $p]} {
364 set v [list $p]
365 } elseif {[is_Windows] && [file exists [gitexec git-$name]]} {
366 # Try to determine what sort of magic will make
367 # git-$name go and do its thing, because native
368 # Tcl on Windows doesn't know it.
370 set p [gitexec git-$name]
371 set f [open $p r]
372 set s [gets $f]
373 close $f
375 switch -glob -- [lindex $s 0] {
376 #!*sh { set i sh }
377 #!*perl { set i perl }
378 #!*python { set i python }
379 default { error "git-$name is not supported: $s" }
382 upvar #0 _$i interp
383 if {![info exists interp]} {
384 set interp [_which $i]
386 if {$interp eq {}} {
387 error "git-$name requires $i (not in PATH)"
389 set v [concat [list $interp] [lrange $s 1 end] [list $p]]
390 } else {
391 # Assume it is builtin to git somehow and we
392 # aren't actually able to see a file for it.
394 set v [list $::_git $name]
396 set _git_cmd_path($name) $v
398 return $v
401 proc _which {what args} {
402 global env _search_exe _search_path
404 if {$_search_path eq {}} {
405 if {[is_Cygwin] && [regexp {^(/|\.:)} $env(PATH)]} {
406 set _search_path [split [exec cygpath \
407 --windows \
408 --path \
409 --absolute \
410 $env(PATH)] {;}]
411 set _search_exe .exe
412 } elseif {[is_Windows]} {
413 set gitguidir [file dirname [info script]]
414 regsub -all ";" $gitguidir "\\;" gitguidir
415 set env(PATH) "$gitguidir;$env(PATH)"
416 set _search_path [split $env(PATH) {;}]
417 set _search_exe .exe
418 } else {
419 set _search_path [split $env(PATH) :]
420 set _search_exe {}
424 if {[is_Windows] && [lsearch -exact $args -script] >= 0} {
425 set suffix {}
426 } else {
427 set suffix $_search_exe
430 foreach p $_search_path {
431 set p [file join $p $what$suffix]
432 if {[file exists $p]} {
433 return [file normalize $p]
436 return {}
439 proc _lappend_nice {cmd_var} {
440 global _nice
441 upvar $cmd_var cmd
443 if {![info exists _nice]} {
444 set _nice [_which nice]
445 if {[catch {exec $_nice git version}]} {
446 set _nice {}
449 if {$_nice ne {}} {
450 lappend cmd $_nice
454 proc git {args} {
455 set opt [list]
457 while {1} {
458 switch -- [lindex $args 0] {
459 --nice {
460 _lappend_nice opt
463 default {
464 break
469 set args [lrange $args 1 end]
472 set cmdp [_git_cmd [lindex $args 0]]
473 set args [lrange $args 1 end]
475 _trace_exec [concat $opt $cmdp $args]
476 set result [eval exec $opt $cmdp $args]
477 if {$::_trace} {
478 puts stderr "< $result"
480 return $result
483 proc _open_stdout_stderr {cmd} {
484 _trace_exec $cmd
485 if {[catch {
486 set fd [open [concat [list | ] $cmd] r]
487 } err]} {
488 if { [lindex $cmd end] eq {2>@1}
489 && $err eq {can not find channel named "1"}
491 # Older versions of Tcl 8.4 don't have this 2>@1 IO
492 # redirect operator. Fallback to |& cat for those.
493 # The command was not actually started, so its safe
494 # to try to start it a second time.
496 set fd [open [concat \
497 [list | ] \
498 [lrange $cmd 0 end-1] \
499 [list |& cat] \
500 ] r]
501 } else {
502 error $err
505 fconfigure $fd -eofchar {}
506 return $fd
509 proc git_read {args} {
510 set opt [list]
512 while {1} {
513 switch -- [lindex $args 0] {
514 --nice {
515 _lappend_nice opt
518 --stderr {
519 lappend args 2>@1
522 default {
523 break
528 set args [lrange $args 1 end]
531 set cmdp [_git_cmd [lindex $args 0]]
532 set args [lrange $args 1 end]
534 return [_open_stdout_stderr [concat $opt $cmdp $args]]
537 proc git_write {args} {
538 set opt [list]
540 while {1} {
541 switch -- [lindex $args 0] {
542 --nice {
543 _lappend_nice opt
546 default {
547 break
552 set args [lrange $args 1 end]
555 set cmdp [_git_cmd [lindex $args 0]]
556 set args [lrange $args 1 end]
558 _trace_exec [concat $opt $cmdp $args]
559 return [open [concat [list | ] $opt $cmdp $args] w]
562 proc githook_read {hook_name args} {
563 set pchook [gitdir hooks $hook_name]
564 lappend args 2>@1
566 # On Windows [file executable] might lie so we need to ask
567 # the shell if the hook is executable. Yes that's annoying.
569 if {[is_Windows]} {
570 upvar #0 _sh interp
571 if {![info exists interp]} {
572 set interp [_which sh]
574 if {$interp eq {}} {
575 error "hook execution requires sh (not in PATH)"
578 set scr {if test -x "$1";then exec "$@";fi}
579 set sh_c [list $interp -c $scr $interp $pchook]
580 return [_open_stdout_stderr [concat $sh_c $args]]
583 if {[file executable $pchook]} {
584 return [_open_stdout_stderr [concat [list $pchook] $args]]
587 return {}
590 proc kill_file_process {fd} {
591 set process [pid $fd]
593 catch {
594 if {[is_Windows]} {
595 # Use a Cygwin-specific flag to allow killing
596 # native Windows processes
597 exec kill -f $process
598 } else {
599 exec kill $process
604 proc gitattr {path attr default} {
605 if {[catch {set r [git check-attr $attr -- $path]}]} {
606 set r unspecified
607 } else {
608 set r [join [lrange [split $r :] 2 end] :]
609 regsub {^ } $r {} r
611 if {$r eq {unspecified}} {
612 return $default
614 return $r
617 proc sq {value} {
618 regsub -all ' $value "'\\''" value
619 return "'$value'"
622 proc load_current_branch {} {
623 global current_branch is_detached
625 set fd [open [gitdir HEAD] r]
626 if {[gets $fd ref] < 1} {
627 set ref {}
629 close $fd
631 set pfx {ref: refs/heads/}
632 set len [string length $pfx]
633 if {[string equal -length $len $pfx $ref]} {
634 # We're on a branch. It might not exist. But
635 # HEAD looks good enough to be a branch.
637 set current_branch [string range $ref $len end]
638 set is_detached 0
639 } else {
640 # Assume this is a detached head.
642 set current_branch HEAD
643 set is_detached 1
647 auto_load tk_optionMenu
648 rename tk_optionMenu real__tkOptionMenu
649 proc tk_optionMenu {w varName args} {
650 set m [eval real__tkOptionMenu $w $varName $args]
651 $m configure -font font_ui
652 $w configure -font font_ui
653 return $m
656 proc rmsel_tag {text} {
657 $text tag conf sel \
658 -background [$text cget -background] \
659 -foreground [$text cget -foreground] \
660 -borderwidth 0
661 $text tag conf in_sel -background lightgray
662 bind $text <Motion> break
663 return $text
666 wm withdraw .
667 set root_exists 0
668 bind . <Visibility> {
669 bind . <Visibility> {}
670 set root_exists 1
673 if {[is_Windows]} {
674 wm iconbitmap . -default $oguilib/git-gui.ico
675 set ::tk::AlwaysShowSelection 1
677 # Spoof an X11 display for SSH
678 if {![info exists env(DISPLAY)]} {
679 set env(DISPLAY) :9999
681 } else {
682 catch {
683 image create photo gitlogo -width 16 -height 16
685 gitlogo put #33CC33 -to 7 0 9 2
686 gitlogo put #33CC33 -to 4 2 12 4
687 gitlogo put #33CC33 -to 7 4 9 6
688 gitlogo put #CC3333 -to 4 6 12 8
689 gitlogo put gray26 -to 4 9 6 10
690 gitlogo put gray26 -to 3 10 6 12
691 gitlogo put gray26 -to 8 9 13 11
692 gitlogo put gray26 -to 8 11 10 12
693 gitlogo put gray26 -to 11 11 13 14
694 gitlogo put gray26 -to 3 12 5 14
695 gitlogo put gray26 -to 5 13
696 gitlogo put gray26 -to 10 13
697 gitlogo put gray26 -to 4 14 12 15
698 gitlogo put gray26 -to 5 15 11 16
699 gitlogo redither
701 wm iconphoto . -default gitlogo
705 ######################################################################
707 ## config defaults
709 set cursor_ptr arrow
710 font create font_ui
711 if {[lsearch -exact [font names] TkDefaultFont] != -1} {
712 eval [linsert [font actual TkDefaultFont] 0 font configure font_ui]
713 eval [linsert [font actual TkFixedFont] 0 font create font_diff]
714 } else {
715 font create font_diff -family Courier -size 10
716 catch {
717 label .dummy
718 eval font configure font_ui [font actual [.dummy cget -font]]
719 destroy .dummy
723 font create font_uiitalic
724 font create font_uibold
725 font create font_diffbold
726 font create font_diffitalic
728 foreach class {Button Checkbutton Entry Label
729 Labelframe Listbox Message
730 Radiobutton Spinbox Text} {
731 option add *$class.font font_ui
733 if {![is_MacOSX]} {
734 option add *Menu.font font_ui
735 option add *Entry.borderWidth 1 startupFile
736 option add *Entry.relief sunken startupFile
737 option add *RadioButton.anchor w startupFile
739 unset class
741 if {[is_Windows] || [is_MacOSX]} {
742 option add *Menu.tearOff 0
745 if {[is_MacOSX]} {
746 set M1B M1
747 set M1T Cmd
748 } else {
749 set M1B Control
750 set M1T Ctrl
753 proc bind_button3 {w cmd} {
754 bind $w <Any-Button-3> $cmd
755 if {[is_MacOSX]} {
756 # Mac OS X sends Button-2 on right click through three-button mouse,
757 # or through trackpad right-clicking (two-finger touch + click).
758 bind $w <Any-Button-2> $cmd
759 bind $w <Control-Button-1> $cmd
763 proc apply_config {} {
764 global repo_config font_descs
766 foreach option $font_descs {
767 set name [lindex $option 0]
768 set font [lindex $option 1]
769 if {[catch {
770 set need_weight 1
771 foreach {cn cv} $repo_config(gui.$name) {
772 if {$cn eq {-weight}} {
773 set need_weight 0
775 font configure $font $cn $cv
777 if {$need_weight} {
778 font configure $font -weight normal
780 } err]} {
781 error_popup [strcat [mc "Invalid font specified in %s:" "gui.$name"] "\n\n$err"]
783 foreach {cn cv} [font configure $font] {
784 font configure ${font}bold $cn $cv
785 font configure ${font}italic $cn $cv
787 font configure ${font}bold -weight bold
788 font configure ${font}italic -slant italic
791 global use_ttk NS
792 set use_ttk 0
793 set NS {}
794 if {$repo_config(gui.usettk)} {
795 set use_ttk [package vsatisfies [package provide Tk] 8.5]
796 if {$use_ttk} {
797 set NS ttk
798 bind [winfo class .] <<ThemeChanged>> [list InitTheme]
799 pave_toplevel .
804 set default_config(branch.autosetupmerge) true
805 set default_config(merge.tool) {}
806 set default_config(mergetool.keepbackup) true
807 set default_config(merge.diffstat) true
808 set default_config(merge.summary) false
809 set default_config(merge.verbosity) 2
810 set default_config(user.name) {}
811 set default_config(user.email) {}
813 set default_config(gui.encoding) [encoding system]
814 set default_config(gui.matchtrackingbranch) false
815 set default_config(gui.textconv) true
816 set default_config(gui.pruneduringfetch) false
817 set default_config(gui.trustmtime) false
818 set default_config(gui.fastcopyblame) false
819 set default_config(gui.copyblamethreshold) 40
820 set default_config(gui.blamehistoryctx) 7
821 set default_config(gui.diffcontext) 5
822 set default_config(gui.commitmsgwidth) 75
823 set default_config(gui.newbranchtemplate) {}
824 set default_config(gui.spellingdictionary) {}
825 set default_config(gui.fontui) [font configure font_ui]
826 set default_config(gui.fontdiff) [font configure font_diff]
827 # TODO: this option should be added to the git-config documentation
828 set default_config(gui.maxfilesdisplayed) 5000
829 set default_config(gui.usettk) 1
830 set font_descs {
831 {fontui font_ui {mc "Main Font"}}
832 {fontdiff font_diff {mc "Diff/Console Font"}}
835 ######################################################################
837 ## find git
839 set _git [_which git]
840 if {$_git eq {}} {
841 catch {wm withdraw .}
842 tk_messageBox \
843 -icon error \
844 -type ok \
845 -title [mc "git-gui: fatal error"] \
846 -message [mc "Cannot find git in PATH."]
847 exit 1
850 ######################################################################
852 ## version check
854 if {[catch {set _git_version [git --version]} err]} {
855 catch {wm withdraw .}
856 tk_messageBox \
857 -icon error \
858 -type ok \
859 -title [mc "git-gui: fatal error"] \
860 -message "Cannot determine Git version:
862 $err
864 [appname] requires Git 1.5.0 or later."
865 exit 1
867 if {![regsub {^git version } $_git_version {} _git_version]} {
868 catch {wm withdraw .}
869 tk_messageBox \
870 -icon error \
871 -type ok \
872 -title [mc "git-gui: fatal error"] \
873 -message [strcat [mc "Cannot parse Git version string:"] "\n\n$_git_version"]
874 exit 1
877 set _real_git_version $_git_version
878 regsub -- {[\-\.]dirty$} $_git_version {} _git_version
879 regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version
880 regsub {\.[a-zA-Z]+\.?[0-9]+$} $_git_version {} _git_version
881 regsub {\.GIT$} $_git_version {} _git_version
882 regsub {\.[a-zA-Z]+\.?[0-9]+$} $_git_version {} _git_version
884 if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} {
885 catch {wm withdraw .}
886 if {[tk_messageBox \
887 -icon warning \
888 -type yesno \
889 -default no \
890 -title "[appname]: warning" \
891 -message [mc "Git version cannot be determined.
893 %s claims it is version '%s'.
895 %s requires at least Git 1.5.0 or later.
897 Assume '%s' is version 1.5.0?
898 " $_git $_real_git_version [appname] $_real_git_version]] eq {yes}} {
899 set _git_version 1.5.0
900 } else {
901 exit 1
904 unset _real_git_version
906 proc git-version {args} {
907 global _git_version
909 switch [llength $args] {
911 return $_git_version
915 set op [lindex $args 0]
916 set vr [lindex $args 1]
917 set cm [package vcompare $_git_version $vr]
918 return [expr $cm $op 0]
922 set type [lindex $args 0]
923 set name [lindex $args 1]
924 set parm [lindex $args 2]
925 set body [lindex $args 3]
927 if {($type ne {proc} && $type ne {method})} {
928 error "Invalid arguments to git-version"
930 if {[llength $body] < 2 || [lindex $body end-1] ne {default}} {
931 error "Last arm of $type $name must be default"
934 foreach {op vr cb} [lrange $body 0 end-2] {
935 if {[git-version $op $vr]} {
936 return [uplevel [list $type $name $parm $cb]]
940 return [uplevel [list $type $name $parm [lindex $body end]]]
943 default {
944 error "git-version >= x"
950 if {[git-version < 1.5]} {
951 catch {wm withdraw .}
952 tk_messageBox \
953 -icon error \
954 -type ok \
955 -title [mc "git-gui: fatal error"] \
956 -message "[appname] requires Git 1.5.0 or later.
958 You are using [git-version]:
960 [git --version]"
961 exit 1
964 ######################################################################
966 ## configure our library
968 set idx [file join $oguilib tclIndex]
969 if {[catch {set fd [open $idx r]} err]} {
970 catch {wm withdraw .}
971 tk_messageBox \
972 -icon error \
973 -type ok \
974 -title [mc "git-gui: fatal error"] \
975 -message $err
976 exit 1
978 if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
979 set idx [list]
980 while {[gets $fd n] >= 0} {
981 if {$n ne {} && ![string match #* $n]} {
982 lappend idx $n
985 } else {
986 set idx {}
988 close $fd
990 if {$idx ne {}} {
991 set loaded [list]
992 foreach p $idx {
993 if {[lsearch -exact $loaded $p] >= 0} continue
994 source [file join $oguilib $p]
995 lappend loaded $p
997 unset loaded p
998 } else {
999 set auto_path [concat [list $oguilib] $auto_path]
1001 unset -nocomplain idx fd
1003 ######################################################################
1005 ## config file parsing
1007 git-version proc _parse_config {arr_name args} {
1008 >= 1.5.3 {
1009 upvar $arr_name arr
1010 array unset arr
1011 set buf {}
1012 catch {
1013 set fd_rc [eval \
1014 [list git_read config] \
1015 $args \
1016 [list --null --list]]
1017 fconfigure $fd_rc -translation binary
1018 set buf [read $fd_rc]
1019 close $fd_rc
1021 foreach line [split $buf "\0"] {
1022 if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} {
1023 if {[is_many_config $name]} {
1024 lappend arr($name) $value
1025 } else {
1026 set arr($name) $value
1031 default {
1032 upvar $arr_name arr
1033 array unset arr
1034 catch {
1035 set fd_rc [eval [list git_read config --list] $args]
1036 while {[gets $fd_rc line] >= 0} {
1037 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
1038 if {[is_many_config $name]} {
1039 lappend arr($name) $value
1040 } else {
1041 set arr($name) $value
1045 close $fd_rc
1050 proc load_config {include_global} {
1051 global repo_config global_config system_config default_config
1053 if {$include_global} {
1054 _parse_config system_config --system
1055 _parse_config global_config --global
1057 _parse_config repo_config
1059 foreach name [array names default_config] {
1060 if {[catch {set v $system_config($name)}]} {
1061 set system_config($name) $default_config($name)
1064 foreach name [array names system_config] {
1065 if {[catch {set v $global_config($name)}]} {
1066 set global_config($name) $system_config($name)
1068 if {[catch {set v $repo_config($name)}]} {
1069 set repo_config($name) $system_config($name)
1074 ######################################################################
1076 ## feature option selection
1078 if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} {
1079 unset _junk
1080 } else {
1081 set subcommand gui
1083 if {$subcommand eq {gui.sh}} {
1084 set subcommand gui
1086 if {$subcommand eq {gui} && [llength $argv] > 0} {
1087 set subcommand [lindex $argv 0]
1088 set argv [lrange $argv 1 end]
1091 enable_option multicommit
1092 enable_option branch
1093 enable_option transport
1094 disable_option bare
1096 switch -- $subcommand {
1097 browser -
1098 blame {
1099 enable_option bare
1101 disable_option multicommit
1102 disable_option branch
1103 disable_option transport
1105 citool {
1106 enable_option singlecommit
1107 enable_option retcode
1109 disable_option multicommit
1110 disable_option branch
1111 disable_option transport
1113 while {[llength $argv] > 0} {
1114 set a [lindex $argv 0]
1115 switch -- $a {
1116 --amend {
1117 enable_option initialamend
1119 --nocommit {
1120 enable_option nocommit
1121 enable_option nocommitmsg
1123 --commitmsg {
1124 disable_option nocommitmsg
1126 default {
1127 break
1131 set argv [lrange $argv 1 end]
1136 ######################################################################
1138 ## execution environment
1140 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
1142 # Suggest our implementation of askpass, if none is set
1143 if {![info exists env(SSH_ASKPASS)]} {
1144 set env(SSH_ASKPASS) [gitexec git-gui--askpass]
1146 if {![info exists env(GIT_ASK_YESNO)]} {
1147 set env(GIT_ASK_YESNO) [gitexec git-gui--askyesno]
1150 ######################################################################
1152 ## repository setup
1154 set picked 0
1155 if {[catch {
1156 set _gitdir $env(GIT_DIR)
1157 set _prefix {}
1159 && [catch {
1160 # beware that from the .git dir this sets _gitdir to .
1161 # and _prefix to the empty string
1162 set _gitdir [git rev-parse --git-dir]
1163 set _prefix [git rev-parse --show-prefix]
1164 } err]} {
1165 load_config 1
1166 apply_config
1167 choose_repository::pick
1168 set picked 1
1171 # we expand the _gitdir when it's just a single dot (i.e. when we're being
1172 # run from the .git dir itself) lest the routines to find the worktree
1173 # get confused
1174 if {$_gitdir eq "."} {
1175 set _gitdir [pwd]
1178 if {![file isdirectory $_gitdir] && [is_Cygwin]} {
1179 catch {set _gitdir [exec cygpath --windows $_gitdir]}
1181 if {![file isdirectory $_gitdir]} {
1182 catch {wm withdraw .}
1183 error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"]
1184 exit 1
1186 # _gitdir exists, so try loading the config
1187 load_config 0
1188 apply_config
1189 # try to set work tree from environment, falling back to core.worktree
1190 if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} {
1191 set _gitworktree [get_config core.worktree]
1192 if {$_gitworktree eq ""} {
1193 set _gitworktree [file dirname [file normalize $_gitdir]]
1196 if {$_prefix ne {}} {
1197 if {$_gitworktree eq {}} {
1198 regsub -all {[^/]+/} $_prefix ../ cdup
1199 } else {
1200 set cdup $_gitworktree
1202 if {[catch {cd $cdup} err]} {
1203 catch {wm withdraw .}
1204 error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"]
1205 exit 1
1207 set _gitworktree [pwd]
1208 unset cdup
1209 } elseif {![is_enabled bare]} {
1210 if {[is_bare]} {
1211 catch {wm withdraw .}
1212 error_popup [strcat [mc "Cannot use bare repository:"] "\n\n$_gitdir"]
1213 exit 1
1215 if {$_gitworktree eq {}} {
1216 set _gitworktree [file dirname $_gitdir]
1218 if {[catch {cd $_gitworktree} err]} {
1219 catch {wm withdraw .}
1220 error_popup [strcat [mc "No working directory"] " $_gitworktree:\n\n$err"]
1221 exit 1
1223 set _gitworktree [pwd]
1225 set _reponame [file split [file normalize $_gitdir]]
1226 if {[lindex $_reponame end] eq {.git}} {
1227 set _reponame [lindex $_reponame end-1]
1228 } else {
1229 set _reponame [lindex $_reponame end]
1232 ######################################################################
1234 ## global init
1236 set current_diff_path {}
1237 set current_diff_side {}
1238 set diff_actions [list]
1240 set HEAD {}
1241 set PARENT {}
1242 set MERGE_HEAD [list]
1243 set commit_type {}
1244 set empty_tree {}
1245 set current_branch {}
1246 set is_detached 0
1247 set current_diff_path {}
1248 set is_3way_diff 0
1249 set is_submodule_diff 0
1250 set is_conflict_diff 0
1251 set selected_commit_type new
1252 set diff_empty_count 0
1254 set nullid "0000000000000000000000000000000000000000"
1255 set nullid2 "0000000000000000000000000000000000000001"
1257 ######################################################################
1259 ## task management
1261 set rescan_active 0
1262 set diff_active 0
1263 set last_clicked {}
1265 set disable_on_lock [list]
1266 set index_lock_type none
1268 proc lock_index {type} {
1269 global index_lock_type disable_on_lock
1271 if {$index_lock_type eq {none}} {
1272 set index_lock_type $type
1273 foreach w $disable_on_lock {
1274 uplevel #0 $w disabled
1276 return 1
1277 } elseif {$index_lock_type eq "begin-$type"} {
1278 set index_lock_type $type
1279 return 1
1281 return 0
1284 proc unlock_index {} {
1285 global index_lock_type disable_on_lock
1287 set index_lock_type none
1288 foreach w $disable_on_lock {
1289 uplevel #0 $w normal
1293 ######################################################################
1295 ## status
1297 proc repository_state {ctvar hdvar mhvar} {
1298 global current_branch
1299 upvar $ctvar ct $hdvar hd $mhvar mh
1301 set mh [list]
1303 load_current_branch
1304 if {[catch {set hd [git rev-parse --verify HEAD]}]} {
1305 set hd {}
1306 set ct initial
1307 return
1310 set merge_head [gitdir MERGE_HEAD]
1311 if {[file exists $merge_head]} {
1312 set ct merge
1313 set fd_mh [open $merge_head r]
1314 while {[gets $fd_mh line] >= 0} {
1315 lappend mh $line
1317 close $fd_mh
1318 return
1321 set ct normal
1324 proc PARENT {} {
1325 global PARENT empty_tree
1327 set p [lindex $PARENT 0]
1328 if {$p ne {}} {
1329 return $p
1331 if {$empty_tree eq {}} {
1332 set empty_tree [git mktree << {}]
1334 return $empty_tree
1337 proc force_amend {} {
1338 global selected_commit_type
1339 global HEAD PARENT MERGE_HEAD commit_type
1341 repository_state newType newHEAD newMERGE_HEAD
1342 set HEAD $newHEAD
1343 set PARENT $newHEAD
1344 set MERGE_HEAD $newMERGE_HEAD
1345 set commit_type $newType
1347 set selected_commit_type amend
1348 do_select_commit_type
1351 proc rescan {after {honor_trustmtime 1}} {
1352 global HEAD PARENT MERGE_HEAD commit_type
1353 global ui_index ui_workdir ui_comm
1354 global rescan_active file_states
1355 global repo_config
1357 if {$rescan_active > 0 || ![lock_index read]} return
1359 repository_state newType newHEAD newMERGE_HEAD
1360 if {[string match amend* $commit_type]
1361 && $newType eq {normal}
1362 && $newHEAD eq $HEAD} {
1363 } else {
1364 set HEAD $newHEAD
1365 set PARENT $newHEAD
1366 set MERGE_HEAD $newMERGE_HEAD
1367 set commit_type $newType
1370 array unset file_states
1372 if {!$::GITGUI_BCK_exists &&
1373 (![$ui_comm edit modified]
1374 || [string trim [$ui_comm get 0.0 end]] eq {})} {
1375 if {[string match amend* $commit_type]} {
1376 } elseif {[load_message GITGUI_MSG]} {
1377 } elseif {[run_prepare_commit_msg_hook]} {
1378 } elseif {[load_message MERGE_MSG]} {
1379 } elseif {[load_message SQUASH_MSG]} {
1381 $ui_comm edit reset
1382 $ui_comm edit modified false
1385 if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
1386 rescan_stage2 {} $after
1387 } else {
1388 set rescan_active 1
1389 ui_status [mc "Refreshing file status..."]
1390 set fd_rf [git_read update-index \
1391 -q \
1392 --unmerged \
1393 --ignore-missing \
1394 --refresh \
1396 fconfigure $fd_rf -blocking 0 -translation binary
1397 fileevent $fd_rf readable \
1398 [list rescan_stage2 $fd_rf $after]
1402 if {[is_Cygwin]} {
1403 set is_git_info_exclude {}
1404 proc have_info_exclude {} {
1405 global is_git_info_exclude
1407 if {$is_git_info_exclude eq {}} {
1408 if {[catch {exec test -f [gitdir info exclude]}]} {
1409 set is_git_info_exclude 0
1410 } else {
1411 set is_git_info_exclude 1
1414 return $is_git_info_exclude
1416 } else {
1417 proc have_info_exclude {} {
1418 return [file readable [gitdir info exclude]]
1422 proc rescan_stage2 {fd after} {
1423 global rescan_active buf_rdi buf_rdf buf_rlo
1425 if {$fd ne {}} {
1426 read $fd
1427 if {![eof $fd]} return
1428 close $fd
1431 set ls_others [list --exclude-per-directory=.gitignore]
1432 if {[have_info_exclude]} {
1433 lappend ls_others "--exclude-from=[gitdir info exclude]"
1435 set user_exclude [get_config core.excludesfile]
1436 if {$user_exclude ne {} && [file readable $user_exclude]} {
1437 lappend ls_others "--exclude-from=$user_exclude"
1440 set buf_rdi {}
1441 set buf_rdf {}
1442 set buf_rlo {}
1444 set rescan_active 3
1445 ui_status [mc "Scanning for modified files ..."]
1446 set fd_di [git_read diff-index --cached -z [PARENT]]
1447 set fd_df [git_read diff-files -z]
1448 set fd_lo [eval git_read ls-files --others -z $ls_others]
1450 fconfigure $fd_di -blocking 0 -translation binary -encoding binary
1451 fconfigure $fd_df -blocking 0 -translation binary -encoding binary
1452 fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
1453 fileevent $fd_di readable [list read_diff_index $fd_di $after]
1454 fileevent $fd_df readable [list read_diff_files $fd_df $after]
1455 fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
1458 proc load_message {file} {
1459 global ui_comm
1461 set f [gitdir $file]
1462 if {[file isfile $f]} {
1463 if {[catch {set fd [open $f r]}]} {
1464 return 0
1466 fconfigure $fd -eofchar {}
1467 set content [string trim [read $fd]]
1468 close $fd
1469 regsub -all -line {[ \r\t]+$} $content {} content
1470 $ui_comm delete 0.0 end
1471 $ui_comm insert end $content
1472 return 1
1474 return 0
1477 proc run_prepare_commit_msg_hook {} {
1478 global pch_error
1480 # prepare-commit-msg requires PREPARE_COMMIT_MSG exist. From git-gui
1481 # it will be .git/MERGE_MSG (merge), .git/SQUASH_MSG (squash), or an
1482 # empty file but existant file.
1484 set fd_pcm [open [gitdir PREPARE_COMMIT_MSG] a]
1486 if {[file isfile [gitdir MERGE_MSG]]} {
1487 set pcm_source "merge"
1488 set fd_mm [open [gitdir MERGE_MSG] r]
1489 puts -nonewline $fd_pcm [read $fd_mm]
1490 close $fd_mm
1491 } elseif {[file isfile [gitdir SQUASH_MSG]]} {
1492 set pcm_source "squash"
1493 set fd_sm [open [gitdir SQUASH_MSG] r]
1494 puts -nonewline $fd_pcm [read $fd_sm]
1495 close $fd_sm
1496 } else {
1497 set pcm_source ""
1500 close $fd_pcm
1502 set fd_ph [githook_read prepare-commit-msg \
1503 [gitdir PREPARE_COMMIT_MSG] $pcm_source]
1504 if {$fd_ph eq {}} {
1505 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1506 return 0;
1509 ui_status [mc "Calling prepare-commit-msg hook..."]
1510 set pch_error {}
1512 fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
1513 fileevent $fd_ph readable \
1514 [list prepare_commit_msg_hook_wait $fd_ph]
1516 return 1;
1519 proc prepare_commit_msg_hook_wait {fd_ph} {
1520 global pch_error
1522 append pch_error [read $fd_ph]
1523 fconfigure $fd_ph -blocking 1
1524 if {[eof $fd_ph]} {
1525 if {[catch {close $fd_ph}]} {
1526 ui_status [mc "Commit declined by prepare-commit-msg hook."]
1527 hook_failed_popup prepare-commit-msg $pch_error
1528 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1529 exit 1
1530 } else {
1531 load_message PREPARE_COMMIT_MSG
1533 set pch_error {}
1534 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1535 return
1537 fconfigure $fd_ph -blocking 0
1538 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1541 proc read_diff_index {fd after} {
1542 global buf_rdi
1544 append buf_rdi [read $fd]
1545 set c 0
1546 set n [string length $buf_rdi]
1547 while {$c < $n} {
1548 set z1 [string first "\0" $buf_rdi $c]
1549 if {$z1 == -1} break
1550 incr z1
1551 set z2 [string first "\0" $buf_rdi $z1]
1552 if {$z2 == -1} break
1554 incr c
1555 set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
1556 set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
1557 merge_state \
1558 [encoding convertfrom $p] \
1559 [lindex $i 4]? \
1560 [list [lindex $i 0] [lindex $i 2]] \
1561 [list]
1562 set c $z2
1563 incr c
1565 if {$c < $n} {
1566 set buf_rdi [string range $buf_rdi $c end]
1567 } else {
1568 set buf_rdi {}
1571 rescan_done $fd buf_rdi $after
1574 proc read_diff_files {fd after} {
1575 global buf_rdf
1577 append buf_rdf [read $fd]
1578 set c 0
1579 set n [string length $buf_rdf]
1580 while {$c < $n} {
1581 set z1 [string first "\0" $buf_rdf $c]
1582 if {$z1 == -1} break
1583 incr z1
1584 set z2 [string first "\0" $buf_rdf $z1]
1585 if {$z2 == -1} break
1587 incr c
1588 set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
1589 set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
1590 merge_state \
1591 [encoding convertfrom $p] \
1592 ?[lindex $i 4] \
1593 [list] \
1594 [list [lindex $i 0] [lindex $i 2]]
1595 set c $z2
1596 incr c
1598 if {$c < $n} {
1599 set buf_rdf [string range $buf_rdf $c end]
1600 } else {
1601 set buf_rdf {}
1604 rescan_done $fd buf_rdf $after
1607 proc read_ls_others {fd after} {
1608 global buf_rlo
1610 append buf_rlo [read $fd]
1611 set pck [split $buf_rlo "\0"]
1612 set buf_rlo [lindex $pck end]
1613 foreach p [lrange $pck 0 end-1] {
1614 set p [encoding convertfrom $p]
1615 if {[string index $p end] eq {/}} {
1616 set p [string range $p 0 end-1]
1618 merge_state $p ?O
1620 rescan_done $fd buf_rlo $after
1623 proc rescan_done {fd buf after} {
1624 global rescan_active current_diff_path
1625 global file_states repo_config
1626 upvar $buf to_clear
1628 if {![eof $fd]} return
1629 set to_clear {}
1630 close $fd
1631 if {[incr rescan_active -1] > 0} return
1633 prune_selection
1634 unlock_index
1635 display_all_files
1636 if {$current_diff_path ne {}} { reshow_diff $after }
1637 if {$current_diff_path eq {}} { select_first_diff $after }
1640 proc prune_selection {} {
1641 global file_states selected_paths
1643 foreach path [array names selected_paths] {
1644 if {[catch {set still_here $file_states($path)}]} {
1645 unset selected_paths($path)
1650 ######################################################################
1652 ## ui helpers
1654 proc mapicon {w state path} {
1655 global all_icons
1657 if {[catch {set r $all_icons($state$w)}]} {
1658 puts "error: no icon for $w state={$state} $path"
1659 return file_plain
1661 return $r
1664 proc mapdesc {state path} {
1665 global all_descs
1667 if {[catch {set r $all_descs($state)}]} {
1668 puts "error: no desc for state={$state} $path"
1669 return $state
1671 return $r
1674 proc ui_status {msg} {
1675 global main_status
1676 if {[info exists main_status]} {
1677 $main_status show $msg
1681 proc ui_ready {{test {}}} {
1682 global main_status
1683 if {[info exists main_status]} {
1684 $main_status show [mc "Ready."] $test
1688 proc escape_path {path} {
1689 regsub -all {\\} $path "\\\\" path
1690 regsub -all "\n" $path "\\n" path
1691 return $path
1694 proc short_path {path} {
1695 return [escape_path [lindex [file split $path] end]]
1698 set next_icon_id 0
1699 set null_sha1 [string repeat 0 40]
1701 proc merge_state {path new_state {head_info {}} {index_info {}}} {
1702 global file_states next_icon_id null_sha1
1704 set s0 [string index $new_state 0]
1705 set s1 [string index $new_state 1]
1707 if {[catch {set info $file_states($path)}]} {
1708 set state __
1709 set icon n[incr next_icon_id]
1710 } else {
1711 set state [lindex $info 0]
1712 set icon [lindex $info 1]
1713 if {$head_info eq {}} {set head_info [lindex $info 2]}
1714 if {$index_info eq {}} {set index_info [lindex $info 3]}
1717 if {$s0 eq {?}} {set s0 [string index $state 0]} \
1718 elseif {$s0 eq {_}} {set s0 _}
1720 if {$s1 eq {?}} {set s1 [string index $state 1]} \
1721 elseif {$s1 eq {_}} {set s1 _}
1723 if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
1724 set head_info [list 0 $null_sha1]
1725 } elseif {$s0 ne {_} && [string index $state 0] eq {_}
1726 && $head_info eq {}} {
1727 set head_info $index_info
1728 } elseif {$s0 eq {_} && [string index $state 0] ne {_}} {
1729 set index_info $head_info
1730 set head_info {}
1733 set file_states($path) [list $s0$s1 $icon \
1734 $head_info $index_info \
1736 return $state
1739 proc display_file_helper {w path icon_name old_m new_m} {
1740 global file_lists
1742 if {$new_m eq {_}} {
1743 set lno [lsearch -sorted -exact $file_lists($w) $path]
1744 if {$lno >= 0} {
1745 set file_lists($w) [lreplace $file_lists($w) $lno $lno]
1746 incr lno
1747 $w conf -state normal
1748 $w delete $lno.0 [expr {$lno + 1}].0
1749 $w conf -state disabled
1751 } elseif {$old_m eq {_} && $new_m ne {_}} {
1752 lappend file_lists($w) $path
1753 set file_lists($w) [lsort -unique $file_lists($w)]
1754 set lno [lsearch -sorted -exact $file_lists($w) $path]
1755 incr lno
1756 $w conf -state normal
1757 $w image create $lno.0 \
1758 -align center -padx 5 -pady 1 \
1759 -name $icon_name \
1760 -image [mapicon $w $new_m $path]
1761 $w insert $lno.1 "[escape_path $path]\n"
1762 $w conf -state disabled
1763 } elseif {$old_m ne $new_m} {
1764 $w conf -state normal
1765 $w image conf $icon_name -image [mapicon $w $new_m $path]
1766 $w conf -state disabled
1770 proc display_file {path state} {
1771 global file_states selected_paths
1772 global ui_index ui_workdir
1774 set old_m [merge_state $path $state]
1775 set s $file_states($path)
1776 set new_m [lindex $s 0]
1777 set icon_name [lindex $s 1]
1779 set o [string index $old_m 0]
1780 set n [string index $new_m 0]
1781 if {$o eq {U}} {
1782 set o _
1784 if {$n eq {U}} {
1785 set n _
1787 display_file_helper $ui_index $path $icon_name $o $n
1789 if {[string index $old_m 0] eq {U}} {
1790 set o U
1791 } else {
1792 set o [string index $old_m 1]
1794 if {[string index $new_m 0] eq {U}} {
1795 set n U
1796 } else {
1797 set n [string index $new_m 1]
1799 display_file_helper $ui_workdir $path $icon_name $o $n
1801 if {$new_m eq {__}} {
1802 unset file_states($path)
1803 catch {unset selected_paths($path)}
1807 proc display_all_files_helper {w path icon_name m} {
1808 global file_lists
1810 lappend file_lists($w) $path
1811 set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
1812 $w image create end \
1813 -align center -padx 5 -pady 1 \
1814 -name $icon_name \
1815 -image [mapicon $w $m $path]
1816 $w insert end "[escape_path $path]\n"
1819 set files_warning 0
1820 proc display_all_files {} {
1821 global ui_index ui_workdir
1822 global file_states file_lists
1823 global last_clicked
1824 global files_warning
1826 $ui_index conf -state normal
1827 $ui_workdir conf -state normal
1829 $ui_index delete 0.0 end
1830 $ui_workdir delete 0.0 end
1831 set last_clicked {}
1833 set file_lists($ui_index) [list]
1834 set file_lists($ui_workdir) [list]
1836 set to_display [lsort [array names file_states]]
1837 set display_limit [get_config gui.maxfilesdisplayed]
1838 if {[llength $to_display] > $display_limit} {
1839 if {!$files_warning} {
1840 # do not repeatedly warn:
1841 set files_warning 1
1842 info_popup [mc "Displaying only %s of %s files." \
1843 $display_limit [llength $to_display]]
1845 set to_display [lrange $to_display 0 [expr {$display_limit-1}]]
1847 foreach path $to_display {
1848 set s $file_states($path)
1849 set m [lindex $s 0]
1850 set icon_name [lindex $s 1]
1852 set s [string index $m 0]
1853 if {$s ne {U} && $s ne {_}} {
1854 display_all_files_helper $ui_index $path \
1855 $icon_name $s
1858 if {[string index $m 0] eq {U}} {
1859 set s U
1860 } else {
1861 set s [string index $m 1]
1863 if {$s ne {_}} {
1864 display_all_files_helper $ui_workdir $path \
1865 $icon_name $s
1869 $ui_index conf -state disabled
1870 $ui_workdir conf -state disabled
1873 ######################################################################
1875 ## icons
1877 set filemask {
1878 #define mask_width 14
1879 #define mask_height 15
1880 static unsigned char mask_bits[] = {
1881 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1882 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1883 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1886 image create bitmap file_plain -background white -foreground black -data {
1887 #define plain_width 14
1888 #define plain_height 15
1889 static unsigned char plain_bits[] = {
1890 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1891 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1892 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1893 } -maskdata $filemask
1895 image create bitmap file_mod -background white -foreground blue -data {
1896 #define mod_width 14
1897 #define mod_height 15
1898 static unsigned char mod_bits[] = {
1899 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1900 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1901 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1902 } -maskdata $filemask
1904 image create bitmap file_fulltick -background white -foreground "#007000" -data {
1905 #define file_fulltick_width 14
1906 #define file_fulltick_height 15
1907 static unsigned char file_fulltick_bits[] = {
1908 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1909 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1910 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1911 } -maskdata $filemask
1913 image create bitmap file_question -background white -foreground black -data {
1914 #define file_question_width 14
1915 #define file_question_height 15
1916 static unsigned char file_question_bits[] = {
1917 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1918 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1919 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1920 } -maskdata $filemask
1922 image create bitmap file_removed -background white -foreground red -data {
1923 #define file_removed_width 14
1924 #define file_removed_height 15
1925 static unsigned char file_removed_bits[] = {
1926 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1927 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1928 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1929 } -maskdata $filemask
1931 image create bitmap file_merge -background white -foreground blue -data {
1932 #define file_merge_width 14
1933 #define file_merge_height 15
1934 static unsigned char file_merge_bits[] = {
1935 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1936 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1937 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1938 } -maskdata $filemask
1940 image create bitmap file_statechange -background white -foreground green -data {
1941 #define file_merge_width 14
1942 #define file_merge_height 15
1943 static unsigned char file_statechange_bits[] = {
1944 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x62, 0x10,
1945 0x62, 0x10, 0xba, 0x11, 0xba, 0x11, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10,
1946 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1947 } -maskdata $filemask
1949 set ui_index .vpane.files.index.list
1950 set ui_workdir .vpane.files.workdir.list
1952 set all_icons(_$ui_index) file_plain
1953 set all_icons(A$ui_index) file_plain
1954 set all_icons(M$ui_index) file_fulltick
1955 set all_icons(D$ui_index) file_removed
1956 set all_icons(U$ui_index) file_merge
1957 set all_icons(T$ui_index) file_statechange
1959 set all_icons(_$ui_workdir) file_plain
1960 set all_icons(M$ui_workdir) file_mod
1961 set all_icons(D$ui_workdir) file_question
1962 set all_icons(U$ui_workdir) file_merge
1963 set all_icons(O$ui_workdir) file_plain
1964 set all_icons(T$ui_workdir) file_statechange
1966 set max_status_desc 0
1967 foreach i {
1968 {__ {mc "Unmodified"}}
1970 {_M {mc "Modified, not staged"}}
1971 {M_ {mc "Staged for commit"}}
1972 {MM {mc "Portions staged for commit"}}
1973 {MD {mc "Staged for commit, missing"}}
1975 {_T {mc "File type changed, not staged"}}
1976 {T_ {mc "File type changed, staged"}}
1978 {_O {mc "Untracked, not staged"}}
1979 {A_ {mc "Staged for commit"}}
1980 {AM {mc "Portions staged for commit"}}
1981 {AD {mc "Staged for commit, missing"}}
1983 {_D {mc "Missing"}}
1984 {D_ {mc "Staged for removal"}}
1985 {DO {mc "Staged for removal, still present"}}
1987 {_U {mc "Requires merge resolution"}}
1988 {U_ {mc "Requires merge resolution"}}
1989 {UU {mc "Requires merge resolution"}}
1990 {UM {mc "Requires merge resolution"}}
1991 {UD {mc "Requires merge resolution"}}
1992 {UT {mc "Requires merge resolution"}}
1994 set text [eval [lindex $i 1]]
1995 if {$max_status_desc < [string length $text]} {
1996 set max_status_desc [string length $text]
1998 set all_descs([lindex $i 0]) $text
2000 unset i
2002 ######################################################################
2004 ## util
2006 proc scrollbar2many {list mode args} {
2007 foreach w $list {eval $w $mode $args}
2010 proc many2scrollbar {list mode sb top bottom} {
2011 $sb set $top $bottom
2012 foreach w $list {$w $mode moveto $top}
2015 proc incr_font_size {font {amt 1}} {
2016 set sz [font configure $font -size]
2017 incr sz $amt
2018 font configure $font -size $sz
2019 font configure ${font}bold -size $sz
2020 font configure ${font}italic -size $sz
2023 ######################################################################
2025 ## ui commands
2027 set starting_gitk_msg [mc "Starting gitk... please wait..."]
2029 proc do_gitk {revs {is_submodule false}} {
2030 global current_diff_path file_states current_diff_side ui_index
2031 global _gitworktree
2033 # -- Always start gitk through whatever we were loaded with. This
2034 # lets us bypass using shell process on Windows systems.
2036 set exe [_which gitk -script]
2037 set cmd [list [info nameofexecutable] $exe]
2038 if {$exe eq {}} {
2039 error_popup [mc "Couldn't find gitk in PATH"]
2040 } else {
2041 global env
2043 if {[info exists env(GIT_DIR)]} {
2044 set old_GIT_DIR $env(GIT_DIR)
2045 } else {
2046 set old_GIT_DIR {}
2049 set pwd [pwd]
2051 if {!$is_submodule} {
2052 if {![is_bare]} {
2053 cd $_gitworktree
2055 set env(GIT_DIR) [file normalize [gitdir]]
2056 } else {
2057 cd $current_diff_path
2058 if {$revs eq {--}} {
2059 set s $file_states($current_diff_path)
2060 set old_sha1 {}
2061 set new_sha1 {}
2062 switch -glob -- [lindex $s 0] {
2063 M_ { set old_sha1 [lindex [lindex $s 2] 1] }
2064 _M { set old_sha1 [lindex [lindex $s 3] 1] }
2065 MM {
2066 if {$current_diff_side eq $ui_index} {
2067 set old_sha1 [lindex [lindex $s 2] 1]
2068 set new_sha1 [lindex [lindex $s 3] 1]
2069 } else {
2070 set old_sha1 [lindex [lindex $s 3] 1]
2074 set revs $old_sha1...$new_sha1
2076 if {[info exists env(GIT_DIR)]} {
2077 unset env(GIT_DIR)
2080 eval exec $cmd $revs "--" "--" &
2082 if {$old_GIT_DIR ne {}} {
2083 set env(GIT_DIR) $old_GIT_DIR
2085 cd $pwd
2087 ui_status $::starting_gitk_msg
2088 after 10000 {
2089 ui_ready $starting_gitk_msg
2094 proc do_git_gui {} {
2095 global current_diff_path
2097 # -- Always start git gui through whatever we were loaded with. This
2098 # lets us bypass using shell process on Windows systems.
2100 set exe [list [_which git]]
2101 if {$exe eq {}} {
2102 error_popup [mc "Couldn't find git gui in PATH"]
2103 } else {
2104 global env
2106 if {[info exists env(GIT_DIR)]} {
2107 set old_GIT_DIR $env(GIT_DIR)
2108 unset env(GIT_DIR)
2109 } else {
2110 set old_GIT_DIR {}
2113 set pwd [pwd]
2114 cd $current_diff_path
2116 eval exec $exe gui &
2118 if {$old_GIT_DIR ne {}} {
2119 set env(GIT_DIR) $old_GIT_DIR
2121 cd $pwd
2123 ui_status $::starting_gitk_msg
2124 after 10000 {
2125 ui_ready $starting_gitk_msg
2130 proc do_explore {} {
2131 global _gitworktree
2132 set explorer {}
2133 if {[is_Cygwin] || [is_Windows]} {
2134 set explorer "explorer.exe"
2135 } elseif {[is_MacOSX]} {
2136 set explorer "open"
2137 } else {
2138 # freedesktop.org-conforming system is our best shot
2139 set explorer "xdg-open"
2141 eval exec $explorer [list [file nativename $_gitworktree]] &
2144 set is_quitting 0
2145 set ret_code 1
2147 proc terminate_me {win} {
2148 global ret_code
2149 if {$win ne {.}} return
2150 exit $ret_code
2153 proc do_quit {{rc {1}}} {
2154 global ui_comm is_quitting repo_config commit_type
2155 global GITGUI_BCK_exists GITGUI_BCK_i
2156 global ui_comm_spell
2157 global ret_code use_ttk
2159 if {$is_quitting} return
2160 set is_quitting 1
2162 if {[winfo exists $ui_comm]} {
2163 # -- Stash our current commit buffer.
2165 set save [gitdir GITGUI_MSG]
2166 if {$GITGUI_BCK_exists && ![$ui_comm edit modified]} {
2167 file rename -force [gitdir GITGUI_BCK] $save
2168 set GITGUI_BCK_exists 0
2169 } else {
2170 set msg [string trim [$ui_comm get 0.0 end]]
2171 regsub -all -line {[ \r\t]+$} $msg {} msg
2172 if {(![string match amend* $commit_type]
2173 || [$ui_comm edit modified])
2174 && $msg ne {}} {
2175 catch {
2176 set fd [open $save w]
2177 puts -nonewline $fd $msg
2178 close $fd
2180 } else {
2181 catch {file delete $save}
2185 # -- Cancel our spellchecker if its running.
2187 if {[info exists ui_comm_spell]} {
2188 $ui_comm_spell stop
2191 # -- Remove our editor backup, its not needed.
2193 after cancel $GITGUI_BCK_i
2194 if {$GITGUI_BCK_exists} {
2195 catch {file delete [gitdir GITGUI_BCK]}
2198 # -- Stash our current window geometry into this repository.
2200 set cfg_wmstate [wm state .]
2201 if {[catch {set rc_wmstate $repo_config(gui.wmstate)}]} {
2202 set rc_wmstate {}
2204 if {$cfg_wmstate ne $rc_wmstate} {
2205 catch {git config gui.wmstate $cfg_wmstate}
2207 if {$cfg_wmstate eq {zoomed}} {
2208 # on Windows wm geometry will lie about window
2209 # position (but not size) when window is zoomed
2210 # restore the window before querying wm geometry
2211 wm state . normal
2213 set cfg_geometry [list]
2214 lappend cfg_geometry [wm geometry .]
2215 if {$use_ttk} {
2216 lappend cfg_geometry [.vpane sashpos 0]
2217 lappend cfg_geometry [.vpane.files sashpos 0]
2218 } else {
2219 lappend cfg_geometry [lindex [.vpane sash coord 0] 0]
2220 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 1]
2222 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
2223 set rc_geometry {}
2225 if {$cfg_geometry ne $rc_geometry} {
2226 catch {git config gui.geometry $cfg_geometry}
2230 set ret_code $rc
2232 # Briefly enable send again, working around Tk bug
2233 # http://sourceforge.net/tracker/?func=detail&atid=112997&aid=1821174&group_id=12997
2234 tk appname [appname]
2236 destroy .
2239 proc do_rescan {} {
2240 rescan ui_ready
2243 proc ui_do_rescan {} {
2244 rescan {force_first_diff ui_ready}
2247 proc do_commit {} {
2248 commit_tree
2251 proc next_diff {{after {}}} {
2252 global next_diff_p next_diff_w next_diff_i
2253 show_diff $next_diff_p $next_diff_w {} {} $after
2256 proc find_anchor_pos {lst name} {
2257 set lid [lsearch -sorted -exact $lst $name]
2259 if {$lid == -1} {
2260 set lid 0
2261 foreach lname $lst {
2262 if {$lname >= $name} break
2263 incr lid
2267 return $lid
2270 proc find_file_from {flist idx delta path mmask} {
2271 global file_states
2273 set len [llength $flist]
2274 while {$idx >= 0 && $idx < $len} {
2275 set name [lindex $flist $idx]
2277 if {$name ne $path && [info exists file_states($name)]} {
2278 set state [lindex $file_states($name) 0]
2280 if {$mmask eq {} || [regexp $mmask $state]} {
2281 return $idx
2285 incr idx $delta
2288 return {}
2291 proc find_next_diff {w path {lno {}} {mmask {}}} {
2292 global next_diff_p next_diff_w next_diff_i
2293 global file_lists ui_index ui_workdir
2295 set flist $file_lists($w)
2296 if {$lno eq {}} {
2297 set lno [find_anchor_pos $flist $path]
2298 } else {
2299 incr lno -1
2302 if {$mmask ne {} && ![regexp {(^\^)|(\$$)} $mmask]} {
2303 if {$w eq $ui_index} {
2304 set mmask "^$mmask"
2305 } else {
2306 set mmask "$mmask\$"
2310 set idx [find_file_from $flist $lno 1 $path $mmask]
2311 if {$idx eq {}} {
2312 incr lno -1
2313 set idx [find_file_from $flist $lno -1 $path $mmask]
2316 if {$idx ne {}} {
2317 set next_diff_w $w
2318 set next_diff_p [lindex $flist $idx]
2319 set next_diff_i [expr {$idx+1}]
2320 return 1
2321 } else {
2322 return 0
2326 proc next_diff_after_action {w path {lno {}} {mmask {}}} {
2327 global current_diff_path
2329 if {$path ne $current_diff_path} {
2330 return {}
2331 } elseif {[find_next_diff $w $path $lno $mmask]} {
2332 return {next_diff;}
2333 } else {
2334 return {reshow_diff;}
2338 proc select_first_diff {after} {
2339 global ui_workdir
2341 if {[find_next_diff $ui_workdir {} 1 {^_?U}] ||
2342 [find_next_diff $ui_workdir {} 1 {[^O]$}]} {
2343 next_diff $after
2344 } else {
2345 uplevel #0 $after
2349 proc force_first_diff {after} {
2350 global ui_workdir current_diff_path file_states
2352 if {[info exists file_states($current_diff_path)]} {
2353 set state [lindex $file_states($current_diff_path) 0]
2354 } else {
2355 set state {OO}
2358 set reselect 0
2359 if {[string first {U} $state] >= 0} {
2360 # Already a conflict, do nothing
2361 } elseif {[find_next_diff $ui_workdir $current_diff_path {} {^_?U}]} {
2362 set reselect 1
2363 } elseif {[string index $state 1] ne {O}} {
2364 # Already a diff & no conflicts, do nothing
2365 } elseif {[find_next_diff $ui_workdir $current_diff_path {} {[^O]$}]} {
2366 set reselect 1
2369 if {$reselect} {
2370 next_diff $after
2371 } else {
2372 uplevel #0 $after
2376 proc toggle_or_diff {w x y} {
2377 global file_states file_lists current_diff_path ui_index ui_workdir
2378 global last_clicked selected_paths
2380 set pos [split [$w index @$x,$y] .]
2381 set lno [lindex $pos 0]
2382 set col [lindex $pos 1]
2383 set path [lindex $file_lists($w) [expr {$lno - 1}]]
2384 if {$path eq {}} {
2385 set last_clicked {}
2386 return
2389 set last_clicked [list $w $lno]
2390 array unset selected_paths
2391 $ui_index tag remove in_sel 0.0 end
2392 $ui_workdir tag remove in_sel 0.0 end
2394 # Determine the state of the file
2395 if {[info exists file_states($path)]} {
2396 set state [lindex $file_states($path) 0]
2397 } else {
2398 set state {__}
2401 # Restage the file, or simply show the diff
2402 if {$col == 0 && $y > 1} {
2403 # Conflicts need special handling
2404 if {[string first {U} $state] >= 0} {
2405 # $w must always be $ui_workdir, but...
2406 if {$w ne $ui_workdir} { set lno {} }
2407 merge_stage_workdir $path $lno
2408 return
2411 if {[string index $state 1] eq {O}} {
2412 set mmask {}
2413 } else {
2414 set mmask {[^O]}
2417 set after [next_diff_after_action $w $path $lno $mmask]
2419 if {$w eq $ui_index} {
2420 update_indexinfo \
2421 "Unstaging [short_path $path] from commit" \
2422 [list $path] \
2423 [concat $after [list ui_ready]]
2424 } elseif {$w eq $ui_workdir} {
2425 update_index \
2426 "Adding [short_path $path]" \
2427 [list $path] \
2428 [concat $after [list ui_ready]]
2430 } else {
2431 show_diff $path $w $lno
2435 proc add_one_to_selection {w x y} {
2436 global file_lists last_clicked selected_paths
2438 set lno [lindex [split [$w index @$x,$y] .] 0]
2439 set path [lindex $file_lists($w) [expr {$lno - 1}]]
2440 if {$path eq {}} {
2441 set last_clicked {}
2442 return
2445 if {$last_clicked ne {}
2446 && [lindex $last_clicked 0] ne $w} {
2447 array unset selected_paths
2448 [lindex $last_clicked 0] tag remove in_sel 0.0 end
2451 set last_clicked [list $w $lno]
2452 if {[catch {set in_sel $selected_paths($path)}]} {
2453 set in_sel 0
2455 if {$in_sel} {
2456 unset selected_paths($path)
2457 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
2458 } else {
2459 set selected_paths($path) 1
2460 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
2464 proc add_range_to_selection {w x y} {
2465 global file_lists last_clicked selected_paths
2467 if {[lindex $last_clicked 0] ne $w} {
2468 toggle_or_diff $w $x $y
2469 return
2472 set lno [lindex [split [$w index @$x,$y] .] 0]
2473 set lc [lindex $last_clicked 1]
2474 if {$lc < $lno} {
2475 set begin $lc
2476 set end $lno
2477 } else {
2478 set begin $lno
2479 set end $lc
2482 foreach path [lrange $file_lists($w) \
2483 [expr {$begin - 1}] \
2484 [expr {$end - 1}]] {
2485 set selected_paths($path) 1
2487 $w tag add in_sel $begin.0 [expr {$end + 1}].0
2490 proc show_more_context {} {
2491 global repo_config
2492 if {$repo_config(gui.diffcontext) < 99} {
2493 incr repo_config(gui.diffcontext)
2494 reshow_diff
2498 proc show_less_context {} {
2499 global repo_config
2500 if {$repo_config(gui.diffcontext) > 1} {
2501 incr repo_config(gui.diffcontext) -1
2502 reshow_diff
2506 ######################################################################
2508 ## ui construction
2510 set ui_comm {}
2512 # -- Menu Bar
2514 menu .mbar -tearoff 0
2515 if {[is_MacOSX]} {
2516 # -- Apple Menu (Mac OS X only)
2518 .mbar add cascade -label Apple -menu .mbar.apple
2519 menu .mbar.apple
2521 .mbar add cascade -label [mc Repository] -menu .mbar.repository
2522 .mbar add cascade -label [mc Edit] -menu .mbar.edit
2523 if {[is_enabled branch]} {
2524 .mbar add cascade -label [mc Branch] -menu .mbar.branch
2526 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2527 .mbar add cascade -label [mc Commit@@noun] -menu .mbar.commit
2529 if {[is_enabled transport]} {
2530 .mbar add cascade -label [mc Merge] -menu .mbar.merge
2531 .mbar add cascade -label [mc Remote] -menu .mbar.remote
2533 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2534 .mbar add cascade -label [mc Tools] -menu .mbar.tools
2537 # -- Repository Menu
2539 menu .mbar.repository
2541 if {![is_bare]} {
2542 .mbar.repository add command \
2543 -label [mc "Explore Working Copy"] \
2544 -command {do_explore}
2545 .mbar.repository add separator
2548 .mbar.repository add command \
2549 -label [mc "Browse Current Branch's Files"] \
2550 -command {browser::new $current_branch}
2551 set ui_browse_current [.mbar.repository index last]
2552 .mbar.repository add command \
2553 -label [mc "Browse Branch Files..."] \
2554 -command browser_open::dialog
2555 .mbar.repository add separator
2557 .mbar.repository add command \
2558 -label [mc "Visualize Current Branch's History"] \
2559 -command {do_gitk $current_branch}
2560 set ui_visualize_current [.mbar.repository index last]
2561 .mbar.repository add command \
2562 -label [mc "Visualize All Branch History"] \
2563 -command {do_gitk --all}
2564 .mbar.repository add separator
2566 proc current_branch_write {args} {
2567 global current_branch
2568 .mbar.repository entryconf $::ui_browse_current \
2569 -label [mc "Browse %s's Files" $current_branch]
2570 .mbar.repository entryconf $::ui_visualize_current \
2571 -label [mc "Visualize %s's History" $current_branch]
2573 trace add variable current_branch write current_branch_write
2575 if {[is_enabled multicommit]} {
2576 .mbar.repository add command -label [mc "Database Statistics"] \
2577 -command do_stats
2579 .mbar.repository add command -label [mc "Compress Database"] \
2580 -command do_gc
2582 .mbar.repository add command -label [mc "Verify Database"] \
2583 -command do_fsck_objects
2585 .mbar.repository add separator
2587 if {[is_Cygwin]} {
2588 .mbar.repository add command \
2589 -label [mc "Create Desktop Icon"] \
2590 -command do_cygwin_shortcut
2591 } elseif {[is_Windows]} {
2592 .mbar.repository add command \
2593 -label [mc "Create Desktop Icon"] \
2594 -command do_windows_shortcut
2595 } elseif {[is_MacOSX]} {
2596 .mbar.repository add command \
2597 -label [mc "Create Desktop Icon"] \
2598 -command do_macosx_app
2602 if {[is_MacOSX]} {
2603 proc ::tk::mac::Quit {args} { do_quit }
2604 } else {
2605 .mbar.repository add command -label [mc Quit] \
2606 -command do_quit \
2607 -accelerator $M1T-Q
2610 # -- Edit Menu
2612 menu .mbar.edit
2613 .mbar.edit add command -label [mc Undo] \
2614 -command {catch {[focus] edit undo}} \
2615 -accelerator $M1T-Z
2616 .mbar.edit add command -label [mc Redo] \
2617 -command {catch {[focus] edit redo}} \
2618 -accelerator $M1T-Y
2619 .mbar.edit add separator
2620 .mbar.edit add command -label [mc Cut] \
2621 -command {catch {tk_textCut [focus]}} \
2622 -accelerator $M1T-X
2623 .mbar.edit add command -label [mc Copy] \
2624 -command {catch {tk_textCopy [focus]}} \
2625 -accelerator $M1T-C
2626 .mbar.edit add command -label [mc Paste] \
2627 -command {catch {tk_textPaste [focus]; [focus] see insert}} \
2628 -accelerator $M1T-V
2629 .mbar.edit add command -label [mc Delete] \
2630 -command {catch {[focus] delete sel.first sel.last}} \
2631 -accelerator Del
2632 .mbar.edit add separator
2633 .mbar.edit add command -label [mc "Select All"] \
2634 -command {catch {[focus] tag add sel 0.0 end}} \
2635 -accelerator $M1T-A
2637 # -- Branch Menu
2639 if {[is_enabled branch]} {
2640 menu .mbar.branch
2642 .mbar.branch add command -label [mc "Create..."] \
2643 -command branch_create::dialog \
2644 -accelerator $M1T-N
2645 lappend disable_on_lock [list .mbar.branch entryconf \
2646 [.mbar.branch index last] -state]
2648 .mbar.branch add command -label [mc "Checkout..."] \
2649 -command branch_checkout::dialog \
2650 -accelerator $M1T-O
2651 lappend disable_on_lock [list .mbar.branch entryconf \
2652 [.mbar.branch index last] -state]
2654 .mbar.branch add command -label [mc "Rename..."] \
2655 -command branch_rename::dialog
2656 lappend disable_on_lock [list .mbar.branch entryconf \
2657 [.mbar.branch index last] -state]
2659 .mbar.branch add command -label [mc "Delete..."] \
2660 -command branch_delete::dialog
2661 lappend disable_on_lock [list .mbar.branch entryconf \
2662 [.mbar.branch index last] -state]
2664 .mbar.branch add command -label [mc "Reset..."] \
2665 -command merge::reset_hard
2666 lappend disable_on_lock [list .mbar.branch entryconf \
2667 [.mbar.branch index last] -state]
2670 # -- Commit Menu
2672 proc commit_btn_caption {} {
2673 if {[is_enabled nocommit]} {
2674 return [mc "Done"]
2675 } else {
2676 return [mc Commit@@verb]
2680 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2681 menu .mbar.commit
2683 if {![is_enabled nocommit]} {
2684 .mbar.commit add radiobutton \
2685 -label [mc "New Commit"] \
2686 -command do_select_commit_type \
2687 -variable selected_commit_type \
2688 -value new
2689 lappend disable_on_lock \
2690 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2692 .mbar.commit add radiobutton \
2693 -label [mc "Amend Last Commit"] \
2694 -command do_select_commit_type \
2695 -variable selected_commit_type \
2696 -value amend
2697 lappend disable_on_lock \
2698 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2700 .mbar.commit add separator
2703 .mbar.commit add command -label [mc Rescan] \
2704 -command ui_do_rescan \
2705 -accelerator F5
2706 lappend disable_on_lock \
2707 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2709 .mbar.commit add command -label [mc "Stage To Commit"] \
2710 -command do_add_selection \
2711 -accelerator $M1T-T
2712 lappend disable_on_lock \
2713 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2715 .mbar.commit add command -label [mc "Stage Changed Files To Commit"] \
2716 -command do_add_all \
2717 -accelerator $M1T-I
2718 lappend disable_on_lock \
2719 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2721 .mbar.commit add command -label [mc "Unstage From Commit"] \
2722 -command do_unstage_selection \
2723 -accelerator $M1T-U
2724 lappend disable_on_lock \
2725 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2727 .mbar.commit add command -label [mc "Revert Changes"] \
2728 -command do_revert_selection \
2729 -accelerator $M1T-J
2730 lappend disable_on_lock \
2731 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2733 .mbar.commit add separator
2735 .mbar.commit add command -label [mc "Show Less Context"] \
2736 -command show_less_context \
2737 -accelerator $M1T-\-
2739 .mbar.commit add command -label [mc "Show More Context"] \
2740 -command show_more_context \
2741 -accelerator $M1T-=
2743 .mbar.commit add separator
2745 if {![is_enabled nocommitmsg]} {
2746 .mbar.commit add command -label [mc "Sign Off"] \
2747 -command do_signoff \
2748 -accelerator $M1T-S
2751 .mbar.commit add command -label [commit_btn_caption] \
2752 -command do_commit \
2753 -accelerator $M1T-Return
2754 lappend disable_on_lock \
2755 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2758 # -- Merge Menu
2760 if {[is_enabled branch]} {
2761 menu .mbar.merge
2762 .mbar.merge add command -label [mc "Local Merge..."] \
2763 -command merge::dialog \
2764 -accelerator $M1T-M
2765 lappend disable_on_lock \
2766 [list .mbar.merge entryconf [.mbar.merge index last] -state]
2767 .mbar.merge add command -label [mc "Abort Merge..."] \
2768 -command merge::reset_hard
2769 lappend disable_on_lock \
2770 [list .mbar.merge entryconf [.mbar.merge index last] -state]
2773 # -- Transport Menu
2775 if {[is_enabled transport]} {
2776 menu .mbar.remote
2778 .mbar.remote add command \
2779 -label [mc "Add..."] \
2780 -command remote_add::dialog \
2781 -accelerator $M1T-A
2782 .mbar.remote add command \
2783 -label [mc "Push..."] \
2784 -command do_push_anywhere \
2785 -accelerator $M1T-P
2786 .mbar.remote add command \
2787 -label [mc "Delete Branch..."] \
2788 -command remote_branch_delete::dialog
2791 if {[is_MacOSX]} {
2792 proc ::tk::mac::ShowPreferences {} {do_options}
2793 } else {
2794 # -- Edit Menu
2796 .mbar.edit add separator
2797 .mbar.edit add command -label [mc "Options..."] \
2798 -command do_options
2801 # -- Tools Menu
2803 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2804 set tools_menubar .mbar.tools
2805 menu $tools_menubar
2806 $tools_menubar add separator
2807 $tools_menubar add command -label [mc "Add..."] -command tools_add::dialog
2808 $tools_menubar add command -label [mc "Remove..."] -command tools_remove::dialog
2809 set tools_tailcnt 3
2810 if {[array names repo_config guitool.*.cmd] ne {}} {
2811 tools_populate_all
2815 # -- Help Menu
2817 .mbar add cascade -label [mc Help] -menu .mbar.help
2818 menu .mbar.help
2820 if {[is_MacOSX]} {
2821 .mbar.apple add command -label [mc "About %s" [appname]] \
2822 -command do_about
2823 .mbar.apple add separator
2824 } else {
2825 .mbar.help add command -label [mc "About %s" [appname]] \
2826 -command do_about
2828 . configure -menu .mbar
2830 set doc_path [githtmldir]
2831 if {$doc_path ne {}} {
2832 set doc_path [file join $doc_path index.html]
2834 if {[is_Cygwin]} {
2835 set doc_path [exec cygpath --mixed $doc_path]
2839 if {[file isfile $doc_path]} {
2840 set doc_url "file:$doc_path"
2841 } else {
2842 set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
2845 proc start_browser {url} {
2846 git "web--browse" $url
2849 .mbar.help add command -label [mc "Online Documentation"] \
2850 -command [list start_browser $doc_url]
2852 .mbar.help add command -label [mc "Show SSH Key"] \
2853 -command do_ssh_key
2855 unset doc_path doc_url
2857 # -- Standard bindings
2859 wm protocol . WM_DELETE_WINDOW do_quit
2860 bind all <$M1B-Key-q> do_quit
2861 bind all <$M1B-Key-Q> do_quit
2862 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
2863 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
2865 set subcommand_args {}
2866 proc usage {} {
2867 set s "usage: $::argv0 $::subcommand $::subcommand_args"
2868 if {[tk windowingsystem] eq "win32"} {
2869 wm withdraw .
2870 tk_messageBox -icon info -title "Usage" -message $s
2871 } else {
2872 puts stderr $s
2874 exit 1
2877 proc normalize_relpath {path} {
2878 set elements {}
2879 foreach item [file split $path] {
2880 if {$item eq {.}} continue
2881 if {$item eq {..} && [llength $elements] > 0
2882 && [lindex $elements end] ne {..}} {
2883 set elements [lrange $elements 0 end-1]
2884 continue
2886 lappend elements $item
2888 return [eval file join $elements]
2891 # -- Not a normal commit type invocation? Do that instead!
2893 switch -- $subcommand {
2894 browser -
2895 blame {
2896 if {$subcommand eq "blame"} {
2897 set subcommand_args {[--line=<num>] rev? path}
2898 } else {
2899 set subcommand_args {rev? path}
2901 if {$argv eq {}} usage
2902 set head {}
2903 set path {}
2904 set jump_spec {}
2905 set is_path 0
2906 foreach a $argv {
2907 if {$is_path || [file exists $_prefix$a]} {
2908 if {$path ne {}} usage
2909 set path [normalize_relpath $_prefix$a]
2910 break
2911 } elseif {$a eq {--}} {
2912 if {$path ne {}} {
2913 if {$head ne {}} usage
2914 set head $path
2915 set path {}
2917 set is_path 1
2918 } elseif {[regexp {^--line=(\d+)$} $a a lnum]} {
2919 if {$jump_spec ne {} || $head ne {}} usage
2920 set jump_spec [list $lnum]
2921 } elseif {$head eq {}} {
2922 if {$head ne {}} usage
2923 set head $a
2924 set is_path 1
2925 } else {
2926 usage
2929 unset is_path
2931 if {$head ne {} && $path eq {}} {
2932 set path [normalize_relpath $_prefix$head]
2933 set head {}
2936 if {$head eq {}} {
2937 load_current_branch
2938 } else {
2939 if {[regexp {^[0-9a-f]{1,39}$} $head]} {
2940 if {[catch {
2941 set head [git rev-parse --verify $head]
2942 } err]} {
2943 puts stderr $err
2944 exit 1
2947 set current_branch $head
2950 wm deiconify .
2951 switch -- $subcommand {
2952 browser {
2953 if {$jump_spec ne {}} usage
2954 if {$head eq {}} {
2955 if {$path ne {} && [file isdirectory $path]} {
2956 set head $current_branch
2957 } else {
2958 set head $path
2959 set path {}
2962 browser::new $head $path
2964 blame {
2965 if {$head eq {} && ![file exists $path]} {
2966 catch {wm withdraw .}
2967 tk_messageBox \
2968 -icon error \
2969 -type ok \
2970 -title [mc "git-gui: fatal error"] \
2971 -message [mc "fatal: cannot stat path %s: No such file or directory" $path]
2972 exit 1
2974 blame::new $head $path $jump_spec
2977 return
2979 citool -
2980 gui {
2981 if {[llength $argv] != 0} {
2982 puts -nonewline stderr "usage: $argv0"
2983 if {$subcommand ne {gui}
2984 && [file tail $argv0] ne "git-$subcommand"} {
2985 puts -nonewline stderr " $subcommand"
2987 puts stderr {}
2988 exit 1
2990 # fall through to setup UI for commits
2992 default {
2993 puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
2994 exit 1
2998 # -- Branch Control
3000 ${NS}::frame .branch
3001 if {!$use_ttk} {.branch configure -borderwidth 1 -relief sunken}
3002 ${NS}::label .branch.l1 \
3003 -text [mc "Current Branch:"] \
3004 -anchor w \
3005 -justify left
3006 ${NS}::label .branch.cb \
3007 -textvariable current_branch \
3008 -anchor w \
3009 -justify left
3010 pack .branch.l1 -side left
3011 pack .branch.cb -side left -fill x
3012 pack .branch -side top -fill x
3014 # -- Main Window Layout
3016 ${NS}::panedwindow .vpane -orient horizontal
3017 ${NS}::panedwindow .vpane.files -orient vertical
3018 if {$use_ttk} {
3019 .vpane add .vpane.files
3020 } else {
3021 .vpane add .vpane.files -sticky nsew -height 100 -width 200
3023 pack .vpane -anchor n -side top -fill both -expand 1
3025 # -- Index File List
3027 ${NS}::frame .vpane.files.index -height 100 -width 200
3028 tlabel .vpane.files.index.title \
3029 -text [mc "Staged Changes (Will Commit)"] \
3030 -background lightgreen -foreground black
3031 text $ui_index -background white -foreground black \
3032 -borderwidth 0 \
3033 -width 20 -height 10 \
3034 -wrap none \
3035 -cursor $cursor_ptr \
3036 -xscrollcommand {.vpane.files.index.sx set} \
3037 -yscrollcommand {.vpane.files.index.sy set} \
3038 -state disabled
3039 ${NS}::scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
3040 ${NS}::scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
3041 pack .vpane.files.index.title -side top -fill x
3042 pack .vpane.files.index.sx -side bottom -fill x
3043 pack .vpane.files.index.sy -side right -fill y
3044 pack $ui_index -side left -fill both -expand 1
3046 # -- Working Directory File List
3048 ${NS}::frame .vpane.files.workdir -height 100 -width 200
3049 tlabel .vpane.files.workdir.title -text [mc "Unstaged Changes"] \
3050 -background lightsalmon -foreground black
3051 text $ui_workdir -background white -foreground black \
3052 -borderwidth 0 \
3053 -width 20 -height 10 \
3054 -wrap none \
3055 -cursor $cursor_ptr \
3056 -xscrollcommand {.vpane.files.workdir.sx set} \
3057 -yscrollcommand {.vpane.files.workdir.sy set} \
3058 -state disabled
3059 ${NS}::scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
3060 ${NS}::scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
3061 pack .vpane.files.workdir.title -side top -fill x
3062 pack .vpane.files.workdir.sx -side bottom -fill x
3063 pack .vpane.files.workdir.sy -side right -fill y
3064 pack $ui_workdir -side left -fill both -expand 1
3066 .vpane.files add .vpane.files.workdir
3067 .vpane.files add .vpane.files.index
3068 if {!$use_ttk} {
3069 .vpane.files paneconfigure .vpane.files.workdir -sticky news
3070 .vpane.files paneconfigure .vpane.files.index -sticky news
3073 foreach i [list $ui_index $ui_workdir] {
3074 rmsel_tag $i
3075 $i tag conf in_diff -background [$i tag cget in_sel -background]
3077 unset i
3079 # -- Diff and Commit Area
3081 ${NS}::frame .vpane.lower -height 300 -width 400
3082 ${NS}::frame .vpane.lower.commarea
3083 ${NS}::frame .vpane.lower.diff -relief sunken -borderwidth 1
3084 pack .vpane.lower.diff -fill both -expand 1
3085 pack .vpane.lower.commarea -side bottom -fill x
3086 .vpane add .vpane.lower
3087 if {!$use_ttk} {.vpane paneconfigure .vpane.lower -sticky nsew}
3089 # -- Commit Area Buttons
3091 ${NS}::frame .vpane.lower.commarea.buttons
3092 ${NS}::label .vpane.lower.commarea.buttons.l -text {} \
3093 -anchor w \
3094 -justify left
3095 pack .vpane.lower.commarea.buttons.l -side top -fill x
3096 pack .vpane.lower.commarea.buttons -side left -fill y
3098 ${NS}::button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \
3099 -command ui_do_rescan
3100 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
3101 lappend disable_on_lock \
3102 {.vpane.lower.commarea.buttons.rescan conf -state}
3104 ${NS}::button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \
3105 -command do_add_all
3106 pack .vpane.lower.commarea.buttons.incall -side top -fill x
3107 lappend disable_on_lock \
3108 {.vpane.lower.commarea.buttons.incall conf -state}
3110 if {![is_enabled nocommitmsg]} {
3111 ${NS}::button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \
3112 -command do_signoff
3113 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
3116 ${NS}::button .vpane.lower.commarea.buttons.commit -text [commit_btn_caption] \
3117 -command do_commit
3118 pack .vpane.lower.commarea.buttons.commit -side top -fill x
3119 lappend disable_on_lock \
3120 {.vpane.lower.commarea.buttons.commit conf -state}
3122 if {![is_enabled nocommit]} {
3123 ${NS}::button .vpane.lower.commarea.buttons.push -text [mc Push] \
3124 -command do_push_anywhere
3125 pack .vpane.lower.commarea.buttons.push -side top -fill x
3128 # -- Commit Message Buffer
3130 ${NS}::frame .vpane.lower.commarea.buffer
3131 ${NS}::frame .vpane.lower.commarea.buffer.header
3132 set ui_comm .vpane.lower.commarea.buffer.t
3133 set ui_coml .vpane.lower.commarea.buffer.header.l
3135 if {![is_enabled nocommit]} {
3136 ${NS}::radiobutton .vpane.lower.commarea.buffer.header.new \
3137 -text [mc "New Commit"] \
3138 -command do_select_commit_type \
3139 -variable selected_commit_type \
3140 -value new
3141 lappend disable_on_lock \
3142 [list .vpane.lower.commarea.buffer.header.new conf -state]
3143 ${NS}::radiobutton .vpane.lower.commarea.buffer.header.amend \
3144 -text [mc "Amend Last Commit"] \
3145 -command do_select_commit_type \
3146 -variable selected_commit_type \
3147 -value amend
3148 lappend disable_on_lock \
3149 [list .vpane.lower.commarea.buffer.header.amend conf -state]
3152 ${NS}::label $ui_coml \
3153 -anchor w \
3154 -justify left
3155 proc trace_commit_type {varname args} {
3156 global ui_coml commit_type
3157 switch -glob -- $commit_type {
3158 initial {set txt [mc "Initial Commit Message:"]}
3159 amend {set txt [mc "Amended Commit Message:"]}
3160 amend-initial {set txt [mc "Amended Initial Commit Message:"]}
3161 amend-merge {set txt [mc "Amended Merge Commit Message:"]}
3162 merge {set txt [mc "Merge Commit Message:"]}
3163 * {set txt [mc "Commit Message:"]}
3165 $ui_coml conf -text $txt
3167 trace add variable commit_type write trace_commit_type
3168 pack $ui_coml -side left -fill x
3170 if {![is_enabled nocommit]} {
3171 pack .vpane.lower.commarea.buffer.header.amend -side right
3172 pack .vpane.lower.commarea.buffer.header.new -side right
3175 text $ui_comm -background white -foreground black \
3176 -borderwidth 1 \
3177 -undo true \
3178 -maxundo 20 \
3179 -autoseparators true \
3180 -relief sunken \
3181 -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \
3182 -font font_diff \
3183 -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
3184 ${NS}::scrollbar .vpane.lower.commarea.buffer.sby \
3185 -command [list $ui_comm yview]
3186 pack .vpane.lower.commarea.buffer.header -side top -fill x
3187 pack .vpane.lower.commarea.buffer.sby -side right -fill y
3188 pack $ui_comm -side left -fill y
3189 pack .vpane.lower.commarea.buffer -side left -fill y
3191 # -- Commit Message Buffer Context Menu
3193 set ctxm .vpane.lower.commarea.buffer.ctxm
3194 menu $ctxm -tearoff 0
3195 $ctxm add command \
3196 -label [mc Cut] \
3197 -command {tk_textCut $ui_comm}
3198 $ctxm add command \
3199 -label [mc Copy] \
3200 -command {tk_textCopy $ui_comm}
3201 $ctxm add command \
3202 -label [mc Paste] \
3203 -command {tk_textPaste $ui_comm}
3204 $ctxm add command \
3205 -label [mc Delete] \
3206 -command {catch {$ui_comm delete sel.first sel.last}}
3207 $ctxm add separator
3208 $ctxm add command \
3209 -label [mc "Select All"] \
3210 -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
3211 $ctxm add command \
3212 -label [mc "Copy All"] \
3213 -command {
3214 $ui_comm tag add sel 0.0 end
3215 tk_textCopy $ui_comm
3216 $ui_comm tag remove sel 0.0 end
3218 $ctxm add separator
3219 $ctxm add command \
3220 -label [mc "Sign Off"] \
3221 -command do_signoff
3222 set ui_comm_ctxm $ctxm
3224 # -- Diff Header
3226 proc trace_current_diff_path {varname args} {
3227 global current_diff_path diff_actions file_states
3228 if {$current_diff_path eq {}} {
3229 set s {}
3230 set f {}
3231 set p {}
3232 set o disabled
3233 } else {
3234 set p $current_diff_path
3235 set s [mapdesc [lindex $file_states($p) 0] $p]
3236 set f [mc "File:"]
3237 set p [escape_path $p]
3238 set o normal
3241 .vpane.lower.diff.header.status configure -text $s
3242 .vpane.lower.diff.header.file configure -text $f
3243 .vpane.lower.diff.header.path configure -text $p
3244 foreach w $diff_actions {
3245 uplevel #0 $w $o
3248 trace add variable current_diff_path write trace_current_diff_path
3250 gold_frame .vpane.lower.diff.header
3251 tlabel .vpane.lower.diff.header.status \
3252 -background gold \
3253 -foreground black \
3254 -width $max_status_desc \
3255 -anchor w \
3256 -justify left
3257 tlabel .vpane.lower.diff.header.file \
3258 -background gold \
3259 -foreground black \
3260 -anchor w \
3261 -justify left
3262 tlabel .vpane.lower.diff.header.path \
3263 -background gold \
3264 -foreground black \
3265 -anchor w \
3266 -justify left
3267 pack .vpane.lower.diff.header.status -side left
3268 pack .vpane.lower.diff.header.file -side left
3269 pack .vpane.lower.diff.header.path -fill x
3270 set ctxm .vpane.lower.diff.header.ctxm
3271 menu $ctxm -tearoff 0
3272 $ctxm add command \
3273 -label [mc Copy] \
3274 -command {
3275 clipboard clear
3276 clipboard append \
3277 -format STRING \
3278 -type STRING \
3279 -- $current_diff_path
3281 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3282 bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
3284 # -- Diff Body
3286 ${NS}::frame .vpane.lower.diff.body
3287 set ui_diff .vpane.lower.diff.body.t
3288 text $ui_diff -background white -foreground black \
3289 -borderwidth 0 \
3290 -width 80 -height 5 -wrap none \
3291 -font font_diff \
3292 -xscrollcommand {.vpane.lower.diff.body.sbx set} \
3293 -yscrollcommand {.vpane.lower.diff.body.sby set} \
3294 -state disabled
3295 ${NS}::scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
3296 -command [list $ui_diff xview]
3297 ${NS}::scrollbar .vpane.lower.diff.body.sby -orient vertical \
3298 -command [list $ui_diff yview]
3299 pack .vpane.lower.diff.body.sbx -side bottom -fill x
3300 pack .vpane.lower.diff.body.sby -side right -fill y
3301 pack $ui_diff -side left -fill both -expand 1
3302 pack .vpane.lower.diff.header -side top -fill x
3303 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
3305 $ui_diff tag conf d_cr -elide true
3306 $ui_diff tag conf d_@ -foreground blue -font font_diffbold
3307 $ui_diff tag conf d_+ -foreground {#00a000}
3308 $ui_diff tag conf d_- -foreground red
3310 $ui_diff tag conf d_++ -foreground {#00a000}
3311 $ui_diff tag conf d_-- -foreground red
3312 $ui_diff tag conf d_+s \
3313 -foreground {#00a000} \
3314 -background {#e2effa}
3315 $ui_diff tag conf d_-s \
3316 -foreground red \
3317 -background {#e2effa}
3318 $ui_diff tag conf d_s+ \
3319 -foreground {#00a000} \
3320 -background ivory1
3321 $ui_diff tag conf d_s- \
3322 -foreground red \
3323 -background ivory1
3325 $ui_diff tag conf d<<<<<<< \
3326 -foreground orange \
3327 -font font_diffbold
3328 $ui_diff tag conf d======= \
3329 -foreground orange \
3330 -font font_diffbold
3331 $ui_diff tag conf d>>>>>>> \
3332 -foreground orange \
3333 -font font_diffbold
3335 $ui_diff tag raise sel
3337 # -- Diff Body Context Menu
3340 proc create_common_diff_popup {ctxm} {
3341 $ctxm add command \
3342 -label [mc Refresh] \
3343 -command reshow_diff
3344 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3345 $ctxm add command \
3346 -label [mc Copy] \
3347 -command {tk_textCopy $ui_diff}
3348 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3349 $ctxm add command \
3350 -label [mc "Select All"] \
3351 -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
3352 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3353 $ctxm add command \
3354 -label [mc "Copy All"] \
3355 -command {
3356 $ui_diff tag add sel 0.0 end
3357 tk_textCopy $ui_diff
3358 $ui_diff tag remove sel 0.0 end
3360 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3361 $ctxm add separator
3362 $ctxm add command \
3363 -label [mc "Decrease Font Size"] \
3364 -command {incr_font_size font_diff -1}
3365 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3366 $ctxm add command \
3367 -label [mc "Increase Font Size"] \
3368 -command {incr_font_size font_diff 1}
3369 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3370 $ctxm add separator
3371 set emenu $ctxm.enc
3372 menu $emenu
3373 build_encoding_menu $emenu [list force_diff_encoding]
3374 $ctxm add cascade \
3375 -label [mc "Encoding"] \
3376 -menu $emenu
3377 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3378 $ctxm add separator
3379 $ctxm add command -label [mc "Options..."] \
3380 -command do_options
3383 set ctxm .vpane.lower.diff.body.ctxm
3384 menu $ctxm -tearoff 0
3385 $ctxm add command \
3386 -label [mc "Apply/Reverse Hunk"] \
3387 -command {apply_hunk $cursorX $cursorY}
3388 set ui_diff_applyhunk [$ctxm index last]
3389 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
3390 $ctxm add command \
3391 -label [mc "Apply/Reverse Line"] \
3392 -command {apply_range_or_line $cursorX $cursorY; do_rescan}
3393 set ui_diff_applyline [$ctxm index last]
3394 lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state]
3395 $ctxm add separator
3396 $ctxm add command \
3397 -label [mc "Show Less Context"] \
3398 -command show_less_context
3399 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3400 $ctxm add command \
3401 -label [mc "Show More Context"] \
3402 -command show_more_context
3403 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3404 $ctxm add separator
3405 create_common_diff_popup $ctxm
3407 set ctxmmg .vpane.lower.diff.body.ctxmmg
3408 menu $ctxmmg -tearoff 0
3409 $ctxmmg add command \
3410 -label [mc "Run Merge Tool"] \
3411 -command {merge_resolve_tool}
3412 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3413 $ctxmmg add separator
3414 $ctxmmg add command \
3415 -label [mc "Use Remote Version"] \
3416 -command {merge_resolve_one 3}
3417 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3418 $ctxmmg add command \
3419 -label [mc "Use Local Version"] \
3420 -command {merge_resolve_one 2}
3421 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3422 $ctxmmg add command \
3423 -label [mc "Revert To Base"] \
3424 -command {merge_resolve_one 1}
3425 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3426 $ctxmmg add separator
3427 $ctxmmg add command \
3428 -label [mc "Show Less Context"] \
3429 -command show_less_context
3430 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3431 $ctxmmg add command \
3432 -label [mc "Show More Context"] \
3433 -command show_more_context
3434 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3435 $ctxmmg add separator
3436 create_common_diff_popup $ctxmmg
3438 set ctxmsm .vpane.lower.diff.body.ctxmsm
3439 menu $ctxmsm -tearoff 0
3440 $ctxmsm add command \
3441 -label [mc "Visualize These Changes In The Submodule"] \
3442 -command {do_gitk -- true}
3443 lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
3444 $ctxmsm add command \
3445 -label [mc "Visualize Current Branch History In The Submodule"] \
3446 -command {do_gitk {} true}
3447 lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
3448 $ctxmsm add command \
3449 -label [mc "Visualize All Branch History In The Submodule"] \
3450 -command {do_gitk --all true}
3451 lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
3452 $ctxmsm add separator
3453 $ctxmsm add command \
3454 -label [mc "Start git gui In The Submodule"] \
3455 -command {do_git_gui}
3456 lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
3457 $ctxmsm add separator
3458 create_common_diff_popup $ctxmsm
3460 proc has_textconv {path} {
3461 if {[is_config_false gui.textconv]} {
3462 return 0
3464 set filter [gitattr $path diff set]
3465 set textconv [get_config [join [list diff $filter textconv] .]]
3466 if {$filter ne {set} && $textconv ne {}} {
3467 return 1
3468 } else {
3469 return 0
3473 proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} {
3474 global current_diff_path file_states
3475 set ::cursorX $x
3476 set ::cursorY $y
3477 if {[info exists file_states($current_diff_path)]} {
3478 set state [lindex $file_states($current_diff_path) 0]
3479 } else {
3480 set state {__}
3482 if {[string first {U} $state] >= 0} {
3483 tk_popup $ctxmmg $X $Y
3484 } elseif {$::is_submodule_diff} {
3485 tk_popup $ctxmsm $X $Y
3486 } else {
3487 set has_range [expr {[$::ui_diff tag nextrange sel 0.0] != {}}]
3488 if {$::ui_index eq $::current_diff_side} {
3489 set l [mc "Unstage Hunk From Commit"]
3490 if {$has_range} {
3491 set t [mc "Unstage Lines From Commit"]
3492 } else {
3493 set t [mc "Unstage Line From Commit"]
3495 } else {
3496 set l [mc "Stage Hunk For Commit"]
3497 if {$has_range} {
3498 set t [mc "Stage Lines For Commit"]
3499 } else {
3500 set t [mc "Stage Line For Commit"]
3503 if {$::is_3way_diff
3504 || $current_diff_path eq {}
3505 || {__} eq $state
3506 || {_O} eq $state
3507 || {_T} eq $state
3508 || {T_} eq $state
3509 || [has_textconv $current_diff_path]} {
3510 set s disabled
3511 } else {
3512 set s normal
3514 $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
3515 $ctxm entryconf $::ui_diff_applyline -state $s -label $t
3516 tk_popup $ctxm $X $Y
3519 bind_button3 $ui_diff [list popup_diff_menu $ctxm $ctxmmg $ctxmsm %x %y %X %Y]
3521 # -- Status Bar
3523 set main_status [::status_bar::new .status]
3524 pack .status -anchor w -side bottom -fill x
3525 $main_status show [mc "Initializing..."]
3527 # -- Load geometry
3529 proc on_ttk_pane_mapped {w pane pos} {
3530 bind $w <Map> {}
3531 after 0 [list after idle [list $w sashpos $pane $pos]]
3533 proc on_tk_pane_mapped {w pane x y} {
3534 bind $w <Map> {}
3535 after 0 [list after idle [list $w sash place $pane $x $y]]
3537 proc on_application_mapped {} {
3538 global repo_config use_ttk
3539 bind . <Map> {}
3540 set gm $repo_config(gui.geometry)
3541 if {$use_ttk} {
3542 bind .vpane <Map> \
3543 [list on_ttk_pane_mapped %W 0 [lindex $gm 1]]
3544 bind .vpane.files <Map> \
3545 [list on_ttk_pane_mapped %W 0 [lindex $gm 2]]
3546 } else {
3547 bind .vpane <Map> \
3548 [list on_tk_pane_mapped %W 0 \
3549 [lindex $gm 1] \
3550 [lindex [.vpane sash coord 0] 1]]
3551 bind .vpane.files <Map> \
3552 [list on_tk_pane_mapped %W 0 \
3553 [lindex [.vpane.files sash coord 0] 0] \
3554 [lindex $gm 2]]
3556 wm geometry . [lindex $gm 0]
3558 if {[info exists repo_config(gui.geometry)]} {
3559 bind . <Map> [list on_application_mapped]
3560 wm geometry . [lindex $repo_config(gui.geometry) 0]
3563 # -- Load window state
3565 if {[info exists repo_config(gui.wmstate)]} {
3566 catch {wm state . $repo_config(gui.wmstate)}
3569 # -- Key Bindings
3571 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
3572 bind $ui_comm <$M1B-Key-t> {do_add_selection;break}
3573 bind $ui_comm <$M1B-Key-T> {do_add_selection;break}
3574 bind $ui_comm <$M1B-Key-u> {do_unstage_selection;break}
3575 bind $ui_comm <$M1B-Key-U> {do_unstage_selection;break}
3576 bind $ui_comm <$M1B-Key-j> {do_revert_selection;break}
3577 bind $ui_comm <$M1B-Key-J> {do_revert_selection;break}
3578 bind $ui_comm <$M1B-Key-i> {do_add_all;break}
3579 bind $ui_comm <$M1B-Key-I> {do_add_all;break}
3580 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
3581 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
3582 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
3583 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
3584 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
3585 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
3586 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3587 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3588 bind $ui_comm <$M1B-Key-minus> {show_less_context;break}
3589 bind $ui_comm <$M1B-Key-KP_Subtract> {show_less_context;break}
3590 bind $ui_comm <$M1B-Key-equal> {show_more_context;break}
3591 bind $ui_comm <$M1B-Key-plus> {show_more_context;break}
3592 bind $ui_comm <$M1B-Key-KP_Add> {show_more_context;break}
3594 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
3595 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
3596 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
3597 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
3598 bind $ui_diff <$M1B-Key-v> {break}
3599 bind $ui_diff <$M1B-Key-V> {break}
3600 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3601 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3602 bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break}
3603 bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break}
3604 bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break}
3605 bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break}
3606 bind $ui_diff <Key-k> {catch {%W yview scroll -1 units};break}
3607 bind $ui_diff <Key-j> {catch {%W yview scroll 1 units};break}
3608 bind $ui_diff <Key-h> {catch {%W xview scroll -1 units};break}
3609 bind $ui_diff <Key-l> {catch {%W xview scroll 1 units};break}
3610 bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
3611 bind $ui_diff <Control-Key-f> {catch {%W yview scroll 1 pages};break}
3612 bind $ui_diff <Button-1> {focus %W}
3614 if {[is_enabled branch]} {
3615 bind . <$M1B-Key-n> branch_create::dialog
3616 bind . <$M1B-Key-N> branch_create::dialog
3617 bind . <$M1B-Key-o> branch_checkout::dialog
3618 bind . <$M1B-Key-O> branch_checkout::dialog
3619 bind . <$M1B-Key-m> merge::dialog
3620 bind . <$M1B-Key-M> merge::dialog
3622 if {[is_enabled transport]} {
3623 bind . <$M1B-Key-p> do_push_anywhere
3624 bind . <$M1B-Key-P> do_push_anywhere
3627 bind . <Key-F5> ui_do_rescan
3628 bind . <$M1B-Key-r> ui_do_rescan
3629 bind . <$M1B-Key-R> ui_do_rescan
3630 bind . <$M1B-Key-s> do_signoff
3631 bind . <$M1B-Key-S> do_signoff
3632 bind . <$M1B-Key-t> do_add_selection
3633 bind . <$M1B-Key-T> do_add_selection
3634 bind . <$M1B-Key-j> do_revert_selection
3635 bind . <$M1B-Key-J> do_revert_selection
3636 bind . <$M1B-Key-i> do_add_all
3637 bind . <$M1B-Key-I> do_add_all
3638 bind . <$M1B-Key-minus> {show_less_context;break}
3639 bind . <$M1B-Key-KP_Subtract> {show_less_context;break}
3640 bind . <$M1B-Key-equal> {show_more_context;break}
3641 bind . <$M1B-Key-plus> {show_more_context;break}
3642 bind . <$M1B-Key-KP_Add> {show_more_context;break}
3643 bind . <$M1B-Key-Return> do_commit
3644 foreach i [list $ui_index $ui_workdir] {
3645 bind $i <Button-1> "toggle_or_diff $i %x %y; break"
3646 bind $i <$M1B-Button-1> "add_one_to_selection $i %x %y; break"
3647 bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
3649 unset i
3651 set file_lists($ui_index) [list]
3652 set file_lists($ui_workdir) [list]
3654 wm title . "[appname] ([reponame]) [file normalize $_gitworktree]"
3655 focus -force $ui_comm
3657 # -- Warn the user about environmental problems. Cygwin's Tcl
3658 # does *not* pass its env array onto any processes it spawns.
3659 # This means that git processes get none of our environment.
3661 if {[is_Cygwin]} {
3662 set ignored_env 0
3663 set suggest_user {}
3664 set msg [mc "Possible environment issues exist.
3666 The following environment variables are probably
3667 going to be ignored by any Git subprocess run
3668 by %s:
3670 " [appname]]
3671 foreach name [array names env] {
3672 switch -regexp -- $name {
3673 {^GIT_INDEX_FILE$} -
3674 {^GIT_OBJECT_DIRECTORY$} -
3675 {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
3676 {^GIT_DIFF_OPTS$} -
3677 {^GIT_EXTERNAL_DIFF$} -
3678 {^GIT_PAGER$} -
3679 {^GIT_TRACE$} -
3680 {^GIT_CONFIG$} -
3681 {^GIT_(AUTHOR|COMMITTER)_DATE$} {
3682 append msg " - $name\n"
3683 incr ignored_env
3685 {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
3686 append msg " - $name\n"
3687 incr ignored_env
3688 set suggest_user $name
3692 if {$ignored_env > 0} {
3693 append msg [mc "
3694 This is due to a known issue with the
3695 Tcl binary distributed by Cygwin."]
3697 if {$suggest_user ne {}} {
3698 append msg [mc "
3700 A good replacement for %s
3701 is placing values for the user.name and
3702 user.email settings into your personal
3703 ~/.gitconfig file.
3704 " $suggest_user]
3706 warn_popup $msg
3708 unset ignored_env msg suggest_user name
3711 # -- Only initialize complex UI if we are going to stay running.
3713 if {[is_enabled transport]} {
3714 load_all_remotes
3716 set n [.mbar.remote index end]
3717 populate_remotes_menu
3718 set n [expr {[.mbar.remote index end] - $n}]
3719 if {$n > 0} {
3720 if {[.mbar.remote type 0] eq "tearoff"} { incr n }
3721 .mbar.remote insert $n separator
3723 unset n
3726 if {[winfo exists $ui_comm]} {
3727 set GITGUI_BCK_exists [load_message GITGUI_BCK]
3729 # -- If both our backup and message files exist use the
3730 # newer of the two files to initialize the buffer.
3732 if {$GITGUI_BCK_exists} {
3733 set m [gitdir GITGUI_MSG]
3734 if {[file isfile $m]} {
3735 if {[file mtime [gitdir GITGUI_BCK]] > [file mtime $m]} {
3736 catch {file delete [gitdir GITGUI_MSG]}
3737 } else {
3738 $ui_comm delete 0.0 end
3739 $ui_comm edit reset
3740 $ui_comm edit modified false
3741 catch {file delete [gitdir GITGUI_BCK]}
3742 set GITGUI_BCK_exists 0
3745 unset m
3748 proc backup_commit_buffer {} {
3749 global ui_comm GITGUI_BCK_exists
3751 set m [$ui_comm edit modified]
3752 if {$m || $GITGUI_BCK_exists} {
3753 set msg [string trim [$ui_comm get 0.0 end]]
3754 regsub -all -line {[ \r\t]+$} $msg {} msg
3756 if {$msg eq {}} {
3757 if {$GITGUI_BCK_exists} {
3758 catch {file delete [gitdir GITGUI_BCK]}
3759 set GITGUI_BCK_exists 0
3761 } elseif {$m} {
3762 catch {
3763 set fd [open [gitdir GITGUI_BCK] w]
3764 puts -nonewline $fd $msg
3765 close $fd
3766 set GITGUI_BCK_exists 1
3770 $ui_comm edit modified false
3773 set ::GITGUI_BCK_i [after 2000 backup_commit_buffer]
3776 backup_commit_buffer
3778 # -- If the user has aspell available we can drive it
3779 # in pipe mode to spellcheck the commit message.
3781 set spell_cmd [list |]
3782 set spell_dict [get_config gui.spellingdictionary]
3783 lappend spell_cmd aspell
3784 if {$spell_dict ne {}} {
3785 lappend spell_cmd --master=$spell_dict
3787 lappend spell_cmd --mode=none
3788 lappend spell_cmd --encoding=utf-8
3789 lappend spell_cmd pipe
3790 if {$spell_dict eq {none}
3791 || [catch {set spell_fd [open $spell_cmd r+]} spell_err]} {
3792 bind_button3 $ui_comm [list tk_popup $ui_comm_ctxm %X %Y]
3793 } else {
3794 set ui_comm_spell [spellcheck::init \
3795 $spell_fd \
3796 $ui_comm \
3797 $ui_comm_ctxm \
3800 unset -nocomplain spell_cmd spell_fd spell_err spell_dict
3803 lock_index begin-read
3804 if {![winfo ismapped .]} {
3805 wm deiconify .
3807 after 1 {
3808 if {[is_enabled initialamend]} {
3809 force_amend
3810 } else {
3811 do_rescan
3814 if {[is_enabled nocommitmsg]} {
3815 $ui_comm configure -state disabled -background gray
3818 if {[is_enabled multicommit]} {
3819 after 1000 hint_gc
3821 if {[is_enabled retcode]} {
3822 bind . <Destroy> {+terminate_me %W}
3824 if {$picked && [is_config_true gui.autoexplore]} {
3825 do_explore
3828 # Local variables:
3829 # mode: tcl
3830 # indent-tabs-mode: t
3831 # tab-width: 4
3832 # End: