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 ne $global_config($name)} {
94 if {$value eq $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 eq $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 ne $repo_config($name)} {
111 if {$value eq $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 eq {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 eq {none}} {
199 set index_lock_type $type
200 foreach w $disable_on_lock {
201 uplevel #0 $w disabled
204 } elseif {$index_lock_type eq {begin-update} && $type eq {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 rescan {after} {
238 global HEAD PARENT commit_type
239 global ui_index ui_other ui_status_value ui_comm
240 global rescan_active file_states
243 if {$rescan_active > 0 || ![lock_index read]} return
245 repository_state new_HEAD new_type
246 if {$commit_type eq {amend}
247 && $new_type eq {normal}
248 && $new_HEAD eq $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]] eq {}} {
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) eq {true}} {
268 rescan_stage2 {} $after
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 rescan_stage2 $fd_rf $after]
284 proc rescan_stage2 {fd after} {
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 $after]
317 fileevent $fd_df readable [list read_diff_files $fd_df $after]
318 fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
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 after} {
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 rescan_done $fd buf_rdi $after
367 proc read_diff_files {fd after} {
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 rescan_done $fd buf_rdf $after
396 proc read_ls_others {fd after} {
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 rescan_done $fd buf_rlo $after
408 proc rescan_done {fd buf after} {
410 global file_states repo_config
413 if {![eof $fd]} return
416 if {[incr rescan_active -1] > 0} return
422 if {$repo_config(gui.partialinclude) ne {true}} {
424 foreach path [array names file_states] {
425 switch -- [lindex $file_states($path) 0] {
427 MM {lappend pathList $path}
430 if {$pathList ne {}} {
431 update_index $pathList
440 proc prune_selection {} {
441 global file_states selected_paths
443 foreach path [array names selected_paths] {
444 if {[catch {set still_here $file_states($path)}]} {
445 unset selected_paths($path)
450 ######################################################################
455 global ui_diff ui_fname_value ui_fstatus_value ui_index ui_other
457 $ui_diff conf -state normal
458 $ui_diff delete 0.0 end
459 $ui_diff conf -state disabled
461 set ui_fname_value {}
462 set ui_fstatus_value {}
464 $ui_index tag remove in_diff 0.0 end
465 $ui_other tag remove in_diff 0.0 end
468 proc reshow_diff {} {
469 global ui_fname_value ui_status_value file_states
471 if {$ui_fname_value eq {}
472 || [catch {set s $file_states($ui_fname_value)}]} {
475 show_diff $ui_fname_value
479 proc handle_empty_diff {} {
480 global ui_fname_value file_states file_lists
482 set path $ui_fname_value
483 set s $file_states($path)
484 if {[lindex $s 0] ne {_M}} return
486 info_popup "No differences detected.
488 [short_path $path] has no changes.
490 The modification date of this file was updated
491 by another application and you currently have
492 the Trust File Modification Timestamps option
493 enabled, so Git did not automatically detect
494 that there are no content differences in this
497 This file will now be removed from the modified
498 files list, to prevent possible confusion.
500 if {[catch {exec git update-index -- $path} err]} {
501 error_popup "Failed to refresh index:\n\n$err"
505 set old_w [mapcol [lindex $file_states($path) 0] $path]
506 set lno [lsearch -sorted $file_lists($old_w) $path]
508 set file_lists($old_w) \
509 [lreplace $file_lists($old_w) $lno $lno]
511 $old_w conf -state normal
512 $old_w delete $lno.0 [expr {$lno + 1}].0
513 $old_w conf -state disabled
517 proc show_diff {path {w {}} {lno {}}} {
518 global file_states file_lists
519 global PARENT diff_3way diff_active repo_config
520 global ui_diff ui_fname_value ui_fstatus_value ui_status_value
522 if {$diff_active || ![lock_index read]} return
525 if {$w eq {} || $lno == {}} {
526 foreach w [array names file_lists] {
527 set lno [lsearch -sorted $file_lists($w) $path]
534 if {$w ne {} && $lno >= 1} {
535 $w tag add in_diff $lno.0 [expr {$lno + 1}].0
538 set s $file_states($path)
542 set ui_fname_value $path
543 set ui_fstatus_value [mapdesc $m $path]
544 set ui_status_value "Loading diff of [escape_path $path]..."
546 set cmd [list | git diff-index]
547 lappend cmd --no-color
548 if {$repo_config(gui.diffcontext) > 0} {
549 lappend cmd "-U$repo_config(gui.diffcontext)"
559 set fd [open $path r]
560 set content [read $fd]
565 set ui_status_value "Unable to display [escape_path $path]"
566 error_popup "Error loading file:\n\n$err"
569 $ui_diff conf -state normal
570 $ui_diff insert end $content
571 $ui_diff conf -state disabled
574 set ui_status_value {Ready.}
583 if {[catch {set fd [open $cmd r]} err]} {
586 set ui_status_value "Unable to display [escape_path $path]"
587 error_popup "Error loading diff:\n\n$err"
591 fconfigure $fd -blocking 0 -translation auto
592 fileevent $fd readable [list read_diff $fd]
595 proc read_diff {fd} {
596 global ui_diff ui_status_value diff_3way diff_active
599 while {[gets $fd line] >= 0} {
600 if {[string match {diff --git *} $line]} continue
601 if {[string match {diff --combined *} $line]} continue
602 if {[string match {--- *} $line]} continue
603 if {[string match {+++ *} $line]} continue
604 if {[string match index* $line]} {
605 if {[string first , $line] >= 0} {
610 $ui_diff conf -state normal
612 set x [string index $line 0]
617 default {set tags {}}
620 set x [string range $line 0 1]
622 default {set tags {}}
624 "++" {set tags dp; set x " +"}
625 " +" {set tags {di bold}; set x "++"}
626 "+ " {set tags dni; set x "-+"}
627 "--" {set tags dm; set x " -"}
628 " -" {set tags {dm bold}; set x "--"}
629 "- " {set tags di; set x "+-"}
630 default {set tags {}}
632 set line [string replace $line 0 1 $x]
634 $ui_diff insert end $line $tags
635 $ui_diff insert end "\n"
636 $ui_diff conf -state disabled
643 set ui_status_value {Ready.}
645 if {$repo_config(gui.trustmtime) eq {true}
646 && [$ui_diff index end] eq {2.0}} {
652 ######################################################################
656 proc load_last_commit {} {
657 global HEAD PARENT commit_type ui_comm
659 if {$commit_type eq {amend}} return
660 if {$commit_type ne {normal}} {
661 error_popup "Can't amend a $commit_type commit."
669 set fd [open "| git cat-file commit $HEAD" r]
670 while {[gets $fd line] > 0} {
671 if {[string match {parent *} $line]} {
672 set parent [string range $line 7 end]
676 set msg [string trim [read $fd]]
679 error_popup "Error loading commit data for amend:\n\n$err"
683 if {$parent_count == 0} {
684 set commit_type amend
687 rescan {set ui_status_value {Ready.}}
688 } elseif {$parent_count == 1} {
689 set commit_type amend
691 $ui_comm delete 0.0 end
692 $ui_comm insert end $msg
693 $ui_comm edit modified false
695 rescan {set ui_status_value {Ready.}}
697 error_popup {You can't amend a merge commit.}
702 proc commit_tree {} {
703 global tcl_platform HEAD gitdir commit_type file_states
705 global ui_status_value ui_comm
707 if {![lock_index update]} return
709 # -- Our in memory state should match the repository.
711 repository_state curHEAD cur_type
712 if {$commit_type eq {amend}
713 && $cur_type eq {normal}
714 && $curHEAD eq $HEAD} {
715 } elseif {$commit_type ne $cur_type || $HEAD ne $curHEAD} {
716 error_popup {Last scanned state does not match repository state.
718 Its highly likely that another Git program modified the
719 repository since our last scan. A rescan is required
723 rescan {set ui_status_value {Ready.}}
727 # -- At least one file should differ in the index.
730 foreach path [array names file_states] {
731 set s $file_states($path)
732 switch -glob -- [lindex $s 0] {
736 M? {set files_ready 1; break}
738 error_popup "Unmerged files cannot be committed.
740 File [short_path $path] has merge conflicts.
741 You must resolve them and include the file before committing.
747 error_popup "Unknown file state [lindex $s 0] detected.
749 File [short_path $path] cannot be committed by this program.
755 error_popup {No included files to commit.
757 You must include at least 1 file before you can commit.
763 # -- A message is required.
765 set msg [string trim [$ui_comm get 1.0 end]]
767 error_popup {Please supply a commit message.
769 A good commit message has the following format:
771 - First line: Describe in one sentance what you did.
773 - Remaining lines: Describe why this change is good.
779 # -- Ask the pre-commit hook for the go-ahead.
781 set pchook [file join $gitdir hooks pre-commit]
782 if {$tcl_platform(platform) eq {windows} && [file isfile $pchook]} {
783 set pchook [list sh -c [concat \
784 "if test -x \"$pchook\";" \
785 "then exec \"$pchook\" 2>&1;" \
787 } elseif {[file executable $pchook]} {
788 set pchook [list $pchook |& cat]
793 set ui_status_value {Calling pre-commit hook...}
795 set fd_ph [open "| $pchook" r]
796 fconfigure $fd_ph -blocking 0 -translation binary
797 fileevent $fd_ph readable \
798 [list commit_stage1 $fd_ph $curHEAD $msg]
800 commit_stage2 $curHEAD $msg
804 proc commit_stage1 {fd_ph curHEAD msg} {
805 global pch_error ui_status_value
807 append pch_error [read $fd_ph]
808 fconfigure $fd_ph -blocking 1
810 if {[catch {close $fd_ph}]} {
811 set ui_status_value {Commit declined by pre-commit hook.}
812 hook_failed_popup pre-commit $pch_error
815 commit_stage2 $curHEAD $msg
819 fconfigure $fd_ph -blocking 0
823 proc commit_stage2 {curHEAD msg} {
824 global ui_status_value
826 # -- Write the tree in the background.
828 set ui_status_value {Committing changes...}
829 set fd_wt [open "| git write-tree" r]
830 fileevent $fd_wt readable [list commit_stage3 $fd_wt $curHEAD $msg]
833 proc commit_stage3 {fd_wt curHEAD msg} {
834 global single_commit gitdir HEAD PARENT commit_type tcl_platform
835 global ui_status_value ui_comm
836 global file_states selected_paths
839 if {$tree_id eq {} || [catch {close $fd_wt} err]} {
840 error_popup "write-tree failed:\n\n$err"
841 set ui_status_value {Commit failed.}
846 # -- Create the commit.
848 set cmd [list git commit-tree $tree_id]
850 lappend cmd -p $PARENT
852 if {$commit_type eq {merge}} {
854 set fd_mh [open [file join $gitdir MERGE_HEAD] r]
855 while {[gets $fd_mh merge_head] >= 0} {
856 lappend cmd -p $merge_head
860 error_popup "Loading MERGE_HEAD failed:\n\n$err"
861 set ui_status_value {Commit failed.}
867 # git commit-tree writes to stderr during initial commit.
868 lappend cmd 2>/dev/null
871 if {[catch {set cmt_id [eval exec $cmd]} err]} {
872 error_popup "commit-tree failed:\n\n$err"
873 set ui_status_value {Commit failed.}
878 # -- Update the HEAD ref.
881 if {$commit_type ne {normal}} {
882 append reflogm " ($commit_type)"
884 set i [string first "\n" $msg]
886 append reflogm {: } [string range $msg 0 [expr {$i - 1}]]
888 append reflogm {: } $msg
890 set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD]
891 if {[catch {eval exec $cmd} err]} {
892 error_popup "update-ref failed:\n\n$err"
893 set ui_status_value {Commit failed.}
898 # -- Cleanup after ourselves.
900 catch {file delete [file join $gitdir MERGE_HEAD]}
901 catch {file delete [file join $gitdir MERGE_MSG]}
902 catch {file delete [file join $gitdir SQUASH_MSG]}
903 catch {file delete [file join $gitdir GITGUI_MSG]}
905 # -- Let rerere do its thing.
907 if {[file isdirectory [file join $gitdir rr-cache]]} {
908 catch {exec git rerere}
911 # -- Run the post-commit hook.
913 set pchook [file join $gitdir hooks post-commit]
914 if {$tcl_platform(platform) eq {windows} && [file isfile $pchook]} {
915 set pchook [list sh -c [concat \
916 "if test -x \"$pchook\";" \
917 "then exec \"$pchook\";" \
919 } elseif {![file executable $pchook]} {
923 catch {exec $pchook &}
926 $ui_comm delete 0.0 end
927 $ui_comm edit modified false
930 if {$single_commit} do_quit
932 # -- Update status without invoking any git commands.
934 set commit_type normal
938 foreach path [array names file_states] {
939 set s $file_states($path)
944 D? {set m _[string index $m 1]}
948 unset file_states($path)
949 catch {unset selected_paths($path)}
951 lset file_states($path) 0 $m
958 set ui_status_value \
959 "Changes committed as [string range $cmt_id 0 7]."
962 ######################################################################
966 proc fetch_from {remote} {
967 set w [new_console "fetch $remote" \
968 "Fetching new changes from $remote"]
969 set cmd [list git fetch]
974 proc pull_remote {remote branch} {
975 global HEAD commit_type file_states repo_config
977 if {![lock_index update]} return
979 # -- Our in memory state should match the repository.
981 repository_state curHEAD cur_type
982 if {$commit_type ne $cur_type || $HEAD ne $curHEAD} {
983 error_popup {Last scanned state does not match repository state.
985 Its highly likely that another Git program modified the
986 repository since our last scan. A rescan is required
987 before a pull can be started.
990 rescan {set ui_status_value {Ready.}}
994 # -- No differences should exist before a pull.
996 if {[array size file_states] != 0} {
997 error_popup {Uncommitted but modified files are present.
999 You should not perform a pull with unmodified files in your working
1000 directory as Git would be unable to recover from an incorrect merge.
1002 Commit or throw away all changes before starting a pull operation.
1008 set w [new_console "pull $remote $branch" \
1009 "Pulling new changes from branch $branch in $remote"]
1010 set cmd [list git pull]
1011 if {$repo_config(gui.pullsummary) eq {false}} {
1012 lappend cmd --no-summary
1016 console_exec $w $cmd [list post_pull_remote $remote $branch]
1019 proc post_pull_remote {remote branch success} {
1020 global HEAD PARENT commit_type
1021 global ui_status_value
1025 repository_state HEAD commit_type
1027 set $ui_status_value "Pulling $branch from $remote complete."
1029 set m "Conflicts detected while pulling $branch from $remote."
1030 rescan "set ui_status_value {$m}"
1034 proc push_to {remote} {
1035 set w [new_console "push $remote" \
1036 "Pushing changes to $remote"]
1037 set cmd [list git push]
1039 console_exec $w $cmd
1042 ######################################################################
1046 proc mapcol {state path} {
1047 global all_cols ui_other
1049 if {[catch {set r $all_cols($state)}]} {
1050 puts "error: no column for state={$state} $path"
1056 proc mapicon {state path} {
1059 if {[catch {set r $all_icons($state)}]} {
1060 puts "error: no icon for state={$state} $path"
1066 proc mapdesc {state path} {
1069 if {[catch {set r $all_descs($state)}]} {
1070 puts "error: no desc for state={$state} $path"
1076 proc escape_path {path} {
1077 regsub -all "\n" $path "\\n" path
1081 proc short_path {path} {
1082 return [escape_path [lindex [file split $path] end]]
1087 proc merge_state {path new_state} {
1088 global file_states next_icon_id
1090 set s0 [string index $new_state 0]
1091 set s1 [string index $new_state 1]
1093 if {[catch {set info $file_states($path)}]} {
1095 set icon n[incr next_icon_id]
1097 set state [lindex $info 0]
1098 set icon [lindex $info 1]
1102 set s0 [string index $state 0]
1103 } elseif {$s0 eq {*}} {
1108 set s1 [string index $state 1]
1109 } elseif {$s1 eq {*}} {
1113 set file_states($path) [list $s0$s1 $icon]
1117 proc display_file {path state} {
1118 global file_states file_lists selected_paths rescan_active
1120 set old_m [merge_state $path $state]
1121 if {$rescan_active > 0} return
1123 set s $file_states($path)
1124 set new_m [lindex $s 0]
1125 set new_w [mapcol $new_m $path]
1126 set old_w [mapcol $old_m $path]
1127 set new_icon [mapicon $new_m $path]
1129 if {$new_w ne $old_w} {
1130 set lno [lsearch -sorted $file_lists($old_w) $path]
1133 $old_w conf -state normal
1134 $old_w delete $lno.0 [expr {$lno + 1}].0
1135 $old_w conf -state disabled
1138 lappend file_lists($new_w) $path
1139 set file_lists($new_w) [lsort $file_lists($new_w)]
1140 set lno [lsearch -sorted $file_lists($new_w) $path]
1142 $new_w conf -state normal
1143 $new_w image create $lno.0 \
1144 -align center -padx 5 -pady 1 \
1145 -name [lindex $s 1] \
1147 $new_w insert $lno.1 "[escape_path $path]\n"
1148 if {[catch {set in_sel $selected_paths($path)}]} {
1152 $new_w tag add in_sel $lno.0 [expr {$lno + 1}].0
1154 $new_w conf -state disabled
1155 } elseif {$new_icon ne [mapicon $old_m $path]} {
1156 $new_w conf -state normal
1157 $new_w image conf [lindex $s 1] -image $new_icon
1158 $new_w conf -state disabled
1162 proc display_all_files {} {
1163 global ui_index ui_other
1164 global file_states file_lists
1165 global last_clicked selected_paths
1167 $ui_index conf -state normal
1168 $ui_other conf -state normal
1170 $ui_index delete 0.0 end
1171 $ui_other delete 0.0 end
1174 set file_lists($ui_index) [list]
1175 set file_lists($ui_other) [list]
1177 foreach path [lsort [array names file_states]] {
1178 set s $file_states($path)
1180 set w [mapcol $m $path]
1181 lappend file_lists($w) $path
1182 set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
1183 $w image create end \
1184 -align center -padx 5 -pady 1 \
1185 -name [lindex $s 1] \
1186 -image [mapicon $m $path]
1187 $w insert end "[escape_path $path]\n"
1188 if {[catch {set in_sel $selected_paths($path)}]} {
1192 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
1196 $ui_index conf -state disabled
1197 $ui_other conf -state disabled
1200 proc update_index {pathList} {
1201 global update_index_cp update_index_rsd ui_status_value
1203 if {![lock_index update]} return
1205 set update_index_cp 0
1206 set update_index_rsd 0
1207 set pathList [lsort $pathList]
1208 set totalCnt [llength $pathList]
1209 set batch [expr {int($totalCnt * .01) + 1}]
1210 if {$batch > 25} {set batch 25}
1212 set ui_status_value [format \
1213 "Including files ... %i/%i files (%.2f%%)" \
1217 set fd [open "| git update-index --add --remove -z --stdin" w]
1223 fileevent $fd writable [list \
1224 write_update_index \
1232 proc write_update_index {fd pathList totalCnt batch} {
1233 global update_index_cp update_index_rsd ui_status_value
1234 global file_states ui_fname_value
1236 if {$update_index_cp >= $totalCnt} {
1239 set ui_status_value {Ready.}
1240 if {$update_index_rsd} {
1246 for {set i $batch} \
1247 {$update_index_cp < $totalCnt && $i > 0} \
1249 set path [lindex $pathList $update_index_cp]
1250 incr update_index_cp
1252 switch -- [lindex $file_states($path) 0] {
1262 puts -nonewline $fd $path
1263 puts -nonewline $fd "\0"
1264 display_file $path $new
1265 if {$ui_fname_value eq $path} {
1266 set update_index_rsd 1
1270 set ui_status_value [format \
1271 "Including files ... %i/%i files (%.2f%%)" \
1274 [expr {100.0 * $update_index_cp / $totalCnt}]]
1277 ######################################################################
1279 ## remote management
1281 proc load_all_remotes {} {
1282 global gitdir all_remotes repo_config
1284 set all_remotes [list]
1285 set rm_dir [file join $gitdir remotes]
1286 if {[file isdirectory $rm_dir]} {
1287 set all_remotes [concat $all_remotes [glob \
1291 -directory $rm_dir *]]
1294 foreach line [array names repo_config remote.*.url] {
1295 if {[regexp ^remote\.(.*)\.url\$ $line line name]} {
1296 lappend all_remotes $name
1300 set all_remotes [lsort -unique $all_remotes]
1303 proc populate_remote_menu {m pfx op} {
1306 foreach remote $all_remotes {
1307 $m add command -label "$pfx $remote..." \
1308 -command [list $op $remote] \
1313 proc populate_pull_menu {m} {
1314 global gitdir repo_config all_remotes disable_on_lock
1316 foreach remote $all_remotes {
1318 if {[array get repo_config remote.$remote.url] ne {}} {
1319 if {[array get repo_config remote.$remote.fetch] ne {}} {
1320 regexp {^([^:]+):} \
1321 [lindex $repo_config(remote.$remote.fetch) 0] \
1326 set fd [open [file join $gitdir remotes $remote] r]
1327 while {[gets $fd line] >= 0} {
1328 if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} {
1337 regsub ^refs/heads/ $rb {} rb_short
1338 if {$rb_short ne {}} {
1340 -label "Branch $rb_short from $remote..." \
1341 -command [list pull_remote $remote $rb] \
1343 lappend disable_on_lock \
1344 [list $m entryconf [$m index last] -state]
1349 ######################################################################
1354 #define mask_width 14
1355 #define mask_height 15
1356 static unsigned char mask_bits[] = {
1357 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1358 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1359 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1362 image create bitmap file_plain -background white -foreground black -data {
1363 #define plain_width 14
1364 #define plain_height 15
1365 static unsigned char plain_bits[] = {
1366 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1367 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1368 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1369 } -maskdata $filemask
1371 image create bitmap file_mod -background white -foreground blue -data {
1372 #define mod_width 14
1373 #define mod_height 15
1374 static unsigned char mod_bits[] = {
1375 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1376 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1377 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1378 } -maskdata $filemask
1380 image create bitmap file_fulltick -background white -foreground "#007000" -data {
1381 #define file_fulltick_width 14
1382 #define file_fulltick_height 15
1383 static unsigned char file_fulltick_bits[] = {
1384 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1385 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1386 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1387 } -maskdata $filemask
1389 image create bitmap file_parttick -background white -foreground "#005050" -data {
1390 #define parttick_width 14
1391 #define parttick_height 15
1392 static unsigned char parttick_bits[] = {
1393 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1394 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1395 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1396 } -maskdata $filemask
1398 image create bitmap file_question -background white -foreground black -data {
1399 #define file_question_width 14
1400 #define file_question_height 15
1401 static unsigned char file_question_bits[] = {
1402 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1403 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1404 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1405 } -maskdata $filemask
1407 image create bitmap file_removed -background white -foreground red -data {
1408 #define file_removed_width 14
1409 #define file_removed_height 15
1410 static unsigned char file_removed_bits[] = {
1411 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1412 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1413 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1414 } -maskdata $filemask
1416 image create bitmap file_merge -background white -foreground blue -data {
1417 #define file_merge_width 14
1418 #define file_merge_height 15
1419 static unsigned char file_merge_bits[] = {
1420 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1421 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1422 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1423 } -maskdata $filemask
1425 set ui_index .vpane.files.index.list
1426 set ui_other .vpane.files.other.list
1427 set max_status_desc 0
1429 {__ i plain "Unmodified"}
1430 {_M i mod "Modified"}
1431 {M_ i fulltick "Included in commit"}
1432 {MM i parttick "Partially included"}
1434 {_O o plain "Untracked"}
1435 {A_ o fulltick "Added by commit"}
1436 {AM o parttick "Partially added"}
1437 {AD o question "Added (but now gone)"}
1439 {_D i question "Missing"}
1440 {D_ i removed "Removed by commit"}
1441 {DD i removed "Removed by commit"}
1442 {DO i removed "Removed (still exists)"}
1444 {UM i merge "Merge conflicts"}
1445 {U_ i merge "Merge conflicts"}
1447 if {$max_status_desc < [string length [lindex $i 3]]} {
1448 set max_status_desc [string length [lindex $i 3]]
1450 if {[lindex $i 1] eq {i}} {
1451 set all_cols([lindex $i 0]) $ui_index
1453 set all_cols([lindex $i 0]) $ui_other
1455 set all_icons([lindex $i 0]) file_[lindex $i 2]
1456 set all_descs([lindex $i 0]) [lindex $i 3]
1460 ######################################################################
1465 global tcl_platform tk_library
1466 if {$tcl_platform(platform) eq {unix}
1467 && $tcl_platform(os) eq {Darwin}
1468 && [string match /Library/Frameworks/* $tk_library]} {
1474 proc bind_button3 {w cmd} {
1475 bind $w <Any-Button-3> $cmd
1477 bind $w <Control-Button-1> $cmd
1481 proc incr_font_size {font {amt 1}} {
1482 set sz [font configure $font -size]
1484 font configure $font -size $sz
1485 font configure ${font}bold -size $sz
1488 proc hook_failed_popup {hook msg} {
1489 global gitdir appname
1495 label $w.m.l1 -text "$hook hook failed:" \
1500 -background white -borderwidth 1 \
1502 -width 80 -height 10 \
1504 -yscrollcommand [list $w.m.sby set]
1506 -text {You must correct the above errors before committing.} \
1510 scrollbar $w.m.sby -command [list $w.m.t yview]
1511 pack $w.m.l1 -side top -fill x
1512 pack $w.m.l2 -side bottom -fill x
1513 pack $w.m.sby -side right -fill y
1514 pack $w.m.t -side left -fill both -expand 1
1515 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1517 $w.m.t insert 1.0 $msg
1518 $w.m.t conf -state disabled
1520 button $w.ok -text OK \
1523 -command "destroy $w"
1524 pack $w.ok -side bottom -anchor e -pady 10 -padx 10
1526 bind $w <Visibility> "grab $w; focus $w"
1527 bind $w <Key-Return> "destroy $w"
1528 wm title $w "$appname ([lindex [file split \
1529 [file normalize [file dirname $gitdir]]] \
1534 set next_console_id 0
1536 proc new_console {short_title long_title} {
1537 global next_console_id console_data
1538 set w .console[incr next_console_id]
1539 set console_data($w) [list $short_title $long_title]
1540 return [console_init $w]
1543 proc console_init {w} {
1544 global console_cr console_data
1545 global gitdir appname M1B
1547 set console_cr($w) 1.0
1550 label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
1555 -background white -borderwidth 1 \
1557 -width 80 -height 10 \
1560 -yscrollcommand [list $w.m.sby set]
1561 label $w.m.s -text {Working... please wait...} \
1565 scrollbar $w.m.sby -command [list $w.m.t yview]
1566 pack $w.m.l1 -side top -fill x
1567 pack $w.m.s -side bottom -fill x
1568 pack $w.m.sby -side right -fill y
1569 pack $w.m.t -side left -fill both -expand 1
1570 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1572 menu $w.ctxm -tearoff 0
1573 $w.ctxm add command -label "Copy" \
1575 -command "tk_textCopy $w.m.t"
1576 $w.ctxm add command -label "Select All" \
1578 -command "$w.m.t tag add sel 0.0 end"
1579 $w.ctxm add command -label "Copy All" \
1582 $w.m.t tag add sel 0.0 end
1584 $w.m.t tag remove sel 0.0 end
1587 button $w.ok -text {Close} \
1590 -command "destroy $w"
1591 pack $w.ok -side bottom -anchor e -pady 10 -padx 10
1593 bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y"
1594 bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break"
1595 bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break"
1596 bind $w <Visibility> "focus $w"
1597 wm title $w "$appname ([lindex [file split \
1598 [file normalize [file dirname $gitdir]]] \
1599 end]): [lindex $console_data($w) 0]"
1603 proc console_exec {w cmd {after {}}} {
1606 # -- Windows tosses the enviroment when we exec our child.
1607 # But most users need that so we have to relogin. :-(
1609 if {$tcl_platform(platform) eq {windows}} {
1610 set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
1613 # -- Tcl won't let us redirect both stdout and stderr to
1614 # the same pipe. So pass it through cat...
1616 set cmd [concat | $cmd |& cat]
1618 set fd_f [open $cmd r]
1619 fconfigure $fd_f -blocking 0 -translation binary
1620 fileevent $fd_f readable [list console_read $w $fd_f $after]
1623 proc console_read {w fd after} {
1624 global console_cr console_data
1628 if {![winfo exists $w]} {console_init $w}
1629 $w.m.t conf -state normal
1631 set n [string length $buf]
1633 set cr [string first "\r" $buf $c]
1634 set lf [string first "\n" $buf $c]
1635 if {$cr < 0} {set cr [expr {$n + 1}]}
1636 if {$lf < 0} {set lf [expr {$n + 1}]}
1639 $w.m.t insert end [string range $buf $c $lf]
1640 set console_cr($w) [$w.m.t index {end -1c}]
1644 $w.m.t delete $console_cr($w) end
1645 $w.m.t insert end "\n"
1646 $w.m.t insert end [string range $buf $c $cr]
1651 $w.m.t conf -state disabled
1655 fconfigure $fd -blocking 1
1657 if {[catch {close $fd}]} {
1658 if {![winfo exists $w]} {console_init $w}
1659 $w.m.s conf -background red -text {Error: Command Failed}
1660 $w.ok conf -state normal
1662 } elseif {[winfo exists $w]} {
1663 $w.m.s conf -background green -text {Success}
1664 $w.ok conf -state normal
1667 array unset console_cr $w
1668 array unset console_data $w
1670 uplevel #0 $after $ok
1674 fconfigure $fd -blocking 0
1677 ######################################################################
1681 set starting_gitk_msg {Please wait... Starting gitk...}
1684 global tcl_platform ui_status_value starting_gitk_msg
1686 set ui_status_value $starting_gitk_msg
1688 if {$ui_status_value eq $starting_gitk_msg} {
1689 set ui_status_value {Ready.}
1693 if {$tcl_platform(platform) eq {windows}} {
1701 set w [new_console "repack" "Repacking the object database"]
1702 set cmd [list git repack]
1705 console_exec $w $cmd
1711 global gitdir ui_comm is_quitting repo_config
1713 if {$is_quitting} return
1716 # -- Stash our current commit buffer.
1718 set save [file join $gitdir GITGUI_MSG]
1719 set msg [string trim [$ui_comm get 0.0 end]]
1720 if {[$ui_comm edit modified] && $msg ne {}} {
1722 set fd [open $save w]
1723 puts $fd [string trim [$ui_comm get 0.0 end]]
1726 } elseif {$msg eq {} && [file exists $save]} {
1730 # -- Stash our current window geometry into this repository.
1732 set cfg_geometry [list]
1733 lappend cfg_geometry [wm geometry .]
1734 lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
1735 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
1736 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1739 if {$cfg_geometry ne $rc_geometry} {
1740 catch {exec git repo-config gui.geometry $cfg_geometry}
1747 rescan {set ui_status_value {Ready.}}
1750 proc do_include_all {} {
1753 if {![lock_index begin-update]} return
1756 foreach path [array names file_states] {
1757 set s $file_states($path)
1763 _D {lappend pathList $path}
1766 if {$pathList eq {}} {
1769 update_index $pathList
1773 set GIT_COMMITTER_IDENT {}
1775 proc do_signoff {} {
1776 global ui_comm GIT_COMMITTER_IDENT
1778 if {$GIT_COMMITTER_IDENT eq {}} {
1779 if {[catch {set me [exec git var GIT_COMMITTER_IDENT]} err]} {
1780 error_popup "Unable to obtain your identity:\n\n$err"
1783 if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
1784 $me me GIT_COMMITTER_IDENT]} {
1785 error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me"
1790 set sob "Signed-off-by: $GIT_COMMITTER_IDENT"
1791 set last [$ui_comm get {end -1c linestart} {end -1c}]
1792 if {$last ne $sob} {
1793 $ui_comm edit separator
1795 && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
1796 $ui_comm insert end "\n"
1798 $ui_comm insert end "\n$sob"
1799 $ui_comm edit separator
1804 proc do_amend_last {} {
1812 proc do_options {} {
1813 global appname gitdir font_descs
1814 global repo_config global_config
1815 global repo_config_new global_config_new
1817 array unset repo_config_new
1818 array unset global_config_new
1819 foreach name [array names repo_config] {
1820 set repo_config_new($name) $repo_config($name)
1823 foreach name [array names repo_config] {
1825 gui.diffcontext {continue}
1827 set repo_config_new($name) $repo_config($name)
1829 foreach name [array names global_config] {
1830 set global_config_new($name) $global_config($name)
1832 set reponame [lindex [file split \
1833 [file normalize [file dirname $gitdir]]] \
1836 set w .options_editor
1838 wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
1840 label $w.header -text "$appname Options" \
1842 pack $w.header -side top -fill x
1845 button $w.buttons.restore -text {Restore Defaults} \
1847 -command do_restore_defaults
1848 pack $w.buttons.restore -side left
1849 button $w.buttons.save -text Save \
1851 -command [list do_save_config $w]
1852 pack $w.buttons.save -side right
1853 button $w.buttons.cancel -text {Cancel} \
1855 -command [list destroy $w]
1856 pack $w.buttons.cancel -side right
1857 pack $w.buttons -side bottom -fill x -pady 10 -padx 10
1859 labelframe $w.repo -text "$reponame Repository" \
1861 -relief raised -borderwidth 2
1862 labelframe $w.global -text {Global (All Repositories)} \
1864 -relief raised -borderwidth 2
1865 pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5
1866 pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5
1869 {b partialinclude {Allow Partially Included Files}}
1870 {b pullsummary {Show Pull Summary}}
1871 {b trustmtime {Trust File Modification Timestamps}}
1872 {i diffcontext {Number of Diff Context Lines}}
1874 set type [lindex $option 0]
1875 set name [lindex $option 1]
1876 set text [lindex $option 2]
1877 foreach f {repo global} {
1880 checkbutton $w.$f.$name -text $text \
1881 -variable ${f}_config_new(gui.$name) \
1885 pack $w.$f.$name -side top -anchor w
1889 label $w.$f.$name.l -text "$text:" -font font_ui
1890 pack $w.$f.$name.l -side left -anchor w -fill x
1891 spinbox $w.$f.$name.v \
1892 -textvariable ${f}_config_new(gui.$name) \
1893 -from 1 -to 99 -increment 1 \
1896 pack $w.$f.$name.v -side right -anchor e
1897 pack $w.$f.$name -side top -anchor w -fill x
1903 set all_fonts [lsort [font families]]
1904 foreach option $font_descs {
1905 set name [lindex $option 0]
1906 set font [lindex $option 1]
1907 set text [lindex $option 2]
1909 set global_config_new(gui.$font^^family) \
1910 [font configure $font -family]
1911 set global_config_new(gui.$font^^size) \
1912 [font configure $font -size]
1914 frame $w.global.$name
1915 label $w.global.$name.l -text "$text:" -font font_ui
1916 pack $w.global.$name.l -side left -anchor w -fill x
1917 eval tk_optionMenu $w.global.$name.family \
1918 global_config_new(gui.$font^^family) \
1920 spinbox $w.global.$name.size \
1921 -textvariable global_config_new(gui.$font^^size) \
1922 -from 2 -to 80 -increment 1 \
1925 pack $w.global.$name.size -side right -anchor e
1926 pack $w.global.$name.family -side right -anchor e
1927 pack $w.global.$name -side top -anchor w -fill x
1930 bind $w <Visibility> "grab $w; focus $w"
1931 bind $w <Key-Escape> "destroy $w"
1932 wm title $w "$appname ($reponame): Options"
1936 proc do_restore_defaults {} {
1937 global font_descs default_config repo_config
1938 global repo_config_new global_config_new
1940 foreach name [array names default_config] {
1941 set repo_config_new($name) $default_config($name)
1942 set global_config_new($name) $default_config($name)
1945 foreach option $font_descs {
1946 set name [lindex $option 0]
1947 set repo_config(gui.$name) $default_config(gui.$name)
1951 foreach option $font_descs {
1952 set name [lindex $option 0]
1953 set font [lindex $option 1]
1954 set global_config_new(gui.$font^^family) \
1955 [font configure $font -family]
1956 set global_config_new(gui.$font^^size) \
1957 [font configure $font -size]
1961 proc do_save_config {w} {
1962 if {[catch {save_config} err]} {
1963 error_popup "Failed to completely save options:\n\n$err"
1969 proc toggle_or_diff {w x y} {
1970 global file_lists ui_index ui_other
1971 global last_clicked selected_paths
1973 set pos [split [$w index @$x,$y] .]
1974 set lno [lindex $pos 0]
1975 set col [lindex $pos 1]
1976 set path [lindex $file_lists($w) [expr {$lno - 1}]]
1982 set last_clicked [list $w $lno]
1983 array unset selected_paths
1984 $ui_index tag remove in_sel 0.0 end
1985 $ui_other tag remove in_sel 0.0 end
1988 update_index [list $path]
1990 show_diff $path $w $lno
1994 proc add_one_to_selection {w x y} {
1996 global last_clicked selected_paths
1998 set pos [split [$w index @$x,$y] .]
1999 set lno [lindex $pos 0]
2000 set col [lindex $pos 1]
2001 set path [lindex $file_lists($w) [expr {$lno - 1}]]
2007 set last_clicked [list $w $lno]
2008 if {[catch {set in_sel $selected_paths($path)}]} {
2012 unset selected_paths($path)
2013 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
2015 set selected_paths($path) 1
2016 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
2020 proc add_range_to_selection {w x y} {
2022 global last_clicked selected_paths
2024 if {[lindex $last_clicked 0] ne $w} {
2025 toggle_or_diff $w $x $y
2029 set pos [split [$w index @$x,$y] .]
2030 set lno [lindex $pos 0]
2031 set lc [lindex $last_clicked 1]
2040 foreach path [lrange $file_lists($w) \
2041 [expr {$begin - 1}] \
2042 [expr {$end - 1}]] {
2043 set selected_paths($path) 1
2045 $w tag add in_sel $begin.0 [expr {$end + 1}].0
2048 ######################################################################
2052 set cursor_ptr arrow
2053 font create font_diff -family Courier -size 10
2057 eval font configure font_ui [font actual [.dummy cget -font]]
2061 font create font_uibold
2062 font create font_diffbold
2066 if {$tcl_platform(platform) eq {windows}} {
2069 } elseif {[is_MacOSX]} {
2074 proc apply_config {} {
2075 global repo_config font_descs
2077 foreach option $font_descs {
2078 set name [lindex $option 0]
2079 set font [lindex $option 1]
2081 foreach {cn cv} $repo_config(gui.$name) {
2082 font configure $font $cn $cv
2085 error_popup "Invalid font specified in gui.$name:\n\n$err"
2087 foreach {cn cv} [font configure $font] {
2088 font configure ${font}bold $cn $cv
2090 font configure ${font}bold -weight bold
2094 set default_config(gui.trustmtime) false
2095 set default_config(gui.pullsummary) true
2096 set default_config(gui.partialinclude) false
2097 set default_config(gui.diffcontext) 5
2098 set default_config(gui.fontui) [font configure font_ui]
2099 set default_config(gui.fontdiff) [font configure font_diff]
2101 {fontui font_ui {Main Font}}
2102 {fontdiff font_diff {Diff/Console Font}}
2107 ######################################################################
2112 menu .mbar -tearoff 0
2113 .mbar add cascade -label Project -menu .mbar.project
2114 .mbar add cascade -label Edit -menu .mbar.edit
2115 .mbar add cascade -label Commit -menu .mbar.commit
2116 if {!$single_commit} {
2117 .mbar add cascade -label Fetch -menu .mbar.fetch
2118 .mbar add cascade -label Pull -menu .mbar.pull
2119 .mbar add cascade -label Push -menu .mbar.push
2121 . configure -menu .mbar
2125 .mbar.project add command -label Visualize \
2128 if {!$single_commit} {
2129 .mbar.project add command -label {Repack Database} \
2130 -command do_repack \
2133 .mbar.project add command -label Quit \
2135 -accelerator $M1T-Q \
2141 .mbar.edit add command -label Undo \
2142 -command {catch {[focus] edit undo}} \
2143 -accelerator $M1T-Z \
2145 .mbar.edit add command -label Redo \
2146 -command {catch {[focus] edit redo}} \
2147 -accelerator $M1T-Y \
2149 .mbar.edit add separator
2150 .mbar.edit add command -label Cut \
2151 -command {catch {tk_textCut [focus]}} \
2152 -accelerator $M1T-X \
2154 .mbar.edit add command -label Copy \
2155 -command {catch {tk_textCopy [focus]}} \
2156 -accelerator $M1T-C \
2158 .mbar.edit add command -label Paste \
2159 -command {catch {tk_textPaste [focus]; [focus] see insert}} \
2160 -accelerator $M1T-V \
2162 .mbar.edit add command -label Delete \
2163 -command {catch {[focus] delete sel.first sel.last}} \
2166 .mbar.edit add separator
2167 .mbar.edit add command -label {Select All} \
2168 -command {catch {[focus] tag add sel 0.0 end}} \
2169 -accelerator $M1T-A \
2171 .mbar.edit add separator
2172 .mbar.edit add command -label {Options...} \
2173 -command do_options \
2178 .mbar.commit add command -label Rescan \
2179 -command do_rescan \
2182 lappend disable_on_lock \
2183 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2184 .mbar.commit add command -label {Amend Last Commit} \
2185 -command do_amend_last \
2187 lappend disable_on_lock \
2188 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2189 .mbar.commit add command -label {Include All Files} \
2190 -command do_include_all \
2191 -accelerator $M1T-I \
2193 lappend disable_on_lock \
2194 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2195 .mbar.commit add command -label {Sign Off} \
2196 -command do_signoff \
2197 -accelerator $M1T-S \
2199 .mbar.commit add command -label Commit \
2200 -command do_commit \
2201 -accelerator $M1T-Return \
2203 lappend disable_on_lock \
2204 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2206 if {!$single_commit} {
2217 # -- Main Window Layout
2218 panedwindow .vpane -orient vertical
2219 panedwindow .vpane.files -orient horizontal
2220 .vpane add .vpane.files -sticky nsew -height 100 -width 400
2221 pack .vpane -anchor n -side top -fill both -expand 1
2223 # -- Index File List
2224 frame .vpane.files.index -height 100 -width 400
2225 label .vpane.files.index.title -text {Modified Files} \
2228 text $ui_index -background white -borderwidth 0 \
2229 -width 40 -height 10 \
2231 -cursor $cursor_ptr \
2232 -yscrollcommand {.vpane.files.index.sb set} \
2234 scrollbar .vpane.files.index.sb -command [list $ui_index yview]
2235 pack .vpane.files.index.title -side top -fill x
2236 pack .vpane.files.index.sb -side right -fill y
2237 pack $ui_index -side left -fill both -expand 1
2238 .vpane.files add .vpane.files.index -sticky nsew
2240 # -- Other (Add) File List
2241 frame .vpane.files.other -height 100 -width 100
2242 label .vpane.files.other.title -text {Untracked Files} \
2245 text $ui_other -background white -borderwidth 0 \
2246 -width 40 -height 10 \
2248 -cursor $cursor_ptr \
2249 -yscrollcommand {.vpane.files.other.sb set} \
2251 scrollbar .vpane.files.other.sb -command [list $ui_other yview]
2252 pack .vpane.files.other.title -side top -fill x
2253 pack .vpane.files.other.sb -side right -fill y
2254 pack $ui_other -side left -fill both -expand 1
2255 .vpane.files add .vpane.files.other -sticky nsew
2257 foreach i [list $ui_index $ui_other] {
2258 $i tag conf in_diff -font font_uibold
2259 $i tag conf in_sel \
2260 -background [$i cget -foreground] \
2261 -foreground [$i cget -background]
2265 # -- Diff and Commit Area
2266 frame .vpane.lower -height 300 -width 400
2267 frame .vpane.lower.commarea
2268 frame .vpane.lower.diff -relief sunken -borderwidth 1
2269 pack .vpane.lower.commarea -side top -fill x
2270 pack .vpane.lower.diff -side bottom -fill both -expand 1
2271 .vpane add .vpane.lower -stick nsew
2273 # -- Commit Area Buttons
2274 frame .vpane.lower.commarea.buttons
2275 label .vpane.lower.commarea.buttons.l -text {} \
2279 pack .vpane.lower.commarea.buttons.l -side top -fill x
2280 pack .vpane.lower.commarea.buttons -side left -fill y
2282 button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
2283 -command do_rescan \
2285 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
2286 lappend disable_on_lock \
2287 {.vpane.lower.commarea.buttons.rescan conf -state}
2289 button .vpane.lower.commarea.buttons.amend -text {Amend Last} \
2290 -command do_amend_last \
2292 pack .vpane.lower.commarea.buttons.amend -side top -fill x
2293 lappend disable_on_lock \
2294 {.vpane.lower.commarea.buttons.amend conf -state}
2296 button .vpane.lower.commarea.buttons.incall -text {Include All} \
2297 -command do_include_all \
2299 pack .vpane.lower.commarea.buttons.incall -side top -fill x
2300 lappend disable_on_lock \
2301 {.vpane.lower.commarea.buttons.incall conf -state}
2303 button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
2304 -command do_signoff \
2306 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
2308 button .vpane.lower.commarea.buttons.commit -text {Commit} \
2309 -command do_commit \
2311 pack .vpane.lower.commarea.buttons.commit -side top -fill x
2312 lappend disable_on_lock \
2313 {.vpane.lower.commarea.buttons.commit conf -state}
2315 # -- Commit Message Buffer
2316 frame .vpane.lower.commarea.buffer
2317 set ui_comm .vpane.lower.commarea.buffer.t
2318 set ui_coml .vpane.lower.commarea.buffer.l
2319 label $ui_coml -text {Commit Message:} \
2323 trace add variable commit_type write {uplevel #0 {
2324 switch -glob $commit_type \
2325 initial {$ui_coml conf -text {Initial Commit Message:}} \
2326 amend {$ui_coml conf -text {Amended Commit Message:}} \
2327 merge {$ui_coml conf -text {Merge Commit Message:}} \
2328 * {$ui_coml conf -text {Commit Message:}}
2330 text $ui_comm -background white -borderwidth 1 \
2333 -autoseparators true \
2335 -width 75 -height 9 -wrap none \
2337 -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
2338 scrollbar .vpane.lower.commarea.buffer.sby \
2339 -command [list $ui_comm yview]
2340 pack $ui_coml -side top -fill x
2341 pack .vpane.lower.commarea.buffer.sby -side right -fill y
2342 pack $ui_comm -side left -fill y
2343 pack .vpane.lower.commarea.buffer -side left -fill y
2345 # -- Commit Message Buffer Context Menu
2347 menu $ui_comm.ctxm -tearoff 0
2348 $ui_comm.ctxm add command -label "Cut" \
2350 -command "tk_textCut $ui_comm"
2351 $ui_comm.ctxm add command -label "Copy" \
2353 -command "tk_textCopy $ui_comm"
2354 $ui_comm.ctxm add command -label "Paste" \
2356 -command "tk_textPaste $ui_comm"
2357 $ui_comm.ctxm add command -label "Delete" \
2359 -command "$ui_comm delete sel.first sel.last"
2360 $ui_comm.ctxm add separator
2361 $ui_comm.ctxm add command -label "Select All" \
2363 -command "$ui_comm tag add sel 0.0 end"
2364 $ui_comm.ctxm add command -label "Copy All" \
2367 $ui_comm tag add sel 0.0 end
2368 tk_textCopy $ui_comm
2369 $ui_comm tag remove sel 0.0 end
2371 $ui_comm.ctxm add separator
2372 $ui_comm.ctxm add command -label "Sign Off" \
2375 bind_button3 $ui_comm "tk_popup $ui_comm.ctxm %X %Y"
2378 set ui_fname_value {}
2379 set ui_fstatus_value {}
2380 frame .vpane.lower.diff.header -background orange
2381 label .vpane.lower.diff.header.l4 \
2382 -textvariable ui_fstatus_value \
2383 -background orange \
2384 -width $max_status_desc \
2388 label .vpane.lower.diff.header.l1 -text {File:} \
2389 -background orange \
2391 set ui_fname .vpane.lower.diff.header.l2
2393 -textvariable ui_fname_value \
2394 -background orange \
2398 menu $ui_fname.ctxm -tearoff 0
2399 $ui_fname.ctxm add command -label "Copy" \
2408 bind_button3 $ui_fname "tk_popup $ui_fname.ctxm %X %Y"
2409 pack .vpane.lower.diff.header.l4 -side left
2410 pack .vpane.lower.diff.header.l1 -side left
2411 pack $ui_fname -fill x
2414 frame .vpane.lower.diff.body
2415 set ui_diff .vpane.lower.diff.body.t
2416 text $ui_diff -background white -borderwidth 0 \
2417 -width 80 -height 15 -wrap none \
2419 -xscrollcommand {.vpane.lower.diff.body.sbx set} \
2420 -yscrollcommand {.vpane.lower.diff.body.sby set} \
2422 scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
2423 -command [list $ui_diff xview]
2424 scrollbar .vpane.lower.diff.body.sby -orient vertical \
2425 -command [list $ui_diff yview]
2426 pack .vpane.lower.diff.body.sbx -side bottom -fill x
2427 pack .vpane.lower.diff.body.sby -side right -fill y
2428 pack $ui_diff -side left -fill both -expand 1
2429 pack .vpane.lower.diff.header -side top -fill x
2430 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
2432 $ui_diff tag conf dm -foreground red
2433 $ui_diff tag conf dp -foreground blue
2434 $ui_diff tag conf di -foreground {#00a000}
2435 $ui_diff tag conf dni -foreground {#a000a0}
2436 $ui_diff tag conf da -font font_diffbold
2437 $ui_diff tag conf bold -font font_diffbold
2439 # -- Diff Body Context Menu
2441 menu $ui_diff.ctxm -tearoff 0
2442 $ui_diff.ctxm add command -label "Copy" \
2444 -command "tk_textCopy $ui_diff"
2445 $ui_diff.ctxm add command -label "Select All" \
2447 -command "$ui_diff tag add sel 0.0 end"
2448 $ui_diff.ctxm add command -label "Copy All" \
2451 $ui_diff tag add sel 0.0 end
2452 tk_textCopy $ui_diff
2453 $ui_diff tag remove sel 0.0 end
2455 $ui_diff.ctxm add separator
2456 $ui_diff.ctxm add command -label "Decrease Font Size" \
2458 -command {incr_font_size font_diff -1}
2459 $ui_diff.ctxm add command -label "Increase Font Size" \
2461 -command {incr_font_size font_diff 1}
2462 $ui_diff.ctxm add separator
2463 $ui_diff.ctxm add command -label "Show Less Context" \
2465 -command {if {$ui_fname_value ne {}
2466 && $repo_config(gui.diffcontext) >= 2} {
2467 incr repo_config(gui.diffcontext) -1
2470 $ui_diff.ctxm add command -label "Show More Context" \
2472 -command {if {$ui_fname_value ne {}} {
2473 incr repo_config(gui.diffcontext)
2476 $ui_diff.ctxm add separator
2477 $ui_diff.ctxm add command -label {Options...} \
2480 bind_button3 $ui_diff "tk_popup $ui_diff.ctxm %X %Y"
2483 set ui_status_value {Initializing...}
2484 label .status -textvariable ui_status_value \
2490 pack .status -anchor w -side bottom -fill x
2494 set gm $repo_config(gui.geometry)
2495 wm geometry . [lindex $gm 0]
2496 .vpane sash place 0 \
2497 [lindex [.vpane sash coord 0] 0] \
2499 .vpane.files sash place 0 \
2501 [lindex [.vpane.files sash coord 0] 1]
2506 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2507 bind $ui_comm <$M1B-Key-i> {do_include_all;break}
2508 bind $ui_comm <$M1B-Key-I> {do_include_all;break}
2509 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2510 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2511 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2512 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2513 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2514 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2515 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2516 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2518 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2519 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2520 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2521 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2522 bind $ui_diff <$M1B-Key-v> {break}
2523 bind $ui_diff <$M1B-Key-V> {break}
2524 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2525 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2526 bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break}
2527 bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break}
2528 bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break}
2529 bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break}
2531 bind . <Destroy> do_quit
2532 bind all <Key-F5> do_rescan
2533 bind all <$M1B-Key-r> do_rescan
2534 bind all <$M1B-Key-R> do_rescan
2535 bind . <$M1B-Key-s> do_signoff
2536 bind . <$M1B-Key-S> do_signoff
2537 bind . <$M1B-Key-i> do_include_all
2538 bind . <$M1B-Key-I> do_include_all
2539 bind . <$M1B-Key-Return> do_commit
2540 bind all <$M1B-Key-q> do_quit
2541 bind all <$M1B-Key-Q> do_quit
2542 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
2543 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
2544 foreach i [list $ui_index $ui_other] {
2545 bind $i <Button-1> "toggle_or_diff $i %x %y; break"
2546 bind $i <$M1B-Button-1> "add_one_to_selection $i %x %y; break"
2547 bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
2551 set file_lists($ui_index) [list]
2552 set file_lists($ui_other) [list]
2554 wm title . "$appname ([file normalize [file dirname $gitdir]])"
2555 focus -force $ui_comm
2556 if {!$single_commit} {
2558 populate_remote_menu .mbar.fetch From fetch_from
2559 populate_remote_menu .mbar.push To push_to
2560 populate_pull_menu .mbar.pull