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