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 load_repo_config {} {
19 global cfg_trust_mtime
21 array unset repo_config
23 set fd_rc [open "| git repo-config --list" r]
24 while {[gets $fd_rc line] >= 0} {
25 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
26 lappend repo_config($name) $value
32 if {[catch {set cfg_trust_mtime \
33 [lindex $repo_config(gui.trustmtime) 0]
35 set cfg_trust_mtime false
39 proc save_my_config {} {
41 global cfg_trust_mtime
43 if {[catch {set rc_trustMTime $repo_config(gui.trustmtime)}]} {
44 set rc_trustMTime [list false]
46 if {$cfg_trust_mtime != [lindex $rc_trustMTime 0]} {
47 exec git repo-config gui.trustMTime $cfg_trust_mtime
48 set repo_config(gui.trustmtime) [list $cfg_trust_mtime]
51 set cfg_geometry [list \
53 [.vpane sash coord 0] \
54 [.vpane.files sash coord 0] \
56 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
57 set rc_geometry [list [list]]
59 if {$cfg_geometry != [lindex $rc_geometry 0]} {
60 exec git repo-config gui.geometry $cfg_geometry
61 set repo_config(gui.geometry) [list $cfg_geometry]
65 proc error_popup {msg} {
71 append title [lindex \
72 [file split [file normalize [file dirname $gitdir]]] \
80 -title "$title: error" \
84 ######################################################################
88 if { [catch {set cdup [exec git rev-parse --show-cdup]} err]
89 || [catch {set gitdir [exec git rev-parse --git-dir]} err]} {
91 error_popup "Cannot find the git directory:\n\n$err"
99 if {$appname == {git-citool}} {
105 ######################################################################
114 set update_index_fd {}
116 set disable_on_lock [list]
117 set index_lock_type none
123 proc lock_index {type} {
124 global index_lock_type disable_on_lock
126 if {$index_lock_type == {none}} {
127 set index_lock_type $type
128 foreach w $disable_on_lock {
129 uplevel #0 $w disabled
132 } elseif {$index_lock_type == {begin-update} && $type == {update}} {
133 set index_lock_type $type
139 proc unlock_index {} {
140 global index_lock_type disable_on_lock
142 set index_lock_type none
143 foreach w $disable_on_lock {
148 ######################################################################
152 proc repository_state {hdvar ctvar} {
154 upvar $hdvar hd $ctvar ct
156 if {[catch {set hd [exec git rev-parse --verify HEAD]}]} {
158 } elseif {[file exists [file join $gitdir MERGE_HEAD]]} {
165 proc update_status {{final Ready.}} {
166 global HEAD PARENT commit_type
167 global ui_index ui_other ui_status_value ui_comm
168 global status_active file_states
169 global cfg_trust_mtime
171 if {$status_active || ![lock_index read]} return
173 repository_state new_HEAD new_type
174 if {$commit_type == {amend}
175 && $new_type == {normal}
176 && $new_HEAD == $HEAD} {
180 set commit_type $new_type
183 array unset file_states
185 if {![$ui_comm edit modified]
186 || [string trim [$ui_comm get 0.0 end]] == {}} {
187 if {[load_message GITGUI_MSG]} {
188 } elseif {[load_message MERGE_MSG]} {
189 } elseif {[load_message SQUASH_MSG]} {
191 $ui_comm edit modified false
195 if {$cfg_trust_mtime == {true}} {
196 update_status_stage2 {} $final
199 set ui_status_value {Refreshing file status...}
200 set fd_rf [open "| git update-index -q --unmerged --refresh" r]
201 fconfigure $fd_rf -blocking 0 -translation binary
202 fileevent $fd_rf readable [list update_status_stage2 $fd_rf $final]
206 proc update_status_stage2 {fd final} {
207 global gitdir PARENT commit_type
208 global ui_index ui_other ui_status_value ui_comm
210 global buf_rdi buf_rdf buf_rlo
214 if {![eof $fd]} return
218 set ls_others [list | git ls-files --others -z \
219 --exclude-per-directory=.gitignore]
220 set info_exclude [file join $gitdir info exclude]
221 if {[file readable $info_exclude]} {
222 lappend ls_others "--exclude-from=$info_exclude"
230 set ui_status_value {Scanning for modified files ...}
231 set fd_di [open "| git diff-index --cached -z $PARENT" r]
232 set fd_df [open "| git diff-files -z" r]
233 set fd_lo [open $ls_others r]
235 fconfigure $fd_di -blocking 0 -translation binary
236 fconfigure $fd_df -blocking 0 -translation binary
237 fconfigure $fd_lo -blocking 0 -translation binary
238 fileevent $fd_di readable [list read_diff_index $fd_di $final]
239 fileevent $fd_df readable [list read_diff_files $fd_df $final]
240 fileevent $fd_lo readable [list read_ls_others $fd_lo $final]
243 proc load_message {file} {
244 global gitdir ui_comm
246 set f [file join $gitdir $file]
247 if {[file isfile $f]} {
248 if {[catch {set fd [open $f r]}]} {
251 set content [string trim [read $fd]]
253 $ui_comm delete 0.0 end
254 $ui_comm insert end $content
260 proc read_diff_index {fd final} {
263 append buf_rdi [read $fd]
265 set n [string length $buf_rdi]
267 set z1 [string first "\0" $buf_rdi $c]
270 set z2 [string first "\0" $buf_rdi $z1]
276 [string range $buf_rdi $z1 $z2] \
277 [string index $buf_rdi [expr $z1 - 2]]_
281 set buf_rdi [string range $buf_rdi $c end]
286 status_eof $fd buf_rdi $final
289 proc read_diff_files {fd final} {
292 append buf_rdf [read $fd]
294 set n [string length $buf_rdf]
296 set z1 [string first "\0" $buf_rdf $c]
299 set z2 [string first "\0" $buf_rdf $z1]
305 [string range $buf_rdf $z1 $z2] \
306 _[string index $buf_rdf [expr $z1 - 2]]
310 set buf_rdf [string range $buf_rdf $c end]
315 status_eof $fd buf_rdf $final
318 proc read_ls_others {fd final} {
321 append buf_rlo [read $fd]
322 set pck [split $buf_rlo "\0"]
323 set buf_rlo [lindex $pck end]
324 foreach p [lrange $pck 0 end-1] {
327 status_eof $fd buf_rlo $final
330 proc status_eof {fd buf final} {
331 global status_active ui_status_value
338 if {[incr status_active -1] == 0} {
342 set ui_status_value $final
347 ######################################################################
352 global ui_diff ui_fname_value ui_fstatus_value ui_index ui_other
354 $ui_diff conf -state normal
355 $ui_diff delete 0.0 end
356 $ui_diff conf -state disabled
358 set ui_fname_value {}
359 set ui_fstatus_value {}
361 $ui_index tag remove in_diff 0.0 end
362 $ui_other tag remove in_diff 0.0 end
365 proc reshow_diff {} {
366 global ui_fname_value ui_status_value file_states
368 if {$ui_fname_value == {}
369 || [catch {set s $file_states($ui_fname_value)}]} {
372 show_diff $ui_fname_value
376 proc show_diff {path {w {}} {lno {}}} {
377 global file_states file_lists
378 global PARENT diff_3way diff_active
379 global ui_diff ui_fname_value ui_fstatus_value ui_status_value
381 if {$diff_active || ![lock_index read]} return
384 if {$w == {} || $lno == {}} {
385 foreach w [array names file_lists] {
386 set lno [lsearch -sorted $file_lists($w) $path]
393 if {$w != {} && $lno >= 1} {
394 $w tag add in_diff $lno.0 [expr $lno + 1].0
397 set s $file_states($path)
401 set ui_fname_value [escape_path $path]
402 set ui_fstatus_value [mapdesc $m $path]
403 set ui_status_value "Loading diff of [escape_path $path]..."
405 set cmd [list | git diff-index -p $PARENT -- $path]
410 set cmd [list | git diff-index -p -c $PARENT $path]
414 set fd [open $path r]
415 set content [read $fd]
420 set ui_status_value "Unable to display [escape_path $path]"
421 error_popup "Error loading file:\n\n$err"
424 $ui_diff conf -state normal
425 $ui_diff insert end $content
426 $ui_diff conf -state disabled
429 set ui_status_value {Ready.}
434 if {[catch {set fd [open $cmd r]} err]} {
437 set ui_status_value "Unable to display [escape_path $path]"
438 error_popup "Error loading diff:\n\n$err"
442 fconfigure $fd -blocking 0 -translation auto
443 fileevent $fd readable [list read_diff $fd]
446 proc read_diff {fd} {
447 global ui_diff ui_status_value diff_3way diff_active
449 while {[gets $fd line] >= 0} {
450 if {[string match {diff --git *} $line]} continue
451 if {[string match {diff --combined *} $line]} continue
452 if {[string match {--- *} $line]} continue
453 if {[string match {+++ *} $line]} continue
454 if {[string match index* $line]} {
455 if {[string first , $line] >= 0} {
460 $ui_diff conf -state normal
462 set x [string index $line 0]
467 default {set tags {}}
470 set x [string range $line 0 1]
472 default {set tags {}}
474 "++" {set tags dp; set x " +"}
475 " +" {set tags {di bold}; set x "++"}
476 "+ " {set tags dni; set x "-+"}
477 "--" {set tags dm; set x " -"}
478 " -" {set tags {dm bold}; set x "--"}
479 "- " {set tags di; set x "+-"}
480 default {set tags {}}
482 set line [string replace $line 0 1 $x]
484 $ui_diff insert end $line $tags
485 $ui_diff insert end "\n"
486 $ui_diff conf -state disabled
493 set ui_status_value {Ready.}
497 ######################################################################
501 proc load_last_commit {} {
502 global HEAD PARENT commit_type ui_comm
504 if {$commit_type == {amend}} return
505 if {$commit_type != {normal}} {
506 error_popup "Can't amend a $commit_type commit."
514 set fd [open "| git cat-file commit $HEAD" r]
515 while {[gets $fd line] > 0} {
516 if {[string match {parent *} $line]} {
517 set parent [string range $line 7 end]
521 set msg [string trim [read $fd]]
524 error_popup "Error loading commit data for amend:\n\n$err"
528 if {$parent_count == 0} {
529 set commit_type amend
533 } elseif {$parent_count == 1} {
534 set commit_type amend
536 $ui_comm delete 0.0 end
537 $ui_comm insert end $msg
538 $ui_comm edit modified false
542 error_popup {You can't amend a merge commit.}
547 proc commit_tree {} {
548 global tcl_platform HEAD gitdir commit_type file_states
549 global commit_active ui_status_value
552 if {$commit_active || ![lock_index update]} return
554 # -- Our in memory state should match the repository.
556 repository_state curHEAD cur_type
557 if {$commit_type == {amend}
558 && $cur_type == {normal}
559 && $curHEAD == $HEAD} {
560 } elseif {$commit_type != $cur_type || $HEAD != $curHEAD} {
561 error_popup {Last scanned state does not match repository state.
563 Its highly likely that another Git program modified the
564 repository since our last scan. A rescan is required
572 # -- At least one file should differ in the index.
575 foreach path [array names file_states] {
576 set s $file_states($path)
577 switch -glob -- [lindex $s 0] {
581 M? {set files_ready 1; break}
583 error_popup "Unmerged files cannot be committed.
585 File [escape_path $path] has merge conflicts.
586 You must resolve them and include the file before committing.
592 error_popup "Unknown file state [lindex $s 0] detected.
594 File [escape_path $path] cannot be committed by this program.
600 error_popup {No included files to commit.
602 You must include at least 1 file before you can commit.
608 # -- A message is required.
610 set msg [string trim [$ui_comm get 1.0 end]]
612 error_popup {Please supply a commit message.
614 A good commit message has the following format:
616 - First line: Describe in one sentance what you did.
618 - Remaining lines: Describe why this change is good.
624 # -- Ask the pre-commit hook for the go-ahead.
626 set pchook [file join $gitdir hooks pre-commit]
627 if {$tcl_platform(platform) == {windows} && [file isfile $pchook]} {
628 set pchook [list sh -c \
629 "if test -x \"$pchook\"; then exec \"$pchook\"; fi"]
630 } elseif {[file executable $pchook]} {
631 set pchook [list $pchook]
635 if {$pchook != {} && [catch {eval exec $pchook} err]} {
636 hook_failed_popup pre-commit $err
641 # -- Write the tree in the background.
644 set ui_status_value {Committing changes...}
646 set fd_wt [open "| git write-tree" r]
647 fileevent $fd_wt readable [list commit_stage2 $fd_wt $curHEAD $msg]
650 proc commit_stage2 {fd_wt curHEAD msg} {
651 global single_commit gitdir HEAD PARENT commit_type
652 global commit_active ui_status_value ui_comm
656 if {$tree_id == {} || [catch {close $fd_wt} err]} {
657 error_popup "write-tree failed:\n\n$err"
659 set ui_status_value {Commit failed.}
664 # -- Create the commit.
666 set cmd [list git commit-tree $tree_id]
668 lappend cmd -p $PARENT
670 if {$commit_type == {merge}} {
672 set fd_mh [open [file join $gitdir MERGE_HEAD] r]
673 while {[gets $fd_mh merge_head] >= 0} {
674 lappend cmd -p $merge_head
678 error_popup "Loading MERGE_HEAD failed:\n\n$err"
680 set ui_status_value {Commit failed.}
686 # git commit-tree writes to stderr during initial commit.
687 lappend cmd 2>/dev/null
690 if {[catch {set cmt_id [eval exec $cmd]} err]} {
691 error_popup "commit-tree failed:\n\n$err"
693 set ui_status_value {Commit failed.}
698 # -- Update the HEAD ref.
701 if {$commit_type != {normal}} {
702 append reflogm " ($commit_type)"
704 set i [string first "\n" $msg]
706 append reflogm {: } [string range $msg 0 [expr $i - 1]]
708 append reflogm {: } $msg
710 set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD]
711 if {[catch {eval exec $cmd} err]} {
712 error_popup "update-ref failed:\n\n$err"
714 set ui_status_value {Commit failed.}
719 # -- Cleanup after ourselves.
721 catch {file delete [file join $gitdir MERGE_HEAD]}
722 catch {file delete [file join $gitdir MERGE_MSG]}
723 catch {file delete [file join $gitdir SQUASH_MSG]}
724 catch {file delete [file join $gitdir GITGUI_MSG]}
726 # -- Let rerere do its thing.
728 if {[file isdirectory [file join $gitdir rr-cache]]} {
729 catch {exec git rerere}
732 $ui_comm delete 0.0 end
733 $ui_comm edit modified false
736 if {$single_commit} do_quit
738 # -- Update status without invoking any git commands.
741 set commit_type normal
745 foreach path [array names file_states] {
746 set s $file_states($path)
751 D? {set m _[string index $m 1]}
755 unset file_states($path)
757 lset file_states($path) 0 $m
764 set ui_status_value \
765 "Changes committed as [string range $cmt_id 0 7]."
768 ######################################################################
772 proc fetch_from {remote} {
773 set w [new_console "fetch $remote" \
774 "Fetching new changes from $remote"]
775 set cmd [list git fetch]
780 proc pull_remote {remote branch} {
781 global HEAD commit_type
784 if {![lock_index update]} return
786 # -- Our in memory state should match the repository.
788 repository_state curHEAD cur_type
789 if {$commit_type != $cur_type || $HEAD != $curHEAD} {
790 error_popup {Last scanned state does not match repository state.
792 Its highly likely that another Git program modified the
793 repository since our last scan. A rescan is required
794 before a pull can be started.
801 # -- No differences should exist before a pull.
803 if {[array size file_states] != 0} {
804 error_popup {Uncommitted but modified files are present.
806 You should not perform a pull with unmodified files in your working
807 directory as Git would be unable to recover from an incorrect merge.
809 Commit or throw away all changes before starting a pull operation.
815 set w [new_console "pull $remote $branch" \
816 "Pulling new changes from branch $branch in $remote"]
817 set cmd [list git pull]
820 console_exec $w $cmd [list post_pull_remote $remote $branch]
823 proc post_pull_remote {remote branch success} {
824 global HEAD PARENT commit_type
825 global ui_status_value
829 repository_state HEAD commit_type
831 set $ui_status_value {Ready.}
833 update_status "Conflicts detected while pulling $branch from $remote."
837 proc push_to {remote} {
838 set w [new_console "push $remote" \
839 "Pushing changes to $remote"]
840 set cmd [list git push]
845 ######################################################################
849 proc mapcol {state path} {
850 global all_cols ui_other
852 if {[catch {set r $all_cols($state)}]} {
853 puts "error: no column for state={$state} $path"
859 proc mapicon {state path} {
862 if {[catch {set r $all_icons($state)}]} {
863 puts "error: no icon for state={$state} $path"
869 proc mapdesc {state path} {
872 if {[catch {set r $all_descs($state)}]} {
873 puts "error: no desc for state={$state} $path"
879 proc escape_path {path} {
880 regsub -all "\n" $path "\\n" path
886 proc merge_state {path new_state} {
887 global file_states next_icon_id
889 set s0 [string index $new_state 0]
890 set s1 [string index $new_state 1]
892 if {[catch {set info $file_states($path)}]} {
894 set icon n[incr next_icon_id]
896 set state [lindex $info 0]
897 set icon [lindex $info 1]
901 set s0 [string index $state 0]
902 } elseif {$s0 == {*}} {
907 set s1 [string index $state 1]
908 } elseif {$s1 == {*}} {
912 set file_states($path) [list $s0$s1 $icon]
916 proc display_file {path state} {
917 global ui_index ui_other
918 global file_states file_lists status_active
920 set old_m [merge_state $path $state]
921 if {$status_active} return
923 set s $file_states($path)
924 set new_m [lindex $s 0]
925 set new_w [mapcol $new_m $path]
926 set old_w [mapcol $old_m $path]
927 set new_icon [mapicon $new_m $path]
929 if {$new_w != $old_w} {
930 set lno [lsearch -sorted $file_lists($old_w) $path]
933 $old_w conf -state normal
934 $old_w delete $lno.0 [expr $lno + 1].0
935 $old_w conf -state disabled
938 lappend file_lists($new_w) $path
939 set file_lists($new_w) [lsort $file_lists($new_w)]
940 set lno [lsearch -sorted $file_lists($new_w) $path]
942 $new_w conf -state normal
943 $new_w image create $lno.0 \
944 -align center -padx 5 -pady 1 \
945 -name [lindex $s 1] \
947 $new_w insert $lno.1 "[escape_path $path]\n"
948 $new_w conf -state disabled
949 } elseif {$new_icon != [mapicon $old_m $path]} {
950 $new_w conf -state normal
951 $new_w image conf [lindex $s 1] -image $new_icon
952 $new_w conf -state disabled
956 proc display_all_files {} {
957 global ui_index ui_other file_states file_lists
959 $ui_index conf -state normal
960 $ui_other conf -state normal
962 $ui_index delete 0.0 end
963 $ui_other delete 0.0 end
965 array unset file_lists
966 foreach path [lsort [array names file_states]] {
967 set s $file_states($path)
969 set w [mapcol $m $path]
970 lappend file_lists($w) $path
971 $w image create end \
972 -align center -padx 5 -pady 1 \
973 -name [lindex $s 1] \
974 -image [mapicon $m $path]
975 $w insert end "[escape_path $path]\n"
978 $ui_index conf -state disabled
979 $ui_other conf -state disabled
982 proc with_update_index {body} {
983 global update_index_fd
985 if {$update_index_fd == {}} {
986 if {![lock_index update]} return
987 set update_index_fd [open \
988 "| git update-index --add --remove -z --stdin" \
990 fconfigure $update_index_fd -translation binary
992 close $update_index_fd
993 set update_index_fd {}
1000 proc update_index {path} {
1001 global update_index_fd
1003 if {$update_index_fd == {}} {
1004 error {not in with_update_index}
1006 puts -nonewline $update_index_fd "$path\0"
1010 proc toggle_mode {path} {
1011 global file_states ui_fname_value
1013 set s $file_states($path)
1026 with_update_index {update_index $path}
1027 display_file $path $new
1028 if {$ui_fname_value == $path} {
1033 ######################################################################
1035 ## remote management
1037 proc load_all_remotes {} {
1038 global gitdir all_remotes repo_config
1040 set all_remotes [list]
1041 set rm_dir [file join $gitdir remotes]
1042 if {[file isdirectory $rm_dir]} {
1043 set all_remotes [concat $all_remotes [glob \
1047 -directory $rm_dir *]]
1050 foreach line [array names repo_config remote.*.url] {
1051 if {[regexp ^remote\.(.*)\.url\$ $line line name]} {
1052 lappend all_remotes $name
1056 set all_remotes [lsort -unique $all_remotes]
1059 proc populate_remote_menu {m pfx op} {
1060 global all_remotes font_ui
1062 foreach remote $all_remotes {
1063 $m add command -label "$pfx $remote..." \
1064 -command [list $op $remote] \
1069 proc populate_pull_menu {m} {
1070 global gitdir repo_config all_remotes font_ui disable_on_lock
1072 foreach remote $all_remotes {
1074 if {[array get repo_config remote.$remote.url] != {}} {
1075 if {[array get repo_config remote.$remote.fetch] != {}} {
1076 regexp {^([^:]+):} \
1077 [lindex $repo_config(remote.$remote.fetch) 0] \
1082 set fd [open [file join $gitdir remotes $remote] r]
1083 while {[gets $fd line] >= 0} {
1084 if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} {
1093 regsub ^refs/heads/ $rb {} rb_short
1094 if {$rb_short != {}} {
1096 -label "Branch $rb_short from $remote..." \
1097 -command [list pull_remote $remote $rb] \
1099 lappend disable_on_lock \
1100 [list $m entryconf [$m index last] -state]
1105 ######################################################################
1110 #define mask_width 14
1111 #define mask_height 15
1112 static unsigned char mask_bits[] = {
1113 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1114 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1115 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1118 image create bitmap file_plain -background white -foreground black -data {
1119 #define plain_width 14
1120 #define plain_height 15
1121 static unsigned char plain_bits[] = {
1122 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1123 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1124 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1125 } -maskdata $filemask
1127 image create bitmap file_mod -background white -foreground blue -data {
1128 #define mod_width 14
1129 #define mod_height 15
1130 static unsigned char mod_bits[] = {
1131 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1132 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1133 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1134 } -maskdata $filemask
1136 image create bitmap file_fulltick -background white -foreground "#007000" -data {
1137 #define file_fulltick_width 14
1138 #define file_fulltick_height 15
1139 static unsigned char file_fulltick_bits[] = {
1140 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1141 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1142 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1143 } -maskdata $filemask
1145 image create bitmap file_parttick -background white -foreground "#005050" -data {
1146 #define parttick_width 14
1147 #define parttick_height 15
1148 static unsigned char parttick_bits[] = {
1149 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1150 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1151 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1152 } -maskdata $filemask
1154 image create bitmap file_question -background white -foreground black -data {
1155 #define file_question_width 14
1156 #define file_question_height 15
1157 static unsigned char file_question_bits[] = {
1158 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1159 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1160 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1161 } -maskdata $filemask
1163 image create bitmap file_removed -background white -foreground red -data {
1164 #define file_removed_width 14
1165 #define file_removed_height 15
1166 static unsigned char file_removed_bits[] = {
1167 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1168 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1169 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1170 } -maskdata $filemask
1172 image create bitmap file_merge -background white -foreground blue -data {
1173 #define file_merge_width 14
1174 #define file_merge_height 15
1175 static unsigned char file_merge_bits[] = {
1176 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1177 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1178 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1179 } -maskdata $filemask
1181 set ui_index .vpane.files.index.list
1182 set ui_other .vpane.files.other.list
1183 set max_status_desc 0
1185 {__ i plain "Unmodified"}
1186 {_M i mod "Modified"}
1187 {M_ i fulltick "Checked in"}
1188 {MM i parttick "Partially included"}
1190 {_O o plain "Untracked"}
1191 {A_ o fulltick "Added"}
1192 {AM o parttick "Partially added"}
1193 {AD o question "Added (but now gone)"}
1195 {_D i question "Missing"}
1196 {D_ i removed "Removed"}
1197 {DD i removed "Removed"}
1198 {DO i removed "Removed (still exists)"}
1200 {UM i merge "Merge conflicts"}
1201 {U_ i merge "Merge conflicts"}
1203 if {$max_status_desc < [string length [lindex $i 3]]} {
1204 set max_status_desc [string length [lindex $i 3]]
1206 if {[lindex $i 1] == {i}} {
1207 set all_cols([lindex $i 0]) $ui_index
1209 set all_cols([lindex $i 0]) $ui_other
1211 set all_icons([lindex $i 0]) file_[lindex $i 2]
1212 set all_descs([lindex $i 0]) [lindex $i 3]
1216 ######################################################################
1220 proc hook_failed_popup {hook msg} {
1221 global gitdir font_ui font_diff appname
1228 label $w.m.l1 -text "$hook hook failed:" \
1231 -font [concat $font_ui bold]
1233 -background white -borderwidth 1 \
1235 -width 80 -height 10 \
1237 -yscrollcommand [list $w.m.sby set]
1239 -text {You must correct the above errors before committing.} \
1242 -font [concat $font_ui bold]
1243 scrollbar $w.m.sby -command [list $w.m.t yview]
1244 pack $w.m.l1 -side top -fill x
1245 pack $w.m.l2 -side bottom -fill x
1246 pack $w.m.sby -side right -fill y
1247 pack $w.m.t -side left -fill both -expand 1
1248 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1250 $w.m.t insert 1.0 $msg
1251 $w.m.t conf -state disabled
1253 button $w.ok -text OK \
1256 -command "destroy $w"
1257 pack $w.ok -side bottom
1259 bind $w <Visibility> "grab $w; focus $w"
1260 bind $w <Key-Return> "destroy $w"
1261 wm title $w "$appname ([lindex [file split \
1262 [file normalize [file dirname $gitdir]]] \
1267 set next_console_id 0
1269 proc new_console {short_title long_title} {
1270 global next_console_id console_data
1271 set w .console[incr next_console_id]
1272 set console_data($w) [list $short_title $long_title]
1273 return [console_init $w]
1276 proc console_init {w} {
1277 global console_cr console_data
1278 global gitdir appname font_ui font_diff
1280 set console_cr($w) 1.0
1283 label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
1286 -font [concat $font_ui bold]
1288 -background white -borderwidth 1 \
1290 -width 80 -height 10 \
1293 -yscrollcommand [list $w.m.sby set]
1294 label $w.m.s -anchor w \
1296 -font [concat $font_ui bold]
1297 scrollbar $w.m.sby -command [list $w.m.t yview]
1298 pack $w.m.l1 -side top -fill x
1299 pack $w.m.s -side bottom -fill x
1300 pack $w.m.sby -side right -fill y
1301 pack $w.m.t -side left -fill both -expand 1
1302 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1304 button $w.ok -text {Running...} \
1308 -command "destroy $w"
1309 pack $w.ok -side bottom
1311 bind $w <Visibility> "focus $w"
1312 wm title $w "$appname ([lindex [file split \
1313 [file normalize [file dirname $gitdir]]] \
1314 end]): [lindex $console_data($w) 0]"
1318 proc console_exec {w cmd {after {}}} {
1321 # -- Windows tosses the enviroment when we exec our child.
1322 # But most users need that so we have to relogin. :-(
1324 if {$tcl_platform(platform) == {windows}} {
1325 set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
1328 # -- Tcl won't let us redirect both stdout and stderr to
1329 # the same pipe. So pass it through cat...
1331 set cmd [concat | $cmd |& cat]
1333 set fd_f [open $cmd r]
1334 fconfigure $fd_f -blocking 0 -translation binary
1335 fileevent $fd_f readable [list console_read $w $fd_f $after]
1338 proc console_read {w fd after} {
1339 global console_cr console_data
1343 if {![winfo exists $w]} {console_init $w}
1344 $w.m.t conf -state normal
1346 set n [string length $buf]
1348 set cr [string first "\r" $buf $c]
1349 set lf [string first "\n" $buf $c]
1350 if {$cr < 0} {set cr [expr $n + 1]}
1351 if {$lf < 0} {set lf [expr $n + 1]}
1354 $w.m.t insert end [string range $buf $c $lf]
1355 set console_cr($w) [$w.m.t index {end -1c}]
1359 $w.m.t delete $console_cr($w) end
1360 $w.m.t insert end "\n"
1361 $w.m.t insert end [string range $buf $c $cr]
1366 $w.m.t conf -state disabled
1370 fconfigure $fd -blocking 1
1372 if {[catch {close $fd}]} {
1373 if {![winfo exists $w]} {console_init $w}
1374 $w.m.s conf -background red -text {Error: Command Failed}
1375 $w.ok conf -text Close
1376 $w.ok conf -state normal
1378 } elseif {[winfo exists $w]} {
1379 $w.m.s conf -background green -text {Success}
1380 $w.ok conf -text Close
1381 $w.ok conf -state normal
1384 array unset console_cr $w
1385 array unset console_data $w
1387 uplevel #0 $after $ok
1391 fconfigure $fd -blocking 0
1394 ######################################################################
1398 set starting_gitk_msg {Please wait... Starting gitk...}
1401 global tcl_platform ui_status_value starting_gitk_msg
1403 set ui_status_value $starting_gitk_msg
1405 if {$ui_status_value == $starting_gitk_msg} {
1406 set ui_status_value {Ready.}
1410 if {$tcl_platform(platform) == {windows}} {
1418 set w [new_console "repack" "Repacking the object database"]
1419 set cmd [list git repack]
1422 console_exec $w $cmd
1426 global gitdir ui_comm
1428 set save [file join $gitdir GITGUI_MSG]
1429 set msg [string trim [$ui_comm get 0.0 end]]
1430 if {[$ui_comm edit modified] && $msg != {}} {
1432 set fd [open $save w]
1433 puts $fd [string trim [$ui_comm get 0.0 end]]
1436 } elseif {$msg == {} && [file exists $save]} {
1448 proc do_include_all {} {
1449 global update_active ui_status_value
1451 if {$update_active || ![lock_index begin-update]} return
1454 set ui_status_value {Including all modified files...}
1457 foreach path [array names file_states] {
1458 set s $file_states($path)
1464 _D {toggle_mode $path}
1469 set ui_status_value {Ready.}
1473 set GIT_COMMITTER_IDENT {}
1475 proc do_signoff {} {
1476 global ui_comm GIT_COMMITTER_IDENT
1478 if {$GIT_COMMITTER_IDENT == {}} {
1479 if {[catch {set me [exec git var GIT_COMMITTER_IDENT]} err]} {
1480 error_popup "Unable to obtain your identity:\n\n$err"
1483 if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
1484 $me me GIT_COMMITTER_IDENT]} {
1485 error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me"
1490 set str "Signed-off-by: $GIT_COMMITTER_IDENT"
1491 if {[$ui_comm get {end -1c linestart} {end -1c}] != $str} {
1492 $ui_comm edit separator
1493 $ui_comm insert end "\n$str"
1494 $ui_comm edit separator
1499 proc do_amend_last {} {
1507 # shift == 1: left click
1509 proc click {w x y shift wx wy} {
1510 global ui_index ui_other file_lists
1512 set pos [split [$w index @$x,$y] .]
1513 set lno [lindex $pos 0]
1514 set col [lindex $pos 1]
1515 set path [lindex $file_lists($w) [expr $lno - 1]]
1516 if {$path == {}} return
1518 if {$col > 0 && $shift == 1} {
1519 show_diff $path $w $lno
1523 proc unclick {w x y} {
1526 set pos [split [$w index @$x,$y] .]
1527 set lno [lindex $pos 0]
1528 set col [lindex $pos 1]
1529 set path [lindex $file_lists($w) [expr $lno - 1]]
1530 if {$path == {}} return
1537 ######################################################################
1541 set font_ui {Helvetica 10}
1542 set font_diff {Courier 10}
1543 set maincursor [. cget -cursor]
1545 switch -glob -- "$tcl_platform(platform),$tcl_platform(os)" {
1546 windows,* {set M1B Control; set M1T Ctrl}
1547 unix,Darwin {set M1B M1; set M1T Cmd}
1548 default {set M1B M1; set M1T M1}
1552 menu .mbar -tearoff 0
1553 .mbar add cascade -label Project -menu .mbar.project
1554 .mbar add cascade -label Edit -menu .mbar.edit
1555 .mbar add cascade -label Commit -menu .mbar.commit
1556 .mbar add cascade -label Fetch -menu .mbar.fetch
1557 .mbar add cascade -label Pull -menu .mbar.pull
1558 .mbar add cascade -label Push -menu .mbar.push
1559 .mbar add cascade -label Options -menu .mbar.options
1560 . configure -menu .mbar
1564 .mbar.project add command -label Visualize \
1567 .mbar.project add command -label {Repack Database} \
1568 -command do_repack \
1570 .mbar.project add command -label Quit \
1572 -accelerator $M1T-Q \
1578 .mbar.edit add command -label Undo \
1579 -command {catch {[focus] edit undo}} \
1580 -accelerator $M1T-Z \
1582 .mbar.edit add command -label Redo \
1583 -command {catch {[focus] edit redo}} \
1584 -accelerator $M1T-Y \
1586 .mbar.edit add separator
1587 .mbar.edit add command -label Cut \
1588 -command {catch {tk_textCut [focus]}} \
1589 -accelerator $M1T-X \
1591 .mbar.edit add command -label Copy \
1592 -command {catch {tk_textCopy [focus]}} \
1593 -accelerator $M1T-C \
1595 .mbar.edit add command -label Paste \
1596 -command {catch {tk_textPaste [focus]; [focus] see insert}} \
1597 -accelerator $M1T-V \
1599 .mbar.edit add command -label Delete \
1600 -command {catch {[focus] delete sel.first sel.last}} \
1603 .mbar.edit add separator
1604 .mbar.edit add command -label {Select All} \
1605 -command {catch {[focus] tag add sel 0.0 end}} \
1606 -accelerator $M1T-A \
1611 .mbar.commit add command -label Rescan \
1612 -command do_rescan \
1615 lappend disable_on_lock \
1616 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1617 .mbar.commit add command -label {Amend Last Commit} \
1618 -command do_amend_last \
1620 lappend disable_on_lock \
1621 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1622 .mbar.commit add command -label {Include All Files} \
1623 -command do_include_all \
1624 -accelerator $M1T-I \
1626 lappend disable_on_lock \
1627 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1628 .mbar.commit add command -label {Sign Off} \
1629 -command do_signoff \
1630 -accelerator $M1T-S \
1632 .mbar.commit add command -label Commit \
1633 -command do_commit \
1634 -accelerator $M1T-Return \
1636 lappend disable_on_lock \
1637 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1650 .mbar.options add checkbutton \
1651 -label {Trust File Modification Timestamps} \
1654 -variable cfg_trust_mtime
1656 # -- Main Window Layout
1657 panedwindow .vpane -orient vertical
1658 panedwindow .vpane.files -orient horizontal
1659 .vpane add .vpane.files -sticky nsew -height 100 -width 400
1660 pack .vpane -anchor n -side top -fill both -expand 1
1662 # -- Index File List
1663 frame .vpane.files.index -height 100 -width 400
1664 label .vpane.files.index.title -text {Modified Files} \
1667 text $ui_index -background white -borderwidth 0 \
1668 -width 40 -height 10 \
1670 -yscrollcommand {.vpane.files.index.sb set} \
1671 -cursor $maincursor \
1673 scrollbar .vpane.files.index.sb -command [list $ui_index yview]
1674 pack .vpane.files.index.title -side top -fill x
1675 pack .vpane.files.index.sb -side right -fill y
1676 pack $ui_index -side left -fill both -expand 1
1677 .vpane.files add .vpane.files.index -sticky nsew
1679 # -- Other (Add) File List
1680 frame .vpane.files.other -height 100 -width 100
1681 label .vpane.files.other.title -text {Untracked Files} \
1684 text $ui_other -background white -borderwidth 0 \
1685 -width 40 -height 10 \
1687 -yscrollcommand {.vpane.files.other.sb set} \
1688 -cursor $maincursor \
1690 scrollbar .vpane.files.other.sb -command [list $ui_other yview]
1691 pack .vpane.files.other.title -side top -fill x
1692 pack .vpane.files.other.sb -side right -fill y
1693 pack $ui_other -side left -fill both -expand 1
1694 .vpane.files add .vpane.files.other -sticky nsew
1696 $ui_index tag conf in_diff -font [concat $font_ui bold]
1697 $ui_other tag conf in_diff -font [concat $font_ui bold]
1699 # -- Diff and Commit Area
1700 frame .vpane.lower -height 400 -width 400
1701 frame .vpane.lower.commarea
1702 frame .vpane.lower.diff -relief sunken -borderwidth 1
1703 pack .vpane.lower.commarea -side top -fill x
1704 pack .vpane.lower.diff -side bottom -fill both -expand 1
1705 .vpane add .vpane.lower -stick nsew
1707 # -- Commit Area Buttons
1708 frame .vpane.lower.commarea.buttons
1709 label .vpane.lower.commarea.buttons.l -text {} \
1713 pack .vpane.lower.commarea.buttons.l -side top -fill x
1714 pack .vpane.lower.commarea.buttons -side left -fill y
1716 button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
1717 -command do_rescan \
1719 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
1720 lappend disable_on_lock {.vpane.lower.commarea.buttons.rescan conf -state}
1722 button .vpane.lower.commarea.buttons.amend -text {Amend Last} \
1723 -command do_amend_last \
1725 pack .vpane.lower.commarea.buttons.amend -side top -fill x
1726 lappend disable_on_lock {.vpane.lower.commarea.buttons.amend conf -state}
1728 button .vpane.lower.commarea.buttons.incall -text {Include All} \
1729 -command do_include_all \
1731 pack .vpane.lower.commarea.buttons.incall -side top -fill x
1732 lappend disable_on_lock {.vpane.lower.commarea.buttons.incall conf -state}
1734 button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
1735 -command do_signoff \
1737 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
1739 button .vpane.lower.commarea.buttons.commit -text {Commit} \
1740 -command do_commit \
1742 pack .vpane.lower.commarea.buttons.commit -side top -fill x
1743 lappend disable_on_lock {.vpane.lower.commarea.buttons.commit conf -state}
1745 # -- Commit Message Buffer
1746 frame .vpane.lower.commarea.buffer
1747 set ui_comm .vpane.lower.commarea.buffer.t
1748 set ui_coml .vpane.lower.commarea.buffer.l
1749 label $ui_coml -text {Commit Message:} \
1753 trace add variable commit_type write {uplevel #0 {
1754 switch -glob $commit_type \
1755 initial {$ui_coml conf -text {Initial Commit Message:}} \
1756 amend {$ui_coml conf -text {Amended Commit Message:}} \
1757 merge {$ui_coml conf -text {Merge Commit Message:}} \
1758 * {$ui_coml conf -text {Commit Message:}}
1760 text $ui_comm -background white -borderwidth 1 \
1763 -autoseparators true \
1765 -width 75 -height 9 -wrap none \
1767 -yscrollcommand {.vpane.lower.commarea.buffer.sby set} \
1769 scrollbar .vpane.lower.commarea.buffer.sby -command [list $ui_comm yview]
1770 pack $ui_coml -side top -fill x
1771 pack .vpane.lower.commarea.buffer.sby -side right -fill y
1772 pack $ui_comm -side left -fill y
1773 pack .vpane.lower.commarea.buffer -side left -fill y
1776 set ui_fname_value {}
1777 set ui_fstatus_value {}
1778 frame .vpane.lower.diff.header -background orange
1779 label .vpane.lower.diff.header.l1 -text {File:} \
1780 -background orange \
1782 label .vpane.lower.diff.header.l2 -textvariable ui_fname_value \
1783 -background orange \
1787 label .vpane.lower.diff.header.l3 -text {Status:} \
1788 -background orange \
1790 label .vpane.lower.diff.header.l4 -textvariable ui_fstatus_value \
1791 -background orange \
1792 -width $max_status_desc \
1796 pack .vpane.lower.diff.header.l1 -side left
1797 pack .vpane.lower.diff.header.l2 -side left -fill x
1798 pack .vpane.lower.diff.header.l4 -side right
1799 pack .vpane.lower.diff.header.l3 -side right
1802 frame .vpane.lower.diff.body
1803 set ui_diff .vpane.lower.diff.body.t
1804 text $ui_diff -background white -borderwidth 0 \
1805 -width 80 -height 15 -wrap none \
1807 -xscrollcommand {.vpane.lower.diff.body.sbx set} \
1808 -yscrollcommand {.vpane.lower.diff.body.sby set} \
1809 -cursor $maincursor \
1811 scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
1812 -command [list $ui_diff xview]
1813 scrollbar .vpane.lower.diff.body.sby -orient vertical \
1814 -command [list $ui_diff yview]
1815 pack .vpane.lower.diff.body.sbx -side bottom -fill x
1816 pack .vpane.lower.diff.body.sby -side right -fill y
1817 pack $ui_diff -side left -fill both -expand 1
1818 pack .vpane.lower.diff.header -side top -fill x
1819 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
1821 $ui_diff tag conf dm -foreground red
1822 $ui_diff tag conf dp -foreground blue
1823 $ui_diff tag conf da -font [concat $font_diff bold]
1824 $ui_diff tag conf di -foreground "#00a000"
1825 $ui_diff tag conf dni -foreground "#a000a0"
1826 $ui_diff tag conf bold -font [concat $font_diff bold]
1829 set ui_status_value {Initializing...}
1830 label .status -textvariable ui_status_value \
1836 pack .status -anchor w -side bottom -fill x
1840 wm geometry . [lindex $repo_config(gui.geometry) 0 0]
1841 eval .vpane sash place 0 [lindex $repo_config(gui.geometry) 0 1]
1842 eval .vpane.files sash place 0 [lindex $repo_config(gui.geometry) 0 2]
1846 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
1847 bind $ui_comm <$M1B-Key-i> {do_include_all;break}
1848 bind $ui_comm <$M1B-Key-I> {do_include_all;break}
1849 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
1850 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
1851 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
1852 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
1853 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
1854 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
1855 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
1856 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
1858 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
1859 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
1860 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
1861 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
1862 bind $ui_diff <$M1B-Key-v> {break}
1863 bind $ui_diff <$M1B-Key-V> {break}
1864 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
1865 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
1866 bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break}
1867 bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break}
1868 bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break}
1869 bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break}
1871 bind . <Destroy> do_quit
1872 bind all <Key-F5> do_rescan
1873 bind all <$M1B-Key-r> do_rescan
1874 bind all <$M1B-Key-R> do_rescan
1875 bind . <$M1B-Key-s> do_signoff
1876 bind . <$M1B-Key-S> do_signoff
1877 bind . <$M1B-Key-i> do_include_all
1878 bind . <$M1B-Key-I> do_include_all
1879 bind . <$M1B-Key-Return> do_commit
1880 bind all <$M1B-Key-q> do_quit
1881 bind all <$M1B-Key-Q> do_quit
1882 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
1883 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
1884 foreach i [list $ui_index $ui_other] {
1885 bind $i <Button-1> {click %W %x %y 1 %X %Y; break}
1886 bind $i <Button-3> {click %W %x %y 3 %X %Y; break}
1887 bind $i <ButtonRelease-1> {unclick %W %x %y; break}
1891 wm title . "$appname ([file normalize [file dirname $gitdir]])"
1892 focus -force $ui_comm
1894 populate_remote_menu .mbar.fetch From fetch_from
1895 populate_remote_menu .mbar.push To push_to
1896 populate_pull_menu .mbar.pull