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