]> git.ipfire.org Git - thirdparty/git.git/blob - git-gui/git-gui.sh
Merge branch 'js/userdiff-php'
[thirdparty/git.git] / git-gui / git-gui.sh
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
12 set appvers {@@GITGUI_VERSION@@}
13 set copyright [string map [list (c) \u00a9] {
14 Copyright (c) 2006-2010 Shawn Pearce, et. al.
15
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.
20
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.
25
26 You should have received a copy of the GNU General Public License
27 along with this program; if not, see <http://www.gnu.org/licenses/>.}]
28
29 ######################################################################
30 ##
31 ## Tcl/Tk sanity check
32
33 if {[catch {package require Tcl 8.5} err]
34 || [catch {package require Tk 8.5} err]
35 } {
36 catch {wm withdraw .}
37 tk_messageBox \
38 -icon error \
39 -type ok \
40 -title "git-gui: fatal error" \
41 -message $err
42 exit 1
43 }
44
45 catch {rename send {}} ; # What an evil concept...
46
47 ######################################################################
48 ##
49 ## locate our library
50
51 if { [info exists ::env(GIT_GUI_LIB_DIR) ] } {
52 set oguilib $::env(GIT_GUI_LIB_DIR)
53 } else {
54 set oguilib {@@GITGUI_LIBDIR@@}
55 }
56 set oguirel {@@GITGUI_RELATIVE@@}
57 if {$oguirel eq {1}} {
58 set oguilib [file dirname [file normalize $argv0]]
59 if {[file tail $oguilib] eq {git-core}} {
60 set oguilib [file dirname $oguilib]
61 }
62 set oguilib [file dirname $oguilib]
63 set oguilib [file join $oguilib share git-gui lib]
64 set oguimsg [file join $oguilib msgs]
65 } elseif {[string match @@* $oguirel]} {
66 set oguilib [file join [file dirname [file normalize $argv0]] lib]
67 set oguimsg [file join [file dirname [file normalize $argv0]] po]
68 } else {
69 set oguimsg [file join $oguilib msgs]
70 }
71 unset oguirel
72
73 ######################################################################
74 ##
75 ## enable verbose loading?
76
77 if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
78 unset _verbose
79 rename auto_load real__auto_load
80 proc auto_load {name args} {
81 puts stderr "auto_load $name"
82 return [uplevel 1 real__auto_load $name $args]
83 }
84 rename source real__source
85 proc source {args} {
86 puts stderr "source $args"
87 uplevel 1 [linsert $args 0 real__source]
88 }
89 if {[tk windowingsystem] eq "win32"} { console show }
90 }
91
92 ######################################################################
93 ##
94 ## Internationalization (i18n) through msgcat and gettext. See
95 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
96
97 package require msgcat
98
99 # Check for Windows 7 MUI language pack (missed by msgcat < 1.4.4)
100 if {[tk windowingsystem] eq "win32"
101 && [package vcompare [package provide msgcat] 1.4.4] < 0
102 } then {
103 proc _mc_update_locale {} {
104 set key {HKEY_CURRENT_USER\Control Panel\Desktop}
105 if {![catch {
106 package require registry
107 set uilocale [registry get $key "PreferredUILanguages"]
108 msgcat::ConvertLocale [string map {- _} [lindex $uilocale 0]]
109 } uilocale]} {
110 if {[string length $uilocale] > 0} {
111 msgcat::mclocale $uilocale
112 }
113 }
114 }
115 _mc_update_locale
116 }
117
118 proc _mc_trim {fmt} {
119 set cmk [string first @@ $fmt]
120 if {$cmk > 0} {
121 return [string range $fmt 0 [expr {$cmk - 1}]]
122 }
123 return $fmt
124 }
125
126 proc mc {en_fmt args} {
127 set fmt [_mc_trim [::msgcat::mc $en_fmt]]
128 if {[catch {set msg [eval [list format $fmt] $args]} err]} {
129 set msg [eval [list format [_mc_trim $en_fmt]] $args]
130 }
131 return $msg
132 }
133
134 proc strcat {args} {
135 return [join $args {}]
136 }
137
138 ::msgcat::mcload $oguimsg
139 unset oguimsg
140
141 ######################################################################
142 ##
143 ## On Mac, bring the current Wish process window to front
144
145 if {[tk windowingsystem] eq "aqua"} {
146 catch {
147 exec osascript -e [format {
148 tell application "System Events"
149 set frontmost of processes whose unix id is %d to true
150 end tell
151 } [pid]]
152 }
153 }
154
155 ######################################################################
156 ##
157 ## read only globals
158
159 set _appname {Git Gui}
160 set _gitdir {}
161 set _gitworktree {}
162 set _isbare {}
163 set _gitexec {}
164 set _githtmldir {}
165 set _reponame {}
166 set _iscygwin {}
167 set _search_path {}
168 set _shellpath {@@SHELL_PATH@@}
169
170 set _trace [lsearch -exact $argv --trace]
171 if {$_trace >= 0} {
172 set argv [lreplace $argv $_trace $_trace]
173 set _trace 1
174 if {[tk windowingsystem] eq "win32"} { console show }
175 } else {
176 set _trace 0
177 }
178
179 # variable for the last merged branch (useful for a default when deleting
180 # branches).
181 set _last_merged_branch {}
182
183 proc shellpath {} {
184 global _shellpath env
185 if {[string match @@* $_shellpath]} {
186 if {[info exists env(SHELL)]} {
187 return $env(SHELL)
188 } else {
189 return /bin/sh
190 }
191 }
192 return $_shellpath
193 }
194
195 proc appname {} {
196 global _appname
197 return $_appname
198 }
199
200 proc gitdir {args} {
201 global _gitdir
202 if {$args eq {}} {
203 return $_gitdir
204 }
205 return [eval [list file join $_gitdir] $args]
206 }
207
208 proc gitexec {args} {
209 global _gitexec
210 if {$_gitexec eq {}} {
211 if {[catch {set _gitexec [git --exec-path]} err]} {
212 error "Git not installed?\n\n$err"
213 }
214 if {[is_Cygwin]} {
215 set _gitexec [exec cygpath \
216 --windows \
217 --absolute \
218 $_gitexec]
219 } else {
220 set _gitexec [file normalize $_gitexec]
221 }
222 }
223 if {$args eq {}} {
224 return $_gitexec
225 }
226 return [eval [list file join $_gitexec] $args]
227 }
228
229 proc githtmldir {args} {
230 global _githtmldir
231 if {$_githtmldir eq {}} {
232 if {[catch {set _githtmldir [git --html-path]}]} {
233 # Git not installed or option not yet supported
234 return {}
235 }
236 if {[is_Cygwin]} {
237 set _githtmldir [exec cygpath \
238 --windows \
239 --absolute \
240 $_githtmldir]
241 } else {
242 set _githtmldir [file normalize $_githtmldir]
243 }
244 }
245 if {$args eq {}} {
246 return $_githtmldir
247 }
248 return [eval [list file join $_githtmldir] $args]
249 }
250
251 proc reponame {} {
252 return $::_reponame
253 }
254
255 proc is_MacOSX {} {
256 if {[tk windowingsystem] eq {aqua}} {
257 return 1
258 }
259 return 0
260 }
261
262 proc is_Windows {} {
263 if {$::tcl_platform(platform) eq {windows}} {
264 return 1
265 }
266 return 0
267 }
268
269 proc is_Cygwin {} {
270 global _iscygwin
271 if {$_iscygwin eq {}} {
272 if {$::tcl_platform(platform) eq {windows}} {
273 if {[catch {set p [exec cygpath --windir]} err]} {
274 set _iscygwin 0
275 } else {
276 set _iscygwin 1
277 # Handle MSys2 which is only cygwin when MSYSTEM is MSYS.
278 if {[info exists ::env(MSYSTEM)] && $::env(MSYSTEM) ne "MSYS"} {
279 set _iscygwin 0
280 }
281 }
282 } else {
283 set _iscygwin 0
284 }
285 }
286 return $_iscygwin
287 }
288
289 proc is_enabled {option} {
290 global enabled_options
291 if {[catch {set on $enabled_options($option)}]} {return 0}
292 return $on
293 }
294
295 proc enable_option {option} {
296 global enabled_options
297 set enabled_options($option) 1
298 }
299
300 proc disable_option {option} {
301 global enabled_options
302 set enabled_options($option) 0
303 }
304
305 ######################################################################
306 ##
307 ## config
308
309 proc is_many_config {name} {
310 switch -glob -- $name {
311 gui.recentrepo -
312 remote.*.fetch -
313 remote.*.push
314 {return 1}
315 *
316 {return 0}
317 }
318 }
319
320 proc is_config_true {name} {
321 global repo_config
322 if {[catch {set v $repo_config($name)}]} {
323 return 0
324 }
325 set v [string tolower $v]
326 if {$v eq {} || $v eq {true} || $v eq {1} || $v eq {yes} || $v eq {on}} {
327 return 1
328 } else {
329 return 0
330 }
331 }
332
333 proc is_config_false {name} {
334 global repo_config
335 if {[catch {set v $repo_config($name)}]} {
336 return 0
337 }
338 set v [string tolower $v]
339 if {$v eq {false} || $v eq {0} || $v eq {no} || $v eq {off}} {
340 return 1
341 } else {
342 return 0
343 }
344 }
345
346 proc get_config {name} {
347 global repo_config
348 if {[catch {set v $repo_config($name)}]} {
349 return {}
350 } else {
351 return $v
352 }
353 }
354
355 proc is_bare {} {
356 global _isbare
357 global _gitdir
358 global _gitworktree
359
360 if {$_isbare eq {}} {
361 if {[catch {
362 set _bare [git rev-parse --is-bare-repository]
363 switch -- $_bare {
364 true { set _isbare 1 }
365 false { set _isbare 0}
366 default { throw }
367 }
368 }]} {
369 if {[is_config_true core.bare]
370 || ($_gitworktree eq {}
371 && [lindex [file split $_gitdir] end] ne {.git})} {
372 set _isbare 1
373 } else {
374 set _isbare 0
375 }
376 }
377 }
378 return $_isbare
379 }
380
381 ######################################################################
382 ##
383 ## handy utils
384
385 proc _trace_exec {cmd} {
386 if {!$::_trace} return
387 set d {}
388 foreach v $cmd {
389 if {$d ne {}} {
390 append d { }
391 }
392 if {[regexp {[ \t\r\n'"$?*]} $v]} {
393 set v [sq $v]
394 }
395 append d $v
396 }
397 puts stderr $d
398 }
399
400 #'" fix poor old emacs font-lock mode
401
402 proc _git_cmd {name} {
403 global _git_cmd_path
404
405 if {[catch {set v $_git_cmd_path($name)}]} {
406 switch -- $name {
407 version -
408 --version -
409 --exec-path { return [list $::_git $name] }
410 }
411
412 set p [gitexec git-$name$::_search_exe]
413 if {[file exists $p]} {
414 set v [list $p]
415 } elseif {[is_Windows] && [file exists [gitexec git-$name]]} {
416 # Try to determine what sort of magic will make
417 # git-$name go and do its thing, because native
418 # Tcl on Windows doesn't know it.
419 #
420 set p [gitexec git-$name]
421 set f [open $p r]
422 set s [gets $f]
423 close $f
424
425 switch -glob -- [lindex $s 0] {
426 #!*sh { set i sh }
427 #!*perl { set i perl }
428 #!*python { set i python }
429 default { error "git-$name is not supported: $s" }
430 }
431
432 upvar #0 _$i interp
433 if {![info exists interp]} {
434 set interp [_which $i]
435 }
436 if {$interp eq {}} {
437 error "git-$name requires $i (not in PATH)"
438 }
439 set v [concat [list $interp] [lrange $s 1 end] [list $p]]
440 } else {
441 # Assume it is builtin to git somehow and we
442 # aren't actually able to see a file for it.
443 #
444 set v [list $::_git $name]
445 }
446 set _git_cmd_path($name) $v
447 }
448 return $v
449 }
450
451 proc _which {what args} {
452 global env _search_exe _search_path
453
454 if {$_search_path eq {}} {
455 if {[is_Cygwin] && [regexp {^(/|\.:)} $env(PATH)]} {
456 set _search_path [split [exec cygpath \
457 --windows \
458 --path \
459 --absolute \
460 $env(PATH)] {;}]
461 set _search_exe .exe
462 } elseif {[is_Windows]} {
463 set gitguidir [file dirname [info script]]
464 regsub -all ";" $gitguidir "\\;" gitguidir
465 set env(PATH) "$gitguidir;$env(PATH)"
466 set _search_path [split $env(PATH) {;}]
467 set _search_exe .exe
468 } else {
469 set _search_path [split $env(PATH) :]
470 set _search_exe {}
471 }
472 }
473
474 if {[is_Windows] && [lsearch -exact $args -script] >= 0} {
475 set suffix {}
476 } else {
477 set suffix $_search_exe
478 }
479
480 foreach p $_search_path {
481 set p [file join $p $what$suffix]
482 if {[file exists $p]} {
483 return [file normalize $p]
484 }
485 }
486 return {}
487 }
488
489 # Test a file for a hashbang to identify executable scripts on Windows.
490 proc is_shellscript {filename} {
491 if {![file exists $filename]} {return 0}
492 set f [open $filename r]
493 fconfigure $f -encoding binary
494 set magic [read $f 2]
495 close $f
496 return [expr {$magic eq "#!"}]
497 }
498
499 # Run a command connected via pipes on stdout.
500 # This is for use with textconv filters and uses sh -c "..." to allow it to
501 # contain a command with arguments. On windows we must check for shell
502 # scripts specifically otherwise just call the filter command.
503 proc open_cmd_pipe {cmd path} {
504 global env
505 if {![file executable [shellpath]]} {
506 set exe [auto_execok [lindex $cmd 0]]
507 if {[is_shellscript [lindex $exe 0]]} {
508 set run [linsert [auto_execok sh] end -c "$cmd \"\$0\"" $path]
509 } else {
510 set run [concat $exe [lrange $cmd 1 end] $path]
511 }
512 } else {
513 set run [list [shellpath] -c "$cmd \"\$0\"" $path]
514 }
515 return [open |$run r]
516 }
517
518 proc _lappend_nice {cmd_var} {
519 global _nice
520 upvar $cmd_var cmd
521
522 if {![info exists _nice]} {
523 set _nice [_which nice]
524 if {[catch {exec $_nice git version}]} {
525 set _nice {}
526 } elseif {[is_Windows] && [file dirname $_nice] ne [file dirname $::_git]} {
527 set _nice {}
528 }
529 }
530 if {$_nice ne {}} {
531 lappend cmd $_nice
532 }
533 }
534
535 proc git {args} {
536 set fd [eval [list git_read] $args]
537 fconfigure $fd -translation binary -encoding utf-8
538 set result [string trimright [read $fd] "\n"]
539 close $fd
540 if {$::_trace} {
541 puts stderr "< $result"
542 }
543 return $result
544 }
545
546 proc _open_stdout_stderr {cmd} {
547 _trace_exec $cmd
548 if {[catch {
549 set fd [open [concat [list | ] $cmd] r]
550 } err]} {
551 if { [lindex $cmd end] eq {2>@1}
552 && $err eq {can not find channel named "1"}
553 } {
554 # Older versions of Tcl 8.4 don't have this 2>@1 IO
555 # redirect operator. Fallback to |& cat for those.
556 # The command was not actually started, so its safe
557 # to try to start it a second time.
558 #
559 set fd [open [concat \
560 [list | ] \
561 [lrange $cmd 0 end-1] \
562 [list |& cat] \
563 ] r]
564 } else {
565 error $err
566 }
567 }
568 fconfigure $fd -eofchar {}
569 return $fd
570 }
571
572 proc git_read {args} {
573 set opt [list]
574
575 while {1} {
576 switch -- [lindex $args 0] {
577 --nice {
578 _lappend_nice opt
579 }
580
581 --stderr {
582 lappend args 2>@1
583 }
584
585 default {
586 break
587 }
588
589 }
590
591 set args [lrange $args 1 end]
592 }
593
594 set cmdp [_git_cmd [lindex $args 0]]
595 set args [lrange $args 1 end]
596
597 return [_open_stdout_stderr [concat $opt $cmdp $args]]
598 }
599
600 proc git_write {args} {
601 set opt [list]
602
603 while {1} {
604 switch -- [lindex $args 0] {
605 --nice {
606 _lappend_nice opt
607 }
608
609 default {
610 break
611 }
612
613 }
614
615 set args [lrange $args 1 end]
616 }
617
618 set cmdp [_git_cmd [lindex $args 0]]
619 set args [lrange $args 1 end]
620
621 _trace_exec [concat $opt $cmdp $args]
622 return [open [concat [list | ] $opt $cmdp $args] w]
623 }
624
625 proc githook_read {hook_name args} {
626 set pchook [gitdir hooks $hook_name]
627 lappend args 2>@1
628
629 # On Windows [file executable] might lie so we need to ask
630 # the shell if the hook is executable. Yes that's annoying.
631 #
632 if {[is_Windows]} {
633 upvar #0 _sh interp
634 if {![info exists interp]} {
635 set interp [_which sh]
636 }
637 if {$interp eq {}} {
638 error "hook execution requires sh (not in PATH)"
639 }
640
641 set scr {if test -x "$1";then exec "$@";fi}
642 set sh_c [list $interp -c $scr $interp $pchook]
643 return [_open_stdout_stderr [concat $sh_c $args]]
644 }
645
646 if {[file executable $pchook]} {
647 return [_open_stdout_stderr [concat [list $pchook] $args]]
648 }
649
650 return {}
651 }
652
653 proc kill_file_process {fd} {
654 set process [pid $fd]
655
656 catch {
657 if {[is_Windows]} {
658 exec taskkill /pid $process
659 } else {
660 exec kill $process
661 }
662 }
663 }
664
665 proc gitattr {path attr default} {
666 if {[catch {set r [git check-attr $attr -- $path]}]} {
667 set r unspecified
668 } else {
669 set r [join [lrange [split $r :] 2 end] :]
670 regsub {^ } $r {} r
671 }
672 if {$r eq {unspecified}} {
673 return $default
674 }
675 return $r
676 }
677
678 proc sq {value} {
679 regsub -all ' $value "'\\''" value
680 return "'$value'"
681 }
682
683 proc load_current_branch {} {
684 global current_branch is_detached
685
686 set fd [open [gitdir HEAD] r]
687 fconfigure $fd -translation binary -encoding utf-8
688 if {[gets $fd ref] < 1} {
689 set ref {}
690 }
691 close $fd
692
693 set pfx {ref: refs/heads/}
694 set len [string length $pfx]
695 if {[string equal -length $len $pfx $ref]} {
696 # We're on a branch. It might not exist. But
697 # HEAD looks good enough to be a branch.
698 #
699 set current_branch [string range $ref $len end]
700 set is_detached 0
701 } else {
702 # Assume this is a detached head.
703 #
704 set current_branch HEAD
705 set is_detached 1
706 }
707 }
708
709 auto_load tk_optionMenu
710 rename tk_optionMenu real__tkOptionMenu
711 proc tk_optionMenu {w varName args} {
712 set m [eval real__tkOptionMenu $w $varName $args]
713 $m configure -font font_ui
714 $w configure -font font_ui
715 return $m
716 }
717
718 proc rmsel_tag {text} {
719 $text tag conf sel \
720 -background [$text cget -background] \
721 -foreground [$text cget -foreground] \
722 -borderwidth 0
723 $text tag conf in_sel\
724 -background $color::select_bg \
725 -foreground $color::select_fg
726 bind $text <Motion> break
727 return $text
728 }
729
730 wm withdraw .
731 set root_exists 0
732 bind . <Visibility> {
733 bind . <Visibility> {}
734 set root_exists 1
735 }
736
737 if {[is_Windows]} {
738 wm iconbitmap . -default $oguilib/git-gui.ico
739 set ::tk::AlwaysShowSelection 1
740 bind . <Control-F2> {console show}
741
742 # Spoof an X11 display for SSH
743 if {![info exists env(DISPLAY)]} {
744 set env(DISPLAY) :9999
745 }
746 } else {
747 catch {
748 image create photo gitlogo -width 16 -height 16
749
750 gitlogo put #33CC33 -to 7 0 9 2
751 gitlogo put #33CC33 -to 4 2 12 4
752 gitlogo put #33CC33 -to 7 4 9 6
753 gitlogo put #CC3333 -to 4 6 12 8
754 gitlogo put gray26 -to 4 9 6 10
755 gitlogo put gray26 -to 3 10 6 12
756 gitlogo put gray26 -to 8 9 13 11
757 gitlogo put gray26 -to 8 11 10 12
758 gitlogo put gray26 -to 11 11 13 14
759 gitlogo put gray26 -to 3 12 5 14
760 gitlogo put gray26 -to 5 13
761 gitlogo put gray26 -to 10 13
762 gitlogo put gray26 -to 4 14 12 15
763 gitlogo put gray26 -to 5 15 11 16
764 gitlogo redither
765
766 image create photo gitlogo32 -width 32 -height 32
767 gitlogo32 copy gitlogo -zoom 2 2
768
769 wm iconphoto . -default gitlogo gitlogo32
770 }
771 }
772
773 ######################################################################
774 ##
775 ## config defaults
776
777 set cursor_ptr arrow
778 font create font_ui
779 if {[lsearch -exact [font names] TkDefaultFont] != -1} {
780 eval [linsert [font actual TkDefaultFont] 0 font configure font_ui]
781 eval [linsert [font actual TkFixedFont] 0 font create font_diff]
782 } else {
783 font create font_diff -family Courier -size 10
784 catch {
785 label .dummy
786 eval font configure font_ui [font actual [.dummy cget -font]]
787 destroy .dummy
788 }
789 }
790
791 font create font_uiitalic
792 font create font_uibold
793 font create font_diffbold
794 font create font_diffitalic
795
796 foreach class {Button Checkbutton Entry Label
797 Labelframe Listbox Message
798 Radiobutton Spinbox Text} {
799 option add *$class.font font_ui
800 }
801 if {![is_MacOSX]} {
802 option add *Menu.font font_ui
803 option add *Entry.borderWidth 1 startupFile
804 option add *Entry.relief sunken startupFile
805 option add *RadioButton.anchor w startupFile
806 }
807 unset class
808
809 if {[is_Windows] || [is_MacOSX]} {
810 option add *Menu.tearOff 0
811 }
812
813 if {[is_MacOSX]} {
814 set M1B M1
815 set M1T Cmd
816 } else {
817 set M1B Control
818 set M1T Ctrl
819 }
820
821 proc bind_button3 {w cmd} {
822 bind $w <Any-Button-3> $cmd
823 if {[is_MacOSX]} {
824 # Mac OS X sends Button-2 on right click through three-button mouse,
825 # or through trackpad right-clicking (two-finger touch + click).
826 bind $w <Any-Button-2> $cmd
827 bind $w <Control-Button-1> $cmd
828 }
829 }
830
831 proc apply_config {} {
832 global repo_config font_descs
833
834 foreach option $font_descs {
835 set name [lindex $option 0]
836 set font [lindex $option 1]
837 if {[catch {
838 set need_weight 1
839 foreach {cn cv} $repo_config(gui.$name) {
840 if {$cn eq {-weight}} {
841 set need_weight 0
842 }
843 font configure $font $cn $cv
844 }
845 if {$need_weight} {
846 font configure $font -weight normal
847 }
848 } err]} {
849 error_popup [strcat [mc "Invalid font specified in %s:" "gui.$name"] "\n\n$err"]
850 }
851 foreach {cn cv} [font configure $font] {
852 font configure ${font}bold $cn $cv
853 font configure ${font}italic $cn $cv
854 }
855 font configure ${font}bold -weight bold
856 font configure ${font}italic -slant italic
857 }
858
859 global use_ttk NS
860 set use_ttk 0
861 set NS {}
862 if {$repo_config(gui.usettk)} {
863 set use_ttk [package vsatisfies [package provide Tk] 8.5]
864 if {$use_ttk} {
865 set NS ttk
866 bind [winfo class .] <<ThemeChanged>> [list InitTheme]
867 pave_toplevel .
868 color::sync_with_theme
869 }
870 }
871 }
872
873 set default_config(branch.autosetupmerge) true
874 set default_config(merge.tool) {}
875 set default_config(mergetool.keepbackup) true
876 set default_config(merge.diffstat) true
877 set default_config(merge.summary) false
878 set default_config(merge.verbosity) 2
879 set default_config(user.name) {}
880 set default_config(user.email) {}
881
882 set default_config(gui.encoding) [encoding system]
883 set default_config(gui.matchtrackingbranch) false
884 set default_config(gui.textconv) true
885 set default_config(gui.pruneduringfetch) false
886 set default_config(gui.trustmtime) false
887 set default_config(gui.fastcopyblame) false
888 set default_config(gui.maxrecentrepo) 10
889 set default_config(gui.copyblamethreshold) 40
890 set default_config(gui.blamehistoryctx) 7
891 set default_config(gui.diffcontext) 5
892 set default_config(gui.diffopts) {}
893 set default_config(gui.commitmsgwidth) 75
894 set default_config(gui.newbranchtemplate) {}
895 set default_config(gui.spellingdictionary) {}
896 set default_config(gui.fontui) [font configure font_ui]
897 set default_config(gui.fontdiff) [font configure font_diff]
898 # TODO: this option should be added to the git-config documentation
899 set default_config(gui.maxfilesdisplayed) 5000
900 set default_config(gui.usettk) 1
901 set default_config(gui.warndetachedcommit) 1
902 set default_config(gui.tabsize) 8
903 set font_descs {
904 {fontui font_ui {mc "Main Font"}}
905 {fontdiff font_diff {mc "Diff/Console Font"}}
906 }
907 set default_config(gui.stageuntracked) ask
908 set default_config(gui.displayuntracked) true
909
910 ######################################################################
911 ##
912 ## find git
913
914 set _git [_which git]
915 if {$_git eq {}} {
916 catch {wm withdraw .}
917 tk_messageBox \
918 -icon error \
919 -type ok \
920 -title [mc "git-gui: fatal error"] \
921 -message [mc "Cannot find git in PATH."]
922 exit 1
923 }
924
925 ######################################################################
926 ##
927 ## version check
928
929 if {[catch {set _git_version [git --version]} err]} {
930 catch {wm withdraw .}
931 tk_messageBox \
932 -icon error \
933 -type ok \
934 -title [mc "git-gui: fatal error"] \
935 -message "Cannot determine Git version:
936
937 $err
938
939 [appname] requires Git 1.5.0 or later."
940 exit 1
941 }
942 if {![regsub {^git version } $_git_version {} _git_version]} {
943 catch {wm withdraw .}
944 tk_messageBox \
945 -icon error \
946 -type ok \
947 -title [mc "git-gui: fatal error"] \
948 -message [strcat [mc "Cannot parse Git version string:"] "\n\n$_git_version"]
949 exit 1
950 }
951
952 proc get_trimmed_version {s} {
953 set r {}
954 foreach x [split $s -._] {
955 if {[string is integer -strict $x]} {
956 lappend r $x
957 } else {
958 break
959 }
960 }
961 return [join $r .]
962 }
963 set _real_git_version $_git_version
964 set _git_version [get_trimmed_version $_git_version]
965
966 if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} {
967 catch {wm withdraw .}
968 if {[tk_messageBox \
969 -icon warning \
970 -type yesno \
971 -default no \
972 -title "[appname]: warning" \
973 -message [mc "Git version cannot be determined.
974
975 %s claims it is version '%s'.
976
977 %s requires at least Git 1.5.0 or later.
978
979 Assume '%s' is version 1.5.0?
980 " $_git $_real_git_version [appname] $_real_git_version]] eq {yes}} {
981 set _git_version 1.5.0
982 } else {
983 exit 1
984 }
985 }
986 unset _real_git_version
987
988 proc git-version {args} {
989 global _git_version
990
991 switch [llength $args] {
992 0 {
993 return $_git_version
994 }
995
996 2 {
997 set op [lindex $args 0]
998 set vr [lindex $args 1]
999 set cm [package vcompare $_git_version $vr]
1000 return [expr $cm $op 0]
1001 }
1002
1003 4 {
1004 set type [lindex $args 0]
1005 set name [lindex $args 1]
1006 set parm [lindex $args 2]
1007 set body [lindex $args 3]
1008
1009 if {($type ne {proc} && $type ne {method})} {
1010 error "Invalid arguments to git-version"
1011 }
1012 if {[llength $body] < 2 || [lindex $body end-1] ne {default}} {
1013 error "Last arm of $type $name must be default"
1014 }
1015
1016 foreach {op vr cb} [lrange $body 0 end-2] {
1017 if {[git-version $op $vr]} {
1018 return [uplevel [list $type $name $parm $cb]]
1019 }
1020 }
1021
1022 return [uplevel [list $type $name $parm [lindex $body end]]]
1023 }
1024
1025 default {
1026 error "git-version >= x"
1027 }
1028
1029 }
1030 }
1031
1032 if {[git-version < 1.5]} {
1033 catch {wm withdraw .}
1034 tk_messageBox \
1035 -icon error \
1036 -type ok \
1037 -title [mc "git-gui: fatal error"] \
1038 -message "[appname] requires Git 1.5.0 or later.
1039
1040 You are using [git-version]:
1041
1042 [git --version]"
1043 exit 1
1044 }
1045
1046 ######################################################################
1047 ##
1048 ## configure our library
1049
1050 set idx [file join $oguilib tclIndex]
1051 if {[catch {set fd [open $idx r]} err]} {
1052 catch {wm withdraw .}
1053 tk_messageBox \
1054 -icon error \
1055 -type ok \
1056 -title [mc "git-gui: fatal error"] \
1057 -message $err
1058 exit 1
1059 }
1060 if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
1061 set idx [list]
1062 while {[gets $fd n] >= 0} {
1063 if {$n ne {} && ![string match #* $n]} {
1064 lappend idx $n
1065 }
1066 }
1067 } else {
1068 set idx {}
1069 }
1070 close $fd
1071
1072 if {$idx ne {}} {
1073 set loaded [list]
1074 foreach p $idx {
1075 if {[lsearch -exact $loaded $p] >= 0} continue
1076 source [file join $oguilib $p]
1077 lappend loaded $p
1078 }
1079 unset loaded p
1080 } else {
1081 set auto_path [concat [list $oguilib] $auto_path]
1082 }
1083 unset -nocomplain idx fd
1084
1085 ######################################################################
1086 ##
1087 ## config file parsing
1088
1089 git-version proc _parse_config {arr_name args} {
1090 >= 1.5.3 {
1091 upvar $arr_name arr
1092 array unset arr
1093 set buf {}
1094 catch {
1095 set fd_rc [eval \
1096 [list git_read config] \
1097 $args \
1098 [list --null --list]]
1099 fconfigure $fd_rc -translation binary -encoding utf-8
1100 set buf [read $fd_rc]
1101 close $fd_rc
1102 }
1103 foreach line [split $buf "\0"] {
1104 if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} {
1105 if {[is_many_config $name]} {
1106 lappend arr($name) $value
1107 } else {
1108 set arr($name) $value
1109 }
1110 } elseif {[regexp {^([^\n]+)$} $line line name]} {
1111 # no value given, but interpreting them as
1112 # boolean will be handled as true
1113 set arr($name) {}
1114 }
1115 }
1116 }
1117 default {
1118 upvar $arr_name arr
1119 array unset arr
1120 catch {
1121 set fd_rc [eval [list git_read config --list] $args]
1122 while {[gets $fd_rc line] >= 0} {
1123 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
1124 if {[is_many_config $name]} {
1125 lappend arr($name) $value
1126 } else {
1127 set arr($name) $value
1128 }
1129 } elseif {[regexp {^([^=]+)$} $line line name]} {
1130 # no value given, but interpreting them as
1131 # boolean will be handled as true
1132 set arr($name) {}
1133 }
1134 }
1135 close $fd_rc
1136 }
1137 }
1138 }
1139
1140 proc load_config {include_global} {
1141 global repo_config global_config system_config default_config
1142
1143 if {$include_global} {
1144 _parse_config system_config --system
1145 _parse_config global_config --global
1146 }
1147 _parse_config repo_config
1148
1149 foreach name [array names default_config] {
1150 if {[catch {set v $system_config($name)}]} {
1151 set system_config($name) $default_config($name)
1152 }
1153 }
1154 foreach name [array names system_config] {
1155 if {[catch {set v $global_config($name)}]} {
1156 set global_config($name) $system_config($name)
1157 }
1158 if {[catch {set v $repo_config($name)}]} {
1159 set repo_config($name) $system_config($name)
1160 }
1161 }
1162 }
1163
1164 ######################################################################
1165 ##
1166 ## feature option selection
1167
1168 if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} {
1169 unset _junk
1170 } else {
1171 set subcommand gui
1172 }
1173 if {$subcommand eq {gui.sh}} {
1174 set subcommand gui
1175 }
1176 if {$subcommand eq {gui} && [llength $argv] > 0} {
1177 set subcommand [lindex $argv 0]
1178 set argv [lrange $argv 1 end]
1179 }
1180
1181 enable_option multicommit
1182 enable_option branch
1183 enable_option transport
1184 disable_option bare
1185
1186 switch -- $subcommand {
1187 browser -
1188 blame {
1189 enable_option bare
1190
1191 disable_option multicommit
1192 disable_option branch
1193 disable_option transport
1194 }
1195 citool {
1196 enable_option singlecommit
1197 enable_option retcode
1198
1199 disable_option multicommit
1200 disable_option branch
1201 disable_option transport
1202
1203 while {[llength $argv] > 0} {
1204 set a [lindex $argv 0]
1205 switch -- $a {
1206 --amend {
1207 enable_option initialamend
1208 }
1209 --nocommit {
1210 enable_option nocommit
1211 enable_option nocommitmsg
1212 }
1213 --commitmsg {
1214 disable_option nocommitmsg
1215 }
1216 default {
1217 break
1218 }
1219 }
1220
1221 set argv [lrange $argv 1 end]
1222 }
1223 }
1224 }
1225
1226 ######################################################################
1227 ##
1228 ## execution environment
1229
1230 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
1231
1232 # Suggest our implementation of askpass, if none is set
1233 if {![info exists env(SSH_ASKPASS)]} {
1234 set env(SSH_ASKPASS) [gitexec git-gui--askpass]
1235 }
1236
1237 ######################################################################
1238 ##
1239 ## repository setup
1240
1241 set picked 0
1242 if {[catch {
1243 set _gitdir $env(GIT_DIR)
1244 set _prefix {}
1245 }]
1246 && [catch {
1247 # beware that from the .git dir this sets _gitdir to .
1248 # and _prefix to the empty string
1249 set _gitdir [git rev-parse --git-dir]
1250 set _prefix [git rev-parse --show-prefix]
1251 } err]} {
1252 load_config 1
1253 apply_config
1254 choose_repository::pick
1255 set picked 1
1256 }
1257
1258 # we expand the _gitdir when it's just a single dot (i.e. when we're being
1259 # run from the .git dir itself) lest the routines to find the worktree
1260 # get confused
1261 if {$_gitdir eq "."} {
1262 set _gitdir [pwd]
1263 }
1264
1265 if {![file isdirectory $_gitdir] && [is_Cygwin]} {
1266 catch {set _gitdir [exec cygpath --windows $_gitdir]}
1267 }
1268 if {![file isdirectory $_gitdir]} {
1269 catch {wm withdraw .}
1270 error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"]
1271 exit 1
1272 }
1273 # _gitdir exists, so try loading the config
1274 load_config 0
1275 apply_config
1276
1277 # v1.7.0 introduced --show-toplevel to return the canonical work-tree
1278 if {[package vcompare $_git_version 1.7.0] >= 0} {
1279 if { [is_Cygwin] } {
1280 catch {set _gitworktree [exec cygpath --windows [git rev-parse --show-toplevel]]}
1281 } else {
1282 set _gitworktree [git rev-parse --show-toplevel]
1283 }
1284 } else {
1285 # try to set work tree from environment, core.worktree or use
1286 # cdup to obtain a relative path to the top of the worktree. If
1287 # run from the top, the ./ prefix ensures normalize expands pwd.
1288 if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} {
1289 set _gitworktree [get_config core.worktree]
1290 if {$_gitworktree eq ""} {
1291 set _gitworktree [file normalize ./[git rev-parse --show-cdup]]
1292 }
1293 }
1294 }
1295
1296 if {$_prefix ne {}} {
1297 if {$_gitworktree eq {}} {
1298 regsub -all {[^/]+/} $_prefix ../ cdup
1299 } else {
1300 set cdup $_gitworktree
1301 }
1302 if {[catch {cd $cdup} err]} {
1303 catch {wm withdraw .}
1304 error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"]
1305 exit 1
1306 }
1307 set _gitworktree [pwd]
1308 unset cdup
1309 } elseif {![is_enabled bare]} {
1310 if {[is_bare]} {
1311 catch {wm withdraw .}
1312 error_popup [strcat [mc "Cannot use bare repository:"] "\n\n$_gitdir"]
1313 exit 1
1314 }
1315 if {$_gitworktree eq {}} {
1316 set _gitworktree [file dirname $_gitdir]
1317 }
1318 if {[catch {cd $_gitworktree} err]} {
1319 catch {wm withdraw .}
1320 error_popup [strcat [mc "No working directory"] " $_gitworktree:\n\n$err"]
1321 exit 1
1322 }
1323 set _gitworktree [pwd]
1324 }
1325 set _reponame [file split [file normalize $_gitdir]]
1326 if {[lindex $_reponame end] eq {.git}} {
1327 set _reponame [lindex $_reponame end-1]
1328 } else {
1329 set _reponame [lindex $_reponame end]
1330 }
1331
1332 set env(GIT_DIR) $_gitdir
1333 set env(GIT_WORK_TREE) $_gitworktree
1334
1335 ######################################################################
1336 ##
1337 ## global init
1338
1339 set current_diff_path {}
1340 set current_diff_side {}
1341 set diff_actions [list]
1342
1343 set HEAD {}
1344 set PARENT {}
1345 set MERGE_HEAD [list]
1346 set commit_type {}
1347 set commit_type_is_amend 0
1348 set empty_tree {}
1349 set current_branch {}
1350 set is_detached 0
1351 set current_diff_path {}
1352 set is_3way_diff 0
1353 set is_submodule_diff 0
1354 set is_conflict_diff 0
1355 set diff_empty_count 0
1356 set last_revert {}
1357 set last_revert_enc {}
1358
1359 set nullid "0000000000000000000000000000000000000000"
1360 set nullid2 "0000000000000000000000000000000000000001"
1361
1362 ######################################################################
1363 ##
1364 ## task management
1365
1366 set rescan_active 0
1367 set diff_active 0
1368 set last_clicked {}
1369
1370 set disable_on_lock [list]
1371 set index_lock_type none
1372
1373 proc lock_index {type} {
1374 global index_lock_type disable_on_lock
1375
1376 if {$index_lock_type eq {none}} {
1377 set index_lock_type $type
1378 foreach w $disable_on_lock {
1379 uplevel #0 $w disabled
1380 }
1381 return 1
1382 } elseif {$index_lock_type eq "begin-$type"} {
1383 set index_lock_type $type
1384 return 1
1385 }
1386 return 0
1387 }
1388
1389 proc unlock_index {} {
1390 global index_lock_type disable_on_lock
1391
1392 set index_lock_type none
1393 foreach w $disable_on_lock {
1394 uplevel #0 $w normal
1395 }
1396 }
1397
1398 ######################################################################
1399 ##
1400 ## status
1401
1402 proc repository_state {ctvar hdvar mhvar} {
1403 global current_branch
1404 upvar $ctvar ct $hdvar hd $mhvar mh
1405
1406 set mh [list]
1407
1408 load_current_branch
1409 if {[catch {set hd [git rev-parse --verify HEAD]}]} {
1410 set hd {}
1411 set ct initial
1412 return
1413 }
1414
1415 set merge_head [gitdir MERGE_HEAD]
1416 if {[file exists $merge_head]} {
1417 set ct merge
1418 set fd_mh [open $merge_head r]
1419 while {[gets $fd_mh line] >= 0} {
1420 lappend mh $line
1421 }
1422 close $fd_mh
1423 return
1424 }
1425
1426 set ct normal
1427 }
1428
1429 proc PARENT {} {
1430 global PARENT empty_tree
1431
1432 set p [lindex $PARENT 0]
1433 if {$p ne {}} {
1434 return $p
1435 }
1436 if {$empty_tree eq {}} {
1437 set empty_tree [git mktree << {}]
1438 }
1439 return $empty_tree
1440 }
1441
1442 proc force_amend {} {
1443 global commit_type_is_amend
1444 global HEAD PARENT MERGE_HEAD commit_type
1445
1446 repository_state newType newHEAD newMERGE_HEAD
1447 set HEAD $newHEAD
1448 set PARENT $newHEAD
1449 set MERGE_HEAD $newMERGE_HEAD
1450 set commit_type $newType
1451
1452 set commit_type_is_amend 1
1453 do_select_commit_type
1454 }
1455
1456 proc rescan {after {honor_trustmtime 1}} {
1457 global HEAD PARENT MERGE_HEAD commit_type
1458 global ui_index ui_workdir ui_comm
1459 global rescan_active file_states
1460 global repo_config
1461
1462 if {$rescan_active > 0 || ![lock_index read]} return
1463
1464 repository_state newType newHEAD newMERGE_HEAD
1465 if {[string match amend* $commit_type]
1466 && $newType eq {normal}
1467 && $newHEAD eq $HEAD} {
1468 } else {
1469 set HEAD $newHEAD
1470 set PARENT $newHEAD
1471 set MERGE_HEAD $newMERGE_HEAD
1472 set commit_type $newType
1473 }
1474
1475 array unset file_states
1476
1477 if {!$::GITGUI_BCK_exists &&
1478 (![$ui_comm edit modified]
1479 || [string trim [$ui_comm get 0.0 end]] eq {})} {
1480 if {[string match amend* $commit_type]} {
1481 } elseif {[load_message GITGUI_MSG utf-8]} {
1482 } elseif {[run_prepare_commit_msg_hook]} {
1483 } elseif {[load_message MERGE_MSG]} {
1484 } elseif {[load_message SQUASH_MSG]} {
1485 }
1486 $ui_comm edit reset
1487 $ui_comm edit modified false
1488 }
1489
1490 if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
1491 rescan_stage2 {} $after
1492 } else {
1493 set rescan_active 1
1494 ui_status [mc "Refreshing file status..."]
1495 set fd_rf [git_read update-index \
1496 -q \
1497 --unmerged \
1498 --ignore-missing \
1499 --refresh \
1500 ]
1501 fconfigure $fd_rf -blocking 0 -translation binary
1502 fileevent $fd_rf readable \
1503 [list rescan_stage2 $fd_rf $after]
1504 }
1505 }
1506
1507 if {[is_Cygwin]} {
1508 set is_git_info_exclude {}
1509 proc have_info_exclude {} {
1510 global is_git_info_exclude
1511
1512 if {$is_git_info_exclude eq {}} {
1513 if {[catch {exec test -f [gitdir info exclude]}]} {
1514 set is_git_info_exclude 0
1515 } else {
1516 set is_git_info_exclude 1
1517 }
1518 }
1519 return $is_git_info_exclude
1520 }
1521 } else {
1522 proc have_info_exclude {} {
1523 return [file readable [gitdir info exclude]]
1524 }
1525 }
1526
1527 proc rescan_stage2 {fd after} {
1528 global rescan_active buf_rdi buf_rdf buf_rlo
1529
1530 if {$fd ne {}} {
1531 read $fd
1532 if {![eof $fd]} return
1533 close $fd
1534 }
1535
1536 if {[package vcompare $::_git_version 1.6.3] >= 0} {
1537 set ls_others [list --exclude-standard]
1538 } else {
1539 set ls_others [list --exclude-per-directory=.gitignore]
1540 if {[have_info_exclude]} {
1541 lappend ls_others "--exclude-from=[gitdir info exclude]"
1542 }
1543 set user_exclude [get_config core.excludesfile]
1544 if {$user_exclude ne {} && [file readable $user_exclude]} {
1545 lappend ls_others "--exclude-from=[file normalize $user_exclude]"
1546 }
1547 }
1548
1549 set buf_rdi {}
1550 set buf_rdf {}
1551 set buf_rlo {}
1552
1553 set rescan_active 2
1554 ui_status [mc "Scanning for modified files ..."]
1555 if {[git-version >= "1.7.2"]} {
1556 set fd_di [git_read diff-index --cached --ignore-submodules=dirty -z [PARENT]]
1557 } else {
1558 set fd_di [git_read diff-index --cached -z [PARENT]]
1559 }
1560 set fd_df [git_read diff-files -z]
1561
1562 fconfigure $fd_di -blocking 0 -translation binary -encoding binary
1563 fconfigure $fd_df -blocking 0 -translation binary -encoding binary
1564
1565 fileevent $fd_di readable [list read_diff_index $fd_di $after]
1566 fileevent $fd_df readable [list read_diff_files $fd_df $after]
1567
1568 if {[is_config_true gui.displayuntracked]} {
1569 set fd_lo [eval git_read ls-files --others -z $ls_others]
1570 fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
1571 fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
1572 incr rescan_active
1573 }
1574 }
1575
1576 proc load_message {file {encoding {}}} {
1577 global ui_comm
1578
1579 set f [gitdir $file]
1580 if {[file isfile $f]} {
1581 if {[catch {set fd [open $f r]}]} {
1582 return 0
1583 }
1584 fconfigure $fd -eofchar {}
1585 if {$encoding ne {}} {
1586 fconfigure $fd -encoding $encoding
1587 }
1588 set content [string trim [read $fd]]
1589 close $fd
1590 regsub -all -line {[ \r\t]+$} $content {} content
1591 $ui_comm delete 0.0 end
1592 $ui_comm insert end $content
1593 return 1
1594 }
1595 return 0
1596 }
1597
1598 proc run_prepare_commit_msg_hook {} {
1599 global pch_error
1600
1601 # prepare-commit-msg requires PREPARE_COMMIT_MSG exist. From git-gui
1602 # it will be .git/MERGE_MSG (merge), .git/SQUASH_MSG (squash), or an
1603 # empty file but existent file.
1604
1605 set fd_pcm [open [gitdir PREPARE_COMMIT_MSG] a]
1606
1607 if {[file isfile [gitdir MERGE_MSG]]} {
1608 set pcm_source "merge"
1609 set fd_mm [open [gitdir MERGE_MSG] r]
1610 fconfigure $fd_mm -encoding utf-8
1611 puts -nonewline $fd_pcm [read $fd_mm]
1612 close $fd_mm
1613 } elseif {[file isfile [gitdir SQUASH_MSG]]} {
1614 set pcm_source "squash"
1615 set fd_sm [open [gitdir SQUASH_MSG] r]
1616 fconfigure $fd_sm -encoding utf-8
1617 puts -nonewline $fd_pcm [read $fd_sm]
1618 close $fd_sm
1619 } else {
1620 set pcm_source ""
1621 }
1622
1623 close $fd_pcm
1624
1625 set fd_ph [githook_read prepare-commit-msg \
1626 [gitdir PREPARE_COMMIT_MSG] $pcm_source]
1627 if {$fd_ph eq {}} {
1628 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1629 return 0;
1630 }
1631
1632 ui_status [mc "Calling prepare-commit-msg hook..."]
1633 set pch_error {}
1634
1635 fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
1636 fileevent $fd_ph readable \
1637 [list prepare_commit_msg_hook_wait $fd_ph]
1638
1639 return 1;
1640 }
1641
1642 proc prepare_commit_msg_hook_wait {fd_ph} {
1643 global pch_error
1644
1645 append pch_error [read $fd_ph]
1646 fconfigure $fd_ph -blocking 1
1647 if {[eof $fd_ph]} {
1648 if {[catch {close $fd_ph}]} {
1649 ui_status [mc "Commit declined by prepare-commit-msg hook."]
1650 hook_failed_popup prepare-commit-msg $pch_error
1651 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1652 exit 1
1653 } else {
1654 load_message PREPARE_COMMIT_MSG
1655 }
1656 set pch_error {}
1657 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1658 return
1659 }
1660 fconfigure $fd_ph -blocking 0
1661 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1662 }
1663
1664 proc read_diff_index {fd after} {
1665 global buf_rdi
1666
1667 append buf_rdi [read $fd]
1668 set c 0
1669 set n [string length $buf_rdi]
1670 while {$c < $n} {
1671 set z1 [string first "\0" $buf_rdi $c]
1672 if {$z1 == -1} break
1673 incr z1
1674 set z2 [string first "\0" $buf_rdi $z1]
1675 if {$z2 == -1} break
1676
1677 incr c
1678 set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
1679 set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
1680 merge_state \
1681 [encoding convertfrom utf-8 $p] \
1682 [lindex $i 4]? \
1683 [list [lindex $i 0] [lindex $i 2]] \
1684 [list]
1685 set c $z2
1686 incr c
1687 }
1688 if {$c < $n} {
1689 set buf_rdi [string range $buf_rdi $c end]
1690 } else {
1691 set buf_rdi {}
1692 }
1693
1694 rescan_done $fd buf_rdi $after
1695 }
1696
1697 proc read_diff_files {fd after} {
1698 global buf_rdf
1699
1700 append buf_rdf [read $fd]
1701 set c 0
1702 set n [string length $buf_rdf]
1703 while {$c < $n} {
1704 set z1 [string first "\0" $buf_rdf $c]
1705 if {$z1 == -1} break
1706 incr z1
1707 set z2 [string first "\0" $buf_rdf $z1]
1708 if {$z2 == -1} break
1709
1710 incr c
1711 set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
1712 set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
1713 merge_state \
1714 [encoding convertfrom utf-8 $p] \
1715 ?[lindex $i 4] \
1716 [list] \
1717 [list [lindex $i 0] [lindex $i 2]]
1718 set c $z2
1719 incr c
1720 }
1721 if {$c < $n} {
1722 set buf_rdf [string range $buf_rdf $c end]
1723 } else {
1724 set buf_rdf {}
1725 }
1726
1727 rescan_done $fd buf_rdf $after
1728 }
1729
1730 proc read_ls_others {fd after} {
1731 global buf_rlo
1732
1733 append buf_rlo [read $fd]
1734 set pck [split $buf_rlo "\0"]
1735 set buf_rlo [lindex $pck end]
1736 foreach p [lrange $pck 0 end-1] {
1737 set p [encoding convertfrom utf-8 $p]
1738 if {[string index $p end] eq {/}} {
1739 set p [string range $p 0 end-1]
1740 }
1741 merge_state $p ?O
1742 }
1743 rescan_done $fd buf_rlo $after
1744 }
1745
1746 proc rescan_done {fd buf after} {
1747 global rescan_active current_diff_path
1748 global file_states repo_config
1749 upvar $buf to_clear
1750
1751 if {![eof $fd]} return
1752 set to_clear {}
1753 close $fd
1754 if {[incr rescan_active -1] > 0} return
1755
1756 prune_selection
1757 unlock_index
1758 display_all_files
1759 if {$current_diff_path ne {}} { reshow_diff $after }
1760 if {$current_diff_path eq {}} { select_first_diff $after }
1761 }
1762
1763 proc prune_selection {} {
1764 global file_states selected_paths
1765
1766 foreach path [array names selected_paths] {
1767 if {[catch {set still_here $file_states($path)}]} {
1768 unset selected_paths($path)
1769 }
1770 }
1771 }
1772
1773 ######################################################################
1774 ##
1775 ## ui helpers
1776
1777 proc mapicon {w state path} {
1778 global all_icons
1779
1780 if {[catch {set r $all_icons($state$w)}]} {
1781 puts "error: no icon for $w state={$state} $path"
1782 return file_plain
1783 }
1784 return $r
1785 }
1786
1787 proc mapdesc {state path} {
1788 global all_descs
1789
1790 if {[catch {set r $all_descs($state)}]} {
1791 puts "error: no desc for state={$state} $path"
1792 return $state
1793 }
1794 return $r
1795 }
1796
1797 proc ui_status {msg} {
1798 global main_status
1799 if {[info exists main_status]} {
1800 $main_status show $msg
1801 }
1802 }
1803
1804 proc ui_ready {} {
1805 global main_status
1806 if {[info exists main_status]} {
1807 $main_status show [mc "Ready."]
1808 }
1809 }
1810
1811 proc escape_path {path} {
1812 regsub -all {\\} $path "\\\\" path
1813 regsub -all "\n" $path "\\n" path
1814 return $path
1815 }
1816
1817 proc short_path {path} {
1818 return [escape_path [lindex [file split $path] end]]
1819 }
1820
1821 set next_icon_id 0
1822 set null_sha1 [string repeat 0 40]
1823
1824 proc merge_state {path new_state {head_info {}} {index_info {}}} {
1825 global file_states next_icon_id null_sha1
1826
1827 set s0 [string index $new_state 0]
1828 set s1 [string index $new_state 1]
1829
1830 if {[catch {set info $file_states($path)}]} {
1831 set state __
1832 set icon n[incr next_icon_id]
1833 } else {
1834 set state [lindex $info 0]
1835 set icon [lindex $info 1]
1836 if {$head_info eq {}} {set head_info [lindex $info 2]}
1837 if {$index_info eq {}} {set index_info [lindex $info 3]}
1838 }
1839
1840 if {$s0 eq {?}} {set s0 [string index $state 0]} \
1841 elseif {$s0 eq {_}} {set s0 _}
1842
1843 if {$s1 eq {?}} {set s1 [string index $state 1]} \
1844 elseif {$s1 eq {_}} {set s1 _}
1845
1846 if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
1847 set head_info [list 0 $null_sha1]
1848 } elseif {$s0 ne {_} && [string index $state 0] eq {_}
1849 && $head_info eq {}} {
1850 set head_info $index_info
1851 } elseif {$s0 eq {_} && [string index $state 0] ne {_}} {
1852 set index_info $head_info
1853 set head_info {}
1854 }
1855
1856 set file_states($path) [list $s0$s1 $icon \
1857 $head_info $index_info \
1858 ]
1859 return $state
1860 }
1861
1862 proc display_file_helper {w path icon_name old_m new_m} {
1863 global file_lists
1864
1865 if {$new_m eq {_}} {
1866 set lno [lsearch -sorted -exact $file_lists($w) $path]
1867 if {$lno >= 0} {
1868 set file_lists($w) [lreplace $file_lists($w) $lno $lno]
1869 incr lno
1870 $w conf -state normal
1871 $w delete $lno.0 [expr {$lno + 1}].0
1872 $w conf -state disabled
1873 }
1874 } elseif {$old_m eq {_} && $new_m ne {_}} {
1875 lappend file_lists($w) $path
1876 set file_lists($w) [lsort -unique $file_lists($w)]
1877 set lno [lsearch -sorted -exact $file_lists($w) $path]
1878 incr lno
1879 $w conf -state normal
1880 $w image create $lno.0 \
1881 -align center -padx 5 -pady 1 \
1882 -name $icon_name \
1883 -image [mapicon $w $new_m $path]
1884 $w insert $lno.1 "[escape_path $path]\n"
1885 $w conf -state disabled
1886 } elseif {$old_m ne $new_m} {
1887 $w conf -state normal
1888 $w image conf $icon_name -image [mapicon $w $new_m $path]
1889 $w conf -state disabled
1890 }
1891 }
1892
1893 proc display_file {path state} {
1894 global file_states selected_paths
1895 global ui_index ui_workdir
1896
1897 set old_m [merge_state $path $state]
1898 set s $file_states($path)
1899 set new_m [lindex $s 0]
1900 set icon_name [lindex $s 1]
1901
1902 set o [string index $old_m 0]
1903 set n [string index $new_m 0]
1904 if {$o eq {U}} {
1905 set o _
1906 }
1907 if {$n eq {U}} {
1908 set n _
1909 }
1910 display_file_helper $ui_index $path $icon_name $o $n
1911
1912 if {[string index $old_m 0] eq {U}} {
1913 set o U
1914 } else {
1915 set o [string index $old_m 1]
1916 }
1917 if {[string index $new_m 0] eq {U}} {
1918 set n U
1919 } else {
1920 set n [string index $new_m 1]
1921 }
1922 display_file_helper $ui_workdir $path $icon_name $o $n
1923
1924 if {$new_m eq {__}} {
1925 unset file_states($path)
1926 catch {unset selected_paths($path)}
1927 }
1928 }
1929
1930 proc display_all_files_helper {w path icon_name m} {
1931 global file_lists
1932
1933 lappend file_lists($w) $path
1934 set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
1935 $w image create end \
1936 -align center -padx 5 -pady 1 \
1937 -name $icon_name \
1938 -image [mapicon $w $m $path]
1939 $w insert end "[escape_path $path]\n"
1940 }
1941
1942 set files_warning 0
1943 proc display_all_files {} {
1944 global ui_index ui_workdir
1945 global file_states file_lists
1946 global last_clicked
1947 global files_warning
1948
1949 $ui_index conf -state normal
1950 $ui_workdir conf -state normal
1951
1952 $ui_index delete 0.0 end
1953 $ui_workdir delete 0.0 end
1954 set last_clicked {}
1955
1956 set file_lists($ui_index) [list]
1957 set file_lists($ui_workdir) [list]
1958
1959 set to_display [lsort [array names file_states]]
1960 set display_limit [get_config gui.maxfilesdisplayed]
1961 set displayed 0
1962 foreach path $to_display {
1963 set s $file_states($path)
1964 set m [lindex $s 0]
1965 set icon_name [lindex $s 1]
1966
1967 if {$displayed > $display_limit && [string index $m 1] eq {O} } {
1968 if {!$files_warning} {
1969 # do not repeatedly warn:
1970 set files_warning 1
1971 info_popup [mc "Display limit (gui.maxfilesdisplayed = %s) reached, not showing all %s files." \
1972 $display_limit [llength $to_display]]
1973 }
1974 continue
1975 }
1976
1977 set s [string index $m 0]
1978 if {$s ne {U} && $s ne {_}} {
1979 display_all_files_helper $ui_index $path \
1980 $icon_name $s
1981 }
1982
1983 if {[string index $m 0] eq {U}} {
1984 set s U
1985 } else {
1986 set s [string index $m 1]
1987 }
1988 if {$s ne {_}} {
1989 display_all_files_helper $ui_workdir $path \
1990 $icon_name $s
1991 incr displayed
1992 }
1993 }
1994
1995 $ui_index conf -state disabled
1996 $ui_workdir conf -state disabled
1997 }
1998
1999 ######################################################################
2000 ##
2001 ## icons
2002
2003 set filemask {
2004 #define mask_width 14
2005 #define mask_height 15
2006 static unsigned char mask_bits[] = {
2007 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
2008 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
2009 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
2010 }
2011
2012 image create bitmap file_plain -background white -foreground black -data {
2013 #define plain_width 14
2014 #define plain_height 15
2015 static unsigned char plain_bits[] = {
2016 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
2017 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
2018 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
2019 } -maskdata $filemask
2020
2021 image create bitmap file_mod -background white -foreground blue -data {
2022 #define mod_width 14
2023 #define mod_height 15
2024 static unsigned char mod_bits[] = {
2025 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
2026 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
2027 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
2028 } -maskdata $filemask
2029
2030 image create bitmap file_fulltick -background white -foreground "#007000" -data {
2031 #define file_fulltick_width 14
2032 #define file_fulltick_height 15
2033 static unsigned char file_fulltick_bits[] = {
2034 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
2035 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
2036 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
2037 } -maskdata $filemask
2038
2039 image create bitmap file_question -background white -foreground black -data {
2040 #define file_question_width 14
2041 #define file_question_height 15
2042 static unsigned char file_question_bits[] = {
2043 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
2044 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
2045 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
2046 } -maskdata $filemask
2047
2048 image create bitmap file_removed -background white -foreground red -data {
2049 #define file_removed_width 14
2050 #define file_removed_height 15
2051 static unsigned char file_removed_bits[] = {
2052 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
2053 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
2054 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
2055 } -maskdata $filemask
2056
2057 image create bitmap file_merge -background white -foreground blue -data {
2058 #define file_merge_width 14
2059 #define file_merge_height 15
2060 static unsigned char file_merge_bits[] = {
2061 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
2062 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
2063 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
2064 } -maskdata $filemask
2065
2066 image create bitmap file_statechange -background white -foreground green -data {
2067 #define file_statechange_width 14
2068 #define file_statechange_height 15
2069 static unsigned char file_statechange_bits[] = {
2070 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x62, 0x10,
2071 0x62, 0x10, 0xba, 0x11, 0xba, 0x11, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10,
2072 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
2073 } -maskdata $filemask
2074
2075 set ui_index .vpane.files.index.list
2076 set ui_workdir .vpane.files.workdir.list
2077
2078 set all_icons(_$ui_index) file_plain
2079 set all_icons(A$ui_index) file_plain
2080 set all_icons(M$ui_index) file_fulltick
2081 set all_icons(D$ui_index) file_removed
2082 set all_icons(U$ui_index) file_merge
2083 set all_icons(T$ui_index) file_statechange
2084
2085 set all_icons(_$ui_workdir) file_plain
2086 set all_icons(M$ui_workdir) file_mod
2087 set all_icons(D$ui_workdir) file_question
2088 set all_icons(U$ui_workdir) file_merge
2089 set all_icons(O$ui_workdir) file_plain
2090 set all_icons(T$ui_workdir) file_statechange
2091
2092 set max_status_desc 0
2093 foreach i {
2094 {__ {mc "Unmodified"}}
2095
2096 {_M {mc "Modified, not staged"}}
2097 {M_ {mc "Staged for commit"}}
2098 {MM {mc "Portions staged for commit"}}
2099 {MD {mc "Staged for commit, missing"}}
2100
2101 {_T {mc "File type changed, not staged"}}
2102 {MT {mc "File type changed, old type staged for commit"}}
2103 {AT {mc "File type changed, old type staged for commit"}}
2104 {T_ {mc "File type changed, staged"}}
2105 {TM {mc "File type change staged, modification not staged"}}
2106 {TD {mc "File type change staged, file missing"}}
2107
2108 {_O {mc "Untracked, not staged"}}
2109 {A_ {mc "Staged for commit"}}
2110 {AM {mc "Portions staged for commit"}}
2111 {AD {mc "Staged for commit, missing"}}
2112
2113 {_D {mc "Missing"}}
2114 {D_ {mc "Staged for removal"}}
2115 {DO {mc "Staged for removal, still present"}}
2116
2117 {_U {mc "Requires merge resolution"}}
2118 {U_ {mc "Requires merge resolution"}}
2119 {UU {mc "Requires merge resolution"}}
2120 {UM {mc "Requires merge resolution"}}
2121 {UD {mc "Requires merge resolution"}}
2122 {UT {mc "Requires merge resolution"}}
2123 } {
2124 set text [eval [lindex $i 1]]
2125 if {$max_status_desc < [string length $text]} {
2126 set max_status_desc [string length $text]
2127 }
2128 set all_descs([lindex $i 0]) $text
2129 }
2130 unset i
2131
2132 ######################################################################
2133 ##
2134 ## util
2135
2136 proc scrollbar2many {list mode args} {
2137 foreach w $list {eval $w $mode $args}
2138 }
2139
2140 proc many2scrollbar {list mode sb top bottom} {
2141 $sb set $top $bottom
2142 foreach w $list {$w $mode moveto $top}
2143 }
2144
2145 proc incr_font_size {font {amt 1}} {
2146 set sz [font configure $font -size]
2147 incr sz $amt
2148 font configure $font -size $sz
2149 font configure ${font}bold -size $sz
2150 font configure ${font}italic -size $sz
2151 }
2152
2153 ######################################################################
2154 ##
2155 ## ui commands
2156
2157 proc do_gitk {revs {is_submodule false}} {
2158 global current_diff_path file_states current_diff_side ui_index
2159 global _gitdir _gitworktree
2160
2161 # -- Always start gitk through whatever we were loaded with. This
2162 # lets us bypass using shell process on Windows systems.
2163 #
2164 set exe [_which gitk -script]
2165 set cmd [list [info nameofexecutable] $exe]
2166 if {$exe eq {}} {
2167 error_popup [mc "Couldn't find gitk in PATH"]
2168 } else {
2169 global env
2170
2171 set pwd [pwd]
2172
2173 if {!$is_submodule} {
2174 if {![is_bare]} {
2175 cd $_gitworktree
2176 }
2177 } else {
2178 cd $current_diff_path
2179 if {$revs eq {--}} {
2180 set s $file_states($current_diff_path)
2181 set old_sha1 {}
2182 set new_sha1 {}
2183 switch -glob -- [lindex $s 0] {
2184 M_ { set old_sha1 [lindex [lindex $s 2] 1] }
2185 _M { set old_sha1 [lindex [lindex $s 3] 1] }
2186 MM {
2187 if {$current_diff_side eq $ui_index} {
2188 set old_sha1 [lindex [lindex $s 2] 1]
2189 set new_sha1 [lindex [lindex $s 3] 1]
2190 } else {
2191 set old_sha1 [lindex [lindex $s 3] 1]
2192 }
2193 }
2194 }
2195 set revs $old_sha1...$new_sha1
2196 }
2197 # GIT_DIR and GIT_WORK_TREE for the submodule are not the ones
2198 # we've been using for the main repository, so unset them.
2199 # TODO we could make life easier (start up faster?) for gitk
2200 # by setting these to the appropriate values to allow gitk
2201 # to skip the heuristics to find their proper value
2202 unset env(GIT_DIR)
2203 unset env(GIT_WORK_TREE)
2204 }
2205 eval exec $cmd $revs "--" "--" &
2206
2207 set env(GIT_DIR) $_gitdir
2208 set env(GIT_WORK_TREE) $_gitworktree
2209 cd $pwd
2210
2211 if {[info exists main_status]} {
2212 set status_operation [$::main_status \
2213 start \
2214 [mc "Starting %s... please wait..." "gitk"]]
2215
2216 after 3500 [list $status_operation stop]
2217 }
2218 }
2219 }
2220
2221 proc do_git_gui {} {
2222 global current_diff_path
2223
2224 # -- Always start git gui through whatever we were loaded with. This
2225 # lets us bypass using shell process on Windows systems.
2226 #
2227 set exe [list [_which git]]
2228 if {$exe eq {}} {
2229 error_popup [mc "Couldn't find git gui in PATH"]
2230 } else {
2231 global env
2232 global _gitdir _gitworktree
2233
2234 # see note in do_gitk about unsetting these vars when
2235 # running tools in a submodule
2236 unset env(GIT_DIR)
2237 unset env(GIT_WORK_TREE)
2238
2239 set pwd [pwd]
2240 cd $current_diff_path
2241
2242 eval exec $exe gui &
2243
2244 set env(GIT_DIR) $_gitdir
2245 set env(GIT_WORK_TREE) $_gitworktree
2246 cd $pwd
2247
2248 set status_operation [$::main_status \
2249 start \
2250 [mc "Starting %s... please wait..." "git-gui"]]
2251
2252 after 3500 [list $status_operation stop]
2253 }
2254 }
2255
2256 # Get the system-specific explorer app/command.
2257 proc get_explorer {} {
2258 if {[is_Cygwin] || [is_Windows]} {
2259 set explorer "explorer.exe"
2260 } elseif {[is_MacOSX]} {
2261 set explorer "open"
2262 } else {
2263 # freedesktop.org-conforming system is our best shot
2264 set explorer "xdg-open"
2265 }
2266 return $explorer
2267 }
2268
2269 proc do_explore {} {
2270 global _gitworktree
2271 set explorer [get_explorer]
2272 eval exec $explorer [list [file nativename $_gitworktree]] &
2273 }
2274
2275 # Open file relative to the working tree by the default associated app.
2276 proc do_file_open {file} {
2277 global _gitworktree
2278 set explorer [get_explorer]
2279 set full_file_path [file join $_gitworktree $file]
2280 exec $explorer [file nativename $full_file_path] &
2281 }
2282
2283 set is_quitting 0
2284 set ret_code 1
2285
2286 proc terminate_me {win} {
2287 global ret_code
2288 if {$win ne {.}} return
2289 exit $ret_code
2290 }
2291
2292 proc do_quit {{rc {1}}} {
2293 global ui_comm is_quitting repo_config commit_type
2294 global GITGUI_BCK_exists GITGUI_BCK_i
2295 global ui_comm_spell
2296 global ret_code use_ttk
2297
2298 if {$is_quitting} return
2299 set is_quitting 1
2300
2301 if {[winfo exists $ui_comm]} {
2302 # -- Stash our current commit buffer.
2303 #
2304 set save [gitdir GITGUI_MSG]
2305 if {$GITGUI_BCK_exists && ![$ui_comm edit modified]} {
2306 file rename -force [gitdir GITGUI_BCK] $save
2307 set GITGUI_BCK_exists 0
2308 } else {
2309 set msg [string trim [$ui_comm get 0.0 end]]
2310 regsub -all -line {[ \r\t]+$} $msg {} msg
2311 if {(![string match amend* $commit_type]
2312 || [$ui_comm edit modified])
2313 && $msg ne {}} {
2314 catch {
2315 set fd [open $save w]
2316 fconfigure $fd -encoding utf-8
2317 puts -nonewline $fd $msg
2318 close $fd
2319 }
2320 } else {
2321 catch {file delete $save}
2322 }
2323 }
2324
2325 # -- Cancel our spellchecker if its running.
2326 #
2327 if {[info exists ui_comm_spell]} {
2328 $ui_comm_spell stop
2329 }
2330
2331 # -- Remove our editor backup, its not needed.
2332 #
2333 after cancel $GITGUI_BCK_i
2334 if {$GITGUI_BCK_exists} {
2335 catch {file delete [gitdir GITGUI_BCK]}
2336 }
2337
2338 # -- Stash our current window geometry into this repository.
2339 #
2340 set cfg_wmstate [wm state .]
2341 if {[catch {set rc_wmstate $repo_config(gui.wmstate)}]} {
2342 set rc_wmstate {}
2343 }
2344 if {$cfg_wmstate ne $rc_wmstate} {
2345 catch {git config gui.wmstate $cfg_wmstate}
2346 }
2347 if {$cfg_wmstate eq {zoomed}} {
2348 # on Windows wm geometry will lie about window
2349 # position (but not size) when window is zoomed
2350 # restore the window before querying wm geometry
2351 wm state . normal
2352 }
2353 set cfg_geometry [list]
2354 lappend cfg_geometry [wm geometry .]
2355 if {$use_ttk} {
2356 lappend cfg_geometry [.vpane sashpos 0]
2357 lappend cfg_geometry [.vpane.files sashpos 0]
2358 } else {
2359 lappend cfg_geometry [lindex [.vpane sash coord 0] 0]
2360 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 1]
2361 }
2362 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
2363 set rc_geometry {}
2364 }
2365 if {$cfg_geometry ne $rc_geometry} {
2366 catch {git config gui.geometry $cfg_geometry}
2367 }
2368 }
2369
2370 set ret_code $rc
2371
2372 # Briefly enable send again, working around Tk bug
2373 # http://sourceforge.net/tracker/?func=detail&atid=112997&aid=1821174&group_id=12997
2374 tk appname [appname]
2375
2376 destroy .
2377 }
2378
2379 proc do_rescan {} {
2380 rescan ui_ready
2381 }
2382
2383 proc ui_do_rescan {} {
2384 rescan {force_first_diff ui_ready}
2385 }
2386
2387 proc do_commit {} {
2388 commit_tree
2389 }
2390
2391 proc next_diff {{after {}}} {
2392 global next_diff_p next_diff_w next_diff_i
2393 show_diff $next_diff_p $next_diff_w {} {} $after
2394 }
2395
2396 proc find_anchor_pos {lst name} {
2397 set lid [lsearch -sorted -exact $lst $name]
2398
2399 if {$lid == -1} {
2400 set lid 0
2401 foreach lname $lst {
2402 if {$lname >= $name} break
2403 incr lid
2404 }
2405 }
2406
2407 return $lid
2408 }
2409
2410 proc find_file_from {flist idx delta path mmask} {
2411 global file_states
2412
2413 set len [llength $flist]
2414 while {$idx >= 0 && $idx < $len} {
2415 set name [lindex $flist $idx]
2416
2417 if {$name ne $path && [info exists file_states($name)]} {
2418 set state [lindex $file_states($name) 0]
2419
2420 if {$mmask eq {} || [regexp $mmask $state]} {
2421 return $idx
2422 }
2423 }
2424
2425 incr idx $delta
2426 }
2427
2428 return {}
2429 }
2430
2431 proc find_next_diff {w path {lno {}} {mmask {}}} {
2432 global next_diff_p next_diff_w next_diff_i
2433 global file_lists ui_index ui_workdir
2434
2435 set flist $file_lists($w)
2436 if {$lno eq {}} {
2437 set lno [find_anchor_pos $flist $path]
2438 } else {
2439 incr lno -1
2440 }
2441
2442 if {$mmask ne {} && ![regexp {(^\^)|(\$$)} $mmask]} {
2443 if {$w eq $ui_index} {
2444 set mmask "^$mmask"
2445 } else {
2446 set mmask "$mmask\$"
2447 }
2448 }
2449
2450 set idx [find_file_from $flist $lno 1 $path $mmask]
2451 if {$idx eq {}} {
2452 incr lno -1
2453 set idx [find_file_from $flist $lno -1 $path $mmask]
2454 }
2455
2456 if {$idx ne {}} {
2457 set next_diff_w $w
2458 set next_diff_p [lindex $flist $idx]
2459 set next_diff_i [expr {$idx+1}]
2460 return 1
2461 } else {
2462 return 0
2463 }
2464 }
2465
2466 proc next_diff_after_action {w path {lno {}} {mmask {}}} {
2467 global current_diff_path
2468
2469 if {$path ne $current_diff_path} {
2470 return {}
2471 } elseif {[find_next_diff $w $path $lno $mmask]} {
2472 return {next_diff;}
2473 } else {
2474 return {reshow_diff;}
2475 }
2476 }
2477
2478 proc select_first_diff {after} {
2479 global ui_workdir
2480
2481 if {[find_next_diff $ui_workdir {} 1 {^_?U}] ||
2482 [find_next_diff $ui_workdir {} 1 {[^O]$}]} {
2483 next_diff $after
2484 } else {
2485 uplevel #0 $after
2486 }
2487 }
2488
2489 proc force_first_diff {after} {
2490 global ui_workdir current_diff_path file_states
2491
2492 if {[info exists file_states($current_diff_path)]} {
2493 set state [lindex $file_states($current_diff_path) 0]
2494 } else {
2495 set state {OO}
2496 }
2497
2498 set reselect 0
2499 if {[string first {U} $state] >= 0} {
2500 # Already a conflict, do nothing
2501 } elseif {[find_next_diff $ui_workdir $current_diff_path {} {^_?U}]} {
2502 set reselect 1
2503 } elseif {[string index $state 1] ne {O}} {
2504 # Already a diff & no conflicts, do nothing
2505 } elseif {[find_next_diff $ui_workdir $current_diff_path {} {[^O]$}]} {
2506 set reselect 1
2507 }
2508
2509 if {$reselect} {
2510 next_diff $after
2511 } else {
2512 uplevel #0 $after
2513 }
2514 }
2515
2516 proc toggle_or_diff {mode w args} {
2517 global file_states file_lists current_diff_path ui_index ui_workdir
2518 global last_clicked selected_paths file_lists_last_clicked
2519
2520 if {$mode eq "click"} {
2521 foreach {x y} $args break
2522 set pos [split [$w index @$x,$y] .]
2523 foreach {lno col} $pos break
2524 } else {
2525 if {$mode eq "toggle"} {
2526 if {$w eq $ui_workdir} {
2527 do_add_selection
2528 set last_clicked {}
2529 return
2530 }
2531 if {$w eq $ui_index} {
2532 do_unstage_selection
2533 set last_clicked {}
2534 return
2535 }
2536 }
2537
2538 if {$last_clicked ne {}} {
2539 set lno [lindex $last_clicked 1]
2540 } else {
2541 if {![info exists file_lists]
2542 || ![info exists file_lists($w)]
2543 || [llength $file_lists($w)] == 0} {
2544 set last_clicked {}
2545 return
2546 }
2547 set lno [expr {int([lindex [$w tag ranges in_diff] 0])}]
2548 }
2549 if {$mode eq "toggle"} {
2550 set col 0; set y 2
2551 } else {
2552 incr lno [expr {$mode eq "up" ? -1 : 1}]
2553 set col 1
2554 }
2555 }
2556
2557 if {![info exists file_lists]
2558 || ![info exists file_lists($w)]
2559 || [llength $file_lists($w)] < $lno - 1} {
2560 set path {}
2561 } else {
2562 set path [lindex $file_lists($w) [expr {$lno - 1}]]
2563 }
2564 if {$path eq {}} {
2565 set last_clicked {}
2566 return
2567 }
2568
2569 set last_clicked [list $w $lno]
2570 focus $w
2571 array unset selected_paths
2572 $ui_index tag remove in_sel 0.0 end
2573 $ui_workdir tag remove in_sel 0.0 end
2574
2575 set file_lists_last_clicked($w) $path
2576
2577 # Determine the state of the file
2578 if {[info exists file_states($path)]} {
2579 set state [lindex $file_states($path) 0]
2580 } else {
2581 set state {__}
2582 }
2583
2584 # Restage the file, or simply show the diff
2585 if {$col == 0 && $y > 1} {
2586 # Conflicts need special handling
2587 if {[string first {U} $state] >= 0} {
2588 # $w must always be $ui_workdir, but...
2589 if {$w ne $ui_workdir} { set lno {} }
2590 merge_stage_workdir $path $lno
2591 return
2592 }
2593
2594 if {[string index $state 1] eq {O}} {
2595 set mmask {}
2596 } else {
2597 set mmask {[^O]}
2598 }
2599
2600 set after [next_diff_after_action $w $path $lno $mmask]
2601
2602 if {$w eq $ui_index} {
2603 update_indexinfo \
2604 "Unstaging [short_path $path] from commit" \
2605 [list $path] \
2606 [concat $after {ui_ready;}]
2607 } elseif {$w eq $ui_workdir} {
2608 update_index \
2609 "Adding [short_path $path]" \
2610 [list $path] \
2611 [concat $after {ui_ready;}]
2612 }
2613 } else {
2614 set selected_paths($path) 1
2615 show_diff $path $w $lno
2616 }
2617 }
2618
2619 proc add_one_to_selection {w x y} {
2620 global file_lists last_clicked selected_paths
2621
2622 set lno [lindex [split [$w index @$x,$y] .] 0]
2623 set path [lindex $file_lists($w) [expr {$lno - 1}]]
2624 if {$path eq {}} {
2625 set last_clicked {}
2626 return
2627 }
2628
2629 if {$last_clicked ne {}
2630 && [lindex $last_clicked 0] ne $w} {
2631 array unset selected_paths
2632 [lindex $last_clicked 0] tag remove in_sel 0.0 end
2633 }
2634
2635 set last_clicked [list $w $lno]
2636 if {[catch {set in_sel $selected_paths($path)}]} {
2637 set in_sel 0
2638 }
2639 if {$in_sel} {
2640 unset selected_paths($path)
2641 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
2642 } else {
2643 set selected_paths($path) 1
2644 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
2645 }
2646 }
2647
2648 proc add_range_to_selection {w x y} {
2649 global file_lists last_clicked selected_paths
2650
2651 if {[lindex $last_clicked 0] ne $w} {
2652 toggle_or_diff click $w $x $y
2653 return
2654 }
2655
2656 set lno [lindex [split [$w index @$x,$y] .] 0]
2657 set lc [lindex $last_clicked 1]
2658 if {$lc < $lno} {
2659 set begin $lc
2660 set end $lno
2661 } else {
2662 set begin $lno
2663 set end $lc
2664 }
2665
2666 foreach path [lrange $file_lists($w) \
2667 [expr {$begin - 1}] \
2668 [expr {$end - 1}]] {
2669 set selected_paths($path) 1
2670 }
2671 $w tag add in_sel $begin.0 [expr {$end + 1}].0
2672 }
2673
2674 proc show_more_context {} {
2675 global repo_config
2676 if {$repo_config(gui.diffcontext) < 99} {
2677 incr repo_config(gui.diffcontext)
2678 reshow_diff
2679 }
2680 }
2681
2682 proc show_less_context {} {
2683 global repo_config
2684 if {$repo_config(gui.diffcontext) > 1} {
2685 incr repo_config(gui.diffcontext) -1
2686 reshow_diff
2687 }
2688 }
2689
2690 proc focus_widget {widget} {
2691 global file_lists last_clicked selected_paths
2692 global file_lists_last_clicked
2693
2694 if {[llength $file_lists($widget)] > 0} {
2695 set path $file_lists_last_clicked($widget)
2696 set index [lsearch -sorted -exact $file_lists($widget) $path]
2697 if {$index < 0} {
2698 set index 0
2699 set path [lindex $file_lists($widget) $index]
2700 }
2701
2702 focus $widget
2703 set last_clicked [list $widget [expr $index + 1]]
2704 array unset selected_paths
2705 set selected_paths($path) 1
2706 show_diff $path $widget
2707 }
2708 }
2709
2710 proc toggle_commit_type {} {
2711 global commit_type_is_amend
2712 set commit_type_is_amend [expr !$commit_type_is_amend]
2713 do_select_commit_type
2714 }
2715
2716 ######################################################################
2717 ##
2718 ## ui construction
2719
2720 set ui_comm {}
2721
2722 # -- Menu Bar
2723 #
2724 menu .mbar -tearoff 0
2725 if {[is_MacOSX]} {
2726 # -- Apple Menu (Mac OS X only)
2727 #
2728 .mbar add cascade -label Apple -menu .mbar.apple
2729 menu .mbar.apple
2730 }
2731 .mbar add cascade -label [mc Repository] -menu .mbar.repository
2732 .mbar add cascade -label [mc Edit] -menu .mbar.edit
2733 if {[is_enabled branch]} {
2734 .mbar add cascade -label [mc Branch] -menu .mbar.branch
2735 }
2736 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2737 .mbar add cascade -label [mc Commit@@noun] -menu .mbar.commit
2738 }
2739 if {[is_enabled transport]} {
2740 .mbar add cascade -label [mc Merge] -menu .mbar.merge
2741 .mbar add cascade -label [mc Remote] -menu .mbar.remote
2742 }
2743 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2744 .mbar add cascade -label [mc Tools] -menu .mbar.tools
2745 }
2746
2747 # -- Repository Menu
2748 #
2749 menu .mbar.repository
2750
2751 if {![is_bare]} {
2752 .mbar.repository add command \
2753 -label [mc "Explore Working Copy"] \
2754 -command {do_explore}
2755 }
2756
2757 if {[is_Windows]} {
2758 # Use /git-bash.exe if available
2759 set normalized [file normalize $::argv0]
2760 regsub "/mingw../libexec/git-core/git-gui$" \
2761 $normalized "/git-bash.exe" cmdLine
2762 if {$cmdLine != $normalized && [file exists $cmdLine]} {
2763 set cmdLine [list "Git Bash" $cmdLine &]
2764 } else {
2765 set cmdLine [list "Git Bash" bash --login -l &]
2766 }
2767 .mbar.repository add command \
2768 -label [mc "Git Bash"] \
2769 -command {eval exec [auto_execok start] $cmdLine}
2770 }
2771
2772 if {[is_Windows] || ![is_bare]} {
2773 .mbar.repository add separator
2774 }
2775
2776 .mbar.repository add command \
2777 -label [mc "Browse Current Branch's Files"] \
2778 -command {browser::new $current_branch}
2779 set ui_browse_current [.mbar.repository index last]
2780 .mbar.repository add command \
2781 -label [mc "Browse Branch Files..."] \
2782 -command browser_open::dialog
2783 .mbar.repository add separator
2784
2785 .mbar.repository add command \
2786 -label [mc "Visualize Current Branch's History"] \
2787 -command {do_gitk $current_branch}
2788 set ui_visualize_current [.mbar.repository index last]
2789 .mbar.repository add command \
2790 -label [mc "Visualize All Branch History"] \
2791 -command {do_gitk --all}
2792 .mbar.repository add separator
2793
2794 proc current_branch_write {args} {
2795 global current_branch
2796 .mbar.repository entryconf $::ui_browse_current \
2797 -label [mc "Browse %s's Files" $current_branch]
2798 .mbar.repository entryconf $::ui_visualize_current \
2799 -label [mc "Visualize %s's History" $current_branch]
2800 }
2801 trace add variable current_branch write current_branch_write
2802
2803 if {[is_enabled multicommit]} {
2804 .mbar.repository add command -label [mc "Database Statistics"] \
2805 -command do_stats
2806
2807 .mbar.repository add command -label [mc "Compress Database"] \
2808 -command do_gc
2809
2810 .mbar.repository add command -label [mc "Verify Database"] \
2811 -command do_fsck_objects
2812
2813 .mbar.repository add separator
2814
2815 if {[is_Cygwin]} {
2816 .mbar.repository add command \
2817 -label [mc "Create Desktop Icon"] \
2818 -command do_cygwin_shortcut
2819 } elseif {[is_Windows]} {
2820 .mbar.repository add command \
2821 -label [mc "Create Desktop Icon"] \
2822 -command do_windows_shortcut
2823 } elseif {[is_MacOSX]} {
2824 .mbar.repository add command \
2825 -label [mc "Create Desktop Icon"] \
2826 -command do_macosx_app
2827 }
2828 }
2829
2830 if {[is_MacOSX]} {
2831 proc ::tk::mac::Quit {args} { do_quit }
2832 } else {
2833 .mbar.repository add command -label [mc Quit] \
2834 -command do_quit \
2835 -accelerator $M1T-Q
2836 }
2837
2838 # -- Edit Menu
2839 #
2840 menu .mbar.edit
2841 .mbar.edit add command -label [mc Undo] \
2842 -command {catch {[focus] edit undo}} \
2843 -accelerator $M1T-Z
2844 .mbar.edit add command -label [mc Redo] \
2845 -command {catch {[focus] edit redo}} \
2846 -accelerator $M1T-Y
2847 .mbar.edit add separator
2848 .mbar.edit add command -label [mc Cut] \
2849 -command {catch {tk_textCut [focus]}} \
2850 -accelerator $M1T-X
2851 .mbar.edit add command -label [mc Copy] \
2852 -command {catch {tk_textCopy [focus]}} \
2853 -accelerator $M1T-C
2854 .mbar.edit add command -label [mc Paste] \
2855 -command {catch {tk_textPaste [focus]; [focus] see insert}} \
2856 -accelerator $M1T-V
2857 .mbar.edit add command -label [mc Delete] \
2858 -command {catch {[focus] delete sel.first sel.last}} \
2859 -accelerator Del
2860 .mbar.edit add separator
2861 .mbar.edit add command -label [mc "Select All"] \
2862 -command {catch {[focus] tag add sel 0.0 end}} \
2863 -accelerator $M1T-A
2864
2865 # -- Branch Menu
2866 #
2867 if {[is_enabled branch]} {
2868 menu .mbar.branch
2869
2870 .mbar.branch add command -label [mc "Create..."] \
2871 -command branch_create::dialog \
2872 -accelerator $M1T-N
2873 lappend disable_on_lock [list .mbar.branch entryconf \
2874 [.mbar.branch index last] -state]
2875
2876 .mbar.branch add command -label [mc "Checkout..."] \
2877 -command branch_checkout::dialog \
2878 -accelerator $M1T-O
2879 lappend disable_on_lock [list .mbar.branch entryconf \
2880 [.mbar.branch index last] -state]
2881
2882 .mbar.branch add command -label [mc "Rename..."] \
2883 -command branch_rename::dialog
2884 lappend disable_on_lock [list .mbar.branch entryconf \
2885 [.mbar.branch index last] -state]
2886
2887 .mbar.branch add command -label [mc "Delete..."] \
2888 -command branch_delete::dialog
2889 lappend disable_on_lock [list .mbar.branch entryconf \
2890 [.mbar.branch index last] -state]
2891
2892 .mbar.branch add command -label [mc "Reset..."] \
2893 -command merge::reset_hard
2894 lappend disable_on_lock [list .mbar.branch entryconf \
2895 [.mbar.branch index last] -state]
2896 }
2897
2898 # -- Commit Menu
2899 #
2900 proc commit_btn_caption {} {
2901 if {[is_enabled nocommit]} {
2902 return [mc "Done"]
2903 } else {
2904 return [mc Commit@@verb]
2905 }
2906 }
2907
2908 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2909 menu .mbar.commit
2910
2911 if {![is_enabled nocommit]} {
2912 .mbar.commit add checkbutton \
2913 -label [mc "Amend Last Commit"] \
2914 -accelerator $M1T-E \
2915 -variable commit_type_is_amend \
2916 -command do_select_commit_type
2917 lappend disable_on_lock \
2918 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2919
2920 .mbar.commit add separator
2921 }
2922
2923 .mbar.commit add command -label [mc Rescan] \
2924 -command ui_do_rescan \
2925 -accelerator F5
2926 lappend disable_on_lock \
2927 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2928
2929 .mbar.commit add command -label [mc "Stage To Commit"] \
2930 -command do_add_selection \
2931 -accelerator $M1T-T
2932 lappend disable_on_lock \
2933 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2934
2935 .mbar.commit add command -label [mc "Stage Changed Files To Commit"] \
2936 -command do_add_all \
2937 -accelerator $M1T-I
2938 lappend disable_on_lock \
2939 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2940
2941 .mbar.commit add command -label [mc "Unstage From Commit"] \
2942 -command do_unstage_selection \
2943 -accelerator $M1T-U
2944 lappend disable_on_lock \
2945 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2946
2947 .mbar.commit add command -label [mc "Revert Changes"] \
2948 -command do_revert_selection \
2949 -accelerator $M1T-J
2950 lappend disable_on_lock \
2951 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2952
2953 .mbar.commit add separator
2954
2955 .mbar.commit add command -label [mc "Show Less Context"] \
2956 -command show_less_context \
2957 -accelerator $M1T-\-
2958
2959 .mbar.commit add command -label [mc "Show More Context"] \
2960 -command show_more_context \
2961 -accelerator $M1T-=
2962
2963 .mbar.commit add separator
2964
2965 if {![is_enabled nocommitmsg]} {
2966 .mbar.commit add command -label [mc "Sign Off"] \
2967 -command do_signoff \
2968 -accelerator $M1T-S
2969 }
2970
2971 .mbar.commit add command -label [commit_btn_caption] \
2972 -command do_commit \
2973 -accelerator $M1T-Return
2974 lappend disable_on_lock \
2975 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2976 }
2977
2978 # -- Merge Menu
2979 #
2980 if {[is_enabled branch]} {
2981 menu .mbar.merge
2982 .mbar.merge add command -label [mc "Local Merge..."] \
2983 -command merge::dialog \
2984 -accelerator $M1T-M
2985 lappend disable_on_lock \
2986 [list .mbar.merge entryconf [.mbar.merge index last] -state]
2987 .mbar.merge add command -label [mc "Abort Merge..."] \
2988 -command merge::reset_hard
2989 lappend disable_on_lock \
2990 [list .mbar.merge entryconf [.mbar.merge index last] -state]
2991 }
2992
2993 # -- Transport Menu
2994 #
2995 if {[is_enabled transport]} {
2996 menu .mbar.remote
2997
2998 .mbar.remote add command \
2999 -label [mc "Add..."] \
3000 -command remote_add::dialog \
3001 -accelerator $M1T-A
3002 .mbar.remote add command \
3003 -label [mc "Push..."] \
3004 -command do_push_anywhere \
3005 -accelerator $M1T-P
3006 .mbar.remote add command \
3007 -label [mc "Delete Branch..."] \
3008 -command remote_branch_delete::dialog
3009 }
3010
3011 if {[is_MacOSX]} {
3012 proc ::tk::mac::ShowPreferences {} {do_options}
3013 } else {
3014 # -- Edit Menu
3015 #
3016 .mbar.edit add separator
3017 .mbar.edit add command -label [mc "Options..."] \
3018 -command do_options
3019 }
3020
3021 # -- Tools Menu
3022 #
3023 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
3024 set tools_menubar .mbar.tools
3025 menu $tools_menubar
3026 $tools_menubar add separator
3027 $tools_menubar add command -label [mc "Add..."] -command tools_add::dialog
3028 $tools_menubar add command -label [mc "Remove..."] -command tools_remove::dialog
3029 set tools_tailcnt 3
3030 if {[array names repo_config guitool.*.cmd] ne {}} {
3031 tools_populate_all
3032 }
3033 }
3034
3035 # -- Help Menu
3036 #
3037 .mbar add cascade -label [mc Help] -menu .mbar.help
3038 menu .mbar.help
3039
3040 if {[is_MacOSX]} {
3041 .mbar.apple add command -label [mc "About %s" [appname]] \
3042 -command do_about
3043 .mbar.apple add separator
3044 } else {
3045 .mbar.help add command -label [mc "About %s" [appname]] \
3046 -command do_about
3047 }
3048 . configure -menu .mbar
3049
3050 set doc_path [githtmldir]
3051 if {$doc_path ne {}} {
3052 set doc_path [file join $doc_path index.html]
3053
3054 if {[is_Cygwin]} {
3055 set doc_path [exec cygpath --mixed $doc_path]
3056 }
3057 }
3058
3059 if {[file isfile $doc_path]} {
3060 set doc_url "file:$doc_path"
3061 } else {
3062 set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
3063 }
3064
3065 proc start_browser {url} {
3066 git "web--browse" $url
3067 }
3068
3069 .mbar.help add command -label [mc "Online Documentation"] \
3070 -command [list start_browser $doc_url]
3071
3072 .mbar.help add command -label [mc "Show SSH Key"] \
3073 -command do_ssh_key
3074
3075 unset doc_path doc_url
3076
3077 # -- Standard bindings
3078 #
3079 wm protocol . WM_DELETE_WINDOW do_quit
3080 bind all <$M1B-Key-q> do_quit
3081 bind all <$M1B-Key-Q> do_quit
3082
3083 set m1b_w_script {
3084 set toplvl_win [winfo toplevel %W]
3085
3086 # If we are destroying the main window, we should call do_quit to take
3087 # care of cleanup before exiting the program.
3088 if {$toplvl_win eq "."} {
3089 do_quit
3090 } else {
3091 destroy $toplvl_win
3092 }
3093 }
3094
3095 bind all <$M1B-Key-w> $m1b_w_script
3096 bind all <$M1B-Key-W> $m1b_w_script
3097
3098 unset m1b_w_script
3099
3100 set subcommand_args {}
3101 proc usage {} {
3102 set s "[mc usage:] $::argv0 $::subcommand $::subcommand_args"
3103 if {[tk windowingsystem] eq "win32"} {
3104 wm withdraw .
3105 tk_messageBox -icon info -message $s \
3106 -title [mc "Usage"]
3107 } else {
3108 puts stderr $s
3109 }
3110 exit 1
3111 }
3112
3113 proc normalize_relpath {path} {
3114 set elements {}
3115 foreach item [file split $path] {
3116 if {$item eq {.}} continue
3117 if {$item eq {..} && [llength $elements] > 0
3118 && [lindex $elements end] ne {..}} {
3119 set elements [lrange $elements 0 end-1]
3120 continue
3121 }
3122 lappend elements $item
3123 }
3124 return [eval file join $elements]
3125 }
3126
3127 # -- Not a normal commit type invocation? Do that instead!
3128 #
3129 switch -- $subcommand {
3130 browser -
3131 blame {
3132 if {$subcommand eq "blame"} {
3133 set subcommand_args {[--line=<num>] rev? path}
3134 } else {
3135 set subcommand_args {rev? path}
3136 }
3137 if {$argv eq {}} usage
3138 set head {}
3139 set path {}
3140 set jump_spec {}
3141 set is_path 0
3142 foreach a $argv {
3143 set p [file join $_prefix $a]
3144
3145 if {$is_path || [file exists $p]} {
3146 if {$path ne {}} usage
3147 set path [normalize_relpath $p]
3148 break
3149 } elseif {$a eq {--}} {
3150 if {$path ne {}} {
3151 if {$head ne {}} usage
3152 set head $path
3153 set path {}
3154 }
3155 set is_path 1
3156 } elseif {[regexp {^--line=(\d+)$} $a a lnum]} {
3157 if {$jump_spec ne {} || $head ne {}} usage
3158 set jump_spec [list $lnum]
3159 } elseif {$head eq {}} {
3160 if {$head ne {}} usage
3161 set head $a
3162 set is_path 1
3163 } else {
3164 usage
3165 }
3166 }
3167 unset is_path
3168
3169 if {$head ne {} && $path eq {}} {
3170 if {[string index $head 0] eq {/}} {
3171 set path [normalize_relpath $head]
3172 set head {}
3173 } else {
3174 set path [normalize_relpath $_prefix$head]
3175 set head {}
3176 }
3177 }
3178
3179 if {$head eq {}} {
3180 load_current_branch
3181 } else {
3182 if {[regexp {^[0-9a-f]{1,39}$} $head]} {
3183 if {[catch {
3184 set head [git rev-parse --verify $head]
3185 } err]} {
3186 if {[tk windowingsystem] eq "win32"} {
3187 tk_messageBox -icon error -title [mc Error] -message $err
3188 } else {
3189 puts stderr $err
3190 }
3191 exit 1
3192 }
3193 }
3194 set current_branch $head
3195 }
3196
3197 wm deiconify .
3198 switch -- $subcommand {
3199 browser {
3200 if {$jump_spec ne {}} usage
3201 if {$head eq {}} {
3202 if {$path ne {} && [file isdirectory $path]} {
3203 set head $current_branch
3204 } else {
3205 set head $path
3206 set path {}
3207 }
3208 }
3209 browser::new $head $path
3210 }
3211 blame {
3212 if {$head eq {} && ![file exists $path]} {
3213 catch {wm withdraw .}
3214 tk_messageBox \
3215 -icon error \
3216 -type ok \
3217 -title [mc "git-gui: fatal error"] \
3218 -message [mc "fatal: cannot stat path %s: No such file or directory" $path]
3219 exit 1
3220 }
3221 blame::new $head $path $jump_spec
3222 }
3223 }
3224 return
3225 }
3226 citool -
3227 gui {
3228 if {[llength $argv] != 0} {
3229 usage
3230 }
3231 # fall through to setup UI for commits
3232 }
3233 default {
3234 set err "[mc usage:] $argv0 \[{blame|browser|citool}\]"
3235 if {[tk windowingsystem] eq "win32"} {
3236 wm withdraw .
3237 tk_messageBox -icon error -message $err \
3238 -title [mc "Usage"]
3239 } else {
3240 puts stderr $err
3241 }
3242 exit 1
3243 }
3244 }
3245
3246 # -- Branch Control
3247 #
3248 ${NS}::frame .branch
3249 if {!$use_ttk} {.branch configure -borderwidth 1 -relief sunken}
3250 ${NS}::label .branch.l1 \
3251 -text [mc "Current Branch:"] \
3252 -anchor w \
3253 -justify left
3254 ${NS}::label .branch.cb \
3255 -textvariable current_branch \
3256 -anchor w \
3257 -justify left
3258 pack .branch.l1 -side left
3259 pack .branch.cb -side left -fill x
3260 pack .branch -side top -fill x
3261
3262 # -- Main Window Layout
3263 #
3264 ${NS}::panedwindow .vpane -orient horizontal
3265 ${NS}::panedwindow .vpane.files -orient vertical
3266 if {$use_ttk} {
3267 .vpane add .vpane.files
3268 } else {
3269 .vpane add .vpane.files -sticky nsew -height 100 -width 200
3270 }
3271 pack .vpane -anchor n -side top -fill both -expand 1
3272
3273 # -- Working Directory File List
3274
3275 textframe .vpane.files.workdir -height 100 -width 200
3276 tlabel .vpane.files.workdir.title -text [mc "Unstaged Changes"] \
3277 -background lightsalmon -foreground black
3278 ttext $ui_workdir \
3279 -borderwidth 0 \
3280 -width 20 -height 10 \
3281 -wrap none \
3282 -takefocus 1 -highlightthickness 1\
3283 -cursor $cursor_ptr \
3284 -xscrollcommand {.vpane.files.workdir.sx set} \
3285 -yscrollcommand {.vpane.files.workdir.sy set} \
3286 -state disabled
3287 ${NS}::scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
3288 ${NS}::scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
3289 pack .vpane.files.workdir.title -side top -fill x
3290 pack .vpane.files.workdir.sx -side bottom -fill x
3291 pack .vpane.files.workdir.sy -side right -fill y
3292 pack $ui_workdir -side left -fill both -expand 1
3293
3294 # -- Index File List
3295 #
3296 textframe .vpane.files.index -height 100 -width 200
3297 tlabel .vpane.files.index.title \
3298 -text [mc "Staged Changes (Will Commit)"] \
3299 -background lightgreen -foreground black
3300 ttext $ui_index \
3301 -borderwidth 0 \
3302 -width 20 -height 10 \
3303 -wrap none \
3304 -takefocus 1 -highlightthickness 1\
3305 -cursor $cursor_ptr \
3306 -xscrollcommand {.vpane.files.index.sx set} \
3307 -yscrollcommand {.vpane.files.index.sy set} \
3308 -state disabled
3309 ${NS}::scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
3310 ${NS}::scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
3311 pack .vpane.files.index.title -side top -fill x
3312 pack .vpane.files.index.sx -side bottom -fill x
3313 pack .vpane.files.index.sy -side right -fill y
3314 pack $ui_index -side left -fill both -expand 1
3315
3316 # -- Insert the workdir and index into the panes
3317 #
3318 .vpane.files add .vpane.files.workdir
3319 .vpane.files add .vpane.files.index
3320 if {!$use_ttk} {
3321 .vpane.files paneconfigure .vpane.files.workdir -sticky news
3322 .vpane.files paneconfigure .vpane.files.index -sticky news
3323 }
3324
3325 foreach i [list $ui_index $ui_workdir] {
3326 rmsel_tag $i
3327 $i tag conf in_diff \
3328 -background $color::select_bg \
3329 -foreground $color::select_fg
3330 }
3331 unset i
3332
3333 # -- Diff and Commit Area
3334 #
3335 if {$have_tk85} {
3336 ${NS}::panedwindow .vpane.lower -orient vertical
3337 ${NS}::frame .vpane.lower.commarea
3338 ${NS}::frame .vpane.lower.diff -relief sunken -borderwidth 1 -height 500
3339 .vpane.lower add .vpane.lower.diff
3340 .vpane.lower add .vpane.lower.commarea
3341 .vpane add .vpane.lower
3342 if {$use_ttk} {
3343 .vpane.lower pane .vpane.lower.diff -weight 1
3344 .vpane.lower pane .vpane.lower.commarea -weight 0
3345 } else {
3346 .vpane.lower paneconfigure .vpane.lower.diff -stretch always
3347 .vpane.lower paneconfigure .vpane.lower.commarea -stretch never
3348 }
3349 } else {
3350 frame .vpane.lower -height 300 -width 400
3351 frame .vpane.lower.commarea
3352 frame .vpane.lower.diff -relief sunken -borderwidth 1
3353 pack .vpane.lower.diff -fill both -expand 1
3354 pack .vpane.lower.commarea -side bottom -fill x
3355 .vpane add .vpane.lower
3356 .vpane paneconfigure .vpane.lower -sticky nsew
3357 }
3358
3359 # -- Commit Area Buttons
3360 #
3361 ${NS}::frame .vpane.lower.commarea.buttons
3362 ${NS}::label .vpane.lower.commarea.buttons.l -text {} \
3363 -anchor w \
3364 -justify left
3365 pack .vpane.lower.commarea.buttons.l -side top -fill x
3366 pack .vpane.lower.commarea.buttons -side left -fill y
3367
3368 ${NS}::button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \
3369 -command ui_do_rescan
3370 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
3371 lappend disable_on_lock \
3372 {.vpane.lower.commarea.buttons.rescan conf -state}
3373
3374 ${NS}::button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \
3375 -command do_add_all
3376 pack .vpane.lower.commarea.buttons.incall -side top -fill x
3377 lappend disable_on_lock \
3378 {.vpane.lower.commarea.buttons.incall conf -state}
3379
3380 if {![is_enabled nocommitmsg]} {
3381 ${NS}::button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \
3382 -command do_signoff
3383 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
3384 }
3385
3386 ${NS}::button .vpane.lower.commarea.buttons.commit -text [commit_btn_caption] \
3387 -command do_commit
3388 pack .vpane.lower.commarea.buttons.commit -side top -fill x
3389 lappend disable_on_lock \
3390 {.vpane.lower.commarea.buttons.commit conf -state}
3391
3392 if {![is_enabled nocommit]} {
3393 ${NS}::button .vpane.lower.commarea.buttons.push -text [mc Push] \
3394 -command do_push_anywhere
3395 pack .vpane.lower.commarea.buttons.push -side top -fill x
3396 }
3397
3398 # -- Commit Message Buffer
3399 #
3400 ${NS}::frame .vpane.lower.commarea.buffer
3401 ${NS}::frame .vpane.lower.commarea.buffer.header
3402 set ui_comm .vpane.lower.commarea.buffer.frame.t
3403 set ui_coml .vpane.lower.commarea.buffer.header.l
3404
3405 if {![is_enabled nocommit]} {
3406 ${NS}::checkbutton .vpane.lower.commarea.buffer.header.amend \
3407 -text [mc "Amend Last Commit"] \
3408 -variable commit_type_is_amend \
3409 -command do_select_commit_type
3410 lappend disable_on_lock \
3411 [list .vpane.lower.commarea.buffer.header.amend conf -state]
3412 }
3413
3414 ${NS}::label $ui_coml \
3415 -anchor w \
3416 -justify left
3417 proc trace_commit_type {varname args} {
3418 global ui_coml commit_type
3419 switch -glob -- $commit_type {
3420 initial {set txt [mc "Initial Commit Message:"]}
3421 amend {set txt [mc "Amended Commit Message:"]}
3422 amend-initial {set txt [mc "Amended Initial Commit Message:"]}
3423 amend-merge {set txt [mc "Amended Merge Commit Message:"]}
3424 merge {set txt [mc "Merge Commit Message:"]}
3425 * {set txt [mc "Commit Message:"]}
3426 }
3427 $ui_coml conf -text $txt
3428 }
3429 trace add variable commit_type write trace_commit_type
3430 pack $ui_coml -side left -fill x
3431
3432 if {![is_enabled nocommit]} {
3433 pack .vpane.lower.commarea.buffer.header.amend -side right
3434 }
3435
3436 textframe .vpane.lower.commarea.buffer.frame
3437 ttext $ui_comm \
3438 -borderwidth 1 \
3439 -undo true \
3440 -maxundo 20 \
3441 -autoseparators true \
3442 -takefocus 1 \
3443 -highlightthickness 1 \
3444 -relief sunken \
3445 -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \
3446 -font font_diff \
3447 -xscrollcommand {.vpane.lower.commarea.buffer.frame.sbx set} \
3448 -yscrollcommand {.vpane.lower.commarea.buffer.frame.sby set}
3449 ${NS}::scrollbar .vpane.lower.commarea.buffer.frame.sbx \
3450 -orient horizontal \
3451 -command [list $ui_comm xview]
3452 ${NS}::scrollbar .vpane.lower.commarea.buffer.frame.sby \
3453 -orient vertical \
3454 -command [list $ui_comm yview]
3455
3456 pack .vpane.lower.commarea.buffer.frame.sbx -side bottom -fill x
3457 pack .vpane.lower.commarea.buffer.frame.sby -side right -fill y
3458 pack $ui_comm -side left -fill y
3459 pack .vpane.lower.commarea.buffer.header -side top -fill x
3460 pack .vpane.lower.commarea.buffer.frame -side left -fill y
3461 pack .vpane.lower.commarea.buffer -side left -fill y
3462
3463 # -- Commit Message Buffer Context Menu
3464 #
3465 set ctxm .vpane.lower.commarea.buffer.ctxm
3466 menu $ctxm -tearoff 0
3467 $ctxm add command \
3468 -label [mc Cut] \
3469 -command {tk_textCut $ui_comm}
3470 $ctxm add command \
3471 -label [mc Copy] \
3472 -command {tk_textCopy $ui_comm}
3473 $ctxm add command \
3474 -label [mc Paste] \
3475 -command {tk_textPaste $ui_comm}
3476 $ctxm add command \
3477 -label [mc Delete] \
3478 -command {catch {$ui_comm delete sel.first sel.last}}
3479 $ctxm add separator
3480 $ctxm add command \
3481 -label [mc "Select All"] \
3482 -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
3483 $ctxm add command \
3484 -label [mc "Copy All"] \
3485 -command {
3486 $ui_comm tag add sel 0.0 end
3487 tk_textCopy $ui_comm
3488 $ui_comm tag remove sel 0.0 end
3489 }
3490 $ctxm add separator
3491 $ctxm add command \
3492 -label [mc "Sign Off"] \
3493 -command do_signoff
3494 set ui_comm_ctxm $ctxm
3495
3496 # -- Diff Header
3497 #
3498 proc trace_current_diff_path {varname args} {
3499 global current_diff_path diff_actions file_states
3500 if {$current_diff_path eq {}} {
3501 set s {}
3502 set f {}
3503 set p {}
3504 set o disabled
3505 } else {
3506 set p $current_diff_path
3507 set s [mapdesc [lindex $file_states($p) 0] $p]
3508 set f [mc "File:"]
3509 set p [escape_path $p]
3510 set o normal
3511 }
3512
3513 .vpane.lower.diff.header.status configure -text $s
3514 .vpane.lower.diff.header.file configure -text $f
3515 .vpane.lower.diff.header.path configure -text $p
3516 foreach w $diff_actions {
3517 uplevel #0 $w $o
3518 }
3519 }
3520 trace add variable current_diff_path write trace_current_diff_path
3521
3522 gold_frame .vpane.lower.diff.header
3523 tlabel .vpane.lower.diff.header.status \
3524 -background gold \
3525 -foreground black \
3526 -width $max_status_desc \
3527 -anchor w \
3528 -justify left
3529 tlabel .vpane.lower.diff.header.file \
3530 -background gold \
3531 -foreground black \
3532 -anchor w \
3533 -justify left
3534 tlabel .vpane.lower.diff.header.path \
3535 -background gold \
3536 -foreground blue \
3537 -anchor w \
3538 -justify left \
3539 -font [eval font create [font configure font_ui] -underline 1] \
3540 -cursor hand2
3541 pack .vpane.lower.diff.header.status -side left
3542 pack .vpane.lower.diff.header.file -side left
3543 pack .vpane.lower.diff.header.path -fill x
3544 set ctxm .vpane.lower.diff.header.ctxm
3545 menu $ctxm -tearoff 0
3546 $ctxm add command \
3547 -label [mc Copy] \
3548 -command {
3549 clipboard clear
3550 clipboard append \
3551 -format STRING \
3552 -type STRING \
3553 -- $current_diff_path
3554 }
3555 $ctxm add command \
3556 -label [mc Open] \
3557 -command {do_file_open $current_diff_path}
3558 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3559 bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
3560 bind .vpane.lower.diff.header.path <Button-1> {do_file_open $current_diff_path}
3561
3562 # -- Diff Body
3563 #
3564 textframe .vpane.lower.diff.body
3565 set ui_diff .vpane.lower.diff.body.t
3566 ttext $ui_diff \
3567 -borderwidth 0 \
3568 -width 80 -height 5 -wrap none \
3569 -font font_diff \
3570 -takefocus 1 -highlightthickness 1 \
3571 -xscrollcommand {.vpane.lower.diff.body.sbx set} \
3572 -yscrollcommand {.vpane.lower.diff.body.sby set} \
3573 -state disabled
3574 catch {$ui_diff configure -tabstyle wordprocessor}
3575 ${NS}::scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
3576 -command [list $ui_diff xview]
3577 ${NS}::scrollbar .vpane.lower.diff.body.sby -orient vertical \
3578 -command [list $ui_diff yview]
3579 pack .vpane.lower.diff.body.sbx -side bottom -fill x
3580 pack .vpane.lower.diff.body.sby -side right -fill y
3581 pack $ui_diff -side left -fill both -expand 1
3582 pack .vpane.lower.diff.header -side top -fill x
3583 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
3584
3585 foreach {n c} {0 black 1 red4 2 green4 3 yellow4 4 blue4 5 magenta4 6 cyan4 7 grey60} {
3586 $ui_diff tag configure clr4$n -background $c
3587 $ui_diff tag configure clri4$n -foreground $c
3588 $ui_diff tag configure clr3$n -foreground $c
3589 $ui_diff tag configure clri3$n -background $c
3590 }
3591 $ui_diff tag configure clr1 -font font_diffbold
3592 $ui_diff tag configure clr4 -underline 1
3593
3594 $ui_diff tag conf d_info -foreground blue -font font_diffbold
3595
3596 $ui_diff tag conf d_cr -elide true
3597 $ui_diff tag conf d_@ -font font_diffbold
3598 $ui_diff tag conf d_+ -foreground {#00a000}
3599 $ui_diff tag conf d_- -foreground red
3600
3601 $ui_diff tag conf d_++ -foreground {#00a000}
3602 $ui_diff tag conf d_-- -foreground red
3603 $ui_diff tag conf d_+s \
3604 -foreground {#00a000} \
3605 -background {#e2effa}
3606 $ui_diff tag conf d_-s \
3607 -foreground red \
3608 -background {#e2effa}
3609 $ui_diff tag conf d_s+ \
3610 -foreground {#00a000} \
3611 -background ivory1
3612 $ui_diff tag conf d_s- \
3613 -foreground red \
3614 -background ivory1
3615
3616 $ui_diff tag conf d< \
3617 -foreground orange \
3618 -font font_diffbold
3619 $ui_diff tag conf d| \
3620 -foreground orange \
3621 -font font_diffbold
3622 $ui_diff tag conf d= \
3623 -foreground orange \
3624 -font font_diffbold
3625 $ui_diff tag conf d> \
3626 -foreground orange \
3627 -font font_diffbold
3628
3629 $ui_diff tag raise sel
3630
3631 # -- Diff Body Context Menu
3632 #
3633
3634 proc create_common_diff_popup {ctxm} {
3635 $ctxm add command \
3636 -label [mc Refresh] \
3637 -command reshow_diff
3638 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3639 $ctxm add command \
3640 -label [mc Copy] \
3641 -command {tk_textCopy $ui_diff}
3642 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3643 $ctxm add command \
3644 -label [mc "Select All"] \
3645 -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
3646 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3647 $ctxm add command \
3648 -label [mc "Copy All"] \
3649 -command {
3650 $ui_diff tag add sel 0.0 end
3651 tk_textCopy $ui_diff
3652 $ui_diff tag remove sel 0.0 end
3653 }
3654 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3655 $ctxm add separator
3656 $ctxm add command \
3657 -label [mc "Decrease Font Size"] \
3658 -command {incr_font_size font_diff -1}
3659 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3660 $ctxm add command \
3661 -label [mc "Increase Font Size"] \
3662 -command {incr_font_size font_diff 1}
3663 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3664 $ctxm add separator
3665 set emenu $ctxm.enc
3666 menu $emenu
3667 build_encoding_menu $emenu [list force_diff_encoding]
3668 $ctxm add cascade \
3669 -label [mc "Encoding"] \
3670 -menu $emenu
3671 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3672 $ctxm add separator
3673 $ctxm add command -label [mc "Options..."] \
3674 -command do_options
3675 }
3676
3677 set ctxm .vpane.lower.diff.body.ctxm
3678 menu $ctxm -tearoff 0
3679 $ctxm add command \
3680 -label [mc "Apply/Reverse Hunk"] \
3681 -command {apply_or_revert_hunk $cursorX $cursorY 0}
3682 set ui_diff_applyhunk [$ctxm index last]
3683 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
3684 $ctxm add command \
3685 -label [mc "Apply/Reverse Line"] \
3686 -command {apply_or_revert_range_or_line $cursorX $cursorY 0; do_rescan}
3687 set ui_diff_applyline [$ctxm index last]
3688 lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state]
3689 $ctxm add separator
3690 $ctxm add command \
3691 -label [mc "Revert Hunk"] \
3692 -command {apply_or_revert_hunk $cursorX $cursorY 1}
3693 set ui_diff_reverthunk [$ctxm index last]
3694 lappend diff_actions [list $ctxm entryconf $ui_diff_reverthunk -state]
3695 $ctxm add command \
3696 -label [mc "Revert Line"] \
3697 -command {apply_or_revert_range_or_line $cursorX $cursorY 1; do_rescan}
3698 set ui_diff_revertline [$ctxm index last]
3699 lappend diff_actions [list $ctxm entryconf $ui_diff_revertline -state]
3700 $ctxm add command \
3701 -label [mc "Undo Last Revert"] \
3702 -command {undo_last_revert; do_rescan}
3703 set ui_diff_undorevert [$ctxm index last]
3704 lappend diff_actions [list $ctxm entryconf $ui_diff_undorevert -state]
3705 $ctxm add separator
3706 $ctxm add command \
3707 -label [mc "Show Less Context"] \
3708 -command show_less_context
3709 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3710 $ctxm add command \
3711 -label [mc "Show More Context"] \
3712 -command show_more_context
3713 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3714 $ctxm add separator
3715 create_common_diff_popup $ctxm
3716
3717 set ctxmmg .vpane.lower.diff.body.ctxmmg
3718 menu $ctxmmg -tearoff 0
3719 $ctxmmg add command \
3720 -label [mc "Run Merge Tool"] \
3721 -command {merge_resolve_tool}
3722 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3723 $ctxmmg add separator
3724 $ctxmmg add command \
3725 -label [mc "Use Remote Version"] \
3726 -command {merge_resolve_one 3}
3727 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3728 $ctxmmg add command \
3729 -label [mc "Use Local Version"] \
3730 -command {merge_resolve_one 2}
3731 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3732 $ctxmmg add command \
3733 -label [mc "Revert To Base"] \
3734 -command {merge_resolve_one 1}
3735 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3736 $ctxmmg add separator
3737 $ctxmmg add command \
3738 -label [mc "Show Less Context"] \
3739 -command show_less_context
3740 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3741 $ctxmmg add command \
3742 -label [mc "Show More Context"] \
3743 -command show_more_context
3744 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3745 $ctxmmg add separator
3746 create_common_diff_popup $ctxmmg
3747
3748 set ctxmsm .vpane.lower.diff.body.ctxmsm
3749 menu $ctxmsm -tearoff 0
3750 $ctxmsm add command \
3751 -label [mc "Visualize These Changes In The Submodule"] \
3752 -command {do_gitk -- true}
3753 lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
3754 $ctxmsm add command \
3755 -label [mc "Visualize Current Branch History In The Submodule"] \
3756 -command {do_gitk {} true}
3757 lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
3758 $ctxmsm add command \
3759 -label [mc "Visualize All Branch History In The Submodule"] \
3760 -command {do_gitk --all true}
3761 lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
3762 $ctxmsm add separator
3763 $ctxmsm add command \
3764 -label [mc "Start git gui In The Submodule"] \
3765 -command {do_git_gui}
3766 lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
3767 $ctxmsm add separator
3768 create_common_diff_popup $ctxmsm
3769
3770 proc has_textconv {path} {
3771 if {[is_config_false gui.textconv]} {
3772 return 0
3773 }
3774 set filter [gitattr $path diff set]
3775 set textconv [get_config [join [list diff $filter textconv] .]]
3776 if {$filter ne {set} && $textconv ne {}} {
3777 return 1
3778 } else {
3779 return 0
3780 }
3781 }
3782
3783 proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} {
3784 global current_diff_path file_states last_revert
3785 set ::cursorX $x
3786 set ::cursorY $y
3787 if {[info exists file_states($current_diff_path)]} {
3788 set state [lindex $file_states($current_diff_path) 0]
3789 } else {
3790 set state {__}
3791 }
3792 if {[string first {U} $state] >= 0} {
3793 tk_popup $ctxmmg $X $Y
3794 } elseif {$::is_submodule_diff} {
3795 tk_popup $ctxmsm $X $Y
3796 } else {
3797 set has_range [expr {[$::ui_diff tag nextrange sel 0.0] != {}}]
3798 set u [mc "Undo Last Revert"]
3799 if {$::ui_index eq $::current_diff_side} {
3800 set l [mc "Unstage Hunk From Commit"]
3801 set h [mc "Revert Hunk"]
3802
3803 if {$has_range} {
3804 set t [mc "Unstage Lines From Commit"]
3805 set r [mc "Revert Lines"]
3806 } else {
3807 set t [mc "Unstage Line From Commit"]
3808 set r [mc "Revert Line"]
3809 }
3810 } else {
3811 set l [mc "Stage Hunk For Commit"]
3812 set h [mc "Revert Hunk"]
3813
3814 if {$has_range} {
3815 set t [mc "Stage Lines For Commit"]
3816 set r [mc "Revert Lines"]
3817 } else {
3818 set t [mc "Stage Line For Commit"]
3819 set r [mc "Revert Line"]
3820 }
3821 }
3822 if {$::is_3way_diff
3823 || $current_diff_path eq {}
3824 || {__} eq $state
3825 || {_O} eq $state
3826 || [string match {?T} $state]
3827 || [string match {T?} $state]
3828 || [has_textconv $current_diff_path]} {
3829 set s disabled
3830 set revert_state disabled
3831 } else {
3832 set s normal
3833
3834 # Only allow reverting changes in the working tree. If
3835 # the user wants to revert changes in the index, they
3836 # need to unstage those first.
3837 if {$::ui_workdir eq $::current_diff_side} {
3838 set revert_state normal
3839 } else {
3840 set revert_state disabled
3841 }
3842 }
3843
3844 if {$last_revert eq {}} {
3845 set undo_state disabled
3846 } else {
3847 set undo_state normal
3848 }
3849
3850 $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
3851 $ctxm entryconf $::ui_diff_applyline -state $s -label $t
3852 $ctxm entryconf $::ui_diff_revertline -state $revert_state \
3853 -label $r
3854 $ctxm entryconf $::ui_diff_reverthunk -state $revert_state \
3855 -label $h
3856 $ctxm entryconf $::ui_diff_undorevert -state $undo_state \
3857 -label $u
3858
3859 tk_popup $ctxm $X $Y
3860 }
3861 }
3862 bind_button3 $ui_diff [list popup_diff_menu $ctxm $ctxmmg $ctxmsm %x %y %X %Y]
3863
3864 # -- Status Bar
3865 #
3866 set main_status [::status_bar::new .status]
3867 pack .status -anchor w -side bottom -fill x
3868 $main_status show [mc "Initializing..."]
3869
3870 # -- Load geometry
3871 #
3872 proc on_ttk_pane_mapped {w pane pos} {
3873 bind $w <Map> {}
3874 after 0 [list after idle [list $w sashpos $pane $pos]]
3875 }
3876 proc on_tk_pane_mapped {w pane x y} {
3877 bind $w <Map> {}
3878 after 0 [list after idle [list $w sash place $pane $x $y]]
3879 }
3880 proc on_application_mapped {} {
3881 global repo_config use_ttk
3882 bind . <Map> {}
3883 set gm $repo_config(gui.geometry)
3884 if {$use_ttk} {
3885 bind .vpane <Map> \
3886 [list on_ttk_pane_mapped %W 0 [lindex $gm 1]]
3887 bind .vpane.files <Map> \
3888 [list on_ttk_pane_mapped %W 0 [lindex $gm 2]]
3889 } else {
3890 bind .vpane <Map> \
3891 [list on_tk_pane_mapped %W 0 \
3892 [lindex $gm 1] \
3893 [lindex [.vpane sash coord 0] 1]]
3894 bind .vpane.files <Map> \
3895 [list on_tk_pane_mapped %W 0 \
3896 [lindex [.vpane.files sash coord 0] 0] \
3897 [lindex $gm 2]]
3898 }
3899 wm geometry . [lindex $gm 0]
3900 }
3901 if {[info exists repo_config(gui.geometry)]} {
3902 bind . <Map> [list on_application_mapped]
3903 wm geometry . [lindex $repo_config(gui.geometry) 0]
3904 }
3905
3906 # -- Load window state
3907 #
3908 if {[info exists repo_config(gui.wmstate)]} {
3909 catch {wm state . $repo_config(gui.wmstate)}
3910 }
3911
3912 # -- Key Bindings
3913 #
3914 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
3915 bind $ui_comm <$M1B-Key-t> {do_add_selection;break}
3916 bind $ui_comm <$M1B-Key-T> {do_add_selection;break}
3917 bind $ui_comm <$M1B-Key-u> {do_unstage_selection;break}
3918 bind $ui_comm <$M1B-Key-U> {do_unstage_selection;break}
3919 bind $ui_comm <$M1B-Key-j> {do_revert_selection;break}
3920 bind $ui_comm <$M1B-Key-J> {do_revert_selection;break}
3921 bind $ui_comm <$M1B-Key-i> {do_add_all;break}
3922 bind $ui_comm <$M1B-Key-I> {do_add_all;break}
3923 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
3924 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
3925 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
3926 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
3927 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
3928 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
3929 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3930 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3931 bind $ui_comm <$M1B-Key-minus> {show_less_context;break}
3932 bind $ui_comm <$M1B-Key-KP_Subtract> {show_less_context;break}
3933 bind $ui_comm <$M1B-Key-equal> {show_more_context;break}
3934 bind $ui_comm <$M1B-Key-plus> {show_more_context;break}
3935 bind $ui_comm <$M1B-Key-KP_Add> {show_more_context;break}
3936 bind $ui_comm <$M1B-Key-BackSpace> {event generate %W <Meta-Delete>;break}
3937 bind $ui_comm <$M1B-Key-Delete> {event generate %W <Meta-d>;break}
3938
3939 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
3940 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
3941 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
3942 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
3943 bind $ui_diff <$M1B-Key-v> {break}
3944 bind $ui_diff <$M1B-Key-V> {break}
3945 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3946 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3947 bind $ui_diff <$M1B-Key-j> {do_revert_selection;break}
3948 bind $ui_diff <$M1B-Key-J> {do_revert_selection;break}
3949 bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break}
3950 bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break}
3951 bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break}
3952 bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break}
3953 bind $ui_diff <Key-k> {catch {%W yview scroll -1 units};break}
3954 bind $ui_diff <Key-j> {catch {%W yview scroll 1 units};break}
3955 bind $ui_diff <Key-h> {catch {%W xview scroll -1 units};break}
3956 bind $ui_diff <Key-l> {catch {%W xview scroll 1 units};break}
3957 bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
3958 bind $ui_diff <Control-Key-f> {catch {%W yview scroll 1 pages};break}
3959 bind $ui_diff <Button-1> {focus %W}
3960
3961 if {[is_enabled branch]} {
3962 bind . <$M1B-Key-n> branch_create::dialog
3963 bind . <$M1B-Key-N> branch_create::dialog
3964 bind . <$M1B-Key-o> branch_checkout::dialog
3965 bind . <$M1B-Key-O> branch_checkout::dialog
3966 bind . <$M1B-Key-m> merge::dialog
3967 bind . <$M1B-Key-M> merge::dialog
3968 }
3969 if {[is_enabled transport]} {
3970 bind . <$M1B-Key-p> do_push_anywhere
3971 bind . <$M1B-Key-P> do_push_anywhere
3972 }
3973
3974 bind . <Key-F5> ui_do_rescan
3975 bind . <$M1B-Key-r> ui_do_rescan
3976 bind . <$M1B-Key-R> ui_do_rescan
3977 bind . <$M1B-Key-s> do_signoff
3978 bind . <$M1B-Key-S> do_signoff
3979 bind . <$M1B-Key-t> { toggle_or_diff toggle %W }
3980 bind . <$M1B-Key-T> { toggle_or_diff toggle %W }
3981 bind . <$M1B-Key-u> { toggle_or_diff toggle %W }
3982 bind . <$M1B-Key-U> { toggle_or_diff toggle %W }
3983 bind . <$M1B-Key-j> do_revert_selection
3984 bind . <$M1B-Key-J> do_revert_selection
3985 bind . <$M1B-Key-i> do_add_all
3986 bind . <$M1B-Key-I> do_add_all
3987 bind . <$M1B-Key-e> toggle_commit_type
3988 bind . <$M1B-Key-E> toggle_commit_type
3989 bind . <$M1B-Key-minus> {show_less_context;break}
3990 bind . <$M1B-Key-KP_Subtract> {show_less_context;break}
3991 bind . <$M1B-Key-equal> {show_more_context;break}
3992 bind . <$M1B-Key-plus> {show_more_context;break}
3993 bind . <$M1B-Key-KP_Add> {show_more_context;break}
3994 bind . <$M1B-Key-Return> do_commit
3995 bind . <$M1B-Key-KP_Enter> do_commit
3996 foreach i [list $ui_index $ui_workdir] {
3997 bind $i <Button-1> { toggle_or_diff click %W %x %y; break }
3998 bind $i <$M1B-Button-1> { add_one_to_selection %W %x %y; break }
3999 bind $i <Shift-Button-1> { add_range_to_selection %W %x %y; break }
4000 bind $i <Key-Up> { toggle_or_diff up %W; break }
4001 bind $i <Key-Down> { toggle_or_diff down %W; break }
4002 }
4003 unset i
4004
4005 bind . <Alt-Key-1> {focus_widget $::ui_workdir}
4006 bind . <Alt-Key-2> {focus_widget $::ui_index}
4007 bind . <Alt-Key-3> {focus $::ui_diff}
4008 bind . <Alt-Key-4> {focus $::ui_comm}
4009
4010 set file_lists_last_clicked($ui_index) {}
4011 set file_lists_last_clicked($ui_workdir) {}
4012
4013 set file_lists($ui_index) [list]
4014 set file_lists($ui_workdir) [list]
4015
4016 wm title . "[appname] ([reponame]) [file normalize $_gitworktree]"
4017 focus -force $ui_comm
4018
4019 # -- Warn the user about environmental problems. Cygwin's Tcl
4020 # does *not* pass its env array onto any processes it spawns.
4021 # This means that git processes get none of our environment.
4022 #
4023 if {[is_Cygwin]} {
4024 set ignored_env 0
4025 set suggest_user {}
4026 set msg [mc "Possible environment issues exist.
4027
4028 The following environment variables are probably
4029 going to be ignored by any Git subprocess run
4030 by %s:
4031
4032 " [appname]]
4033 foreach name [array names env] {
4034 switch -regexp -- $name {
4035 {^GIT_INDEX_FILE$} -
4036 {^GIT_OBJECT_DIRECTORY$} -
4037 {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
4038 {^GIT_DIFF_OPTS$} -
4039 {^GIT_EXTERNAL_DIFF$} -
4040 {^GIT_PAGER$} -
4041 {^GIT_TRACE$} -
4042 {^GIT_CONFIG$} -
4043 {^GIT_(AUTHOR|COMMITTER)_DATE$} {
4044 append msg " - $name\n"
4045 incr ignored_env
4046 }
4047 {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
4048 append msg " - $name\n"
4049 incr ignored_env
4050 set suggest_user $name
4051 }
4052 }
4053 }
4054 if {$ignored_env > 0} {
4055 append msg [mc "
4056 This is due to a known issue with the
4057 Tcl binary distributed by Cygwin."]
4058
4059 if {$suggest_user ne {}} {
4060 append msg [mc "
4061
4062 A good replacement for %s
4063 is placing values for the user.name and
4064 user.email settings into your personal
4065 ~/.gitconfig file.
4066 " $suggest_user]
4067 }
4068 warn_popup $msg
4069 }
4070 unset ignored_env msg suggest_user name
4071 }
4072
4073 # -- Only initialize complex UI if we are going to stay running.
4074 #
4075 if {[is_enabled transport]} {
4076 load_all_remotes
4077
4078 set n [.mbar.remote index end]
4079 populate_remotes_menu
4080 set n [expr {[.mbar.remote index end] - $n}]
4081 if {$n > 0} {
4082 if {[.mbar.remote type 0] eq "tearoff"} { incr n }
4083 .mbar.remote insert $n separator
4084 }
4085 unset n
4086 }
4087
4088 if {[winfo exists $ui_comm]} {
4089 set GITGUI_BCK_exists [load_message GITGUI_BCK utf-8]
4090
4091 # -- If both our backup and message files exist use the
4092 # newer of the two files to initialize the buffer.
4093 #
4094 if {$GITGUI_BCK_exists} {
4095 set m [gitdir GITGUI_MSG]
4096 if {[file isfile $m]} {
4097 if {[file mtime [gitdir GITGUI_BCK]] > [file mtime $m]} {
4098 catch {file delete [gitdir GITGUI_MSG]}
4099 } else {
4100 $ui_comm delete 0.0 end
4101 $ui_comm edit reset
4102 $ui_comm edit modified false
4103 catch {file delete [gitdir GITGUI_BCK]}
4104 set GITGUI_BCK_exists 0
4105 }
4106 }
4107 unset m
4108 }
4109
4110 proc backup_commit_buffer {} {
4111 global ui_comm GITGUI_BCK_exists
4112
4113 set m [$ui_comm edit modified]
4114 if {$m || $GITGUI_BCK_exists} {
4115 set msg [string trim [$ui_comm get 0.0 end]]
4116 regsub -all -line {[ \r\t]+$} $msg {} msg
4117
4118 if {$msg eq {}} {
4119 if {$GITGUI_BCK_exists} {
4120 catch {file delete [gitdir GITGUI_BCK]}
4121 set GITGUI_BCK_exists 0
4122 }
4123 } elseif {$m} {
4124 catch {
4125 set fd [open [gitdir GITGUI_BCK] w]
4126 fconfigure $fd -encoding utf-8
4127 puts -nonewline $fd $msg
4128 close $fd
4129 set GITGUI_BCK_exists 1
4130 }
4131 }
4132
4133 $ui_comm edit modified false
4134 }
4135
4136 set ::GITGUI_BCK_i [after 2000 backup_commit_buffer]
4137 }
4138
4139 backup_commit_buffer
4140
4141 # -- If the user has aspell available we can drive it
4142 # in pipe mode to spellcheck the commit message.
4143 #
4144 set spell_cmd [list |]
4145 set spell_dict [get_config gui.spellingdictionary]
4146 lappend spell_cmd aspell
4147 if {$spell_dict ne {}} {
4148 lappend spell_cmd --master=$spell_dict
4149 }
4150 lappend spell_cmd --mode=none
4151 lappend spell_cmd --encoding=utf-8
4152 lappend spell_cmd pipe
4153 if {$spell_dict eq {none}
4154 || [catch {set spell_fd [open $spell_cmd r+]} spell_err]} {
4155 bind_button3 $ui_comm [list tk_popup $ui_comm_ctxm %X %Y]
4156 } else {
4157 set ui_comm_spell [spellcheck::init \
4158 $spell_fd \
4159 $ui_comm \
4160 $ui_comm_ctxm \
4161 ]
4162 }
4163 unset -nocomplain spell_cmd spell_fd spell_err spell_dict
4164 }
4165
4166 lock_index begin-read
4167 if {![winfo ismapped .]} {
4168 wm deiconify .
4169 }
4170 after 1 {
4171 if {[is_enabled initialamend]} {
4172 force_amend
4173 } else {
4174 do_rescan
4175 }
4176
4177 if {[is_enabled nocommitmsg]} {
4178 $ui_comm configure -state disabled -background gray
4179 }
4180 }
4181 if {[is_enabled multicommit] && ![is_config_false gui.gcwarning]} {
4182 after 1000 hint_gc
4183 }
4184 if {[is_enabled retcode]} {
4185 bind . <Destroy> {+terminate_me %W}
4186 }
4187 if {$picked && [is_config_true gui.autoexplore]} {
4188 do_explore
4189 }
4190
4191 # Clear "Initializing..." status
4192 after 500 {$main_status show ""}
4193
4194 # Local variables:
4195 # mode: tcl
4196 # indent-tabs-mode: t
4197 # tab-width: 4
4198 # End: