git-gui: Font chooser to handle a large number of font families
[git.git] / git-gui.sh
blob31a36cb49f2149b211f6e78f70b8436f59a0770f
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 ######################################################################
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
45 catch {rename send {}} ; # What an evil concept...
47 ######################################################################
49 ## enable verbose loading?
51 if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
52 unset _verbose
53 rename auto_load real__auto_load
54 proc auto_load {name args} {
55 puts stderr "auto_load $name"
56 return [uplevel 1 real__auto_load $name $args]
58 rename source real__source
59 proc source {name} {
60 puts stderr "source $name"
61 uplevel 1 real__source $name
65 ######################################################################
67 ## Fake internationalization to ease backporting of changes.
69 proc mc {fmt args} {
70 set cmk [string first @@ $fmt]
71 if {$cmk > 0} {
72 set fmt [string range $fmt 0 [expr {$cmk - 1}]]
74 return [eval [list format $fmt] $args]
77 ######################################################################
79 ## read only globals
81 set _appname [lindex [file split $argv0] end]
82 set _gitdir {}
83 set _gitexec {}
84 set _reponame {}
85 set _iscygwin {}
86 set _search_path {}
88 proc appname {} {
89 global _appname
90 return $_appname
93 proc gitdir {args} {
94 global _gitdir
95 if {$args eq {}} {
96 return $_gitdir
98 return [eval [list file join $_gitdir] $args]
101 proc gitexec {args} {
102 global _gitexec
103 if {$_gitexec eq {}} {
104 if {[catch {set _gitexec [git --exec-path]} err]} {
105 error "Git not installed?\n\n$err"
107 if {[is_Cygwin]} {
108 set _gitexec [exec cygpath \
109 --windows \
110 --absolute \
111 $_gitexec]
112 } else {
113 set _gitexec [file normalize $_gitexec]
116 if {$args eq {}} {
117 return $_gitexec
119 return [eval [list file join $_gitexec] $args]
122 proc reponame {} {
123 return $::_reponame
126 proc is_MacOSX {} {
127 if {[tk windowingsystem] eq {aqua}} {
128 return 1
130 return 0
133 proc is_Windows {} {
134 if {$::tcl_platform(platform) eq {windows}} {
135 return 1
137 return 0
140 proc is_Cygwin {} {
141 global _iscygwin
142 if {$_iscygwin eq {}} {
143 if {$::tcl_platform(platform) eq {windows}} {
144 if {[catch {set p [exec cygpath --windir]} err]} {
145 set _iscygwin 0
146 } else {
147 set _iscygwin 1
149 } else {
150 set _iscygwin 0
153 return $_iscygwin
156 proc is_enabled {option} {
157 global enabled_options
158 if {[catch {set on $enabled_options($option)}]} {return 0}
159 return $on
162 proc enable_option {option} {
163 global enabled_options
164 set enabled_options($option) 1
167 proc disable_option {option} {
168 global enabled_options
169 set enabled_options($option) 0
172 ######################################################################
174 ## config
176 proc is_many_config {name} {
177 switch -glob -- $name {
178 remote.*.fetch -
179 remote.*.push
180 {return 1}
182 {return 0}
186 proc is_config_true {name} {
187 global repo_config
188 if {[catch {set v $repo_config($name)}]} {
189 return 0
190 } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
191 return 1
192 } else {
193 return 0
197 proc get_config {name} {
198 global repo_config
199 if {[catch {set v $repo_config($name)}]} {
200 return {}
201 } else {
202 return $v
206 proc load_config {include_global} {
207 global repo_config global_config default_config
209 array unset global_config
210 if {$include_global} {
211 catch {
212 set fd_rc [git_read config --global --list]
213 while {[gets $fd_rc line] >= 0} {
214 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
215 if {[is_many_config $name]} {
216 lappend global_config($name) $value
217 } else {
218 set global_config($name) $value
222 close $fd_rc
226 array unset repo_config
227 catch {
228 set fd_rc [git_read config --list]
229 while {[gets $fd_rc line] >= 0} {
230 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
231 if {[is_many_config $name]} {
232 lappend repo_config($name) $value
233 } else {
234 set repo_config($name) $value
238 close $fd_rc
241 foreach name [array names default_config] {
242 if {[catch {set v $global_config($name)}]} {
243 set global_config($name) $default_config($name)
245 if {[catch {set v $repo_config($name)}]} {
246 set repo_config($name) $default_config($name)
251 ######################################################################
253 ## handy utils
255 proc _git_cmd {name} {
256 global _git_cmd_path
258 if {[catch {set v $_git_cmd_path($name)}]} {
259 switch -- $name {
260 version -
261 --version -
262 --exec-path { return [list $::_git $name] }
265 set p [gitexec git-$name$::_search_exe]
266 if {[file exists $p]} {
267 set v [list $p]
268 } elseif {[is_Windows] && [file exists [gitexec git-$name]]} {
269 # Try to determine what sort of magic will make
270 # git-$name go and do its thing, because native
271 # Tcl on Windows doesn't know it.
273 set p [gitexec git-$name]
274 set f [open $p r]
275 set s [gets $f]
276 close $f
278 switch -glob -- [lindex $s 0] {
279 #!*sh { set i sh }
280 #!*perl { set i perl }
281 #!*python { set i python }
282 default { error "git-$name is not supported: $s" }
285 upvar #0 _$i interp
286 if {![info exists interp]} {
287 set interp [_which $i]
289 if {$interp eq {}} {
290 error "git-$name requires $i (not in PATH)"
292 set v [concat [list $interp] [lrange $s 1 end] [list $p]]
293 } else {
294 # Assume it is builtin to git somehow and we
295 # aren't actually able to see a file for it.
297 set v [list $::_git $name]
299 set _git_cmd_path($name) $v
301 return $v
304 proc _which {what} {
305 global env _search_exe _search_path
307 if {$_search_path eq {}} {
308 if {[is_Cygwin]} {
309 set _search_path [split [exec cygpath \
310 --windows \
311 --path \
312 --absolute \
313 $env(PATH)] {;}]
314 set _search_exe .exe
315 } elseif {[is_Windows]} {
316 set _search_path [split $env(PATH) {;}]
317 set _search_exe .exe
318 } else {
319 set _search_path [split $env(PATH) :]
320 set _search_exe {}
324 foreach p $_search_path {
325 set p [file join $p $what$_search_exe]
326 if {[file exists $p]} {
327 return [file normalize $p]
330 return {}
333 proc _lappend_nice {cmd_var} {
334 global _nice
335 upvar $cmd_var cmd
337 if {![info exists _nice]} {
338 set _nice [_which nice]
340 if {$_nice ne {}} {
341 lappend cmd $_nice
345 proc git {args} {
346 set opt [list exec]
348 while {1} {
349 switch -- [lindex $args 0] {
350 --nice {
351 _lappend_nice opt
354 default {
355 break
360 set args [lrange $args 1 end]
363 set cmdp [_git_cmd [lindex $args 0]]
364 set args [lrange $args 1 end]
366 return [eval $opt $cmdp $args]
369 proc _open_stdout_stderr {cmd} {
370 if {[catch {
371 set fd [open $cmd r]
372 } err]} {
373 if { [lindex $cmd end] eq {2>@1}
374 && $err eq {can not find channel named "1"}
376 # Older versions of Tcl 8.4 don't have this 2>@1 IO
377 # redirect operator. Fallback to |& cat for those.
378 # The command was not actually started, so its safe
379 # to try to start it a second time.
381 set fd [open [concat \
382 [lrange $cmd 0 end-1] \
383 [list |& cat] \
384 ] r]
385 } else {
386 error $err
389 fconfigure $fd -eofchar {}
390 return $fd
393 proc git_read {args} {
394 set opt [list |]
396 while {1} {
397 switch -- [lindex $args 0] {
398 --nice {
399 _lappend_nice opt
402 --stderr {
403 lappend args 2>@1
406 default {
407 break
412 set args [lrange $args 1 end]
415 set cmdp [_git_cmd [lindex $args 0]]
416 set args [lrange $args 1 end]
418 return [_open_stdout_stderr [concat $opt $cmdp $args]]
421 proc git_write {args} {
422 set opt [list |]
424 while {1} {
425 switch -- [lindex $args 0] {
426 --nice {
427 _lappend_nice opt
430 default {
431 break
436 set args [lrange $args 1 end]
439 set cmdp [_git_cmd [lindex $args 0]]
440 set args [lrange $args 1 end]
442 return [open [concat $opt $cmdp $args] w]
445 proc sq {value} {
446 regsub -all ' $value "'\\''" value
447 return "'$value'"
450 proc load_current_branch {} {
451 global current_branch is_detached
453 set fd [open [gitdir HEAD] r]
454 if {[gets $fd ref] < 1} {
455 set ref {}
457 close $fd
459 set pfx {ref: refs/heads/}
460 set len [string length $pfx]
461 if {[string equal -length $len $pfx $ref]} {
462 # We're on a branch. It might not exist. But
463 # HEAD looks good enough to be a branch.
465 set current_branch [string range $ref $len end]
466 set is_detached 0
467 } else {
468 # Assume this is a detached head.
470 set current_branch HEAD
471 set is_detached 1
475 auto_load tk_optionMenu
476 rename tk_optionMenu real__tkOptionMenu
477 proc tk_optionMenu {w varName args} {
478 set m [eval real__tkOptionMenu $w $varName $args]
479 $m configure -font font_ui
480 $w configure -font font_ui
481 return $m
484 ######################################################################
486 ## find git
488 set _git [_which git]
489 if {$_git eq {}} {
490 catch {wm withdraw .}
491 error_popup "Cannot find git in PATH."
492 exit 1
495 ######################################################################
497 ## version check
499 if {[catch {set _git_version [git --version]} err]} {
500 catch {wm withdraw .}
501 tk_messageBox \
502 -icon error \
503 -type ok \
504 -title "git-gui: fatal error" \
505 -message "Cannot determine Git version:
507 $err
509 [appname] requires Git 1.5.0 or later."
510 exit 1
512 if {![regsub {^git version } $_git_version {} _git_version]} {
513 catch {wm withdraw .}
514 tk_messageBox \
515 -icon error \
516 -type ok \
517 -title "git-gui: fatal error" \
518 -message "Cannot parse Git version string:\n\n$_git_version"
519 exit 1
522 set _real_git_version $_git_version
523 regsub -- {-dirty$} $_git_version {} _git_version
524 regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version
525 regsub {\.rc[0-9]+$} $_git_version {} _git_version
526 regsub {\.GIT$} $_git_version {} _git_version
528 if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} {
529 catch {wm withdraw .}
530 if {[tk_messageBox \
531 -icon warning \
532 -type yesno \
533 -default no \
534 -title "[appname]: warning" \
535 -message "Git version cannot be determined.
537 $_git claims it is version '$_real_git_version'.
539 [appname] requires at least Git 1.5.0 or later.
541 Assume '$_real_git_version' is version 1.5.0?
542 "] eq {yes}} {
543 set _git_version 1.5.0
544 } else {
545 exit 1
548 unset _real_git_version
550 proc git-version {args} {
551 global _git_version
553 switch [llength $args] {
555 return $_git_version
559 set op [lindex $args 0]
560 set vr [lindex $args 1]
561 set cm [package vcompare $_git_version $vr]
562 return [expr $cm $op 0]
566 set type [lindex $args 0]
567 set name [lindex $args 1]
568 set parm [lindex $args 2]
569 set body [lindex $args 3]
571 if {($type ne {proc} && $type ne {method})} {
572 error "Invalid arguments to git-version"
574 if {[llength $body] < 2 || [lindex $body end-1] ne {default}} {
575 error "Last arm of $type $name must be default"
578 foreach {op vr cb} [lrange $body 0 end-2] {
579 if {[git-version $op $vr]} {
580 return [uplevel [list $type $name $parm $cb]]
584 return [uplevel [list $type $name $parm [lindex $body end]]]
587 default {
588 error "git-version >= x"
594 if {[git-version < 1.5]} {
595 catch {wm withdraw .}
596 tk_messageBox \
597 -icon error \
598 -type ok \
599 -title "git-gui: fatal error" \
600 -message "[appname] requires Git 1.5.0 or later.
602 You are using [git-version]:
604 [git --version]"
605 exit 1
608 ######################################################################
610 ## configure our library
612 set oguilib {@@GITGUI_LIBDIR@@}
613 set oguirel {@@GITGUI_RELATIVE@@}
614 if {$oguirel eq {1}} {
615 set oguilib [file dirname [file dirname [file normalize $argv0]]]
616 set oguilib [file join $oguilib share git-gui lib]
617 } elseif {[string match @@* $oguirel]} {
618 set oguilib [file join [file dirname [file normalize $argv0]] lib]
621 set idx [file join $oguilib tclIndex]
622 if {[catch {set fd [open $idx r]} err]} {
623 catch {wm withdraw .}
624 tk_messageBox \
625 -icon error \
626 -type ok \
627 -title "git-gui: fatal error" \
628 -message $err
629 exit 1
631 if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
632 set idx [list]
633 while {[gets $fd n] >= 0} {
634 if {$n ne {} && ![string match #* $n]} {
635 lappend idx $n
638 } else {
639 set idx {}
641 close $fd
643 if {$idx ne {}} {
644 set loaded [list]
645 foreach p $idx {
646 if {[lsearch -exact $loaded $p] >= 0} continue
647 source [file join $oguilib $p]
648 lappend loaded $p
650 unset loaded p
651 } else {
652 set auto_path [concat [list $oguilib] $auto_path]
654 unset -nocomplain oguirel idx fd
656 ######################################################################
658 ## feature option selection
660 if {[regexp {^git-(.+)$} [appname] _junk subcommand]} {
661 unset _junk
662 } else {
663 set subcommand gui
665 if {$subcommand eq {gui.sh}} {
666 set subcommand gui
668 if {$subcommand eq {gui} && [llength $argv] > 0} {
669 set subcommand [lindex $argv 0]
670 set argv [lrange $argv 1 end]
673 enable_option multicommit
674 enable_option branch
675 enable_option transport
676 disable_option bare
678 switch -- $subcommand {
679 browser -
680 blame {
681 enable_option bare
683 disable_option multicommit
684 disable_option branch
685 disable_option transport
687 citool {
688 enable_option singlecommit
690 disable_option multicommit
691 disable_option branch
692 disable_option transport
696 ######################################################################
698 ## repository setup
700 if {[catch {
701 set _gitdir $env(GIT_DIR)
702 set _prefix {}
704 && [catch {
705 set _gitdir [git rev-parse --git-dir]
706 set _prefix [git rev-parse --show-prefix]
707 } err]} {
708 catch {wm withdraw .}
709 error_popup "Cannot find the git directory:\n\n$err"
710 exit 1
712 if {![file isdirectory $_gitdir] && [is_Cygwin]} {
713 catch {set _gitdir [exec cygpath --unix $_gitdir]}
715 if {![file isdirectory $_gitdir]} {
716 catch {wm withdraw .}
717 error_popup "Git directory not found:\n\n$_gitdir"
718 exit 1
720 if {$_prefix ne {}} {
721 regsub -all {[^/]+/} $_prefix ../ cdup
722 if {[catch {cd $cdup} err]} {
723 catch {wm withdraw .}
724 error_popup "Cannot move to top of working directory:\n\n$err"
725 exit 1
727 unset cdup
728 } elseif {![is_enabled bare]} {
729 if {[lindex [file split $_gitdir] end] ne {.git}} {
730 catch {wm withdraw .}
731 error_popup "Cannot use funny .git directory:\n\n$_gitdir"
732 exit 1
734 if {[catch {cd [file dirname $_gitdir]} err]} {
735 catch {wm withdraw .}
736 error_popup "No working directory [file dirname $_gitdir]:\n\n$err"
737 exit 1
740 set _reponame [file split [file normalize $_gitdir]]
741 if {[lindex $_reponame end] eq {.git}} {
742 set _reponame [lindex $_reponame end-1]
743 } else {
744 set _reponame [lindex $_reponame end]
747 ######################################################################
749 ## global init
751 set current_diff_path {}
752 set current_diff_side {}
753 set diff_actions [list]
755 set HEAD {}
756 set PARENT {}
757 set MERGE_HEAD [list]
758 set commit_type {}
759 set empty_tree {}
760 set current_branch {}
761 set is_detached 0
762 set current_diff_path {}
763 set is_3way_diff 0
764 set selected_commit_type new
766 ######################################################################
768 ## task management
770 set rescan_active 0
771 set diff_active 0
772 set last_clicked {}
774 set disable_on_lock [list]
775 set index_lock_type none
777 proc lock_index {type} {
778 global index_lock_type disable_on_lock
780 if {$index_lock_type eq {none}} {
781 set index_lock_type $type
782 foreach w $disable_on_lock {
783 uplevel #0 $w disabled
785 return 1
786 } elseif {$index_lock_type eq "begin-$type"} {
787 set index_lock_type $type
788 return 1
790 return 0
793 proc unlock_index {} {
794 global index_lock_type disable_on_lock
796 set index_lock_type none
797 foreach w $disable_on_lock {
798 uplevel #0 $w normal
802 ######################################################################
804 ## status
806 proc repository_state {ctvar hdvar mhvar} {
807 global current_branch
808 upvar $ctvar ct $hdvar hd $mhvar mh
810 set mh [list]
812 load_current_branch
813 if {[catch {set hd [git rev-parse --verify HEAD]}]} {
814 set hd {}
815 set ct initial
816 return
819 set merge_head [gitdir MERGE_HEAD]
820 if {[file exists $merge_head]} {
821 set ct merge
822 set fd_mh [open $merge_head r]
823 while {[gets $fd_mh line] >= 0} {
824 lappend mh $line
826 close $fd_mh
827 return
830 set ct normal
833 proc PARENT {} {
834 global PARENT empty_tree
836 set p [lindex $PARENT 0]
837 if {$p ne {}} {
838 return $p
840 if {$empty_tree eq {}} {
841 set empty_tree [git mktree << {}]
843 return $empty_tree
846 proc rescan {after {honor_trustmtime 1}} {
847 global HEAD PARENT MERGE_HEAD commit_type
848 global ui_index ui_workdir ui_comm
849 global rescan_active file_states
850 global repo_config
852 if {$rescan_active > 0 || ![lock_index read]} return
854 repository_state newType newHEAD newMERGE_HEAD
855 if {[string match amend* $commit_type]
856 && $newType eq {normal}
857 && $newHEAD eq $HEAD} {
858 } else {
859 set HEAD $newHEAD
860 set PARENT $newHEAD
861 set MERGE_HEAD $newMERGE_HEAD
862 set commit_type $newType
865 array unset file_states
867 if {!$::GITGUI_BCK_exists &&
868 (![$ui_comm edit modified]
869 || [string trim [$ui_comm get 0.0 end]] eq {})} {
870 if {[string match amend* $commit_type]} {
871 } elseif {[load_message GITGUI_MSG]} {
872 } elseif {[load_message MERGE_MSG]} {
873 } elseif {[load_message SQUASH_MSG]} {
875 $ui_comm edit reset
876 $ui_comm edit modified false
879 if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
880 rescan_stage2 {} $after
881 } else {
882 set rescan_active 1
883 ui_status {Refreshing file status...}
884 set fd_rf [git_read update-index \
885 -q \
886 --unmerged \
887 --ignore-missing \
888 --refresh \
890 fconfigure $fd_rf -blocking 0 -translation binary
891 fileevent $fd_rf readable \
892 [list rescan_stage2 $fd_rf $after]
896 proc rescan_stage2 {fd after} {
897 global rescan_active buf_rdi buf_rdf buf_rlo
899 if {$fd ne {}} {
900 read $fd
901 if {![eof $fd]} return
902 close $fd
905 set ls_others [list --exclude-per-directory=.gitignore]
906 set info_exclude [gitdir info exclude]
907 if {[file readable $info_exclude]} {
908 lappend ls_others "--exclude-from=$info_exclude"
910 set user_exclude [get_config core.excludesfile]
911 if {$user_exclude ne {} && [file readable $user_exclude]} {
912 lappend ls_others "--exclude-from=$user_exclude"
915 set buf_rdi {}
916 set buf_rdf {}
917 set buf_rlo {}
919 set rescan_active 3
920 ui_status {Scanning for modified files ...}
921 set fd_di [git_read diff-index --cached -z [PARENT]]
922 set fd_df [git_read diff-files -z]
923 set fd_lo [eval git_read ls-files --others -z $ls_others]
925 fconfigure $fd_di -blocking 0 -translation binary -encoding binary
926 fconfigure $fd_df -blocking 0 -translation binary -encoding binary
927 fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
928 fileevent $fd_di readable [list read_diff_index $fd_di $after]
929 fileevent $fd_df readable [list read_diff_files $fd_df $after]
930 fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
933 proc load_message {file} {
934 global ui_comm
936 set f [gitdir $file]
937 if {[file isfile $f]} {
938 if {[catch {set fd [open $f r]}]} {
939 return 0
941 fconfigure $fd -eofchar {}
942 set content [string trim [read $fd]]
943 close $fd
944 regsub -all -line {[ \r\t]+$} $content {} content
945 $ui_comm delete 0.0 end
946 $ui_comm insert end $content
947 return 1
949 return 0
952 proc read_diff_index {fd after} {
953 global buf_rdi
955 append buf_rdi [read $fd]
956 set c 0
957 set n [string length $buf_rdi]
958 while {$c < $n} {
959 set z1 [string first "\0" $buf_rdi $c]
960 if {$z1 == -1} break
961 incr z1
962 set z2 [string first "\0" $buf_rdi $z1]
963 if {$z2 == -1} break
965 incr c
966 set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
967 set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
968 merge_state \
969 [encoding convertfrom $p] \
970 [lindex $i 4]? \
971 [list [lindex $i 0] [lindex $i 2]] \
972 [list]
973 set c $z2
974 incr c
976 if {$c < $n} {
977 set buf_rdi [string range $buf_rdi $c end]
978 } else {
979 set buf_rdi {}
982 rescan_done $fd buf_rdi $after
985 proc read_diff_files {fd after} {
986 global buf_rdf
988 append buf_rdf [read $fd]
989 set c 0
990 set n [string length $buf_rdf]
991 while {$c < $n} {
992 set z1 [string first "\0" $buf_rdf $c]
993 if {$z1 == -1} break
994 incr z1
995 set z2 [string first "\0" $buf_rdf $z1]
996 if {$z2 == -1} break
998 incr c
999 set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
1000 set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
1001 merge_state \
1002 [encoding convertfrom $p] \
1003 ?[lindex $i 4] \
1004 [list] \
1005 [list [lindex $i 0] [lindex $i 2]]
1006 set c $z2
1007 incr c
1009 if {$c < $n} {
1010 set buf_rdf [string range $buf_rdf $c end]
1011 } else {
1012 set buf_rdf {}
1015 rescan_done $fd buf_rdf $after
1018 proc read_ls_others {fd after} {
1019 global buf_rlo
1021 append buf_rlo [read $fd]
1022 set pck [split $buf_rlo "\0"]
1023 set buf_rlo [lindex $pck end]
1024 foreach p [lrange $pck 0 end-1] {
1025 set p [encoding convertfrom $p]
1026 if {[string index $p end] eq {/}} {
1027 set p [string range $p 0 end-1]
1029 merge_state $p ?O
1031 rescan_done $fd buf_rlo $after
1034 proc rescan_done {fd buf after} {
1035 global rescan_active current_diff_path
1036 global file_states repo_config
1037 upvar $buf to_clear
1039 if {![eof $fd]} return
1040 set to_clear {}
1041 close $fd
1042 if {[incr rescan_active -1] > 0} return
1044 prune_selection
1045 unlock_index
1046 display_all_files
1047 if {$current_diff_path ne {}} reshow_diff
1048 uplevel #0 $after
1051 proc prune_selection {} {
1052 global file_states selected_paths
1054 foreach path [array names selected_paths] {
1055 if {[catch {set still_here $file_states($path)}]} {
1056 unset selected_paths($path)
1061 ######################################################################
1063 ## ui helpers
1065 proc mapicon {w state path} {
1066 global all_icons
1068 if {[catch {set r $all_icons($state$w)}]} {
1069 puts "error: no icon for $w state={$state} $path"
1070 return file_plain
1072 return $r
1075 proc mapdesc {state path} {
1076 global all_descs
1078 if {[catch {set r $all_descs($state)}]} {
1079 puts "error: no desc for state={$state} $path"
1080 return $state
1082 return $r
1085 proc ui_status {msg} {
1086 $::main_status show $msg
1089 proc ui_ready {{test {}}} {
1090 $::main_status show {Ready.} $test
1093 proc escape_path {path} {
1094 regsub -all {\\} $path "\\\\" path
1095 regsub -all "\n" $path "\\n" path
1096 return $path
1099 proc short_path {path} {
1100 return [escape_path [lindex [file split $path] end]]
1103 set next_icon_id 0
1104 set null_sha1 [string repeat 0 40]
1106 proc merge_state {path new_state {head_info {}} {index_info {}}} {
1107 global file_states next_icon_id null_sha1
1109 set s0 [string index $new_state 0]
1110 set s1 [string index $new_state 1]
1112 if {[catch {set info $file_states($path)}]} {
1113 set state __
1114 set icon n[incr next_icon_id]
1115 } else {
1116 set state [lindex $info 0]
1117 set icon [lindex $info 1]
1118 if {$head_info eq {}} {set head_info [lindex $info 2]}
1119 if {$index_info eq {}} {set index_info [lindex $info 3]}
1122 if {$s0 eq {?}} {set s0 [string index $state 0]} \
1123 elseif {$s0 eq {_}} {set s0 _}
1125 if {$s1 eq {?}} {set s1 [string index $state 1]} \
1126 elseif {$s1 eq {_}} {set s1 _}
1128 if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
1129 set head_info [list 0 $null_sha1]
1130 } elseif {$s0 ne {_} && [string index $state 0] eq {_}
1131 && $head_info eq {}} {
1132 set head_info $index_info
1135 set file_states($path) [list $s0$s1 $icon \
1136 $head_info $index_info \
1138 return $state
1141 proc display_file_helper {w path icon_name old_m new_m} {
1142 global file_lists
1144 if {$new_m eq {_}} {
1145 set lno [lsearch -sorted -exact $file_lists($w) $path]
1146 if {$lno >= 0} {
1147 set file_lists($w) [lreplace $file_lists($w) $lno $lno]
1148 incr lno
1149 $w conf -state normal
1150 $w delete $lno.0 [expr {$lno + 1}].0
1151 $w conf -state disabled
1153 } elseif {$old_m eq {_} && $new_m ne {_}} {
1154 lappend file_lists($w) $path
1155 set file_lists($w) [lsort -unique $file_lists($w)]
1156 set lno [lsearch -sorted -exact $file_lists($w) $path]
1157 incr lno
1158 $w conf -state normal
1159 $w image create $lno.0 \
1160 -align center -padx 5 -pady 1 \
1161 -name $icon_name \
1162 -image [mapicon $w $new_m $path]
1163 $w insert $lno.1 "[escape_path $path]\n"
1164 $w conf -state disabled
1165 } elseif {$old_m ne $new_m} {
1166 $w conf -state normal
1167 $w image conf $icon_name -image [mapicon $w $new_m $path]
1168 $w conf -state disabled
1172 proc display_file {path state} {
1173 global file_states selected_paths
1174 global ui_index ui_workdir
1176 set old_m [merge_state $path $state]
1177 set s $file_states($path)
1178 set new_m [lindex $s 0]
1179 set icon_name [lindex $s 1]
1181 set o [string index $old_m 0]
1182 set n [string index $new_m 0]
1183 if {$o eq {U}} {
1184 set o _
1186 if {$n eq {U}} {
1187 set n _
1189 display_file_helper $ui_index $path $icon_name $o $n
1191 if {[string index $old_m 0] eq {U}} {
1192 set o U
1193 } else {
1194 set o [string index $old_m 1]
1196 if {[string index $new_m 0] eq {U}} {
1197 set n U
1198 } else {
1199 set n [string index $new_m 1]
1201 display_file_helper $ui_workdir $path $icon_name $o $n
1203 if {$new_m eq {__}} {
1204 unset file_states($path)
1205 catch {unset selected_paths($path)}
1209 proc display_all_files_helper {w path icon_name m} {
1210 global file_lists
1212 lappend file_lists($w) $path
1213 set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
1214 $w image create end \
1215 -align center -padx 5 -pady 1 \
1216 -name $icon_name \
1217 -image [mapicon $w $m $path]
1218 $w insert end "[escape_path $path]\n"
1221 proc display_all_files {} {
1222 global ui_index ui_workdir
1223 global file_states file_lists
1224 global last_clicked
1226 $ui_index conf -state normal
1227 $ui_workdir conf -state normal
1229 $ui_index delete 0.0 end
1230 $ui_workdir delete 0.0 end
1231 set last_clicked {}
1233 set file_lists($ui_index) [list]
1234 set file_lists($ui_workdir) [list]
1236 foreach path [lsort [array names file_states]] {
1237 set s $file_states($path)
1238 set m [lindex $s 0]
1239 set icon_name [lindex $s 1]
1241 set s [string index $m 0]
1242 if {$s ne {U} && $s ne {_}} {
1243 display_all_files_helper $ui_index $path \
1244 $icon_name $s
1247 if {[string index $m 0] eq {U}} {
1248 set s U
1249 } else {
1250 set s [string index $m 1]
1252 if {$s ne {_}} {
1253 display_all_files_helper $ui_workdir $path \
1254 $icon_name $s
1258 $ui_index conf -state disabled
1259 $ui_workdir conf -state disabled
1262 ######################################################################
1264 ## icons
1266 set filemask {
1267 #define mask_width 14
1268 #define mask_height 15
1269 static unsigned char mask_bits[] = {
1270 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1271 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1272 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1275 image create bitmap file_plain -background white -foreground black -data {
1276 #define plain_width 14
1277 #define plain_height 15
1278 static unsigned char plain_bits[] = {
1279 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1280 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1281 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1282 } -maskdata $filemask
1284 image create bitmap file_mod -background white -foreground blue -data {
1285 #define mod_width 14
1286 #define mod_height 15
1287 static unsigned char mod_bits[] = {
1288 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1289 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1290 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1291 } -maskdata $filemask
1293 image create bitmap file_fulltick -background white -foreground "#007000" -data {
1294 #define file_fulltick_width 14
1295 #define file_fulltick_height 15
1296 static unsigned char file_fulltick_bits[] = {
1297 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1298 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1299 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1300 } -maskdata $filemask
1302 image create bitmap file_parttick -background white -foreground "#005050" -data {
1303 #define parttick_width 14
1304 #define parttick_height 15
1305 static unsigned char parttick_bits[] = {
1306 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1307 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1308 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1309 } -maskdata $filemask
1311 image create bitmap file_question -background white -foreground black -data {
1312 #define file_question_width 14
1313 #define file_question_height 15
1314 static unsigned char file_question_bits[] = {
1315 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1316 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1317 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1318 } -maskdata $filemask
1320 image create bitmap file_removed -background white -foreground red -data {
1321 #define file_removed_width 14
1322 #define file_removed_height 15
1323 static unsigned char file_removed_bits[] = {
1324 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1325 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1326 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1327 } -maskdata $filemask
1329 image create bitmap file_merge -background white -foreground blue -data {
1330 #define file_merge_width 14
1331 #define file_merge_height 15
1332 static unsigned char file_merge_bits[] = {
1333 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1334 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1335 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1336 } -maskdata $filemask
1338 set ui_index .vpane.files.index.list
1339 set ui_workdir .vpane.files.workdir.list
1341 set all_icons(_$ui_index) file_plain
1342 set all_icons(A$ui_index) file_fulltick
1343 set all_icons(M$ui_index) file_fulltick
1344 set all_icons(D$ui_index) file_removed
1345 set all_icons(U$ui_index) file_merge
1347 set all_icons(_$ui_workdir) file_plain
1348 set all_icons(M$ui_workdir) file_mod
1349 set all_icons(D$ui_workdir) file_question
1350 set all_icons(U$ui_workdir) file_merge
1351 set all_icons(O$ui_workdir) file_plain
1353 set max_status_desc 0
1354 foreach i {
1355 {__ "Unmodified"}
1357 {_M "Modified, not staged"}
1358 {M_ "Staged for commit"}
1359 {MM "Portions staged for commit"}
1360 {MD "Staged for commit, missing"}
1362 {_O "Untracked, not staged"}
1363 {A_ "Staged for commit"}
1364 {AM "Portions staged for commit"}
1365 {AD "Staged for commit, missing"}
1367 {_D "Missing"}
1368 {D_ "Staged for removal"}
1369 {DO "Staged for removal, still present"}
1371 {U_ "Requires merge resolution"}
1372 {UU "Requires merge resolution"}
1373 {UM "Requires merge resolution"}
1374 {UD "Requires merge resolution"}
1376 if {$max_status_desc < [string length [lindex $i 1]]} {
1377 set max_status_desc [string length [lindex $i 1]]
1379 set all_descs([lindex $i 0]) [lindex $i 1]
1381 unset i
1383 ######################################################################
1385 ## util
1387 proc bind_button3 {w cmd} {
1388 bind $w <Any-Button-3> $cmd
1389 if {[is_MacOSX]} {
1390 # Mac OS X sends Button-2 on right click through three-button mouse,
1391 # or through trackpad right-clicking (two-finger touch + click).
1392 bind $w <Any-Button-2> $cmd
1393 bind $w <Control-Button-1> $cmd
1397 proc scrollbar2many {list mode args} {
1398 foreach w $list {eval $w $mode $args}
1401 proc many2scrollbar {list mode sb top bottom} {
1402 $sb set $top $bottom
1403 foreach w $list {$w $mode moveto $top}
1406 proc incr_font_size {font {amt 1}} {
1407 set sz [font configure $font -size]
1408 incr sz $amt
1409 font configure $font -size $sz
1410 font configure ${font}bold -size $sz
1411 font configure ${font}italic -size $sz
1414 ######################################################################
1416 ## ui commands
1418 set starting_gitk_msg {Starting gitk... please wait...}
1420 proc do_gitk {revs} {
1421 # -- Always start gitk through whatever we were loaded with. This
1422 # lets us bypass using shell process on Windows systems.
1424 set exe [file join [file dirname $::_git] gitk]
1425 set cmd [list [info nameofexecutable] $exe]
1426 if {! [file exists $exe]} {
1427 error_popup "Unable to start gitk:\n\n$exe does not exist"
1428 } else {
1429 eval exec $cmd $revs &
1430 ui_status $::starting_gitk_msg
1431 after 10000 {
1432 ui_ready $starting_gitk_msg
1437 set is_quitting 0
1439 proc do_quit {} {
1440 global ui_comm is_quitting repo_config commit_type
1441 global GITGUI_BCK_exists GITGUI_BCK_i
1443 if {$is_quitting} return
1444 set is_quitting 1
1446 if {[winfo exists $ui_comm]} {
1447 # -- Stash our current commit buffer.
1449 set save [gitdir GITGUI_MSG]
1450 if {$GITGUI_BCK_exists && ![$ui_comm edit modified]} {
1451 file rename -force [gitdir GITGUI_BCK] $save
1452 set GITGUI_BCK_exists 0
1453 } else {
1454 set msg [string trim [$ui_comm get 0.0 end]]
1455 regsub -all -line {[ \r\t]+$} $msg {} msg
1456 if {(![string match amend* $commit_type]
1457 || [$ui_comm edit modified])
1458 && $msg ne {}} {
1459 catch {
1460 set fd [open $save w]
1461 puts -nonewline $fd $msg
1462 close $fd
1464 } else {
1465 catch {file delete $save}
1469 # -- Remove our editor backup, its not needed.
1471 after cancel $GITGUI_BCK_i
1472 if {$GITGUI_BCK_exists} {
1473 catch {file delete [gitdir GITGUI_BCK]}
1476 # -- Stash our current window geometry into this repository.
1478 set cfg_geometry [list]
1479 lappend cfg_geometry [wm geometry .]
1480 lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
1481 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
1482 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1483 set rc_geometry {}
1485 if {$cfg_geometry ne $rc_geometry} {
1486 catch {git config gui.geometry $cfg_geometry}
1490 destroy .
1493 proc do_rescan {} {
1494 rescan ui_ready
1497 proc do_commit {} {
1498 commit_tree
1501 proc toggle_or_diff {w x y} {
1502 global file_states file_lists current_diff_path ui_index ui_workdir
1503 global last_clicked selected_paths
1505 set pos [split [$w index @$x,$y] .]
1506 set lno [lindex $pos 0]
1507 set col [lindex $pos 1]
1508 set path [lindex $file_lists($w) [expr {$lno - 1}]]
1509 if {$path eq {}} {
1510 set last_clicked {}
1511 return
1514 set last_clicked [list $w $lno]
1515 array unset selected_paths
1516 $ui_index tag remove in_sel 0.0 end
1517 $ui_workdir tag remove in_sel 0.0 end
1519 if {$col == 0} {
1520 if {$current_diff_path eq $path} {
1521 set after {reshow_diff;}
1522 } else {
1523 set after {}
1525 if {$w eq $ui_index} {
1526 update_indexinfo \
1527 "Unstaging [short_path $path] from commit" \
1528 [list $path] \
1529 [concat $after [list ui_ready]]
1530 } elseif {$w eq $ui_workdir} {
1531 update_index \
1532 "Adding [short_path $path]" \
1533 [list $path] \
1534 [concat $after [list ui_ready]]
1536 } else {
1537 show_diff $path $w $lno
1541 proc add_one_to_selection {w x y} {
1542 global file_lists last_clicked selected_paths
1544 set lno [lindex [split [$w index @$x,$y] .] 0]
1545 set path [lindex $file_lists($w) [expr {$lno - 1}]]
1546 if {$path eq {}} {
1547 set last_clicked {}
1548 return
1551 if {$last_clicked ne {}
1552 && [lindex $last_clicked 0] ne $w} {
1553 array unset selected_paths
1554 [lindex $last_clicked 0] tag remove in_sel 0.0 end
1557 set last_clicked [list $w $lno]
1558 if {[catch {set in_sel $selected_paths($path)}]} {
1559 set in_sel 0
1561 if {$in_sel} {
1562 unset selected_paths($path)
1563 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
1564 } else {
1565 set selected_paths($path) 1
1566 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
1570 proc add_range_to_selection {w x y} {
1571 global file_lists last_clicked selected_paths
1573 if {[lindex $last_clicked 0] ne $w} {
1574 toggle_or_diff $w $x $y
1575 return
1578 set lno [lindex [split [$w index @$x,$y] .] 0]
1579 set lc [lindex $last_clicked 1]
1580 if {$lc < $lno} {
1581 set begin $lc
1582 set end $lno
1583 } else {
1584 set begin $lno
1585 set end $lc
1588 foreach path [lrange $file_lists($w) \
1589 [expr {$begin - 1}] \
1590 [expr {$end - 1}]] {
1591 set selected_paths($path) 1
1593 $w tag add in_sel $begin.0 [expr {$end + 1}].0
1596 ######################################################################
1598 ## config defaults
1600 set cursor_ptr arrow
1601 font create font_diff -family Courier -size 10
1602 font create font_ui
1603 catch {
1604 label .dummy
1605 eval font configure font_ui [font actual [.dummy cget -font]]
1606 destroy .dummy
1609 font create font_uiitalic
1610 font create font_uibold
1611 font create font_diffbold
1612 font create font_diffitalic
1614 foreach class {Button Checkbutton Entry Label
1615 Labelframe Listbox Menu Message
1616 Radiobutton Spinbox Text} {
1617 option add *$class.font font_ui
1619 unset class
1621 if {[is_Windows] || [is_MacOSX]} {
1622 option add *Menu.tearOff 0
1625 if {[is_MacOSX]} {
1626 set M1B M1
1627 set M1T Cmd
1628 } else {
1629 set M1B Control
1630 set M1T Ctrl
1633 proc apply_config {} {
1634 global repo_config font_descs
1636 foreach option $font_descs {
1637 set name [lindex $option 0]
1638 set font [lindex $option 1]
1639 if {[catch {
1640 foreach {cn cv} $repo_config(gui.$name) {
1641 font configure $font $cn $cv
1643 } err]} {
1644 error_popup "Invalid font specified in gui.$name:\n\n$err"
1646 foreach {cn cv} [font configure $font] {
1647 font configure ${font}bold $cn $cv
1648 font configure ${font}italic $cn $cv
1650 font configure ${font}bold -weight bold
1651 font configure ${font}italic -slant italic
1655 set default_config(merge.diffstat) true
1656 set default_config(merge.summary) false
1657 set default_config(merge.verbosity) 2
1658 set default_config(user.name) {}
1659 set default_config(user.email) {}
1661 set default_config(gui.matchtrackingbranch) false
1662 set default_config(gui.pruneduringfetch) false
1663 set default_config(gui.trustmtime) false
1664 set default_config(gui.diffcontext) 5
1665 set default_config(gui.newbranchtemplate) {}
1666 set default_config(gui.fontui) [font configure font_ui]
1667 set default_config(gui.fontdiff) [font configure font_diff]
1668 set font_descs {
1669 {fontui font_ui {Main Font}}
1670 {fontdiff font_diff {Diff/Console Font}}
1672 load_config 0
1673 apply_config
1675 ######################################################################
1677 ## ui construction
1679 set ui_comm {}
1681 # -- Menu Bar
1683 menu .mbar -tearoff 0
1684 .mbar add cascade -label Repository -menu .mbar.repository
1685 .mbar add cascade -label Edit -menu .mbar.edit
1686 if {[is_enabled branch]} {
1687 .mbar add cascade -label Branch -menu .mbar.branch
1689 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1690 .mbar add cascade -label Commit -menu .mbar.commit
1692 if {[is_enabled transport]} {
1693 .mbar add cascade -label Merge -menu .mbar.merge
1694 .mbar add cascade -label Fetch -menu .mbar.fetch
1695 .mbar add cascade -label Push -menu .mbar.push
1697 . configure -menu .mbar
1699 # -- Repository Menu
1701 menu .mbar.repository
1703 .mbar.repository add command \
1704 -label {Browse Current Branch's Files} \
1705 -command {browser::new $current_branch}
1706 set ui_browse_current [.mbar.repository index last]
1707 .mbar.repository add command \
1708 -label {Browse Branch Files...} \
1709 -command browser_open::dialog
1710 .mbar.repository add separator
1712 .mbar.repository add command \
1713 -label {Visualize Current Branch's History} \
1714 -command {do_gitk $current_branch}
1715 set ui_visualize_current [.mbar.repository index last]
1716 .mbar.repository add command \
1717 -label {Visualize All Branch History} \
1718 -command {do_gitk --all}
1719 .mbar.repository add separator
1721 proc current_branch_write {args} {
1722 global current_branch
1723 .mbar.repository entryconf $::ui_browse_current \
1724 -label "Browse $current_branch's Files"
1725 .mbar.repository entryconf $::ui_visualize_current \
1726 -label "Visualize $current_branch's History"
1728 trace add variable current_branch write current_branch_write
1730 if {[is_enabled multicommit]} {
1731 .mbar.repository add command -label {Database Statistics} \
1732 -command do_stats
1734 .mbar.repository add command -label {Compress Database} \
1735 -command do_gc
1737 .mbar.repository add command -label {Verify Database} \
1738 -command do_fsck_objects
1740 .mbar.repository add separator
1742 if {[is_Cygwin]} {
1743 .mbar.repository add command \
1744 -label {Create Desktop Icon} \
1745 -command do_cygwin_shortcut
1746 } elseif {[is_Windows]} {
1747 .mbar.repository add command \
1748 -label {Create Desktop Icon} \
1749 -command do_windows_shortcut
1750 } elseif {[is_MacOSX]} {
1751 .mbar.repository add command \
1752 -label {Create Desktop Icon} \
1753 -command do_macosx_app
1757 .mbar.repository add command -label Quit \
1758 -command do_quit \
1759 -accelerator $M1T-Q
1761 # -- Edit Menu
1763 menu .mbar.edit
1764 .mbar.edit add command -label Undo \
1765 -command {catch {[focus] edit undo}} \
1766 -accelerator $M1T-Z
1767 .mbar.edit add command -label Redo \
1768 -command {catch {[focus] edit redo}} \
1769 -accelerator $M1T-Y
1770 .mbar.edit add separator
1771 .mbar.edit add command -label Cut \
1772 -command {catch {tk_textCut [focus]}} \
1773 -accelerator $M1T-X
1774 .mbar.edit add command -label Copy \
1775 -command {catch {tk_textCopy [focus]}} \
1776 -accelerator $M1T-C
1777 .mbar.edit add command -label Paste \
1778 -command {catch {tk_textPaste [focus]; [focus] see insert}} \
1779 -accelerator $M1T-V
1780 .mbar.edit add command -label Delete \
1781 -command {catch {[focus] delete sel.first sel.last}} \
1782 -accelerator Del
1783 .mbar.edit add separator
1784 .mbar.edit add command -label {Select All} \
1785 -command {catch {[focus] tag add sel 0.0 end}} \
1786 -accelerator $M1T-A
1788 # -- Branch Menu
1790 if {[is_enabled branch]} {
1791 menu .mbar.branch
1793 .mbar.branch add command -label {Create...} \
1794 -command branch_create::dialog \
1795 -accelerator $M1T-N
1796 lappend disable_on_lock [list .mbar.branch entryconf \
1797 [.mbar.branch index last] -state]
1799 .mbar.branch add command -label {Checkout...} \
1800 -command branch_checkout::dialog \
1801 -accelerator $M1T-O
1802 lappend disable_on_lock [list .mbar.branch entryconf \
1803 [.mbar.branch index last] -state]
1805 .mbar.branch add command -label {Rename...} \
1806 -command branch_rename::dialog
1807 lappend disable_on_lock [list .mbar.branch entryconf \
1808 [.mbar.branch index last] -state]
1810 .mbar.branch add command -label {Delete...} \
1811 -command branch_delete::dialog
1812 lappend disable_on_lock [list .mbar.branch entryconf \
1813 [.mbar.branch index last] -state]
1815 .mbar.branch add command -label {Reset...} \
1816 -command merge::reset_hard
1817 lappend disable_on_lock [list .mbar.branch entryconf \
1818 [.mbar.branch index last] -state]
1821 # -- Commit Menu
1823 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1824 menu .mbar.commit
1826 .mbar.commit add radiobutton \
1827 -label {New Commit} \
1828 -command do_select_commit_type \
1829 -variable selected_commit_type \
1830 -value new
1831 lappend disable_on_lock \
1832 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1834 .mbar.commit add radiobutton \
1835 -label {Amend Last Commit} \
1836 -command do_select_commit_type \
1837 -variable selected_commit_type \
1838 -value amend
1839 lappend disable_on_lock \
1840 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1842 .mbar.commit add separator
1844 .mbar.commit add command -label Rescan \
1845 -command do_rescan \
1846 -accelerator F5
1847 lappend disable_on_lock \
1848 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1850 .mbar.commit add command -label {Stage To Commit} \
1851 -command do_add_selection
1852 lappend disable_on_lock \
1853 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1855 .mbar.commit add command -label {Stage Changed Files To Commit} \
1856 -command do_add_all \
1857 -accelerator $M1T-I
1858 lappend disable_on_lock \
1859 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1861 .mbar.commit add command -label {Unstage From Commit} \
1862 -command do_unstage_selection
1863 lappend disable_on_lock \
1864 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1866 .mbar.commit add command -label {Revert Changes} \
1867 -command do_revert_selection
1868 lappend disable_on_lock \
1869 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1871 .mbar.commit add separator
1873 .mbar.commit add command -label {Sign Off} \
1874 -command do_signoff \
1875 -accelerator $M1T-S
1877 .mbar.commit add command -label Commit \
1878 -command do_commit \
1879 -accelerator $M1T-Return
1880 lappend disable_on_lock \
1881 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1884 # -- Merge Menu
1886 if {[is_enabled branch]} {
1887 menu .mbar.merge
1888 .mbar.merge add command -label {Local Merge...} \
1889 -command merge::dialog \
1890 -accelerator $M1T-M
1891 lappend disable_on_lock \
1892 [list .mbar.merge entryconf [.mbar.merge index last] -state]
1893 .mbar.merge add command -label {Abort Merge...} \
1894 -command merge::reset_hard
1895 lappend disable_on_lock \
1896 [list .mbar.merge entryconf [.mbar.merge index last] -state]
1899 # -- Transport Menu
1901 if {[is_enabled transport]} {
1902 menu .mbar.fetch
1904 menu .mbar.push
1905 .mbar.push add command -label {Push...} \
1906 -command do_push_anywhere \
1907 -accelerator $M1T-P
1908 .mbar.push add command -label {Delete...} \
1909 -command remote_branch_delete::dialog
1912 if {[is_MacOSX]} {
1913 # -- Apple Menu (Mac OS X only)
1915 .mbar add cascade -label Apple -menu .mbar.apple
1916 menu .mbar.apple
1918 .mbar.apple add command -label "About [appname]" \
1919 -command do_about
1920 .mbar.apple add command -label "Options..." \
1921 -command do_options
1922 } else {
1923 # -- Edit Menu
1925 .mbar.edit add separator
1926 .mbar.edit add command -label {Options...} \
1927 -command do_options
1930 # -- Help Menu
1932 .mbar add cascade -label Help -menu .mbar.help
1933 menu .mbar.help
1935 if {![is_MacOSX]} {
1936 .mbar.help add command -label "About [appname]" \
1937 -command do_about
1940 set browser {}
1941 catch {set browser $repo_config(instaweb.browser)}
1942 set doc_path [file dirname [gitexec]]
1943 set doc_path [file join $doc_path Documentation index.html]
1945 if {[is_Cygwin]} {
1946 set doc_path [exec cygpath --mixed $doc_path]
1949 if {$browser eq {}} {
1950 if {[is_MacOSX]} {
1951 set browser open
1952 } elseif {[is_Cygwin]} {
1953 set program_files [file dirname [exec cygpath --windir]]
1954 set program_files [file join $program_files {Program Files}]
1955 set firefox [file join $program_files {Mozilla Firefox} firefox.exe]
1956 set ie [file join $program_files {Internet Explorer} IEXPLORE.EXE]
1957 if {[file exists $firefox]} {
1958 set browser $firefox
1959 } elseif {[file exists $ie]} {
1960 set browser $ie
1962 unset program_files firefox ie
1966 if {[file isfile $doc_path]} {
1967 set doc_url "file:$doc_path"
1968 } else {
1969 set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
1972 if {$browser ne {}} {
1973 .mbar.help add command -label {Online Documentation} \
1974 -command [list exec $browser $doc_url &]
1976 unset browser doc_path doc_url
1978 set root_exists 0
1979 bind . <Visibility> {
1980 bind . <Visibility> {}
1981 set root_exists 1
1984 # -- Standard bindings
1986 wm protocol . WM_DELETE_WINDOW do_quit
1987 bind all <$M1B-Key-q> do_quit
1988 bind all <$M1B-Key-Q> do_quit
1989 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
1990 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
1992 set subcommand_args {}
1993 proc usage {} {
1994 puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
1995 exit 1
1998 # -- Not a normal commit type invocation? Do that instead!
2000 switch -- $subcommand {
2001 browser -
2002 blame {
2003 set subcommand_args {rev? path}
2004 if {$argv eq {}} usage
2005 set head {}
2006 set path {}
2007 set is_path 0
2008 foreach a $argv {
2009 if {$is_path || [file exists $_prefix$a]} {
2010 if {$path ne {}} usage
2011 set path $_prefix$a
2012 break
2013 } elseif {$a eq {--}} {
2014 if {$path ne {}} {
2015 if {$head ne {}} usage
2016 set head $path
2017 set path {}
2019 set is_path 1
2020 } elseif {$head eq {}} {
2021 if {$head ne {}} usage
2022 set head $a
2023 set is_path 1
2024 } else {
2025 usage
2028 unset is_path
2030 if {$head ne {} && $path eq {}} {
2031 set path $_prefix$head
2032 set head {}
2035 if {$head eq {}} {
2036 load_current_branch
2037 } else {
2038 if {[regexp {^[0-9a-f]{1,39}$} $head]} {
2039 if {[catch {
2040 set head [git rev-parse --verify $head]
2041 } err]} {
2042 puts stderr $err
2043 exit 1
2046 set current_branch $head
2049 switch -- $subcommand {
2050 browser {
2051 if {$head eq {}} {
2052 if {$path ne {} && [file isdirectory $path]} {
2053 set head $current_branch
2054 } else {
2055 set head $path
2056 set path {}
2059 browser::new $head $path
2061 blame {
2062 if {$head eq {} && ![file exists $path]} {
2063 puts stderr "fatal: cannot stat path $path: No such file or directory"
2064 exit 1
2066 blame::new $head $path
2069 return
2071 citool -
2072 gui {
2073 if {[llength $argv] != 0} {
2074 puts -nonewline stderr "usage: $argv0"
2075 if {$subcommand ne {gui} && [appname] ne "git-$subcommand"} {
2076 puts -nonewline stderr " $subcommand"
2078 puts stderr {}
2079 exit 1
2081 # fall through to setup UI for commits
2083 default {
2084 puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
2085 exit 1
2089 # -- Branch Control
2091 frame .branch \
2092 -borderwidth 1 \
2093 -relief sunken
2094 label .branch.l1 \
2095 -text {Current Branch:} \
2096 -anchor w \
2097 -justify left
2098 label .branch.cb \
2099 -textvariable current_branch \
2100 -anchor w \
2101 -justify left
2102 pack .branch.l1 -side left
2103 pack .branch.cb -side left -fill x
2104 pack .branch -side top -fill x
2106 # -- Main Window Layout
2108 panedwindow .vpane -orient vertical
2109 panedwindow .vpane.files -orient horizontal
2110 .vpane add .vpane.files -sticky nsew -height 100 -width 200
2111 pack .vpane -anchor n -side top -fill both -expand 1
2113 # -- Index File List
2115 frame .vpane.files.index -height 100 -width 200
2116 label .vpane.files.index.title -text {Staged Changes (Will Be Committed)} \
2117 -background lightgreen
2118 text $ui_index -background white -borderwidth 0 \
2119 -width 20 -height 10 \
2120 -wrap none \
2121 -cursor $cursor_ptr \
2122 -xscrollcommand {.vpane.files.index.sx set} \
2123 -yscrollcommand {.vpane.files.index.sy set} \
2124 -state disabled
2125 scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
2126 scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
2127 pack .vpane.files.index.title -side top -fill x
2128 pack .vpane.files.index.sx -side bottom -fill x
2129 pack .vpane.files.index.sy -side right -fill y
2130 pack $ui_index -side left -fill both -expand 1
2131 .vpane.files add .vpane.files.index -sticky nsew
2133 # -- Working Directory File List
2135 frame .vpane.files.workdir -height 100 -width 200
2136 label .vpane.files.workdir.title -text {Unstaged Changes (Will Not Be Committed)} \
2137 -background lightsalmon
2138 text $ui_workdir -background white -borderwidth 0 \
2139 -width 20 -height 10 \
2140 -wrap none \
2141 -cursor $cursor_ptr \
2142 -xscrollcommand {.vpane.files.workdir.sx set} \
2143 -yscrollcommand {.vpane.files.workdir.sy set} \
2144 -state disabled
2145 scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
2146 scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
2147 pack .vpane.files.workdir.title -side top -fill x
2148 pack .vpane.files.workdir.sx -side bottom -fill x
2149 pack .vpane.files.workdir.sy -side right -fill y
2150 pack $ui_workdir -side left -fill both -expand 1
2151 .vpane.files add .vpane.files.workdir -sticky nsew
2153 foreach i [list $ui_index $ui_workdir] {
2154 $i tag conf in_diff -background lightgray
2155 $i tag conf in_sel -background lightgray
2157 unset i
2159 # -- Diff and Commit Area
2161 frame .vpane.lower -height 300 -width 400
2162 frame .vpane.lower.commarea
2163 frame .vpane.lower.diff -relief sunken -borderwidth 1
2164 pack .vpane.lower.commarea -side top -fill x
2165 pack .vpane.lower.diff -side bottom -fill both -expand 1
2166 .vpane add .vpane.lower -sticky nsew
2168 # -- Commit Area Buttons
2170 frame .vpane.lower.commarea.buttons
2171 label .vpane.lower.commarea.buttons.l -text {} \
2172 -anchor w \
2173 -justify left
2174 pack .vpane.lower.commarea.buttons.l -side top -fill x
2175 pack .vpane.lower.commarea.buttons -side left -fill y
2177 button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
2178 -command do_rescan
2179 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
2180 lappend disable_on_lock \
2181 {.vpane.lower.commarea.buttons.rescan conf -state}
2183 button .vpane.lower.commarea.buttons.incall -text {Stage Changed} \
2184 -command do_add_all
2185 pack .vpane.lower.commarea.buttons.incall -side top -fill x
2186 lappend disable_on_lock \
2187 {.vpane.lower.commarea.buttons.incall conf -state}
2189 button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
2190 -command do_signoff
2191 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
2193 button .vpane.lower.commarea.buttons.commit -text {Commit} \
2194 -command do_commit
2195 pack .vpane.lower.commarea.buttons.commit -side top -fill x
2196 lappend disable_on_lock \
2197 {.vpane.lower.commarea.buttons.commit conf -state}
2199 button .vpane.lower.commarea.buttons.push -text {Push} \
2200 -command do_push_anywhere
2201 pack .vpane.lower.commarea.buttons.push -side top -fill x
2203 # -- Commit Message Buffer
2205 frame .vpane.lower.commarea.buffer
2206 frame .vpane.lower.commarea.buffer.header
2207 set ui_comm .vpane.lower.commarea.buffer.t
2208 set ui_coml .vpane.lower.commarea.buffer.header.l
2209 radiobutton .vpane.lower.commarea.buffer.header.new \
2210 -text {New Commit} \
2211 -command do_select_commit_type \
2212 -variable selected_commit_type \
2213 -value new
2214 lappend disable_on_lock \
2215 [list .vpane.lower.commarea.buffer.header.new conf -state]
2216 radiobutton .vpane.lower.commarea.buffer.header.amend \
2217 -text {Amend Last Commit} \
2218 -command do_select_commit_type \
2219 -variable selected_commit_type \
2220 -value amend
2221 lappend disable_on_lock \
2222 [list .vpane.lower.commarea.buffer.header.amend conf -state]
2223 label $ui_coml \
2224 -anchor w \
2225 -justify left
2226 proc trace_commit_type {varname args} {
2227 global ui_coml commit_type
2228 switch -glob -- $commit_type {
2229 initial {set txt {Initial Commit Message:}}
2230 amend {set txt {Amended Commit Message:}}
2231 amend-initial {set txt {Amended Initial Commit Message:}}
2232 amend-merge {set txt {Amended Merge Commit Message:}}
2233 merge {set txt {Merge Commit Message:}}
2234 * {set txt {Commit Message:}}
2236 $ui_coml conf -text $txt
2238 trace add variable commit_type write trace_commit_type
2239 pack $ui_coml -side left -fill x
2240 pack .vpane.lower.commarea.buffer.header.amend -side right
2241 pack .vpane.lower.commarea.buffer.header.new -side right
2243 text $ui_comm -background white -borderwidth 1 \
2244 -undo true \
2245 -maxundo 20 \
2246 -autoseparators true \
2247 -relief sunken \
2248 -width 75 -height 9 -wrap none \
2249 -font font_diff \
2250 -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
2251 scrollbar .vpane.lower.commarea.buffer.sby \
2252 -command [list $ui_comm yview]
2253 pack .vpane.lower.commarea.buffer.header -side top -fill x
2254 pack .vpane.lower.commarea.buffer.sby -side right -fill y
2255 pack $ui_comm -side left -fill y
2256 pack .vpane.lower.commarea.buffer -side left -fill y
2258 # -- Commit Message Buffer Context Menu
2260 set ctxm .vpane.lower.commarea.buffer.ctxm
2261 menu $ctxm -tearoff 0
2262 $ctxm add command \
2263 -label {Cut} \
2264 -command {tk_textCut $ui_comm}
2265 $ctxm add command \
2266 -label {Copy} \
2267 -command {tk_textCopy $ui_comm}
2268 $ctxm add command \
2269 -label {Paste} \
2270 -command {tk_textPaste $ui_comm}
2271 $ctxm add command \
2272 -label {Delete} \
2273 -command {$ui_comm delete sel.first sel.last}
2274 $ctxm add separator
2275 $ctxm add command \
2276 -label {Select All} \
2277 -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
2278 $ctxm add command \
2279 -label {Copy All} \
2280 -command {
2281 $ui_comm tag add sel 0.0 end
2282 tk_textCopy $ui_comm
2283 $ui_comm tag remove sel 0.0 end
2285 $ctxm add separator
2286 $ctxm add command \
2287 -label {Sign Off} \
2288 -command do_signoff
2289 bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
2291 # -- Diff Header
2293 proc trace_current_diff_path {varname args} {
2294 global current_diff_path diff_actions file_states
2295 if {$current_diff_path eq {}} {
2296 set s {}
2297 set f {}
2298 set p {}
2299 set o disabled
2300 } else {
2301 set p $current_diff_path
2302 set s [mapdesc [lindex $file_states($p) 0] $p]
2303 set f {File:}
2304 set p [escape_path $p]
2305 set o normal
2308 .vpane.lower.diff.header.status configure -text $s
2309 .vpane.lower.diff.header.file configure -text $f
2310 .vpane.lower.diff.header.path configure -text $p
2311 foreach w $diff_actions {
2312 uplevel #0 $w $o
2315 trace add variable current_diff_path write trace_current_diff_path
2317 frame .vpane.lower.diff.header -background gold
2318 label .vpane.lower.diff.header.status \
2319 -background gold \
2320 -width $max_status_desc \
2321 -anchor w \
2322 -justify left
2323 label .vpane.lower.diff.header.file \
2324 -background gold \
2325 -anchor w \
2326 -justify left
2327 label .vpane.lower.diff.header.path \
2328 -background gold \
2329 -anchor w \
2330 -justify left
2331 pack .vpane.lower.diff.header.status -side left
2332 pack .vpane.lower.diff.header.file -side left
2333 pack .vpane.lower.diff.header.path -fill x
2334 set ctxm .vpane.lower.diff.header.ctxm
2335 menu $ctxm -tearoff 0
2336 $ctxm add command \
2337 -label {Copy} \
2338 -command {
2339 clipboard clear
2340 clipboard append \
2341 -format STRING \
2342 -type STRING \
2343 -- $current_diff_path
2345 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2346 bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
2348 # -- Diff Body
2350 frame .vpane.lower.diff.body
2351 set ui_diff .vpane.lower.diff.body.t
2352 text $ui_diff -background white -borderwidth 0 \
2353 -width 80 -height 15 -wrap none \
2354 -font font_diff \
2355 -xscrollcommand {.vpane.lower.diff.body.sbx set} \
2356 -yscrollcommand {.vpane.lower.diff.body.sby set} \
2357 -state disabled
2358 scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
2359 -command [list $ui_diff xview]
2360 scrollbar .vpane.lower.diff.body.sby -orient vertical \
2361 -command [list $ui_diff yview]
2362 pack .vpane.lower.diff.body.sbx -side bottom -fill x
2363 pack .vpane.lower.diff.body.sby -side right -fill y
2364 pack $ui_diff -side left -fill both -expand 1
2365 pack .vpane.lower.diff.header -side top -fill x
2366 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
2368 $ui_diff tag conf d_cr -elide true
2369 $ui_diff tag conf d_@ -foreground blue -font font_diffbold
2370 $ui_diff tag conf d_+ -foreground {#00a000}
2371 $ui_diff tag conf d_- -foreground red
2373 $ui_diff tag conf d_++ -foreground {#00a000}
2374 $ui_diff tag conf d_-- -foreground red
2375 $ui_diff tag conf d_+s \
2376 -foreground {#00a000} \
2377 -background {#e2effa}
2378 $ui_diff tag conf d_-s \
2379 -foreground red \
2380 -background {#e2effa}
2381 $ui_diff tag conf d_s+ \
2382 -foreground {#00a000} \
2383 -background ivory1
2384 $ui_diff tag conf d_s- \
2385 -foreground red \
2386 -background ivory1
2388 $ui_diff tag conf d<<<<<<< \
2389 -foreground orange \
2390 -font font_diffbold
2391 $ui_diff tag conf d======= \
2392 -foreground orange \
2393 -font font_diffbold
2394 $ui_diff tag conf d>>>>>>> \
2395 -foreground orange \
2396 -font font_diffbold
2398 $ui_diff tag raise sel
2400 # -- Diff Body Context Menu
2402 set ctxm .vpane.lower.diff.body.ctxm
2403 menu $ctxm -tearoff 0
2404 $ctxm add command \
2405 -label {Refresh} \
2406 -command reshow_diff
2407 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2408 $ctxm add command \
2409 -label {Copy} \
2410 -command {tk_textCopy $ui_diff}
2411 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2412 $ctxm add command \
2413 -label {Select All} \
2414 -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
2415 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2416 $ctxm add command \
2417 -label {Copy All} \
2418 -command {
2419 $ui_diff tag add sel 0.0 end
2420 tk_textCopy $ui_diff
2421 $ui_diff tag remove sel 0.0 end
2423 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2424 $ctxm add separator
2425 $ctxm add command \
2426 -label {Apply/Reverse Hunk} \
2427 -command {apply_hunk $cursorX $cursorY}
2428 set ui_diff_applyhunk [$ctxm index last]
2429 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
2430 $ctxm add separator
2431 $ctxm add command \
2432 -label {Decrease Font Size} \
2433 -command {incr_font_size font_diff -1}
2434 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2435 $ctxm add command \
2436 -label {Increase Font Size} \
2437 -command {incr_font_size font_diff 1}
2438 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2439 $ctxm add separator
2440 $ctxm add command \
2441 -label {Show Less Context} \
2442 -command {if {$repo_config(gui.diffcontext) >= 1} {
2443 incr repo_config(gui.diffcontext) -1
2444 reshow_diff
2446 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2447 $ctxm add command \
2448 -label {Show More Context} \
2449 -command {if {$repo_config(gui.diffcontext) < 99} {
2450 incr repo_config(gui.diffcontext)
2451 reshow_diff
2453 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2454 $ctxm add separator
2455 $ctxm add command -label {Options...} \
2456 -command do_options
2457 proc popup_diff_menu {ctxm x y X Y} {
2458 global current_diff_path file_states
2459 set ::cursorX $x
2460 set ::cursorY $y
2461 if {$::ui_index eq $::current_diff_side} {
2462 set l "Unstage Hunk From Commit"
2463 } else {
2464 set l "Stage Hunk For Commit"
2466 if {$::is_3way_diff
2467 || $current_diff_path eq {}
2468 || ![info exists file_states($current_diff_path)]
2469 || {_O} eq [lindex $file_states($current_diff_path) 0]} {
2470 set s disabled
2471 } else {
2472 set s normal
2474 $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
2475 tk_popup $ctxm $X $Y
2477 bind_button3 $ui_diff [list popup_diff_menu $ctxm %x %y %X %Y]
2479 # -- Status Bar
2481 set main_status [::status_bar::new .status]
2482 pack .status -anchor w -side bottom -fill x
2483 $main_status show {Initializing...}
2485 # -- Load geometry
2487 catch {
2488 set gm $repo_config(gui.geometry)
2489 wm geometry . [lindex $gm 0]
2490 .vpane sash place 0 \
2491 [lindex [.vpane sash coord 0] 0] \
2492 [lindex $gm 1]
2493 .vpane.files sash place 0 \
2494 [lindex $gm 2] \
2495 [lindex [.vpane.files sash coord 0] 1]
2496 unset gm
2499 # -- Key Bindings
2501 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2502 bind $ui_comm <$M1B-Key-i> {do_add_all;break}
2503 bind $ui_comm <$M1B-Key-I> {do_add_all;break}
2504 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2505 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2506 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2507 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2508 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2509 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2510 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2511 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2513 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2514 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2515 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2516 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2517 bind $ui_diff <$M1B-Key-v> {break}
2518 bind $ui_diff <$M1B-Key-V> {break}
2519 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2520 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2521 bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break}
2522 bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break}
2523 bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break}
2524 bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break}
2525 bind $ui_diff <Key-k> {catch {%W yview scroll -1 units};break}
2526 bind $ui_diff <Key-j> {catch {%W yview scroll 1 units};break}
2527 bind $ui_diff <Key-h> {catch {%W xview scroll -1 units};break}
2528 bind $ui_diff <Key-l> {catch {%W xview scroll 1 units};break}
2529 bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
2530 bind $ui_diff <Control-Key-f> {catch {%W yview scroll 1 pages};break}
2531 bind $ui_diff <Button-1> {focus %W}
2533 if {[is_enabled branch]} {
2534 bind . <$M1B-Key-n> branch_create::dialog
2535 bind . <$M1B-Key-N> branch_create::dialog
2536 bind . <$M1B-Key-o> branch_checkout::dialog
2537 bind . <$M1B-Key-O> branch_checkout::dialog
2538 bind . <$M1B-Key-m> merge::dialog
2539 bind . <$M1B-Key-M> merge::dialog
2541 if {[is_enabled transport]} {
2542 bind . <$M1B-Key-p> do_push_anywhere
2543 bind . <$M1B-Key-P> do_push_anywhere
2546 bind . <Key-F5> do_rescan
2547 bind . <$M1B-Key-r> do_rescan
2548 bind . <$M1B-Key-R> do_rescan
2549 bind . <$M1B-Key-s> do_signoff
2550 bind . <$M1B-Key-S> do_signoff
2551 bind . <$M1B-Key-i> do_add_all
2552 bind . <$M1B-Key-I> do_add_all
2553 bind . <$M1B-Key-Return> do_commit
2554 foreach i [list $ui_index $ui_workdir] {
2555 bind $i <Button-1> "toggle_or_diff $i %x %y; break"
2556 bind $i <$M1B-Button-1> "add_one_to_selection $i %x %y; break"
2557 bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
2559 unset i
2561 set file_lists($ui_index) [list]
2562 set file_lists($ui_workdir) [list]
2564 wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
2565 focus -force $ui_comm
2567 # -- Warn the user about environmental problems. Cygwin's Tcl
2568 # does *not* pass its env array onto any processes it spawns.
2569 # This means that git processes get none of our environment.
2571 if {[is_Cygwin]} {
2572 set ignored_env 0
2573 set suggest_user {}
2574 set msg "Possible environment issues exist.
2576 The following environment variables are probably
2577 going to be ignored by any Git subprocess run
2578 by [appname]:
2581 foreach name [array names env] {
2582 switch -regexp -- $name {
2583 {^GIT_INDEX_FILE$} -
2584 {^GIT_OBJECT_DIRECTORY$} -
2585 {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
2586 {^GIT_DIFF_OPTS$} -
2587 {^GIT_EXTERNAL_DIFF$} -
2588 {^GIT_PAGER$} -
2589 {^GIT_TRACE$} -
2590 {^GIT_CONFIG$} -
2591 {^GIT_CONFIG_LOCAL$} -
2592 {^GIT_(AUTHOR|COMMITTER)_DATE$} {
2593 append msg " - $name\n"
2594 incr ignored_env
2596 {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
2597 append msg " - $name\n"
2598 incr ignored_env
2599 set suggest_user $name
2603 if {$ignored_env > 0} {
2604 append msg "
2605 This is due to a known issue with the
2606 Tcl binary distributed by Cygwin."
2608 if {$suggest_user ne {}} {
2609 append msg "
2611 A good replacement for $suggest_user
2612 is placing values for the user.name and
2613 user.email settings into your personal
2614 ~/.gitconfig file.
2617 warn_popup $msg
2619 unset ignored_env msg suggest_user name
2622 # -- Only initialize complex UI if we are going to stay running.
2624 if {[is_enabled transport]} {
2625 load_all_remotes
2627 populate_fetch_menu
2628 populate_push_menu
2631 if {[winfo exists $ui_comm]} {
2632 set GITGUI_BCK_exists [load_message GITGUI_BCK]
2634 # -- If both our backup and message files exist use the
2635 # newer of the two files to initialize the buffer.
2637 if {$GITGUI_BCK_exists} {
2638 set m [gitdir GITGUI_MSG]
2639 if {[file isfile $m]} {
2640 if {[file mtime [gitdir GITGUI_BCK]] > [file mtime $m]} {
2641 catch {file delete [gitdir GITGUI_MSG]}
2642 } else {
2643 $ui_comm delete 0.0 end
2644 $ui_comm edit reset
2645 $ui_comm edit modified false
2646 catch {file delete [gitdir GITGUI_BCK]}
2647 set GITGUI_BCK_exists 0
2650 unset m
2653 proc backup_commit_buffer {} {
2654 global ui_comm GITGUI_BCK_exists
2656 set m [$ui_comm edit modified]
2657 if {$m || $GITGUI_BCK_exists} {
2658 set msg [string trim [$ui_comm get 0.0 end]]
2659 regsub -all -line {[ \r\t]+$} $msg {} msg
2661 if {$msg eq {}} {
2662 if {$GITGUI_BCK_exists} {
2663 catch {file delete [gitdir GITGUI_BCK]}
2664 set GITGUI_BCK_exists 0
2666 } elseif {$m} {
2667 catch {
2668 set fd [open [gitdir GITGUI_BCK] w]
2669 puts -nonewline $fd $msg
2670 close $fd
2671 set GITGUI_BCK_exists 1
2675 $ui_comm edit modified false
2678 set ::GITGUI_BCK_i [after 2000 backup_commit_buffer]
2681 backup_commit_buffer
2684 lock_index begin-read
2685 if {![winfo ismapped .]} {
2686 wm deiconify .
2688 after 1 do_rescan
2689 if {[is_enabled multicommit]} {
2690 after 1000 hint_gc