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