git-gui: Show the git-gui library path in 'About git-gui'
[git-gui.git] / git-gui.sh
blobc1a6b84fa44262dd3410dafd064a14e32191d25c
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 set oguirel {@@GITGUI_RELATIVE@@}
29 if {$oguirel eq {1}} {
30 set oguilib [file dirname [file dirname [file normalize $argv0]]]
31 set oguilib [file join $oguilib share git-gui lib]
32 } elseif {[string match @@* $oguirel]} {
33 set oguilib [file join [file dirname [file normalize $argv0]] lib]
35 set idx [file join $oguilib tclIndex]
36 catch {
37 set fd [open $idx r]
38 if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
39 set idx [list]
40 while {[gets $fd n] >= 0} {
41 if {$n ne {} && ![string match #* $n]} {
42 lappend idx $n
45 } else {
46 set idx {}
48 close $fd
50 if {$idx ne {}} {
51 set loaded [list]
52 foreach p $idx {
53 if {[lsearch -exact $loaded $p] >= 0} continue
54 puts $p
55 source [file join $oguilib $p]
56 lappend loaded $p
58 unset loaded p
59 } else {
60 set auto_path [concat [list $oguilib] $auto_path]
62 unset -nocomplain oguirel idx fd
64 if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
65 unset _verbose
66 rename auto_load real__auto_load
67 proc auto_load {name args} {
68 puts stderr "auto_load $name"
69 return [uplevel 1 real__auto_load $name $args]
71 rename source real__source
72 proc source {name} {
73 puts stderr "source $name"
74 uplevel 1 real__source $name
78 ######################################################################
80 ## read only globals
82 set _appname [lindex [file split $argv0] end]
83 set _gitdir {}
84 set _gitexec {}
85 set _reponame {}
86 set _iscygwin {}
88 proc appname {} {
89 global _appname
90 return $_appname
93 proc gitdir {args} {
94 global _gitdir
95 if {$args eq {}} {
96 return $_gitdir
98 return [eval [concat [list file join $_gitdir] $args]]
101 proc gitexec {args} {
102 global _gitexec
103 if {$_gitexec eq {}} {
104 if {[catch {set _gitexec [git --exec-path]} err]} {
105 error "Git not installed?\n\n$err"
108 if {$args eq {}} {
109 return $_gitexec
111 return [eval [concat [list file join $_gitexec] $args]]
114 proc reponame {} {
115 global _reponame
116 return $_reponame
119 proc is_MacOSX {} {
120 global tcl_platform tk_library
121 if {[tk windowingsystem] eq {aqua}} {
122 return 1
124 return 0
127 proc is_Windows {} {
128 global tcl_platform
129 if {$tcl_platform(platform) eq {windows}} {
130 return 1
132 return 0
135 proc is_Cygwin {} {
136 global tcl_platform _iscygwin
137 if {$_iscygwin eq {}} {
138 if {$tcl_platform(platform) eq {windows}} {
139 if {[catch {set p [exec cygpath --windir]} err]} {
140 set _iscygwin 0
141 } else {
142 set _iscygwin 1
144 } else {
145 set _iscygwin 0
148 return $_iscygwin
151 proc is_enabled {option} {
152 global enabled_options
153 if {[catch {set on $enabled_options($option)}]} {return 0}
154 return $on
157 proc enable_option {option} {
158 global enabled_options
159 set enabled_options($option) 1
162 proc disable_option {option} {
163 global enabled_options
164 set enabled_options($option) 0
167 ######################################################################
169 ## config
171 proc is_many_config {name} {
172 switch -glob -- $name {
173 remote.*.fetch -
174 remote.*.push
175 {return 1}
177 {return 0}
181 proc is_config_true {name} {
182 global repo_config
183 if {[catch {set v $repo_config($name)}]} {
184 return 0
185 } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
186 return 1
187 } else {
188 return 0
192 proc load_config {include_global} {
193 global repo_config global_config default_config
195 array unset global_config
196 if {$include_global} {
197 catch {
198 set fd_rc [open "| git config --global --list" r]
199 while {[gets $fd_rc line] >= 0} {
200 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
201 if {[is_many_config $name]} {
202 lappend global_config($name) $value
203 } else {
204 set global_config($name) $value
208 close $fd_rc
212 array unset repo_config
213 catch {
214 set fd_rc [open "| git config --list" r]
215 while {[gets $fd_rc line] >= 0} {
216 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
217 if {[is_many_config $name]} {
218 lappend repo_config($name) $value
219 } else {
220 set repo_config($name) $value
224 close $fd_rc
227 foreach name [array names default_config] {
228 if {[catch {set v $global_config($name)}]} {
229 set global_config($name) $default_config($name)
231 if {[catch {set v $repo_config($name)}]} {
232 set repo_config($name) $default_config($name)
237 ######################################################################
239 ## handy utils
241 proc git {args} {
242 return [eval exec git $args]
245 auto_load tk_optionMenu
246 rename tk_optionMenu real__tkOptionMenu
247 proc tk_optionMenu {w varName args} {
248 set m [eval real__tkOptionMenu $w $varName $args]
249 $m configure -font font_ui
250 $w configure -font font_ui
251 return $m
254 ######################################################################
256 ## version check
258 if {{--version} eq $argv || {version} eq $argv} {
259 puts "git-gui version $appvers"
260 exit
263 set req_maj 1
264 set req_min 5
266 if {[catch {set v [git --version]} err]} {
267 catch {wm withdraw .}
268 error_popup "Cannot determine Git version:
270 $err
272 [appname] requires Git $req_maj.$req_min or later."
273 exit 1
275 if {[regexp {^git version (\d+)\.(\d+)} $v _junk act_maj act_min]} {
276 if {$act_maj < $req_maj
277 || ($act_maj == $req_maj && $act_min < $req_min)} {
278 catch {wm withdraw .}
279 error_popup "[appname] requires Git $req_maj.$req_min or later.
281 You are using $v."
282 exit 1
284 } else {
285 catch {wm withdraw .}
286 error_popup "Cannot parse Git version string:\n\n$v"
287 exit 1
289 unset -nocomplain v _junk act_maj act_min req_maj req_min
291 ######################################################################
293 ## repository setup
295 if {[catch {
296 set _gitdir $env(GIT_DIR)
297 set _prefix {}
299 && [catch {
300 set _gitdir [git rev-parse --git-dir]
301 set _prefix [git rev-parse --show-prefix]
302 } err]} {
303 catch {wm withdraw .}
304 error_popup "Cannot find the git directory:\n\n$err"
305 exit 1
307 if {![file isdirectory $_gitdir] && [is_Cygwin]} {
308 catch {set _gitdir [exec cygpath --unix $_gitdir]}
310 if {![file isdirectory $_gitdir]} {
311 catch {wm withdraw .}
312 error_popup "Git directory not found:\n\n$_gitdir"
313 exit 1
315 if {[lindex [file split $_gitdir] end] ne {.git}} {
316 catch {wm withdraw .}
317 error_popup "Cannot use funny .git directory:\n\n$_gitdir"
318 exit 1
320 if {[catch {cd [file dirname $_gitdir]} err]} {
321 catch {wm withdraw .}
322 error_popup "No working directory [file dirname $_gitdir]:\n\n$err"
323 exit 1
325 set _reponame [lindex [file split \
326 [file normalize [file dirname $_gitdir]]] \
327 end]
329 ######################################################################
331 ## global init
333 set current_diff_path {}
334 set current_diff_side {}
335 set diff_actions [list]
336 set ui_status_value {Initializing...}
338 set HEAD {}
339 set PARENT {}
340 set MERGE_HEAD [list]
341 set commit_type {}
342 set empty_tree {}
343 set current_branch {}
344 set current_diff_path {}
345 set selected_commit_type new
347 ######################################################################
349 ## task management
351 set rescan_active 0
352 set diff_active 0
353 set last_clicked {}
355 set disable_on_lock [list]
356 set index_lock_type none
358 proc lock_index {type} {
359 global index_lock_type disable_on_lock
361 if {$index_lock_type eq {none}} {
362 set index_lock_type $type
363 foreach w $disable_on_lock {
364 uplevel #0 $w disabled
366 return 1
367 } elseif {$index_lock_type eq "begin-$type"} {
368 set index_lock_type $type
369 return 1
371 return 0
374 proc unlock_index {} {
375 global index_lock_type disable_on_lock
377 set index_lock_type none
378 foreach w $disable_on_lock {
379 uplevel #0 $w normal
383 ######################################################################
385 ## status
387 proc repository_state {ctvar hdvar mhvar} {
388 global current_branch
389 upvar $ctvar ct $hdvar hd $mhvar mh
391 set mh [list]
393 if {[catch {set current_branch [git symbolic-ref HEAD]}]} {
394 set current_branch {}
395 } else {
396 regsub ^refs/((heads|tags|remotes)/)? \
397 $current_branch \
398 {} \
399 current_branch
402 if {[catch {set hd [git rev-parse --verify HEAD]}]} {
403 set hd {}
404 set ct initial
405 return
408 set merge_head [gitdir MERGE_HEAD]
409 if {[file exists $merge_head]} {
410 set ct merge
411 set fd_mh [open $merge_head r]
412 while {[gets $fd_mh line] >= 0} {
413 lappend mh $line
415 close $fd_mh
416 return
419 set ct normal
422 proc PARENT {} {
423 global PARENT empty_tree
425 set p [lindex $PARENT 0]
426 if {$p ne {}} {
427 return $p
429 if {$empty_tree eq {}} {
430 set empty_tree [git mktree << {}]
432 return $empty_tree
435 proc rescan {after {honor_trustmtime 1}} {
436 global HEAD PARENT MERGE_HEAD commit_type
437 global ui_index ui_workdir ui_status_value ui_comm
438 global rescan_active file_states
439 global repo_config
441 if {$rescan_active > 0 || ![lock_index read]} return
443 repository_state newType newHEAD newMERGE_HEAD
444 if {[string match amend* $commit_type]
445 && $newType eq {normal}
446 && $newHEAD eq $HEAD} {
447 } else {
448 set HEAD $newHEAD
449 set PARENT $newHEAD
450 set MERGE_HEAD $newMERGE_HEAD
451 set commit_type $newType
454 array unset file_states
456 if {![$ui_comm edit modified]
457 || [string trim [$ui_comm get 0.0 end]] eq {}} {
458 if {[load_message GITGUI_MSG]} {
459 } elseif {[load_message MERGE_MSG]} {
460 } elseif {[load_message SQUASH_MSG]} {
462 $ui_comm edit reset
463 $ui_comm edit modified false
466 if {[is_enabled branch]} {
467 load_all_heads
468 populate_branch_menu
471 if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
472 rescan_stage2 {} $after
473 } else {
474 set rescan_active 1
475 set ui_status_value {Refreshing file status...}
476 set cmd [list git update-index]
477 lappend cmd -q
478 lappend cmd --unmerged
479 lappend cmd --ignore-missing
480 lappend cmd --refresh
481 set fd_rf [open "| $cmd" r]
482 fconfigure $fd_rf -blocking 0 -translation binary
483 fileevent $fd_rf readable \
484 [list rescan_stage2 $fd_rf $after]
488 proc rescan_stage2 {fd after} {
489 global ui_status_value
490 global rescan_active buf_rdi buf_rdf buf_rlo
492 if {$fd ne {}} {
493 read $fd
494 if {![eof $fd]} return
495 close $fd
498 set ls_others [list | git ls-files --others -z \
499 --exclude-per-directory=.gitignore]
500 set info_exclude [gitdir info exclude]
501 if {[file readable $info_exclude]} {
502 lappend ls_others "--exclude-from=$info_exclude"
505 set buf_rdi {}
506 set buf_rdf {}
507 set buf_rlo {}
509 set rescan_active 3
510 set ui_status_value {Scanning for modified files ...}
511 set fd_di [open "| git diff-index --cached -z [PARENT]" r]
512 set fd_df [open "| git diff-files -z" r]
513 set fd_lo [open $ls_others r]
515 fconfigure $fd_di -blocking 0 -translation binary -encoding binary
516 fconfigure $fd_df -blocking 0 -translation binary -encoding binary
517 fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
518 fileevent $fd_di readable [list read_diff_index $fd_di $after]
519 fileevent $fd_df readable [list read_diff_files $fd_df $after]
520 fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
523 proc load_message {file} {
524 global ui_comm
526 set f [gitdir $file]
527 if {[file isfile $f]} {
528 if {[catch {set fd [open $f r]}]} {
529 return 0
531 set content [string trim [read $fd]]
532 close $fd
533 regsub -all -line {[ \r\t]+$} $content {} content
534 $ui_comm delete 0.0 end
535 $ui_comm insert end $content
536 return 1
538 return 0
541 proc read_diff_index {fd after} {
542 global buf_rdi
544 append buf_rdi [read $fd]
545 set c 0
546 set n [string length $buf_rdi]
547 while {$c < $n} {
548 set z1 [string first "\0" $buf_rdi $c]
549 if {$z1 == -1} break
550 incr z1
551 set z2 [string first "\0" $buf_rdi $z1]
552 if {$z2 == -1} break
554 incr c
555 set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
556 set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
557 merge_state \
558 [encoding convertfrom $p] \
559 [lindex $i 4]? \
560 [list [lindex $i 0] [lindex $i 2]] \
561 [list]
562 set c $z2
563 incr c
565 if {$c < $n} {
566 set buf_rdi [string range $buf_rdi $c end]
567 } else {
568 set buf_rdi {}
571 rescan_done $fd buf_rdi $after
574 proc read_diff_files {fd after} {
575 global buf_rdf
577 append buf_rdf [read $fd]
578 set c 0
579 set n [string length $buf_rdf]
580 while {$c < $n} {
581 set z1 [string first "\0" $buf_rdf $c]
582 if {$z1 == -1} break
583 incr z1
584 set z2 [string first "\0" $buf_rdf $z1]
585 if {$z2 == -1} break
587 incr c
588 set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
589 set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
590 merge_state \
591 [encoding convertfrom $p] \
592 ?[lindex $i 4] \
593 [list] \
594 [list [lindex $i 0] [lindex $i 2]]
595 set c $z2
596 incr c
598 if {$c < $n} {
599 set buf_rdf [string range $buf_rdf $c end]
600 } else {
601 set buf_rdf {}
604 rescan_done $fd buf_rdf $after
607 proc read_ls_others {fd after} {
608 global buf_rlo
610 append buf_rlo [read $fd]
611 set pck [split $buf_rlo "\0"]
612 set buf_rlo [lindex $pck end]
613 foreach p [lrange $pck 0 end-1] {
614 merge_state [encoding convertfrom $p] ?O
616 rescan_done $fd buf_rlo $after
619 proc rescan_done {fd buf after} {
620 global rescan_active current_diff_path
621 global file_states repo_config
622 upvar $buf to_clear
624 if {![eof $fd]} return
625 set to_clear {}
626 close $fd
627 if {[incr rescan_active -1] > 0} return
629 prune_selection
630 unlock_index
631 display_all_files
632 if {$current_diff_path ne {}} reshow_diff
633 uplevel #0 $after
636 proc prune_selection {} {
637 global file_states selected_paths
639 foreach path [array names selected_paths] {
640 if {[catch {set still_here $file_states($path)}]} {
641 unset selected_paths($path)
646 ######################################################################
648 ## ui helpers
650 proc mapicon {w state path} {
651 global all_icons
653 if {[catch {set r $all_icons($state$w)}]} {
654 puts "error: no icon for $w state={$state} $path"
655 return file_plain
657 return $r
660 proc mapdesc {state path} {
661 global all_descs
663 if {[catch {set r $all_descs($state)}]} {
664 puts "error: no desc for state={$state} $path"
665 return $state
667 return $r
670 proc escape_path {path} {
671 regsub -all {\\} $path "\\\\" path
672 regsub -all "\n" $path "\\n" path
673 return $path
676 proc short_path {path} {
677 return [escape_path [lindex [file split $path] end]]
680 set next_icon_id 0
681 set null_sha1 [string repeat 0 40]
683 proc merge_state {path new_state {head_info {}} {index_info {}}} {
684 global file_states next_icon_id null_sha1
686 set s0 [string index $new_state 0]
687 set s1 [string index $new_state 1]
689 if {[catch {set info $file_states($path)}]} {
690 set state __
691 set icon n[incr next_icon_id]
692 } else {
693 set state [lindex $info 0]
694 set icon [lindex $info 1]
695 if {$head_info eq {}} {set head_info [lindex $info 2]}
696 if {$index_info eq {}} {set index_info [lindex $info 3]}
699 if {$s0 eq {?}} {set s0 [string index $state 0]} \
700 elseif {$s0 eq {_}} {set s0 _}
702 if {$s1 eq {?}} {set s1 [string index $state 1]} \
703 elseif {$s1 eq {_}} {set s1 _}
705 if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
706 set head_info [list 0 $null_sha1]
707 } elseif {$s0 ne {_} && [string index $state 0] eq {_}
708 && $head_info eq {}} {
709 set head_info $index_info
712 set file_states($path) [list $s0$s1 $icon \
713 $head_info $index_info \
715 return $state
718 proc display_file_helper {w path icon_name old_m new_m} {
719 global file_lists
721 if {$new_m eq {_}} {
722 set lno [lsearch -sorted -exact $file_lists($w) $path]
723 if {$lno >= 0} {
724 set file_lists($w) [lreplace $file_lists($w) $lno $lno]
725 incr lno
726 $w conf -state normal
727 $w delete $lno.0 [expr {$lno + 1}].0
728 $w conf -state disabled
730 } elseif {$old_m eq {_} && $new_m ne {_}} {
731 lappend file_lists($w) $path
732 set file_lists($w) [lsort -unique $file_lists($w)]
733 set lno [lsearch -sorted -exact $file_lists($w) $path]
734 incr lno
735 $w conf -state normal
736 $w image create $lno.0 \
737 -align center -padx 5 -pady 1 \
738 -name $icon_name \
739 -image [mapicon $w $new_m $path]
740 $w insert $lno.1 "[escape_path $path]\n"
741 $w conf -state disabled
742 } elseif {$old_m ne $new_m} {
743 $w conf -state normal
744 $w image conf $icon_name -image [mapicon $w $new_m $path]
745 $w conf -state disabled
749 proc display_file {path state} {
750 global file_states selected_paths
751 global ui_index ui_workdir
753 set old_m [merge_state $path $state]
754 set s $file_states($path)
755 set new_m [lindex $s 0]
756 set icon_name [lindex $s 1]
758 set o [string index $old_m 0]
759 set n [string index $new_m 0]
760 if {$o eq {U}} {
761 set o _
763 if {$n eq {U}} {
764 set n _
766 display_file_helper $ui_index $path $icon_name $o $n
768 if {[string index $old_m 0] eq {U}} {
769 set o U
770 } else {
771 set o [string index $old_m 1]
773 if {[string index $new_m 0] eq {U}} {
774 set n U
775 } else {
776 set n [string index $new_m 1]
778 display_file_helper $ui_workdir $path $icon_name $o $n
780 if {$new_m eq {__}} {
781 unset file_states($path)
782 catch {unset selected_paths($path)}
786 proc display_all_files_helper {w path icon_name m} {
787 global file_lists
789 lappend file_lists($w) $path
790 set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
791 $w image create end \
792 -align center -padx 5 -pady 1 \
793 -name $icon_name \
794 -image [mapicon $w $m $path]
795 $w insert end "[escape_path $path]\n"
798 proc display_all_files {} {
799 global ui_index ui_workdir
800 global file_states file_lists
801 global last_clicked
803 $ui_index conf -state normal
804 $ui_workdir conf -state normal
806 $ui_index delete 0.0 end
807 $ui_workdir delete 0.0 end
808 set last_clicked {}
810 set file_lists($ui_index) [list]
811 set file_lists($ui_workdir) [list]
813 foreach path [lsort [array names file_states]] {
814 set s $file_states($path)
815 set m [lindex $s 0]
816 set icon_name [lindex $s 1]
818 set s [string index $m 0]
819 if {$s ne {U} && $s ne {_}} {
820 display_all_files_helper $ui_index $path \
821 $icon_name $s
824 if {[string index $m 0] eq {U}} {
825 set s U
826 } else {
827 set s [string index $m 1]
829 if {$s ne {_}} {
830 display_all_files_helper $ui_workdir $path \
831 $icon_name $s
835 $ui_index conf -state disabled
836 $ui_workdir conf -state disabled
839 ######################################################################
841 ## icons
843 set filemask {
844 #define mask_width 14
845 #define mask_height 15
846 static unsigned char mask_bits[] = {
847 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
848 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
849 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
852 image create bitmap file_plain -background white -foreground black -data {
853 #define plain_width 14
854 #define plain_height 15
855 static unsigned char plain_bits[] = {
856 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
857 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
858 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
859 } -maskdata $filemask
861 image create bitmap file_mod -background white -foreground blue -data {
862 #define mod_width 14
863 #define mod_height 15
864 static unsigned char mod_bits[] = {
865 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
866 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
867 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
868 } -maskdata $filemask
870 image create bitmap file_fulltick -background white -foreground "#007000" -data {
871 #define file_fulltick_width 14
872 #define file_fulltick_height 15
873 static unsigned char file_fulltick_bits[] = {
874 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
875 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
876 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
877 } -maskdata $filemask
879 image create bitmap file_parttick -background white -foreground "#005050" -data {
880 #define parttick_width 14
881 #define parttick_height 15
882 static unsigned char parttick_bits[] = {
883 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
884 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
885 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
886 } -maskdata $filemask
888 image create bitmap file_question -background white -foreground black -data {
889 #define file_question_width 14
890 #define file_question_height 15
891 static unsigned char file_question_bits[] = {
892 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
893 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
894 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
895 } -maskdata $filemask
897 image create bitmap file_removed -background white -foreground red -data {
898 #define file_removed_width 14
899 #define file_removed_height 15
900 static unsigned char file_removed_bits[] = {
901 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
902 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
903 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
904 } -maskdata $filemask
906 image create bitmap file_merge -background white -foreground blue -data {
907 #define file_merge_width 14
908 #define file_merge_height 15
909 static unsigned char file_merge_bits[] = {
910 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
911 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
912 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
913 } -maskdata $filemask
915 set file_dir_data {
916 #define file_width 18
917 #define file_height 18
918 static unsigned char file_bits[] = {
919 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00,
920 0x0c, 0x03, 0x00, 0x04, 0xfe, 0x00, 0x06, 0x80, 0x00, 0xff, 0x9f, 0x00,
921 0x03, 0x98, 0x00, 0x02, 0x90, 0x00, 0x06, 0xb0, 0x00, 0x04, 0xa0, 0x00,
922 0x0c, 0xe0, 0x00, 0x08, 0xc0, 0x00, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00,
923 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
925 image create bitmap file_dir -background white -foreground blue \
926 -data $file_dir_data -maskdata $file_dir_data
927 unset file_dir_data
929 set file_uplevel_data {
930 #define up_width 15
931 #define up_height 15
932 static unsigned char up_bits[] = {
933 0x80, 0x00, 0xc0, 0x01, 0xe0, 0x03, 0xf0, 0x07, 0xf8, 0x0f, 0xfc, 0x1f,
934 0xfe, 0x3f, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01,
935 0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00};
937 image create bitmap file_uplevel -background white -foreground red \
938 -data $file_uplevel_data -maskdata $file_uplevel_data
939 unset file_uplevel_data
941 set ui_index .vpane.files.index.list
942 set ui_workdir .vpane.files.workdir.list
944 set all_icons(_$ui_index) file_plain
945 set all_icons(A$ui_index) file_fulltick
946 set all_icons(M$ui_index) file_fulltick
947 set all_icons(D$ui_index) file_removed
948 set all_icons(U$ui_index) file_merge
950 set all_icons(_$ui_workdir) file_plain
951 set all_icons(M$ui_workdir) file_mod
952 set all_icons(D$ui_workdir) file_question
953 set all_icons(U$ui_workdir) file_merge
954 set all_icons(O$ui_workdir) file_plain
956 set max_status_desc 0
957 foreach i {
958 {__ "Unmodified"}
960 {_M "Modified, not staged"}
961 {M_ "Staged for commit"}
962 {MM "Portions staged for commit"}
963 {MD "Staged for commit, missing"}
965 {_O "Untracked, not staged"}
966 {A_ "Staged for commit"}
967 {AM "Portions staged for commit"}
968 {AD "Staged for commit, missing"}
970 {_D "Missing"}
971 {D_ "Staged for removal"}
972 {DO "Staged for removal, still present"}
974 {U_ "Requires merge resolution"}
975 {UU "Requires merge resolution"}
976 {UM "Requires merge resolution"}
977 {UD "Requires merge resolution"}
979 if {$max_status_desc < [string length [lindex $i 1]]} {
980 set max_status_desc [string length [lindex $i 1]]
982 set all_descs([lindex $i 0]) [lindex $i 1]
984 unset i
986 ######################################################################
988 ## util
990 proc bind_button3 {w cmd} {
991 bind $w <Any-Button-3> $cmd
992 if {[is_MacOSX]} {
993 bind $w <Control-Button-1> $cmd
997 proc scrollbar2many {list mode args} {
998 foreach w $list {eval $w $mode $args}
1001 proc many2scrollbar {list mode sb top bottom} {
1002 $sb set $top $bottom
1003 foreach w $list {$w $mode moveto $top}
1006 proc incr_font_size {font {amt 1}} {
1007 set sz [font configure $font -size]
1008 incr sz $amt
1009 font configure $font -size $sz
1010 font configure ${font}bold -size $sz
1013 ######################################################################
1015 ## ui commands
1017 set starting_gitk_msg {Starting gitk... please wait...}
1019 proc do_gitk {revs} {
1020 global env ui_status_value starting_gitk_msg
1022 # -- Always start gitk through whatever we were loaded with. This
1023 # lets us bypass using shell process on Windows systems.
1025 set cmd [list [info nameofexecutable]]
1026 lappend cmd [gitexec gitk]
1027 if {$revs ne {}} {
1028 append cmd { }
1029 append cmd $revs
1032 if {[catch {eval exec $cmd &} err]} {
1033 error_popup "Failed to start gitk:\n\n$err"
1034 } else {
1035 set ui_status_value $starting_gitk_msg
1036 after 10000 {
1037 if {$ui_status_value eq $starting_gitk_msg} {
1038 set ui_status_value {Ready.}
1044 set is_quitting 0
1046 proc do_quit {} {
1047 global ui_comm is_quitting repo_config commit_type
1049 if {$is_quitting} return
1050 set is_quitting 1
1052 if {[winfo exists $ui_comm]} {
1053 # -- Stash our current commit buffer.
1055 set save [gitdir GITGUI_MSG]
1056 set msg [string trim [$ui_comm get 0.0 end]]
1057 regsub -all -line {[ \r\t]+$} $msg {} msg
1058 if {(![string match amend* $commit_type]
1059 || [$ui_comm edit modified])
1060 && $msg ne {}} {
1061 catch {
1062 set fd [open $save w]
1063 puts -nonewline $fd $msg
1064 close $fd
1066 } else {
1067 catch {file delete $save}
1070 # -- Stash our current window geometry into this repository.
1072 set cfg_geometry [list]
1073 lappend cfg_geometry [wm geometry .]
1074 lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
1075 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
1076 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1077 set rc_geometry {}
1079 if {$cfg_geometry ne $rc_geometry} {
1080 catch {git config gui.geometry $cfg_geometry}
1084 destroy .
1087 proc do_rescan {} {
1088 rescan {set ui_status_value {Ready.}}
1091 proc do_commit {} {
1092 commit_tree
1095 proc toggle_or_diff {w x y} {
1096 global file_states file_lists current_diff_path ui_index ui_workdir
1097 global last_clicked selected_paths
1099 set pos [split [$w index @$x,$y] .]
1100 set lno [lindex $pos 0]
1101 set col [lindex $pos 1]
1102 set path [lindex $file_lists($w) [expr {$lno - 1}]]
1103 if {$path eq {}} {
1104 set last_clicked {}
1105 return
1108 set last_clicked [list $w $lno]
1109 array unset selected_paths
1110 $ui_index tag remove in_sel 0.0 end
1111 $ui_workdir tag remove in_sel 0.0 end
1113 if {$col == 0} {
1114 if {$current_diff_path eq $path} {
1115 set after {reshow_diff;}
1116 } else {
1117 set after {}
1119 if {$w eq $ui_index} {
1120 update_indexinfo \
1121 "Unstaging [short_path $path] from commit" \
1122 [list $path] \
1123 [concat $after {set ui_status_value {Ready.}}]
1124 } elseif {$w eq $ui_workdir} {
1125 update_index \
1126 "Adding [short_path $path]" \
1127 [list $path] \
1128 [concat $after {set ui_status_value {Ready.}}]
1130 } else {
1131 show_diff $path $w $lno
1135 proc add_one_to_selection {w x y} {
1136 global file_lists last_clicked selected_paths
1138 set lno [lindex [split [$w index @$x,$y] .] 0]
1139 set path [lindex $file_lists($w) [expr {$lno - 1}]]
1140 if {$path eq {}} {
1141 set last_clicked {}
1142 return
1145 if {$last_clicked ne {}
1146 && [lindex $last_clicked 0] ne $w} {
1147 array unset selected_paths
1148 [lindex $last_clicked 0] tag remove in_sel 0.0 end
1151 set last_clicked [list $w $lno]
1152 if {[catch {set in_sel $selected_paths($path)}]} {
1153 set in_sel 0
1155 if {$in_sel} {
1156 unset selected_paths($path)
1157 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
1158 } else {
1159 set selected_paths($path) 1
1160 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
1164 proc add_range_to_selection {w x y} {
1165 global file_lists last_clicked selected_paths
1167 if {[lindex $last_clicked 0] ne $w} {
1168 toggle_or_diff $w $x $y
1169 return
1172 set lno [lindex [split [$w index @$x,$y] .] 0]
1173 set lc [lindex $last_clicked 1]
1174 if {$lc < $lno} {
1175 set begin $lc
1176 set end $lno
1177 } else {
1178 set begin $lno
1179 set end $lc
1182 foreach path [lrange $file_lists($w) \
1183 [expr {$begin - 1}] \
1184 [expr {$end - 1}]] {
1185 set selected_paths($path) 1
1187 $w tag add in_sel $begin.0 [expr {$end + 1}].0
1190 ######################################################################
1192 ## config defaults
1194 set cursor_ptr arrow
1195 font create font_diff -family Courier -size 10
1196 font create font_ui
1197 catch {
1198 label .dummy
1199 eval font configure font_ui [font actual [.dummy cget -font]]
1200 destroy .dummy
1203 font create font_uibold
1204 font create font_diffbold
1206 foreach class {Button Checkbutton Entry Label
1207 Labelframe Listbox Menu Message
1208 Radiobutton Text} {
1209 option add *$class.font font_ui
1211 unset class
1213 if {[is_MacOSX]} {
1214 set M1B M1
1215 set M1T Cmd
1216 } else {
1217 set M1B Control
1218 set M1T Ctrl
1221 proc apply_config {} {
1222 global repo_config font_descs
1224 foreach option $font_descs {
1225 set name [lindex $option 0]
1226 set font [lindex $option 1]
1227 if {[catch {
1228 foreach {cn cv} $repo_config(gui.$name) {
1229 font configure $font $cn $cv
1231 } err]} {
1232 error_popup "Invalid font specified in gui.$name:\n\n$err"
1234 foreach {cn cv} [font configure $font] {
1235 font configure ${font}bold $cn $cv
1237 font configure ${font}bold -weight bold
1241 set default_config(merge.summary) false
1242 set default_config(merge.verbosity) 2
1243 set default_config(user.name) {}
1244 set default_config(user.email) {}
1246 set default_config(gui.pruneduringfetch) false
1247 set default_config(gui.trustmtime) false
1248 set default_config(gui.diffcontext) 5
1249 set default_config(gui.newbranchtemplate) {}
1250 set default_config(gui.fontui) [font configure font_ui]
1251 set default_config(gui.fontdiff) [font configure font_diff]
1252 set font_descs {
1253 {fontui font_ui {Main Font}}
1254 {fontdiff font_diff {Diff/Console Font}}
1256 load_config 0
1257 apply_config
1259 ######################################################################
1261 ## feature option selection
1263 if {[regexp {^git-(.+)$} [appname] _junk subcommand]} {
1264 unset _junk
1265 } else {
1266 set subcommand gui
1268 if {$subcommand eq {gui.sh}} {
1269 set subcommand gui
1271 if {$subcommand eq {gui} && [llength $argv] > 0} {
1272 set subcommand [lindex $argv 0]
1273 set argv [lrange $argv 1 end]
1276 enable_option multicommit
1277 enable_option branch
1278 enable_option transport
1280 switch -- $subcommand {
1281 browser -
1282 blame {
1283 disable_option multicommit
1284 disable_option branch
1285 disable_option transport
1287 citool {
1288 enable_option singlecommit
1290 disable_option multicommit
1291 disable_option branch
1292 disable_option transport
1296 ######################################################################
1298 ## ui construction
1300 set ui_comm {}
1302 # -- Menu Bar
1304 menu .mbar -tearoff 0
1305 .mbar add cascade -label Repository -menu .mbar.repository
1306 .mbar add cascade -label Edit -menu .mbar.edit
1307 if {[is_enabled branch]} {
1308 .mbar add cascade -label Branch -menu .mbar.branch
1310 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1311 .mbar add cascade -label Commit -menu .mbar.commit
1313 if {[is_enabled transport]} {
1314 .mbar add cascade -label Merge -menu .mbar.merge
1315 .mbar add cascade -label Fetch -menu .mbar.fetch
1316 .mbar add cascade -label Push -menu .mbar.push
1318 . configure -menu .mbar
1320 # -- Repository Menu
1322 menu .mbar.repository
1324 .mbar.repository add command \
1325 -label {Browse Current Branch} \
1326 -command {browser::new $current_branch}
1327 trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#"
1328 .mbar.repository add separator
1330 .mbar.repository add command \
1331 -label {Visualize Current Branch} \
1332 -command {do_gitk $current_branch}
1333 trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch\" ;#"
1334 .mbar.repository add command \
1335 -label {Visualize All Branches} \
1336 -command {do_gitk --all}
1337 .mbar.repository add separator
1339 if {[is_enabled multicommit]} {
1340 .mbar.repository add command -label {Database Statistics} \
1341 -command do_stats
1343 .mbar.repository add command -label {Compress Database} \
1344 -command do_gc
1346 .mbar.repository add command -label {Verify Database} \
1347 -command do_fsck_objects
1349 .mbar.repository add separator
1351 if {[is_Cygwin]} {
1352 .mbar.repository add command \
1353 -label {Create Desktop Icon} \
1354 -command do_cygwin_shortcut
1355 } elseif {[is_Windows]} {
1356 .mbar.repository add command \
1357 -label {Create Desktop Icon} \
1358 -command do_windows_shortcut
1359 } elseif {[is_MacOSX]} {
1360 .mbar.repository add command \
1361 -label {Create Desktop Icon} \
1362 -command do_macosx_app
1366 .mbar.repository add command -label Quit \
1367 -command do_quit \
1368 -accelerator $M1T-Q
1370 # -- Edit Menu
1372 menu .mbar.edit
1373 .mbar.edit add command -label Undo \
1374 -command {catch {[focus] edit undo}} \
1375 -accelerator $M1T-Z
1376 .mbar.edit add command -label Redo \
1377 -command {catch {[focus] edit redo}} \
1378 -accelerator $M1T-Y
1379 .mbar.edit add separator
1380 .mbar.edit add command -label Cut \
1381 -command {catch {tk_textCut [focus]}} \
1382 -accelerator $M1T-X
1383 .mbar.edit add command -label Copy \
1384 -command {catch {tk_textCopy [focus]}} \
1385 -accelerator $M1T-C
1386 .mbar.edit add command -label Paste \
1387 -command {catch {tk_textPaste [focus]; [focus] see insert}} \
1388 -accelerator $M1T-V
1389 .mbar.edit add command -label Delete \
1390 -command {catch {[focus] delete sel.first sel.last}} \
1391 -accelerator Del
1392 .mbar.edit add separator
1393 .mbar.edit add command -label {Select All} \
1394 -command {catch {[focus] tag add sel 0.0 end}} \
1395 -accelerator $M1T-A
1397 # -- Branch Menu
1399 if {[is_enabled branch]} {
1400 menu .mbar.branch
1402 .mbar.branch add command -label {Create...} \
1403 -command do_create_branch \
1404 -accelerator $M1T-N
1405 lappend disable_on_lock [list .mbar.branch entryconf \
1406 [.mbar.branch index last] -state]
1408 .mbar.branch add command -label {Delete...} \
1409 -command do_delete_branch
1410 lappend disable_on_lock [list .mbar.branch entryconf \
1411 [.mbar.branch index last] -state]
1413 .mbar.branch add command -label {Reset...} \
1414 -command merge::reset_hard
1415 lappend disable_on_lock [list .mbar.branch entryconf \
1416 [.mbar.branch index last] -state]
1419 # -- Commit Menu
1421 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1422 menu .mbar.commit
1424 .mbar.commit add radiobutton \
1425 -label {New Commit} \
1426 -command do_select_commit_type \
1427 -variable selected_commit_type \
1428 -value new
1429 lappend disable_on_lock \
1430 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1432 .mbar.commit add radiobutton \
1433 -label {Amend Last Commit} \
1434 -command do_select_commit_type \
1435 -variable selected_commit_type \
1436 -value amend
1437 lappend disable_on_lock \
1438 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1440 .mbar.commit add separator
1442 .mbar.commit add command -label Rescan \
1443 -command do_rescan \
1444 -accelerator F5
1445 lappend disable_on_lock \
1446 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1448 .mbar.commit add command -label {Add To Commit} \
1449 -command do_add_selection
1450 lappend disable_on_lock \
1451 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1453 .mbar.commit add command -label {Add Existing To Commit} \
1454 -command do_add_all \
1455 -accelerator $M1T-I
1456 lappend disable_on_lock \
1457 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1459 .mbar.commit add command -label {Unstage From Commit} \
1460 -command do_unstage_selection
1461 lappend disable_on_lock \
1462 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1464 .mbar.commit add command -label {Revert Changes} \
1465 -command do_revert_selection
1466 lappend disable_on_lock \
1467 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1469 .mbar.commit add separator
1471 .mbar.commit add command -label {Sign Off} \
1472 -command do_signoff \
1473 -accelerator $M1T-S
1475 .mbar.commit add command -label Commit \
1476 -command do_commit \
1477 -accelerator $M1T-Return
1478 lappend disable_on_lock \
1479 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1482 # -- Merge Menu
1484 if {[is_enabled branch]} {
1485 menu .mbar.merge
1486 .mbar.merge add command -label {Local Merge...} \
1487 -command merge::dialog
1488 lappend disable_on_lock \
1489 [list .mbar.merge entryconf [.mbar.merge index last] -state]
1490 .mbar.merge add command -label {Abort Merge...} \
1491 -command merge::reset_hard
1492 lappend disable_on_lock \
1493 [list .mbar.merge entryconf [.mbar.merge index last] -state]
1497 # -- Transport Menu
1499 if {[is_enabled transport]} {
1500 menu .mbar.fetch
1502 menu .mbar.push
1503 .mbar.push add command -label {Push...} \
1504 -command do_push_anywhere
1507 if {[is_MacOSX]} {
1508 # -- Apple Menu (Mac OS X only)
1510 .mbar add cascade -label Apple -menu .mbar.apple
1511 menu .mbar.apple
1513 .mbar.apple add command -label "About [appname]" \
1514 -command do_about
1515 .mbar.apple add command -label "Options..." \
1516 -command do_options
1517 } else {
1518 # -- Edit Menu
1520 .mbar.edit add separator
1521 .mbar.edit add command -label {Options...} \
1522 -command do_options
1524 # -- Tools Menu
1526 if {[file exists /usr/local/miga/lib/gui-miga]
1527 && [file exists .pvcsrc]} {
1528 proc do_miga {} {
1529 global ui_status_value
1530 if {![lock_index update]} return
1531 set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""]
1532 set miga_fd [open "|$cmd" r]
1533 fconfigure $miga_fd -blocking 0
1534 fileevent $miga_fd readable [list miga_done $miga_fd]
1535 set ui_status_value {Running miga...}
1537 proc miga_done {fd} {
1538 read $fd 512
1539 if {[eof $fd]} {
1540 close $fd
1541 unlock_index
1542 rescan [list set ui_status_value {Ready.}]
1545 .mbar add cascade -label Tools -menu .mbar.tools
1546 menu .mbar.tools
1547 .mbar.tools add command -label "Migrate" \
1548 -command do_miga
1549 lappend disable_on_lock \
1550 [list .mbar.tools entryconf [.mbar.tools index last] -state]
1554 # -- Help Menu
1556 .mbar add cascade -label Help -menu .mbar.help
1557 menu .mbar.help
1559 if {![is_MacOSX]} {
1560 .mbar.help add command -label "About [appname]" \
1561 -command do_about
1564 set browser {}
1565 catch {set browser $repo_config(instaweb.browser)}
1566 set doc_path [file dirname [gitexec]]
1567 set doc_path [file join $doc_path Documentation index.html]
1569 if {[is_Cygwin]} {
1570 set doc_path [exec cygpath --mixed $doc_path]
1573 if {$browser eq {}} {
1574 if {[is_MacOSX]} {
1575 set browser open
1576 } elseif {[is_Cygwin]} {
1577 set program_files [file dirname [exec cygpath --windir]]
1578 set program_files [file join $program_files {Program Files}]
1579 set firefox [file join $program_files {Mozilla Firefox} firefox.exe]
1580 set ie [file join $program_files {Internet Explorer} IEXPLORE.EXE]
1581 if {[file exists $firefox]} {
1582 set browser $firefox
1583 } elseif {[file exists $ie]} {
1584 set browser $ie
1586 unset program_files firefox ie
1590 if {[file isfile $doc_path]} {
1591 set doc_url "file:$doc_path"
1592 } else {
1593 set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
1596 if {$browser ne {}} {
1597 .mbar.help add command -label {Online Documentation} \
1598 -command [list exec $browser $doc_url &]
1600 unset browser doc_path doc_url
1602 # -- Standard bindings
1604 bind . <Destroy> do_quit
1605 bind all <$M1B-Key-q> do_quit
1606 bind all <$M1B-Key-Q> do_quit
1607 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
1608 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
1610 set subcommand_args {}
1611 proc usage {} {
1612 puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
1613 exit 1
1616 # -- Not a normal commit type invocation? Do that instead!
1618 switch -- $subcommand {
1619 browser {
1620 set subcommand_args {rev?}
1621 switch [llength $argv] {
1623 set current_branch [git symbolic-ref HEAD]
1624 regsub ^refs/((heads|tags|remotes)/)? \
1625 $current_branch {} current_branch
1628 set current_branch [lindex $argv 0]
1630 default usage
1632 browser::new $current_branch
1633 return
1635 blame {
1636 set subcommand_args {rev? path?}
1637 set head {}
1638 set path {}
1639 set is_path 0
1640 foreach a $argv {
1641 if {$is_path || [file exists $_prefix$a]} {
1642 if {$path ne {}} usage
1643 set path $_prefix$a
1644 break
1645 } elseif {$a eq {--}} {
1646 if {$path ne {}} {
1647 if {$head ne {}} usage
1648 set head $path
1649 set path {}
1651 set is_path 1
1652 } elseif {$head eq {}} {
1653 if {$head ne {}} usage
1654 set head $a
1655 } else {
1656 usage
1659 unset is_path
1661 if {$head eq {}} {
1662 set current_branch [git symbolic-ref HEAD]
1663 regsub ^refs/((heads|tags|remotes)/)? \
1664 $current_branch {} current_branch
1665 } else {
1666 set current_branch $head
1669 if {$path eq {}} usage
1670 blame::new $head $path
1671 return
1673 citool -
1674 gui {
1675 if {[llength $argv] != 0} {
1676 puts -nonewline stderr "usage: $argv0"
1677 if {$subcommand ne {gui} && [appname] ne "git-$subcommand"} {
1678 puts -nonewline stderr " $subcommand"
1680 puts stderr {}
1681 exit 1
1683 # fall through to setup UI for commits
1685 default {
1686 puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
1687 exit 1
1691 # -- Branch Control
1693 frame .branch \
1694 -borderwidth 1 \
1695 -relief sunken
1696 label .branch.l1 \
1697 -text {Current Branch:} \
1698 -anchor w \
1699 -justify left
1700 label .branch.cb \
1701 -textvariable current_branch \
1702 -anchor w \
1703 -justify left
1704 pack .branch.l1 -side left
1705 pack .branch.cb -side left -fill x
1706 pack .branch -side top -fill x
1708 # -- Main Window Layout
1710 panedwindow .vpane -orient vertical
1711 panedwindow .vpane.files -orient horizontal
1712 .vpane add .vpane.files -sticky nsew -height 100 -width 200
1713 pack .vpane -anchor n -side top -fill both -expand 1
1715 # -- Index File List
1717 frame .vpane.files.index -height 100 -width 200
1718 label .vpane.files.index.title -text {Staged Changes (Will Be Committed)} \
1719 -background green
1720 text $ui_index -background white -borderwidth 0 \
1721 -width 20 -height 10 \
1722 -wrap none \
1723 -cursor $cursor_ptr \
1724 -xscrollcommand {.vpane.files.index.sx set} \
1725 -yscrollcommand {.vpane.files.index.sy set} \
1726 -state disabled
1727 scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
1728 scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
1729 pack .vpane.files.index.title -side top -fill x
1730 pack .vpane.files.index.sx -side bottom -fill x
1731 pack .vpane.files.index.sy -side right -fill y
1732 pack $ui_index -side left -fill both -expand 1
1733 .vpane.files add .vpane.files.index -sticky nsew
1735 # -- Working Directory File List
1737 frame .vpane.files.workdir -height 100 -width 200
1738 label .vpane.files.workdir.title -text {Unstaged Changes (Will Not Be Committed)} \
1739 -background red
1740 text $ui_workdir -background white -borderwidth 0 \
1741 -width 20 -height 10 \
1742 -wrap none \
1743 -cursor $cursor_ptr \
1744 -xscrollcommand {.vpane.files.workdir.sx set} \
1745 -yscrollcommand {.vpane.files.workdir.sy set} \
1746 -state disabled
1747 scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
1748 scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
1749 pack .vpane.files.workdir.title -side top -fill x
1750 pack .vpane.files.workdir.sx -side bottom -fill x
1751 pack .vpane.files.workdir.sy -side right -fill y
1752 pack $ui_workdir -side left -fill both -expand 1
1753 .vpane.files add .vpane.files.workdir -sticky nsew
1755 foreach i [list $ui_index $ui_workdir] {
1756 $i tag conf in_diff -font font_uibold
1757 $i tag conf in_sel \
1758 -background [$i cget -foreground] \
1759 -foreground [$i cget -background]
1761 unset i
1763 # -- Diff and Commit Area
1765 frame .vpane.lower -height 300 -width 400
1766 frame .vpane.lower.commarea
1767 frame .vpane.lower.diff -relief sunken -borderwidth 1
1768 pack .vpane.lower.commarea -side top -fill x
1769 pack .vpane.lower.diff -side bottom -fill both -expand 1
1770 .vpane add .vpane.lower -sticky nsew
1772 # -- Commit Area Buttons
1774 frame .vpane.lower.commarea.buttons
1775 label .vpane.lower.commarea.buttons.l -text {} \
1776 -anchor w \
1777 -justify left
1778 pack .vpane.lower.commarea.buttons.l -side top -fill x
1779 pack .vpane.lower.commarea.buttons -side left -fill y
1781 button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
1782 -command do_rescan
1783 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
1784 lappend disable_on_lock \
1785 {.vpane.lower.commarea.buttons.rescan conf -state}
1787 button .vpane.lower.commarea.buttons.incall -text {Add Existing} \
1788 -command do_add_all
1789 pack .vpane.lower.commarea.buttons.incall -side top -fill x
1790 lappend disable_on_lock \
1791 {.vpane.lower.commarea.buttons.incall conf -state}
1793 button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
1794 -command do_signoff
1795 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
1797 button .vpane.lower.commarea.buttons.commit -text {Commit} \
1798 -command do_commit
1799 pack .vpane.lower.commarea.buttons.commit -side top -fill x
1800 lappend disable_on_lock \
1801 {.vpane.lower.commarea.buttons.commit conf -state}
1803 # -- Commit Message Buffer
1805 frame .vpane.lower.commarea.buffer
1806 frame .vpane.lower.commarea.buffer.header
1807 set ui_comm .vpane.lower.commarea.buffer.t
1808 set ui_coml .vpane.lower.commarea.buffer.header.l
1809 radiobutton .vpane.lower.commarea.buffer.header.new \
1810 -text {New Commit} \
1811 -command do_select_commit_type \
1812 -variable selected_commit_type \
1813 -value new
1814 lappend disable_on_lock \
1815 [list .vpane.lower.commarea.buffer.header.new conf -state]
1816 radiobutton .vpane.lower.commarea.buffer.header.amend \
1817 -text {Amend Last Commit} \
1818 -command do_select_commit_type \
1819 -variable selected_commit_type \
1820 -value amend
1821 lappend disable_on_lock \
1822 [list .vpane.lower.commarea.buffer.header.amend conf -state]
1823 label $ui_coml \
1824 -anchor w \
1825 -justify left
1826 proc trace_commit_type {varname args} {
1827 global ui_coml commit_type
1828 switch -glob -- $commit_type {
1829 initial {set txt {Initial Commit Message:}}
1830 amend {set txt {Amended Commit Message:}}
1831 amend-initial {set txt {Amended Initial Commit Message:}}
1832 amend-merge {set txt {Amended Merge Commit Message:}}
1833 merge {set txt {Merge Commit Message:}}
1834 * {set txt {Commit Message:}}
1836 $ui_coml conf -text $txt
1838 trace add variable commit_type write trace_commit_type
1839 pack $ui_coml -side left -fill x
1840 pack .vpane.lower.commarea.buffer.header.amend -side right
1841 pack .vpane.lower.commarea.buffer.header.new -side right
1843 text $ui_comm -background white -borderwidth 1 \
1844 -undo true \
1845 -maxundo 20 \
1846 -autoseparators true \
1847 -relief sunken \
1848 -width 75 -height 9 -wrap none \
1849 -font font_diff \
1850 -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
1851 scrollbar .vpane.lower.commarea.buffer.sby \
1852 -command [list $ui_comm yview]
1853 pack .vpane.lower.commarea.buffer.header -side top -fill x
1854 pack .vpane.lower.commarea.buffer.sby -side right -fill y
1855 pack $ui_comm -side left -fill y
1856 pack .vpane.lower.commarea.buffer -side left -fill y
1858 # -- Commit Message Buffer Context Menu
1860 set ctxm .vpane.lower.commarea.buffer.ctxm
1861 menu $ctxm -tearoff 0
1862 $ctxm add command \
1863 -label {Cut} \
1864 -command {tk_textCut $ui_comm}
1865 $ctxm add command \
1866 -label {Copy} \
1867 -command {tk_textCopy $ui_comm}
1868 $ctxm add command \
1869 -label {Paste} \
1870 -command {tk_textPaste $ui_comm}
1871 $ctxm add command \
1872 -label {Delete} \
1873 -command {$ui_comm delete sel.first sel.last}
1874 $ctxm add separator
1875 $ctxm add command \
1876 -label {Select All} \
1877 -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
1878 $ctxm add command \
1879 -label {Copy All} \
1880 -command {
1881 $ui_comm tag add sel 0.0 end
1882 tk_textCopy $ui_comm
1883 $ui_comm tag remove sel 0.0 end
1885 $ctxm add separator
1886 $ctxm add command \
1887 -label {Sign Off} \
1888 -command do_signoff
1889 bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
1891 # -- Diff Header
1893 proc trace_current_diff_path {varname args} {
1894 global current_diff_path diff_actions file_states
1895 if {$current_diff_path eq {}} {
1896 set s {}
1897 set f {}
1898 set p {}
1899 set o disabled
1900 } else {
1901 set p $current_diff_path
1902 set s [mapdesc [lindex $file_states($p) 0] $p]
1903 set f {File:}
1904 set p [escape_path $p]
1905 set o normal
1908 .vpane.lower.diff.header.status configure -text $s
1909 .vpane.lower.diff.header.file configure -text $f
1910 .vpane.lower.diff.header.path configure -text $p
1911 foreach w $diff_actions {
1912 uplevel #0 $w $o
1915 trace add variable current_diff_path write trace_current_diff_path
1917 frame .vpane.lower.diff.header -background orange
1918 label .vpane.lower.diff.header.status \
1919 -background orange \
1920 -width $max_status_desc \
1921 -anchor w \
1922 -justify left
1923 label .vpane.lower.diff.header.file \
1924 -background orange \
1925 -anchor w \
1926 -justify left
1927 label .vpane.lower.diff.header.path \
1928 -background orange \
1929 -anchor w \
1930 -justify left
1931 pack .vpane.lower.diff.header.status -side left
1932 pack .vpane.lower.diff.header.file -side left
1933 pack .vpane.lower.diff.header.path -fill x
1934 set ctxm .vpane.lower.diff.header.ctxm
1935 menu $ctxm -tearoff 0
1936 $ctxm add command \
1937 -label {Copy} \
1938 -command {
1939 clipboard clear
1940 clipboard append \
1941 -format STRING \
1942 -type STRING \
1943 -- $current_diff_path
1945 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
1946 bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
1948 # -- Diff Body
1950 frame .vpane.lower.diff.body
1951 set ui_diff .vpane.lower.diff.body.t
1952 text $ui_diff -background white -borderwidth 0 \
1953 -width 80 -height 15 -wrap none \
1954 -font font_diff \
1955 -xscrollcommand {.vpane.lower.diff.body.sbx set} \
1956 -yscrollcommand {.vpane.lower.diff.body.sby set} \
1957 -state disabled
1958 scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
1959 -command [list $ui_diff xview]
1960 scrollbar .vpane.lower.diff.body.sby -orient vertical \
1961 -command [list $ui_diff yview]
1962 pack .vpane.lower.diff.body.sbx -side bottom -fill x
1963 pack .vpane.lower.diff.body.sby -side right -fill y
1964 pack $ui_diff -side left -fill both -expand 1
1965 pack .vpane.lower.diff.header -side top -fill x
1966 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
1968 $ui_diff tag conf d_cr -elide true
1969 $ui_diff tag conf d_@ -foreground blue -font font_diffbold
1970 $ui_diff tag conf d_+ -foreground {#00a000}
1971 $ui_diff tag conf d_- -foreground red
1973 $ui_diff tag conf d_++ -foreground {#00a000}
1974 $ui_diff tag conf d_-- -foreground red
1975 $ui_diff tag conf d_+s \
1976 -foreground {#00a000} \
1977 -background {#e2effa}
1978 $ui_diff tag conf d_-s \
1979 -foreground red \
1980 -background {#e2effa}
1981 $ui_diff tag conf d_s+ \
1982 -foreground {#00a000} \
1983 -background ivory1
1984 $ui_diff tag conf d_s- \
1985 -foreground red \
1986 -background ivory1
1988 $ui_diff tag conf d<<<<<<< \
1989 -foreground orange \
1990 -font font_diffbold
1991 $ui_diff tag conf d======= \
1992 -foreground orange \
1993 -font font_diffbold
1994 $ui_diff tag conf d>>>>>>> \
1995 -foreground orange \
1996 -font font_diffbold
1998 $ui_diff tag raise sel
2000 # -- Diff Body Context Menu
2002 set ctxm .vpane.lower.diff.body.ctxm
2003 menu $ctxm -tearoff 0
2004 $ctxm add command \
2005 -label {Refresh} \
2006 -command reshow_diff
2007 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2008 $ctxm add command \
2009 -label {Copy} \
2010 -command {tk_textCopy $ui_diff}
2011 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2012 $ctxm add command \
2013 -label {Select All} \
2014 -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
2015 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2016 $ctxm add command \
2017 -label {Copy All} \
2018 -command {
2019 $ui_diff tag add sel 0.0 end
2020 tk_textCopy $ui_diff
2021 $ui_diff tag remove sel 0.0 end
2023 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2024 $ctxm add separator
2025 $ctxm add command \
2026 -label {Apply/Reverse Hunk} \
2027 -command {apply_hunk $cursorX $cursorY}
2028 set ui_diff_applyhunk [$ctxm index last]
2029 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
2030 $ctxm add separator
2031 $ctxm add command \
2032 -label {Decrease Font Size} \
2033 -command {incr_font_size font_diff -1}
2034 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2035 $ctxm add command \
2036 -label {Increase Font Size} \
2037 -command {incr_font_size font_diff 1}
2038 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2039 $ctxm add separator
2040 $ctxm add command \
2041 -label {Show Less Context} \
2042 -command {if {$repo_config(gui.diffcontext) >= 2} {
2043 incr repo_config(gui.diffcontext) -1
2044 reshow_diff
2046 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2047 $ctxm add command \
2048 -label {Show More Context} \
2049 -command {
2050 incr repo_config(gui.diffcontext)
2051 reshow_diff
2053 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2054 $ctxm add separator
2055 $ctxm add command -label {Options...} \
2056 -command do_options
2057 bind_button3 $ui_diff "
2058 set cursorX %x
2059 set cursorY %y
2060 if {\$ui_index eq \$current_diff_side} {
2061 $ctxm entryconf $ui_diff_applyhunk -label {Unstage Hunk From Commit}
2062 } else {
2063 $ctxm entryconf $ui_diff_applyhunk -label {Stage Hunk For Commit}
2065 tk_popup $ctxm %X %Y
2067 unset ui_diff_applyhunk
2069 # -- Status Bar
2071 label .status -textvariable ui_status_value \
2072 -anchor w \
2073 -justify left \
2074 -borderwidth 1 \
2075 -relief sunken
2076 pack .status -anchor w -side bottom -fill x
2078 # -- Load geometry
2080 catch {
2081 set gm $repo_config(gui.geometry)
2082 wm geometry . [lindex $gm 0]
2083 .vpane sash place 0 \
2084 [lindex [.vpane sash coord 0] 0] \
2085 [lindex $gm 1]
2086 .vpane.files sash place 0 \
2087 [lindex $gm 2] \
2088 [lindex [.vpane.files sash coord 0] 1]
2089 unset gm
2092 # -- Key Bindings
2094 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2095 bind $ui_comm <$M1B-Key-i> {do_add_all;break}
2096 bind $ui_comm <$M1B-Key-I> {do_add_all;break}
2097 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2098 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2099 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2100 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2101 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2102 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2103 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2104 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2106 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2107 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2108 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2109 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2110 bind $ui_diff <$M1B-Key-v> {break}
2111 bind $ui_diff <$M1B-Key-V> {break}
2112 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2113 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2114 bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break}
2115 bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break}
2116 bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break}
2117 bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break}
2118 bind $ui_diff <Key-k> {catch {%W yview scroll -1 units};break}
2119 bind $ui_diff <Key-j> {catch {%W yview scroll 1 units};break}
2120 bind $ui_diff <Key-h> {catch {%W xview scroll -1 units};break}
2121 bind $ui_diff <Key-l> {catch {%W xview scroll 1 units};break}
2122 bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
2123 bind $ui_diff <Control-Key-f> {catch {%W yview scroll 1 pages};break}
2124 bind $ui_diff <Button-1> {focus %W}
2126 if {[is_enabled branch]} {
2127 bind . <$M1B-Key-n> do_create_branch
2128 bind . <$M1B-Key-N> do_create_branch
2131 bind all <Key-F5> do_rescan
2132 bind all <$M1B-Key-r> do_rescan
2133 bind all <$M1B-Key-R> do_rescan
2134 bind . <$M1B-Key-s> do_signoff
2135 bind . <$M1B-Key-S> do_signoff
2136 bind . <$M1B-Key-i> do_add_all
2137 bind . <$M1B-Key-I> do_add_all
2138 bind . <$M1B-Key-Return> do_commit
2139 foreach i [list $ui_index $ui_workdir] {
2140 bind $i <Button-1> "toggle_or_diff $i %x %y; break"
2141 bind $i <$M1B-Button-1> "add_one_to_selection $i %x %y; break"
2142 bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
2144 unset i
2146 set file_lists($ui_index) [list]
2147 set file_lists($ui_workdir) [list]
2149 wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
2150 focus -force $ui_comm
2152 # -- Warn the user about environmental problems. Cygwin's Tcl
2153 # does *not* pass its env array onto any processes it spawns.
2154 # This means that git processes get none of our environment.
2156 if {[is_Cygwin]} {
2157 set ignored_env 0
2158 set suggest_user {}
2159 set msg "Possible environment issues exist.
2161 The following environment variables are probably
2162 going to be ignored by any Git subprocess run
2163 by [appname]:
2166 foreach name [array names env] {
2167 switch -regexp -- $name {
2168 {^GIT_INDEX_FILE$} -
2169 {^GIT_OBJECT_DIRECTORY$} -
2170 {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
2171 {^GIT_DIFF_OPTS$} -
2172 {^GIT_EXTERNAL_DIFF$} -
2173 {^GIT_PAGER$} -
2174 {^GIT_TRACE$} -
2175 {^GIT_CONFIG$} -
2176 {^GIT_CONFIG_LOCAL$} -
2177 {^GIT_(AUTHOR|COMMITTER)_DATE$} {
2178 append msg " - $name\n"
2179 incr ignored_env
2181 {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
2182 append msg " - $name\n"
2183 incr ignored_env
2184 set suggest_user $name
2188 if {$ignored_env > 0} {
2189 append msg "
2190 This is due to a known issue with the
2191 Tcl binary distributed by Cygwin."
2193 if {$suggest_user ne {}} {
2194 append msg "
2196 A good replacement for $suggest_user
2197 is placing values for the user.name and
2198 user.email settings into your personal
2199 ~/.gitconfig file.
2202 warn_popup $msg
2204 unset ignored_env msg suggest_user name
2207 # -- Only initialize complex UI if we are going to stay running.
2209 if {[is_enabled transport]} {
2210 load_all_remotes
2211 load_all_heads
2213 populate_branch_menu
2214 populate_fetch_menu
2215 populate_push_menu
2218 # -- Only suggest a gc run if we are going to stay running.
2220 if {[is_enabled multicommit]} {
2221 set object_limit 2000
2222 if {[is_Windows]} {set object_limit 200}
2223 regexp {^([0-9]+) objects,} [git count-objects] _junk objects_current
2224 if {$objects_current >= $object_limit} {
2225 if {[ask_popup \
2226 "This repository currently has $objects_current loose objects.
2228 To maintain optimal performance it is strongly recommended that you compress the database when more than $object_limit loose objects exist.
2230 Compress the database now?"] eq yes} {
2231 do_gc
2234 unset object_limit _junk objects_current
2237 lock_index begin-read
2238 after 1 do_rescan