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