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" -- "$@"
11
12set appvers {@@GITGUI_VERSION@@}
13set copyright [encoding convertfrom utf-8 {
14Copyright © 2006, 2007 Shawn Pearce, et. al.
15
16This program is free software; you can redistribute it and/or modify
17it under the terms of the GNU General Public License as published by
18the Free Software Foundation; either version 2 of the License, or
19(at your option) any later version.
20
21This program is distributed in the hope that it will be useful,
22but WITHOUT ANY WARRANTY; without even the implied warranty of
23MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24GNU General Public License for more details.
25
26You should have received a copy of the GNU General Public License
27along with this program; if not, write to the Free Software
28Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA}]
29
30######################################################################
31##
32## Tcl/Tk sanity check
33
34if {[catch {package require Tcl 8.4} err]
35 || [catch {package require Tk 8.4} err]
36} {
37 catch {wm withdraw .}
38 tk_messageBox \
39 -icon error \
40 -type ok \
41 -title [mc "git-gui: fatal error"] \
42 -message $err
43 exit 1
44}
45
46catch {rename send {}} ; # What an evil concept...
47
48######################################################################
49##
50## locate our library
51
52set oguilib {@@GITGUI_LIBDIR@@}
53set oguirel {@@GITGUI_RELATIVE@@}
54if {$oguirel eq {1}} {
55 set oguilib [file dirname [file dirname [file normalize $argv0]]]
56 set oguilib [file join $oguilib share git-gui lib]
57 set oguimsg [file join $oguilib msgs]
58} elseif {[string match @@* $oguirel]} {
59 set oguilib [file join [file dirname [file normalize $argv0]] lib]
60 set oguimsg [file join [file dirname [file normalize $argv0]] po]
61} else {
62 set oguimsg [file join $oguilib msgs]
63}
64unset oguirel
65
66######################################################################
67##
68## enable verbose loading?
69
70if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
71 unset _verbose
72 rename auto_load real__auto_load
73 proc auto_load {name args} {
74 puts stderr "auto_load $name"
75 return [uplevel 1 real__auto_load $name $args]
76 }
77 rename source real__source
78 proc source {name} {
79 puts stderr "source $name"
80 uplevel 1 real__source $name
81 }
82}
83
84######################################################################
85##
86## Internationalization (i18n) through msgcat and gettext. See
87## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
88
89package require msgcat
90
91proc _mc_trim {fmt} {
92 set cmk [string first @@ $fmt]
93 if {$cmk > 0} {
94 return [string range $fmt 0 [expr {$cmk - 1}]]
95 }
96 return $fmt
97}
98
99proc mc {en_fmt args} {
100 set fmt [_mc_trim [::msgcat::mc $en_fmt]]
101 if {[catch {set msg [eval [list format $fmt] $args]} err]} {
102 set msg [eval [list format [_mc_trim $en_fmt]] $args]
103 }
104 return $msg
105}
106
107proc strcat {args} {
108 return [join $args {}]
109}
110
111::msgcat::mcload $oguimsg
112unset oguimsg
113
114######################################################################
115##
116## read only globals
117
118set _appname {Git Gui}
119set _gitdir {}
120set _gitexec {}
121set _reponame {}
122set _iscygwin {}
123set _search_path {}
124
125set _trace [lsearch -exact $argv --trace]
126if {$_trace >= 0} {
127 set argv [lreplace $argv $_trace $_trace]
128 set _trace 1
129} else {
130 set _trace 0
131}
132
133proc appname {} {
134 global _appname
135 return $_appname
136}
137
138proc gitdir {args} {
139 global _gitdir
140 if {$args eq {}} {
141 return $_gitdir
142 }
143 return [eval [list file join $_gitdir] $args]
144}
145
146proc gitexec {args} {
147 global _gitexec
148 if {$_gitexec eq {}} {
149 if {[catch {set _gitexec [git --exec-path]} err]} {
150 error "Git not installed?\n\n$err"
151 }
152 if {[is_Cygwin]} {
153 set _gitexec [exec cygpath \
154 --windows \
155 --absolute \
156 $_gitexec]
157 } else {
158 set _gitexec [file normalize $_gitexec]
159 }
160 }
161 if {$args eq {}} {
162 return $_gitexec
163 }
164 return [eval [list file join $_gitexec] $args]
165}
166
167proc reponame {} {
168 return $::_reponame
169}
170
171proc is_MacOSX {} {
172 if {[tk windowingsystem] eq {aqua}} {
173 return 1
174 }
175 return 0
176}
177
178proc is_Windows {} {
179 if {$::tcl_platform(platform) eq {windows}} {
180 return 1
181 }
182 return 0
183}
184
185proc is_Cygwin {} {
186 global _iscygwin
187 if {$_iscygwin eq {}} {
188 if {$::tcl_platform(platform) eq {windows}} {
189 if {[catch {set p [exec cygpath --windir]} err]} {
190 set _iscygwin 0
191 } else {
192 set _iscygwin 1
193 }
194 } else {
195 set _iscygwin 0
196 }
197 }
198 return $_iscygwin
199}
200
201proc is_enabled {option} {
202 global enabled_options
203 if {[catch {set on $enabled_options($option)}]} {return 0}
204 return $on
205}
206
207proc enable_option {option} {
208 global enabled_options
209 set enabled_options($option) 1
210}
211
212proc disable_option {option} {
213 global enabled_options
214 set enabled_options($option) 0
215}
216
217######################################################################
218##
219## config
220
221proc is_many_config {name} {
222 switch -glob -- $name {
223 gui.recentrepo -
224 remote.*.fetch -
225 remote.*.push
226 {return 1}
227 *
228 {return 0}
229 }
230}
231
232proc is_config_true {name} {
233 global repo_config
234 if {[catch {set v $repo_config($name)}]} {
235 return 0
236 } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
237 return 1
238 } else {
239 return 0
240 }
241}
242
243proc get_config {name} {
244 global repo_config
245 if {[catch {set v $repo_config($name)}]} {
246 return {}
247 } else {
248 return $v
249 }
250}
251
252######################################################################
253##
254## handy utils
255
256proc _trace_exec {cmd} {
257 if {!$::_trace} return
258 set d {}
259 foreach v $cmd {
260 if {$d ne {}} {
261 append d { }
262 }
263 if {[regexp {[ \t\r\n'"$?*]} $v]} {
264 set v [sq $v]
265 }
266 append d $v
267 }
268 puts stderr $d
269}
270
271proc _git_cmd {name} {
272 global _git_cmd_path
273
274 if {[catch {set v $_git_cmd_path($name)}]} {
275 switch -- $name {
276 version -
277 --version -
278 --exec-path { return [list $::_git $name] }
279 }
280
281 set p [gitexec git-$name$::_search_exe]
282 if {[file exists $p]} {
283 set v [list $p]
284 } elseif {[is_Windows] && [file exists [gitexec git-$name]]} {
285 # Try to determine what sort of magic will make
286 # git-$name go and do its thing, because native
287 # Tcl on Windows doesn't know it.
288 #
289 set p [gitexec git-$name]
290 set f [open $p r]
291 set s [gets $f]
292 close $f
293
294 switch -glob -- [lindex $s 0] {
295 #!*sh { set i sh }
296 #!*perl { set i perl }
297 #!*python { set i python }
298 default { error "git-$name is not supported: $s" }
299 }
300
301 upvar #0 _$i interp
302 if {![info exists interp]} {
303 set interp [_which $i]
304 }
305 if {$interp eq {}} {
306 error "git-$name requires $i (not in PATH)"
307 }
308 set v [concat [list $interp] [lrange $s 1 end] [list $p]]
309 } else {
310 # Assume it is builtin to git somehow and we
311 # aren't actually able to see a file for it.
312 #
313 set v [list $::_git $name]
314 }
315 set _git_cmd_path($name) $v
316 }
317 return $v
318}
319
320proc _which {what} {
321 global env _search_exe _search_path
322
323 if {$_search_path eq {}} {
324 if {[is_Cygwin] && [regexp {^(/|\.:)} $env(PATH)]} {
325 set _search_path [split [exec cygpath \
326 --windows \
327 --path \
328 --absolute \
329 $env(PATH)] {;}]
330 set _search_exe .exe
331 } elseif {[is_Windows]} {
332 set gitguidir [file dirname [info script]]
333 regsub -all ";" $gitguidir "\\;" gitguidir
334 set env(PATH) "$gitguidir;$env(PATH)"
335 set _search_path [split $env(PATH) {;}]
336 set _search_exe .exe
337 } else {
338 set _search_path [split $env(PATH) :]
339 set _search_exe {}
340 }
341 }
342
343 foreach p $_search_path {
344 set p [file join $p $what$_search_exe]
345 if {[file exists $p]} {
346 return [file normalize $p]
347 }
348 }
349 return {}
350}
351
352proc _lappend_nice {cmd_var} {
353 global _nice
354 upvar $cmd_var cmd
355
356 if {![info exists _nice]} {
357 set _nice [_which nice]
358 }
359 if {$_nice ne {}} {
360 lappend cmd $_nice
361 }
362}
363
364proc git {args} {
365 set opt [list]
366
367 while {1} {
368 switch -- [lindex $args 0] {
369 --nice {
370 _lappend_nice opt
371 }
372
373 default {
374 break
375 }
376
377 }
378
379 set args [lrange $args 1 end]
380 }
381
382 set cmdp [_git_cmd [lindex $args 0]]
383 set args [lrange $args 1 end]
384
385 _trace_exec [concat $opt $cmdp $args]
386 set result [eval exec $opt $cmdp $args]
387 if {$::_trace} {
388 puts stderr "< $result"
389 }
390 return $result
391}
392
393proc _open_stdout_stderr {cmd} {
394 _trace_exec $cmd
395 if {[catch {
396 set fd [open [concat [list | ] $cmd] r]
397 } err]} {
398 if { [lindex $cmd end] eq {2>@1}
399 && $err eq {can not find channel named "1"}
400 } {
401 # Older versions of Tcl 8.4 don't have this 2>@1 IO
402 # redirect operator. Fallback to |& cat for those.
403 # The command was not actually started, so its safe
404 # to try to start it a second time.
405 #
406 set fd [open [concat \
407 [list | ] \
408 [lrange $cmd 0 end-1] \
409 [list |& cat] \
410 ] r]
411 } else {
412 error $err
413 }
414 }
415 fconfigure $fd -eofchar {}
416 return $fd
417}
418
419proc git_read {args} {
420 set opt [list]
421
422 while {1} {
423 switch -- [lindex $args 0] {
424 --nice {
425 _lappend_nice opt
426 }
427
428 --stderr {
429 lappend args 2>@1
430 }
431
432 default {
433 break
434 }
435
436 }
437
438 set args [lrange $args 1 end]
439 }
440
441 set cmdp [_git_cmd [lindex $args 0]]
442 set args [lrange $args 1 end]
443
444 return [_open_stdout_stderr [concat $opt $cmdp $args]]
445}
446
447proc git_write {args} {
448 set opt [list]
449
450 while {1} {
451 switch -- [lindex $args 0] {
452 --nice {
453 _lappend_nice opt
454 }
455
456 default {
457 break
458 }
459
460 }
461
462 set args [lrange $args 1 end]
463 }
464
465 set cmdp [_git_cmd [lindex $args 0]]
466 set args [lrange $args 1 end]
467
468 _trace_exec [concat $opt $cmdp $args]
469 return [open [concat [list | ] $opt $cmdp $args] w]
470}
471
472proc githook_read {hook_name args} {
473 set pchook [gitdir hooks $hook_name]
474 lappend args 2>@1
475
476 # On Windows [file executable] might lie so we need to ask
477 # the shell if the hook is executable. Yes that's annoying.
478 #
479 if {[is_Windows]} {
480 upvar #0 _sh interp
481 if {![info exists interp]} {
482 set interp [_which sh]
483 }
484 if {$interp eq {}} {
485 error "hook execution requires sh (not in PATH)"
486 }
487
488 set scr {if test -x "$1";then exec "$@";fi}
489 set sh_c [list $interp -c $scr $interp $pchook]
490 return [_open_stdout_stderr [concat $sh_c $args]]
491 }
492
493 if {[file executable $pchook]} {
494 return [_open_stdout_stderr [concat [list $pchook] $args]]
495 }
496
497 return {}
498}
499
500proc kill_file_process {fd} {
501 set process [pid $fd]
502
503 catch {
504 if {[is_Windows]} {
505 # Use a Cygwin-specific flag to allow killing
506 # native Windows processes
507 exec kill -f $process
508 } else {
509 exec kill $process
510 }
511 }
512}
513
514proc sq {value} {
515 regsub -all ' $value "'\\''" value
516 return "'$value'"
517}
518
519proc load_current_branch {} {
520 global current_branch is_detached
521
522 set fd [open [gitdir HEAD] r]
523 if {[gets $fd ref] < 1} {
524 set ref {}
525 }
526 close $fd
527
528 set pfx {ref: refs/heads/}
529 set len [string length $pfx]
530 if {[string equal -length $len $pfx $ref]} {
531 # We're on a branch. It might not exist. But
532 # HEAD looks good enough to be a branch.
533 #
534 set current_branch [string range $ref $len end]
535 set is_detached 0
536 } else {
537 # Assume this is a detached head.
538 #
539 set current_branch HEAD
540 set is_detached 1
541 }
542}
543
544auto_load tk_optionMenu
545rename tk_optionMenu real__tkOptionMenu
546proc tk_optionMenu {w varName args} {
547 set m [eval real__tkOptionMenu $w $varName $args]
548 $m configure -font font_ui
549 $w configure -font font_ui
550 return $m
551}
552
553proc rmsel_tag {text} {
554 $text tag conf sel \
555 -background [$text cget -background] \
556 -foreground [$text cget -foreground] \
557 -borderwidth 0
558 $text tag conf in_sel -background lightgray
559 bind $text <Motion> break
560 return $text
561}
562
563set root_exists 0
564bind . <Visibility> {
565 bind . <Visibility> {}
566 set root_exists 1
567}
568
569if {[is_Windows]} {
570 wm iconbitmap . -default $oguilib/git-gui.ico
571}
572
573######################################################################
574##
575## config defaults
576
577set cursor_ptr arrow
578font create font_diff -family Courier -size 10
579font create font_ui
580catch {
581 label .dummy
582 eval font configure font_ui [font actual [.dummy cget -font]]
583 destroy .dummy
584}
585
586font create font_uiitalic
587font create font_uibold
588font create font_diffbold
589font create font_diffitalic
590
591foreach class {Button Checkbutton Entry Label
592 Labelframe Listbox Menu Message
593 Radiobutton Spinbox Text} {
594 option add *$class.font font_ui
595}
596unset class
597
598if {[is_Windows] || [is_MacOSX]} {
599 option add *Menu.tearOff 0
600}
601
602if {[is_MacOSX]} {
603 set M1B M1
604 set M1T Cmd
605} else {
606 set M1B Control
607 set M1T Ctrl
608}
609
610proc bind_button3 {w cmd} {
611 bind $w <Any-Button-3> $cmd
612 if {[is_MacOSX]} {
613 # Mac OS X sends Button-2 on right click through three-button mouse,
614 # or through trackpad right-clicking (two-finger touch + click).
615 bind $w <Any-Button-2> $cmd
616 bind $w <Control-Button-1> $cmd
617 }
618}
619
620proc apply_config {} {
621 global repo_config font_descs
622
623 foreach option $font_descs {
624 set name [lindex $option 0]
625 set font [lindex $option 1]
626 if {[catch {
627 set need_weight 1
628 foreach {cn cv} $repo_config(gui.$name) {
629 if {$cn eq {-weight}} {
630 set need_weight 0
631 }
632 font configure $font $cn $cv
633 }
634 if {$need_weight} {
635 font configure $font -weight normal
636 }
637 } err]} {
638 error_popup [strcat [mc "Invalid font specified in %s:" "gui.$name"] "\n\n$err"]
639 }
640 foreach {cn cv} [font configure $font] {
641 font configure ${font}bold $cn $cv
642 font configure ${font}italic $cn $cv
643 }
644 font configure ${font}bold -weight bold
645 font configure ${font}italic -slant italic
646 }
647}
648
649set default_config(branch.autosetupmerge) true
650set default_config(merge.diffstat) true
651set default_config(merge.summary) false
652set default_config(merge.verbosity) 2
653set default_config(user.name) {}
654set default_config(user.email) {}
655
656set default_config(gui.matchtrackingbranch) false
657set default_config(gui.pruneduringfetch) false
658set default_config(gui.trustmtime) false
659set default_config(gui.fastcopyblame) false
660set default_config(gui.copyblamethreshold) 40
661set default_config(gui.diffcontext) 5
662set default_config(gui.commitmsgwidth) 75
663set default_config(gui.newbranchtemplate) {}
664set default_config(gui.spellingdictionary) {}
665set default_config(gui.fontui) [font configure font_ui]
666set default_config(gui.fontdiff) [font configure font_diff]
667set font_descs {
668 {fontui font_ui {mc "Main Font"}}
669 {fontdiff font_diff {mc "Diff/Console Font"}}
670}
671
672######################################################################
673##
674## find git
675
676set _git [_which git]
677if {$_git eq {}} {
678 catch {wm withdraw .}
679 tk_messageBox \
680 -icon error \
681 -type ok \
682 -title [mc "git-gui: fatal error"] \
683 -message [mc "Cannot find git in PATH."]
684 exit 1
685}
686
687######################################################################
688##
689## version check
690
691if {[catch {set _git_version [git --version]} err]} {
692 catch {wm withdraw .}
693 tk_messageBox \
694 -icon error \
695 -type ok \
696 -title [mc "git-gui: fatal error"] \
697 -message "Cannot determine Git version:
698
699$err
700
701[appname] requires Git 1.5.0 or later."
702 exit 1
703}
704if {![regsub {^git version } $_git_version {} _git_version]} {
705 catch {wm withdraw .}
706 tk_messageBox \
707 -icon error \
708 -type ok \
709 -title [mc "git-gui: fatal error"] \
710 -message [strcat [mc "Cannot parse Git version string:"] "\n\n$_git_version"]
711 exit 1
712}
713
714set _real_git_version $_git_version
715regsub -- {[\-\.]dirty$} $_git_version {} _git_version
716regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version
717regsub {\.rc[0-9]+$} $_git_version {} _git_version
718regsub {\.GIT$} $_git_version {} _git_version
719regsub {\.[a-zA-Z]+\.[0-9]+$} $_git_version {} _git_version
720
721if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} {
722 catch {wm withdraw .}
723 if {[tk_messageBox \
724 -icon warning \
725 -type yesno \
726 -default no \
727 -title "[appname]: warning" \
728 -message [mc "Git version cannot be determined.
729
730%s claims it is version '%s'.
731
732%s requires at least Git 1.5.0 or later.
733
734Assume '%s' is version 1.5.0?
735" $_git $_real_git_version [appname] $_real_git_version]] eq {yes}} {
736 set _git_version 1.5.0
737 } else {
738 exit 1
739 }
740}
741unset _real_git_version
742
743proc git-version {args} {
744 global _git_version
745
746 switch [llength $args] {
747 0 {
748 return $_git_version
749 }
750
751 2 {
752 set op [lindex $args 0]
753 set vr [lindex $args 1]
754 set cm [package vcompare $_git_version $vr]
755 return [expr $cm $op 0]
756 }
757
758 4 {
759 set type [lindex $args 0]
760 set name [lindex $args 1]
761 set parm [lindex $args 2]
762 set body [lindex $args 3]
763
764 if {($type ne {proc} && $type ne {method})} {
765 error "Invalid arguments to git-version"
766 }
767 if {[llength $body] < 2 || [lindex $body end-1] ne {default}} {
768 error "Last arm of $type $name must be default"
769 }
770
771 foreach {op vr cb} [lrange $body 0 end-2] {
772 if {[git-version $op $vr]} {
773 return [uplevel [list $type $name $parm $cb]]
774 }
775 }
776
777 return [uplevel [list $type $name $parm [lindex $body end]]]
778 }
779
780 default {
781 error "git-version >= x"
782 }
783
784 }
785}
786
787if {[git-version < 1.5]} {
788 catch {wm withdraw .}
789 tk_messageBox \
790 -icon error \
791 -type ok \
792 -title [mc "git-gui: fatal error"] \
793 -message "[appname] requires Git 1.5.0 or later.
794
795You are using [git-version]:
796
797[git --version]"
798 exit 1
799}
800
801######################################################################
802##
803## configure our library
804
805set idx [file join $oguilib tclIndex]
806if {[catch {set fd [open $idx r]} err]} {
807 catch {wm withdraw .}
808 tk_messageBox \
809 -icon error \
810 -type ok \
811 -title [mc "git-gui: fatal error"] \
812 -message $err
813 exit 1
814}
815if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
816 set idx [list]
817 while {[gets $fd n] >= 0} {
818 if {$n ne {} && ![string match #* $n]} {
819 lappend idx $n
820 }
821 }
822} else {
823 set idx {}
824}
825close $fd
826
827if {$idx ne {}} {
828 set loaded [list]
829 foreach p $idx {
830 if {[lsearch -exact $loaded $p] >= 0} continue
831 source [file join $oguilib $p]
832 lappend loaded $p
833 }
834 unset loaded p
835} else {
836 set auto_path [concat [list $oguilib] $auto_path]
837}
838unset -nocomplain idx fd
839
840######################################################################
841##
842## config file parsing
843
844git-version proc _parse_config {arr_name args} {
845 >= 1.5.3 {
846 upvar $arr_name arr
847 array unset arr
848 set buf {}
849 catch {
850 set fd_rc [eval \
851 [list git_read config] \
852 $args \
853 [list --null --list]]
854 fconfigure $fd_rc -translation binary
855 set buf [read $fd_rc]
856 close $fd_rc
857 }
858 foreach line [split $buf "\0"] {
859 if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} {
860 if {[is_many_config $name]} {
861 lappend arr($name) $value
862 } else {
863 set arr($name) $value
864 }
865 }
866 }
867 }
868 default {
869 upvar $arr_name arr
870 array unset arr
871 catch {
872 set fd_rc [eval [list git_read config --list] $args]
873 while {[gets $fd_rc line] >= 0} {
874 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
875 if {[is_many_config $name]} {
876 lappend arr($name) $value
877 } else {
878 set arr($name) $value
879 }
880 }
881 }
882 close $fd_rc
883 }
884 }
885}
886
887proc load_config {include_global} {
888 global repo_config global_config default_config
889
890 if {$include_global} {
891 _parse_config global_config --global
892 }
893 _parse_config repo_config
894
895 foreach name [array names default_config] {
896 if {[catch {set v $global_config($name)}]} {
897 set global_config($name) $default_config($name)
898 }
899 if {[catch {set v $repo_config($name)}]} {
900 set repo_config($name) $default_config($name)
901 }
902 }
903}
904
905######################################################################
906##
907## feature option selection
908
909if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} {
910 unset _junk
911} else {
912 set subcommand gui
913}
914if {$subcommand eq {gui.sh}} {
915 set subcommand gui
916}
917if {$subcommand eq {gui} && [llength $argv] > 0} {
918 set subcommand [lindex $argv 0]
919 set argv [lrange $argv 1 end]
920}
921
922enable_option multicommit
923enable_option branch
924enable_option transport
925disable_option bare
926
927switch -- $subcommand {
928browser -
929blame {
930 enable_option bare
931
932 disable_option multicommit
933 disable_option branch
934 disable_option transport
935}
936citool {
937 enable_option singlecommit
938
939 disable_option multicommit
940 disable_option branch
941 disable_option transport
942}
943}
944
945######################################################################
946##
947## repository setup
948
949if {[catch {
950 set _gitdir $env(GIT_DIR)
951 set _prefix {}
952 }]
953 && [catch {
954 set _gitdir [git rev-parse --git-dir]
955 set _prefix [git rev-parse --show-prefix]
956 } err]} {
957 load_config 1
958 apply_config
959 choose_repository::pick
960}
961if {![file isdirectory $_gitdir] && [is_Cygwin]} {
962 catch {set _gitdir [exec cygpath --windows $_gitdir]}
963}
964if {![file isdirectory $_gitdir]} {
965 catch {wm withdraw .}
966 error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"]
967 exit 1
968}
969if {$_prefix ne {}} {
970 regsub -all {[^/]+/} $_prefix ../ cdup
971 if {[catch {cd $cdup} err]} {
972 catch {wm withdraw .}
973 error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"]
974 exit 1
975 }
976 unset cdup
977} elseif {![is_enabled bare]} {
978 if {[lindex [file split $_gitdir] end] ne {.git}} {
979 catch {wm withdraw .}
980 error_popup [strcat [mc "Cannot use funny .git directory:"] "\n\n$_gitdir"]
981 exit 1
982 }
983 if {[catch {cd [file dirname $_gitdir]} err]} {
984 catch {wm withdraw .}
985 error_popup [strcat [mc "No working directory"] " [file dirname $_gitdir]:\n\n$err"]
986 exit 1
987 }
988}
989set _reponame [file split [file normalize $_gitdir]]
990if {[lindex $_reponame end] eq {.git}} {
991 set _reponame [lindex $_reponame end-1]
992} else {
993 set _reponame [lindex $_reponame end]
994}
995
996######################################################################
997##
998## global init
999
1000set current_diff_path {}
1001set current_diff_side {}
1002set diff_actions [list]
1003
1004set HEAD {}
1005set PARENT {}
1006set MERGE_HEAD [list]
1007set commit_type {}
1008set empty_tree {}
1009set current_branch {}
1010set is_detached 0
1011set current_diff_path {}
1012set is_3way_diff 0
1013set selected_commit_type new
1014
1015######################################################################
1016##
1017## task management
1018
1019set rescan_active 0
1020set diff_active 0
1021set last_clicked {}
1022
1023set disable_on_lock [list]
1024set index_lock_type none
1025
1026proc lock_index {type} {
1027 global index_lock_type disable_on_lock
1028
1029 if {$index_lock_type eq {none}} {
1030 set index_lock_type $type
1031 foreach w $disable_on_lock {
1032 uplevel #0 $w disabled
1033 }
1034 return 1
1035 } elseif {$index_lock_type eq "begin-$type"} {
1036 set index_lock_type $type
1037 return 1
1038 }
1039 return 0
1040}
1041
1042proc unlock_index {} {
1043 global index_lock_type disable_on_lock
1044
1045 set index_lock_type none
1046 foreach w $disable_on_lock {
1047 uplevel #0 $w normal
1048 }
1049}
1050
1051######################################################################
1052##
1053## status
1054
1055proc repository_state {ctvar hdvar mhvar} {
1056 global current_branch
1057 upvar $ctvar ct $hdvar hd $mhvar mh
1058
1059 set mh [list]
1060
1061 load_current_branch
1062 if {[catch {set hd [git rev-parse --verify HEAD]}]} {
1063 set hd {}
1064 set ct initial
1065 return
1066 }
1067
1068 set merge_head [gitdir MERGE_HEAD]
1069 if {[file exists $merge_head]} {
1070 set ct merge
1071 set fd_mh [open $merge_head r]
1072 while {[gets $fd_mh line] >= 0} {
1073 lappend mh $line
1074 }
1075 close $fd_mh
1076 return
1077 }
1078
1079 set ct normal
1080}
1081
1082proc PARENT {} {
1083 global PARENT empty_tree
1084
1085 set p [lindex $PARENT 0]
1086 if {$p ne {}} {
1087 return $p
1088 }
1089 if {$empty_tree eq {}} {
1090 set empty_tree [git mktree << {}]
1091 }
1092 return $empty_tree
1093}
1094
1095proc rescan {after {honor_trustmtime 1}} {
1096 global HEAD PARENT MERGE_HEAD commit_type
1097 global ui_index ui_workdir ui_comm
1098 global rescan_active file_states
1099 global repo_config
1100
1101 if {$rescan_active > 0 || ![lock_index read]} return
1102
1103 repository_state newType newHEAD newMERGE_HEAD
1104 if {[string match amend* $commit_type]
1105 && $newType eq {normal}
1106 && $newHEAD eq $HEAD} {
1107 } else {
1108 set HEAD $newHEAD
1109 set PARENT $newHEAD
1110 set MERGE_HEAD $newMERGE_HEAD
1111 set commit_type $newType
1112 }
1113
1114 array unset file_states
1115
1116 if {!$::GITGUI_BCK_exists &&
1117 (![$ui_comm edit modified]
1118 || [string trim [$ui_comm get 0.0 end]] eq {})} {
1119 if {[string match amend* $commit_type]} {
1120 } elseif {[load_message GITGUI_MSG]} {
1121 } elseif {[load_message MERGE_MSG]} {
1122 } elseif {[load_message SQUASH_MSG]} {
1123 }
1124 $ui_comm edit reset
1125 $ui_comm edit modified false
1126 }
1127
1128 if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
1129 rescan_stage2 {} $after
1130 } else {
1131 set rescan_active 1
1132 ui_status [mc "Refreshing file status..."]
1133 set fd_rf [git_read update-index \
1134 -q \
1135 --unmerged \
1136 --ignore-missing \
1137 --refresh \
1138 ]
1139 fconfigure $fd_rf -blocking 0 -translation binary
1140 fileevent $fd_rf readable \
1141 [list rescan_stage2 $fd_rf $after]
1142 }
1143}
1144
1145if {[is_Cygwin]} {
1146 set is_git_info_exclude {}
1147 proc have_info_exclude {} {
1148 global is_git_info_exclude
1149
1150 if {$is_git_info_exclude eq {}} {
1151 if {[catch {exec test -f [gitdir info exclude]}]} {
1152 set is_git_info_exclude 0
1153 } else {
1154 set is_git_info_exclude 1
1155 }
1156 }
1157 return $is_git_info_exclude
1158 }
1159} else {
1160 proc have_info_exclude {} {
1161 return [file readable [gitdir info exclude]]
1162 }
1163}
1164
1165proc rescan_stage2 {fd after} {
1166 global rescan_active buf_rdi buf_rdf buf_rlo
1167
1168 if {$fd ne {}} {
1169 read $fd
1170 if {![eof $fd]} return
1171 close $fd
1172 }
1173
1174 set ls_others [list --exclude-per-directory=.gitignore]
1175 if {[have_info_exclude]} {
1176 lappend ls_others "--exclude-from=[gitdir info exclude]"
1177 }
1178 set user_exclude [get_config core.excludesfile]
1179 if {$user_exclude ne {} && [file readable $user_exclude]} {
1180 lappend ls_others "--exclude-from=$user_exclude"
1181 }
1182
1183 set buf_rdi {}
1184 set buf_rdf {}
1185 set buf_rlo {}
1186
1187 set rescan_active 3
1188 ui_status [mc "Scanning for modified files ..."]
1189 set fd_di [git_read diff-index --cached -z [PARENT]]
1190 set fd_df [git_read diff-files -z]
1191 set fd_lo [eval git_read ls-files --others -z $ls_others]
1192
1193 fconfigure $fd_di -blocking 0 -translation binary -encoding binary
1194 fconfigure $fd_df -blocking 0 -translation binary -encoding binary
1195 fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
1196 fileevent $fd_di readable [list read_diff_index $fd_di $after]
1197 fileevent $fd_df readable [list read_diff_files $fd_df $after]
1198 fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
1199}
1200
1201proc load_message {file} {
1202 global ui_comm
1203
1204 set f [gitdir $file]
1205 if {[file isfile $f]} {
1206 if {[catch {set fd [open $f r]}]} {
1207 return 0
1208 }
1209 fconfigure $fd -eofchar {}
1210 set content [string trim [read $fd]]
1211 close $fd
1212 regsub -all -line {[ \r\t]+$} $content {} content
1213 $ui_comm delete 0.0 end
1214 $ui_comm insert end $content
1215 return 1
1216 }
1217 return 0
1218}
1219
1220proc read_diff_index {fd after} {
1221 global buf_rdi
1222
1223 append buf_rdi [read $fd]
1224 set c 0
1225 set n [string length $buf_rdi]
1226 while {$c < $n} {
1227 set z1 [string first "\0" $buf_rdi $c]
1228 if {$z1 == -1} break
1229 incr z1
1230 set z2 [string first "\0" $buf_rdi $z1]
1231 if {$z2 == -1} break
1232
1233 incr c
1234 set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
1235 set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
1236 merge_state \
1237 [encoding convertfrom $p] \
1238 [lindex $i 4]? \
1239 [list [lindex $i 0] [lindex $i 2]] \
1240 [list]
1241 set c $z2
1242 incr c
1243 }
1244 if {$c < $n} {
1245 set buf_rdi [string range $buf_rdi $c end]
1246 } else {
1247 set buf_rdi {}
1248 }
1249
1250 rescan_done $fd buf_rdi $after
1251}
1252
1253proc read_diff_files {fd after} {
1254 global buf_rdf
1255
1256 append buf_rdf [read $fd]
1257 set c 0
1258 set n [string length $buf_rdf]
1259 while {$c < $n} {
1260 set z1 [string first "\0" $buf_rdf $c]
1261 if {$z1 == -1} break
1262 incr z1
1263 set z2 [string first "\0" $buf_rdf $z1]
1264 if {$z2 == -1} break
1265
1266 incr c
1267 set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
1268 set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
1269 merge_state \
1270 [encoding convertfrom $p] \
1271 ?[lindex $i 4] \
1272 [list] \
1273 [list [lindex $i 0] [lindex $i 2]]
1274 set c $z2
1275 incr c
1276 }
1277 if {$c < $n} {
1278 set buf_rdf [string range $buf_rdf $c end]
1279 } else {
1280 set buf_rdf {}
1281 }
1282
1283 rescan_done $fd buf_rdf $after
1284}
1285
1286proc read_ls_others {fd after} {
1287 global buf_rlo
1288
1289 append buf_rlo [read $fd]
1290 set pck [split $buf_rlo "\0"]
1291 set buf_rlo [lindex $pck end]
1292 foreach p [lrange $pck 0 end-1] {
1293 set p [encoding convertfrom $p]
1294 if {[string index $p end] eq {/}} {
1295 set p [string range $p 0 end-1]
1296 }
1297 merge_state $p ?O
1298 }
1299 rescan_done $fd buf_rlo $after
1300}
1301
1302proc rescan_done {fd buf after} {
1303 global rescan_active current_diff_path
1304 global file_states repo_config
1305 upvar $buf to_clear
1306
1307 if {![eof $fd]} return
1308 set to_clear {}
1309 close $fd
1310 if {[incr rescan_active -1] > 0} return
1311
1312 prune_selection
1313 unlock_index
1314 display_all_files
1315 if {$current_diff_path ne {}} reshow_diff
1316 uplevel #0 $after
1317}
1318
1319proc prune_selection {} {
1320 global file_states selected_paths
1321
1322 foreach path [array names selected_paths] {
1323 if {[catch {set still_here $file_states($path)}]} {
1324 unset selected_paths($path)
1325 }
1326 }
1327}
1328
1329######################################################################
1330##
1331## ui helpers
1332
1333proc mapicon {w state path} {
1334 global all_icons
1335
1336 if {[catch {set r $all_icons($state$w)}]} {
1337 puts "error: no icon for $w state={$state} $path"
1338 return file_plain
1339 }
1340 return $r
1341}
1342
1343proc mapdesc {state path} {
1344 global all_descs
1345
1346 if {[catch {set r $all_descs($state)}]} {
1347 puts "error: no desc for state={$state} $path"
1348 return $state
1349 }
1350 return $r
1351}
1352
1353proc ui_status {msg} {
1354 global main_status
1355 if {[info exists main_status]} {
1356 $main_status show $msg
1357 }
1358}
1359
1360proc ui_ready {{test {}}} {
1361 global main_status
1362 if {[info exists main_status]} {
1363 $main_status show [mc "Ready."] $test
1364 }
1365}
1366
1367proc escape_path {path} {
1368 regsub -all {\\} $path "\\\\" path
1369 regsub -all "\n" $path "\\n" path
1370 return $path
1371}
1372
1373proc short_path {path} {
1374 return [escape_path [lindex [file split $path] end]]
1375}
1376
1377set next_icon_id 0
1378set null_sha1 [string repeat 0 40]
1379
1380proc merge_state {path new_state {head_info {}} {index_info {}}} {
1381 global file_states next_icon_id null_sha1
1382
1383 set s0 [string index $new_state 0]
1384 set s1 [string index $new_state 1]
1385
1386 if {[catch {set info $file_states($path)}]} {
1387 set state __
1388 set icon n[incr next_icon_id]
1389 } else {
1390 set state [lindex $info 0]
1391 set icon [lindex $info 1]
1392 if {$head_info eq {}} {set head_info [lindex $info 2]}
1393 if {$index_info eq {}} {set index_info [lindex $info 3]}
1394 }
1395
1396 if {$s0 eq {?}} {set s0 [string index $state 0]} \
1397 elseif {$s0 eq {_}} {set s0 _}
1398
1399 if {$s1 eq {?}} {set s1 [string index $state 1]} \
1400 elseif {$s1 eq {_}} {set s1 _}
1401
1402 if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
1403 set head_info [list 0 $null_sha1]
1404 } elseif {$s0 ne {_} && [string index $state 0] eq {_}
1405 && $head_info eq {}} {
1406 set head_info $index_info
1407 }
1408
1409 set file_states($path) [list $s0$s1 $icon \
1410 $head_info $index_info \
1411 ]
1412 return $state
1413}
1414
1415proc display_file_helper {w path icon_name old_m new_m} {
1416 global file_lists
1417
1418 if {$new_m eq {_}} {
1419 set lno [lsearch -sorted -exact $file_lists($w) $path]
1420 if {$lno >= 0} {
1421 set file_lists($w) [lreplace $file_lists($w) $lno $lno]
1422 incr lno
1423 $w conf -state normal
1424 $w delete $lno.0 [expr {$lno + 1}].0
1425 $w conf -state disabled
1426 }
1427 } elseif {$old_m eq {_} && $new_m ne {_}} {
1428 lappend file_lists($w) $path
1429 set file_lists($w) [lsort -unique $file_lists($w)]
1430 set lno [lsearch -sorted -exact $file_lists($w) $path]
1431 incr lno
1432 $w conf -state normal
1433 $w image create $lno.0 \
1434 -align center -padx 5 -pady 1 \
1435 -name $icon_name \
1436 -image [mapicon $w $new_m $path]
1437 $w insert $lno.1 "[escape_path $path]\n"
1438 $w conf -state disabled
1439 } elseif {$old_m ne $new_m} {
1440 $w conf -state normal
1441 $w image conf $icon_name -image [mapicon $w $new_m $path]
1442 $w conf -state disabled
1443 }
1444}
1445
1446proc display_file {path state} {
1447 global file_states selected_paths
1448 global ui_index ui_workdir
1449
1450 set old_m [merge_state $path $state]
1451 set s $file_states($path)
1452 set new_m [lindex $s 0]
1453 set icon_name [lindex $s 1]
1454
1455 set o [string index $old_m 0]
1456 set n [string index $new_m 0]
1457 if {$o eq {U}} {
1458 set o _
1459 }
1460 if {$n eq {U}} {
1461 set n _
1462 }
1463 display_file_helper $ui_index $path $icon_name $o $n
1464
1465 if {[string index $old_m 0] eq {U}} {
1466 set o U
1467 } else {
1468 set o [string index $old_m 1]
1469 }
1470 if {[string index $new_m 0] eq {U}} {
1471 set n U
1472 } else {
1473 set n [string index $new_m 1]
1474 }
1475 display_file_helper $ui_workdir $path $icon_name $o $n
1476
1477 if {$new_m eq {__}} {
1478 unset file_states($path)
1479 catch {unset selected_paths($path)}
1480 }
1481}
1482
1483proc display_all_files_helper {w path icon_name m} {
1484 global file_lists
1485
1486 lappend file_lists($w) $path
1487 set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
1488 $w image create end \
1489 -align center -padx 5 -pady 1 \
1490 -name $icon_name \
1491 -image [mapicon $w $m $path]
1492 $w insert end "[escape_path $path]\n"
1493}
1494
1495proc display_all_files {} {
1496 global ui_index ui_workdir
1497 global file_states file_lists
1498 global last_clicked
1499
1500 $ui_index conf -state normal
1501 $ui_workdir conf -state normal
1502
1503 $ui_index delete 0.0 end
1504 $ui_workdir delete 0.0 end
1505 set last_clicked {}
1506
1507 set file_lists($ui_index) [list]
1508 set file_lists($ui_workdir) [list]
1509
1510 foreach path [lsort [array names file_states]] {
1511 set s $file_states($path)
1512 set m [lindex $s 0]
1513 set icon_name [lindex $s 1]
1514
1515 set s [string index $m 0]
1516 if {$s ne {U} && $s ne {_}} {
1517 display_all_files_helper $ui_index $path \
1518 $icon_name $s
1519 }
1520
1521 if {[string index $m 0] eq {U}} {
1522 set s U
1523 } else {
1524 set s [string index $m 1]
1525 }
1526 if {$s ne {_}} {
1527 display_all_files_helper $ui_workdir $path \
1528 $icon_name $s
1529 }
1530 }
1531
1532 $ui_index conf -state disabled
1533 $ui_workdir conf -state disabled
1534}
1535
1536######################################################################
1537##
1538## icons
1539
1540set filemask {
1541#define mask_width 14
1542#define mask_height 15
1543static unsigned char mask_bits[] = {
1544 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1545 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1546 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1547}
1548
1549image create bitmap file_plain -background white -foreground black -data {
1550#define plain_width 14
1551#define plain_height 15
1552static unsigned char plain_bits[] = {
1553 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1554 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1555 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1556} -maskdata $filemask
1557
1558image create bitmap file_mod -background white -foreground blue -data {
1559#define mod_width 14
1560#define mod_height 15
1561static unsigned char mod_bits[] = {
1562 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1563 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1564 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1565} -maskdata $filemask
1566
1567image create bitmap file_fulltick -background white -foreground "#007000" -data {
1568#define file_fulltick_width 14
1569#define file_fulltick_height 15
1570static unsigned char file_fulltick_bits[] = {
1571 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1572 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1573 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1574} -maskdata $filemask
1575
1576image create bitmap file_parttick -background white -foreground "#005050" -data {
1577#define parttick_width 14
1578#define parttick_height 15
1579static unsigned char parttick_bits[] = {
1580 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1581 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1582 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1583} -maskdata $filemask
1584
1585image create bitmap file_question -background white -foreground black -data {
1586#define file_question_width 14
1587#define file_question_height 15
1588static unsigned char file_question_bits[] = {
1589 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1590 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1591 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1592} -maskdata $filemask
1593
1594image create bitmap file_removed -background white -foreground red -data {
1595#define file_removed_width 14
1596#define file_removed_height 15
1597static unsigned char file_removed_bits[] = {
1598 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1599 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1600 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1601} -maskdata $filemask
1602
1603image create bitmap file_merge -background white -foreground blue -data {
1604#define file_merge_width 14
1605#define file_merge_height 15
1606static unsigned char file_merge_bits[] = {
1607 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1608 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1609 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1610} -maskdata $filemask
1611
1612set ui_index .vpane.files.index.list
1613set ui_workdir .vpane.files.workdir.list
1614
1615set all_icons(_$ui_index) file_plain
1616set all_icons(A$ui_index) file_fulltick
1617set all_icons(M$ui_index) file_fulltick
1618set all_icons(D$ui_index) file_removed
1619set all_icons(U$ui_index) file_merge
1620
1621set all_icons(_$ui_workdir) file_plain
1622set all_icons(M$ui_workdir) file_mod
1623set all_icons(D$ui_workdir) file_question
1624set all_icons(U$ui_workdir) file_merge
1625set all_icons(O$ui_workdir) file_plain
1626
1627set max_status_desc 0
1628foreach i {
1629 {__ {mc "Unmodified"}}
1630
1631 {_M {mc "Modified, not staged"}}
1632 {M_ {mc "Staged for commit"}}
1633 {MM {mc "Portions staged for commit"}}
1634 {MD {mc "Staged for commit, missing"}}
1635
1636 {_O {mc "Untracked, not staged"}}
1637 {A_ {mc "Staged for commit"}}
1638 {AM {mc "Portions staged for commit"}}
1639 {AD {mc "Staged for commit, missing"}}
1640
1641 {_D {mc "Missing"}}
1642 {D_ {mc "Staged for removal"}}
1643 {DO {mc "Staged for removal, still present"}}
1644
1645 {U_ {mc "Requires merge resolution"}}
1646 {UU {mc "Requires merge resolution"}}
1647 {UM {mc "Requires merge resolution"}}
1648 {UD {mc "Requires merge resolution"}}
1649 } {
1650 set text [eval [lindex $i 1]]
1651 if {$max_status_desc < [string length $text]} {
1652 set max_status_desc [string length $text]
1653 }
1654 set all_descs([lindex $i 0]) $text
1655}
1656unset i
1657
1658######################################################################
1659##
1660## util
1661
1662proc scrollbar2many {list mode args} {
1663 foreach w $list {eval $w $mode $args}
1664}
1665
1666proc many2scrollbar {list mode sb top bottom} {
1667 $sb set $top $bottom
1668 foreach w $list {$w $mode moveto $top}
1669}
1670
1671proc incr_font_size {font {amt 1}} {
1672 set sz [font configure $font -size]
1673 incr sz $amt
1674 font configure $font -size $sz
1675 font configure ${font}bold -size $sz
1676 font configure ${font}italic -size $sz
1677}
1678
1679######################################################################
1680##
1681## ui commands
1682
1683set starting_gitk_msg [mc "Starting gitk... please wait..."]
1684
1685proc do_gitk {revs} {
1686 # -- Always start gitk through whatever we were loaded with. This
1687 # lets us bypass using shell process on Windows systems.
1688 #
1689 set exe [_which gitk]
1690 set cmd [list [info nameofexecutable] $exe]
1691 if {$exe eq {}} {
1692 error_popup [mc "Couldn't find gitk in PATH"]
1693 } else {
1694 global env
1695
1696 if {[info exists env(GIT_DIR)]} {
1697 set old_GIT_DIR $env(GIT_DIR)
1698 } else {
1699 set old_GIT_DIR {}
1700 }
1701
1702 set pwd [pwd]
1703 cd [file dirname [gitdir]]
1704 set env(GIT_DIR) [file tail [gitdir]]
1705
1706 eval exec $cmd $revs &
1707
1708 if {$old_GIT_DIR eq {}} {
1709 unset env(GIT_DIR)
1710 } else {
1711 set env(GIT_DIR) $old_GIT_DIR
1712 }
1713 cd $pwd
1714
1715 ui_status $::starting_gitk_msg
1716 after 10000 {
1717 ui_ready $starting_gitk_msg
1718 }
1719 }
1720}
1721
1722set is_quitting 0
1723
1724proc do_quit {} {
1725 global ui_comm is_quitting repo_config commit_type
1726 global GITGUI_BCK_exists GITGUI_BCK_i
1727 global ui_comm_spell
1728
1729 if {$is_quitting} return
1730 set is_quitting 1
1731
1732 if {[winfo exists $ui_comm]} {
1733 # -- Stash our current commit buffer.
1734 #
1735 set save [gitdir GITGUI_MSG]
1736 if {$GITGUI_BCK_exists && ![$ui_comm edit modified]} {
1737 file rename -force [gitdir GITGUI_BCK] $save
1738 set GITGUI_BCK_exists 0
1739 } else {
1740 set msg [string trim [$ui_comm get 0.0 end]]
1741 regsub -all -line {[ \r\t]+$} $msg {} msg
1742 if {(![string match amend* $commit_type]
1743 || [$ui_comm edit modified])
1744 && $msg ne {}} {
1745 catch {
1746 set fd [open $save w]
1747 puts -nonewline $fd $msg
1748 close $fd
1749 }
1750 } else {
1751 catch {file delete $save}
1752 }
1753 }
1754
1755 # -- Cancel our spellchecker if its running.
1756 #
1757 if {[info exists ui_comm_spell]} {
1758 $ui_comm_spell stop
1759 }
1760
1761 # -- Remove our editor backup, its not needed.
1762 #
1763 after cancel $GITGUI_BCK_i
1764 if {$GITGUI_BCK_exists} {
1765 catch {file delete [gitdir GITGUI_BCK]}
1766 }
1767
1768 # -- Stash our current window geometry into this repository.
1769 #
1770 set cfg_geometry [list]
1771 lappend cfg_geometry [wm geometry .]
1772 lappend cfg_geometry [lindex [.vpane sash coord 0] 0]
1773 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 1]
1774 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1775 set rc_geometry {}
1776 }
1777 if {$cfg_geometry ne $rc_geometry} {
1778 catch {git config gui.geometry $cfg_geometry}
1779 }
1780 }
1781
1782 destroy .
1783}
1784
1785proc do_rescan {} {
1786 rescan ui_ready
1787}
1788
1789proc do_commit {} {
1790 commit_tree
1791}
1792
1793proc next_diff {} {
1794 global next_diff_p next_diff_w next_diff_i
1795 show_diff $next_diff_p $next_diff_w $next_diff_i
1796}
1797
1798proc toggle_or_diff {w x y} {
1799 global file_states file_lists current_diff_path ui_index ui_workdir
1800 global last_clicked selected_paths
1801
1802 set pos [split [$w index @$x,$y] .]
1803 set lno [lindex $pos 0]
1804 set col [lindex $pos 1]
1805 set path [lindex $file_lists($w) [expr {$lno - 1}]]
1806 if {$path eq {}} {
1807 set last_clicked {}
1808 return
1809 }
1810
1811 set last_clicked [list $w $lno]
1812 array unset selected_paths
1813 $ui_index tag remove in_sel 0.0 end
1814 $ui_workdir tag remove in_sel 0.0 end
1815
1816 if {$col == 0 && $y > 1} {
1817 set i [expr {$lno-1}]
1818 set ll [expr {[llength $file_lists($w)]-1}]
1819
1820 if {$i == $ll && $i == 0} {
1821 set after {reshow_diff;}
1822 } else {
1823 global next_diff_p next_diff_w next_diff_i
1824
1825 set next_diff_w $w
1826
1827 if {$i < $ll} {
1828 set i [expr {$i + 1}]
1829 set next_diff_i $i
1830 } else {
1831 set next_diff_i $i
1832 set i [expr {$i - 1}]
1833 }
1834
1835 set next_diff_p [lindex $file_lists($w) $i]
1836
1837 if {$next_diff_p ne {} && $current_diff_path ne {}} {
1838 set after {next_diff;}
1839 } else {
1840 set after {}
1841 }
1842 }
1843
1844 if {$w eq $ui_index} {
1845 update_indexinfo \
1846 "Unstaging [short_path $path] from commit" \
1847 [list $path] \
1848 [concat $after [list ui_ready]]
1849 } elseif {$w eq $ui_workdir} {
1850 update_index \
1851 "Adding [short_path $path]" \
1852 [list $path] \
1853 [concat $after [list ui_ready]]
1854 }
1855 } else {
1856 show_diff $path $w $lno
1857 }
1858}
1859
1860proc add_one_to_selection {w x y} {
1861 global file_lists last_clicked selected_paths
1862
1863 set lno [lindex [split [$w index @$x,$y] .] 0]
1864 set path [lindex $file_lists($w) [expr {$lno - 1}]]
1865 if {$path eq {}} {
1866 set last_clicked {}
1867 return
1868 }
1869
1870 if {$last_clicked ne {}
1871 && [lindex $last_clicked 0] ne $w} {
1872 array unset selected_paths
1873 [lindex $last_clicked 0] tag remove in_sel 0.0 end
1874 }
1875
1876 set last_clicked [list $w $lno]
1877 if {[catch {set in_sel $selected_paths($path)}]} {
1878 set in_sel 0
1879 }
1880 if {$in_sel} {
1881 unset selected_paths($path)
1882 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
1883 } else {
1884 set selected_paths($path) 1
1885 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
1886 }
1887}
1888
1889proc add_range_to_selection {w x y} {
1890 global file_lists last_clicked selected_paths
1891
1892 if {[lindex $last_clicked 0] ne $w} {
1893 toggle_or_diff $w $x $y
1894 return
1895 }
1896
1897 set lno [lindex [split [$w index @$x,$y] .] 0]
1898 set lc [lindex $last_clicked 1]
1899 if {$lc < $lno} {
1900 set begin $lc
1901 set end $lno
1902 } else {
1903 set begin $lno
1904 set end $lc
1905 }
1906
1907 foreach path [lrange $file_lists($w) \
1908 [expr {$begin - 1}] \
1909 [expr {$end - 1}]] {
1910 set selected_paths($path) 1
1911 }
1912 $w tag add in_sel $begin.0 [expr {$end + 1}].0
1913}
1914
1915proc show_more_context {} {
1916 global repo_config
1917 if {$repo_config(gui.diffcontext) < 99} {
1918 incr repo_config(gui.diffcontext)
1919 reshow_diff
1920 }
1921}
1922
1923proc show_less_context {} {
1924 global repo_config
1925 if {$repo_config(gui.diffcontext) >= 1} {
1926 incr repo_config(gui.diffcontext) -1
1927 reshow_diff
1928 }
1929}
1930
1931######################################################################
1932##
1933## ui construction
1934
1935load_config 0
1936apply_config
1937set ui_comm {}
1938
1939# -- Menu Bar
1940#
1941menu .mbar -tearoff 0
1942.mbar add cascade -label [mc Repository] -menu .mbar.repository
1943.mbar add cascade -label [mc Edit] -menu .mbar.edit
1944if {[is_enabled branch]} {
1945 .mbar add cascade -label [mc Branch] -menu .mbar.branch
1946}
1947if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1948 .mbar add cascade -label [mc Commit@@noun] -menu .mbar.commit
1949}
1950if {[is_enabled transport]} {
1951 .mbar add cascade -label [mc Merge] -menu .mbar.merge
1952 .mbar add cascade -label [mc Remote] -menu .mbar.remote
1953}
1954. configure -menu .mbar
1955
1956# -- Repository Menu
1957#
1958menu .mbar.repository
1959
1960.mbar.repository add command \
1961 -label [mc "Browse Current Branch's Files"] \
1962 -command {browser::new $current_branch}
1963set ui_browse_current [.mbar.repository index last]
1964.mbar.repository add command \
1965 -label [mc "Browse Branch Files..."] \
1966 -command browser_open::dialog
1967.mbar.repository add separator
1968
1969.mbar.repository add command \
1970 -label [mc "Visualize Current Branch's History"] \
1971 -command {do_gitk $current_branch}
1972set ui_visualize_current [.mbar.repository index last]
1973.mbar.repository add command \
1974 -label [mc "Visualize All Branch History"] \
1975 -command {do_gitk --all}
1976.mbar.repository add separator
1977
1978proc current_branch_write {args} {
1979 global current_branch
1980 .mbar.repository entryconf $::ui_browse_current \
1981 -label [mc "Browse %s's Files" $current_branch]
1982 .mbar.repository entryconf $::ui_visualize_current \
1983 -label [mc "Visualize %s's History" $current_branch]
1984}
1985trace add variable current_branch write current_branch_write
1986
1987if {[is_enabled multicommit]} {
1988 .mbar.repository add command -label [mc "Database Statistics"] \
1989 -command do_stats
1990
1991 .mbar.repository add command -label [mc "Compress Database"] \
1992 -command do_gc
1993
1994 .mbar.repository add command -label [mc "Verify Database"] \
1995 -command do_fsck_objects
1996
1997 .mbar.repository add separator
1998
1999 if {[is_Cygwin]} {
2000 .mbar.repository add command \
2001 -label [mc "Create Desktop Icon"] \
2002 -command do_cygwin_shortcut
2003 } elseif {[is_Windows]} {
2004 .mbar.repository add command \
2005 -label [mc "Create Desktop Icon"] \
2006 -command do_windows_shortcut
2007 } elseif {[is_MacOSX]} {
2008 .mbar.repository add command \
2009 -label [mc "Create Desktop Icon"] \
2010 -command do_macosx_app
2011 }
2012}
2013
2014if {[is_MacOSX]} {
2015 proc ::tk::mac::Quit {args} { do_quit }
2016} else {
2017 .mbar.repository add command -label [mc Quit] \
2018 -command do_quit \
2019 -accelerator $M1T-Q
2020}
2021
2022# -- Edit Menu
2023#
2024menu .mbar.edit
2025.mbar.edit add command -label [mc Undo] \
2026 -command {catch {[focus] edit undo}} \
2027 -accelerator $M1T-Z
2028.mbar.edit add command -label [mc Redo] \
2029 -command {catch {[focus] edit redo}} \
2030 -accelerator $M1T-Y
2031.mbar.edit add separator
2032.mbar.edit add command -label [mc Cut] \
2033 -command {catch {tk_textCut [focus]}} \
2034 -accelerator $M1T-X
2035.mbar.edit add command -label [mc Copy] \
2036 -command {catch {tk_textCopy [focus]}} \
2037 -accelerator $M1T-C
2038.mbar.edit add command -label [mc Paste] \
2039 -command {catch {tk_textPaste [focus]; [focus] see insert}} \
2040 -accelerator $M1T-V
2041.mbar.edit add command -label [mc Delete] \
2042 -command {catch {[focus] delete sel.first sel.last}} \
2043 -accelerator Del
2044.mbar.edit add separator
2045.mbar.edit add command -label [mc "Select All"] \
2046 -command {catch {[focus] tag add sel 0.0 end}} \
2047 -accelerator $M1T-A
2048
2049# -- Branch Menu
2050#
2051if {[is_enabled branch]} {
2052 menu .mbar.branch
2053
2054 .mbar.branch add command -label [mc "Create..."] \
2055 -command branch_create::dialog \
2056 -accelerator $M1T-N
2057 lappend disable_on_lock [list .mbar.branch entryconf \
2058 [.mbar.branch index last] -state]
2059
2060 .mbar.branch add command -label [mc "Checkout..."] \
2061 -command branch_checkout::dialog \
2062 -accelerator $M1T-O
2063 lappend disable_on_lock [list .mbar.branch entryconf \
2064 [.mbar.branch index last] -state]
2065
2066 .mbar.branch add command -label [mc "Rename..."] \
2067 -command branch_rename::dialog
2068 lappend disable_on_lock [list .mbar.branch entryconf \
2069 [.mbar.branch index last] -state]
2070
2071 .mbar.branch add command -label [mc "Delete..."] \
2072 -command branch_delete::dialog
2073 lappend disable_on_lock [list .mbar.branch entryconf \
2074 [.mbar.branch index last] -state]
2075
2076 .mbar.branch add command -label [mc "Reset..."] \
2077 -command merge::reset_hard
2078 lappend disable_on_lock [list .mbar.branch entryconf \
2079 [.mbar.branch index last] -state]
2080}
2081
2082# -- Commit Menu
2083#
2084if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2085 menu .mbar.commit
2086
2087 .mbar.commit add radiobutton \
2088 -label [mc "New Commit"] \
2089 -command do_select_commit_type \
2090 -variable selected_commit_type \
2091 -value new
2092 lappend disable_on_lock \
2093 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2094
2095 .mbar.commit add radiobutton \
2096 -label [mc "Amend Last Commit"] \
2097 -command do_select_commit_type \
2098 -variable selected_commit_type \
2099 -value amend
2100 lappend disable_on_lock \
2101 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2102
2103 .mbar.commit add separator
2104
2105 .mbar.commit add command -label [mc Rescan] \
2106 -command do_rescan \
2107 -accelerator F5
2108 lappend disable_on_lock \
2109 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2110
2111 .mbar.commit add command -label [mc "Stage To Commit"] \
2112 -command do_add_selection \
2113 -accelerator $M1T-T
2114 lappend disable_on_lock \
2115 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2116
2117 .mbar.commit add command -label [mc "Stage Changed Files To Commit"] \
2118 -command do_add_all \
2119 -accelerator $M1T-I
2120 lappend disable_on_lock \
2121 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2122
2123 .mbar.commit add command -label [mc "Unstage From Commit"] \
2124 -command do_unstage_selection
2125 lappend disable_on_lock \
2126 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2127
2128 .mbar.commit add command -label [mc "Revert Changes"] \
2129 -command do_revert_selection
2130 lappend disable_on_lock \
2131 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2132
2133 .mbar.commit add separator
2134
2135 .mbar.commit add command -label [mc "Show Less Context"] \
2136 -command show_less_context \
2137 -accelerator $M1T-\-
2138
2139 .mbar.commit add command -label [mc "Show More Context"] \
2140 -command show_more_context \
2141 -accelerator $M1T-=
2142
2143 .mbar.commit add separator
2144
2145 .mbar.commit add command -label [mc "Sign Off"] \
2146 -command do_signoff \
2147 -accelerator $M1T-S
2148
2149 .mbar.commit add command -label [mc Commit@@verb] \
2150 -command do_commit \
2151 -accelerator $M1T-Return
2152 lappend disable_on_lock \
2153 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2154}
2155
2156# -- Merge Menu
2157#
2158if {[is_enabled branch]} {
2159 menu .mbar.merge
2160 .mbar.merge add command -label [mc "Local Merge..."] \
2161 -command merge::dialog \
2162 -accelerator $M1T-M
2163 lappend disable_on_lock \
2164 [list .mbar.merge entryconf [.mbar.merge index last] -state]
2165 .mbar.merge add command -label [mc "Abort Merge..."] \
2166 -command merge::reset_hard
2167 lappend disable_on_lock \
2168 [list .mbar.merge entryconf [.mbar.merge index last] -state]
2169}
2170
2171# -- Transport Menu
2172#
2173if {[is_enabled transport]} {
2174 menu .mbar.remote
2175
2176 .mbar.remote add command \
2177 -label [mc "Push..."] \
2178 -command do_push_anywhere \
2179 -accelerator $M1T-P
2180 .mbar.remote add command \
2181 -label [mc "Delete..."] \
2182 -command remote_branch_delete::dialog
2183}
2184
2185if {[is_MacOSX]} {
2186 # -- Apple Menu (Mac OS X only)
2187 #
2188 .mbar add cascade -label Apple -menu .mbar.apple
2189 menu .mbar.apple
2190
2191 .mbar.apple add command -label [mc "About %s" [appname]] \
2192 -command do_about
2193 .mbar.apple add separator
2194 .mbar.apple add command \
2195 -label [mc "Preferences..."] \
2196 -command do_options \
2197 -accelerator $M1T-,
2198 bind . <$M1B-,> do_options
2199} else {
2200 # -- Edit Menu
2201 #
2202 .mbar.edit add separator
2203 .mbar.edit add command -label [mc "Options..."] \
2204 -command do_options
2205}
2206
2207# -- Help Menu
2208#
2209.mbar add cascade -label [mc Help] -menu .mbar.help
2210menu .mbar.help
2211
2212if {![is_MacOSX]} {
2213 .mbar.help add command -label [mc "About %s" [appname]] \
2214 -command do_about
2215}
2216
2217set browser {}
2218catch {set browser $repo_config(instaweb.browser)}
2219set doc_path [file dirname [gitexec]]
2220set doc_path [file join $doc_path Documentation index.html]
2221
2222if {[is_Cygwin]} {
2223 set doc_path [exec cygpath --mixed $doc_path]
2224}
2225
2226if {$browser eq {}} {
2227 if {[is_MacOSX]} {
2228 set browser open
2229 } elseif {[is_Cygwin]} {
2230 set program_files [file dirname [exec cygpath --windir]]
2231 set program_files [file join $program_files {Program Files}]
2232 set firefox [file join $program_files {Mozilla Firefox} firefox.exe]
2233 set ie [file join $program_files {Internet Explorer} IEXPLORE.EXE]
2234 if {[file exists $firefox]} {
2235 set browser $firefox
2236 } elseif {[file exists $ie]} {
2237 set browser $ie
2238 }
2239 unset program_files firefox ie
2240 }
2241}
2242
2243if {[file isfile $doc_path]} {
2244 set doc_url "file:$doc_path"
2245} else {
2246 set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
2247}
2248
2249if {$browser ne {}} {
2250 .mbar.help add command -label [mc "Online Documentation"] \
2251 -command [list exec $browser $doc_url &]
2252}
2253unset browser doc_path doc_url
2254
2255# -- Standard bindings
2256#
2257wm protocol . WM_DELETE_WINDOW do_quit
2258bind all <$M1B-Key-q> do_quit
2259bind all <$M1B-Key-Q> do_quit
2260bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
2261bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
2262
2263set subcommand_args {}
2264proc usage {} {
2265 puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
2266 exit 1
2267}
2268
2269# -- Not a normal commit type invocation? Do that instead!
2270#
2271switch -- $subcommand {
2272browser -
2273blame {
2274 set subcommand_args {rev? path}
2275 if {$argv eq {}} usage
2276 set head {}
2277 set path {}
2278 set is_path 0
2279 foreach a $argv {
2280 if {$is_path || [file exists $_prefix$a]} {
2281 if {$path ne {}} usage
2282 set path $_prefix$a
2283 break
2284 } elseif {$a eq {--}} {
2285 if {$path ne {}} {
2286 if {$head ne {}} usage
2287 set head $path
2288 set path {}
2289 }
2290 set is_path 1
2291 } elseif {$head eq {}} {
2292 if {$head ne {}} usage
2293 set head $a
2294 set is_path 1
2295 } else {
2296 usage
2297 }
2298 }
2299 unset is_path
2300
2301 if {$head ne {} && $path eq {}} {
2302 set path $_prefix$head
2303 set head {}
2304 }
2305
2306 if {$head eq {}} {
2307 load_current_branch
2308 } else {
2309 if {[regexp {^[0-9a-f]{1,39}$} $head]} {
2310 if {[catch {
2311 set head [git rev-parse --verify $head]
2312 } err]} {
2313 puts stderr $err
2314 exit 1
2315 }
2316 }
2317 set current_branch $head
2318 }
2319
2320 switch -- $subcommand {
2321 browser {
2322 if {$head eq {}} {
2323 if {$path ne {} && [file isdirectory $path]} {
2324 set head $current_branch
2325 } else {
2326 set head $path
2327 set path {}
2328 }
2329 }
2330 browser::new $head $path
2331 }
2332 blame {
2333 if {$head eq {} && ![file exists $path]} {
2334 puts stderr [mc "fatal: cannot stat path %s: No such file or directory" $path]
2335 exit 1
2336 }
2337 blame::new $head $path
2338 }
2339 }
2340 return
2341}
2342citool -
2343gui {
2344 if {[llength $argv] != 0} {
2345 puts -nonewline stderr "usage: $argv0"
2346 if {$subcommand ne {gui}
2347 && [file tail $argv0] ne "git-$subcommand"} {
2348 puts -nonewline stderr " $subcommand"
2349 }
2350 puts stderr {}
2351 exit 1
2352 }
2353 # fall through to setup UI for commits
2354}
2355default {
2356 puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
2357 exit 1
2358}
2359}
2360
2361# -- Branch Control
2362#
2363frame .branch \
2364 -borderwidth 1 \
2365 -relief sunken
2366label .branch.l1 \
2367 -text [mc "Current Branch:"] \
2368 -anchor w \
2369 -justify left
2370label .branch.cb \
2371 -textvariable current_branch \
2372 -anchor w \
2373 -justify left
2374pack .branch.l1 -side left
2375pack .branch.cb -side left -fill x
2376pack .branch -side top -fill x
2377
2378# -- Main Window Layout
2379#
2380panedwindow .vpane -orient horizontal
2381panedwindow .vpane.files -orient vertical
2382.vpane add .vpane.files -sticky nsew -height 100 -width 200
2383pack .vpane -anchor n -side top -fill both -expand 1
2384
2385# -- Index File List
2386#
2387frame .vpane.files.index -height 100 -width 200
2388label .vpane.files.index.title -text [mc "Staged Changes (Will Commit)"] \
2389 -background lightgreen -foreground black
2390text $ui_index -background white -foreground black \
2391 -borderwidth 0 \
2392 -width 20 -height 10 \
2393 -wrap none \
2394 -cursor $cursor_ptr \
2395 -xscrollcommand {.vpane.files.index.sx set} \
2396 -yscrollcommand {.vpane.files.index.sy set} \
2397 -state disabled
2398scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
2399scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
2400pack .vpane.files.index.title -side top -fill x
2401pack .vpane.files.index.sx -side bottom -fill x
2402pack .vpane.files.index.sy -side right -fill y
2403pack $ui_index -side left -fill both -expand 1
2404
2405# -- Working Directory File List
2406#
2407frame .vpane.files.workdir -height 100 -width 200
2408label .vpane.files.workdir.title -text [mc "Unstaged Changes"] \
2409 -background lightsalmon -foreground black
2410text $ui_workdir -background white -foreground black \
2411 -borderwidth 0 \
2412 -width 20 -height 10 \
2413 -wrap none \
2414 -cursor $cursor_ptr \
2415 -xscrollcommand {.vpane.files.workdir.sx set} \
2416 -yscrollcommand {.vpane.files.workdir.sy set} \
2417 -state disabled
2418scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
2419scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
2420pack .vpane.files.workdir.title -side top -fill x
2421pack .vpane.files.workdir.sx -side bottom -fill x
2422pack .vpane.files.workdir.sy -side right -fill y
2423pack $ui_workdir -side left -fill both -expand 1
2424
2425.vpane.files add .vpane.files.workdir -sticky nsew
2426.vpane.files add .vpane.files.index -sticky nsew
2427
2428foreach i [list $ui_index $ui_workdir] {
2429 rmsel_tag $i
2430 $i tag conf in_diff -background [$i tag cget in_sel -background]
2431}
2432unset i
2433
2434# -- Diff and Commit Area
2435#
2436frame .vpane.lower -height 300 -width 400
2437frame .vpane.lower.commarea
2438frame .vpane.lower.diff -relief sunken -borderwidth 1
2439pack .vpane.lower.diff -fill both -expand 1
2440pack .vpane.lower.commarea -side bottom -fill x
2441.vpane add .vpane.lower -sticky nsew
2442
2443# -- Commit Area Buttons
2444#
2445frame .vpane.lower.commarea.buttons
2446label .vpane.lower.commarea.buttons.l -text {} \
2447 -anchor w \
2448 -justify left
2449pack .vpane.lower.commarea.buttons.l -side top -fill x
2450pack .vpane.lower.commarea.buttons -side left -fill y
2451
2452button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \
2453 -command do_rescan
2454pack .vpane.lower.commarea.buttons.rescan -side top -fill x
2455lappend disable_on_lock \
2456 {.vpane.lower.commarea.buttons.rescan conf -state}
2457
2458button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \
2459 -command do_add_all
2460pack .vpane.lower.commarea.buttons.incall -side top -fill x
2461lappend disable_on_lock \
2462 {.vpane.lower.commarea.buttons.incall conf -state}
2463
2464button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \
2465 -command do_signoff
2466pack .vpane.lower.commarea.buttons.signoff -side top -fill x
2467
2468button .vpane.lower.commarea.buttons.commit -text [mc Commit@@verb] \
2469 -command do_commit
2470pack .vpane.lower.commarea.buttons.commit -side top -fill x
2471lappend disable_on_lock \
2472 {.vpane.lower.commarea.buttons.commit conf -state}
2473
2474button .vpane.lower.commarea.buttons.push -text [mc Push] \
2475 -command do_push_anywhere
2476pack .vpane.lower.commarea.buttons.push -side top -fill x
2477
2478# -- Commit Message Buffer
2479#
2480frame .vpane.lower.commarea.buffer
2481frame .vpane.lower.commarea.buffer.header
2482set ui_comm .vpane.lower.commarea.buffer.t
2483set ui_coml .vpane.lower.commarea.buffer.header.l
2484radiobutton .vpane.lower.commarea.buffer.header.new \
2485 -text [mc "New Commit"] \
2486 -command do_select_commit_type \
2487 -variable selected_commit_type \
2488 -value new
2489lappend disable_on_lock \
2490 [list .vpane.lower.commarea.buffer.header.new conf -state]
2491radiobutton .vpane.lower.commarea.buffer.header.amend \
2492 -text [mc "Amend Last Commit"] \
2493 -command do_select_commit_type \
2494 -variable selected_commit_type \
2495 -value amend
2496lappend disable_on_lock \
2497 [list .vpane.lower.commarea.buffer.header.amend conf -state]
2498label $ui_coml \
2499 -anchor w \
2500 -justify left
2501proc trace_commit_type {varname args} {
2502 global ui_coml commit_type
2503 switch -glob -- $commit_type {
2504 initial {set txt [mc "Initial Commit Message:"]}
2505 amend {set txt [mc "Amended Commit Message:"]}
2506 amend-initial {set txt [mc "Amended Initial Commit Message:"]}
2507 amend-merge {set txt [mc "Amended Merge Commit Message:"]}
2508 merge {set txt [mc "Merge Commit Message:"]}
2509 * {set txt [mc "Commit Message:"]}
2510 }
2511 $ui_coml conf -text $txt
2512}
2513trace add variable commit_type write trace_commit_type
2514pack $ui_coml -side left -fill x
2515pack .vpane.lower.commarea.buffer.header.amend -side right
2516pack .vpane.lower.commarea.buffer.header.new -side right
2517
2518text $ui_comm -background white -foreground black \
2519 -borderwidth 1 \
2520 -undo true \
2521 -maxundo 20 \
2522 -autoseparators true \
2523 -relief sunken \
2524 -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \
2525 -font font_diff \
2526 -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
2527scrollbar .vpane.lower.commarea.buffer.sby \
2528 -command [list $ui_comm yview]
2529pack .vpane.lower.commarea.buffer.header -side top -fill x
2530pack .vpane.lower.commarea.buffer.sby -side right -fill y
2531pack $ui_comm -side left -fill y
2532pack .vpane.lower.commarea.buffer -side left -fill y
2533
2534# -- Commit Message Buffer Context Menu
2535#
2536set ctxm .vpane.lower.commarea.buffer.ctxm
2537menu $ctxm -tearoff 0
2538$ctxm add command \
2539 -label [mc Cut] \
2540 -command {tk_textCut $ui_comm}
2541$ctxm add command \
2542 -label [mc Copy] \
2543 -command {tk_textCopy $ui_comm}
2544$ctxm add command \
2545 -label [mc Paste] \
2546 -command {tk_textPaste $ui_comm}
2547$ctxm add command \
2548 -label [mc Delete] \
2549 -command {$ui_comm delete sel.first sel.last}
2550$ctxm add separator
2551$ctxm add command \
2552 -label [mc "Select All"] \
2553 -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
2554$ctxm add command \
2555 -label [mc "Copy All"] \
2556 -command {
2557 $ui_comm tag add sel 0.0 end
2558 tk_textCopy $ui_comm
2559 $ui_comm tag remove sel 0.0 end
2560 }
2561$ctxm add separator
2562$ctxm add command \
2563 -label [mc "Sign Off"] \
2564 -command do_signoff
2565set ui_comm_ctxm $ctxm
2566
2567# -- Diff Header
2568#
2569proc trace_current_diff_path {varname args} {
2570 global current_diff_path diff_actions file_states
2571 if {$current_diff_path eq {}} {
2572 set s {}
2573 set f {}
2574 set p {}
2575 set o disabled
2576 } else {
2577 set p $current_diff_path
2578 set s [mapdesc [lindex $file_states($p) 0] $p]
2579 set f [mc "File:"]
2580 set p [escape_path $p]
2581 set o normal
2582 }
2583
2584 .vpane.lower.diff.header.status configure -text $s
2585 .vpane.lower.diff.header.file configure -text $f
2586 .vpane.lower.diff.header.path configure -text $p
2587 foreach w $diff_actions {
2588 uplevel #0 $w $o
2589 }
2590}
2591trace add variable current_diff_path write trace_current_diff_path
2592
2593frame .vpane.lower.diff.header -background gold
2594label .vpane.lower.diff.header.status \
2595 -background gold \
2596 -foreground black \
2597 -width $max_status_desc \
2598 -anchor w \
2599 -justify left
2600label .vpane.lower.diff.header.file \
2601 -background gold \
2602 -foreground black \
2603 -anchor w \
2604 -justify left
2605label .vpane.lower.diff.header.path \
2606 -background gold \
2607 -foreground black \
2608 -anchor w \
2609 -justify left
2610pack .vpane.lower.diff.header.status -side left
2611pack .vpane.lower.diff.header.file -side left
2612pack .vpane.lower.diff.header.path -fill x
2613set ctxm .vpane.lower.diff.header.ctxm
2614menu $ctxm -tearoff 0
2615$ctxm add command \
2616 -label [mc Copy] \
2617 -command {
2618 clipboard clear
2619 clipboard append \
2620 -format STRING \
2621 -type STRING \
2622 -- $current_diff_path
2623 }
2624lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2625bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
2626
2627# -- Diff Body
2628#
2629frame .vpane.lower.diff.body
2630set ui_diff .vpane.lower.diff.body.t
2631text $ui_diff -background white -foreground black \
2632 -borderwidth 0 \
2633 -width 80 -height 15 -wrap none \
2634 -font font_diff \
2635 -xscrollcommand {.vpane.lower.diff.body.sbx set} \
2636 -yscrollcommand {.vpane.lower.diff.body.sby set} \
2637 -state disabled
2638scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
2639 -command [list $ui_diff xview]
2640scrollbar .vpane.lower.diff.body.sby -orient vertical \
2641 -command [list $ui_diff yview]
2642pack .vpane.lower.diff.body.sbx -side bottom -fill x
2643pack .vpane.lower.diff.body.sby -side right -fill y
2644pack $ui_diff -side left -fill both -expand 1
2645pack .vpane.lower.diff.header -side top -fill x
2646pack .vpane.lower.diff.body -side bottom -fill both -expand 1
2647
2648$ui_diff tag conf d_cr -elide true
2649$ui_diff tag conf d_@ -foreground blue -font font_diffbold
2650$ui_diff tag conf d_+ -foreground {#00a000}
2651$ui_diff tag conf d_- -foreground red
2652
2653$ui_diff tag conf d_++ -foreground {#00a000}
2654$ui_diff tag conf d_-- -foreground red
2655$ui_diff tag conf d_+s \
2656 -foreground {#00a000} \
2657 -background {#e2effa}
2658$ui_diff tag conf d_-s \
2659 -foreground red \
2660 -background {#e2effa}
2661$ui_diff tag conf d_s+ \
2662 -foreground {#00a000} \
2663 -background ivory1
2664$ui_diff tag conf d_s- \
2665 -foreground red \
2666 -background ivory1
2667
2668$ui_diff tag conf d<<<<<<< \
2669 -foreground orange \
2670 -font font_diffbold
2671$ui_diff tag conf d======= \
2672 -foreground orange \
2673 -font font_diffbold
2674$ui_diff tag conf d>>>>>>> \
2675 -foreground orange \
2676 -font font_diffbold
2677
2678$ui_diff tag raise sel
2679
2680# -- Diff Body Context Menu
2681#
2682set ctxm .vpane.lower.diff.body.ctxm
2683menu $ctxm -tearoff 0
2684$ctxm add command \
2685 -label [mc "Apply/Reverse Hunk"] \
2686 -command {apply_hunk $cursorX $cursorY}
2687set ui_diff_applyhunk [$ctxm index last]
2688lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
2689$ctxm add command \
2690 -label [mc "Apply/Reverse Line"] \
2691 -command {apply_line $cursorX $cursorY; do_rescan}
2692set ui_diff_applyline [$ctxm index last]
2693lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state]
2694$ctxm add separator
2695$ctxm add command \
2696 -label [mc "Show Less Context"] \
2697 -command show_less_context
2698lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2699$ctxm add command \
2700 -label [mc "Show More Context"] \
2701 -command show_more_context
2702lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2703$ctxm add separator
2704$ctxm add command \
2705 -label [mc Refresh] \
2706 -command reshow_diff
2707lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2708$ctxm add command \
2709 -label [mc Copy] \
2710 -command {tk_textCopy $ui_diff}
2711lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2712$ctxm add command \
2713 -label [mc "Select All"] \
2714 -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
2715lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2716$ctxm add command \
2717 -label [mc "Copy All"] \
2718 -command {
2719 $ui_diff tag add sel 0.0 end
2720 tk_textCopy $ui_diff
2721 $ui_diff tag remove sel 0.0 end
2722 }
2723lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2724$ctxm add separator
2725$ctxm add command \
2726 -label [mc "Decrease Font Size"] \
2727 -command {incr_font_size font_diff -1}
2728lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2729$ctxm add command \
2730 -label [mc "Increase Font Size"] \
2731 -command {incr_font_size font_diff 1}
2732lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2733$ctxm add separator
2734$ctxm add command -label [mc "Options..."] \
2735 -command do_options
2736proc popup_diff_menu {ctxm x y X Y} {
2737 global current_diff_path file_states
2738 set ::cursorX $x
2739 set ::cursorY $y
2740 if {$::ui_index eq $::current_diff_side} {
2741 set l [mc "Unstage Hunk From Commit"]
2742 set t [mc "Unstage Line From Commit"]
2743 } else {
2744 set l [mc "Stage Hunk For Commit"]
2745 set t [mc "Stage Line For Commit"]
2746 }
2747 if {$::is_3way_diff
2748 || $current_diff_path eq {}
2749 || ![info exists file_states($current_diff_path)]
2750 || {_O} eq [lindex $file_states($current_diff_path) 0]} {
2751 set s disabled
2752 } else {
2753 set s normal
2754 }
2755 $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
2756 $ctxm entryconf $::ui_diff_applyline -state $s -label $t
2757 tk_popup $ctxm $X $Y
2758}
2759bind_button3 $ui_diff [list popup_diff_menu $ctxm %x %y %X %Y]
2760
2761# -- Status Bar
2762#
2763set main_status [::status_bar::new .status]
2764pack .status -anchor w -side bottom -fill x
2765$main_status show [mc "Initializing..."]
2766
2767# -- Load geometry
2768#
2769catch {
2770set gm $repo_config(gui.geometry)
2771wm geometry . [lindex $gm 0]
2772.vpane sash place 0 \
2773 [lindex $gm 1] \
2774 [lindex [.vpane sash coord 0] 1]
2775.vpane.files sash place 0 \
2776 [lindex [.vpane.files sash coord 0] 0] \
2777 [lindex $gm 2]
2778unset gm
2779}
2780
2781# -- Key Bindings
2782#
2783bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2784bind $ui_comm <$M1B-Key-t> {do_add_selection;break}
2785bind $ui_comm <$M1B-Key-T> {do_add_selection;break}
2786bind $ui_comm <$M1B-Key-i> {do_add_all;break}
2787bind $ui_comm <$M1B-Key-I> {do_add_all;break}
2788bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2789bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2790bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2791bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2792bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2793bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2794bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2795bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2796bind $ui_comm <$M1B-Key-minus> {show_less_context;break}
2797bind $ui_comm <$M1B-Key-KP_Subtract> {show_less_context;break}
2798bind $ui_comm <$M1B-Key-equal> {show_more_context;break}
2799bind $ui_comm <$M1B-Key-plus> {show_more_context;break}
2800bind $ui_comm <$M1B-Key-KP_Add> {show_more_context;break}
2801
2802bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2803bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2804bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2805bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2806bind $ui_diff <$M1B-Key-v> {break}
2807bind $ui_diff <$M1B-Key-V> {break}
2808bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2809bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2810bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break}
2811bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break}
2812bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break}
2813bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break}
2814bind $ui_diff <Key-k> {catch {%W yview scroll -1 units};break}
2815bind $ui_diff <Key-j> {catch {%W yview scroll 1 units};break}
2816bind $ui_diff <Key-h> {catch {%W xview scroll -1 units};break}
2817bind $ui_diff <Key-l> {catch {%W xview scroll 1 units};break}
2818bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
2819bind $ui_diff <Control-Key-f> {catch {%W yview scroll 1 pages};break}
2820bind $ui_diff <Button-1> {focus %W}
2821
2822if {[is_enabled branch]} {
2823 bind . <$M1B-Key-n> branch_create::dialog
2824 bind . <$M1B-Key-N> branch_create::dialog
2825 bind . <$M1B-Key-o> branch_checkout::dialog
2826 bind . <$M1B-Key-O> branch_checkout::dialog
2827 bind . <$M1B-Key-m> merge::dialog
2828 bind . <$M1B-Key-M> merge::dialog
2829}
2830if {[is_enabled transport]} {
2831 bind . <$M1B-Key-p> do_push_anywhere
2832 bind . <$M1B-Key-P> do_push_anywhere
2833}
2834
2835bind . <Key-F5> do_rescan
2836bind . <$M1B-Key-r> do_rescan
2837bind . <$M1B-Key-R> do_rescan
2838bind . <$M1B-Key-s> do_signoff
2839bind . <$M1B-Key-S> do_signoff
2840bind . <$M1B-Key-t> do_add_selection
2841bind . <$M1B-Key-T> do_add_selection
2842bind . <$M1B-Key-i> do_add_all
2843bind . <$M1B-Key-I> do_add_all
2844bind . <$M1B-Key-minus> {show_less_context;break}
2845bind . <$M1B-Key-KP_Subtract> {show_less_context;break}
2846bind . <$M1B-Key-equal> {show_more_context;break}
2847bind . <$M1B-Key-plus> {show_more_context;break}
2848bind . <$M1B-Key-KP_Add> {show_more_context;break}
2849bind . <$M1B-Key-Return> do_commit
2850foreach i [list $ui_index $ui_workdir] {
2851 bind $i <Button-1> "toggle_or_diff $i %x %y; break"
2852 bind $i <$M1B-Button-1> "add_one_to_selection $i %x %y; break"
2853 bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
2854}
2855unset i
2856
2857set file_lists($ui_index) [list]
2858set file_lists($ui_workdir) [list]
2859
2860wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
2861focus -force $ui_comm
2862
2863# -- Warn the user about environmental problems. Cygwin's Tcl
2864# does *not* pass its env array onto any processes it spawns.
2865# This means that git processes get none of our environment.
2866#
2867if {[is_Cygwin]} {
2868 set ignored_env 0
2869 set suggest_user {}
2870 set msg [mc "Possible environment issues exist.
2871
2872The following environment variables are probably
2873going to be ignored by any Git subprocess run
2874by %s:
2875
2876" [appname]]
2877 foreach name [array names env] {
2878 switch -regexp -- $name {
2879 {^GIT_INDEX_FILE$} -
2880 {^GIT_OBJECT_DIRECTORY$} -
2881 {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
2882 {^GIT_DIFF_OPTS$} -
2883 {^GIT_EXTERNAL_DIFF$} -
2884 {^GIT_PAGER$} -
2885 {^GIT_TRACE$} -
2886 {^GIT_CONFIG$} -
2887 {^GIT_CONFIG_LOCAL$} -
2888 {^GIT_(AUTHOR|COMMITTER)_DATE$} {
2889 append msg " - $name\n"
2890 incr ignored_env
2891 }
2892 {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
2893 append msg " - $name\n"
2894 incr ignored_env
2895 set suggest_user $name
2896 }
2897 }
2898 }
2899 if {$ignored_env > 0} {
2900 append msg [mc "
2901This is due to a known issue with the
2902Tcl binary distributed by Cygwin."]
2903
2904 if {$suggest_user ne {}} {
2905 append msg [mc "
2906
2907A good replacement for %s
2908is placing values for the user.name and
2909user.email settings into your personal
2910~/.gitconfig file.
2911" $suggest_user]
2912 }
2913 warn_popup $msg
2914 }
2915 unset ignored_env msg suggest_user name
2916}
2917
2918# -- Only initialize complex UI if we are going to stay running.
2919#
2920if {[is_enabled transport]} {
2921 load_all_remotes
2922
2923 set n [.mbar.remote index end]
2924 populate_push_menu
2925 populate_fetch_menu
2926 set n [expr {[.mbar.remote index end] - $n}]
2927 if {$n > 0} {
2928 .mbar.remote insert $n separator
2929 }
2930 unset n
2931}
2932
2933if {[winfo exists $ui_comm]} {
2934 set GITGUI_BCK_exists [load_message GITGUI_BCK]
2935
2936 # -- If both our backup and message files exist use the
2937 # newer of the two files to initialize the buffer.
2938 #
2939 if {$GITGUI_BCK_exists} {
2940 set m [gitdir GITGUI_MSG]
2941 if {[file isfile $m]} {
2942 if {[file mtime [gitdir GITGUI_BCK]] > [file mtime $m]} {
2943 catch {file delete [gitdir GITGUI_MSG]}
2944 } else {
2945 $ui_comm delete 0.0 end
2946 $ui_comm edit reset
2947 $ui_comm edit modified false
2948 catch {file delete [gitdir GITGUI_BCK]}
2949 set GITGUI_BCK_exists 0
2950 }
2951 }
2952 unset m
2953 }
2954
2955 proc backup_commit_buffer {} {
2956 global ui_comm GITGUI_BCK_exists
2957
2958 set m [$ui_comm edit modified]
2959 if {$m || $GITGUI_BCK_exists} {
2960 set msg [string trim [$ui_comm get 0.0 end]]
2961 regsub -all -line {[ \r\t]+$} $msg {} msg
2962
2963 if {$msg eq {}} {
2964 if {$GITGUI_BCK_exists} {
2965 catch {file delete [gitdir GITGUI_BCK]}
2966 set GITGUI_BCK_exists 0
2967 }
2968 } elseif {$m} {
2969 catch {
2970 set fd [open [gitdir GITGUI_BCK] w]
2971 puts -nonewline $fd $msg
2972 close $fd
2973 set GITGUI_BCK_exists 1
2974 }
2975 }
2976
2977 $ui_comm edit modified false
2978 }
2979
2980 set ::GITGUI_BCK_i [after 2000 backup_commit_buffer]
2981 }
2982
2983 backup_commit_buffer
2984
2985 # -- If the user has aspell available we can drive it
2986 # in pipe mode to spellcheck the commit message.
2987 #
2988 set spell_cmd [list |]
2989 set spell_dict [get_config gui.spellingdictionary]
2990 lappend spell_cmd aspell
2991 if {$spell_dict ne {}} {
2992 lappend spell_cmd --master=$spell_dict
2993 }
2994 lappend spell_cmd --mode=none
2995 lappend spell_cmd --encoding=utf-8
2996 lappend spell_cmd pipe
2997 if {$spell_dict eq {none}
2998 || [catch {set spell_fd [open $spell_cmd r+]} spell_err]} {
2999 bind_button3 $ui_comm [list tk_popup $ui_comm_ctxm %X %Y]
3000 } else {
3001 set ui_comm_spell [spellcheck::init \
3002 $spell_fd \
3003 $ui_comm \
3004 $ui_comm_ctxm \
3005 ]
3006 }
3007 unset -nocomplain spell_cmd spell_fd spell_err spell_dict
3008}
3009
3010lock_index begin-read
3011if {![winfo ismapped .]} {
3012 wm deiconify .
3013}
3014after 1 do_rescan
3015if {[is_enabled multicommit]} {
3016 after 1000 hint_gc
3017}