git-gui: Gracefully handle bad TCL_PATH at compile time
[git.git] / git-gui.sh
blob0a471a5c7d4a2126f1f3322424a643796e1de715
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "$@"
5 set appvers {@@GITGUI_VERSION@@}
6 set copyright {
7 Copyright © 2006, 2007 Shawn Pearce, et. al.
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA}
23 ######################################################################
25 ## configure our library
27 set oguilib {@@GITGUI_LIBDIR@@}
28 if {[string match @@* $oguilib]} {
29 set oguilib [file join [file dirname [file normalize $argv0]] lib]
31 set idx [file join $oguilib tclIndex]
32 catch {
33 set fd [open $idx r]
34 if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
35 set idx [list]
36 while {[gets $fd n] >= 0} {
37 if {$n ne {} && ![string match #* $n]} {
38 lappend idx $n
41 } else {
42 set idx {}
44 close $fd
46 if {$idx ne {}} {
47 set loaded [list]
48 foreach p $idx {
49 if {[lsearch -exact $loaded $p] >= 0} continue
50 puts $p
51 source [file join $oguilib $p]
52 lappend loaded $p
54 unset loaded p
55 } else {
56 set auto_path [concat [list $oguilib] $auto_path]
58 unset -nocomplain fd idx
60 if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
61 unset _verbose
62 rename auto_load real__auto_load
63 proc auto_load {name args} {
64 puts stderr "auto_load $name"
65 return [uplevel 1 real__auto_load $name $args]
67 rename source real__source
68 proc source {name} {
69 puts stderr "source $name"
70 uplevel 1 real__source $name
74 ######################################################################
76 ## read only globals
78 set _appname [lindex [file split $argv0] end]
79 set _gitdir {}
80 set _gitexec {}
81 set _reponame {}
82 set _iscygwin {}
84 proc appname {} {
85 global _appname
86 return $_appname
89 proc gitdir {args} {
90 global _gitdir
91 if {$args eq {}} {
92 return $_gitdir
94 return [eval [concat [list file join $_gitdir] $args]]
97 proc gitexec {args} {
98 global _gitexec
99 if {$_gitexec eq {}} {
100 if {[catch {set _gitexec [git --exec-path]} err]} {
101 error "Git not installed?\n\n$err"
104 if {$args eq {}} {
105 return $_gitexec
107 return [eval [concat [list file join $_gitexec] $args]]
110 proc reponame {} {
111 global _reponame
112 return $_reponame
115 proc is_MacOSX {} {
116 global tcl_platform tk_library
117 if {[tk windowingsystem] eq {aqua}} {
118 return 1
120 return 0
123 proc is_Windows {} {
124 global tcl_platform
125 if {$tcl_platform(platform) eq {windows}} {
126 return 1
128 return 0
131 proc is_Cygwin {} {
132 global tcl_platform _iscygwin
133 if {$_iscygwin eq {}} {
134 if {$tcl_platform(platform) eq {windows}} {
135 if {[catch {set p [exec cygpath --windir]} err]} {
136 set _iscygwin 0
137 } else {
138 set _iscygwin 1
140 } else {
141 set _iscygwin 0
144 return $_iscygwin
147 proc is_enabled {option} {
148 global enabled_options
149 if {[catch {set on $enabled_options($option)}]} {return 0}
150 return $on
153 proc enable_option {option} {
154 global enabled_options
155 set enabled_options($option) 1
158 proc disable_option {option} {
159 global enabled_options
160 set enabled_options($option) 0
163 ######################################################################
165 ## config
167 proc is_many_config {name} {
168 switch -glob -- $name {
169 remote.*.fetch -
170 remote.*.push
171 {return 1}
173 {return 0}
177 proc is_config_true {name} {
178 global repo_config
179 if {[catch {set v $repo_config($name)}]} {
180 return 0
181 } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
182 return 1
183 } else {
184 return 0
188 proc load_config {include_global} {
189 global repo_config global_config default_config
191 array unset global_config
192 if {$include_global} {
193 catch {
194 set fd_rc [open "| git config --global --list" r]
195 while {[gets $fd_rc line] >= 0} {
196 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
197 if {[is_many_config $name]} {
198 lappend global_config($name) $value
199 } else {
200 set global_config($name) $value
204 close $fd_rc
208 array unset repo_config
209 catch {
210 set fd_rc [open "| git config --list" r]
211 while {[gets $fd_rc line] >= 0} {
212 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
213 if {[is_many_config $name]} {
214 lappend repo_config($name) $value
215 } else {
216 set repo_config($name) $value
220 close $fd_rc
223 foreach name [array names default_config] {
224 if {[catch {set v $global_config($name)}]} {
225 set global_config($name) $default_config($name)
227 if {[catch {set v $repo_config($name)}]} {
228 set repo_config($name) $default_config($name)
233 ######################################################################
235 ## handy utils
237 proc git {args} {
238 return [eval exec git $args]
241 auto_load tk_optionMenu
242 rename tk_optionMenu real__tkOptionMenu
243 proc tk_optionMenu {w varName args} {
244 set m [eval real__tkOptionMenu $w $varName $args]
245 $m configure -font font_ui
246 $w configure -font font_ui
247 return $m
250 ######################################################################
252 ## version check
254 if {{--version} eq $argv || {version} eq $argv} {
255 puts "git-gui version $appvers"
256 exit
259 set req_maj 1
260 set req_min 5
262 if {[catch {set v [git --version]} err]} {
263 catch {wm withdraw .}
264 error_popup "Cannot determine Git version:
266 $err
268 [appname] requires Git $req_maj.$req_min or later."
269 exit 1
271 if {[regexp {^git version (\d+)\.(\d+)} $v _junk act_maj act_min]} {
272 if {$act_maj < $req_maj
273 || ($act_maj == $req_maj && $act_min < $req_min)} {
274 catch {wm withdraw .}
275 error_popup "[appname] requires Git $req_maj.$req_min or later.
277 You are using $v."
278 exit 1
280 } else {
281 catch {wm withdraw .}
282 error_popup "Cannot parse Git version string:\n\n$v"
283 exit 1
285 unset -nocomplain v _junk act_maj act_min req_maj req_min
287 ######################################################################
289 ## repository setup
291 if {[catch {
292 set _gitdir $env(GIT_DIR)
293 set _prefix {}
295 && [catch {
296 set _gitdir [git rev-parse --git-dir]
297 set _prefix [git rev-parse --show-prefix]
298 } err]} {
299 catch {wm withdraw .}
300 error_popup "Cannot find the git directory:\n\n$err"
301 exit 1
303 if {![file isdirectory $_gitdir] && [is_Cygwin]} {
304 catch {set _gitdir [exec cygpath --unix $_gitdir]}
306 if {![file isdirectory $_gitdir]} {
307 catch {wm withdraw .}
308 error_popup "Git directory not found:\n\n$_gitdir"
309 exit 1
311 if {[lindex [file split $_gitdir] end] ne {.git}} {
312 catch {wm withdraw .}
313 error_popup "Cannot use funny .git directory:\n\n$_gitdir"
314 exit 1
316 if {[catch {cd [file dirname $_gitdir]} err]} {
317 catch {wm withdraw .}
318 error_popup "No working directory [file dirname $_gitdir]:\n\n$err"
319 exit 1
321 set _reponame [lindex [file split \
322 [file normalize [file dirname $_gitdir]]] \
323 end]
325 ######################################################################
327 ## global init
329 set current_diff_path {}
330 set current_diff_side {}
331 set diff_actions [list]
332 set ui_status_value {Initializing...}
334 set HEAD {}
335 set PARENT {}
336 set MERGE_HEAD [list]
337 set commit_type {}
338 set empty_tree {}
339 set current_branch {}
340 set current_diff_path {}
341 set selected_commit_type new
343 ######################################################################
345 ## task management
347 set rescan_active 0
348 set diff_active 0
349 set last_clicked {}
351 set disable_on_lock [list]
352 set index_lock_type none
354 proc lock_index {type} {
355 global index_lock_type disable_on_lock
357 if {$index_lock_type eq {none}} {
358 set index_lock_type $type
359 foreach w $disable_on_lock {
360 uplevel #0 $w disabled
362 return 1
363 } elseif {$index_lock_type eq "begin-$type"} {
364 set index_lock_type $type
365 return 1
367 return 0
370 proc unlock_index {} {
371 global index_lock_type disable_on_lock
373 set index_lock_type none
374 foreach w $disable_on_lock {
375 uplevel #0 $w normal
379 ######################################################################
381 ## status
383 proc repository_state {ctvar hdvar mhvar} {
384 global current_branch
385 upvar $ctvar ct $hdvar hd $mhvar mh
387 set mh [list]
389 if {[catch {set current_branch [git symbolic-ref HEAD]}]} {
390 set current_branch {}
391 } else {
392 regsub ^refs/((heads|tags|remotes)/)? \
393 $current_branch \
394 {} \
395 current_branch
398 if {[catch {set hd [git rev-parse --verify HEAD]}]} {
399 set hd {}
400 set ct initial
401 return
404 set merge_head [gitdir MERGE_HEAD]
405 if {[file exists $merge_head]} {
406 set ct merge
407 set fd_mh [open $merge_head r]
408 while {[gets $fd_mh line] >= 0} {
409 lappend mh $line
411 close $fd_mh
412 return
415 set ct normal
418 proc PARENT {} {
419 global PARENT empty_tree
421 set p [lindex $PARENT 0]
422 if {$p ne {}} {
423 return $p
425 if {$empty_tree eq {}} {
426 set empty_tree [git mktree << {}]
428 return $empty_tree
431 proc rescan {after {honor_trustmtime 1}} {
432 global HEAD PARENT MERGE_HEAD commit_type
433 global ui_index ui_workdir ui_status_value ui_comm
434 global rescan_active file_states
435 global repo_config
437 if {$rescan_active > 0 || ![lock_index read]} return
439 repository_state newType newHEAD newMERGE_HEAD
440 if {[string match amend* $commit_type]
441 && $newType eq {normal}
442 && $newHEAD eq $HEAD} {
443 } else {
444 set HEAD $newHEAD
445 set PARENT $newHEAD
446 set MERGE_HEAD $newMERGE_HEAD
447 set commit_type $newType
450 array unset file_states
452 if {![$ui_comm edit modified]
453 || [string trim [$ui_comm get 0.0 end]] eq {}} {
454 if {[load_message GITGUI_MSG]} {
455 } elseif {[load_message MERGE_MSG]} {
456 } elseif {[load_message SQUASH_MSG]} {
458 $ui_comm edit reset
459 $ui_comm edit modified false
462 if {[is_enabled branch]} {
463 load_all_heads
464 populate_branch_menu
467 if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
468 rescan_stage2 {} $after
469 } else {
470 set rescan_active 1
471 set ui_status_value {Refreshing file status...}
472 set cmd [list git update-index]
473 lappend cmd -q
474 lappend cmd --unmerged
475 lappend cmd --ignore-missing
476 lappend cmd --refresh
477 set fd_rf [open "| $cmd" r]
478 fconfigure $fd_rf -blocking 0 -translation binary
479 fileevent $fd_rf readable \
480 [list rescan_stage2 $fd_rf $after]
484 proc rescan_stage2 {fd after} {
485 global ui_status_value
486 global rescan_active buf_rdi buf_rdf buf_rlo
488 if {$fd ne {}} {
489 read $fd
490 if {![eof $fd]} return
491 close $fd
494 set ls_others [list | git ls-files --others -z \
495 --exclude-per-directory=.gitignore]
496 set info_exclude [gitdir info exclude]
497 if {[file readable $info_exclude]} {
498 lappend ls_others "--exclude-from=$info_exclude"
501 set buf_rdi {}
502 set buf_rdf {}
503 set buf_rlo {}
505 set rescan_active 3
506 set ui_status_value {Scanning for modified files ...}
507 set fd_di [open "| git diff-index --cached -z [PARENT]" r]
508 set fd_df [open "| git diff-files -z" r]
509 set fd_lo [open $ls_others r]
511 fconfigure $fd_di -blocking 0 -translation binary -encoding binary
512 fconfigure $fd_df -blocking 0 -translation binary -encoding binary
513 fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
514 fileevent $fd_di readable [list read_diff_index $fd_di $after]
515 fileevent $fd_df readable [list read_diff_files $fd_df $after]
516 fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
519 proc load_message {file} {
520 global ui_comm
522 set f [gitdir $file]
523 if {[file isfile $f]} {
524 if {[catch {set fd [open $f r]}]} {
525 return 0
527 set content [string trim [read $fd]]
528 close $fd
529 regsub -all -line {[ \r\t]+$} $content {} content
530 $ui_comm delete 0.0 end
531 $ui_comm insert end $content
532 return 1
534 return 0
537 proc read_diff_index {fd after} {
538 global buf_rdi
540 append buf_rdi [read $fd]
541 set c 0
542 set n [string length $buf_rdi]
543 while {$c < $n} {
544 set z1 [string first "\0" $buf_rdi $c]
545 if {$z1 == -1} break
546 incr z1
547 set z2 [string first "\0" $buf_rdi $z1]
548 if {$z2 == -1} break
550 incr c
551 set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
552 set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
553 merge_state \
554 [encoding convertfrom $p] \
555 [lindex $i 4]? \
556 [list [lindex $i 0] [lindex $i 2]] \
557 [list]
558 set c $z2
559 incr c
561 if {$c < $n} {
562 set buf_rdi [string range $buf_rdi $c end]
563 } else {
564 set buf_rdi {}
567 rescan_done $fd buf_rdi $after
570 proc read_diff_files {fd after} {
571 global buf_rdf
573 append buf_rdf [read $fd]
574 set c 0
575 set n [string length $buf_rdf]
576 while {$c < $n} {
577 set z1 [string first "\0" $buf_rdf $c]
578 if {$z1 == -1} break
579 incr z1
580 set z2 [string first "\0" $buf_rdf $z1]
581 if {$z2 == -1} break
583 incr c
584 set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
585 set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
586 merge_state \
587 [encoding convertfrom $p] \
588 ?[lindex $i 4] \
589 [list] \
590 [list [lindex $i 0] [lindex $i 2]]
591 set c $z2
592 incr c
594 if {$c < $n} {
595 set buf_rdf [string range $buf_rdf $c end]
596 } else {
597 set buf_rdf {}
600 rescan_done $fd buf_rdf $after
603 proc read_ls_others {fd after} {
604 global buf_rlo
606 append buf_rlo [read $fd]
607 set pck [split $buf_rlo "\0"]
608 set buf_rlo [lindex $pck end]
609 foreach p [lrange $pck 0 end-1] {
610 merge_state [encoding convertfrom $p] ?O
612 rescan_done $fd buf_rlo $after
615 proc rescan_done {fd buf after} {
616 global rescan_active current_diff_path
617 global file_states repo_config
618 upvar $buf to_clear
620 if {![eof $fd]} return
621 set to_clear {}
622 close $fd
623 if {[incr rescan_active -1] > 0} return
625 prune_selection
626 unlock_index
627 display_all_files
628 if {$current_diff_path ne {}} reshow_diff
629 uplevel #0 $after
632 proc prune_selection {} {
633 global file_states selected_paths
635 foreach path [array names selected_paths] {
636 if {[catch {set still_here $file_states($path)}]} {
637 unset selected_paths($path)
642 ######################################################################
644 ## ui helpers
646 proc mapicon {w state path} {
647 global all_icons
649 if {[catch {set r $all_icons($state$w)}]} {
650 puts "error: no icon for $w state={$state} $path"
651 return file_plain
653 return $r
656 proc mapdesc {state path} {
657 global all_descs
659 if {[catch {set r $all_descs($state)}]} {
660 puts "error: no desc for state={$state} $path"
661 return $state
663 return $r
666 proc escape_path {path} {
667 regsub -all {\\} $path "\\\\" path
668 regsub -all "\n" $path "\\n" path
669 return $path
672 proc short_path {path} {
673 return [escape_path [lindex [file split $path] end]]
676 set next_icon_id 0
677 set null_sha1 [string repeat 0 40]
679 proc merge_state {path new_state {head_info {}} {index_info {}}} {
680 global file_states next_icon_id null_sha1
682 set s0 [string index $new_state 0]
683 set s1 [string index $new_state 1]
685 if {[catch {set info $file_states($path)}]} {
686 set state __
687 set icon n[incr next_icon_id]
688 } else {
689 set state [lindex $info 0]
690 set icon [lindex $info 1]
691 if {$head_info eq {}} {set head_info [lindex $info 2]}
692 if {$index_info eq {}} {set index_info [lindex $info 3]}
695 if {$s0 eq {?}} {set s0 [string index $state 0]} \
696 elseif {$s0 eq {_}} {set s0 _}
698 if {$s1 eq {?}} {set s1 [string index $state 1]} \
699 elseif {$s1 eq {_}} {set s1 _}
701 if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
702 set head_info [list 0 $null_sha1]
703 } elseif {$s0 ne {_} && [string index $state 0] eq {_}
704 && $head_info eq {}} {
705 set head_info $index_info
708 set file_states($path) [list $s0$s1 $icon \
709 $head_info $index_info \
711 return $state
714 proc display_file_helper {w path icon_name old_m new_m} {
715 global file_lists
717 if {$new_m eq {_}} {
718 set lno [lsearch -sorted -exact $file_lists($w) $path]
719 if {$lno >= 0} {
720 set file_lists($w) [lreplace $file_lists($w) $lno $lno]
721 incr lno
722 $w conf -state normal
723 $w delete $lno.0 [expr {$lno + 1}].0
724 $w conf -state disabled
726 } elseif {$old_m eq {_} && $new_m ne {_}} {
727 lappend file_lists($w) $path
728 set file_lists($w) [lsort -unique $file_lists($w)]
729 set lno [lsearch -sorted -exact $file_lists($w) $path]
730 incr lno
731 $w conf -state normal
732 $w image create $lno.0 \
733 -align center -padx 5 -pady 1 \
734 -name $icon_name \
735 -image [mapicon $w $new_m $path]
736 $w insert $lno.1 "[escape_path $path]\n"
737 $w conf -state disabled
738 } elseif {$old_m ne $new_m} {
739 $w conf -state normal
740 $w image conf $icon_name -image [mapicon $w $new_m $path]
741 $w conf -state disabled
745 proc display_file {path state} {
746 global file_states selected_paths
747 global ui_index ui_workdir
749 set old_m [merge_state $path $state]
750 set s $file_states($path)
751 set new_m [lindex $s 0]
752 set icon_name [lindex $s 1]
754 set o [string index $old_m 0]
755 set n [string index $new_m 0]
756 if {$o eq {U}} {
757 set o _
759 if {$n eq {U}} {
760 set n _
762 display_file_helper $ui_index $path $icon_name $o $n
764 if {[string index $old_m 0] eq {U}} {
765 set o U
766 } else {
767 set o [string index $old_m 1]
769 if {[string index $new_m 0] eq {U}} {
770 set n U
771 } else {
772 set n [string index $new_m 1]
774 display_file_helper $ui_workdir $path $icon_name $o $n
776 if {$new_m eq {__}} {
777 unset file_states($path)
778 catch {unset selected_paths($path)}
782 proc display_all_files_helper {w path icon_name m} {
783 global file_lists
785 lappend file_lists($w) $path
786 set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
787 $w image create end \
788 -align center -padx 5 -pady 1 \
789 -name $icon_name \
790 -image [mapicon $w $m $path]
791 $w insert end "[escape_path $path]\n"
794 proc display_all_files {} {
795 global ui_index ui_workdir
796 global file_states file_lists
797 global last_clicked
799 $ui_index conf -state normal
800 $ui_workdir conf -state normal
802 $ui_index delete 0.0 end
803 $ui_workdir delete 0.0 end
804 set last_clicked {}
806 set file_lists($ui_index) [list]
807 set file_lists($ui_workdir) [list]
809 foreach path [lsort [array names file_states]] {
810 set s $file_states($path)
811 set m [lindex $s 0]
812 set icon_name [lindex $s 1]
814 set s [string index $m 0]
815 if {$s ne {U} && $s ne {_}} {
816 display_all_files_helper $ui_index $path \
817 $icon_name $s
820 if {[string index $m 0] eq {U}} {
821 set s U
822 } else {
823 set s [string index $m 1]
825 if {$s ne {_}} {
826 display_all_files_helper $ui_workdir $path \
827 $icon_name $s
831 $ui_index conf -state disabled
832 $ui_workdir conf -state disabled
835 ######################################################################
837 ## icons
839 set filemask {
840 #define mask_width 14
841 #define mask_height 15
842 static unsigned char mask_bits[] = {
843 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
844 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
845 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
848 image create bitmap file_plain -background white -foreground black -data {
849 #define plain_width 14
850 #define plain_height 15
851 static unsigned char plain_bits[] = {
852 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
853 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
854 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
855 } -maskdata $filemask
857 image create bitmap file_mod -background white -foreground blue -data {
858 #define mod_width 14
859 #define mod_height 15
860 static unsigned char mod_bits[] = {
861 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
862 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
863 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
864 } -maskdata $filemask
866 image create bitmap file_fulltick -background white -foreground "#007000" -data {
867 #define file_fulltick_width 14
868 #define file_fulltick_height 15
869 static unsigned char file_fulltick_bits[] = {
870 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
871 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
872 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
873 } -maskdata $filemask
875 image create bitmap file_parttick -background white -foreground "#005050" -data {
876 #define parttick_width 14
877 #define parttick_height 15
878 static unsigned char parttick_bits[] = {
879 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
880 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
881 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
882 } -maskdata $filemask
884 image create bitmap file_question -background white -foreground black -data {
885 #define file_question_width 14
886 #define file_question_height 15
887 static unsigned char file_question_bits[] = {
888 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
889 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
890 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
891 } -maskdata $filemask
893 image create bitmap file_removed -background white -foreground red -data {
894 #define file_removed_width 14
895 #define file_removed_height 15
896 static unsigned char file_removed_bits[] = {
897 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
898 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
899 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
900 } -maskdata $filemask
902 image create bitmap file_merge -background white -foreground blue -data {
903 #define file_merge_width 14
904 #define file_merge_height 15
905 static unsigned char file_merge_bits[] = {
906 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
907 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
908 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
909 } -maskdata $filemask
911 set file_dir_data {
912 #define file_width 18
913 #define file_height 18
914 static unsigned char file_bits[] = {
915 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00,
916 0x0c, 0x03, 0x00, 0x04, 0xfe, 0x00, 0x06, 0x80, 0x00, 0xff, 0x9f, 0x00,
917 0x03, 0x98, 0x00, 0x02, 0x90, 0x00, 0x06, 0xb0, 0x00, 0x04, 0xa0, 0x00,
918 0x0c, 0xe0, 0x00, 0x08, 0xc0, 0x00, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00,
919 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
921 image create bitmap file_dir -background white -foreground blue \
922 -data $file_dir_data -maskdata $file_dir_data
923 unset file_dir_data
925 set file_uplevel_data {
926 #define up_width 15
927 #define up_height 15
928 static unsigned char up_bits[] = {
929 0x80, 0x00, 0xc0, 0x01, 0xe0, 0x03, 0xf0, 0x07, 0xf8, 0x0f, 0xfc, 0x1f,
930 0xfe, 0x3f, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01,
931 0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00};
933 image create bitmap file_uplevel -background white -foreground red \
934 -data $file_uplevel_data -maskdata $file_uplevel_data
935 unset file_uplevel_data
937 set ui_index .vpane.files.index.list
938 set ui_workdir .vpane.files.workdir.list
940 set all_icons(_$ui_index) file_plain
941 set all_icons(A$ui_index) file_fulltick
942 set all_icons(M$ui_index) file_fulltick
943 set all_icons(D$ui_index) file_removed
944 set all_icons(U$ui_index) file_merge
946 set all_icons(_$ui_workdir) file_plain
947 set all_icons(M$ui_workdir) file_mod
948 set all_icons(D$ui_workdir) file_question
949 set all_icons(U$ui_workdir) file_merge
950 set all_icons(O$ui_workdir) file_plain
952 set max_status_desc 0
953 foreach i {
954 {__ "Unmodified"}
956 {_M "Modified, not staged"}
957 {M_ "Staged for commit"}
958 {MM "Portions staged for commit"}
959 {MD "Staged for commit, missing"}
961 {_O "Untracked, not staged"}
962 {A_ "Staged for commit"}
963 {AM "Portions staged for commit"}
964 {AD "Staged for commit, missing"}
966 {_D "Missing"}
967 {D_ "Staged for removal"}
968 {DO "Staged for removal, still present"}
970 {U_ "Requires merge resolution"}
971 {UU "Requires merge resolution"}
972 {UM "Requires merge resolution"}
973 {UD "Requires merge resolution"}
975 if {$max_status_desc < [string length [lindex $i 1]]} {
976 set max_status_desc [string length [lindex $i 1]]
978 set all_descs([lindex $i 0]) [lindex $i 1]
980 unset i
982 ######################################################################
984 ## util
986 proc bind_button3 {w cmd} {
987 bind $w <Any-Button-3> $cmd
988 if {[is_MacOSX]} {
989 bind $w <Control-Button-1> $cmd
993 proc scrollbar2many {list mode args} {
994 foreach w $list {eval $w $mode $args}
997 proc many2scrollbar {list mode sb top bottom} {
998 $sb set $top $bottom
999 foreach w $list {$w $mode moveto $top}
1002 proc incr_font_size {font {amt 1}} {
1003 set sz [font configure $font -size]
1004 incr sz $amt
1005 font configure $font -size $sz
1006 font configure ${font}bold -size $sz
1009 ######################################################################
1011 ## ui commands
1013 set starting_gitk_msg {Starting gitk... please wait...}
1015 proc do_gitk {revs} {
1016 global env ui_status_value starting_gitk_msg
1018 # -- Always start gitk through whatever we were loaded with. This
1019 # lets us bypass using shell process on Windows systems.
1021 set cmd [list [info nameofexecutable]]
1022 lappend cmd [gitexec gitk]
1023 if {$revs ne {}} {
1024 append cmd { }
1025 append cmd $revs
1028 if {[catch {eval exec $cmd &} err]} {
1029 error_popup "Failed to start gitk:\n\n$err"
1030 } else {
1031 set ui_status_value $starting_gitk_msg
1032 after 10000 {
1033 if {$ui_status_value eq $starting_gitk_msg} {
1034 set ui_status_value {Ready.}
1040 set is_quitting 0
1042 proc do_quit {} {
1043 global ui_comm is_quitting repo_config commit_type
1045 if {$is_quitting} return
1046 set is_quitting 1
1048 if {[winfo exists $ui_comm]} {
1049 # -- Stash our current commit buffer.
1051 set save [gitdir GITGUI_MSG]
1052 set msg [string trim [$ui_comm get 0.0 end]]
1053 regsub -all -line {[ \r\t]+$} $msg {} msg
1054 if {(![string match amend* $commit_type]
1055 || [$ui_comm edit modified])
1056 && $msg ne {}} {
1057 catch {
1058 set fd [open $save w]
1059 puts -nonewline $fd $msg
1060 close $fd
1062 } else {
1063 catch {file delete $save}
1066 # -- Stash our current window geometry into this repository.
1068 set cfg_geometry [list]
1069 lappend cfg_geometry [wm geometry .]
1070 lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
1071 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
1072 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1073 set rc_geometry {}
1075 if {$cfg_geometry ne $rc_geometry} {
1076 catch {git config gui.geometry $cfg_geometry}
1080 destroy .
1083 proc do_rescan {} {
1084 rescan {set ui_status_value {Ready.}}
1087 proc do_commit {} {
1088 commit_tree
1091 proc toggle_or_diff {w x y} {
1092 global file_states file_lists current_diff_path ui_index ui_workdir
1093 global last_clicked selected_paths
1095 set pos [split [$w index @$x,$y] .]
1096 set lno [lindex $pos 0]
1097 set col [lindex $pos 1]
1098 set path [lindex $file_lists($w) [expr {$lno - 1}]]
1099 if {$path eq {}} {
1100 set last_clicked {}
1101 return
1104 set last_clicked [list $w $lno]
1105 array unset selected_paths
1106 $ui_index tag remove in_sel 0.0 end
1107 $ui_workdir tag remove in_sel 0.0 end
1109 if {$col == 0} {
1110 if {$current_diff_path eq $path} {
1111 set after {reshow_diff;}
1112 } else {
1113 set after {}
1115 if {$w eq $ui_index} {
1116 update_indexinfo \
1117 "Unstaging [short_path $path] from commit" \
1118 [list $path] \
1119 [concat $after {set ui_status_value {Ready.}}]
1120 } elseif {$w eq $ui_workdir} {
1121 update_index \
1122 "Adding [short_path $path]" \
1123 [list $path] \
1124 [concat $after {set ui_status_value {Ready.}}]
1126 } else {
1127 show_diff $path $w $lno
1131 proc add_one_to_selection {w x y} {
1132 global file_lists last_clicked selected_paths
1134 set lno [lindex [split [$w index @$x,$y] .] 0]
1135 set path [lindex $file_lists($w) [expr {$lno - 1}]]
1136 if {$path eq {}} {
1137 set last_clicked {}
1138 return
1141 if {$last_clicked ne {}
1142 && [lindex $last_clicked 0] ne $w} {
1143 array unset selected_paths
1144 [lindex $last_clicked 0] tag remove in_sel 0.0 end
1147 set last_clicked [list $w $lno]
1148 if {[catch {set in_sel $selected_paths($path)}]} {
1149 set in_sel 0
1151 if {$in_sel} {
1152 unset selected_paths($path)
1153 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
1154 } else {
1155 set selected_paths($path) 1
1156 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
1160 proc add_range_to_selection {w x y} {
1161 global file_lists last_clicked selected_paths
1163 if {[lindex $last_clicked 0] ne $w} {
1164 toggle_or_diff $w $x $y
1165 return
1168 set lno [lindex [split [$w index @$x,$y] .] 0]
1169 set lc [lindex $last_clicked 1]
1170 if {$lc < $lno} {
1171 set begin $lc
1172 set end $lno
1173 } else {
1174 set begin $lno
1175 set end $lc
1178 foreach path [lrange $file_lists($w) \
1179 [expr {$begin - 1}] \
1180 [expr {$end - 1}]] {
1181 set selected_paths($path) 1
1183 $w tag add in_sel $begin.0 [expr {$end + 1}].0
1186 ######################################################################
1188 ## config defaults
1190 set cursor_ptr arrow
1191 font create font_diff -family Courier -size 10
1192 font create font_ui
1193 catch {
1194 label .dummy
1195 eval font configure font_ui [font actual [.dummy cget -font]]
1196 destroy .dummy
1199 font create font_uibold
1200 font create font_diffbold
1202 foreach class {Button Checkbutton Entry Label
1203 Labelframe Listbox Menu Message
1204 Radiobutton Text} {
1205 option add *$class.font font_ui
1207 unset class
1209 if {[is_Windows]} {
1210 set M1B Control
1211 set M1T Ctrl
1212 } elseif {[is_MacOSX]} {
1213 set M1B M1
1214 set M1T Cmd
1215 } else {
1216 set M1B M1
1217 set M1T M1
1220 proc apply_config {} {
1221 global repo_config font_descs
1223 foreach option $font_descs {
1224 set name [lindex $option 0]
1225 set font [lindex $option 1]
1226 if {[catch {
1227 foreach {cn cv} $repo_config(gui.$name) {
1228 font configure $font $cn $cv
1230 } err]} {
1231 error_popup "Invalid font specified in gui.$name:\n\n$err"
1233 foreach {cn cv} [font configure $font] {
1234 font configure ${font}bold $cn $cv
1236 font configure ${font}bold -weight bold
1240 set default_config(merge.summary) false
1241 set default_config(merge.verbosity) 2
1242 set default_config(user.name) {}
1243 set default_config(user.email) {}
1245 set default_config(gui.trustmtime) false
1246 set default_config(gui.diffcontext) 5
1247 set default_config(gui.newbranchtemplate) {}
1248 set default_config(gui.fontui) [font configure font_ui]
1249 set default_config(gui.fontdiff) [font configure font_diff]
1250 set font_descs {
1251 {fontui font_ui {Main Font}}
1252 {fontdiff font_diff {Diff/Console Font}}
1254 load_config 0
1255 apply_config
1257 ######################################################################
1259 ## feature option selection
1261 if {[regexp {^git-(.+)$} [appname] _junk subcommand]} {
1262 unset _junk
1263 } else {
1264 set subcommand gui
1266 if {$subcommand eq {gui.sh}} {
1267 set subcommand gui
1269 if {$subcommand eq {gui} && [llength $argv] > 0} {
1270 set subcommand [lindex $argv 0]
1271 set argv [lrange $argv 1 end]
1274 enable_option multicommit
1275 enable_option branch
1276 enable_option transport
1278 switch -- $subcommand {
1279 browser -
1280 blame {
1281 disable_option multicommit
1282 disable_option branch
1283 disable_option transport
1285 citool {
1286 enable_option singlecommit
1288 disable_option multicommit
1289 disable_option branch
1290 disable_option transport
1294 ######################################################################
1296 ## ui construction
1298 set ui_comm {}
1300 # -- Menu Bar
1302 menu .mbar -tearoff 0
1303 .mbar add cascade -label Repository -menu .mbar.repository
1304 .mbar add cascade -label Edit -menu .mbar.edit
1305 if {[is_enabled branch]} {
1306 .mbar add cascade -label Branch -menu .mbar.branch
1308 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1309 .mbar add cascade -label Commit -menu .mbar.commit
1311 if {[is_enabled transport]} {
1312 .mbar add cascade -label Merge -menu .mbar.merge
1313 .mbar add cascade -label Fetch -menu .mbar.fetch
1314 .mbar add cascade -label Push -menu .mbar.push
1316 . configure -menu .mbar
1318 # -- Repository Menu
1320 menu .mbar.repository
1322 .mbar.repository add command \
1323 -label {Browse Current Branch} \
1324 -command {browser::new $current_branch}
1325 trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#"
1326 .mbar.repository add separator
1328 .mbar.repository add command \
1329 -label {Visualize Current Branch} \
1330 -command {do_gitk $current_branch}
1331 trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch\" ;#"
1332 .mbar.repository add command \
1333 -label {Visualize All Branches} \
1334 -command {do_gitk --all}
1335 .mbar.repository add separator
1337 if {[is_enabled multicommit]} {
1338 .mbar.repository add command -label {Database Statistics} \
1339 -command do_stats
1341 .mbar.repository add command -label {Compress Database} \
1342 -command do_gc
1344 .mbar.repository add command -label {Verify Database} \
1345 -command do_fsck_objects
1347 .mbar.repository add separator
1349 if {[is_Cygwin]} {
1350 .mbar.repository add command \
1351 -label {Create Desktop Icon} \
1352 -command do_cygwin_shortcut
1353 } elseif {[is_Windows]} {
1354 .mbar.repository add command \
1355 -label {Create Desktop Icon} \
1356 -command do_windows_shortcut
1357 } elseif {[is_MacOSX]} {
1358 .mbar.repository add command \
1359 -label {Create Desktop Icon} \
1360 -command do_macosx_app
1364 .mbar.repository add command -label Quit \
1365 -command do_quit \
1366 -accelerator $M1T-Q
1368 # -- Edit Menu
1370 menu .mbar.edit
1371 .mbar.edit add command -label Undo \
1372 -command {catch {[focus] edit undo}} \
1373 -accelerator $M1T-Z
1374 .mbar.edit add command -label Redo \
1375 -command {catch {[focus] edit redo}} \
1376 -accelerator $M1T-Y
1377 .mbar.edit add separator
1378 .mbar.edit add command -label Cut \
1379 -command {catch {tk_textCut [focus]}} \
1380 -accelerator $M1T-X
1381 .mbar.edit add command -label Copy \
1382 -command {catch {tk_textCopy [focus]}} \
1383 -accelerator $M1T-C
1384 .mbar.edit add command -label Paste \
1385 -command {catch {tk_textPaste [focus]; [focus] see insert}} \
1386 -accelerator $M1T-V
1387 .mbar.edit add command -label Delete \
1388 -command {catch {[focus] delete sel.first sel.last}} \
1389 -accelerator Del
1390 .mbar.edit add separator
1391 .mbar.edit add command -label {Select All} \
1392 -command {catch {[focus] tag add sel 0.0 end}} \
1393 -accelerator $M1T-A
1395 # -- Branch Menu
1397 if {[is_enabled branch]} {
1398 menu .mbar.branch
1400 .mbar.branch add command -label {Create...} \
1401 -command do_create_branch \
1402 -accelerator $M1T-N
1403 lappend disable_on_lock [list .mbar.branch entryconf \
1404 [.mbar.branch index last] -state]
1406 .mbar.branch add command -label {Delete...} \
1407 -command do_delete_branch
1408 lappend disable_on_lock [list .mbar.branch entryconf \
1409 [.mbar.branch index last] -state]
1411 .mbar.branch add command -label {Reset...} \
1412 -command merge::reset_hard
1413 lappend disable_on_lock [list .mbar.branch entryconf \
1414 [.mbar.branch index last] -state]
1417 # -- Commit Menu
1419 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1420 menu .mbar.commit
1422 .mbar.commit add radiobutton \
1423 -label {New Commit} \
1424 -command do_select_commit_type \
1425 -variable selected_commit_type \
1426 -value new
1427 lappend disable_on_lock \
1428 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1430 .mbar.commit add radiobutton \
1431 -label {Amend Last Commit} \
1432 -command do_select_commit_type \
1433 -variable selected_commit_type \
1434 -value amend
1435 lappend disable_on_lock \
1436 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1438 .mbar.commit add separator
1440 .mbar.commit add command -label Rescan \
1441 -command do_rescan \
1442 -accelerator F5
1443 lappend disable_on_lock \
1444 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1446 .mbar.commit add command -label {Add To Commit} \
1447 -command do_add_selection
1448 lappend disable_on_lock \
1449 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1451 .mbar.commit add command -label {Add Existing To Commit} \
1452 -command do_add_all \
1453 -accelerator $M1T-I
1454 lappend disable_on_lock \
1455 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1457 .mbar.commit add command -label {Unstage From Commit} \
1458 -command do_unstage_selection
1459 lappend disable_on_lock \
1460 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1462 .mbar.commit add command -label {Revert Changes} \
1463 -command do_revert_selection
1464 lappend disable_on_lock \
1465 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1467 .mbar.commit add separator
1469 .mbar.commit add command -label {Sign Off} \
1470 -command do_signoff \
1471 -accelerator $M1T-S
1473 .mbar.commit add command -label Commit \
1474 -command do_commit \
1475 -accelerator $M1T-Return
1476 lappend disable_on_lock \
1477 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1480 # -- Merge Menu
1482 if {[is_enabled branch]} {
1483 menu .mbar.merge
1484 .mbar.merge add command -label {Local Merge...} \
1485 -command merge::dialog
1486 lappend disable_on_lock \
1487 [list .mbar.merge entryconf [.mbar.merge index last] -state]
1488 .mbar.merge add command -label {Abort Merge...} \
1489 -command merge::reset_hard
1490 lappend disable_on_lock \
1491 [list .mbar.merge entryconf [.mbar.merge index last] -state]
1495 # -- Transport Menu
1497 if {[is_enabled transport]} {
1498 menu .mbar.fetch
1500 menu .mbar.push
1501 .mbar.push add command -label {Push...} \
1502 -command do_push_anywhere
1505 if {[is_MacOSX]} {
1506 # -- Apple Menu (Mac OS X only)
1508 .mbar add cascade -label Apple -menu .mbar.apple
1509 menu .mbar.apple
1511 .mbar.apple add command -label "About [appname]" \
1512 -command do_about
1513 .mbar.apple add command -label "Options..." \
1514 -command do_options
1515 } else {
1516 # -- Edit Menu
1518 .mbar.edit add separator
1519 .mbar.edit add command -label {Options...} \
1520 -command do_options
1522 # -- Tools Menu
1524 if {[file exists /usr/local/miga/lib/gui-miga]
1525 && [file exists .pvcsrc]} {
1526 proc do_miga {} {
1527 global ui_status_value
1528 if {![lock_index update]} return
1529 set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""]
1530 set miga_fd [open "|$cmd" r]
1531 fconfigure $miga_fd -blocking 0
1532 fileevent $miga_fd readable [list miga_done $miga_fd]
1533 set ui_status_value {Running miga...}
1535 proc miga_done {fd} {
1536 read $fd 512
1537 if {[eof $fd]} {
1538 close $fd
1539 unlock_index
1540 rescan [list set ui_status_value {Ready.}]
1543 .mbar add cascade -label Tools -menu .mbar.tools
1544 menu .mbar.tools
1545 .mbar.tools add command -label "Migrate" \
1546 -command do_miga
1547 lappend disable_on_lock \
1548 [list .mbar.tools entryconf [.mbar.tools index last] -state]
1552 # -- Help Menu
1554 .mbar add cascade -label Help -menu .mbar.help
1555 menu .mbar.help
1557 if {![is_MacOSX]} {
1558 .mbar.help add command -label "About [appname]" \
1559 -command do_about
1562 set browser {}
1563 catch {set browser $repo_config(instaweb.browser)}
1564 set doc_path [file dirname [gitexec]]
1565 set doc_path [file join $doc_path Documentation index.html]
1567 if {[is_Cygwin]} {
1568 set doc_path [exec cygpath --mixed $doc_path]
1571 if {$browser eq {}} {
1572 if {[is_MacOSX]} {
1573 set browser open
1574 } elseif {[is_Cygwin]} {
1575 set program_files [file dirname [exec cygpath --windir]]
1576 set program_files [file join $program_files {Program Files}]
1577 set firefox [file join $program_files {Mozilla Firefox} firefox.exe]
1578 set ie [file join $program_files {Internet Explorer} IEXPLORE.EXE]
1579 if {[file exists $firefox]} {
1580 set browser $firefox
1581 } elseif {[file exists $ie]} {
1582 set browser $ie
1584 unset program_files firefox ie
1588 if {[file isfile $doc_path]} {
1589 set doc_url "file:$doc_path"
1590 } else {
1591 set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
1594 if {$browser ne {}} {
1595 .mbar.help add command -label {Online Documentation} \
1596 -command [list exec $browser $doc_url &]
1598 unset browser doc_path doc_url
1600 # -- Standard bindings
1602 bind . <Destroy> do_quit
1603 bind all <$M1B-Key-q> do_quit
1604 bind all <$M1B-Key-Q> do_quit
1605 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
1606 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
1608 set subcommand_args {}
1609 proc usage {} {
1610 puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
1611 exit 1
1614 # -- Not a normal commit type invocation? Do that instead!
1616 switch -- $subcommand {
1617 browser {
1618 set subcommand_args {rev?}
1619 switch [llength $argv] {
1621 set current_branch [git symbolic-ref HEAD]
1622 regsub ^refs/((heads|tags|remotes)/)? \
1623 $current_branch {} current_branch
1626 set current_branch [lindex $argv 0]
1628 default usage
1630 browser::new $current_branch
1631 return
1633 blame {
1634 set subcommand_args {rev? path?}
1635 set head {}
1636 set path {}
1637 set is_path 0
1638 foreach a $argv {
1639 if {$is_path || [file exists $_prefix$a]} {
1640 if {$path ne {}} usage
1641 set path $_prefix$a
1642 break
1643 } elseif {$a eq {--}} {
1644 if {$path ne {}} {
1645 if {$head ne {}} usage
1646 set head $path
1647 set path {}
1649 set is_path 1
1650 } elseif {$head eq {}} {
1651 if {$head ne {}} usage
1652 set head $a
1653 } else {
1654 usage
1657 unset is_path
1659 if {$head eq {}} {
1660 set current_branch [git symbolic-ref HEAD]
1661 regsub ^refs/((heads|tags|remotes)/)? \
1662 $current_branch {} current_branch
1663 } else {
1664 set current_branch $head
1667 if {$path eq {}} usage
1668 blame::new $head $path
1669 return
1671 citool -
1672 gui {
1673 if {[llength $argv] != 0} {
1674 puts -nonewline stderr "usage: $argv0"
1675 if {$subcommand ne {gui} && [appname] ne "git-$subcommand"} {
1676 puts -nonewline stderr " $subcommand"
1678 puts stderr {}
1679 exit 1
1681 # fall through to setup UI for commits
1683 default {
1684 puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
1685 exit 1
1689 # -- Branch Control
1691 frame .branch \
1692 -borderwidth 1 \
1693 -relief sunken
1694 label .branch.l1 \
1695 -text {Current Branch:} \
1696 -anchor w \
1697 -justify left
1698 label .branch.cb \
1699 -textvariable current_branch \
1700 -anchor w \
1701 -justify left
1702 pack .branch.l1 -side left
1703 pack .branch.cb -side left -fill x
1704 pack .branch -side top -fill x
1706 # -- Main Window Layout
1708 panedwindow .vpane -orient vertical
1709 panedwindow .vpane.files -orient horizontal
1710 .vpane add .vpane.files -sticky nsew -height 100 -width 200
1711 pack .vpane -anchor n -side top -fill both -expand 1
1713 # -- Index File List
1715 frame .vpane.files.index -height 100 -width 200
1716 label .vpane.files.index.title -text {Staged Changes (Will Be Committed)} \
1717 -background green
1718 text $ui_index -background white -borderwidth 0 \
1719 -width 20 -height 10 \
1720 -wrap none \
1721 -cursor $cursor_ptr \
1722 -xscrollcommand {.vpane.files.index.sx set} \
1723 -yscrollcommand {.vpane.files.index.sy set} \
1724 -state disabled
1725 scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
1726 scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
1727 pack .vpane.files.index.title -side top -fill x
1728 pack .vpane.files.index.sx -side bottom -fill x
1729 pack .vpane.files.index.sy -side right -fill y
1730 pack $ui_index -side left -fill both -expand 1
1731 .vpane.files add .vpane.files.index -sticky nsew
1733 # -- Working Directory File List
1735 frame .vpane.files.workdir -height 100 -width 200
1736 label .vpane.files.workdir.title -text {Unstaged Changes (Will Not Be Committed)} \
1737 -background red
1738 text $ui_workdir -background white -borderwidth 0 \
1739 -width 20 -height 10 \
1740 -wrap none \
1741 -cursor $cursor_ptr \
1742 -xscrollcommand {.vpane.files.workdir.sx set} \
1743 -yscrollcommand {.vpane.files.workdir.sy set} \
1744 -state disabled
1745 scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
1746 scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
1747 pack .vpane.files.workdir.title -side top -fill x
1748 pack .vpane.files.workdir.sx -side bottom -fill x
1749 pack .vpane.files.workdir.sy -side right -fill y
1750 pack $ui_workdir -side left -fill both -expand 1
1751 .vpane.files add .vpane.files.workdir -sticky nsew
1753 foreach i [list $ui_index $ui_workdir] {
1754 $i tag conf in_diff -font font_uibold
1755 $i tag conf in_sel \
1756 -background [$i cget -foreground] \
1757 -foreground [$i cget -background]
1759 unset i
1761 # -- Diff and Commit Area
1763 frame .vpane.lower -height 300 -width 400
1764 frame .vpane.lower.commarea
1765 frame .vpane.lower.diff -relief sunken -borderwidth 1
1766 pack .vpane.lower.commarea -side top -fill x
1767 pack .vpane.lower.diff -side bottom -fill both -expand 1
1768 .vpane add .vpane.lower -sticky nsew
1770 # -- Commit Area Buttons
1772 frame .vpane.lower.commarea.buttons
1773 label .vpane.lower.commarea.buttons.l -text {} \
1774 -anchor w \
1775 -justify left
1776 pack .vpane.lower.commarea.buttons.l -side top -fill x
1777 pack .vpane.lower.commarea.buttons -side left -fill y
1779 button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
1780 -command do_rescan
1781 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
1782 lappend disable_on_lock \
1783 {.vpane.lower.commarea.buttons.rescan conf -state}
1785 button .vpane.lower.commarea.buttons.incall -text {Add Existing} \
1786 -command do_add_all
1787 pack .vpane.lower.commarea.buttons.incall -side top -fill x
1788 lappend disable_on_lock \
1789 {.vpane.lower.commarea.buttons.incall conf -state}
1791 button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
1792 -command do_signoff
1793 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
1795 button .vpane.lower.commarea.buttons.commit -text {Commit} \
1796 -command do_commit
1797 pack .vpane.lower.commarea.buttons.commit -side top -fill x
1798 lappend disable_on_lock \
1799 {.vpane.lower.commarea.buttons.commit conf -state}
1801 # -- Commit Message Buffer
1803 frame .vpane.lower.commarea.buffer
1804 frame .vpane.lower.commarea.buffer.header
1805 set ui_comm .vpane.lower.commarea.buffer.t
1806 set ui_coml .vpane.lower.commarea.buffer.header.l
1807 radiobutton .vpane.lower.commarea.buffer.header.new \
1808 -text {New Commit} \
1809 -command do_select_commit_type \
1810 -variable selected_commit_type \
1811 -value new
1812 lappend disable_on_lock \
1813 [list .vpane.lower.commarea.buffer.header.new conf -state]
1814 radiobutton .vpane.lower.commarea.buffer.header.amend \
1815 -text {Amend Last Commit} \
1816 -command do_select_commit_type \
1817 -variable selected_commit_type \
1818 -value amend
1819 lappend disable_on_lock \
1820 [list .vpane.lower.commarea.buffer.header.amend conf -state]
1821 label $ui_coml \
1822 -anchor w \
1823 -justify left
1824 proc trace_commit_type {varname args} {
1825 global ui_coml commit_type
1826 switch -glob -- $commit_type {
1827 initial {set txt {Initial Commit Message:}}
1828 amend {set txt {Amended Commit Message:}}
1829 amend-initial {set txt {Amended Initial Commit Message:}}
1830 amend-merge {set txt {Amended Merge Commit Message:}}
1831 merge {set txt {Merge Commit Message:}}
1832 * {set txt {Commit Message:}}
1834 $ui_coml conf -text $txt
1836 trace add variable commit_type write trace_commit_type
1837 pack $ui_coml -side left -fill x
1838 pack .vpane.lower.commarea.buffer.header.amend -side right
1839 pack .vpane.lower.commarea.buffer.header.new -side right
1841 text $ui_comm -background white -borderwidth 1 \
1842 -undo true \
1843 -maxundo 20 \
1844 -autoseparators true \
1845 -relief sunken \
1846 -width 75 -height 9 -wrap none \
1847 -font font_diff \
1848 -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
1849 scrollbar .vpane.lower.commarea.buffer.sby \
1850 -command [list $ui_comm yview]
1851 pack .vpane.lower.commarea.buffer.header -side top -fill x
1852 pack .vpane.lower.commarea.buffer.sby -side right -fill y
1853 pack $ui_comm -side left -fill y
1854 pack .vpane.lower.commarea.buffer -side left -fill y
1856 # -- Commit Message Buffer Context Menu
1858 set ctxm .vpane.lower.commarea.buffer.ctxm
1859 menu $ctxm -tearoff 0
1860 $ctxm add command \
1861 -label {Cut} \
1862 -command {tk_textCut $ui_comm}
1863 $ctxm add command \
1864 -label {Copy} \
1865 -command {tk_textCopy $ui_comm}
1866 $ctxm add command \
1867 -label {Paste} \
1868 -command {tk_textPaste $ui_comm}
1869 $ctxm add command \
1870 -label {Delete} \
1871 -command {$ui_comm delete sel.first sel.last}
1872 $ctxm add separator
1873 $ctxm add command \
1874 -label {Select All} \
1875 -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
1876 $ctxm add command \
1877 -label {Copy All} \
1878 -command {
1879 $ui_comm tag add sel 0.0 end
1880 tk_textCopy $ui_comm
1881 $ui_comm tag remove sel 0.0 end
1883 $ctxm add separator
1884 $ctxm add command \
1885 -label {Sign Off} \
1886 -command do_signoff
1887 bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
1889 # -- Diff Header
1891 proc trace_current_diff_path {varname args} {
1892 global current_diff_path diff_actions file_states
1893 if {$current_diff_path eq {}} {
1894 set s {}
1895 set f {}
1896 set p {}
1897 set o disabled
1898 } else {
1899 set p $current_diff_path
1900 set s [mapdesc [lindex $file_states($p) 0] $p]
1901 set f {File:}
1902 set p [escape_path $p]
1903 set o normal
1906 .vpane.lower.diff.header.status configure -text $s
1907 .vpane.lower.diff.header.file configure -text $f
1908 .vpane.lower.diff.header.path configure -text $p
1909 foreach w $diff_actions {
1910 uplevel #0 $w $o
1913 trace add variable current_diff_path write trace_current_diff_path
1915 frame .vpane.lower.diff.header -background orange
1916 label .vpane.lower.diff.header.status \
1917 -background orange \
1918 -width $max_status_desc \
1919 -anchor w \
1920 -justify left
1921 label .vpane.lower.diff.header.file \
1922 -background orange \
1923 -anchor w \
1924 -justify left
1925 label .vpane.lower.diff.header.path \
1926 -background orange \
1927 -anchor w \
1928 -justify left
1929 pack .vpane.lower.diff.header.status -side left
1930 pack .vpane.lower.diff.header.file -side left
1931 pack .vpane.lower.diff.header.path -fill x
1932 set ctxm .vpane.lower.diff.header.ctxm
1933 menu $ctxm -tearoff 0
1934 $ctxm add command \
1935 -label {Copy} \
1936 -command {
1937 clipboard clear
1938 clipboard append \
1939 -format STRING \
1940 -type STRING \
1941 -- $current_diff_path
1943 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
1944 bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
1946 # -- Diff Body
1948 frame .vpane.lower.diff.body
1949 set ui_diff .vpane.lower.diff.body.t
1950 text $ui_diff -background white -borderwidth 0 \
1951 -width 80 -height 15 -wrap none \
1952 -font font_diff \
1953 -xscrollcommand {.vpane.lower.diff.body.sbx set} \
1954 -yscrollcommand {.vpane.lower.diff.body.sby set} \
1955 -state disabled
1956 scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
1957 -command [list $ui_diff xview]
1958 scrollbar .vpane.lower.diff.body.sby -orient vertical \
1959 -command [list $ui_diff yview]
1960 pack .vpane.lower.diff.body.sbx -side bottom -fill x
1961 pack .vpane.lower.diff.body.sby -side right -fill y
1962 pack $ui_diff -side left -fill both -expand 1
1963 pack .vpane.lower.diff.header -side top -fill x
1964 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
1966 $ui_diff tag conf d_cr -elide true
1967 $ui_diff tag conf d_@ -foreground blue -font font_diffbold
1968 $ui_diff tag conf d_+ -foreground {#00a000}
1969 $ui_diff tag conf d_- -foreground red
1971 $ui_diff tag conf d_++ -foreground {#00a000}
1972 $ui_diff tag conf d_-- -foreground red
1973 $ui_diff tag conf d_+s \
1974 -foreground {#00a000} \
1975 -background {#e2effa}
1976 $ui_diff tag conf d_-s \
1977 -foreground red \
1978 -background {#e2effa}
1979 $ui_diff tag conf d_s+ \
1980 -foreground {#00a000} \
1981 -background ivory1
1982 $ui_diff tag conf d_s- \
1983 -foreground red \
1984 -background ivory1
1986 $ui_diff tag conf d<<<<<<< \
1987 -foreground orange \
1988 -font font_diffbold
1989 $ui_diff tag conf d======= \
1990 -foreground orange \
1991 -font font_diffbold
1992 $ui_diff tag conf d>>>>>>> \
1993 -foreground orange \
1994 -font font_diffbold
1996 $ui_diff tag raise sel
1998 # -- Diff Body Context Menu
2000 set ctxm .vpane.lower.diff.body.ctxm
2001 menu $ctxm -tearoff 0
2002 $ctxm add command \
2003 -label {Refresh} \
2004 -command reshow_diff
2005 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2006 $ctxm add command \
2007 -label {Copy} \
2008 -command {tk_textCopy $ui_diff}
2009 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2010 $ctxm add command \
2011 -label {Select All} \
2012 -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
2013 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2014 $ctxm add command \
2015 -label {Copy All} \
2016 -command {
2017 $ui_diff tag add sel 0.0 end
2018 tk_textCopy $ui_diff
2019 $ui_diff tag remove sel 0.0 end
2021 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2022 $ctxm add separator
2023 $ctxm add command \
2024 -label {Apply/Reverse Hunk} \
2025 -command {apply_hunk $cursorX $cursorY}
2026 set ui_diff_applyhunk [$ctxm index last]
2027 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
2028 $ctxm add separator
2029 $ctxm add command \
2030 -label {Decrease Font Size} \
2031 -command {incr_font_size font_diff -1}
2032 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2033 $ctxm add command \
2034 -label {Increase Font Size} \
2035 -command {incr_font_size font_diff 1}
2036 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2037 $ctxm add separator
2038 $ctxm add command \
2039 -label {Show Less Context} \
2040 -command {if {$repo_config(gui.diffcontext) >= 2} {
2041 incr repo_config(gui.diffcontext) -1
2042 reshow_diff
2044 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2045 $ctxm add command \
2046 -label {Show More Context} \
2047 -command {
2048 incr repo_config(gui.diffcontext)
2049 reshow_diff
2051 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2052 $ctxm add separator
2053 $ctxm add command -label {Options...} \
2054 -command do_options
2055 bind_button3 $ui_diff "
2056 set cursorX %x
2057 set cursorY %y
2058 if {\$ui_index eq \$current_diff_side} {
2059 $ctxm entryconf $ui_diff_applyhunk -label {Unstage Hunk From Commit}
2060 } else {
2061 $ctxm entryconf $ui_diff_applyhunk -label {Stage Hunk For Commit}
2063 tk_popup $ctxm %X %Y
2065 unset ui_diff_applyhunk
2067 # -- Status Bar
2069 label .status -textvariable ui_status_value \
2070 -anchor w \
2071 -justify left \
2072 -borderwidth 1 \
2073 -relief sunken
2074 pack .status -anchor w -side bottom -fill x
2076 # -- Load geometry
2078 catch {
2079 set gm $repo_config(gui.geometry)
2080 wm geometry . [lindex $gm 0]
2081 .vpane sash place 0 \
2082 [lindex [.vpane sash coord 0] 0] \
2083 [lindex $gm 1]
2084 .vpane.files sash place 0 \
2085 [lindex $gm 2] \
2086 [lindex [.vpane.files sash coord 0] 1]
2087 unset gm
2090 # -- Key Bindings
2092 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2093 bind $ui_comm <$M1B-Key-i> {do_add_all;break}
2094 bind $ui_comm <$M1B-Key-I> {do_add_all;break}
2095 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2096 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2097 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2098 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2099 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2100 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2101 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2102 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2104 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2105 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2106 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2107 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2108 bind $ui_diff <$M1B-Key-v> {break}
2109 bind $ui_diff <$M1B-Key-V> {break}
2110 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2111 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2112 bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break}
2113 bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break}
2114 bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break}
2115 bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break}
2116 bind $ui_diff <Key-k> {catch {%W yview scroll -1 units};break}
2117 bind $ui_diff <Key-j> {catch {%W yview scroll 1 units};break}
2118 bind $ui_diff <Key-h> {catch {%W xview scroll -1 units};break}
2119 bind $ui_diff <Key-l> {catch {%W xview scroll 1 units};break}
2120 bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
2121 bind $ui_diff <Control-Key-f> {catch {%W yview scroll 1 pages};break}
2122 bind $ui_diff <Button-1> {focus %W}
2124 if {[is_enabled branch]} {
2125 bind . <$M1B-Key-n> do_create_branch
2126 bind . <$M1B-Key-N> do_create_branch
2129 bind all <Key-F5> do_rescan
2130 bind all <$M1B-Key-r> do_rescan
2131 bind all <$M1B-Key-R> do_rescan
2132 bind . <$M1B-Key-s> do_signoff
2133 bind . <$M1B-Key-S> do_signoff
2134 bind . <$M1B-Key-i> do_add_all
2135 bind . <$M1B-Key-I> do_add_all
2136 bind . <$M1B-Key-Return> do_commit
2137 foreach i [list $ui_index $ui_workdir] {
2138 bind $i <Button-1> "toggle_or_diff $i %x %y; break"
2139 bind $i <$M1B-Button-1> "add_one_to_selection $i %x %y; break"
2140 bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
2142 unset i
2144 set file_lists($ui_index) [list]
2145 set file_lists($ui_workdir) [list]
2147 wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
2148 focus -force $ui_comm
2150 # -- Warn the user about environmental problems. Cygwin's Tcl
2151 # does *not* pass its env array onto any processes it spawns.
2152 # This means that git processes get none of our environment.
2154 if {[is_Cygwin]} {
2155 set ignored_env 0
2156 set suggest_user {}
2157 set msg "Possible environment issues exist.
2159 The following environment variables are probably
2160 going to be ignored by any Git subprocess run
2161 by [appname]:
2164 foreach name [array names env] {
2165 switch -regexp -- $name {
2166 {^GIT_INDEX_FILE$} -
2167 {^GIT_OBJECT_DIRECTORY$} -
2168 {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
2169 {^GIT_DIFF_OPTS$} -
2170 {^GIT_EXTERNAL_DIFF$} -
2171 {^GIT_PAGER$} -
2172 {^GIT_TRACE$} -
2173 {^GIT_CONFIG$} -
2174 {^GIT_CONFIG_LOCAL$} -
2175 {^GIT_(AUTHOR|COMMITTER)_DATE$} {
2176 append msg " - $name\n"
2177 incr ignored_env
2179 {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
2180 append msg " - $name\n"
2181 incr ignored_env
2182 set suggest_user $name
2186 if {$ignored_env > 0} {
2187 append msg "
2188 This is due to a known issue with the
2189 Tcl binary distributed by Cygwin."
2191 if {$suggest_user ne {}} {
2192 append msg "
2194 A good replacement for $suggest_user
2195 is placing values for the user.name and
2196 user.email settings into your personal
2197 ~/.gitconfig file.
2200 warn_popup $msg
2202 unset ignored_env msg suggest_user name
2205 # -- Only initialize complex UI if we are going to stay running.
2207 if {[is_enabled transport]} {
2208 load_all_remotes
2209 load_all_heads
2211 populate_branch_menu
2212 populate_fetch_menu
2213 populate_push_menu
2216 # -- Only suggest a gc run if we are going to stay running.
2218 if {[is_enabled multicommit]} {
2219 set object_limit 2000
2220 if {[is_Windows]} {set object_limit 200}
2221 regexp {^([0-9]+) objects,} [git count-objects] _junk objects_current
2222 if {$objects_current >= $object_limit} {
2223 if {[ask_popup \
2224 "This repository currently has $objects_current loose objects.
2226 To maintain optimal performance it is strongly recommended that you compress the database when more than $object_limit loose objects exist.
2228 Compress the database now?"] eq yes} {
2229 do_gc
2232 unset object_limit _junk objects_current
2235 lock_index begin-read
2236 after 1 do_rescan