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