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