2 # Tcl ignores the next line -*- tcl -*- \
5 # Copyright (C) 2006 Shawn Pearce, Paul Mackerras. All rights reserved.
6 # This program is free software; it may be used, copied, modified
7 # and distributed under the terms of the GNU General Public Licence,
8 # either version 2, or (at your option) any later version.
10 set appname [lindex [file split $argv0] end]
13 ######################################################################
17 proc is_many_config {name} {
18 switch -glob -- $name {
27 proc load_config {include_global} {
28 global repo_config global_config default_config
30 array unset global_config
31 if {$include_global} {
33 set fd_rc [open "| git repo-config --global --list" r]
34 while {[gets $fd_rc line] >= 0} {
35 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
36 if {[is_many_config $name]} {
37 lappend global_config($name) $value
39 set global_config($name) $value
47 array unset repo_config
49 set fd_rc [open "| git repo-config --list" r]
50 while {[gets $fd_rc line] >= 0} {
51 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
52 if {[is_many_config $name]} {
53 lappend repo_config($name) $value
55 set repo_config($name) $value
62 foreach name [array names default_config] {
63 if {[catch {set v $global_config($name)}]} {
64 set global_config($name) $default_config($name)
66 if {[catch {set v $repo_config($name)}]} {
67 set repo_config($name) $default_config($name)
73 global default_config font_descs
74 global repo_config global_config
75 global repo_config_new global_config_new
77 foreach option $font_descs {
78 set name [lindex $option 0]
79 set font [lindex $option 1]
80 font configure $font \
81 -family $global_config_new(gui.$font^^family) \
82 -size $global_config_new(gui.$font^^size)
83 font configure ${font}bold \
84 -family $global_config_new(gui.$font^^family) \
85 -size $global_config_new(gui.$font^^size)
86 set global_config_new(gui.$name) [font configure $font]
87 unset global_config_new(gui.$font^^family)
88 unset global_config_new(gui.$font^^size)
91 foreach name [array names default_config] {
92 set value $global_config_new($name)
93 if {$value != $global_config($name)} {
94 if {$value == $default_config($name)} {
95 catch {exec git repo-config --global --unset $name}
97 regsub -all "\[{}\]" $value {"} value
98 exec git repo-config --global $name $value
100 set global_config($name) $value
101 if {$value == $repo_config($name)} {
102 catch {exec git repo-config --unset $name}
103 set repo_config($name) $value
108 foreach name [array names default_config] {
109 set value $repo_config_new($name)
110 if {$value != $repo_config($name)} {
111 if {$value == $global_config($name)} {
112 catch {exec git repo-config --unset $name}
114 regsub -all "\[{}\]" $value {"} value
115 exec git repo-config $name $value
117 set repo_config($name) $value
122 proc error_popup {msg} {
123 global gitdir appname
128 append title [lindex \
129 [file split [file normalize [file dirname $gitdir]]] \
137 -title "$title: error" \
141 proc info_popup {msg} {
142 global gitdir appname
147 append title [lindex \
148 [file split [file normalize [file dirname $gitdir]]] \
160 ######################################################################
164 if { [catch {set cdup [exec git rev-parse --show-cdup]} err]
165 || [catch {set gitdir [exec git rev-parse --git-dir]} err]} {
166 catch {wm withdraw .}
167 error_popup "Cannot find the git directory:\n\n$err"
176 if {$appname == {git-citool}} {
180 ######################################################################
188 set disable_on_lock [list]
189 set index_lock_type none
195 proc lock_index {type} {
196 global index_lock_type disable_on_lock
198 if {$index_lock_type == {none}} {
199 set index_lock_type $type
200 foreach w $disable_on_lock {
201 uplevel #0 $w disabled
204 } elseif {$index_lock_type == {begin-update} && $type == {update}} {
205 set index_lock_type $type
211 proc unlock_index {} {
212 global index_lock_type disable_on_lock
214 set index_lock_type none
215 foreach w $disable_on_lock {
220 ######################################################################
224 proc repository_state {hdvar ctvar} {
226 upvar $hdvar hd $ctvar ct
228 if {[catch {set hd [exec git rev-parse --verify HEAD]}]} {
230 } elseif {[file exists [file join $gitdir MERGE_HEAD]]} {
237 proc update_status {{final Ready.}} {
238 global HEAD PARENT commit_type
239 global ui_index ui_other ui_status_value ui_comm
240 global status_active file_states
243 if {$status_active || ![lock_index read]} return
245 repository_state new_HEAD new_type
246 if {$commit_type == {amend}
247 && $new_type == {normal}
248 && $new_HEAD == $HEAD} {
252 set commit_type $new_type
255 array unset file_states
257 if {![$ui_comm edit modified]
258 || [string trim [$ui_comm get 0.0 end]] == {}} {
259 if {[load_message GITGUI_MSG]} {
260 } elseif {[load_message MERGE_MSG]} {
261 } elseif {[load_message SQUASH_MSG]} {
263 $ui_comm edit modified false
267 if {$repo_config(gui.trustmtime) == {true}} {
268 update_status_stage2 {} $final
271 set ui_status_value {Refreshing file status...}
272 set cmd [list git update-index]
274 lappend cmd --unmerged
275 lappend cmd --ignore-missing
276 lappend cmd --refresh
277 set fd_rf [open "| $cmd" r]
278 fconfigure $fd_rf -blocking 0 -translation binary
279 fileevent $fd_rf readable \
280 [list update_status_stage2 $fd_rf $final]
284 proc update_status_stage2 {fd final} {
285 global gitdir PARENT commit_type
286 global ui_index ui_other ui_status_value ui_comm
288 global buf_rdi buf_rdf buf_rlo
292 if {![eof $fd]} return
296 set ls_others [list | git ls-files --others -z \
297 --exclude-per-directory=.gitignore]
298 set info_exclude [file join $gitdir info exclude]
299 if {[file readable $info_exclude]} {
300 lappend ls_others "--exclude-from=$info_exclude"
308 set ui_status_value {Scanning for modified files ...}
309 set fd_di [open "| git diff-index --cached -z $PARENT" r]
310 set fd_df [open "| git diff-files -z" r]
311 set fd_lo [open $ls_others r]
313 fconfigure $fd_di -blocking 0 -translation binary
314 fconfigure $fd_df -blocking 0 -translation binary
315 fconfigure $fd_lo -blocking 0 -translation binary
316 fileevent $fd_di readable [list read_diff_index $fd_di $final]
317 fileevent $fd_df readable [list read_diff_files $fd_df $final]
318 fileevent $fd_lo readable [list read_ls_others $fd_lo $final]
321 proc load_message {file} {
322 global gitdir ui_comm
324 set f [file join $gitdir $file]
325 if {[file isfile $f]} {
326 if {[catch {set fd [open $f r]}]} {
329 set content [string trim [read $fd]]
331 $ui_comm delete 0.0 end
332 $ui_comm insert end $content
338 proc read_diff_index {fd final} {
341 append buf_rdi [read $fd]
343 set n [string length $buf_rdi]
345 set z1 [string first "\0" $buf_rdi $c]
348 set z2 [string first "\0" $buf_rdi $z1]
354 [string range $buf_rdi $z1 $z2] \
355 [string index $buf_rdi [expr $z1 - 2]]_
359 set buf_rdi [string range $buf_rdi $c end]
364 status_eof $fd buf_rdi $final
367 proc read_diff_files {fd final} {
370 append buf_rdf [read $fd]
372 set n [string length $buf_rdf]
374 set z1 [string first "\0" $buf_rdf $c]
377 set z2 [string first "\0" $buf_rdf $z1]
383 [string range $buf_rdf $z1 $z2] \
384 _[string index $buf_rdf [expr $z1 - 2]]
388 set buf_rdf [string range $buf_rdf $c end]
393 status_eof $fd buf_rdf $final
396 proc read_ls_others {fd final} {
399 append buf_rlo [read $fd]
400 set pck [split $buf_rlo "\0"]
401 set buf_rlo [lindex $pck end]
402 foreach p [lrange $pck 0 end-1] {
405 status_eof $fd buf_rlo $final
408 proc status_eof {fd buf final} {
409 global status_active ui_status_value
416 if {[incr status_active -1] == 0} {
420 set ui_status_value $final
425 ######################################################################
430 global ui_diff ui_fname_value ui_fstatus_value ui_index ui_other
432 $ui_diff conf -state normal
433 $ui_diff delete 0.0 end
434 $ui_diff conf -state disabled
436 set ui_fname_value {}
437 set ui_fstatus_value {}
439 $ui_index tag remove in_diff 0.0 end
440 $ui_other tag remove in_diff 0.0 end
443 proc reshow_diff {} {
444 global ui_fname_value ui_status_value file_states
446 if {$ui_fname_value == {}
447 || [catch {set s $file_states($ui_fname_value)}]} {
450 show_diff $ui_fname_value
454 proc handle_empty_diff {} {
455 global ui_fname_value file_states file_lists
457 set path $ui_fname_value
458 set s $file_states($path)
459 if {[lindex $s 0] != {_M}} return
461 info_popup "No differences detected.
463 [short_path $path] has no changes.
465 The modification date of this file was updated by another
466 application and you currently have the Trust File Modification
467 Timestamps option enabled, so Git did not automatically detect
468 that there are no content differences in this file.
470 This file will now be removed from the modified files list, to
471 prevent possible confusion.
473 if {[catch {exec git update-index -- $path} err]} {
474 error_popup "Failed to refresh index:\n\n$err"
478 set old_w [mapcol [lindex $file_states($path) 0] $path]
479 set lno [lsearch -sorted $file_lists($old_w) $path]
481 set file_lists($old_w) \
482 [lreplace $file_lists($old_w) $lno $lno]
484 $old_w conf -state normal
485 $old_w delete $lno.0 [expr $lno + 1].0
486 $old_w conf -state disabled
490 proc show_diff {path {w {}} {lno {}}} {
491 global file_states file_lists
492 global PARENT diff_3way diff_active
493 global ui_diff ui_fname_value ui_fstatus_value ui_status_value
495 if {$diff_active || ![lock_index read]} return
498 if {$w == {} || $lno == {}} {
499 foreach w [array names file_lists] {
500 set lno [lsearch -sorted $file_lists($w) $path]
507 if {$w != {} && $lno >= 1} {
508 $w tag add in_diff $lno.0 [expr $lno + 1].0
511 set s $file_states($path)
515 set ui_fname_value [escape_path $path]
516 set ui_fstatus_value [mapdesc $m $path]
517 set ui_status_value "Loading diff of [escape_path $path]..."
519 set cmd [list | git diff-index -p $PARENT -- $path]
522 set cmd [list | git diff-index -p -c $PARENT $path]
526 set fd [open $path r]
527 set content [read $fd]
532 set ui_status_value "Unable to display [escape_path $path]"
533 error_popup "Error loading file:\n\n$err"
536 $ui_diff conf -state normal
537 $ui_diff insert end $content
538 $ui_diff conf -state disabled
541 set ui_status_value {Ready.}
546 if {[catch {set fd [open $cmd r]} err]} {
549 set ui_status_value "Unable to display [escape_path $path]"
550 error_popup "Error loading diff:\n\n$err"
554 fconfigure $fd -blocking 0 -translation auto
555 fileevent $fd readable [list read_diff $fd]
558 proc read_diff {fd} {
559 global ui_diff ui_status_value diff_3way diff_active
562 while {[gets $fd line] >= 0} {
563 if {[string match {diff --git *} $line]} continue
564 if {[string match {diff --combined *} $line]} continue
565 if {[string match {--- *} $line]} continue
566 if {[string match {+++ *} $line]} continue
567 if {[string match index* $line]} {
568 if {[string first , $line] >= 0} {
573 $ui_diff conf -state normal
575 set x [string index $line 0]
580 default {set tags {}}
583 set x [string range $line 0 1]
585 default {set tags {}}
587 "++" {set tags dp; set x " +"}
588 " +" {set tags {di bold}; set x "++"}
589 "+ " {set tags dni; set x "-+"}
590 "--" {set tags dm; set x " -"}
591 " -" {set tags {dm bold}; set x "--"}
592 "- " {set tags di; set x "+-"}
593 default {set tags {}}
595 set line [string replace $line 0 1 $x]
597 $ui_diff insert end $line $tags
598 $ui_diff insert end "\n"
599 $ui_diff conf -state disabled
606 set ui_status_value {Ready.}
608 if {$repo_config(gui.trustmtime) == {true}
609 && [$ui_diff index end] == {2.0}} {
615 ######################################################################
619 proc load_last_commit {} {
620 global HEAD PARENT commit_type ui_comm
622 if {$commit_type == {amend}} return
623 if {$commit_type != {normal}} {
624 error_popup "Can't amend a $commit_type commit."
632 set fd [open "| git cat-file commit $HEAD" r]
633 while {[gets $fd line] > 0} {
634 if {[string match {parent *} $line]} {
635 set parent [string range $line 7 end]
639 set msg [string trim [read $fd]]
642 error_popup "Error loading commit data for amend:\n\n$err"
646 if {$parent_count == 0} {
647 set commit_type amend
651 } elseif {$parent_count == 1} {
652 set commit_type amend
654 $ui_comm delete 0.0 end
655 $ui_comm insert end $msg
656 $ui_comm edit modified false
660 error_popup {You can't amend a merge commit.}
665 proc commit_tree {} {
666 global tcl_platform HEAD gitdir commit_type file_states
667 global commit_active pch_error
668 global ui_status_value ui_comm
670 if {$commit_active || ![lock_index update]} return
672 # -- Our in memory state should match the repository.
674 repository_state curHEAD cur_type
675 if {$commit_type == {amend}
676 && $cur_type == {normal}
677 && $curHEAD == $HEAD} {
678 } elseif {$commit_type != $cur_type || $HEAD != $curHEAD} {
679 error_popup {Last scanned state does not match repository state.
681 Its highly likely that another Git program modified the
682 repository since our last scan. A rescan is required
690 # -- At least one file should differ in the index.
693 foreach path [array names file_states] {
694 set s $file_states($path)
695 switch -glob -- [lindex $s 0] {
699 M? {set files_ready 1; break}
701 error_popup "Unmerged files cannot be committed.
703 File [short_path $path] has merge conflicts.
704 You must resolve them and include the file before committing.
710 error_popup "Unknown file state [lindex $s 0] detected.
712 File [short_path $path] cannot be committed by this program.
718 error_popup {No included files to commit.
720 You must include at least 1 file before you can commit.
726 # -- A message is required.
728 set msg [string trim [$ui_comm get 1.0 end]]
730 error_popup {Please supply a commit message.
732 A good commit message has the following format:
734 - First line: Describe in one sentance what you did.
736 - Remaining lines: Describe why this change is good.
744 # -- Ask the pre-commit hook for the go-ahead.
746 set pchook [file join $gitdir hooks pre-commit]
747 if {$tcl_platform(platform) == {windows} && [file isfile $pchook]} {
748 set pchook [list sh -c [concat \
749 "if test -x \"$pchook\";" \
750 "then exec \"$pchook\" 2>&1;" \
752 } elseif {[file executable $pchook]} {
753 set pchook [list $pchook |& cat]
758 set ui_status_value {Calling pre-commit hook...}
760 set fd_ph [open "| $pchook" r]
761 fconfigure $fd_ph -blocking 0 -translation binary
762 fileevent $fd_ph readable \
763 [list commit_stage1 $fd_ph $curHEAD $msg]
765 commit_stage2 $curHEAD $msg
769 proc commit_stage1 {fd_ph curHEAD msg} {
770 global commit_active pch_error ui_status_value
772 append pch_error [read $fd_ph]
773 fconfigure $fd_ph -blocking 1
775 if {[catch {close $fd_ph}]} {
776 set ui_status_value {Commit declined by pre-commit hook.}
777 hook_failed_popup pre-commit $pch_error
783 commit_stage2 $curHEAD $msg
786 fconfigure $fd_ph -blocking 0
789 proc commit_stage2 {curHEAD msg} {
790 global ui_status_value
792 # -- Write the tree in the background.
794 set ui_status_value {Committing changes...}
795 set fd_wt [open "| git write-tree" r]
796 fileevent $fd_wt readable [list commit_stage3 $fd_wt $curHEAD $msg]
799 proc commit_stage3 {fd_wt curHEAD msg} {
800 global single_commit gitdir HEAD PARENT commit_type
801 global commit_active ui_status_value ui_comm
805 if {$tree_id == {} || [catch {close $fd_wt} err]} {
806 error_popup "write-tree failed:\n\n$err"
808 set ui_status_value {Commit failed.}
813 # -- Create the commit.
815 set cmd [list git commit-tree $tree_id]
817 lappend cmd -p $PARENT
819 if {$commit_type == {merge}} {
821 set fd_mh [open [file join $gitdir MERGE_HEAD] r]
822 while {[gets $fd_mh merge_head] >= 0} {
823 lappend cmd -p $merge_head
827 error_popup "Loading MERGE_HEAD failed:\n\n$err"
829 set ui_status_value {Commit failed.}
835 # git commit-tree writes to stderr during initial commit.
836 lappend cmd 2>/dev/null
839 if {[catch {set cmt_id [eval exec $cmd]} err]} {
840 error_popup "commit-tree failed:\n\n$err"
842 set ui_status_value {Commit failed.}
847 # -- Update the HEAD ref.
850 if {$commit_type != {normal}} {
851 append reflogm " ($commit_type)"
853 set i [string first "\n" $msg]
855 append reflogm {: } [string range $msg 0 [expr $i - 1]]
857 append reflogm {: } $msg
859 set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD]
860 if {[catch {eval exec $cmd} err]} {
861 error_popup "update-ref failed:\n\n$err"
863 set ui_status_value {Commit failed.}
868 # -- Cleanup after ourselves.
870 catch {file delete [file join $gitdir MERGE_HEAD]}
871 catch {file delete [file join $gitdir MERGE_MSG]}
872 catch {file delete [file join $gitdir SQUASH_MSG]}
873 catch {file delete [file join $gitdir GITGUI_MSG]}
875 # -- Let rerere do its thing.
877 if {[file isdirectory [file join $gitdir rr-cache]]} {
878 catch {exec git rerere}
881 $ui_comm delete 0.0 end
882 $ui_comm edit modified false
885 if {$single_commit} do_quit
887 # -- Update status without invoking any git commands.
890 set commit_type normal
894 foreach path [array names file_states] {
895 set s $file_states($path)
900 D? {set m _[string index $m 1]}
904 unset file_states($path)
906 lset file_states($path) 0 $m
913 set ui_status_value \
914 "Changes committed as [string range $cmt_id 0 7]."
917 ######################################################################
921 proc fetch_from {remote} {
922 set w [new_console "fetch $remote" \
923 "Fetching new changes from $remote"]
924 set cmd [list git fetch]
929 proc pull_remote {remote branch} {
930 global HEAD commit_type file_states repo_config
932 if {![lock_index update]} return
934 # -- Our in memory state should match the repository.
936 repository_state curHEAD cur_type
937 if {$commit_type != $cur_type || $HEAD != $curHEAD} {
938 error_popup {Last scanned state does not match repository state.
940 Its highly likely that another Git program modified the
941 repository since our last scan. A rescan is required
942 before a pull can be started.
949 # -- No differences should exist before a pull.
951 if {[array size file_states] != 0} {
952 error_popup {Uncommitted but modified files are present.
954 You should not perform a pull with unmodified files in your working
955 directory as Git would be unable to recover from an incorrect merge.
957 Commit or throw away all changes before starting a pull operation.
963 set w [new_console "pull $remote $branch" \
964 "Pulling new changes from branch $branch in $remote"]
965 set cmd [list git pull]
966 if {$repo_config(gui.pullsummary) == {false}} {
967 lappend cmd --no-summary
971 console_exec $w $cmd [list post_pull_remote $remote $branch]
974 proc post_pull_remote {remote branch success} {
975 global HEAD PARENT commit_type
976 global ui_status_value
980 repository_state HEAD commit_type
982 set $ui_status_value {Ready.}
985 "Conflicts detected while pulling $branch from $remote."
989 proc push_to {remote} {
990 set w [new_console "push $remote" \
991 "Pushing changes to $remote"]
992 set cmd [list git push]
997 ######################################################################
1001 proc mapcol {state path} {
1002 global all_cols ui_other
1004 if {[catch {set r $all_cols($state)}]} {
1005 puts "error: no column for state={$state} $path"
1011 proc mapicon {state path} {
1014 if {[catch {set r $all_icons($state)}]} {
1015 puts "error: no icon for state={$state} $path"
1021 proc mapdesc {state path} {
1024 if {[catch {set r $all_descs($state)}]} {
1025 puts "error: no desc for state={$state} $path"
1031 proc escape_path {path} {
1032 regsub -all "\n" $path "\\n" path
1036 proc short_path {path} {
1037 return [escape_path [lindex [file split $path] end]]
1042 proc merge_state {path new_state} {
1043 global file_states next_icon_id
1045 set s0 [string index $new_state 0]
1046 set s1 [string index $new_state 1]
1048 if {[catch {set info $file_states($path)}]} {
1050 set icon n[incr next_icon_id]
1052 set state [lindex $info 0]
1053 set icon [lindex $info 1]
1057 set s0 [string index $state 0]
1058 } elseif {$s0 == {*}} {
1063 set s1 [string index $state 1]
1064 } elseif {$s1 == {*}} {
1068 set file_states($path) [list $s0$s1 $icon]
1072 proc display_file {path state} {
1073 global file_states file_lists status_active
1075 set old_m [merge_state $path $state]
1076 if {$status_active} return
1078 set s $file_states($path)
1079 set new_m [lindex $s 0]
1080 set new_w [mapcol $new_m $path]
1081 set old_w [mapcol $old_m $path]
1082 set new_icon [mapicon $new_m $path]
1084 if {$new_w != $old_w} {
1085 set lno [lsearch -sorted $file_lists($old_w) $path]
1088 $old_w conf -state normal
1089 $old_w delete $lno.0 [expr $lno + 1].0
1090 $old_w conf -state disabled
1093 lappend file_lists($new_w) $path
1094 set file_lists($new_w) [lsort $file_lists($new_w)]
1095 set lno [lsearch -sorted $file_lists($new_w) $path]
1097 $new_w conf -state normal
1098 $new_w image create $lno.0 \
1099 -align center -padx 5 -pady 1 \
1100 -name [lindex $s 1] \
1102 $new_w insert $lno.1 "[escape_path $path]\n"
1103 $new_w conf -state disabled
1104 } elseif {$new_icon != [mapicon $old_m $path]} {
1105 $new_w conf -state normal
1106 $new_w image conf [lindex $s 1] -image $new_icon
1107 $new_w conf -state disabled
1111 proc display_all_files {} {
1112 global ui_index ui_other file_states file_lists
1114 $ui_index conf -state normal
1115 $ui_other conf -state normal
1117 $ui_index delete 0.0 end
1118 $ui_other delete 0.0 end
1120 set file_lists($ui_index) [list]
1121 set file_lists($ui_other) [list]
1123 foreach path [lsort [array names file_states]] {
1124 set s $file_states($path)
1126 set w [mapcol $m $path]
1127 lappend file_lists($w) $path
1128 $w image create end \
1129 -align center -padx 5 -pady 1 \
1130 -name [lindex $s 1] \
1131 -image [mapicon $m $path]
1132 $w insert end "[escape_path $path]\n"
1135 $ui_index conf -state disabled
1136 $ui_other conf -state disabled
1139 proc update_index {pathList} {
1140 global update_index_cp ui_status_value
1142 if {![lock_index update]} return
1144 set update_index_cp 0
1145 set totalCnt [llength $pathList]
1146 set batch [expr {int($totalCnt * .01) + 1}]
1147 if {$batch > 25} {set batch 25}
1149 set ui_status_value "Including files ... 0/$totalCnt 0%"
1150 set ui_status_value [format \
1151 "Including files ... %i/%i files (%.2f%%)" \
1155 set fd [open "| git update-index --add --remove -z --stdin" w]
1156 fconfigure $fd -blocking 0 -translation binary
1157 fileevent $fd writable [list \
1158 write_update_index \
1166 proc write_update_index {fd pathList totalCnt batch} {
1167 global update_index_cp ui_status_value
1168 global file_states ui_fname_value
1170 if {$update_index_cp >= $totalCnt} {
1173 set ui_status_value {Ready.}
1177 for {set i $batch} \
1178 {$update_index_cp < $totalCnt && $i > 0} \
1180 set path [lindex $pathList $update_index_cp]
1181 incr update_index_cp
1183 switch -- [lindex $file_states($path) 0] {
1193 puts -nonewline $fd $path
1194 puts -nonewline $fd "\0"
1195 display_file $path $new
1196 if {$ui_fname_value == $path} {
1201 set ui_status_value [format \
1202 "Including files ... %i/%i files (%.2f%%)" \
1205 [expr {100.0 * $update_index_cp / $totalCnt}]]
1208 ######################################################################
1210 ## remote management
1212 proc load_all_remotes {} {
1213 global gitdir all_remotes repo_config
1215 set all_remotes [list]
1216 set rm_dir [file join $gitdir remotes]
1217 if {[file isdirectory $rm_dir]} {
1218 set all_remotes [concat $all_remotes [glob \
1222 -directory $rm_dir *]]
1225 foreach line [array names repo_config remote.*.url] {
1226 if {[regexp ^remote\.(.*)\.url\$ $line line name]} {
1227 lappend all_remotes $name
1231 set all_remotes [lsort -unique $all_remotes]
1234 proc populate_remote_menu {m pfx op} {
1237 foreach remote $all_remotes {
1238 $m add command -label "$pfx $remote..." \
1239 -command [list $op $remote] \
1244 proc populate_pull_menu {m} {
1245 global gitdir repo_config all_remotes disable_on_lock
1247 foreach remote $all_remotes {
1249 if {[array get repo_config remote.$remote.url] != {}} {
1250 if {[array get repo_config remote.$remote.fetch] != {}} {
1251 regexp {^([^:]+):} \
1252 [lindex $repo_config(remote.$remote.fetch) 0] \
1257 set fd [open [file join $gitdir remotes $remote] r]
1258 while {[gets $fd line] >= 0} {
1259 if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} {
1268 regsub ^refs/heads/ $rb {} rb_short
1269 if {$rb_short != {}} {
1271 -label "Branch $rb_short from $remote..." \
1272 -command [list pull_remote $remote $rb] \
1274 lappend disable_on_lock \
1275 [list $m entryconf [$m index last] -state]
1280 ######################################################################
1285 #define mask_width 14
1286 #define mask_height 15
1287 static unsigned char mask_bits[] = {
1288 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1289 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1290 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1293 image create bitmap file_plain -background white -foreground black -data {
1294 #define plain_width 14
1295 #define plain_height 15
1296 static unsigned char plain_bits[] = {
1297 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1298 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1299 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1300 } -maskdata $filemask
1302 image create bitmap file_mod -background white -foreground blue -data {
1303 #define mod_width 14
1304 #define mod_height 15
1305 static unsigned char mod_bits[] = {
1306 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1307 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1308 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1309 } -maskdata $filemask
1311 image create bitmap file_fulltick -background white -foreground "#007000" -data {
1312 #define file_fulltick_width 14
1313 #define file_fulltick_height 15
1314 static unsigned char file_fulltick_bits[] = {
1315 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1316 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1317 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1318 } -maskdata $filemask
1320 image create bitmap file_parttick -background white -foreground "#005050" -data {
1321 #define parttick_width 14
1322 #define parttick_height 15
1323 static unsigned char parttick_bits[] = {
1324 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1325 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1326 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1327 } -maskdata $filemask
1329 image create bitmap file_question -background white -foreground black -data {
1330 #define file_question_width 14
1331 #define file_question_height 15
1332 static unsigned char file_question_bits[] = {
1333 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1334 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1335 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1336 } -maskdata $filemask
1338 image create bitmap file_removed -background white -foreground red -data {
1339 #define file_removed_width 14
1340 #define file_removed_height 15
1341 static unsigned char file_removed_bits[] = {
1342 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1343 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1344 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1345 } -maskdata $filemask
1347 image create bitmap file_merge -background white -foreground blue -data {
1348 #define file_merge_width 14
1349 #define file_merge_height 15
1350 static unsigned char file_merge_bits[] = {
1351 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1352 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1353 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1354 } -maskdata $filemask
1356 set ui_index .vpane.files.index.list
1357 set ui_other .vpane.files.other.list
1358 set max_status_desc 0
1360 {__ i plain "Unmodified"}
1361 {_M i mod "Modified"}
1362 {M_ i fulltick "Checked in"}
1363 {MM i parttick "Partially included"}
1365 {_O o plain "Untracked"}
1366 {A_ o fulltick "Added"}
1367 {AM o parttick "Partially added"}
1368 {AD o question "Added (but now gone)"}
1370 {_D i question "Missing"}
1371 {D_ i removed "Removed"}
1372 {DD i removed "Removed"}
1373 {DO i removed "Removed (still exists)"}
1375 {UM i merge "Merge conflicts"}
1376 {U_ i merge "Merge conflicts"}
1378 if {$max_status_desc < [string length [lindex $i 3]]} {
1379 set max_status_desc [string length [lindex $i 3]]
1381 if {[lindex $i 1] == {i}} {
1382 set all_cols([lindex $i 0]) $ui_index
1384 set all_cols([lindex $i 0]) $ui_other
1386 set all_icons([lindex $i 0]) file_[lindex $i 2]
1387 set all_descs([lindex $i 0]) [lindex $i 3]
1391 ######################################################################
1396 global tcl_platform tk_library
1397 if {$tcl_platform(platform) == {unix}
1398 && $tcl_platform(os) == {Darwin}
1399 && [string match /Library/Frameworks/* $tk_library]} {
1405 proc bind_button3 {w cmd} {
1406 bind $w <Any-Button-3> $cmd
1408 bind $w <Control-Button-1> $cmd
1412 proc incr_font_size {font {amt 1}} {
1413 set sz [font configure $font -size]
1415 font configure $font -size $sz
1416 font configure ${font}bold -size $sz
1419 proc hook_failed_popup {hook msg} {
1420 global gitdir appname
1426 label $w.m.l1 -text "$hook hook failed:" \
1431 -background white -borderwidth 1 \
1433 -width 80 -height 10 \
1435 -yscrollcommand [list $w.m.sby set]
1437 -text {You must correct the above errors before committing.} \
1441 scrollbar $w.m.sby -command [list $w.m.t yview]
1442 pack $w.m.l1 -side top -fill x
1443 pack $w.m.l2 -side bottom -fill x
1444 pack $w.m.sby -side right -fill y
1445 pack $w.m.t -side left -fill both -expand 1
1446 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1448 $w.m.t insert 1.0 $msg
1449 $w.m.t conf -state disabled
1451 button $w.ok -text OK \
1454 -command "destroy $w"
1455 pack $w.ok -side bottom
1457 bind $w <Visibility> "grab $w; focus $w"
1458 bind $w <Key-Return> "destroy $w"
1459 wm title $w "$appname ([lindex [file split \
1460 [file normalize [file dirname $gitdir]]] \
1465 set next_console_id 0
1467 proc new_console {short_title long_title} {
1468 global next_console_id console_data
1469 set w .console[incr next_console_id]
1470 set console_data($w) [list $short_title $long_title]
1471 return [console_init $w]
1474 proc console_init {w} {
1475 global console_cr console_data
1476 global gitdir appname M1B
1478 set console_cr($w) 1.0
1481 label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
1486 -background white -borderwidth 1 \
1488 -width 80 -height 10 \
1491 -yscrollcommand [list $w.m.sby set]
1492 label $w.m.s -anchor w \
1495 scrollbar $w.m.sby -command [list $w.m.t yview]
1496 pack $w.m.l1 -side top -fill x
1497 pack $w.m.s -side bottom -fill x
1498 pack $w.m.sby -side right -fill y
1499 pack $w.m.t -side left -fill both -expand 1
1500 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1502 menu $w.ctxm -tearoff 0
1503 $w.ctxm add command -label "Copy" \
1505 -command "tk_textCopy $w.m.t"
1506 $w.ctxm add command -label "Select All" \
1508 -command "$w.m.t tag add sel 0.0 end"
1509 $w.ctxm add command -label "Copy All" \
1512 $w.m.t tag add sel 0.0 end
1514 $w.m.t tag remove sel 0.0 end
1517 button $w.ok -text {Running...} \
1521 -command "destroy $w"
1522 pack $w.ok -side bottom
1524 bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y"
1525 bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break"
1526 bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break"
1527 bind $w <Visibility> "focus $w"
1528 wm title $w "$appname ([lindex [file split \
1529 [file normalize [file dirname $gitdir]]] \
1530 end]): [lindex $console_data($w) 0]"
1534 proc console_exec {w cmd {after {}}} {
1537 # -- Windows tosses the enviroment when we exec our child.
1538 # But most users need that so we have to relogin. :-(
1540 if {$tcl_platform(platform) == {windows}} {
1541 set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
1544 # -- Tcl won't let us redirect both stdout and stderr to
1545 # the same pipe. So pass it through cat...
1547 set cmd [concat | $cmd |& cat]
1549 set fd_f [open $cmd r]
1550 fconfigure $fd_f -blocking 0 -translation binary
1551 fileevent $fd_f readable [list console_read $w $fd_f $after]
1554 proc console_read {w fd after} {
1555 global console_cr console_data
1559 if {![winfo exists $w]} {console_init $w}
1560 $w.m.t conf -state normal
1562 set n [string length $buf]
1564 set cr [string first "\r" $buf $c]
1565 set lf [string first "\n" $buf $c]
1566 if {$cr < 0} {set cr [expr $n + 1]}
1567 if {$lf < 0} {set lf [expr $n + 1]}
1570 $w.m.t insert end [string range $buf $c $lf]
1571 set console_cr($w) [$w.m.t index {end -1c}]
1575 $w.m.t delete $console_cr($w) end
1576 $w.m.t insert end "\n"
1577 $w.m.t insert end [string range $buf $c $cr]
1582 $w.m.t conf -state disabled
1586 fconfigure $fd -blocking 1
1588 if {[catch {close $fd}]} {
1589 if {![winfo exists $w]} {console_init $w}
1590 $w.m.s conf -background red -text {Error: Command Failed}
1591 $w.ok conf -text Close
1592 $w.ok conf -state normal
1594 } elseif {[winfo exists $w]} {
1595 $w.m.s conf -background green -text {Success}
1596 $w.ok conf -text Close
1597 $w.ok conf -state normal
1600 array unset console_cr $w
1601 array unset console_data $w
1603 uplevel #0 $after $ok
1607 fconfigure $fd -blocking 0
1610 ######################################################################
1614 set starting_gitk_msg {Please wait... Starting gitk...}
1617 global tcl_platform ui_status_value starting_gitk_msg
1619 set ui_status_value $starting_gitk_msg
1621 if {$ui_status_value == $starting_gitk_msg} {
1622 set ui_status_value {Ready.}
1626 if {$tcl_platform(platform) == {windows}} {
1634 set w [new_console "repack" "Repacking the object database"]
1635 set cmd [list git repack]
1638 console_exec $w $cmd
1644 global gitdir ui_comm is_quitting repo_config
1646 if {$is_quitting} return
1649 # -- Stash our current commit buffer.
1651 set save [file join $gitdir GITGUI_MSG]
1652 set msg [string trim [$ui_comm get 0.0 end]]
1653 if {[$ui_comm edit modified] && $msg != {}} {
1655 set fd [open $save w]
1656 puts $fd [string trim [$ui_comm get 0.0 end]]
1659 } elseif {$msg == {} && [file exists $save]} {
1663 # -- Stash our current window geometry into this repository.
1665 set cfg_geometry [list]
1666 lappend cfg_geometry [wm geometry .]
1667 lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
1668 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
1669 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1672 if {$cfg_geometry != $rc_geometry} {
1673 catch {exec git repo-config gui.geometry $cfg_geometry}
1683 proc do_include_all {} {
1686 if {![lock_index begin-update]} return
1689 foreach path [array names file_states] {
1690 set s $file_states($path)
1696 _D {lappend pathList $path}
1699 if {$pathList == {}} {
1702 update_index $pathList
1706 set GIT_COMMITTER_IDENT {}
1708 proc do_signoff {} {
1709 global ui_comm GIT_COMMITTER_IDENT
1711 if {$GIT_COMMITTER_IDENT == {}} {
1712 if {[catch {set me [exec git var GIT_COMMITTER_IDENT]} err]} {
1713 error_popup "Unable to obtain your identity:\n\n$err"
1716 if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
1717 $me me GIT_COMMITTER_IDENT]} {
1718 error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me"
1723 set sob "Signed-off-by: $GIT_COMMITTER_IDENT"
1724 set last [$ui_comm get {end -1c linestart} {end -1c}]
1725 if {$last != $sob} {
1726 $ui_comm edit separator
1728 && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
1729 $ui_comm insert end "\n"
1731 $ui_comm insert end "\n$sob"
1732 $ui_comm edit separator
1737 proc do_amend_last {} {
1745 proc do_options {} {
1746 global appname gitdir font_descs
1747 global repo_config global_config
1748 global repo_config_new global_config_new
1751 array unset repo_config_new
1752 array unset global_config_new
1753 foreach name [array names repo_config] {
1754 set repo_config_new($name) $repo_config($name)
1756 foreach name [array names global_config] {
1757 set global_config_new($name) $global_config($name)
1759 set reponame [lindex [file split \
1760 [file normalize [file dirname $gitdir]]] \
1763 set w .options_editor
1765 wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
1767 label $w.header -text "$appname Options" \
1769 pack $w.header -side top -fill x
1772 button $w.buttons.restore -text {Restore Defaults} \
1774 -command do_restore_defaults
1775 pack $w.buttons.restore -side left
1776 button $w.buttons.save -text Save \
1778 -command [list do_save_config $w]
1779 pack $w.buttons.save -side right
1780 button $w.buttons.cancel -text {Cancel} \
1782 -command [list destroy $w]
1783 pack $w.buttons.cancel -side right
1784 pack $w.buttons -side bottom -fill x -pady 10 -padx 10
1786 labelframe $w.repo -text "$reponame Repository" \
1788 -relief raised -borderwidth 2
1789 labelframe $w.global -text {Global (All Repositories)} \
1791 -relief raised -borderwidth 2
1792 pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5
1793 pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5
1796 {pullsummary {Show Pull Summary}}
1797 {trustmtime {Trust File Modification Timestamps}}
1799 set name [lindex $option 0]
1800 set text [lindex $option 1]
1801 foreach f {repo global} {
1802 checkbutton $w.$f.$name -text $text \
1803 -variable ${f}_config_new(gui.$name) \
1807 pack $w.$f.$name -side top -anchor w
1811 set all_fonts [lsort [font families]]
1812 foreach option $font_descs {
1813 set name [lindex $option 0]
1814 set font [lindex $option 1]
1815 set text [lindex $option 2]
1817 set global_config_new(gui.$font^^family) \
1818 [font configure $font -family]
1819 set global_config_new(gui.$font^^size) \
1820 [font configure $font -size]
1822 frame $w.global.$name
1823 label $w.global.$name.l -text "$text:" -font font_ui
1824 pack $w.global.$name.l -side left -anchor w -fill x
1825 eval tk_optionMenu $w.global.$name.family \
1826 global_config_new(gui.$font^^family) \
1828 spinbox $w.global.$name.size \
1829 -textvariable global_config_new(gui.$font^^size) \
1830 -from 2 -to 80 -increment 1 \
1833 pack $w.global.$name.size -side right -anchor e
1834 pack $w.global.$name.family -side right -anchor e
1835 pack $w.global.$name -side top -anchor w -fill x
1838 bind $w <Visibility> "grab $w; focus $w"
1839 bind $w <Key-Escape> "destroy $w"
1840 wm title $w "$appname ($reponame): Options"
1844 proc do_restore_defaults {} {
1845 global font_descs default_config repo_config
1846 global repo_config_new global_config_new
1848 foreach name [array names default_config] {
1849 set repo_config_new($name) $default_config($name)
1850 set global_config_new($name) $default_config($name)
1853 foreach option $font_descs {
1854 set name [lindex $option 0]
1855 set repo_config(gui.$name) $default_config(gui.$name)
1859 foreach option $font_descs {
1860 set name [lindex $option 0]
1861 set font [lindex $option 1]
1862 set global_config_new(gui.$font^^family) \
1863 [font configure $font -family]
1864 set global_config_new(gui.$font^^size) \
1865 [font configure $font -size]
1869 proc do_save_config {w} {
1870 if {[catch {save_config} err]} {
1871 error_popup "Failed to completely save options:\n\n$err"
1876 # shift == 1: left click
1878 proc click {w x y shift wx wy} {
1879 global ui_index ui_other file_lists
1881 set pos [split [$w index @$x,$y] .]
1882 set lno [lindex $pos 0]
1883 set col [lindex $pos 1]
1884 set path [lindex $file_lists($w) [expr $lno - 1]]
1885 if {$path == {}} return
1887 if {$col > 0 && $shift == 1} {
1888 show_diff $path $w $lno
1892 proc unclick {w x y} {
1895 set pos [split [$w index @$x,$y] .]
1896 set lno [lindex $pos 0]
1897 set col [lindex $pos 1]
1898 set path [lindex $file_lists($w) [expr $lno - 1]]
1899 if {$path == {}} return
1902 update_index [list $path]
1906 ######################################################################
1910 set cursor_ptr arrow
1911 font create font_diff -family Courier -size 10
1915 eval font configure font_ui [font actual [.dummy cget -font]]
1919 font create font_uibold
1920 font create font_diffbold
1924 if {$tcl_platform(platform) == {windows}} {
1927 } elseif {[is_MacOSX]} {
1932 proc apply_config {} {
1933 global repo_config font_descs
1935 foreach option $font_descs {
1936 set name [lindex $option 0]
1937 set font [lindex $option 1]
1939 foreach {cn cv} $repo_config(gui.$name) {
1940 font configure $font $cn $cv
1943 error_popup "Invalid font specified in gui.$name:\n\n$err"
1945 foreach {cn cv} [font configure $font] {
1946 font configure ${font}bold $cn $cv
1948 font configure ${font}bold -weight bold
1952 set default_config(gui.trustmtime) false
1953 set default_config(gui.pullsummary) true
1954 set default_config(gui.fontui) [font configure font_ui]
1955 set default_config(gui.fontdiff) [font configure font_diff]
1957 {fontui font_ui {Main Font}}
1958 {fontdiff font_diff {Diff/Console Font}}
1963 ######################################################################
1968 menu .mbar -tearoff 0
1969 .mbar add cascade -label Project -menu .mbar.project
1970 .mbar add cascade -label Edit -menu .mbar.edit
1971 .mbar add cascade -label Commit -menu .mbar.commit
1972 if {!$single_commit} {
1973 .mbar add cascade -label Fetch -menu .mbar.fetch
1974 .mbar add cascade -label Pull -menu .mbar.pull
1975 .mbar add cascade -label Push -menu .mbar.push
1977 . configure -menu .mbar
1981 .mbar.project add command -label Visualize \
1984 if {!$single_commit} {
1985 .mbar.project add command -label {Repack Database} \
1986 -command do_repack \
1989 .mbar.project add command -label Quit \
1991 -accelerator $M1T-Q \
1997 .mbar.edit add command -label Undo \
1998 -command {catch {[focus] edit undo}} \
1999 -accelerator $M1T-Z \
2001 .mbar.edit add command -label Redo \
2002 -command {catch {[focus] edit redo}} \
2003 -accelerator $M1T-Y \
2005 .mbar.edit add separator
2006 .mbar.edit add command -label Cut \
2007 -command {catch {tk_textCut [focus]}} \
2008 -accelerator $M1T-X \
2010 .mbar.edit add command -label Copy \
2011 -command {catch {tk_textCopy [focus]}} \
2012 -accelerator $M1T-C \
2014 .mbar.edit add command -label Paste \
2015 -command {catch {tk_textPaste [focus]; [focus] see insert}} \
2016 -accelerator $M1T-V \
2018 .mbar.edit add command -label Delete \
2019 -command {catch {[focus] delete sel.first sel.last}} \
2022 .mbar.edit add separator
2023 .mbar.edit add command -label {Select All} \
2024 -command {catch {[focus] tag add sel 0.0 end}} \
2025 -accelerator $M1T-A \
2027 .mbar.edit add separator
2028 .mbar.edit add command -label {Options...} \
2029 -command do_options \
2034 .mbar.commit add command -label Rescan \
2035 -command do_rescan \
2038 lappend disable_on_lock \
2039 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2040 .mbar.commit add command -label {Amend Last Commit} \
2041 -command do_amend_last \
2043 lappend disable_on_lock \
2044 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2045 .mbar.commit add command -label {Include All Files} \
2046 -command do_include_all \
2047 -accelerator $M1T-I \
2049 lappend disable_on_lock \
2050 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2051 .mbar.commit add command -label {Sign Off} \
2052 -command do_signoff \
2053 -accelerator $M1T-S \
2055 .mbar.commit add command -label Commit \
2056 -command do_commit \
2057 -accelerator $M1T-Return \
2059 lappend disable_on_lock \
2060 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2062 if {!$single_commit} {
2073 # -- Main Window Layout
2074 panedwindow .vpane -orient vertical
2075 panedwindow .vpane.files -orient horizontal
2076 .vpane add .vpane.files -sticky nsew -height 100 -width 400
2077 pack .vpane -anchor n -side top -fill both -expand 1
2079 # -- Index File List
2080 frame .vpane.files.index -height 100 -width 400
2081 label .vpane.files.index.title -text {Modified Files} \
2084 text $ui_index -background white -borderwidth 0 \
2085 -width 40 -height 10 \
2087 -cursor $cursor_ptr \
2088 -yscrollcommand {.vpane.files.index.sb set} \
2090 scrollbar .vpane.files.index.sb -command [list $ui_index yview]
2091 pack .vpane.files.index.title -side top -fill x
2092 pack .vpane.files.index.sb -side right -fill y
2093 pack $ui_index -side left -fill both -expand 1
2094 .vpane.files add .vpane.files.index -sticky nsew
2096 # -- Other (Add) File List
2097 frame .vpane.files.other -height 100 -width 100
2098 label .vpane.files.other.title -text {Untracked Files} \
2101 text $ui_other -background white -borderwidth 0 \
2102 -width 40 -height 10 \
2104 -cursor $cursor_ptr \
2105 -yscrollcommand {.vpane.files.other.sb set} \
2107 scrollbar .vpane.files.other.sb -command [list $ui_other yview]
2108 pack .vpane.files.other.title -side top -fill x
2109 pack .vpane.files.other.sb -side right -fill y
2110 pack $ui_other -side left -fill both -expand 1
2111 .vpane.files add .vpane.files.other -sticky nsew
2113 $ui_index tag conf in_diff -font font_uibold
2114 $ui_other tag conf in_diff -font font_uibold
2116 # -- Diff and Commit Area
2117 frame .vpane.lower -height 300 -width 400
2118 frame .vpane.lower.commarea
2119 frame .vpane.lower.diff -relief sunken -borderwidth 1
2120 pack .vpane.lower.commarea -side top -fill x
2121 pack .vpane.lower.diff -side bottom -fill both -expand 1
2122 .vpane add .vpane.lower -stick nsew
2124 # -- Commit Area Buttons
2125 frame .vpane.lower.commarea.buttons
2126 label .vpane.lower.commarea.buttons.l -text {} \
2130 pack .vpane.lower.commarea.buttons.l -side top -fill x
2131 pack .vpane.lower.commarea.buttons -side left -fill y
2133 button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
2134 -command do_rescan \
2136 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
2137 lappend disable_on_lock \
2138 {.vpane.lower.commarea.buttons.rescan conf -state}
2140 button .vpane.lower.commarea.buttons.amend -text {Amend Last} \
2141 -command do_amend_last \
2143 pack .vpane.lower.commarea.buttons.amend -side top -fill x
2144 lappend disable_on_lock \
2145 {.vpane.lower.commarea.buttons.amend conf -state}
2147 button .vpane.lower.commarea.buttons.incall -text {Include All} \
2148 -command do_include_all \
2150 pack .vpane.lower.commarea.buttons.incall -side top -fill x
2151 lappend disable_on_lock \
2152 {.vpane.lower.commarea.buttons.incall conf -state}
2154 button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
2155 -command do_signoff \
2157 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
2159 button .vpane.lower.commarea.buttons.commit -text {Commit} \
2160 -command do_commit \
2162 pack .vpane.lower.commarea.buttons.commit -side top -fill x
2163 lappend disable_on_lock \
2164 {.vpane.lower.commarea.buttons.commit conf -state}
2166 # -- Commit Message Buffer
2167 frame .vpane.lower.commarea.buffer
2168 set ui_comm .vpane.lower.commarea.buffer.t
2169 set ui_coml .vpane.lower.commarea.buffer.l
2170 label $ui_coml -text {Commit Message:} \
2174 trace add variable commit_type write {uplevel #0 {
2175 switch -glob $commit_type \
2176 initial {$ui_coml conf -text {Initial Commit Message:}} \
2177 amend {$ui_coml conf -text {Amended Commit Message:}} \
2178 merge {$ui_coml conf -text {Merge Commit Message:}} \
2179 * {$ui_coml conf -text {Commit Message:}}
2181 text $ui_comm -background white -borderwidth 1 \
2184 -autoseparators true \
2186 -width 75 -height 9 -wrap none \
2188 -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
2189 scrollbar .vpane.lower.commarea.buffer.sby \
2190 -command [list $ui_comm yview]
2191 pack $ui_coml -side top -fill x
2192 pack .vpane.lower.commarea.buffer.sby -side right -fill y
2193 pack $ui_comm -side left -fill y
2194 pack .vpane.lower.commarea.buffer -side left -fill y
2196 # -- Commit Message Buffer Context Menu
2198 menu $ui_comm.ctxm -tearoff 0
2199 $ui_comm.ctxm add command -label "Cut" \
2201 -command "tk_textCut $ui_comm"
2202 $ui_comm.ctxm add command -label "Copy" \
2204 -command "tk_textCopy $ui_comm"
2205 $ui_comm.ctxm add command -label "Paste" \
2207 -command "tk_textPaste $ui_comm"
2208 $ui_comm.ctxm add command -label "Delete" \
2210 -command "$ui_comm delete sel.first sel.last"
2211 $ui_comm.ctxm add separator
2212 $ui_comm.ctxm add command -label "Select All" \
2214 -command "$ui_comm tag add sel 0.0 end"
2215 $ui_comm.ctxm add command -label "Copy All" \
2218 $ui_comm tag add sel 0.0 end
2219 tk_textCopy $ui_comm
2220 $ui_comm tag remove sel 0.0 end
2222 $ui_comm.ctxm add separator
2223 $ui_comm.ctxm add command -label "Sign Off" \
2226 bind_button3 $ui_comm "tk_popup $ui_comm.ctxm %X %Y"
2229 set ui_fname_value {}
2230 set ui_fstatus_value {}
2231 frame .vpane.lower.diff.header -background orange
2232 label .vpane.lower.diff.header.l1 -text {File:} \
2233 -background orange \
2235 label .vpane.lower.diff.header.l2 -textvariable ui_fname_value \
2236 -background orange \
2240 label .vpane.lower.diff.header.l3 -text {Status:} \
2241 -background orange \
2243 label .vpane.lower.diff.header.l4 -textvariable ui_fstatus_value \
2244 -background orange \
2245 -width $max_status_desc \
2249 pack .vpane.lower.diff.header.l1 -side left
2250 pack .vpane.lower.diff.header.l2 -side left -fill x
2251 pack .vpane.lower.diff.header.l4 -side right
2252 pack .vpane.lower.diff.header.l3 -side right
2255 frame .vpane.lower.diff.body
2256 set ui_diff .vpane.lower.diff.body.t
2257 text $ui_diff -background white -borderwidth 0 \
2258 -width 80 -height 15 -wrap none \
2260 -xscrollcommand {.vpane.lower.diff.body.sbx set} \
2261 -yscrollcommand {.vpane.lower.diff.body.sby set} \
2263 scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
2264 -command [list $ui_diff xview]
2265 scrollbar .vpane.lower.diff.body.sby -orient vertical \
2266 -command [list $ui_diff yview]
2267 pack .vpane.lower.diff.body.sbx -side bottom -fill x
2268 pack .vpane.lower.diff.body.sby -side right -fill y
2269 pack $ui_diff -side left -fill both -expand 1
2270 pack .vpane.lower.diff.header -side top -fill x
2271 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
2273 $ui_diff tag conf dm -foreground red
2274 $ui_diff tag conf dp -foreground blue
2275 $ui_diff tag conf di -foreground {#00a000}
2276 $ui_diff tag conf dni -foreground {#a000a0}
2277 $ui_diff tag conf da -font font_diffbold
2278 $ui_diff tag conf bold -font font_diffbold
2280 # -- Diff Body Context Menu
2282 menu $ui_diff.ctxm -tearoff 0
2283 $ui_diff.ctxm add command -label "Copy" \
2285 -command "tk_textCopy $ui_diff"
2286 $ui_diff.ctxm add command -label "Select All" \
2288 -command "$ui_diff tag add sel 0.0 end"
2289 $ui_diff.ctxm add command -label "Copy All" \
2292 $ui_diff tag add sel 0.0 end
2293 tk_textCopy $ui_diff
2294 $ui_diff tag remove sel 0.0 end
2296 $ui_diff.ctxm add separator
2297 $ui_diff.ctxm add command -label "Decrease Font Size" \
2299 -command {incr_font_size font_diff -1}
2300 $ui_diff.ctxm add command -label "Increase Font Size" \
2302 -command {incr_font_size font_diff 1}
2303 $ui_diff.ctxm add command -label {Options...} \
2306 bind_button3 $ui_diff "tk_popup $ui_diff.ctxm %X %Y"
2309 set ui_status_value {Initializing...}
2310 label .status -textvariable ui_status_value \
2316 pack .status -anchor w -side bottom -fill x
2320 set gm $repo_config(gui.geometry)
2321 wm geometry . [lindex $gm 0]
2322 .vpane sash place 0 \
2323 [lindex [.vpane sash coord 0] 0] \
2325 .vpane.files sash place 0 \
2327 [lindex [.vpane.files sash coord 0] 1]
2332 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2333 bind $ui_comm <$M1B-Key-i> {do_include_all;break}
2334 bind $ui_comm <$M1B-Key-I> {do_include_all;break}
2335 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2336 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2337 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2338 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2339 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2340 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2341 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2342 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2344 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2345 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2346 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2347 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2348 bind $ui_diff <$M1B-Key-v> {break}
2349 bind $ui_diff <$M1B-Key-V> {break}
2350 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2351 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2352 bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break}
2353 bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break}
2354 bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break}
2355 bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break}
2357 bind . <Destroy> do_quit
2358 bind all <Key-F5> do_rescan
2359 bind all <$M1B-Key-r> do_rescan
2360 bind all <$M1B-Key-R> do_rescan
2361 bind . <$M1B-Key-s> do_signoff
2362 bind . <$M1B-Key-S> do_signoff
2363 bind . <$M1B-Key-i> do_include_all
2364 bind . <$M1B-Key-I> do_include_all
2365 bind . <$M1B-Key-Return> do_commit
2366 bind all <$M1B-Key-q> do_quit
2367 bind all <$M1B-Key-Q> do_quit
2368 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
2369 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
2370 foreach i [list $ui_index $ui_other] {
2371 bind $i <Button-1> {click %W %x %y 1 %X %Y; break}
2372 bind $i <ButtonRelease-1> {unclick %W %x %y; break}
2373 bind_button3 $i {click %W %x %y 3 %X %Y; break}
2377 set file_lists($ui_index) [list]
2378 set file_lists($ui_other) [list]
2380 wm title . "$appname ([file normalize [file dirname $gitdir]])"
2381 focus -force $ui_comm
2382 if {!$single_commit} {
2384 populate_remote_menu .mbar.fetch From fetch_from
2385 populate_remote_menu .mbar.push To push_to
2386 populate_pull_menu .mbar.pull
2388 after 1 update_status