git-gui: Skip -dirty suffix on core git versions
[git/builtin-gsoc.git] / git-gui.sh
blobbdb557184ea41616ff85e93d75762db2e2446dbb
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 ######################################################################
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]
56 rename source real__source
57 proc source {name} {
58 puts stderr "source $name"
59 uplevel 1 real__source $name
63 ######################################################################
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]
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
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
93 } else {
94 set idx {}
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
105 unset loaded p
106 } else {
107 set auto_path [concat [list $oguilib] $auto_path]
109 unset -nocomplain oguirel idx fd
111 ######################################################################
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
127 proc gitdir {args} {
128 global _gitdir
129 if {$args eq {}} {
130 return $_gitdir
132 return [eval [list file join $_gitdir] $args]
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"
141 if {[is_Cygwin]} {
142 set _gitexec [exec cygpath \
143 --windows \
144 --absolute \
145 $_gitexec]
146 } else {
147 set _gitexec [file normalize $_gitexec]
150 if {$args eq {}} {
151 return $_gitexec
153 return [eval [list file join $_gitexec] $args]
156 proc reponame {} {
157 global _reponame
158 return $_reponame
161 proc is_MacOSX {} {
162 global tcl_platform tk_library
163 if {[tk windowingsystem] eq {aqua}} {
164 return 1
166 return 0
169 proc is_Windows {} {
170 global tcl_platform
171 if {$tcl_platform(platform) eq {windows}} {
172 return 1
174 return 0
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
186 } else {
187 set _iscygwin 0
190 return $_iscygwin
193 proc is_enabled {option} {
194 global enabled_options
195 if {[catch {set on $enabled_options($option)}]} {return 0}
196 return $on
199 proc enable_option {option} {
200 global enabled_options
201 set enabled_options($option) 1
204 proc disable_option {option} {
205 global enabled_options
206 set enabled_options($option) 0
209 ######################################################################
211 ## config
213 proc is_many_config {name} {
214 switch -glob -- $name {
215 remote.*.fetch -
216 remote.*.push
217 {return 1}
219 {return 0}
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
234 proc get_config {name} {
235 global repo_config
236 if {[catch {set v $repo_config($name)}]} {
237 return {}
238 } else {
239 return $v
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
259 close $fd_rc
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
275 close $fd_rc
278 foreach name [array names default_config] {
279 if {[catch {set v $global_config($name)}]} {
280 set global_config($name) $default_config($name)
282 if {[catch {set v $repo_config($name)}]} {
283 set repo_config($name) $default_config($name)
288 ######################################################################
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] }
302 set p [gitexec git-$name$::_search_exe]
303 if {[file exists $p]} {
304 set v [list $p]
305 } elseif {[is_Windows] && [file exists [gitexec git-$name]]} {
306 # Try to determine what sort of magic will make
307 # git-$name go and do its thing, because native
308 # Tcl on Windows doesn't know it.
310 set p [gitexec git-$name]
311 set f [open $p r]
312 set s [gets $f]
313 close $f
315 switch -glob -- $s {
316 #!*sh { set i sh }
317 #!*perl { set i perl }
318 #!*python { set i python }
319 default { error "git-$name is not supported: $s" }
322 upvar #0 _$i interp
323 if {![info exists interp]} {
324 set interp [_which $i]
326 if {$interp eq {}} {
327 error "git-$name requires $i (not in PATH)"
329 set v [list $interp $p]
330 } else {
331 # Assume it is builtin to git somehow and we
332 # aren't actually able to see a file for it.
334 set v [list $::_git $name]
336 set _git_cmd_path($name) $v
338 return $v
341 proc _which {what} {
342 global env _search_exe _search_path
344 if {$_search_path eq {}} {
345 if {[is_Cygwin]} {
346 set _search_path [split [exec cygpath \
347 --windows \
348 --path \
349 --absolute \
350 $env(PATH)] {;}]
351 set _search_exe .exe
352 } elseif {[is_Windows]} {
353 set _search_path [split $env(PATH) {;}]
354 set _search_exe .exe
355 } else {
356 set _search_path [split $env(PATH) :]
357 set _search_exe {}
361 foreach p $_search_path {
362 set p [file join $p $what$_search_exe]
363 if {[file exists $p]} {
364 return [file normalize $p]
367 return {}
370 proc git {args} {
371 set opt [list exec]
373 while {1} {
374 switch -- [lindex $args 0] {
375 --nice {
376 global _nice
377 if {$_nice ne {}} {
378 lappend opt $_nice
382 default {
383 break
388 set args [lrange $args 1 end]
391 set cmdp [_git_cmd [lindex $args 0]]
392 set args [lrange $args 1 end]
394 return [eval $opt $cmdp $args]
397 proc _open_stdout_stderr {cmd} {
398 if {[catch {
399 set fd [open $cmd r]
400 } err]} {
401 if { [lindex $cmd end] eq {2>@1}
402 && $err eq {can not find channel named "1"}
404 # Older versions of Tcl 8.4 don't have this 2>@1 IO
405 # redirect operator. Fallback to |& cat for those.
406 # The command was not actually started, so its safe
407 # to try to start it a second time.
409 set fd [open [concat \
410 [lrange $cmd 0 end-1] \
411 [list |& cat] \
412 ] r]
413 } else {
414 error $err
417 return $fd
420 proc git_read {args} {
421 set opt [list |]
423 while {1} {
424 switch -- [lindex $args 0] {
425 --nice {
426 global _nice
427 if {$_nice ne {}} {
428 lappend opt $_nice
432 --stderr {
433 lappend args 2>@1
436 default {
437 break
442 set args [lrange $args 1 end]
445 set cmdp [_git_cmd [lindex $args 0]]
446 set args [lrange $args 1 end]
448 return [_open_stdout_stderr [concat $opt $cmdp $args]]
451 proc git_write {args} {
452 set opt [list |]
454 while {1} {
455 switch -- [lindex $args 0] {
456 --nice {
457 global _nice
458 if {$_nice ne {}} {
459 lappend opt $_nice
463 default {
464 break
469 set args [lrange $args 1 end]
472 set cmdp [_git_cmd [lindex $args 0]]
473 set args [lrange $args 1 end]
475 return [open [concat $opt $cmdp $args] w]
478 proc sq {value} {
479 regsub -all ' $value "'\\''" value
480 return "'$value'"
483 proc load_current_branch {} {
484 global current_branch is_detached
486 set fd [open [gitdir HEAD] r]
487 if {[gets $fd ref] < 1} {
488 set ref {}
490 close $fd
492 set pfx {ref: refs/heads/}
493 set len [string length $pfx]
494 if {[string equal -length $len $pfx $ref]} {
495 # We're on a branch. It might not exist. But
496 # HEAD looks good enough to be a branch.
498 set current_branch [string range $ref $len end]
499 set is_detached 0
500 } else {
501 # Assume this is a detached head.
503 set current_branch HEAD
504 set is_detached 1
508 auto_load tk_optionMenu
509 rename tk_optionMenu real__tkOptionMenu
510 proc tk_optionMenu {w varName args} {
511 set m [eval real__tkOptionMenu $w $varName $args]
512 $m configure -font font_ui
513 $w configure -font font_ui
514 return $m
517 ######################################################################
519 ## find git
521 set _git [_which git]
522 if {$_git eq {}} {
523 catch {wm withdraw .}
524 error_popup "Cannot find git in PATH."
525 exit 1
527 set _nice [_which nice]
529 ######################################################################
531 ## version check
533 if {[catch {set _git_version [git --version]} err]} {
534 catch {wm withdraw .}
535 error_popup "Cannot determine Git version:
537 $err
539 [appname] requires Git 1.5.0 or later."
540 exit 1
542 if {![regsub {^git version } $_git_version {} _git_version]} {
543 catch {wm withdraw .}
544 error_popup "Cannot parse Git version string:\n\n$_git_version"
545 exit 1
547 regsub {-dirty$} $_git_version {} _git_version
548 regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version
549 regsub {\.rc[0-9]+$} $_git_version {} _git_version
551 proc git-version {args} {
552 global _git_version
554 switch [llength $args] {
556 return $_git_version
560 set op [lindex $args 0]
561 set vr [lindex $args 1]
562 set cm [package vcompare $_git_version $vr]
563 return [expr $cm $op 0]
567 set type [lindex $args 0]
568 set name [lindex $args 1]
569 set parm [lindex $args 2]
570 set body [lindex $args 3]
572 if {($type ne {proc} && $type ne {method})} {
573 error "Invalid arguments to git-version"
575 if {[llength $body] < 2 || [lindex $body end-1] ne {default}} {
576 error "Last arm of $type $name must be default"
579 foreach {op vr cb} [lrange $body 0 end-2] {
580 if {[git-version $op $vr]} {
581 return [uplevel [list $type $name $parm $cb]]
585 return [uplevel [list $type $name $parm [lindex $body end]]]
588 default {
589 error "git-version >= x"
595 if {[git-version < 1.5]} {
596 catch {wm withdraw .}
597 error_popup "[appname] requires Git 1.5.0 or later.
599 You are using [git-version]:
601 [git --version]"
602 exit 1
605 ######################################################################
607 ## repository setup
609 if {[catch {
610 set _gitdir $env(GIT_DIR)
611 set _prefix {}
613 && [catch {
614 set _gitdir [git rev-parse --git-dir]
615 set _prefix [git rev-parse --show-prefix]
616 } err]} {
617 catch {wm withdraw .}
618 error_popup "Cannot find the git directory:\n\n$err"
619 exit 1
621 if {![file isdirectory $_gitdir] && [is_Cygwin]} {
622 catch {set _gitdir [exec cygpath --unix $_gitdir]}
624 if {![file isdirectory $_gitdir]} {
625 catch {wm withdraw .}
626 error_popup "Git directory not found:\n\n$_gitdir"
627 exit 1
629 if {[lindex [file split $_gitdir] end] ne {.git}} {
630 catch {wm withdraw .}
631 error_popup "Cannot use funny .git directory:\n\n$_gitdir"
632 exit 1
634 if {[catch {cd [file dirname $_gitdir]} err]} {
635 catch {wm withdraw .}
636 error_popup "No working directory [file dirname $_gitdir]:\n\n$err"
637 exit 1
639 set _reponame [lindex [file split \
640 [file normalize [file dirname $_gitdir]]] \
641 end]
643 ######################################################################
645 ## global init
647 set current_diff_path {}
648 set current_diff_side {}
649 set diff_actions [list]
651 set HEAD {}
652 set PARENT {}
653 set MERGE_HEAD [list]
654 set commit_type {}
655 set empty_tree {}
656 set current_branch {}
657 set is_detached 0
658 set current_diff_path {}
659 set selected_commit_type new
661 ######################################################################
663 ## task management
665 set rescan_active 0
666 set diff_active 0
667 set last_clicked {}
669 set disable_on_lock [list]
670 set index_lock_type none
672 proc lock_index {type} {
673 global index_lock_type disable_on_lock
675 if {$index_lock_type eq {none}} {
676 set index_lock_type $type
677 foreach w $disable_on_lock {
678 uplevel #0 $w disabled
680 return 1
681 } elseif {$index_lock_type eq "begin-$type"} {
682 set index_lock_type $type
683 return 1
685 return 0
688 proc unlock_index {} {
689 global index_lock_type disable_on_lock
691 set index_lock_type none
692 foreach w $disable_on_lock {
693 uplevel #0 $w normal
697 ######################################################################
699 ## status
701 proc repository_state {ctvar hdvar mhvar} {
702 global current_branch
703 upvar $ctvar ct $hdvar hd $mhvar mh
705 set mh [list]
707 load_current_branch
708 if {[catch {set hd [git rev-parse --verify HEAD]}]} {
709 set hd {}
710 set ct initial
711 return
714 set merge_head [gitdir MERGE_HEAD]
715 if {[file exists $merge_head]} {
716 set ct merge
717 set fd_mh [open $merge_head r]
718 while {[gets $fd_mh line] >= 0} {
719 lappend mh $line
721 close $fd_mh
722 return
725 set ct normal
728 proc PARENT {} {
729 global PARENT empty_tree
731 set p [lindex $PARENT 0]
732 if {$p ne {}} {
733 return $p
735 if {$empty_tree eq {}} {
736 set empty_tree [git mktree << {}]
738 return $empty_tree
741 proc rescan {after {honor_trustmtime 1}} {
742 global HEAD PARENT MERGE_HEAD commit_type
743 global ui_index ui_workdir ui_comm
744 global rescan_active file_states
745 global repo_config
747 if {$rescan_active > 0 || ![lock_index read]} return
749 repository_state newType newHEAD newMERGE_HEAD
750 if {[string match amend* $commit_type]
751 && $newType eq {normal}
752 && $newHEAD eq $HEAD} {
753 } else {
754 set HEAD $newHEAD
755 set PARENT $newHEAD
756 set MERGE_HEAD $newMERGE_HEAD
757 set commit_type $newType
760 array unset file_states
762 if {![$ui_comm edit modified]
763 || [string trim [$ui_comm get 0.0 end]] eq {}} {
764 if {[string match amend* $commit_type]} {
765 } elseif {[load_message GITGUI_MSG]} {
766 } elseif {[load_message MERGE_MSG]} {
767 } elseif {[load_message SQUASH_MSG]} {
769 $ui_comm edit reset
770 $ui_comm edit modified false
773 if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
774 rescan_stage2 {} $after
775 } else {
776 set rescan_active 1
777 ui_status {Refreshing file status...}
778 set fd_rf [git_read update-index \
779 -q \
780 --unmerged \
781 --ignore-missing \
782 --refresh \
784 fconfigure $fd_rf -blocking 0 -translation binary
785 fileevent $fd_rf readable \
786 [list rescan_stage2 $fd_rf $after]
790 proc rescan_stage2 {fd after} {
791 global rescan_active buf_rdi buf_rdf buf_rlo
793 if {$fd ne {}} {
794 read $fd
795 if {![eof $fd]} return
796 close $fd
799 set ls_others [list --exclude-per-directory=.gitignore]
800 set info_exclude [gitdir info exclude]
801 if {[file readable $info_exclude]} {
802 lappend ls_others "--exclude-from=$info_exclude"
805 set buf_rdi {}
806 set buf_rdf {}
807 set buf_rlo {}
809 set rescan_active 3
810 ui_status {Scanning for modified files ...}
811 set fd_di [git_read diff-index --cached -z [PARENT]]
812 set fd_df [git_read diff-files -z]
813 set fd_lo [eval git_read ls-files --others -z $ls_others]
815 fconfigure $fd_di -blocking 0 -translation binary -encoding binary
816 fconfigure $fd_df -blocking 0 -translation binary -encoding binary
817 fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
818 fileevent $fd_di readable [list read_diff_index $fd_di $after]
819 fileevent $fd_df readable [list read_diff_files $fd_df $after]
820 fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
823 proc load_message {file} {
824 global ui_comm
826 set f [gitdir $file]
827 if {[file isfile $f]} {
828 if {[catch {set fd [open $f r]}]} {
829 return 0
831 set content [string trim [read $fd]]
832 close $fd
833 regsub -all -line {[ \r\t]+$} $content {} content
834 $ui_comm delete 0.0 end
835 $ui_comm insert end $content
836 return 1
838 return 0
841 proc read_diff_index {fd after} {
842 global buf_rdi
844 append buf_rdi [read $fd]
845 set c 0
846 set n [string length $buf_rdi]
847 while {$c < $n} {
848 set z1 [string first "\0" $buf_rdi $c]
849 if {$z1 == -1} break
850 incr z1
851 set z2 [string first "\0" $buf_rdi $z1]
852 if {$z2 == -1} break
854 incr c
855 set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
856 set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
857 merge_state \
858 [encoding convertfrom $p] \
859 [lindex $i 4]? \
860 [list [lindex $i 0] [lindex $i 2]] \
861 [list]
862 set c $z2
863 incr c
865 if {$c < $n} {
866 set buf_rdi [string range $buf_rdi $c end]
867 } else {
868 set buf_rdi {}
871 rescan_done $fd buf_rdi $after
874 proc read_diff_files {fd after} {
875 global buf_rdf
877 append buf_rdf [read $fd]
878 set c 0
879 set n [string length $buf_rdf]
880 while {$c < $n} {
881 set z1 [string first "\0" $buf_rdf $c]
882 if {$z1 == -1} break
883 incr z1
884 set z2 [string first "\0" $buf_rdf $z1]
885 if {$z2 == -1} break
887 incr c
888 set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
889 set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
890 merge_state \
891 [encoding convertfrom $p] \
892 ?[lindex $i 4] \
893 [list] \
894 [list [lindex $i 0] [lindex $i 2]]
895 set c $z2
896 incr c
898 if {$c < $n} {
899 set buf_rdf [string range $buf_rdf $c end]
900 } else {
901 set buf_rdf {}
904 rescan_done $fd buf_rdf $after
907 proc read_ls_others {fd after} {
908 global buf_rlo
910 append buf_rlo [read $fd]
911 set pck [split $buf_rlo "\0"]
912 set buf_rlo [lindex $pck end]
913 foreach p [lrange $pck 0 end-1] {
914 merge_state [encoding convertfrom $p] ?O
916 rescan_done $fd buf_rlo $after
919 proc rescan_done {fd buf after} {
920 global rescan_active current_diff_path
921 global file_states repo_config
922 upvar $buf to_clear
924 if {![eof $fd]} return
925 set to_clear {}
926 close $fd
927 if {[incr rescan_active -1] > 0} return
929 prune_selection
930 unlock_index
931 display_all_files
932 if {$current_diff_path ne {}} reshow_diff
933 uplevel #0 $after
936 proc prune_selection {} {
937 global file_states selected_paths
939 foreach path [array names selected_paths] {
940 if {[catch {set still_here $file_states($path)}]} {
941 unset selected_paths($path)
946 ######################################################################
948 ## ui helpers
950 proc mapicon {w state path} {
951 global all_icons
953 if {[catch {set r $all_icons($state$w)}]} {
954 puts "error: no icon for $w state={$state} $path"
955 return file_plain
957 return $r
960 proc mapdesc {state path} {
961 global all_descs
963 if {[catch {set r $all_descs($state)}]} {
964 puts "error: no desc for state={$state} $path"
965 return $state
967 return $r
970 proc ui_status {msg} {
971 $::main_status show $msg
974 proc ui_ready {{test {}}} {
975 $::main_status show {Ready.} $test
978 proc escape_path {path} {
979 regsub -all {\\} $path "\\\\" path
980 regsub -all "\n" $path "\\n" path
981 return $path
984 proc short_path {path} {
985 return [escape_path [lindex [file split $path] end]]
988 set next_icon_id 0
989 set null_sha1 [string repeat 0 40]
991 proc merge_state {path new_state {head_info {}} {index_info {}}} {
992 global file_states next_icon_id null_sha1
994 set s0 [string index $new_state 0]
995 set s1 [string index $new_state 1]
997 if {[catch {set info $file_states($path)}]} {
998 set state __
999 set icon n[incr next_icon_id]
1000 } else {
1001 set state [lindex $info 0]
1002 set icon [lindex $info 1]
1003 if {$head_info eq {}} {set head_info [lindex $info 2]}
1004 if {$index_info eq {}} {set index_info [lindex $info 3]}
1007 if {$s0 eq {?}} {set s0 [string index $state 0]} \
1008 elseif {$s0 eq {_}} {set s0 _}
1010 if {$s1 eq {?}} {set s1 [string index $state 1]} \
1011 elseif {$s1 eq {_}} {set s1 _}
1013 if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
1014 set head_info [list 0 $null_sha1]
1015 } elseif {$s0 ne {_} && [string index $state 0] eq {_}
1016 && $head_info eq {}} {
1017 set head_info $index_info
1020 set file_states($path) [list $s0$s1 $icon \
1021 $head_info $index_info \
1023 return $state
1026 proc display_file_helper {w path icon_name old_m new_m} {
1027 global file_lists
1029 if {$new_m eq {_}} {
1030 set lno [lsearch -sorted -exact $file_lists($w) $path]
1031 if {$lno >= 0} {
1032 set file_lists($w) [lreplace $file_lists($w) $lno $lno]
1033 incr lno
1034 $w conf -state normal
1035 $w delete $lno.0 [expr {$lno + 1}].0
1036 $w conf -state disabled
1038 } elseif {$old_m eq {_} && $new_m ne {_}} {
1039 lappend file_lists($w) $path
1040 set file_lists($w) [lsort -unique $file_lists($w)]
1041 set lno [lsearch -sorted -exact $file_lists($w) $path]
1042 incr lno
1043 $w conf -state normal
1044 $w image create $lno.0 \
1045 -align center -padx 5 -pady 1 \
1046 -name $icon_name \
1047 -image [mapicon $w $new_m $path]
1048 $w insert $lno.1 "[escape_path $path]\n"
1049 $w conf -state disabled
1050 } elseif {$old_m ne $new_m} {
1051 $w conf -state normal
1052 $w image conf $icon_name -image [mapicon $w $new_m $path]
1053 $w conf -state disabled
1057 proc display_file {path state} {
1058 global file_states selected_paths
1059 global ui_index ui_workdir
1061 set old_m [merge_state $path $state]
1062 set s $file_states($path)
1063 set new_m [lindex $s 0]
1064 set icon_name [lindex $s 1]
1066 set o [string index $old_m 0]
1067 set n [string index $new_m 0]
1068 if {$o eq {U}} {
1069 set o _
1071 if {$n eq {U}} {
1072 set n _
1074 display_file_helper $ui_index $path $icon_name $o $n
1076 if {[string index $old_m 0] eq {U}} {
1077 set o U
1078 } else {
1079 set o [string index $old_m 1]
1081 if {[string index $new_m 0] eq {U}} {
1082 set n U
1083 } else {
1084 set n [string index $new_m 1]
1086 display_file_helper $ui_workdir $path $icon_name $o $n
1088 if {$new_m eq {__}} {
1089 unset file_states($path)
1090 catch {unset selected_paths($path)}
1094 proc display_all_files_helper {w path icon_name m} {
1095 global file_lists
1097 lappend file_lists($w) $path
1098 set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
1099 $w image create end \
1100 -align center -padx 5 -pady 1 \
1101 -name $icon_name \
1102 -image [mapicon $w $m $path]
1103 $w insert end "[escape_path $path]\n"
1106 proc display_all_files {} {
1107 global ui_index ui_workdir
1108 global file_states file_lists
1109 global last_clicked
1111 $ui_index conf -state normal
1112 $ui_workdir conf -state normal
1114 $ui_index delete 0.0 end
1115 $ui_workdir delete 0.0 end
1116 set last_clicked {}
1118 set file_lists($ui_index) [list]
1119 set file_lists($ui_workdir) [list]
1121 foreach path [lsort [array names file_states]] {
1122 set s $file_states($path)
1123 set m [lindex $s 0]
1124 set icon_name [lindex $s 1]
1126 set s [string index $m 0]
1127 if {$s ne {U} && $s ne {_}} {
1128 display_all_files_helper $ui_index $path \
1129 $icon_name $s
1132 if {[string index $m 0] eq {U}} {
1133 set s U
1134 } else {
1135 set s [string index $m 1]
1137 if {$s ne {_}} {
1138 display_all_files_helper $ui_workdir $path \
1139 $icon_name $s
1143 $ui_index conf -state disabled
1144 $ui_workdir conf -state disabled
1147 ######################################################################
1149 ## icons
1151 set filemask {
1152 #define mask_width 14
1153 #define mask_height 15
1154 static unsigned char mask_bits[] = {
1155 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1156 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1157 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1160 image create bitmap file_plain -background white -foreground black -data {
1161 #define plain_width 14
1162 #define plain_height 15
1163 static unsigned char plain_bits[] = {
1164 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1165 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1166 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1167 } -maskdata $filemask
1169 image create bitmap file_mod -background white -foreground blue -data {
1170 #define mod_width 14
1171 #define mod_height 15
1172 static unsigned char mod_bits[] = {
1173 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1174 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1175 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1176 } -maskdata $filemask
1178 image create bitmap file_fulltick -background white -foreground "#007000" -data {
1179 #define file_fulltick_width 14
1180 #define file_fulltick_height 15
1181 static unsigned char file_fulltick_bits[] = {
1182 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1183 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1184 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1185 } -maskdata $filemask
1187 image create bitmap file_parttick -background white -foreground "#005050" -data {
1188 #define parttick_width 14
1189 #define parttick_height 15
1190 static unsigned char parttick_bits[] = {
1191 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1192 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1193 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1194 } -maskdata $filemask
1196 image create bitmap file_question -background white -foreground black -data {
1197 #define file_question_width 14
1198 #define file_question_height 15
1199 static unsigned char file_question_bits[] = {
1200 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1201 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1202 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1203 } -maskdata $filemask
1205 image create bitmap file_removed -background white -foreground red -data {
1206 #define file_removed_width 14
1207 #define file_removed_height 15
1208 static unsigned char file_removed_bits[] = {
1209 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1210 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1211 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1212 } -maskdata $filemask
1214 image create bitmap file_merge -background white -foreground blue -data {
1215 #define file_merge_width 14
1216 #define file_merge_height 15
1217 static unsigned char file_merge_bits[] = {
1218 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1219 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1220 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1221 } -maskdata $filemask
1223 set file_dir_data {
1224 #define file_width 18
1225 #define file_height 18
1226 static unsigned char file_bits[] = {
1227 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00,
1228 0x0c, 0x03, 0x00, 0x04, 0xfe, 0x00, 0x06, 0x80, 0x00, 0xff, 0x9f, 0x00,
1229 0x03, 0x98, 0x00, 0x02, 0x90, 0x00, 0x06, 0xb0, 0x00, 0x04, 0xa0, 0x00,
1230 0x0c, 0xe0, 0x00, 0x08, 0xc0, 0x00, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00,
1231 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
1233 image create bitmap file_dir -background white -foreground blue \
1234 -data $file_dir_data -maskdata $file_dir_data
1235 unset file_dir_data
1237 set file_uplevel_data {
1238 #define up_width 15
1239 #define up_height 15
1240 static unsigned char up_bits[] = {
1241 0x80, 0x00, 0xc0, 0x01, 0xe0, 0x03, 0xf0, 0x07, 0xf8, 0x0f, 0xfc, 0x1f,
1242 0xfe, 0x3f, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01,
1243 0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00};
1245 image create bitmap file_uplevel -background white -foreground red \
1246 -data $file_uplevel_data -maskdata $file_uplevel_data
1247 unset file_uplevel_data
1249 set ui_index .vpane.files.index.list
1250 set ui_workdir .vpane.files.workdir.list
1252 set all_icons(_$ui_index) file_plain
1253 set all_icons(A$ui_index) file_fulltick
1254 set all_icons(M$ui_index) file_fulltick
1255 set all_icons(D$ui_index) file_removed
1256 set all_icons(U$ui_index) file_merge
1258 set all_icons(_$ui_workdir) file_plain
1259 set all_icons(M$ui_workdir) file_mod
1260 set all_icons(D$ui_workdir) file_question
1261 set all_icons(U$ui_workdir) file_merge
1262 set all_icons(O$ui_workdir) file_plain
1264 set max_status_desc 0
1265 foreach i {
1266 {__ "Unmodified"}
1268 {_M "Modified, not staged"}
1269 {M_ "Staged for commit"}
1270 {MM "Portions staged for commit"}
1271 {MD "Staged for commit, missing"}
1273 {_O "Untracked, not staged"}
1274 {A_ "Staged for commit"}
1275 {AM "Portions staged for commit"}
1276 {AD "Staged for commit, missing"}
1278 {_D "Missing"}
1279 {D_ "Staged for removal"}
1280 {DO "Staged for removal, still present"}
1282 {U_ "Requires merge resolution"}
1283 {UU "Requires merge resolution"}
1284 {UM "Requires merge resolution"}
1285 {UD "Requires merge resolution"}
1287 if {$max_status_desc < [string length [lindex $i 1]]} {
1288 set max_status_desc [string length [lindex $i 1]]
1290 set all_descs([lindex $i 0]) [lindex $i 1]
1292 unset i
1294 ######################################################################
1296 ## util
1298 proc bind_button3 {w cmd} {
1299 bind $w <Any-Button-3> $cmd
1300 if {[is_MacOSX]} {
1301 bind $w <Control-Button-1> $cmd
1305 proc scrollbar2many {list mode args} {
1306 foreach w $list {eval $w $mode $args}
1309 proc many2scrollbar {list mode sb top bottom} {
1310 $sb set $top $bottom
1311 foreach w $list {$w $mode moveto $top}
1314 proc incr_font_size {font {amt 1}} {
1315 set sz [font configure $font -size]
1316 incr sz $amt
1317 font configure $font -size $sz
1318 font configure ${font}bold -size $sz
1319 font configure ${font}italic -size $sz
1322 ######################################################################
1324 ## ui commands
1326 set starting_gitk_msg {Starting gitk... please wait...}
1328 proc do_gitk {revs} {
1329 # -- Always start gitk through whatever we were loaded with. This
1330 # lets us bypass using shell process on Windows systems.
1332 set exe [file join [file dirname $::_git] gitk]
1333 set cmd [list [info nameofexecutable] $exe]
1334 if {! [file exists $exe]} {
1335 error_popup "Unable to start gitk:\n\n$exe does not exist"
1336 } else {
1337 eval exec $cmd $revs &
1338 ui_status $::starting_gitk_msg
1339 after 10000 {
1340 ui_ready $starting_gitk_msg
1345 set is_quitting 0
1347 proc do_quit {} {
1348 global ui_comm is_quitting repo_config commit_type
1350 if {$is_quitting} return
1351 set is_quitting 1
1353 if {[winfo exists $ui_comm]} {
1354 # -- Stash our current commit buffer.
1356 set save [gitdir GITGUI_MSG]
1357 set msg [string trim [$ui_comm get 0.0 end]]
1358 regsub -all -line {[ \r\t]+$} $msg {} msg
1359 if {(![string match amend* $commit_type]
1360 || [$ui_comm edit modified])
1361 && $msg ne {}} {
1362 catch {
1363 set fd [open $save w]
1364 puts -nonewline $fd $msg
1365 close $fd
1367 } else {
1368 catch {file delete $save}
1371 # -- Stash our current window geometry into this repository.
1373 set cfg_geometry [list]
1374 lappend cfg_geometry [wm geometry .]
1375 lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
1376 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
1377 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1378 set rc_geometry {}
1380 if {$cfg_geometry ne $rc_geometry} {
1381 catch {git config gui.geometry $cfg_geometry}
1385 destroy .
1388 proc do_rescan {} {
1389 rescan ui_ready
1392 proc do_commit {} {
1393 commit_tree
1396 proc toggle_or_diff {w x y} {
1397 global file_states file_lists current_diff_path ui_index ui_workdir
1398 global last_clicked selected_paths
1400 set pos [split [$w index @$x,$y] .]
1401 set lno [lindex $pos 0]
1402 set col [lindex $pos 1]
1403 set path [lindex $file_lists($w) [expr {$lno - 1}]]
1404 if {$path eq {}} {
1405 set last_clicked {}
1406 return
1409 set last_clicked [list $w $lno]
1410 array unset selected_paths
1411 $ui_index tag remove in_sel 0.0 end
1412 $ui_workdir tag remove in_sel 0.0 end
1414 if {$col == 0} {
1415 if {$current_diff_path eq $path} {
1416 set after {reshow_diff;}
1417 } else {
1418 set after {}
1420 if {$w eq $ui_index} {
1421 update_indexinfo \
1422 "Unstaging [short_path $path] from commit" \
1423 [list $path] \
1424 [concat $after [list ui_ready]]
1425 } elseif {$w eq $ui_workdir} {
1426 update_index \
1427 "Adding [short_path $path]" \
1428 [list $path] \
1429 [concat $after [list ui_ready]]
1431 } else {
1432 show_diff $path $w $lno
1436 proc add_one_to_selection {w x y} {
1437 global file_lists last_clicked selected_paths
1439 set lno [lindex [split [$w index @$x,$y] .] 0]
1440 set path [lindex $file_lists($w) [expr {$lno - 1}]]
1441 if {$path eq {}} {
1442 set last_clicked {}
1443 return
1446 if {$last_clicked ne {}
1447 && [lindex $last_clicked 0] ne $w} {
1448 array unset selected_paths
1449 [lindex $last_clicked 0] tag remove in_sel 0.0 end
1452 set last_clicked [list $w $lno]
1453 if {[catch {set in_sel $selected_paths($path)}]} {
1454 set in_sel 0
1456 if {$in_sel} {
1457 unset selected_paths($path)
1458 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
1459 } else {
1460 set selected_paths($path) 1
1461 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
1465 proc add_range_to_selection {w x y} {
1466 global file_lists last_clicked selected_paths
1468 if {[lindex $last_clicked 0] ne $w} {
1469 toggle_or_diff $w $x $y
1470 return
1473 set lno [lindex [split [$w index @$x,$y] .] 0]
1474 set lc [lindex $last_clicked 1]
1475 if {$lc < $lno} {
1476 set begin $lc
1477 set end $lno
1478 } else {
1479 set begin $lno
1480 set end $lc
1483 foreach path [lrange $file_lists($w) \
1484 [expr {$begin - 1}] \
1485 [expr {$end - 1}]] {
1486 set selected_paths($path) 1
1488 $w tag add in_sel $begin.0 [expr {$end + 1}].0
1491 ######################################################################
1493 ## config defaults
1495 set cursor_ptr arrow
1496 font create font_diff -family Courier -size 10
1497 font create font_ui
1498 catch {
1499 label .dummy
1500 eval font configure font_ui [font actual [.dummy cget -font]]
1501 destroy .dummy
1504 font create font_uiitalic
1505 font create font_uibold
1506 font create font_diffbold
1507 font create font_diffitalic
1509 foreach class {Button Checkbutton Entry Label
1510 Labelframe Listbox Menu Message
1511 Radiobutton Spinbox Text} {
1512 option add *$class.font font_ui
1514 unset class
1516 if {[is_Windows] || [is_MacOSX]} {
1517 option add *Menu.tearOff 0
1520 if {[is_MacOSX]} {
1521 set M1B M1
1522 set M1T Cmd
1523 } else {
1524 set M1B Control
1525 set M1T Ctrl
1528 proc apply_config {} {
1529 global repo_config font_descs
1531 foreach option $font_descs {
1532 set name [lindex $option 0]
1533 set font [lindex $option 1]
1534 if {[catch {
1535 foreach {cn cv} $repo_config(gui.$name) {
1536 font configure $font $cn $cv
1538 } err]} {
1539 error_popup "Invalid font specified in gui.$name:\n\n$err"
1541 foreach {cn cv} [font configure $font] {
1542 font configure ${font}bold $cn $cv
1543 font configure ${font}italic $cn $cv
1545 font configure ${font}bold -weight bold
1546 font configure ${font}italic -slant italic
1550 set default_config(merge.diffstat) true
1551 set default_config(merge.summary) false
1552 set default_config(merge.verbosity) 2
1553 set default_config(user.name) {}
1554 set default_config(user.email) {}
1556 set default_config(gui.matchtrackingbranch) false
1557 set default_config(gui.pruneduringfetch) false
1558 set default_config(gui.trustmtime) false
1559 set default_config(gui.diffcontext) 5
1560 set default_config(gui.newbranchtemplate) {}
1561 set default_config(gui.fontui) [font configure font_ui]
1562 set default_config(gui.fontdiff) [font configure font_diff]
1563 set font_descs {
1564 {fontui font_ui {Main Font}}
1565 {fontdiff font_diff {Diff/Console Font}}
1567 load_config 0
1568 apply_config
1570 ######################################################################
1572 ## feature option selection
1574 if {[regexp {^git-(.+)$} [appname] _junk subcommand]} {
1575 unset _junk
1576 } else {
1577 set subcommand gui
1579 if {$subcommand eq {gui.sh}} {
1580 set subcommand gui
1582 if {$subcommand eq {gui} && [llength $argv] > 0} {
1583 set subcommand [lindex $argv 0]
1584 set argv [lrange $argv 1 end]
1587 enable_option multicommit
1588 enable_option branch
1589 enable_option transport
1591 switch -- $subcommand {
1592 browser -
1593 blame {
1594 disable_option multicommit
1595 disable_option branch
1596 disable_option transport
1598 citool {
1599 enable_option singlecommit
1601 disable_option multicommit
1602 disable_option branch
1603 disable_option transport
1607 ######################################################################
1609 ## ui construction
1611 set ui_comm {}
1613 # -- Menu Bar
1615 menu .mbar -tearoff 0
1616 .mbar add cascade -label Repository -menu .mbar.repository
1617 .mbar add cascade -label Edit -menu .mbar.edit
1618 if {[is_enabled branch]} {
1619 .mbar add cascade -label Branch -menu .mbar.branch
1621 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1622 .mbar add cascade -label Commit -menu .mbar.commit
1624 if {[is_enabled transport]} {
1625 .mbar add cascade -label Merge -menu .mbar.merge
1626 .mbar add cascade -label Fetch -menu .mbar.fetch
1627 .mbar add cascade -label Push -menu .mbar.push
1629 . configure -menu .mbar
1631 # -- Repository Menu
1633 menu .mbar.repository
1635 .mbar.repository add command \
1636 -label {Browse Current Branch} \
1637 -command {browser::new $current_branch}
1638 trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#"
1639 .mbar.repository add separator
1641 .mbar.repository add command \
1642 -label {Visualize Current Branch} \
1643 -command {do_gitk $current_branch}
1644 trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch\" ;#"
1645 .mbar.repository add command \
1646 -label {Visualize All Branches} \
1647 -command {do_gitk --all}
1648 .mbar.repository add separator
1650 if {[is_enabled multicommit]} {
1651 .mbar.repository add command -label {Database Statistics} \
1652 -command do_stats
1654 .mbar.repository add command -label {Compress Database} \
1655 -command do_gc
1657 .mbar.repository add command -label {Verify Database} \
1658 -command do_fsck_objects
1660 .mbar.repository add separator
1662 if {[is_Cygwin]} {
1663 .mbar.repository add command \
1664 -label {Create Desktop Icon} \
1665 -command do_cygwin_shortcut
1666 } elseif {[is_Windows]} {
1667 .mbar.repository add command \
1668 -label {Create Desktop Icon} \
1669 -command do_windows_shortcut
1670 } elseif {[is_MacOSX]} {
1671 .mbar.repository add command \
1672 -label {Create Desktop Icon} \
1673 -command do_macosx_app
1677 .mbar.repository add command -label Quit \
1678 -command do_quit \
1679 -accelerator $M1T-Q
1681 # -- Edit Menu
1683 menu .mbar.edit
1684 .mbar.edit add command -label Undo \
1685 -command {catch {[focus] edit undo}} \
1686 -accelerator $M1T-Z
1687 .mbar.edit add command -label Redo \
1688 -command {catch {[focus] edit redo}} \
1689 -accelerator $M1T-Y
1690 .mbar.edit add separator
1691 .mbar.edit add command -label Cut \
1692 -command {catch {tk_textCut [focus]}} \
1693 -accelerator $M1T-X
1694 .mbar.edit add command -label Copy \
1695 -command {catch {tk_textCopy [focus]}} \
1696 -accelerator $M1T-C
1697 .mbar.edit add command -label Paste \
1698 -command {catch {tk_textPaste [focus]; [focus] see insert}} \
1699 -accelerator $M1T-V
1700 .mbar.edit add command -label Delete \
1701 -command {catch {[focus] delete sel.first sel.last}} \
1702 -accelerator Del
1703 .mbar.edit add separator
1704 .mbar.edit add command -label {Select All} \
1705 -command {catch {[focus] tag add sel 0.0 end}} \
1706 -accelerator $M1T-A
1708 # -- Branch Menu
1710 if {[is_enabled branch]} {
1711 menu .mbar.branch
1713 .mbar.branch add command -label {Create...} \
1714 -command branch_create::dialog \
1715 -accelerator $M1T-N
1716 lappend disable_on_lock [list .mbar.branch entryconf \
1717 [.mbar.branch index last] -state]
1719 .mbar.branch add command -label {Checkout...} \
1720 -command branch_checkout::dialog \
1721 -accelerator $M1T-O
1722 lappend disable_on_lock [list .mbar.branch entryconf \
1723 [.mbar.branch index last] -state]
1725 .mbar.branch add command -label {Rename...} \
1726 -command branch_rename::dialog
1727 lappend disable_on_lock [list .mbar.branch entryconf \
1728 [.mbar.branch index last] -state]
1730 .mbar.branch add command -label {Delete...} \
1731 -command branch_delete::dialog
1732 lappend disable_on_lock [list .mbar.branch entryconf \
1733 [.mbar.branch index last] -state]
1735 .mbar.branch add command -label {Reset...} \
1736 -command merge::reset_hard
1737 lappend disable_on_lock [list .mbar.branch entryconf \
1738 [.mbar.branch index last] -state]
1741 # -- Commit Menu
1743 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1744 menu .mbar.commit
1746 .mbar.commit add radiobutton \
1747 -label {New Commit} \
1748 -command do_select_commit_type \
1749 -variable selected_commit_type \
1750 -value new
1751 lappend disable_on_lock \
1752 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1754 .mbar.commit add radiobutton \
1755 -label {Amend Last Commit} \
1756 -command do_select_commit_type \
1757 -variable selected_commit_type \
1758 -value amend
1759 lappend disable_on_lock \
1760 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1762 .mbar.commit add separator
1764 .mbar.commit add command -label Rescan \
1765 -command do_rescan \
1766 -accelerator F5
1767 lappend disable_on_lock \
1768 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1770 .mbar.commit add command -label {Add To Commit} \
1771 -command do_add_selection
1772 lappend disable_on_lock \
1773 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1775 .mbar.commit add command -label {Add Existing To Commit} \
1776 -command do_add_all \
1777 -accelerator $M1T-I
1778 lappend disable_on_lock \
1779 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1781 .mbar.commit add command -label {Unstage From Commit} \
1782 -command do_unstage_selection
1783 lappend disable_on_lock \
1784 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1786 .mbar.commit add command -label {Revert Changes} \
1787 -command do_revert_selection
1788 lappend disable_on_lock \
1789 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1791 .mbar.commit add separator
1793 .mbar.commit add command -label {Sign Off} \
1794 -command do_signoff \
1795 -accelerator $M1T-S
1797 .mbar.commit add command -label Commit \
1798 -command do_commit \
1799 -accelerator $M1T-Return
1800 lappend disable_on_lock \
1801 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1804 # -- Merge Menu
1806 if {[is_enabled branch]} {
1807 menu .mbar.merge
1808 .mbar.merge add command -label {Local Merge...} \
1809 -command merge::dialog
1810 lappend disable_on_lock \
1811 [list .mbar.merge entryconf [.mbar.merge index last] -state]
1812 .mbar.merge add command -label {Abort Merge...} \
1813 -command merge::reset_hard
1814 lappend disable_on_lock \
1815 [list .mbar.merge entryconf [.mbar.merge index last] -state]
1819 # -- Transport Menu
1821 if {[is_enabled transport]} {
1822 menu .mbar.fetch
1824 menu .mbar.push
1825 .mbar.push add command -label {Push...} \
1826 -command do_push_anywhere \
1827 -accelerator $M1T-P
1828 .mbar.push add command -label {Delete...} \
1829 -command remote_branch_delete::dialog
1832 if {[is_MacOSX]} {
1833 # -- Apple Menu (Mac OS X only)
1835 .mbar add cascade -label Apple -menu .mbar.apple
1836 menu .mbar.apple
1838 .mbar.apple add command -label "About [appname]" \
1839 -command do_about
1840 .mbar.apple add command -label "Options..." \
1841 -command do_options
1842 } else {
1843 # -- Edit Menu
1845 .mbar.edit add separator
1846 .mbar.edit add command -label {Options...} \
1847 -command do_options
1849 # -- Tools Menu
1851 if {[is_Cygwin] && [file exists /usr/local/miga/lib/gui-miga]} {
1852 proc do_miga {} {
1853 if {![lock_index update]} return
1854 set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""]
1855 set miga_fd [open "|$cmd" r]
1856 fconfigure $miga_fd -blocking 0
1857 fileevent $miga_fd readable [list miga_done $miga_fd]
1858 ui_status {Running miga...}
1860 proc miga_done {fd} {
1861 read $fd 512
1862 if {[eof $fd]} {
1863 close $fd
1864 unlock_index
1865 rescan ui_ready
1868 .mbar add cascade -label Tools -menu .mbar.tools
1869 menu .mbar.tools
1870 .mbar.tools add command -label "Migrate" \
1871 -command do_miga
1872 lappend disable_on_lock \
1873 [list .mbar.tools entryconf [.mbar.tools index last] -state]
1877 # -- Help Menu
1879 .mbar add cascade -label Help -menu .mbar.help
1880 menu .mbar.help
1882 if {![is_MacOSX]} {
1883 .mbar.help add command -label "About [appname]" \
1884 -command do_about
1887 set browser {}
1888 catch {set browser $repo_config(instaweb.browser)}
1889 set doc_path [file dirname [gitexec]]
1890 set doc_path [file join $doc_path Documentation index.html]
1892 if {[is_Cygwin]} {
1893 set doc_path [exec cygpath --mixed $doc_path]
1896 if {$browser eq {}} {
1897 if {[is_MacOSX]} {
1898 set browser open
1899 } elseif {[is_Cygwin]} {
1900 set program_files [file dirname [exec cygpath --windir]]
1901 set program_files [file join $program_files {Program Files}]
1902 set firefox [file join $program_files {Mozilla Firefox} firefox.exe]
1903 set ie [file join $program_files {Internet Explorer} IEXPLORE.EXE]
1904 if {[file exists $firefox]} {
1905 set browser $firefox
1906 } elseif {[file exists $ie]} {
1907 set browser $ie
1909 unset program_files firefox ie
1913 if {[file isfile $doc_path]} {
1914 set doc_url "file:$doc_path"
1915 } else {
1916 set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
1919 if {$browser ne {}} {
1920 .mbar.help add command -label {Online Documentation} \
1921 -command [list exec $browser $doc_url &]
1923 unset browser doc_path doc_url
1925 # -- Standard bindings
1927 wm protocol . WM_DELETE_WINDOW do_quit
1928 bind all <$M1B-Key-q> do_quit
1929 bind all <$M1B-Key-Q> do_quit
1930 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
1931 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
1933 set subcommand_args {}
1934 proc usage {} {
1935 puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
1936 exit 1
1939 # -- Not a normal commit type invocation? Do that instead!
1941 switch -- $subcommand {
1942 browser {
1943 set subcommand_args {rev?}
1944 switch [llength $argv] {
1945 0 { load_current_branch }
1947 set current_branch [lindex $argv 0]
1948 if {[regexp {^[0-9a-f]{1,39}$} $current_branch]} {
1949 if {[catch {
1950 set current_branch \
1951 [git rev-parse --verify $current_branch]
1952 } err]} {
1953 puts stderr $err
1954 exit 1
1958 default usage
1960 browser::new $current_branch
1961 return
1963 blame {
1964 set subcommand_args {rev? path?}
1965 set head {}
1966 set path {}
1967 set is_path 0
1968 foreach a $argv {
1969 if {$is_path || [file exists $_prefix$a]} {
1970 if {$path ne {}} usage
1971 set path $_prefix$a
1972 break
1973 } elseif {$a eq {--}} {
1974 if {$path ne {}} {
1975 if {$head ne {}} usage
1976 set head $path
1977 set path {}
1979 set is_path 1
1980 } elseif {$head eq {}} {
1981 if {$head ne {}} usage
1982 set head $a
1983 } else {
1984 usage
1987 unset is_path
1989 if {$head eq {}} {
1990 load_current_branch
1991 } else {
1992 if {[regexp {^[0-9a-f]{1,39}$} $head]} {
1993 if {[catch {
1994 set head [git rev-parse --verify $head]
1995 } err]} {
1996 puts stderr $err
1997 exit 1
2000 set current_branch $head
2003 if {$path eq {}} usage
2004 blame::new $head $path
2005 return
2007 citool -
2008 gui {
2009 if {[llength $argv] != 0} {
2010 puts -nonewline stderr "usage: $argv0"
2011 if {$subcommand ne {gui} && [appname] ne "git-$subcommand"} {
2012 puts -nonewline stderr " $subcommand"
2014 puts stderr {}
2015 exit 1
2017 # fall through to setup UI for commits
2019 default {
2020 puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
2021 exit 1
2025 # -- Branch Control
2027 frame .branch \
2028 -borderwidth 1 \
2029 -relief sunken
2030 label .branch.l1 \
2031 -text {Current Branch:} \
2032 -anchor w \
2033 -justify left
2034 label .branch.cb \
2035 -textvariable current_branch \
2036 -anchor w \
2037 -justify left
2038 pack .branch.l1 -side left
2039 pack .branch.cb -side left -fill x
2040 pack .branch -side top -fill x
2042 # -- Main Window Layout
2044 panedwindow .vpane -orient vertical
2045 panedwindow .vpane.files -orient horizontal
2046 .vpane add .vpane.files -sticky nsew -height 100 -width 200
2047 pack .vpane -anchor n -side top -fill both -expand 1
2049 # -- Index File List
2051 frame .vpane.files.index -height 100 -width 200
2052 label .vpane.files.index.title -text {Staged Changes (Will Be Committed)} \
2053 -background lightgreen
2054 text $ui_index -background white -borderwidth 0 \
2055 -width 20 -height 10 \
2056 -wrap none \
2057 -cursor $cursor_ptr \
2058 -xscrollcommand {.vpane.files.index.sx set} \
2059 -yscrollcommand {.vpane.files.index.sy set} \
2060 -state disabled
2061 scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
2062 scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
2063 pack .vpane.files.index.title -side top -fill x
2064 pack .vpane.files.index.sx -side bottom -fill x
2065 pack .vpane.files.index.sy -side right -fill y
2066 pack $ui_index -side left -fill both -expand 1
2067 .vpane.files add .vpane.files.index -sticky nsew
2069 # -- Working Directory File List
2071 frame .vpane.files.workdir -height 100 -width 200
2072 label .vpane.files.workdir.title -text {Unstaged Changes (Will Not Be Committed)} \
2073 -background lightsalmon
2074 text $ui_workdir -background white -borderwidth 0 \
2075 -width 20 -height 10 \
2076 -wrap none \
2077 -cursor $cursor_ptr \
2078 -xscrollcommand {.vpane.files.workdir.sx set} \
2079 -yscrollcommand {.vpane.files.workdir.sy set} \
2080 -state disabled
2081 scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
2082 scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
2083 pack .vpane.files.workdir.title -side top -fill x
2084 pack .vpane.files.workdir.sx -side bottom -fill x
2085 pack .vpane.files.workdir.sy -side right -fill y
2086 pack $ui_workdir -side left -fill both -expand 1
2087 .vpane.files add .vpane.files.workdir -sticky nsew
2089 foreach i [list $ui_index $ui_workdir] {
2090 $i tag conf in_diff -background lightgray
2091 $i tag conf in_sel -background lightgray
2093 unset i
2095 # -- Diff and Commit Area
2097 frame .vpane.lower -height 300 -width 400
2098 frame .vpane.lower.commarea
2099 frame .vpane.lower.diff -relief sunken -borderwidth 1
2100 pack .vpane.lower.commarea -side top -fill x
2101 pack .vpane.lower.diff -side bottom -fill both -expand 1
2102 .vpane add .vpane.lower -sticky nsew
2104 # -- Commit Area Buttons
2106 frame .vpane.lower.commarea.buttons
2107 label .vpane.lower.commarea.buttons.l -text {} \
2108 -anchor w \
2109 -justify left
2110 pack .vpane.lower.commarea.buttons.l -side top -fill x
2111 pack .vpane.lower.commarea.buttons -side left -fill y
2113 button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
2114 -command do_rescan
2115 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
2116 lappend disable_on_lock \
2117 {.vpane.lower.commarea.buttons.rescan conf -state}
2119 button .vpane.lower.commarea.buttons.incall -text {Add Existing} \
2120 -command do_add_all
2121 pack .vpane.lower.commarea.buttons.incall -side top -fill x
2122 lappend disable_on_lock \
2123 {.vpane.lower.commarea.buttons.incall conf -state}
2125 button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
2126 -command do_signoff
2127 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
2129 button .vpane.lower.commarea.buttons.commit -text {Commit} \
2130 -command do_commit
2131 pack .vpane.lower.commarea.buttons.commit -side top -fill x
2132 lappend disable_on_lock \
2133 {.vpane.lower.commarea.buttons.commit conf -state}
2135 button .vpane.lower.commarea.buttons.push -text {Push} \
2136 -command do_push_anywhere
2137 pack .vpane.lower.commarea.buttons.push -side top -fill x
2139 # -- Commit Message Buffer
2141 frame .vpane.lower.commarea.buffer
2142 frame .vpane.lower.commarea.buffer.header
2143 set ui_comm .vpane.lower.commarea.buffer.t
2144 set ui_coml .vpane.lower.commarea.buffer.header.l
2145 radiobutton .vpane.lower.commarea.buffer.header.new \
2146 -text {New Commit} \
2147 -command do_select_commit_type \
2148 -variable selected_commit_type \
2149 -value new
2150 lappend disable_on_lock \
2151 [list .vpane.lower.commarea.buffer.header.new conf -state]
2152 radiobutton .vpane.lower.commarea.buffer.header.amend \
2153 -text {Amend Last Commit} \
2154 -command do_select_commit_type \
2155 -variable selected_commit_type \
2156 -value amend
2157 lappend disable_on_lock \
2158 [list .vpane.lower.commarea.buffer.header.amend conf -state]
2159 label $ui_coml \
2160 -anchor w \
2161 -justify left
2162 proc trace_commit_type {varname args} {
2163 global ui_coml commit_type
2164 switch -glob -- $commit_type {
2165 initial {set txt {Initial Commit Message:}}
2166 amend {set txt {Amended Commit Message:}}
2167 amend-initial {set txt {Amended Initial Commit Message:}}
2168 amend-merge {set txt {Amended Merge Commit Message:}}
2169 merge {set txt {Merge Commit Message:}}
2170 * {set txt {Commit Message:}}
2172 $ui_coml conf -text $txt
2174 trace add variable commit_type write trace_commit_type
2175 pack $ui_coml -side left -fill x
2176 pack .vpane.lower.commarea.buffer.header.amend -side right
2177 pack .vpane.lower.commarea.buffer.header.new -side right
2179 text $ui_comm -background white -borderwidth 1 \
2180 -undo true \
2181 -maxundo 20 \
2182 -autoseparators true \
2183 -relief sunken \
2184 -width 75 -height 9 -wrap none \
2185 -font font_diff \
2186 -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
2187 scrollbar .vpane.lower.commarea.buffer.sby \
2188 -command [list $ui_comm yview]
2189 pack .vpane.lower.commarea.buffer.header -side top -fill x
2190 pack .vpane.lower.commarea.buffer.sby -side right -fill y
2191 pack $ui_comm -side left -fill y
2192 pack .vpane.lower.commarea.buffer -side left -fill y
2194 # -- Commit Message Buffer Context Menu
2196 set ctxm .vpane.lower.commarea.buffer.ctxm
2197 menu $ctxm -tearoff 0
2198 $ctxm add command \
2199 -label {Cut} \
2200 -command {tk_textCut $ui_comm}
2201 $ctxm add command \
2202 -label {Copy} \
2203 -command {tk_textCopy $ui_comm}
2204 $ctxm add command \
2205 -label {Paste} \
2206 -command {tk_textPaste $ui_comm}
2207 $ctxm add command \
2208 -label {Delete} \
2209 -command {$ui_comm delete sel.first sel.last}
2210 $ctxm add separator
2211 $ctxm add command \
2212 -label {Select All} \
2213 -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
2214 $ctxm add command \
2215 -label {Copy All} \
2216 -command {
2217 $ui_comm tag add sel 0.0 end
2218 tk_textCopy $ui_comm
2219 $ui_comm tag remove sel 0.0 end
2221 $ctxm add separator
2222 $ctxm add command \
2223 -label {Sign Off} \
2224 -command do_signoff
2225 bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
2227 # -- Diff Header
2229 proc trace_current_diff_path {varname args} {
2230 global current_diff_path diff_actions file_states
2231 if {$current_diff_path eq {}} {
2232 set s {}
2233 set f {}
2234 set p {}
2235 set o disabled
2236 } else {
2237 set p $current_diff_path
2238 set s [mapdesc [lindex $file_states($p) 0] $p]
2239 set f {File:}
2240 set p [escape_path $p]
2241 set o normal
2244 .vpane.lower.diff.header.status configure -text $s
2245 .vpane.lower.diff.header.file configure -text $f
2246 .vpane.lower.diff.header.path configure -text $p
2247 foreach w $diff_actions {
2248 uplevel #0 $w $o
2251 trace add variable current_diff_path write trace_current_diff_path
2253 frame .vpane.lower.diff.header -background gold
2254 label .vpane.lower.diff.header.status \
2255 -background gold \
2256 -width $max_status_desc \
2257 -anchor w \
2258 -justify left
2259 label .vpane.lower.diff.header.file \
2260 -background gold \
2261 -anchor w \
2262 -justify left
2263 label .vpane.lower.diff.header.path \
2264 -background gold \
2265 -anchor w \
2266 -justify left
2267 pack .vpane.lower.diff.header.status -side left
2268 pack .vpane.lower.diff.header.file -side left
2269 pack .vpane.lower.diff.header.path -fill x
2270 set ctxm .vpane.lower.diff.header.ctxm
2271 menu $ctxm -tearoff 0
2272 $ctxm add command \
2273 -label {Copy} \
2274 -command {
2275 clipboard clear
2276 clipboard append \
2277 -format STRING \
2278 -type STRING \
2279 -- $current_diff_path
2281 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2282 bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
2284 # -- Diff Body
2286 frame .vpane.lower.diff.body
2287 set ui_diff .vpane.lower.diff.body.t
2288 text $ui_diff -background white -borderwidth 0 \
2289 -width 80 -height 15 -wrap none \
2290 -font font_diff \
2291 -xscrollcommand {.vpane.lower.diff.body.sbx set} \
2292 -yscrollcommand {.vpane.lower.diff.body.sby set} \
2293 -state disabled
2294 scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
2295 -command [list $ui_diff xview]
2296 scrollbar .vpane.lower.diff.body.sby -orient vertical \
2297 -command [list $ui_diff yview]
2298 pack .vpane.lower.diff.body.sbx -side bottom -fill x
2299 pack .vpane.lower.diff.body.sby -side right -fill y
2300 pack $ui_diff -side left -fill both -expand 1
2301 pack .vpane.lower.diff.header -side top -fill x
2302 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
2304 $ui_diff tag conf d_cr -elide true
2305 $ui_diff tag conf d_@ -foreground blue -font font_diffbold
2306 $ui_diff tag conf d_+ -foreground {#00a000}
2307 $ui_diff tag conf d_- -foreground red
2309 $ui_diff tag conf d_++ -foreground {#00a000}
2310 $ui_diff tag conf d_-- -foreground red
2311 $ui_diff tag conf d_+s \
2312 -foreground {#00a000} \
2313 -background {#e2effa}
2314 $ui_diff tag conf d_-s \
2315 -foreground red \
2316 -background {#e2effa}
2317 $ui_diff tag conf d_s+ \
2318 -foreground {#00a000} \
2319 -background ivory1
2320 $ui_diff tag conf d_s- \
2321 -foreground red \
2322 -background ivory1
2324 $ui_diff tag conf d<<<<<<< \
2325 -foreground orange \
2326 -font font_diffbold
2327 $ui_diff tag conf d======= \
2328 -foreground orange \
2329 -font font_diffbold
2330 $ui_diff tag conf d>>>>>>> \
2331 -foreground orange \
2332 -font font_diffbold
2334 $ui_diff tag raise sel
2336 # -- Diff Body Context Menu
2338 set ctxm .vpane.lower.diff.body.ctxm
2339 menu $ctxm -tearoff 0
2340 $ctxm add command \
2341 -label {Refresh} \
2342 -command reshow_diff
2343 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2344 $ctxm add command \
2345 -label {Copy} \
2346 -command {tk_textCopy $ui_diff}
2347 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2348 $ctxm add command \
2349 -label {Select All} \
2350 -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
2351 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2352 $ctxm add command \
2353 -label {Copy All} \
2354 -command {
2355 $ui_diff tag add sel 0.0 end
2356 tk_textCopy $ui_diff
2357 $ui_diff tag remove sel 0.0 end
2359 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2360 $ctxm add separator
2361 $ctxm add command \
2362 -label {Apply/Reverse Hunk} \
2363 -command {apply_hunk $cursorX $cursorY}
2364 set ui_diff_applyhunk [$ctxm index last]
2365 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
2366 $ctxm add separator
2367 $ctxm add command \
2368 -label {Decrease Font Size} \
2369 -command {incr_font_size font_diff -1}
2370 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2371 $ctxm add command \
2372 -label {Increase Font Size} \
2373 -command {incr_font_size font_diff 1}
2374 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2375 $ctxm add separator
2376 $ctxm add command \
2377 -label {Show Less Context} \
2378 -command {if {$repo_config(gui.diffcontext) >= 1} {
2379 incr repo_config(gui.diffcontext) -1
2380 reshow_diff
2382 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2383 $ctxm add command \
2384 -label {Show More Context} \
2385 -command {if {$repo_config(gui.diffcontext) < 99} {
2386 incr repo_config(gui.diffcontext)
2387 reshow_diff
2389 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2390 $ctxm add separator
2391 $ctxm add command -label {Options...} \
2392 -command do_options
2393 bind_button3 $ui_diff "
2394 set cursorX %x
2395 set cursorY %y
2396 if {\$ui_index eq \$current_diff_side} {
2397 $ctxm entryconf $ui_diff_applyhunk -label {Unstage Hunk From Commit}
2398 } else {
2399 $ctxm entryconf $ui_diff_applyhunk -label {Stage Hunk For Commit}
2401 tk_popup $ctxm %X %Y
2403 unset ui_diff_applyhunk
2405 # -- Status Bar
2407 set main_status [::status_bar::new .status]
2408 pack .status -anchor w -side bottom -fill x
2409 $main_status show {Initializing...}
2411 # -- Load geometry
2413 catch {
2414 set gm $repo_config(gui.geometry)
2415 wm geometry . [lindex $gm 0]
2416 .vpane sash place 0 \
2417 [lindex [.vpane sash coord 0] 0] \
2418 [lindex $gm 1]
2419 .vpane.files sash place 0 \
2420 [lindex $gm 2] \
2421 [lindex [.vpane.files sash coord 0] 1]
2422 unset gm
2425 # -- Key Bindings
2427 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2428 bind $ui_comm <$M1B-Key-i> {do_add_all;break}
2429 bind $ui_comm <$M1B-Key-I> {do_add_all;break}
2430 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2431 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2432 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2433 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2434 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2435 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2436 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2437 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2439 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2440 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2441 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2442 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2443 bind $ui_diff <$M1B-Key-v> {break}
2444 bind $ui_diff <$M1B-Key-V> {break}
2445 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2446 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2447 bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break}
2448 bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break}
2449 bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break}
2450 bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break}
2451 bind $ui_diff <Key-k> {catch {%W yview scroll -1 units};break}
2452 bind $ui_diff <Key-j> {catch {%W yview scroll 1 units};break}
2453 bind $ui_diff <Key-h> {catch {%W xview scroll -1 units};break}
2454 bind $ui_diff <Key-l> {catch {%W xview scroll 1 units};break}
2455 bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
2456 bind $ui_diff <Control-Key-f> {catch {%W yview scroll 1 pages};break}
2457 bind $ui_diff <Button-1> {focus %W}
2459 if {[is_enabled branch]} {
2460 bind . <$M1B-Key-n> branch_create::dialog
2461 bind . <$M1B-Key-N> branch_create::dialog
2462 bind . <$M1B-Key-o> branch_checkout::dialog
2463 bind . <$M1B-Key-O> branch_checkout::dialog
2465 if {[is_enabled transport]} {
2466 bind . <$M1B-Key-p> do_push_anywhere
2467 bind . <$M1B-Key-P> do_push_anywhere
2470 bind . <Key-F5> do_rescan
2471 bind . <$M1B-Key-r> do_rescan
2472 bind . <$M1B-Key-R> do_rescan
2473 bind . <$M1B-Key-s> do_signoff
2474 bind . <$M1B-Key-S> do_signoff
2475 bind . <$M1B-Key-i> do_add_all
2476 bind . <$M1B-Key-I> do_add_all
2477 bind . <$M1B-Key-Return> do_commit
2478 foreach i [list $ui_index $ui_workdir] {
2479 bind $i <Button-1> "toggle_or_diff $i %x %y; break"
2480 bind $i <$M1B-Button-1> "add_one_to_selection $i %x %y; break"
2481 bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
2483 unset i
2485 set file_lists($ui_index) [list]
2486 set file_lists($ui_workdir) [list]
2488 wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
2489 focus -force $ui_comm
2491 # -- Warn the user about environmental problems. Cygwin's Tcl
2492 # does *not* pass its env array onto any processes it spawns.
2493 # This means that git processes get none of our environment.
2495 if {[is_Cygwin]} {
2496 set ignored_env 0
2497 set suggest_user {}
2498 set msg "Possible environment issues exist.
2500 The following environment variables are probably
2501 going to be ignored by any Git subprocess run
2502 by [appname]:
2505 foreach name [array names env] {
2506 switch -regexp -- $name {
2507 {^GIT_INDEX_FILE$} -
2508 {^GIT_OBJECT_DIRECTORY$} -
2509 {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
2510 {^GIT_DIFF_OPTS$} -
2511 {^GIT_EXTERNAL_DIFF$} -
2512 {^GIT_PAGER$} -
2513 {^GIT_TRACE$} -
2514 {^GIT_CONFIG$} -
2515 {^GIT_CONFIG_LOCAL$} -
2516 {^GIT_(AUTHOR|COMMITTER)_DATE$} {
2517 append msg " - $name\n"
2518 incr ignored_env
2520 {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
2521 append msg " - $name\n"
2522 incr ignored_env
2523 set suggest_user $name
2527 if {$ignored_env > 0} {
2528 append msg "
2529 This is due to a known issue with the
2530 Tcl binary distributed by Cygwin."
2532 if {$suggest_user ne {}} {
2533 append msg "
2535 A good replacement for $suggest_user
2536 is placing values for the user.name and
2537 user.email settings into your personal
2538 ~/.gitconfig file.
2541 warn_popup $msg
2543 unset ignored_env msg suggest_user name
2546 # -- Only initialize complex UI if we are going to stay running.
2548 if {[is_enabled transport]} {
2549 load_all_remotes
2551 populate_fetch_menu
2552 populate_push_menu
2555 # -- Only suggest a gc run if we are going to stay running.
2557 if {[is_enabled multicommit]} {
2558 set object_limit 2000
2559 if {[is_Windows]} {set object_limit 200}
2560 regexp {^([0-9]+) objects,} [git count-objects] _junk objects_current
2561 if {$objects_current >= $object_limit} {
2562 if {[ask_popup \
2563 "This repository currently has $objects_current loose objects.
2565 To maintain optimal performance it is strongly recommended that you compress the database when more than $object_limit loose objects exist.
2567 Compress the database now?"] eq yes} {
2568 do_gc
2571 unset object_limit _junk objects_current
2574 lock_index begin-read
2575 after 1 do_rescan