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 [wm geometry .]
52 append cfg_geometry " [lindex [.vpane sash coord 0] 1]"
53 append cfg_geometry " [lindex [.vpane.files sash coord 0] 0]"
54 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
55 set rc_geometry [list [list]]
57 if {$cfg_geometry != [lindex $rc_geometry 0]} {
58 exec git repo-config gui.geometry $cfg_geometry
59 set repo_config(gui.geometry) [list $cfg_geometry]
63 proc error_popup {msg} {
69 append title [lindex \
70 [file split [file normalize [file dirname $gitdir]]] \
78 -title "$title: error" \
82 ######################################################################
86 if { [catch {set cdup [exec git rev-parse --show-cdup]} err]
87 || [catch {set gitdir [exec git rev-parse --git-dir]} err]} {
89 error_popup "Cannot find the git directory:\n\n$err"
97 if {$appname == {git-citool}} {
103 ######################################################################
112 set update_index_fd {}
114 set disable_on_lock [list]
115 set index_lock_type none
121 proc lock_index {type} {
122 global index_lock_type disable_on_lock
124 if {$index_lock_type == {none}} {
125 set index_lock_type $type
126 foreach w $disable_on_lock {
127 uplevel #0 $w disabled
130 } elseif {$index_lock_type == {begin-update} && $type == {update}} {
131 set index_lock_type $type
137 proc unlock_index {} {
138 global index_lock_type disable_on_lock
140 set index_lock_type none
141 foreach w $disable_on_lock {
146 ######################################################################
150 proc repository_state {hdvar ctvar} {
152 upvar $hdvar hd $ctvar ct
154 if {[catch {set hd [exec git rev-parse --verify HEAD]}]} {
156 } elseif {[file exists [file join $gitdir MERGE_HEAD]]} {
163 proc update_status {{final Ready.}} {
164 global HEAD PARENT commit_type
165 global ui_index ui_other ui_status_value ui_comm
166 global status_active file_states
167 global cfg_trust_mtime
169 if {$status_active || ![lock_index read]} return
171 repository_state new_HEAD new_type
172 if {$commit_type == {amend}
173 && $new_type == {normal}
174 && $new_HEAD == $HEAD} {
178 set commit_type $new_type
181 array unset file_states
183 if {![$ui_comm edit modified]
184 || [string trim [$ui_comm get 0.0 end]] == {}} {
185 if {[load_message GITGUI_MSG]} {
186 } elseif {[load_message MERGE_MSG]} {
187 } elseif {[load_message SQUASH_MSG]} {
189 $ui_comm edit modified false
193 if {$cfg_trust_mtime == {true}} {
194 update_status_stage2 {} $final
197 set ui_status_value {Refreshing file status...}
198 set fd_rf [open "| git update-index -q --unmerged --refresh" r]
199 fconfigure $fd_rf -blocking 0 -translation binary
200 fileevent $fd_rf readable [list update_status_stage2 $fd_rf $final]
204 proc update_status_stage2 {fd final} {
205 global gitdir PARENT commit_type
206 global ui_index ui_other ui_status_value ui_comm
208 global buf_rdi buf_rdf buf_rlo
212 if {![eof $fd]} return
216 set ls_others [list | git ls-files --others -z \
217 --exclude-per-directory=.gitignore]
218 set info_exclude [file join $gitdir info exclude]
219 if {[file readable $info_exclude]} {
220 lappend ls_others "--exclude-from=$info_exclude"
228 set ui_status_value {Scanning for modified files ...}
229 set fd_di [open "| git diff-index --cached -z $PARENT" r]
230 set fd_df [open "| git diff-files -z" r]
231 set fd_lo [open $ls_others r]
233 fconfigure $fd_di -blocking 0 -translation binary
234 fconfigure $fd_df -blocking 0 -translation binary
235 fconfigure $fd_lo -blocking 0 -translation binary
236 fileevent $fd_di readable [list read_diff_index $fd_di $final]
237 fileevent $fd_df readable [list read_diff_files $fd_df $final]
238 fileevent $fd_lo readable [list read_ls_others $fd_lo $final]
241 proc load_message {file} {
242 global gitdir ui_comm
244 set f [file join $gitdir $file]
245 if {[file isfile $f]} {
246 if {[catch {set fd [open $f r]}]} {
249 set content [string trim [read $fd]]
251 $ui_comm delete 0.0 end
252 $ui_comm insert end $content
258 proc read_diff_index {fd final} {
261 append buf_rdi [read $fd]
263 set n [string length $buf_rdi]
265 set z1 [string first "\0" $buf_rdi $c]
268 set z2 [string first "\0" $buf_rdi $z1]
274 [string range $buf_rdi $z1 $z2] \
275 [string index $buf_rdi [expr $z1 - 2]]_
279 set buf_rdi [string range $buf_rdi $c end]
284 status_eof $fd buf_rdi $final
287 proc read_diff_files {fd final} {
290 append buf_rdf [read $fd]
292 set n [string length $buf_rdf]
294 set z1 [string first "\0" $buf_rdf $c]
297 set z2 [string first "\0" $buf_rdf $z1]
303 [string range $buf_rdf $z1 $z2] \
304 _[string index $buf_rdf [expr $z1 - 2]]
308 set buf_rdf [string range $buf_rdf $c end]
313 status_eof $fd buf_rdf $final
316 proc read_ls_others {fd final} {
319 append buf_rlo [read $fd]
320 set pck [split $buf_rlo "\0"]
321 set buf_rlo [lindex $pck end]
322 foreach p [lrange $pck 0 end-1] {
325 status_eof $fd buf_rlo $final
328 proc status_eof {fd buf final} {
329 global status_active ui_status_value
336 if {[incr status_active -1] == 0} {
340 set ui_status_value $final
345 ######################################################################
350 global ui_diff ui_fname_value ui_fstatus_value ui_index ui_other
352 $ui_diff conf -state normal
353 $ui_diff delete 0.0 end
354 $ui_diff conf -state disabled
356 set ui_fname_value {}
357 set ui_fstatus_value {}
359 $ui_index tag remove in_diff 0.0 end
360 $ui_other tag remove in_diff 0.0 end
363 proc reshow_diff {} {
364 global ui_fname_value ui_status_value file_states
366 if {$ui_fname_value == {}
367 || [catch {set s $file_states($ui_fname_value)}]} {
370 show_diff $ui_fname_value
374 proc show_diff {path {w {}} {lno {}}} {
375 global file_states file_lists
376 global PARENT diff_3way diff_active
377 global ui_diff ui_fname_value ui_fstatus_value ui_status_value
379 if {$diff_active || ![lock_index read]} return
382 if {$w == {} || $lno == {}} {
383 foreach w [array names file_lists] {
384 set lno [lsearch -sorted $file_lists($w) $path]
391 if {$w != {} && $lno >= 1} {
392 $w tag add in_diff $lno.0 [expr $lno + 1].0
395 set s $file_states($path)
399 set ui_fname_value [escape_path $path]
400 set ui_fstatus_value [mapdesc $m $path]
401 set ui_status_value "Loading diff of [escape_path $path]..."
403 set cmd [list | git diff-index -p $PARENT -- $path]
408 set cmd [list | git diff-index -p -c $PARENT $path]
412 set fd [open $path r]
413 set content [read $fd]
418 set ui_status_value "Unable to display [escape_path $path]"
419 error_popup "Error loading file:\n\n$err"
422 $ui_diff conf -state normal
423 $ui_diff insert end $content
424 $ui_diff conf -state disabled
427 set ui_status_value {Ready.}
432 if {[catch {set fd [open $cmd r]} err]} {
435 set ui_status_value "Unable to display [escape_path $path]"
436 error_popup "Error loading diff:\n\n$err"
440 fconfigure $fd -blocking 0 -translation auto
441 fileevent $fd readable [list read_diff $fd]
444 proc read_diff {fd} {
445 global ui_diff ui_status_value diff_3way diff_active
447 while {[gets $fd line] >= 0} {
448 if {[string match {diff --git *} $line]} continue
449 if {[string match {diff --combined *} $line]} continue
450 if {[string match {--- *} $line]} continue
451 if {[string match {+++ *} $line]} continue
452 if {[string match index* $line]} {
453 if {[string first , $line] >= 0} {
458 $ui_diff conf -state normal
460 set x [string index $line 0]
465 default {set tags {}}
468 set x [string range $line 0 1]
470 default {set tags {}}
472 "++" {set tags dp; set x " +"}
473 " +" {set tags {di bold}; set x "++"}
474 "+ " {set tags dni; set x "-+"}
475 "--" {set tags dm; set x " -"}
476 " -" {set tags {dm bold}; set x "--"}
477 "- " {set tags di; set x "+-"}
478 default {set tags {}}
480 set line [string replace $line 0 1 $x]
482 $ui_diff insert end $line $tags
483 $ui_diff insert end "\n"
484 $ui_diff conf -state disabled
491 set ui_status_value {Ready.}
495 ######################################################################
499 proc load_last_commit {} {
500 global HEAD PARENT commit_type ui_comm
502 if {$commit_type == {amend}} return
503 if {$commit_type != {normal}} {
504 error_popup "Can't amend a $commit_type commit."
512 set fd [open "| git cat-file commit $HEAD" r]
513 while {[gets $fd line] > 0} {
514 if {[string match {parent *} $line]} {
515 set parent [string range $line 7 end]
519 set msg [string trim [read $fd]]
522 error_popup "Error loading commit data for amend:\n\n$err"
526 if {$parent_count == 0} {
527 set commit_type amend
531 } elseif {$parent_count == 1} {
532 set commit_type amend
534 $ui_comm delete 0.0 end
535 $ui_comm insert end $msg
536 $ui_comm edit modified false
540 error_popup {You can't amend a merge commit.}
545 proc commit_tree {} {
546 global tcl_platform HEAD gitdir commit_type file_states
547 global commit_active ui_status_value
550 if {$commit_active || ![lock_index update]} return
552 # -- Our in memory state should match the repository.
554 repository_state curHEAD cur_type
555 if {$commit_type == {amend}
556 && $cur_type == {normal}
557 && $curHEAD == $HEAD} {
558 } elseif {$commit_type != $cur_type || $HEAD != $curHEAD} {
559 error_popup {Last scanned state does not match repository state.
561 Its highly likely that another Git program modified the
562 repository since our last scan. A rescan is required
570 # -- At least one file should differ in the index.
573 foreach path [array names file_states] {
574 set s $file_states($path)
575 switch -glob -- [lindex $s 0] {
579 M? {set files_ready 1; break}
581 error_popup "Unmerged files cannot be committed.
583 File [escape_path $path] has merge conflicts.
584 You must resolve them and include the file before committing.
590 error_popup "Unknown file state [lindex $s 0] detected.
592 File [escape_path $path] cannot be committed by this program.
598 error_popup {No included files to commit.
600 You must include at least 1 file before you can commit.
606 # -- A message is required.
608 set msg [string trim [$ui_comm get 1.0 end]]
610 error_popup {Please supply a commit message.
612 A good commit message has the following format:
614 - First line: Describe in one sentance what you did.
616 - Remaining lines: Describe why this change is good.
622 # -- Ask the pre-commit hook for the go-ahead.
624 set pchook [file join $gitdir hooks pre-commit]
625 if {$tcl_platform(platform) == {windows} && [file isfile $pchook]} {
626 set pchook [list sh -c \
627 "if test -x \"$pchook\"; then exec \"$pchook\"; fi"]
628 } elseif {[file executable $pchook]} {
629 set pchook [list $pchook]
633 if {$pchook != {} && [catch {eval exec $pchook} err]} {
634 hook_failed_popup pre-commit $err
639 # -- Write the tree in the background.
642 set ui_status_value {Committing changes...}
644 set fd_wt [open "| git write-tree" r]
645 fileevent $fd_wt readable [list commit_stage2 $fd_wt $curHEAD $msg]
648 proc commit_stage2 {fd_wt curHEAD msg} {
649 global single_commit gitdir HEAD PARENT commit_type
650 global commit_active ui_status_value ui_comm
654 if {$tree_id == {} || [catch {close $fd_wt} err]} {
655 error_popup "write-tree failed:\n\n$err"
657 set ui_status_value {Commit failed.}
662 # -- Create the commit.
664 set cmd [list git commit-tree $tree_id]
666 lappend cmd -p $PARENT
668 if {$commit_type == {merge}} {
670 set fd_mh [open [file join $gitdir MERGE_HEAD] r]
671 while {[gets $fd_mh merge_head] >= 0} {
672 lappend cmd -p $merge_head
676 error_popup "Loading MERGE_HEAD failed:\n\n$err"
678 set ui_status_value {Commit failed.}
684 # git commit-tree writes to stderr during initial commit.
685 lappend cmd 2>/dev/null
688 if {[catch {set cmt_id [eval exec $cmd]} err]} {
689 error_popup "commit-tree failed:\n\n$err"
691 set ui_status_value {Commit failed.}
696 # -- Update the HEAD ref.
699 if {$commit_type != {normal}} {
700 append reflogm " ($commit_type)"
702 set i [string first "\n" $msg]
704 append reflogm {: } [string range $msg 0 [expr $i - 1]]
706 append reflogm {: } $msg
708 set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD]
709 if {[catch {eval exec $cmd} err]} {
710 error_popup "update-ref failed:\n\n$err"
712 set ui_status_value {Commit failed.}
717 # -- Cleanup after ourselves.
719 catch {file delete [file join $gitdir MERGE_HEAD]}
720 catch {file delete [file join $gitdir MERGE_MSG]}
721 catch {file delete [file join $gitdir SQUASH_MSG]}
722 catch {file delete [file join $gitdir GITGUI_MSG]}
724 # -- Let rerere do its thing.
726 if {[file isdirectory [file join $gitdir rr-cache]]} {
727 catch {exec git rerere}
730 $ui_comm delete 0.0 end
731 $ui_comm edit modified false
734 if {$single_commit} do_quit
736 # -- Update status without invoking any git commands.
739 set commit_type normal
743 foreach path [array names file_states] {
744 set s $file_states($path)
749 D? {set m _[string index $m 1]}
753 unset file_states($path)
755 lset file_states($path) 0 $m
762 set ui_status_value \
763 "Changes committed as [string range $cmt_id 0 7]."
766 ######################################################################
770 proc fetch_from {remote} {
771 set w [new_console "fetch $remote" \
772 "Fetching new changes from $remote"]
773 set cmd [list git fetch]
778 proc pull_remote {remote branch} {
779 global HEAD commit_type
782 if {![lock_index update]} return
784 # -- Our in memory state should match the repository.
786 repository_state curHEAD cur_type
787 if {$commit_type != $cur_type || $HEAD != $curHEAD} {
788 error_popup {Last scanned state does not match repository state.
790 Its highly likely that another Git program modified the
791 repository since our last scan. A rescan is required
792 before a pull can be started.
799 # -- No differences should exist before a pull.
801 if {[array size file_states] != 0} {
802 error_popup {Uncommitted but modified files are present.
804 You should not perform a pull with unmodified files in your working
805 directory as Git would be unable to recover from an incorrect merge.
807 Commit or throw away all changes before starting a pull operation.
813 set w [new_console "pull $remote $branch" \
814 "Pulling new changes from branch $branch in $remote"]
815 set cmd [list git pull]
818 console_exec $w $cmd [list post_pull_remote $remote $branch]
821 proc post_pull_remote {remote branch success} {
822 global HEAD PARENT commit_type
823 global ui_status_value
827 repository_state HEAD commit_type
829 set $ui_status_value {Ready.}
831 update_status "Conflicts detected while pulling $branch from $remote."
835 proc push_to {remote} {
836 set w [new_console "push $remote" \
837 "Pushing changes to $remote"]
838 set cmd [list git push]
843 ######################################################################
847 proc mapcol {state path} {
848 global all_cols ui_other
850 if {[catch {set r $all_cols($state)}]} {
851 puts "error: no column for state={$state} $path"
857 proc mapicon {state path} {
860 if {[catch {set r $all_icons($state)}]} {
861 puts "error: no icon for state={$state} $path"
867 proc mapdesc {state path} {
870 if {[catch {set r $all_descs($state)}]} {
871 puts "error: no desc for state={$state} $path"
877 proc escape_path {path} {
878 regsub -all "\n" $path "\\n" path
884 proc merge_state {path new_state} {
885 global file_states next_icon_id
887 set s0 [string index $new_state 0]
888 set s1 [string index $new_state 1]
890 if {[catch {set info $file_states($path)}]} {
892 set icon n[incr next_icon_id]
894 set state [lindex $info 0]
895 set icon [lindex $info 1]
899 set s0 [string index $state 0]
900 } elseif {$s0 == {*}} {
905 set s1 [string index $state 1]
906 } elseif {$s1 == {*}} {
910 set file_states($path) [list $s0$s1 $icon]
914 proc display_file {path state} {
915 global ui_index ui_other
916 global file_states file_lists status_active
918 set old_m [merge_state $path $state]
919 if {$status_active} return
921 set s $file_states($path)
922 set new_m [lindex $s 0]
923 set new_w [mapcol $new_m $path]
924 set old_w [mapcol $old_m $path]
925 set new_icon [mapicon $new_m $path]
927 if {$new_w != $old_w} {
928 set lno [lsearch -sorted $file_lists($old_w) $path]
931 $old_w conf -state normal
932 $old_w delete $lno.0 [expr $lno + 1].0
933 $old_w conf -state disabled
936 lappend file_lists($new_w) $path
937 set file_lists($new_w) [lsort $file_lists($new_w)]
938 set lno [lsearch -sorted $file_lists($new_w) $path]
940 $new_w conf -state normal
941 $new_w image create $lno.0 \
942 -align center -padx 5 -pady 1 \
943 -name [lindex $s 1] \
945 $new_w insert $lno.1 "[escape_path $path]\n"
946 $new_w conf -state disabled
947 } elseif {$new_icon != [mapicon $old_m $path]} {
948 $new_w conf -state normal
949 $new_w image conf [lindex $s 1] -image $new_icon
950 $new_w conf -state disabled
954 proc display_all_files {} {
955 global ui_index ui_other file_states file_lists
957 $ui_index conf -state normal
958 $ui_other conf -state normal
960 $ui_index delete 0.0 end
961 $ui_other delete 0.0 end
963 array unset file_lists
964 foreach path [lsort [array names file_states]] {
965 set s $file_states($path)
967 set w [mapcol $m $path]
968 lappend file_lists($w) $path
969 $w image create end \
970 -align center -padx 5 -pady 1 \
971 -name [lindex $s 1] \
972 -image [mapicon $m $path]
973 $w insert end "[escape_path $path]\n"
976 $ui_index conf -state disabled
977 $ui_other conf -state disabled
980 proc with_update_index {body} {
981 global update_index_fd
983 if {$update_index_fd == {}} {
984 if {![lock_index update]} return
985 set update_index_fd [open \
986 "| git update-index --add --remove -z --stdin" \
988 fconfigure $update_index_fd -translation binary
990 close $update_index_fd
991 set update_index_fd {}
998 proc update_index {path} {
999 global update_index_fd
1001 if {$update_index_fd == {}} {
1002 error {not in with_update_index}
1004 puts -nonewline $update_index_fd "$path\0"
1008 proc toggle_mode {path} {
1009 global file_states ui_fname_value
1011 set s $file_states($path)
1024 with_update_index {update_index $path}
1025 display_file $path $new
1026 if {$ui_fname_value == $path} {
1031 ######################################################################
1033 ## remote management
1035 proc load_all_remotes {} {
1036 global gitdir all_remotes repo_config
1038 set all_remotes [list]
1039 set rm_dir [file join $gitdir remotes]
1040 if {[file isdirectory $rm_dir]} {
1041 set all_remotes [concat $all_remotes [glob \
1045 -directory $rm_dir *]]
1048 foreach line [array names repo_config remote.*.url] {
1049 if {[regexp ^remote\.(.*)\.url\$ $line line name]} {
1050 lappend all_remotes $name
1054 set all_remotes [lsort -unique $all_remotes]
1057 proc populate_remote_menu {m pfx op} {
1058 global all_remotes font_ui
1060 foreach remote $all_remotes {
1061 $m add command -label "$pfx $remote..." \
1062 -command [list $op $remote] \
1067 proc populate_pull_menu {m} {
1068 global gitdir repo_config all_remotes font_ui disable_on_lock
1070 foreach remote $all_remotes {
1072 if {[array get repo_config remote.$remote.url] != {}} {
1073 if {[array get repo_config remote.$remote.fetch] != {}} {
1074 regexp {^([^:]+):} \
1075 [lindex $repo_config(remote.$remote.fetch) 0] \
1080 set fd [open [file join $gitdir remotes $remote] r]
1081 while {[gets $fd line] >= 0} {
1082 if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} {
1091 regsub ^refs/heads/ $rb {} rb_short
1092 if {$rb_short != {}} {
1094 -label "Branch $rb_short from $remote..." \
1095 -command [list pull_remote $remote $rb] \
1097 lappend disable_on_lock \
1098 [list $m entryconf [$m index last] -state]
1103 ######################################################################
1108 #define mask_width 14
1109 #define mask_height 15
1110 static unsigned char mask_bits[] = {
1111 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1112 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1113 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1116 image create bitmap file_plain -background white -foreground black -data {
1117 #define plain_width 14
1118 #define plain_height 15
1119 static unsigned char plain_bits[] = {
1120 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1121 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1122 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1123 } -maskdata $filemask
1125 image create bitmap file_mod -background white -foreground blue -data {
1126 #define mod_width 14
1127 #define mod_height 15
1128 static unsigned char mod_bits[] = {
1129 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1130 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1131 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1132 } -maskdata $filemask
1134 image create bitmap file_fulltick -background white -foreground "#007000" -data {
1135 #define file_fulltick_width 14
1136 #define file_fulltick_height 15
1137 static unsigned char file_fulltick_bits[] = {
1138 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1139 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1140 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1141 } -maskdata $filemask
1143 image create bitmap file_parttick -background white -foreground "#005050" -data {
1144 #define parttick_width 14
1145 #define parttick_height 15
1146 static unsigned char parttick_bits[] = {
1147 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1148 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1149 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1150 } -maskdata $filemask
1152 image create bitmap file_question -background white -foreground black -data {
1153 #define file_question_width 14
1154 #define file_question_height 15
1155 static unsigned char file_question_bits[] = {
1156 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1157 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1158 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1159 } -maskdata $filemask
1161 image create bitmap file_removed -background white -foreground red -data {
1162 #define file_removed_width 14
1163 #define file_removed_height 15
1164 static unsigned char file_removed_bits[] = {
1165 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1166 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1167 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1168 } -maskdata $filemask
1170 image create bitmap file_merge -background white -foreground blue -data {
1171 #define file_merge_width 14
1172 #define file_merge_height 15
1173 static unsigned char file_merge_bits[] = {
1174 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1175 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1176 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1177 } -maskdata $filemask
1179 set ui_index .vpane.files.index.list
1180 set ui_other .vpane.files.other.list
1181 set max_status_desc 0
1183 {__ i plain "Unmodified"}
1184 {_M i mod "Modified"}
1185 {M_ i fulltick "Checked in"}
1186 {MM i parttick "Partially included"}
1188 {_O o plain "Untracked"}
1189 {A_ o fulltick "Added"}
1190 {AM o parttick "Partially added"}
1191 {AD o question "Added (but now gone)"}
1193 {_D i question "Missing"}
1194 {D_ i removed "Removed"}
1195 {DD i removed "Removed"}
1196 {DO i removed "Removed (still exists)"}
1198 {UM i merge "Merge conflicts"}
1199 {U_ i merge "Merge conflicts"}
1201 if {$max_status_desc < [string length [lindex $i 3]]} {
1202 set max_status_desc [string length [lindex $i 3]]
1204 if {[lindex $i 1] == {i}} {
1205 set all_cols([lindex $i 0]) $ui_index
1207 set all_cols([lindex $i 0]) $ui_other
1209 set all_icons([lindex $i 0]) file_[lindex $i 2]
1210 set all_descs([lindex $i 0]) [lindex $i 3]
1214 ######################################################################
1218 proc hook_failed_popup {hook msg} {
1219 global gitdir font_ui font_diff appname
1226 label $w.m.l1 -text "$hook hook failed:" \
1229 -font [concat $font_ui bold]
1231 -background white -borderwidth 1 \
1233 -width 80 -height 10 \
1235 -yscrollcommand [list $w.m.sby set]
1237 -text {You must correct the above errors before committing.} \
1240 -font [concat $font_ui bold]
1241 scrollbar $w.m.sby -command [list $w.m.t yview]
1242 pack $w.m.l1 -side top -fill x
1243 pack $w.m.l2 -side bottom -fill x
1244 pack $w.m.sby -side right -fill y
1245 pack $w.m.t -side left -fill both -expand 1
1246 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1248 $w.m.t insert 1.0 $msg
1249 $w.m.t conf -state disabled
1251 button $w.ok -text OK \
1254 -command "destroy $w"
1255 pack $w.ok -side bottom
1257 bind $w <Visibility> "grab $w; focus $w"
1258 bind $w <Key-Return> "destroy $w"
1259 wm title $w "$appname ([lindex [file split \
1260 [file normalize [file dirname $gitdir]]] \
1265 set next_console_id 0
1267 proc new_console {short_title long_title} {
1268 global next_console_id console_data
1269 set w .console[incr next_console_id]
1270 set console_data($w) [list $short_title $long_title]
1271 return [console_init $w]
1274 proc console_init {w} {
1275 global console_cr console_data
1276 global gitdir appname font_ui font_diff
1278 set console_cr($w) 1.0
1281 label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
1284 -font [concat $font_ui bold]
1286 -background white -borderwidth 1 \
1288 -width 80 -height 10 \
1291 -yscrollcommand [list $w.m.sby set]
1292 label $w.m.s -anchor w \
1294 -font [concat $font_ui bold]
1295 scrollbar $w.m.sby -command [list $w.m.t yview]
1296 pack $w.m.l1 -side top -fill x
1297 pack $w.m.s -side bottom -fill x
1298 pack $w.m.sby -side right -fill y
1299 pack $w.m.t -side left -fill both -expand 1
1300 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1302 button $w.ok -text {Running...} \
1306 -command "destroy $w"
1307 pack $w.ok -side bottom
1309 bind $w <Visibility> "focus $w"
1310 wm title $w "$appname ([lindex [file split \
1311 [file normalize [file dirname $gitdir]]] \
1312 end]): [lindex $console_data($w) 0]"
1316 proc console_exec {w cmd {after {}}} {
1319 # -- Windows tosses the enviroment when we exec our child.
1320 # But most users need that so we have to relogin. :-(
1322 if {$tcl_platform(platform) == {windows}} {
1323 set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
1326 # -- Tcl won't let us redirect both stdout and stderr to
1327 # the same pipe. So pass it through cat...
1329 set cmd [concat | $cmd |& cat]
1331 set fd_f [open $cmd r]
1332 fconfigure $fd_f -blocking 0 -translation binary
1333 fileevent $fd_f readable [list console_read $w $fd_f $after]
1336 proc console_read {w fd after} {
1337 global console_cr console_data
1341 if {![winfo exists $w]} {console_init $w}
1342 $w.m.t conf -state normal
1344 set n [string length $buf]
1346 set cr [string first "\r" $buf $c]
1347 set lf [string first "\n" $buf $c]
1348 if {$cr < 0} {set cr [expr $n + 1]}
1349 if {$lf < 0} {set lf [expr $n + 1]}
1352 $w.m.t insert end [string range $buf $c $lf]
1353 set console_cr($w) [$w.m.t index {end -1c}]
1357 $w.m.t delete $console_cr($w) end
1358 $w.m.t insert end "\n"
1359 $w.m.t insert end [string range $buf $c $cr]
1364 $w.m.t conf -state disabled
1368 fconfigure $fd -blocking 1
1370 if {[catch {close $fd}]} {
1371 if {![winfo exists $w]} {console_init $w}
1372 $w.m.s conf -background red -text {Error: Command Failed}
1373 $w.ok conf -text Close
1374 $w.ok conf -state normal
1376 } elseif {[winfo exists $w]} {
1377 $w.m.s conf -background green -text {Success}
1378 $w.ok conf -text Close
1379 $w.ok conf -state normal
1382 array unset console_cr $w
1383 array unset console_data $w
1385 uplevel #0 $after $ok
1389 fconfigure $fd -blocking 0
1392 ######################################################################
1396 set starting_gitk_msg {Please wait... Starting gitk...}
1399 global tcl_platform ui_status_value starting_gitk_msg
1401 set ui_status_value $starting_gitk_msg
1403 if {$ui_status_value == $starting_gitk_msg} {
1404 set ui_status_value {Ready.}
1408 if {$tcl_platform(platform) == {windows}} {
1416 set w [new_console "repack" "Repacking the object database"]
1417 set cmd [list git repack]
1420 console_exec $w $cmd
1426 global gitdir ui_comm quitting
1428 if {$quitting} return
1431 set save [file join $gitdir GITGUI_MSG]
1432 set msg [string trim [$ui_comm get 0.0 end]]
1433 if {[$ui_comm edit modified] && $msg != {}} {
1435 set fd [open $save w]
1436 puts $fd [string trim [$ui_comm get 0.0 end]]
1439 } elseif {$msg == {} && [file exists $save]} {
1451 proc do_include_all {} {
1452 global update_active ui_status_value
1454 if {$update_active || ![lock_index begin-update]} return
1457 set ui_status_value {Including all modified files...}
1460 foreach path [array names file_states] {
1461 set s $file_states($path)
1467 _D {toggle_mode $path}
1472 set ui_status_value {Ready.}
1476 set GIT_COMMITTER_IDENT {}
1478 proc do_signoff {} {
1479 global ui_comm GIT_COMMITTER_IDENT
1481 if {$GIT_COMMITTER_IDENT == {}} {
1482 if {[catch {set me [exec git var GIT_COMMITTER_IDENT]} err]} {
1483 error_popup "Unable to obtain your identity:\n\n$err"
1486 if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
1487 $me me GIT_COMMITTER_IDENT]} {
1488 error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me"
1493 set str "Signed-off-by: $GIT_COMMITTER_IDENT"
1494 if {[$ui_comm get {end -1c linestart} {end -1c}] != $str} {
1495 $ui_comm edit separator
1496 $ui_comm insert end "\n$str"
1497 $ui_comm edit separator
1502 proc do_amend_last {} {
1510 # shift == 1: left click
1512 proc click {w x y shift wx wy} {
1513 global ui_index ui_other file_lists
1515 set pos [split [$w index @$x,$y] .]
1516 set lno [lindex $pos 0]
1517 set col [lindex $pos 1]
1518 set path [lindex $file_lists($w) [expr $lno - 1]]
1519 if {$path == {}} return
1521 if {$col > 0 && $shift == 1} {
1522 show_diff $path $w $lno
1526 proc unclick {w x y} {
1529 set pos [split [$w index @$x,$y] .]
1530 set lno [lindex $pos 0]
1531 set col [lindex $pos 1]
1532 set path [lindex $file_lists($w) [expr $lno - 1]]
1533 if {$path == {}} return
1540 ######################################################################
1544 set font_ui {Helvetica 10}
1545 set font_diff {Courier 10}
1546 set maincursor [. cget -cursor]
1548 switch -glob -- "$tcl_platform(platform),$tcl_platform(os)" {
1549 windows,* {set M1B Control; set M1T Ctrl}
1550 unix,Darwin {set M1B M1; set M1T Cmd}
1551 default {set M1B M1; set M1T M1}
1555 menu .mbar -tearoff 0
1556 .mbar add cascade -label Project -menu .mbar.project
1557 .mbar add cascade -label Edit -menu .mbar.edit
1558 .mbar add cascade -label Commit -menu .mbar.commit
1559 .mbar add cascade -label Fetch -menu .mbar.fetch
1560 .mbar add cascade -label Pull -menu .mbar.pull
1561 .mbar add cascade -label Push -menu .mbar.push
1562 .mbar add cascade -label Options -menu .mbar.options
1563 . configure -menu .mbar
1567 .mbar.project add command -label Visualize \
1570 .mbar.project add command -label {Repack Database} \
1571 -command do_repack \
1573 .mbar.project add command -label Quit \
1575 -accelerator $M1T-Q \
1581 .mbar.edit add command -label Undo \
1582 -command {catch {[focus] edit undo}} \
1583 -accelerator $M1T-Z \
1585 .mbar.edit add command -label Redo \
1586 -command {catch {[focus] edit redo}} \
1587 -accelerator $M1T-Y \
1589 .mbar.edit add separator
1590 .mbar.edit add command -label Cut \
1591 -command {catch {tk_textCut [focus]}} \
1592 -accelerator $M1T-X \
1594 .mbar.edit add command -label Copy \
1595 -command {catch {tk_textCopy [focus]}} \
1596 -accelerator $M1T-C \
1598 .mbar.edit add command -label Paste \
1599 -command {catch {tk_textPaste [focus]; [focus] see insert}} \
1600 -accelerator $M1T-V \
1602 .mbar.edit add command -label Delete \
1603 -command {catch {[focus] delete sel.first sel.last}} \
1606 .mbar.edit add separator
1607 .mbar.edit add command -label {Select All} \
1608 -command {catch {[focus] tag add sel 0.0 end}} \
1609 -accelerator $M1T-A \
1614 .mbar.commit add command -label Rescan \
1615 -command do_rescan \
1618 lappend disable_on_lock \
1619 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1620 .mbar.commit add command -label {Amend Last Commit} \
1621 -command do_amend_last \
1623 lappend disable_on_lock \
1624 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1625 .mbar.commit add command -label {Include All Files} \
1626 -command do_include_all \
1627 -accelerator $M1T-I \
1629 lappend disable_on_lock \
1630 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1631 .mbar.commit add command -label {Sign Off} \
1632 -command do_signoff \
1633 -accelerator $M1T-S \
1635 .mbar.commit add command -label Commit \
1636 -command do_commit \
1637 -accelerator $M1T-Return \
1639 lappend disable_on_lock \
1640 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1653 .mbar.options add checkbutton \
1654 -label {Trust File Modification Timestamps} \
1657 -variable cfg_trust_mtime
1659 # -- Main Window Layout
1660 panedwindow .vpane -orient vertical
1661 panedwindow .vpane.files -orient horizontal
1662 .vpane add .vpane.files -sticky nsew -height 100 -width 400
1663 pack .vpane -anchor n -side top -fill both -expand 1
1665 # -- Index File List
1666 frame .vpane.files.index -height 100 -width 400
1667 label .vpane.files.index.title -text {Modified Files} \
1670 text $ui_index -background white -borderwidth 0 \
1671 -width 40 -height 10 \
1673 -yscrollcommand {.vpane.files.index.sb set} \
1674 -cursor $maincursor \
1676 scrollbar .vpane.files.index.sb -command [list $ui_index yview]
1677 pack .vpane.files.index.title -side top -fill x
1678 pack .vpane.files.index.sb -side right -fill y
1679 pack $ui_index -side left -fill both -expand 1
1680 .vpane.files add .vpane.files.index -sticky nsew
1682 # -- Other (Add) File List
1683 frame .vpane.files.other -height 100 -width 100
1684 label .vpane.files.other.title -text {Untracked Files} \
1687 text $ui_other -background white -borderwidth 0 \
1688 -width 40 -height 10 \
1690 -yscrollcommand {.vpane.files.other.sb set} \
1691 -cursor $maincursor \
1693 scrollbar .vpane.files.other.sb -command [list $ui_other yview]
1694 pack .vpane.files.other.title -side top -fill x
1695 pack .vpane.files.other.sb -side right -fill y
1696 pack $ui_other -side left -fill both -expand 1
1697 .vpane.files add .vpane.files.other -sticky nsew
1699 $ui_index tag conf in_diff -font [concat $font_ui bold]
1700 $ui_other tag conf in_diff -font [concat $font_ui bold]
1702 # -- Diff and Commit Area
1703 frame .vpane.lower -height 400 -width 400
1704 frame .vpane.lower.commarea
1705 frame .vpane.lower.diff -relief sunken -borderwidth 1
1706 pack .vpane.lower.commarea -side top -fill x
1707 pack .vpane.lower.diff -side bottom -fill both -expand 1
1708 .vpane add .vpane.lower -stick nsew
1710 # -- Commit Area Buttons
1711 frame .vpane.lower.commarea.buttons
1712 label .vpane.lower.commarea.buttons.l -text {} \
1716 pack .vpane.lower.commarea.buttons.l -side top -fill x
1717 pack .vpane.lower.commarea.buttons -side left -fill y
1719 button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
1720 -command do_rescan \
1722 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
1723 lappend disable_on_lock {.vpane.lower.commarea.buttons.rescan conf -state}
1725 button .vpane.lower.commarea.buttons.amend -text {Amend Last} \
1726 -command do_amend_last \
1728 pack .vpane.lower.commarea.buttons.amend -side top -fill x
1729 lappend disable_on_lock {.vpane.lower.commarea.buttons.amend conf -state}
1731 button .vpane.lower.commarea.buttons.incall -text {Include All} \
1732 -command do_include_all \
1734 pack .vpane.lower.commarea.buttons.incall -side top -fill x
1735 lappend disable_on_lock {.vpane.lower.commarea.buttons.incall conf -state}
1737 button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
1738 -command do_signoff \
1740 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
1742 button .vpane.lower.commarea.buttons.commit -text {Commit} \
1743 -command do_commit \
1745 pack .vpane.lower.commarea.buttons.commit -side top -fill x
1746 lappend disable_on_lock {.vpane.lower.commarea.buttons.commit conf -state}
1748 # -- Commit Message Buffer
1749 frame .vpane.lower.commarea.buffer
1750 set ui_comm .vpane.lower.commarea.buffer.t
1751 set ui_coml .vpane.lower.commarea.buffer.l
1752 label $ui_coml -text {Commit Message:} \
1756 trace add variable commit_type write {uplevel #0 {
1757 switch -glob $commit_type \
1758 initial {$ui_coml conf -text {Initial Commit Message:}} \
1759 amend {$ui_coml conf -text {Amended Commit Message:}} \
1760 merge {$ui_coml conf -text {Merge Commit Message:}} \
1761 * {$ui_coml conf -text {Commit Message:}}
1763 text $ui_comm -background white -borderwidth 1 \
1766 -autoseparators true \
1768 -width 75 -height 9 -wrap none \
1770 -yscrollcommand {.vpane.lower.commarea.buffer.sby set} \
1772 scrollbar .vpane.lower.commarea.buffer.sby -command [list $ui_comm yview]
1773 pack $ui_coml -side top -fill x
1774 pack .vpane.lower.commarea.buffer.sby -side right -fill y
1775 pack $ui_comm -side left -fill y
1776 pack .vpane.lower.commarea.buffer -side left -fill y
1779 set ui_fname_value {}
1780 set ui_fstatus_value {}
1781 frame .vpane.lower.diff.header -background orange
1782 label .vpane.lower.diff.header.l1 -text {File:} \
1783 -background orange \
1785 label .vpane.lower.diff.header.l2 -textvariable ui_fname_value \
1786 -background orange \
1790 label .vpane.lower.diff.header.l3 -text {Status:} \
1791 -background orange \
1793 label .vpane.lower.diff.header.l4 -textvariable ui_fstatus_value \
1794 -background orange \
1795 -width $max_status_desc \
1799 pack .vpane.lower.diff.header.l1 -side left
1800 pack .vpane.lower.diff.header.l2 -side left -fill x
1801 pack .vpane.lower.diff.header.l4 -side right
1802 pack .vpane.lower.diff.header.l3 -side right
1805 frame .vpane.lower.diff.body
1806 set ui_diff .vpane.lower.diff.body.t
1807 text $ui_diff -background white -borderwidth 0 \
1808 -width 80 -height 15 -wrap none \
1810 -xscrollcommand {.vpane.lower.diff.body.sbx set} \
1811 -yscrollcommand {.vpane.lower.diff.body.sby set} \
1812 -cursor $maincursor \
1814 scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
1815 -command [list $ui_diff xview]
1816 scrollbar .vpane.lower.diff.body.sby -orient vertical \
1817 -command [list $ui_diff yview]
1818 pack .vpane.lower.diff.body.sbx -side bottom -fill x
1819 pack .vpane.lower.diff.body.sby -side right -fill y
1820 pack $ui_diff -side left -fill both -expand 1
1821 pack .vpane.lower.diff.header -side top -fill x
1822 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
1824 $ui_diff tag conf dm -foreground red
1825 $ui_diff tag conf dp -foreground blue
1826 $ui_diff tag conf da -font [concat $font_diff bold]
1827 $ui_diff tag conf di -foreground "#00a000"
1828 $ui_diff tag conf dni -foreground "#a000a0"
1829 $ui_diff tag conf bold -font [concat $font_diff bold]
1832 set ui_status_value {Initializing...}
1833 label .status -textvariable ui_status_value \
1839 pack .status -anchor w -side bottom -fill x
1843 set gm [lindex $repo_config(gui.geometry) 0]
1844 wm geometry . [lindex $gm 0]
1845 .vpane sash place 0 \
1846 [lindex [.vpane sash coord 0] 0] \
1848 .vpane.files sash place 0 \
1850 [lindex [.vpane.files sash coord 0] 1]
1855 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
1856 bind $ui_comm <$M1B-Key-i> {do_include_all;break}
1857 bind $ui_comm <$M1B-Key-I> {do_include_all;break}
1858 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
1859 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
1860 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
1861 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
1862 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
1863 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
1864 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
1865 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
1867 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
1868 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
1869 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
1870 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
1871 bind $ui_diff <$M1B-Key-v> {break}
1872 bind $ui_diff <$M1B-Key-V> {break}
1873 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
1874 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
1875 bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break}
1876 bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break}
1877 bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break}
1878 bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break}
1880 bind . <Destroy> do_quit
1881 bind all <Key-F5> do_rescan
1882 bind all <$M1B-Key-r> do_rescan
1883 bind all <$M1B-Key-R> do_rescan
1884 bind . <$M1B-Key-s> do_signoff
1885 bind . <$M1B-Key-S> do_signoff
1886 bind . <$M1B-Key-i> do_include_all
1887 bind . <$M1B-Key-I> do_include_all
1888 bind . <$M1B-Key-Return> do_commit
1889 bind all <$M1B-Key-q> do_quit
1890 bind all <$M1B-Key-Q> do_quit
1891 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
1892 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
1893 foreach i [list $ui_index $ui_other] {
1894 bind $i <Button-1> {click %W %x %y 1 %X %Y; break}
1895 bind $i <Button-3> {click %W %x %y 3 %X %Y; break}
1896 bind $i <ButtonRelease-1> {unclick %W %x %y; break}
1900 wm title . "$appname ([file normalize [file dirname $gitdir]])"
1901 focus -force $ui_comm
1903 populate_remote_menu .mbar.fetch From fetch_from
1904 populate_remote_menu .mbar.push To push_to
1905 populate_pull_menu .mbar.pull