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" -- "$@"
11 set appvers {@@GITGUI_VERSION@@}
12 set copyright {
13 Copyright © 2006, 2007 Shawn Pearce, et. al.
15 This program is free software; you can redistribute it and/or modify
16 it under the terms of the GNU General Public License as published by
17 the Free Software Foundation; either version 2 of the License, or
18 (at your option) any later version.
20 This program is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
25 You should have received a copy of the GNU General Public License
26 along with this program; if not, write to the Free Software
27 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA}
29 ######################################################################
30 ##
31 ## Tcl/Tk sanity check
33 if {[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 }
45 ######################################################################
46 ##
47 ## enable verbose loading?
49 if {![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 }
63 ######################################################################
64 ##
65 ## configure our library
67 set oguilib {@@GITGUI_LIBDIR@@}
68 set oguirel {@@GITGUI_RELATIVE@@}
69 if {$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 }
76 set idx [file join $oguilib tclIndex]
77 if {[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 }
86 if {[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 }
96 close $fd
98 if {$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 }
109 unset -nocomplain oguirel idx fd
111 ######################################################################
112 ##
113 ## read only globals
115 set _appname [lindex [file split $argv0] end]
116 set _gitdir {}
117 set _gitexec {}
118 set _reponame {}
119 set _iscygwin {}
120 set _search_path {}
122 proc appname {} {
123 global _appname
124 return $_appname
125 }
127 proc gitdir {args} {
128 global _gitdir
129 if {$args eq {}} {
130 return $_gitdir
131 }
132 return [eval [list file join $_gitdir] $args]
133 }
135 proc 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 }
156 proc reponame {} {
157 global _reponame
158 return $_reponame
159 }
161 proc is_MacOSX {} {
162 global tcl_platform tk_library
163 if {[tk windowingsystem] eq {aqua}} {
164 return 1
165 }
166 return 0
167 }
169 proc is_Windows {} {
170 global tcl_platform
171 if {$tcl_platform(platform) eq {windows}} {
172 return 1
173 }
174 return 0
175 }
177 proc 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 }
193 proc is_enabled {option} {
194 global enabled_options
195 if {[catch {set on $enabled_options($option)}]} {return 0}
196 return $on
197 }
199 proc enable_option {option} {
200 global enabled_options
201 set enabled_options($option) 1
202 }
204 proc disable_option {option} {
205 global enabled_options
206 set enabled_options($option) 0
207 }
209 ######################################################################
210 ##
211 ## config
213 proc is_many_config {name} {
214 switch -glob -- $name {
215 remote.*.fetch -
216 remote.*.push
217 {return 1}
218 *
219 {return 0}
220 }
221 }
223 proc 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 }
234 proc get_config {name} {
235 global repo_config
236 if {[catch {set v $repo_config($name)}]} {
237 return {}
238 } else {
239 return $v
240 }
241 }
243 proc load_config {include_global} {
244 global repo_config global_config default_config
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 }
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 }
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 }
288 ######################################################################
289 ##
290 ## handy utils
292 proc _git_cmd {name} {
293 global _git_cmd_path
295 if {[catch {set v $_git_cmd_path($name)}]} {
296 switch -- $name {
297 version -
298 --version -
299 --exec-path { return [list $::_git $name] }
300 }
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 }
329 proc _which {what} {
330 global env _search_exe _search_path
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 }
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 }
358 proc git {args} {
359 set opt [list exec]
361 while {1} {
362 switch -- [lindex $args 0] {
363 --nice {
364 global _nice
365 if {$_nice ne {}} {
366 lappend opt $_nice
367 }
368 }
370 default {
371 break
372 }
374 }
376 set args [lrange $args 1 end]
377 }
379 set cmdp [_git_cmd [lindex $args 0]]
380 set args [lrange $args 1 end]
382 return [eval $opt $cmdp $args]
383 }
385 proc git_read {args} {
386 set opt [list |]
388 while {1} {
389 switch -- [lindex $args 0] {
390 --nice {
391 global _nice
392 if {$_nice ne {}} {
393 lappend opt $_nice
394 }
395 }
397 --stderr {
398 lappend args 2>@1
399 }
401 default {
402 break
403 }
405 }
407 set args [lrange $args 1 end]
408 }
410 set cmdp [_git_cmd [lindex $args 0]]
411 set args [lrange $args 1 end]
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 }
437 proc git_write {args} {
438 set opt [list |]
440 while {1} {
441 switch -- [lindex $args 0] {
442 --nice {
443 global _nice
444 if {$_nice ne {}} {
445 lappend opt $_nice
446 }
447 }
449 default {
450 break
451 }
453 }
455 set args [lrange $args 1 end]
456 }
458 set cmdp [_git_cmd [lindex $args 0]]
459 set args [lrange $args 1 end]
461 return [open [concat $opt $cmdp $args] w]
462 }
464 proc load_current_branch {} {
465 global current_branch is_detached
467 set fd [open [gitdir HEAD] r]
468 if {[gets $fd ref] < 1} {
469 set ref {}
470 }
471 close $fd
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 }
489 auto_load tk_optionMenu
490 rename tk_optionMenu real__tkOptionMenu
491 proc 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 }
498 ######################################################################
499 ##
500 ## find git
502 set _git [_which git]
503 if {$_git eq {}} {
504 catch {wm withdraw .}
505 error_popup "Cannot find git in PATH."
506 exit 1
507 }
508 set _nice [_which nice]
509 set _sh [_which sh]
511 ######################################################################
512 ##
513 ## version check
515 if {[catch {set _git_version [git --version]} err]} {
516 catch {wm withdraw .}
517 error_popup "Cannot determine Git version:
519 $err
521 [appname] requires Git 1.5.0 or later."
522 exit 1
523 }
524 if {![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 }
529 regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version
530 regsub {\.rc[0-9]+$} $_git_version {} _git_version
532 proc git-version {args} {
533 global _git_version
535 switch [llength $args] {
536 0 {
537 return $_git_version
538 }
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 }
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]
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 }
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 }
566 return [uplevel [list $type $name $parm [lindex $body end]]]
567 }
569 default {
570 error "git-version >= x"
571 }
573 }
574 }
576 if {[git-version < 1.5]} {
577 catch {wm withdraw .}
578 error_popup "[appname] requires Git 1.5.0 or later.
580 You are using [git-version]:
582 [git --version]"
583 exit 1
584 }
586 ######################################################################
587 ##
588 ## repository setup
590 if {[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 }
602 if {![file isdirectory $_gitdir] && [is_Cygwin]} {
603 catch {set _gitdir [exec cygpath --unix $_gitdir]}
604 }
605 if {![file isdirectory $_gitdir]} {
606 catch {wm withdraw .}
607 error_popup "Git directory not found:\n\n$_gitdir"
608 exit 1
609 }
610 if {[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 }
615 if {[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 }
620 set _reponame [lindex [file split \
621 [file normalize [file dirname $_gitdir]]] \
622 end]
624 ######################################################################
625 ##
626 ## global init
628 set current_diff_path {}
629 set current_diff_side {}
630 set diff_actions [list]
632 set HEAD {}
633 set PARENT {}
634 set MERGE_HEAD [list]
635 set commit_type {}
636 set empty_tree {}
637 set current_branch {}
638 set is_detached 0
639 set current_diff_path {}
640 set selected_commit_type new
642 ######################################################################
643 ##
644 ## task management
646 set rescan_active 0
647 set diff_active 0
648 set last_clicked {}
650 set disable_on_lock [list]
651 set index_lock_type none
653 proc lock_index {type} {
654 global index_lock_type disable_on_lock
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 }
669 proc unlock_index {} {
670 global index_lock_type disable_on_lock
672 set index_lock_type none
673 foreach w $disable_on_lock {
674 uplevel #0 $w normal
675 }
676 }
678 ######################################################################
679 ##
680 ## status
682 proc repository_state {ctvar hdvar mhvar} {
683 global current_branch
684 upvar $ctvar ct $hdvar hd $mhvar mh
686 set mh [list]
688 load_current_branch
689 if {[catch {set hd [git rev-parse --verify HEAD]}]} {
690 set hd {}
691 set ct initial
692 return
693 }
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 }
706 set ct normal
707 }
709 proc PARENT {} {
710 global PARENT empty_tree
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 }
722 proc 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
728 if {$rescan_active > 0 || ![lock_index read]} return
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 }
741 array unset file_states
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 }
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 }
771 proc rescan_stage2 {fd after} {
772 global rescan_active buf_rdi buf_rdf buf_rlo
774 if {$fd ne {}} {
775 read $fd
776 if {![eof $fd]} return
777 close $fd
778 }
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 }
786 set buf_rdi {}
787 set buf_rdf {}
788 set buf_rlo {}
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]
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 }
804 proc load_message {file} {
805 global ui_comm
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 }
822 proc read_diff_index {fd after} {
823 global buf_rdi
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
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 }
852 rescan_done $fd buf_rdi $after
853 }
855 proc read_diff_files {fd after} {
856 global buf_rdf
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
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 }
885 rescan_done $fd buf_rdf $after
886 }
888 proc read_ls_others {fd after} {
889 global buf_rlo
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 }
900 proc rescan_done {fd buf after} {
901 global rescan_active current_diff_path
902 global file_states repo_config
903 upvar $buf to_clear
905 if {![eof $fd]} return
906 set to_clear {}
907 close $fd
908 if {[incr rescan_active -1] > 0} return
910 prune_selection
911 unlock_index
912 display_all_files
913 if {$current_diff_path ne {}} reshow_diff
914 uplevel #0 $after
915 }
917 proc prune_selection {} {
918 global file_states selected_paths
920 foreach path [array names selected_paths] {
921 if {[catch {set still_here $file_states($path)}]} {
922 unset selected_paths($path)
923 }
924 }
925 }
927 ######################################################################
928 ##
929 ## ui helpers
931 proc mapicon {w state path} {
932 global all_icons
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 }
941 proc mapdesc {state path} {
942 global all_descs
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 }
951 proc ui_status {msg} {
952 $::main_status show $msg
953 }
955 proc ui_ready {{test {}}} {
956 $::main_status show {Ready.} $test
957 }
959 proc escape_path {path} {
960 regsub -all {\\} $path "\\\\" path
961 regsub -all "\n" $path "\\n" path
962 return $path
963 }
965 proc short_path {path} {
966 return [escape_path [lindex [file split $path] end]]
967 }
969 set next_icon_id 0
970 set null_sha1 [string repeat 0 40]
972 proc merge_state {path new_state {head_info {}} {index_info {}}} {
973 global file_states next_icon_id null_sha1
975 set s0 [string index $new_state 0]
976 set s1 [string index $new_state 1]
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 }
988 if {$s0 eq {?}} {set s0 [string index $state 0]} \
989 elseif {$s0 eq {_}} {set s0 _}
991 if {$s1 eq {?}} {set s1 [string index $state 1]} \
992 elseif {$s1 eq {_}} {set s1 _}
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 }
1001 set file_states($path) [list $s0$s1 $icon \
1002 $head_info $index_info \
1003 ]
1004 return $state
1005 }
1007 proc display_file_helper {w path icon_name old_m new_m} {
1008 global file_lists
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 }
1038 proc display_file {path state} {
1039 global file_states selected_paths
1040 global ui_index ui_workdir
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]
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
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
1069 if {$new_m eq {__}} {
1070 unset file_states($path)
1071 catch {unset selected_paths($path)}
1072 }
1073 }
1075 proc display_all_files_helper {w path icon_name m} {
1076 global file_lists
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 }
1087 proc display_all_files {} {
1088 global ui_index ui_workdir
1089 global file_states file_lists
1090 global last_clicked
1092 $ui_index conf -state normal
1093 $ui_workdir conf -state normal
1095 $ui_index delete 0.0 end
1096 $ui_workdir delete 0.0 end
1097 set last_clicked {}
1099 set file_lists($ui_index) [list]
1100 set file_lists($ui_workdir) [list]
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]
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 }
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 }
1124 $ui_index conf -state disabled
1125 $ui_workdir conf -state disabled
1126 }
1128 ######################################################################
1129 ##
1130 ## icons
1132 set filemask {
1133 #define mask_width 14
1134 #define mask_height 15
1135 static 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 }
1141 image create bitmap file_plain -background white -foreground black -data {
1142 #define plain_width 14
1143 #define plain_height 15
1144 static 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
1150 image create bitmap file_mod -background white -foreground blue -data {
1151 #define mod_width 14
1152 #define mod_height 15
1153 static 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
1159 image create bitmap file_fulltick -background white -foreground "#007000" -data {
1160 #define file_fulltick_width 14
1161 #define file_fulltick_height 15
1162 static 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
1168 image create bitmap file_parttick -background white -foreground "#005050" -data {
1169 #define parttick_width 14
1170 #define parttick_height 15
1171 static 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
1177 image create bitmap file_question -background white -foreground black -data {
1178 #define file_question_width 14
1179 #define file_question_height 15
1180 static 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
1186 image create bitmap file_removed -background white -foreground red -data {
1187 #define file_removed_width 14
1188 #define file_removed_height 15
1189 static 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
1195 image create bitmap file_merge -background white -foreground blue -data {
1196 #define file_merge_width 14
1197 #define file_merge_height 15
1198 static 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
1204 set file_dir_data {
1205 #define file_width 18
1206 #define file_height 18
1207 static 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 }
1214 image create bitmap file_dir -background white -foreground blue \
1215 -data $file_dir_data -maskdata $file_dir_data
1216 unset file_dir_data
1218 set file_uplevel_data {
1219 #define up_width 15
1220 #define up_height 15
1221 static 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 }
1226 image create bitmap file_uplevel -background white -foreground red \
1227 -data $file_uplevel_data -maskdata $file_uplevel_data
1228 unset file_uplevel_data
1230 set ui_index .vpane.files.index.list
1231 set ui_workdir .vpane.files.workdir.list
1233 set all_icons(_$ui_index) file_plain
1234 set all_icons(A$ui_index) file_fulltick
1235 set all_icons(M$ui_index) file_fulltick
1236 set all_icons(D$ui_index) file_removed
1237 set all_icons(U$ui_index) file_merge
1239 set all_icons(_$ui_workdir) file_plain
1240 set all_icons(M$ui_workdir) file_mod
1241 set all_icons(D$ui_workdir) file_question
1242 set all_icons(U$ui_workdir) file_merge
1243 set all_icons(O$ui_workdir) file_plain
1245 set max_status_desc 0
1246 foreach i {
1247 {__ "Unmodified"}
1249 {_M "Modified, not staged"}
1250 {M_ "Staged for commit"}
1251 {MM "Portions staged for commit"}
1252 {MD "Staged for commit, missing"}
1254 {_O "Untracked, not staged"}
1255 {A_ "Staged for commit"}
1256 {AM "Portions staged for commit"}
1257 {AD "Staged for commit, missing"}
1259 {_D "Missing"}
1260 {D_ "Staged for removal"}
1261 {DO "Staged for removal, still present"}
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 }
1273 unset i
1275 ######################################################################
1276 ##
1277 ## util
1279 proc bind_button3 {w cmd} {
1280 bind $w <Any-Button-3> $cmd
1281 if {[is_MacOSX]} {
1282 bind $w <Control-Button-1> $cmd
1283 }
1284 }
1286 proc scrollbar2many {list mode args} {
1287 foreach w $list {eval $w $mode $args}
1288 }
1290 proc many2scrollbar {list mode sb top bottom} {
1291 $sb set $top $bottom
1292 foreach w $list {$w $mode moveto $top}
1293 }
1295 proc 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 }
1303 ######################################################################
1304 ##
1305 ## ui commands
1307 set starting_gitk_msg {Starting gitk... please wait...}
1309 proc 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 }
1326 set is_quitting 0
1328 proc do_quit {} {
1329 global ui_comm is_quitting repo_config commit_type
1331 if {$is_quitting} return
1332 set is_quitting 1
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 }
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 }
1366 destroy .
1367 }
1369 proc do_rescan {} {
1370 rescan ui_ready
1371 }
1373 proc do_commit {} {
1374 commit_tree
1375 }
1377 proc 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
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 }
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
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 }
1417 proc add_one_to_selection {w x y} {
1418 global file_lists last_clicked selected_paths
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 }
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 }
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 }
1446 proc add_range_to_selection {w x y} {
1447 global file_lists last_clicked selected_paths
1449 if {[lindex $last_clicked 0] ne $w} {
1450 toggle_or_diff $w $x $y
1451 return
1452 }
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 }
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 }
1472 ######################################################################
1473 ##
1474 ## config defaults
1476 set cursor_ptr arrow
1477 font create font_diff -family Courier -size 10
1478 font create font_ui
1479 catch {
1480 label .dummy
1481 eval font configure font_ui [font actual [.dummy cget -font]]
1482 destroy .dummy
1483 }
1485 font create font_uiitalic
1486 font create font_uibold
1487 font create font_diffbold
1488 font create font_diffitalic
1490 foreach class {Button Checkbutton Entry Label
1491 Labelframe Listbox Menu Message
1492 Radiobutton Spinbox Text} {
1493 option add *$class.font font_ui
1494 }
1495 unset class
1497 if {[is_Windows] || [is_MacOSX]} {
1498 option add *Menu.tearOff 0
1499 }
1501 if {[is_MacOSX]} {
1502 set M1B M1
1503 set M1T Cmd
1504 } else {
1505 set M1B Control
1506 set M1T Ctrl
1507 }
1509 proc apply_config {} {
1510 global repo_config font_descs
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 }
1531 set default_config(merge.diffstat) true
1532 set default_config(merge.summary) false
1533 set default_config(merge.verbosity) 2
1534 set default_config(user.name) {}
1535 set default_config(user.email) {}
1537 set default_config(gui.matchtrackingbranch) false
1538 set default_config(gui.pruneduringfetch) false
1539 set default_config(gui.trustmtime) false
1540 set default_config(gui.diffcontext) 5
1541 set default_config(gui.newbranchtemplate) {}
1542 set default_config(gui.fontui) [font configure font_ui]
1543 set default_config(gui.fontdiff) [font configure font_diff]
1544 set font_descs {
1545 {fontui font_ui {Main Font}}
1546 {fontdiff font_diff {Diff/Console Font}}
1547 }
1548 load_config 0
1549 apply_config
1551 ######################################################################
1552 ##
1553 ## feature option selection
1555 if {[regexp {^git-(.+)$} [appname] _junk subcommand]} {
1556 unset _junk
1557 } else {
1558 set subcommand gui
1559 }
1560 if {$subcommand eq {gui.sh}} {
1561 set subcommand gui
1562 }
1563 if {$subcommand eq {gui} && [llength $argv] > 0} {
1564 set subcommand [lindex $argv 0]
1565 set argv [lrange $argv 1 end]
1566 }
1568 enable_option multicommit
1569 enable_option branch
1570 enable_option transport
1572 switch -- $subcommand {
1573 browser -
1574 blame {
1575 disable_option multicommit
1576 disable_option branch
1577 disable_option transport
1578 }
1579 citool {
1580 enable_option singlecommit
1582 disable_option multicommit
1583 disable_option branch
1584 disable_option transport
1585 }
1586 }
1588 ######################################################################
1589 ##
1590 ## ui construction
1592 set ui_comm {}
1594 # -- Menu Bar
1595 #
1596 menu .mbar -tearoff 0
1597 .mbar add cascade -label Repository -menu .mbar.repository
1598 .mbar add cascade -label Edit -menu .mbar.edit
1599 if {[is_enabled branch]} {
1600 .mbar add cascade -label Branch -menu .mbar.branch
1601 }
1602 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1603 .mbar add cascade -label Commit -menu .mbar.commit
1604 }
1605 if {[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
1612 # -- Repository Menu
1613 #
1614 menu .mbar.repository
1616 .mbar.repository add command \
1617 -label {Browse Current Branch} \
1618 -command {browser::new $current_branch}
1619 trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#"
1620 .mbar.repository add separator
1622 .mbar.repository add command \
1623 -label {Visualize Current Branch} \
1624 -command {do_gitk $current_branch}
1625 trace 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
1631 if {[is_enabled multicommit]} {
1632 .mbar.repository add command -label {Database Statistics} \
1633 -command do_stats
1635 .mbar.repository add command -label {Compress Database} \
1636 -command do_gc
1638 .mbar.repository add command -label {Verify Database} \
1639 -command do_fsck_objects
1641 .mbar.repository add separator
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 }
1658 .mbar.repository add command -label Quit \
1659 -command do_quit \
1660 -accelerator $M1T-Q
1662 # -- Edit Menu
1663 #
1664 menu .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
1689 # -- Branch Menu
1690 #
1691 if {[is_enabled branch]} {
1692 menu .mbar.branch
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]
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]
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]
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]
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 }
1722 # -- Commit Menu
1723 #
1724 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1725 menu .mbar.commit
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]
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]
1743 .mbar.commit add separator
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]
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]
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]
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]
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]
1772 .mbar.commit add separator
1774 .mbar.commit add command -label {Sign Off} \
1775 -command do_signoff \
1776 -accelerator $M1T-S
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 }
1785 # -- Merge Menu
1786 #
1787 if {[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]
1798 }
1800 # -- Transport Menu
1801 #
1802 if {[is_enabled transport]} {
1803 menu .mbar.fetch
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 }
1813 if {[is_MacOSX]} {
1814 # -- Apple Menu (Mac OS X only)
1815 #
1816 .mbar add cascade -label Apple -menu .mbar.apple
1817 menu .mbar.apple
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
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 }
1858 # -- Help Menu
1859 #
1860 .mbar add cascade -label Help -menu .mbar.help
1861 menu .mbar.help
1863 if {![is_MacOSX]} {
1864 .mbar.help add command -label "About [appname]" \
1865 -command do_about
1866 }
1868 set browser {}
1869 catch {set browser $repo_config(instaweb.browser)}
1870 set doc_path [file dirname [gitexec]]
1871 set doc_path [file join $doc_path Documentation index.html]
1873 if {[is_Cygwin]} {
1874 set doc_path [exec cygpath --mixed $doc_path]
1875 }
1877 if {$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 }
1894 if {[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 }
1900 if {$browser ne {}} {
1901 .mbar.help add command -label {Online Documentation} \
1902 -command [list exec $browser $doc_url &]
1903 }
1904 unset browser doc_path doc_url
1906 # -- Standard bindings
1907 #
1908 wm protocol . WM_DELETE_WINDOW do_quit
1909 bind all <$M1B-Key-q> do_quit
1910 bind all <$M1B-Key-Q> do_quit
1911 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
1912 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
1914 set subcommand_args {}
1915 proc usage {} {
1916 puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
1917 exit 1
1918 }
1920 # -- Not a normal commit type invocation? Do that instead!
1921 #
1922 switch -- $subcommand {
1923 browser {
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 }
1944 blame {
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
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 }
1984 if {$path eq {}} usage
1985 blame::new $head $path
1986 return
1987 }
1988 citool -
1989 gui {
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 }
2000 default {
2001 puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
2002 exit 1
2003 }
2004 }
2006 # -- Branch Control
2007 #
2008 frame .branch \
2009 -borderwidth 1 \
2010 -relief sunken
2011 label .branch.l1 \
2012 -text {Current Branch:} \
2013 -anchor w \
2014 -justify left
2015 label .branch.cb \
2016 -textvariable current_branch \
2017 -anchor w \
2018 -justify left
2019 pack .branch.l1 -side left
2020 pack .branch.cb -side left -fill x
2021 pack .branch -side top -fill x
2023 # -- Main Window Layout
2024 #
2025 panedwindow .vpane -orient vertical
2026 panedwindow .vpane.files -orient horizontal
2027 .vpane add .vpane.files -sticky nsew -height 100 -width 200
2028 pack .vpane -anchor n -side top -fill both -expand 1
2030 # -- Index File List
2031 #
2032 frame .vpane.files.index -height 100 -width 200
2033 label .vpane.files.index.title -text {Staged Changes (Will Be Committed)} \
2034 -background lightgreen
2035 text $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
2042 scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
2043 scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
2044 pack .vpane.files.index.title -side top -fill x
2045 pack .vpane.files.index.sx -side bottom -fill x
2046 pack .vpane.files.index.sy -side right -fill y
2047 pack $ui_index -side left -fill both -expand 1
2048 .vpane.files add .vpane.files.index -sticky nsew
2050 # -- Working Directory File List
2051 #
2052 frame .vpane.files.workdir -height 100 -width 200
2053 label .vpane.files.workdir.title -text {Unstaged Changes (Will Not Be Committed)} \
2054 -background lightsalmon
2055 text $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
2062 scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
2063 scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
2064 pack .vpane.files.workdir.title -side top -fill x
2065 pack .vpane.files.workdir.sx -side bottom -fill x
2066 pack .vpane.files.workdir.sy -side right -fill y
2067 pack $ui_workdir -side left -fill both -expand 1
2068 .vpane.files add .vpane.files.workdir -sticky nsew
2070 foreach i [list $ui_index $ui_workdir] {
2071 $i tag conf in_diff -background lightgray
2072 $i tag conf in_sel -background lightgray
2073 }
2074 unset i
2076 # -- Diff and Commit Area
2077 #
2078 frame .vpane.lower -height 300 -width 400
2079 frame .vpane.lower.commarea
2080 frame .vpane.lower.diff -relief sunken -borderwidth 1
2081 pack .vpane.lower.commarea -side top -fill x
2082 pack .vpane.lower.diff -side bottom -fill both -expand 1
2083 .vpane add .vpane.lower -sticky nsew
2085 # -- Commit Area Buttons
2086 #
2087 frame .vpane.lower.commarea.buttons
2088 label .vpane.lower.commarea.buttons.l -text {} \
2089 -anchor w \
2090 -justify left
2091 pack .vpane.lower.commarea.buttons.l -side top -fill x
2092 pack .vpane.lower.commarea.buttons -side left -fill y
2094 button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
2095 -command do_rescan
2096 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
2097 lappend disable_on_lock \
2098 {.vpane.lower.commarea.buttons.rescan conf -state}
2100 button .vpane.lower.commarea.buttons.incall -text {Add Existing} \
2101 -command do_add_all
2102 pack .vpane.lower.commarea.buttons.incall -side top -fill x
2103 lappend disable_on_lock \
2104 {.vpane.lower.commarea.buttons.incall conf -state}
2106 button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
2107 -command do_signoff
2108 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
2110 button .vpane.lower.commarea.buttons.commit -text {Commit} \
2111 -command do_commit
2112 pack .vpane.lower.commarea.buttons.commit -side top -fill x
2113 lappend disable_on_lock \
2114 {.vpane.lower.commarea.buttons.commit conf -state}
2116 button .vpane.lower.commarea.buttons.push -text {Push} \
2117 -command do_push_anywhere
2118 pack .vpane.lower.commarea.buttons.push -side top -fill x
2120 # -- Commit Message Buffer
2121 #
2122 frame .vpane.lower.commarea.buffer
2123 frame .vpane.lower.commarea.buffer.header
2124 set ui_comm .vpane.lower.commarea.buffer.t
2125 set ui_coml .vpane.lower.commarea.buffer.header.l
2126 radiobutton .vpane.lower.commarea.buffer.header.new \
2127 -text {New Commit} \
2128 -command do_select_commit_type \
2129 -variable selected_commit_type \
2130 -value new
2131 lappend disable_on_lock \
2132 [list .vpane.lower.commarea.buffer.header.new conf -state]
2133 radiobutton .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
2138 lappend disable_on_lock \
2139 [list .vpane.lower.commarea.buffer.header.amend conf -state]
2140 label $ui_coml \
2141 -anchor w \
2142 -justify left
2143 proc 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 }
2155 trace add variable commit_type write trace_commit_type
2156 pack $ui_coml -side left -fill x
2157 pack .vpane.lower.commarea.buffer.header.amend -side right
2158 pack .vpane.lower.commarea.buffer.header.new -side right
2160 text $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}
2168 scrollbar .vpane.lower.commarea.buffer.sby \
2169 -command [list $ui_comm yview]
2170 pack .vpane.lower.commarea.buffer.header -side top -fill x
2171 pack .vpane.lower.commarea.buffer.sby -side right -fill y
2172 pack $ui_comm -side left -fill y
2173 pack .vpane.lower.commarea.buffer -side left -fill y
2175 # -- Commit Message Buffer Context Menu
2176 #
2177 set ctxm .vpane.lower.commarea.buffer.ctxm
2178 menu $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
2206 bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
2208 # -- Diff Header
2209 #
2210 proc 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 }
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 }
2232 trace add variable current_diff_path write trace_current_diff_path
2234 frame .vpane.lower.diff.header -background gold
2235 label .vpane.lower.diff.header.status \
2236 -background gold \
2237 -width $max_status_desc \
2238 -anchor w \
2239 -justify left
2240 label .vpane.lower.diff.header.file \
2241 -background gold \
2242 -anchor w \
2243 -justify left
2244 label .vpane.lower.diff.header.path \
2245 -background gold \
2246 -anchor w \
2247 -justify left
2248 pack .vpane.lower.diff.header.status -side left
2249 pack .vpane.lower.diff.header.file -side left
2250 pack .vpane.lower.diff.header.path -fill x
2251 set ctxm .vpane.lower.diff.header.ctxm
2252 menu $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 }
2262 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2263 bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
2265 # -- Diff Body
2266 #
2267 frame .vpane.lower.diff.body
2268 set ui_diff .vpane.lower.diff.body.t
2269 text $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
2275 scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
2276 -command [list $ui_diff xview]
2277 scrollbar .vpane.lower.diff.body.sby -orient vertical \
2278 -command [list $ui_diff yview]
2279 pack .vpane.lower.diff.body.sbx -side bottom -fill x
2280 pack .vpane.lower.diff.body.sby -side right -fill y
2281 pack $ui_diff -side left -fill both -expand 1
2282 pack .vpane.lower.diff.header -side top -fill x
2283 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
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
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
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
2315 $ui_diff tag raise sel
2317 # -- Diff Body Context Menu
2318 #
2319 set ctxm .vpane.lower.diff.body.ctxm
2320 menu $ctxm -tearoff 0
2321 $ctxm add command \
2322 -label {Refresh} \
2323 -command reshow_diff
2324 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2325 $ctxm add command \
2326 -label {Copy} \
2327 -command {tk_textCopy $ui_diff}
2328 lappend 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}
2332 lappend 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 }
2340 lappend 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}
2345 set ui_diff_applyhunk [$ctxm index last]
2346 lappend 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}
2351 lappend 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}
2355 lappend 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 }}
2363 lappend 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 }}
2370 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2371 $ctxm add separator
2372 $ctxm add command -label {Options...} \
2373 -command do_options
2374 bind_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 "
2384 unset ui_diff_applyhunk
2386 # -- Status Bar
2387 #
2388 set main_status [::status_bar::new .status]
2389 pack .status -anchor w -side bottom -fill x
2390 $main_status show {Initializing...}
2392 # -- Load geometry
2393 #
2394 catch {
2395 set gm $repo_config(gui.geometry)
2396 wm 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]
2403 unset gm
2404 }
2406 # -- Key Bindings
2407 #
2408 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2409 bind $ui_comm <$M1B-Key-i> {do_add_all;break}
2410 bind $ui_comm <$M1B-Key-I> {do_add_all;break}
2411 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2412 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2413 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2414 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2415 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2416 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2417 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2418 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2420 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2421 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2422 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2423 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2424 bind $ui_diff <$M1B-Key-v> {break}
2425 bind $ui_diff <$M1B-Key-V> {break}
2426 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2427 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2428 bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break}
2429 bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break}
2430 bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break}
2431 bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break}
2432 bind $ui_diff <Key-k> {catch {%W yview scroll -1 units};break}
2433 bind $ui_diff <Key-j> {catch {%W yview scroll 1 units};break}
2434 bind $ui_diff <Key-h> {catch {%W xview scroll -1 units};break}
2435 bind $ui_diff <Key-l> {catch {%W xview scroll 1 units};break}
2436 bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
2437 bind $ui_diff <Control-Key-f> {catch {%W yview scroll 1 pages};break}
2438 bind $ui_diff <Button-1> {focus %W}
2440 if {[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 }
2446 if {[is_enabled transport]} {
2447 bind . <$M1B-Key-p> do_push_anywhere
2448 bind . <$M1B-Key-P> do_push_anywhere
2449 }
2451 bind . <Key-F5> do_rescan
2452 bind . <$M1B-Key-r> do_rescan
2453 bind . <$M1B-Key-R> do_rescan
2454 bind . <$M1B-Key-s> do_signoff
2455 bind . <$M1B-Key-S> do_signoff
2456 bind . <$M1B-Key-i> do_add_all
2457 bind . <$M1B-Key-I> do_add_all
2458 bind . <$M1B-Key-Return> do_commit
2459 foreach 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 }
2464 unset i
2466 set file_lists($ui_index) [list]
2467 set file_lists($ui_workdir) [list]
2469 wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
2470 focus -force $ui_comm
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 #
2476 if {[is_Cygwin]} {
2477 set ignored_env 0
2478 set suggest_user {}
2479 set msg "Possible environment issues exist.
2481 The following environment variables are probably
2482 going to be ignored by any Git subprocess run
2483 by [appname]:
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 "
2510 This is due to a known issue with the
2511 Tcl binary distributed by Cygwin."
2513 if {$suggest_user ne {}} {
2514 append msg "
2516 A good replacement for $suggest_user
2517 is placing values for the user.name and
2518 user.email settings into your personal
2519 ~/.gitconfig file.
2520 "
2521 }
2522 warn_popup $msg
2523 }
2524 unset ignored_env msg suggest_user name
2525 }
2527 # -- Only initialize complex UI if we are going to stay running.
2528 #
2529 if {[is_enabled transport]} {
2530 load_all_remotes
2532 populate_fetch_menu
2533 populate_push_menu
2534 }
2536 # -- Only suggest a gc run if we are going to stay running.
2537 #
2538 if {[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.
2546 To maintain optimal performance it is strongly recommended that you compress the database when more than $object_limit loose objects exist.
2548 Compress the database now?"] eq yes} {
2549 do_gc
2550 }
2551 }
2552 unset object_limit _junk objects_current
2553 }
2555 lock_index begin-read
2556 after 1 do_rescan