2 # Tcl ignores the next line -*- tcl -*- \
6 Copyright © 2006, 2007 Shawn Pearce, Paul Mackerras.
10 This program is free software; it may be used, copied, modified
11 and distributed under the terms of the GNU General Public Licence,
12 either version 2, or (at your option) any later version.}
14 set appvers {@@GITGUI_VERSION@@}
15 set appname [lindex [file split $argv0] end]
18 ######################################################################
22 proc is_many_config {name} {
23 switch -glob -- $name {
32 proc load_config {include_global} {
33 global repo_config global_config default_config
35 array unset global_config
36 if {$include_global} {
38 set fd_rc [open "| git repo-config --global --list" r]
39 while {[gets $fd_rc line] >= 0} {
40 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
41 if {[is_many_config $name]} {
42 lappend global_config($name) $value
44 set global_config($name) $value
52 array unset repo_config
54 set fd_rc [open "| git repo-config --list" r]
55 while {[gets $fd_rc line] >= 0} {
56 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
57 if {[is_many_config $name]} {
58 lappend repo_config($name) $value
60 set repo_config($name) $value
67 foreach name [array names default_config] {
68 if {[catch {set v $global_config($name)}]} {
69 set global_config($name) $default_config($name)
71 if {[catch {set v $repo_config($name)}]} {
72 set repo_config($name) $default_config($name)
78 global default_config font_descs
79 global repo_config global_config
80 global repo_config_new global_config_new
82 foreach option $font_descs {
83 set name [lindex $option 0]
84 set font [lindex $option 1]
85 font configure $font \
86 -family $global_config_new(gui.$font^^family) \
87 -size $global_config_new(gui.$font^^size)
88 font configure ${font}bold \
89 -family $global_config_new(gui.$font^^family) \
90 -size $global_config_new(gui.$font^^size)
91 set global_config_new(gui.$name) [font configure $font]
92 unset global_config_new(gui.$font^^family)
93 unset global_config_new(gui.$font^^size)
96 foreach name [array names default_config] {
97 set value $global_config_new($name)
98 if {$value ne $global_config($name)} {
99 if {$value eq $default_config($name)} {
100 catch {exec git repo-config --global --unset $name}
102 regsub -all "\[{}\]" $value {"} value
103 exec git repo-config --global $name $value
105 set global_config($name) $value
106 if {$value eq $repo_config($name)} {
107 catch {exec git repo-config --unset $name}
108 set repo_config($name) $value
113 foreach name [array names default_config] {
114 set value $repo_config_new($name)
115 if {$value ne $repo_config($name)} {
116 if {$value eq $global_config($name)} {
117 catch {exec git repo-config --unset $name}
119 regsub -all "\[{}\]" $value {"} value
120 exec git repo-config $name $value
122 set repo_config($name) $value
127 proc error_popup {msg} {
128 global gitdir appname
133 append title [lindex \
134 [file split [file normalize [file dirname $gitdir]]] \
138 set cmd [list tk_messageBox \
141 -title "$title: error" \
143 if {[winfo ismapped .]} {
144 lappend cmd -parent .
149 proc warn_popup {msg} {
150 global gitdir appname
155 append title [lindex \
156 [file split [file normalize [file dirname $gitdir]]] \
160 set cmd [list tk_messageBox \
163 -title "$title: warning" \
165 if {[winfo ismapped .]} {
166 lappend cmd -parent .
171 proc info_popup {msg} {
172 global gitdir appname
177 append title [lindex \
178 [file split [file normalize [file dirname $gitdir]]] \
190 ######################################################################
194 if { [catch {set gitdir $env(GIT_DIR)}]
195 && [catch {set gitdir [exec git rev-parse --git-dir]} err]} {
196 catch {wm withdraw .}
197 error_popup "Cannot find the git directory:\n\n$err"
200 if {![file isdirectory $gitdir]} {
201 catch {wm withdraw .}
202 error_popup "Git directory not found:\n\n$gitdir"
205 if {[lindex [file split $gitdir] end] ne {.git}} {
206 catch {wm withdraw .}
207 error_popup "Cannot use funny .git directory:\n\n$gitdir"
210 if {[catch {cd [file dirname $gitdir]} err]} {
211 catch {wm withdraw .}
212 error_popup "No working directory [file dirname $gitdir]:\n\n$err"
217 if {$appname eq {git-citool}} {
221 ######################################################################
229 set disable_on_lock [list]
230 set index_lock_type none
232 proc lock_index {type} {
233 global index_lock_type disable_on_lock
235 if {$index_lock_type eq {none}} {
236 set index_lock_type $type
237 foreach w $disable_on_lock {
238 uplevel #0 $w disabled
241 } elseif {$index_lock_type eq "begin-$type"} {
242 set index_lock_type $type
248 proc unlock_index {} {
249 global index_lock_type disable_on_lock
251 set index_lock_type none
252 foreach w $disable_on_lock {
257 ######################################################################
261 proc repository_state {ctvar hdvar mhvar} {
262 global gitdir current_branch
263 upvar $ctvar ct $hdvar hd $mhvar mh
267 if {[catch {set current_branch [exec git symbolic-ref HEAD]}]} {
268 set current_branch {}
270 regsub ^refs/((heads|tags|remotes)/)? \
276 if {[catch {set hd [exec git rev-parse --verify HEAD]}]} {
282 set merge_head [file join $gitdir MERGE_HEAD]
283 if {[file exists $merge_head]} {
285 set fd_mh [open $merge_head r]
286 while {[gets $fd_mh line] >= 0} {
297 global PARENT empty_tree
299 set p [lindex $PARENT 0]
303 if {$empty_tree eq {}} {
304 set empty_tree [exec git mktree << {}]
309 proc rescan {after} {
310 global HEAD PARENT MERGE_HEAD commit_type
311 global ui_index ui_other ui_status_value ui_comm
312 global rescan_active file_states
315 if {$rescan_active > 0 || ![lock_index read]} return
317 repository_state newType newHEAD newMERGE_HEAD
318 if {[string match amend* $commit_type]
319 && $newType eq {normal}
320 && $newHEAD eq $HEAD} {
324 set MERGE_HEAD $newMERGE_HEAD
325 set commit_type $newType
328 array unset file_states
330 if {![$ui_comm edit modified]
331 || [string trim [$ui_comm get 0.0 end]] eq {}} {
332 if {[load_message GITGUI_MSG]} {
333 } elseif {[load_message MERGE_MSG]} {
334 } elseif {[load_message SQUASH_MSG]} {
337 $ui_comm edit modified false
340 if {$repo_config(gui.trustmtime) eq {true}} {
341 rescan_stage2 {} $after
344 set ui_status_value {Refreshing file status...}
345 set cmd [list git update-index]
347 lappend cmd --unmerged
348 lappend cmd --ignore-missing
349 lappend cmd --refresh
350 set fd_rf [open "| $cmd" r]
351 fconfigure $fd_rf -blocking 0 -translation binary
352 fileevent $fd_rf readable \
353 [list rescan_stage2 $fd_rf $after]
357 proc rescan_stage2 {fd after} {
358 global gitdir ui_status_value
359 global rescan_active buf_rdi buf_rdf buf_rlo
363 if {![eof $fd]} return
367 set ls_others [list | git ls-files --others -z \
368 --exclude-per-directory=.gitignore]
369 set info_exclude [file join $gitdir info exclude]
370 if {[file readable $info_exclude]} {
371 lappend ls_others "--exclude-from=$info_exclude"
379 set ui_status_value {Scanning for modified files ...}
380 set fd_di [open "| git diff-index --cached -z [PARENT]" r]
381 set fd_df [open "| git diff-files -z" r]
382 set fd_lo [open $ls_others r]
384 fconfigure $fd_di -blocking 0 -translation binary
385 fconfigure $fd_df -blocking 0 -translation binary
386 fconfigure $fd_lo -blocking 0 -translation binary
387 fileevent $fd_di readable [list read_diff_index $fd_di $after]
388 fileevent $fd_df readable [list read_diff_files $fd_df $after]
389 fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
392 proc load_message {file} {
393 global gitdir ui_comm
395 set f [file join $gitdir $file]
396 if {[file isfile $f]} {
397 if {[catch {set fd [open $f r]}]} {
400 set content [string trim [read $fd]]
402 $ui_comm delete 0.0 end
403 $ui_comm insert end $content
409 proc read_diff_index {fd after} {
412 append buf_rdi [read $fd]
414 set n [string length $buf_rdi]
416 set z1 [string first "\0" $buf_rdi $c]
419 set z2 [string first "\0" $buf_rdi $z1]
423 set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
425 [string range $buf_rdi $z1 [expr {$z2 - 1}]] \
427 [list [lindex $i 0] [lindex $i 2]] \
433 set buf_rdi [string range $buf_rdi $c end]
438 rescan_done $fd buf_rdi $after
441 proc read_diff_files {fd after} {
444 append buf_rdf [read $fd]
446 set n [string length $buf_rdf]
448 set z1 [string first "\0" $buf_rdf $c]
451 set z2 [string first "\0" $buf_rdf $z1]
455 set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
457 [string range $buf_rdf $z1 [expr {$z2 - 1}]] \
460 [list [lindex $i 0] [lindex $i 2]]
465 set buf_rdf [string range $buf_rdf $c end]
470 rescan_done $fd buf_rdf $after
473 proc read_ls_others {fd after} {
476 append buf_rlo [read $fd]
477 set pck [split $buf_rlo "\0"]
478 set buf_rlo [lindex $pck end]
479 foreach p [lrange $pck 0 end-1] {
482 rescan_done $fd buf_rlo $after
485 proc rescan_done {fd buf after} {
487 global file_states repo_config
490 if {![eof $fd]} return
493 if {[incr rescan_active -1] > 0} return
499 if {$repo_config(gui.partialinclude) ne {true}} {
501 foreach path [array names file_states] {
502 switch -- [lindex $file_states($path) 0] {
504 M? {lappend pathList $path}
507 if {$pathList ne {}} {
509 "Updating included files" \
511 [concat {reshow_diff;} $after]
520 proc prune_selection {} {
521 global file_states selected_paths
523 foreach path [array names selected_paths] {
524 if {[catch {set still_here $file_states($path)}]} {
525 unset selected_paths($path)
530 ######################################################################
535 global ui_diff current_diff ui_index ui_other
537 $ui_diff conf -state normal
538 $ui_diff delete 0.0 end
539 $ui_diff conf -state disabled
543 $ui_index tag remove in_diff 0.0 end
544 $ui_other tag remove in_diff 0.0 end
547 proc reshow_diff {} {
548 global current_diff ui_status_value file_states
550 if {$current_diff eq {}
551 || [catch {set s $file_states($current_diff)}]} {
554 show_diff $current_diff
558 proc handle_empty_diff {} {
559 global current_diff file_states file_lists
561 set path $current_diff
562 set s $file_states($path)
563 if {[lindex $s 0] ne {_M}} return
565 info_popup "No differences detected.
567 [short_path $path] has no changes.
569 The modification date of this file was updated
570 by another application and you currently have
571 the Trust File Modification Timestamps option
572 enabled, so Git did not automatically detect
573 that there are no content differences in this
576 This file will now be removed from the modified
577 files list, to prevent possible confusion.
579 if {[catch {exec git update-index -- $path} err]} {
580 error_popup "Failed to refresh index:\n\n$err"
584 set old_w [mapcol [lindex $file_states($path) 0] $path]
585 set lno [lsearch -sorted $file_lists($old_w) $path]
587 set file_lists($old_w) \
588 [lreplace $file_lists($old_w) $lno $lno]
590 $old_w conf -state normal
591 $old_w delete $lno.0 [expr {$lno + 1}].0
592 $old_w conf -state disabled
596 proc show_diff {path {w {}} {lno {}}} {
597 global file_states file_lists
598 global is_3way_diff diff_active repo_config
599 global ui_diff current_diff ui_status_value
601 if {$diff_active || ![lock_index read]} return
604 if {$w eq {} || $lno == {}} {
605 foreach w [array names file_lists] {
606 set lno [lsearch -sorted $file_lists($w) $path]
613 if {$w ne {} && $lno >= 1} {
614 $w tag add in_diff $lno.0 [expr {$lno + 1}].0
617 set s $file_states($path)
621 set current_diff $path
622 set ui_status_value "Loading diff of [escape_path $path]..."
624 set cmd [list | git diff-index]
625 lappend cmd --no-color
626 if {$repo_config(gui.diffcontext) > 0} {
627 lappend cmd "-U$repo_config(gui.diffcontext)"
637 set fd [open $path r]
638 set content [read $fd]
643 set ui_status_value "Unable to display [escape_path $path]"
644 error_popup "Error loading file:\n\n$err"
647 $ui_diff conf -state normal
648 $ui_diff insert end $content
649 $ui_diff conf -state disabled
652 set ui_status_value {Ready.}
661 if {[catch {set fd [open $cmd r]} err]} {
664 set ui_status_value "Unable to display [escape_path $path]"
665 error_popup "Error loading diff:\n\n$err"
669 fconfigure $fd -blocking 0 -translation auto
670 fileevent $fd readable [list read_diff $fd]
673 proc read_diff {fd} {
674 global ui_diff ui_status_value is_3way_diff diff_active
677 $ui_diff conf -state normal
678 while {[gets $fd line] >= 0} {
679 # -- Cleanup uninteresting diff header lines.
681 if {[string match {diff --git *} $line]} continue
682 if {[string match {diff --combined *} $line]} continue
683 if {[string match {--- *} $line]} continue
684 if {[string match {+++ *} $line]} continue
685 if {$line eq {deleted file mode 120000}} {
686 set line "deleted symlink"
689 # -- Automatically detect if this is a 3 way diff.
691 if {[string match {@@@ *} $line]} {set is_3way_diff 1}
693 # -- Reformat a 3 way diff, 'cause its too weird.
696 set op [string range $line 0 1]
699 {++} {set tags d_+ ; set op { +}}
700 {--} {set tags d_- ; set op { -}}
701 { +} {set tags d_++; set op {++}}
702 { -} {set tags d_--; set op {--}}
703 {+ } {set tags d_-+; set op {-+}}
704 {- } {set tags d_+-; set op {+-}}
705 default {set tags {}}
707 set line [string replace $line 0 1 $op]
709 switch -- [string index $line 0] {
713 default {set tags {}}
716 $ui_diff insert end $line $tags
717 $ui_diff insert end "\n" $tags
719 $ui_diff conf -state disabled
725 set ui_status_value {Ready.}
727 if {$repo_config(gui.trustmtime) eq {true}
728 && [$ui_diff index end] eq {2.0}} {
734 ######################################################################
738 proc load_last_commit {} {
739 global HEAD PARENT MERGE_HEAD commit_type ui_comm
741 if {[llength $PARENT] == 0} {
742 error_popup {There is nothing to amend.
744 You are about to create the initial commit.
745 There is no commit before this to amend.
750 repository_state curType curHEAD curMERGE_HEAD
751 if {$curType eq {merge}} {
752 error_popup {Cannot amend while merging.
754 You are currently in the middle of a merge that
755 has not been fully completed. You cannot amend
756 the prior commit unless you first abort the
757 current merge activity.
765 set fd [open "| git cat-file commit $curHEAD" r]
766 while {[gets $fd line] > 0} {
767 if {[string match {parent *} $line]} {
768 lappend parents [string range $line 7 end]
771 set msg [string trim [read $fd]]
774 error_popup "Error loading commit data for amend:\n\n$err"
780 set MERGE_HEAD [list]
781 switch -- [llength $parents] {
782 0 {set commit_type amend-initial}
783 1 {set commit_type amend}
784 default {set commit_type amend-merge}
787 $ui_comm delete 0.0 end
788 $ui_comm insert end $msg
790 $ui_comm edit modified false
791 rescan {set ui_status_value {Ready.}}
794 proc create_new_commit {} {
795 global commit_type ui_comm
797 set commit_type normal
798 $ui_comm delete 0.0 end
800 $ui_comm edit modified false
801 rescan {set ui_status_value {Ready.}}
804 set GIT_COMMITTER_IDENT {}
806 proc committer_ident {} {
807 global GIT_COMMITTER_IDENT
809 if {$GIT_COMMITTER_IDENT eq {}} {
810 if {[catch {set me [exec git var GIT_COMMITTER_IDENT]} err]} {
811 error_popup "Unable to obtain your identity:\n\n$err"
814 if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
815 $me me GIT_COMMITTER_IDENT]} {
816 error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me"
821 return $GIT_COMMITTER_IDENT
824 proc commit_tree {} {
825 global HEAD commit_type file_states ui_comm repo_config
827 if {![lock_index update]} return
828 if {[committer_ident] eq {}} return
830 # -- Our in memory state should match the repository.
832 repository_state curType curHEAD curMERGE_HEAD
833 if {[string match amend* $commit_type]
834 && $curType eq {normal}
835 && $curHEAD eq $HEAD} {
836 } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
837 info_popup {Last scanned state does not match repository state.
839 Another Git program has modified this repository
840 since the last scan. A rescan must be performed
841 before another commit can be created.
843 The rescan will be automatically started now.
846 rescan {set ui_status_value {Ready.}}
850 # -- At least one file should differ in the index.
853 foreach path [array names file_states] {
854 switch -glob -- [lindex $file_states($path) 0] {
858 M? {set files_ready 1; break}
860 error_popup "Unmerged files cannot be committed.
862 File [short_path $path] has merge conflicts.
863 You must resolve them and include the file before committing.
869 error_popup "Unknown file state [lindex $s 0] detected.
871 File [short_path $path] cannot be committed by this program.
877 error_popup {No included files to commit.
879 You must include at least 1 file before you can commit.
885 # -- A message is required.
887 set msg [string trim [$ui_comm get 1.0 end]]
889 error_popup {Please supply a commit message.
891 A good commit message has the following format:
893 - First line: Describe in one sentance what you did.
895 - Remaining lines: Describe why this change is good.
901 # -- Update included files if partialincludes are off.
903 if {$repo_config(gui.partialinclude) ne {true}} {
905 foreach path [array names file_states] {
906 switch -glob -- [lindex $file_states($path) 0] {
908 M? {lappend pathList $path}
911 if {$pathList ne {}} {
914 "Updating included files" \
916 [concat {lock_index update;} \
917 [list commit_prehook $curHEAD $msg]]
922 commit_prehook $curHEAD $msg
925 proc commit_prehook {curHEAD msg} {
926 global gitdir ui_status_value pch_error
928 set pchook [file join $gitdir hooks pre-commit]
930 # On Cygwin [file executable] might lie so we need to ask
931 # the shell if the hook is executable. Yes that's annoying.
933 if {[is_Windows] && [file isfile $pchook]} {
934 set pchook [list sh -c [concat \
935 "if test -x \"$pchook\";" \
936 "then exec \"$pchook\" 2>&1;" \
938 } elseif {[file executable $pchook]} {
939 set pchook [list $pchook |& cat]
941 commit_writetree $curHEAD $msg
945 set ui_status_value {Calling pre-commit hook...}
947 set fd_ph [open "| $pchook" r]
948 fconfigure $fd_ph -blocking 0 -translation binary
949 fileevent $fd_ph readable \
950 [list commit_prehook_wait $fd_ph $curHEAD $msg]
953 proc commit_prehook_wait {fd_ph curHEAD msg} {
954 global pch_error ui_status_value
956 append pch_error [read $fd_ph]
957 fconfigure $fd_ph -blocking 1
959 if {[catch {close $fd_ph}]} {
960 set ui_status_value {Commit declined by pre-commit hook.}
961 hook_failed_popup pre-commit $pch_error
964 commit_writetree $curHEAD $msg
969 fconfigure $fd_ph -blocking 0
972 proc commit_writetree {curHEAD msg} {
973 global ui_status_value
975 set ui_status_value {Committing changes...}
976 set fd_wt [open "| git write-tree" r]
977 fileevent $fd_wt readable \
978 [list commit_committree $fd_wt $curHEAD $msg]
981 proc commit_committree {fd_wt curHEAD msg} {
982 global HEAD PARENT MERGE_HEAD commit_type
983 global single_commit gitdir
984 global ui_status_value ui_comm selected_commit_type
985 global file_states selected_paths rescan_active
988 if {$tree_id eq {} || [catch {close $fd_wt} err]} {
989 error_popup "write-tree failed:\n\n$err"
990 set ui_status_value {Commit failed.}
995 # -- Create the commit.
997 set cmd [list git commit-tree $tree_id]
998 set parents [concat $PARENT $MERGE_HEAD]
999 if {[llength $parents] > 0} {
1000 foreach p $parents {
1004 # git commit-tree writes to stderr during initial commit.
1005 lappend cmd 2>/dev/null
1008 if {[catch {set cmt_id [eval exec $cmd]} err]} {
1009 error_popup "commit-tree failed:\n\n$err"
1010 set ui_status_value {Commit failed.}
1015 # -- Update the HEAD ref.
1018 if {$commit_type ne {normal}} {
1019 append reflogm " ($commit_type)"
1021 set i [string first "\n" $msg]
1023 append reflogm {: } [string range $msg 0 [expr {$i - 1}]]
1025 append reflogm {: } $msg
1027 set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD]
1028 if {[catch {eval exec $cmd} err]} {
1029 error_popup "update-ref failed:\n\n$err"
1030 set ui_status_value {Commit failed.}
1035 # -- Cleanup after ourselves.
1037 catch {file delete [file join $gitdir MERGE_HEAD]}
1038 catch {file delete [file join $gitdir MERGE_MSG]}
1039 catch {file delete [file join $gitdir SQUASH_MSG]}
1040 catch {file delete [file join $gitdir GITGUI_MSG]}
1042 # -- Let rerere do its thing.
1044 if {[file isdirectory [file join $gitdir rr-cache]]} {
1045 catch {exec git rerere}
1048 # -- Run the post-commit hook.
1050 set pchook [file join $gitdir hooks post-commit]
1051 if {[is_Windows] && [file isfile $pchook]} {
1052 set pchook [list sh -c [concat \
1053 "if test -x \"$pchook\";" \
1054 "then exec \"$pchook\";" \
1056 } elseif {![file executable $pchook]} {
1059 if {$pchook ne {}} {
1060 catch {exec $pchook &}
1063 $ui_comm delete 0.0 end
1065 $ui_comm edit modified false
1067 if {$single_commit} do_quit
1069 # -- Update in memory status
1071 set selected_commit_type new
1072 set commit_type normal
1075 set MERGE_HEAD [list]
1077 foreach path [array names file_states] {
1078 set s $file_states($path)
1080 switch -glob -- $m {
1088 unset file_states($path)
1089 catch {unset selected_paths($path)}
1092 set file_states($path) [list _O [lindex $s 1] {} {}]
1099 set file_states($path) [list \
1100 _[string index $m 1] \
1111 set ui_status_value \
1112 "Changes committed as [string range $cmt_id 0 7]."
1115 ######################################################################
1119 proc fetch_from {remote} {
1120 set w [new_console "fetch $remote" \
1121 "Fetching new changes from $remote"]
1122 set cmd [list git fetch]
1124 console_exec $w $cmd
1127 proc pull_remote {remote branch} {
1128 global HEAD commit_type file_states repo_config
1130 if {![lock_index update]} return
1132 # -- Our in memory state should match the repository.
1134 repository_state curType curHEAD curMERGE_HEAD
1135 if {$commit_type ne $curType || $HEAD ne $curHEAD} {
1136 info_popup {Last scanned state does not match repository state.
1138 Another Git program has modified this repository
1139 since the last scan. A rescan must be performed
1140 before a pull operation can be started.
1142 The rescan will be automatically started now.
1145 rescan {set ui_status_value {Ready.}}
1149 # -- No differences should exist before a pull.
1151 if {[array size file_states] != 0} {
1152 error_popup {Uncommitted but modified files are present.
1154 You should not perform a pull with unmodified
1155 files in your working directory as Git will be
1156 unable to recover from an incorrect merge.
1158 You should commit or revert all changes before
1159 starting a pull operation.
1165 set w [new_console "pull $remote $branch" \
1166 "Pulling new changes from branch $branch in $remote"]
1167 set cmd [list git pull]
1168 if {$repo_config(gui.pullsummary) eq {false}} {
1169 lappend cmd --no-summary
1173 console_exec $w $cmd [list post_pull_remote $remote $branch]
1176 proc post_pull_remote {remote branch success} {
1177 global HEAD PARENT MERGE_HEAD commit_type selected_commit_type
1178 global ui_status_value
1182 repository_state commit_type HEAD MERGE_HEAD
1184 set selected_commit_type new
1185 set ui_status_value "Pulling $branch from $remote complete."
1187 rescan [list set ui_status_value \
1188 "Conflicts detected while pulling $branch from $remote."]
1192 proc push_to {remote} {
1193 set w [new_console "push $remote" \
1194 "Pushing changes to $remote"]
1195 set cmd [list git push]
1197 console_exec $w $cmd
1200 ######################################################################
1204 proc mapcol {state path} {
1205 global all_cols ui_other
1207 if {[catch {set r $all_cols($state)}]} {
1208 puts "error: no column for state={$state} $path"
1214 proc mapicon {state path} {
1217 if {[catch {set r $all_icons($state)}]} {
1218 puts "error: no icon for state={$state} $path"
1224 proc mapdesc {state path} {
1227 if {[catch {set r $all_descs($state)}]} {
1228 puts "error: no desc for state={$state} $path"
1234 proc escape_path {path} {
1235 regsub -all "\n" $path "\\n" path
1239 proc short_path {path} {
1240 return [escape_path [lindex [file split $path] end]]
1244 set null_sha1 [string repeat 0 40]
1246 proc merge_state {path new_state {head_info {}} {index_info {}}} {
1247 global file_states next_icon_id null_sha1
1249 set s0 [string index $new_state 0]
1250 set s1 [string index $new_state 1]
1252 if {[catch {set info $file_states($path)}]} {
1254 set icon n[incr next_icon_id]
1256 set state [lindex $info 0]
1257 set icon [lindex $info 1]
1258 if {$head_info eq {}} {set head_info [lindex $info 2]}
1259 if {$index_info eq {}} {set index_info [lindex $info 3]}
1262 if {$s0 eq {?}} {set s0 [string index $state 0]} \
1263 elseif {$s0 eq {_}} {set s0 _}
1265 if {$s1 eq {?}} {set s1 [string index $state 1]} \
1266 elseif {$s1 eq {_}} {set s1 _}
1268 if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
1269 set head_info [list 0 $null_sha1]
1270 } elseif {$s0 ne {_} && [string index $state 0] eq {_}
1271 && $head_info eq {}} {
1272 set head_info $index_info
1275 set file_states($path) [list $s0$s1 $icon \
1276 $head_info $index_info \
1281 proc display_file {path state} {
1282 global file_states file_lists selected_paths
1284 set old_m [merge_state $path $state]
1285 set s $file_states($path)
1286 set new_m [lindex $s 0]
1287 set new_w [mapcol $new_m $path]
1288 set old_w [mapcol $old_m $path]
1289 set new_icon [mapicon $new_m $path]
1291 if {$new_m eq {__}} {
1292 set lno [lsearch -sorted $file_lists($old_w) $path]
1294 set file_lists($old_w) \
1295 [lreplace $file_lists($old_w) $lno $lno]
1297 $old_w conf -state normal
1298 $old_w delete $lno.0 [expr {$lno + 1}].0
1299 $old_w conf -state disabled
1301 unset file_states($path)
1302 catch {unset selected_paths($path)}
1306 if {$new_w ne $old_w} {
1307 set lno [lsearch -sorted $file_lists($old_w) $path]
1309 set file_lists($old_w) \
1310 [lreplace $file_lists($old_w) $lno $lno]
1312 $old_w conf -state normal
1313 $old_w delete $lno.0 [expr {$lno + 1}].0
1314 $old_w conf -state disabled
1317 lappend file_lists($new_w) $path
1318 set file_lists($new_w) [lsort $file_lists($new_w)]
1319 set lno [lsearch -sorted $file_lists($new_w) $path]
1321 $new_w conf -state normal
1322 $new_w image create $lno.0 \
1323 -align center -padx 5 -pady 1 \
1324 -name [lindex $s 1] \
1326 $new_w insert $lno.1 "[escape_path $path]\n"
1327 if {[catch {set in_sel $selected_paths($path)}]} {
1331 $new_w tag add in_sel $lno.0 [expr {$lno + 1}].0
1333 $new_w conf -state disabled
1334 } elseif {$new_icon ne [mapicon $old_m $path]} {
1335 $new_w conf -state normal
1336 $new_w image conf [lindex $s 1] -image $new_icon
1337 $new_w conf -state disabled
1341 proc display_all_files {} {
1342 global ui_index ui_other
1343 global file_states file_lists
1344 global last_clicked selected_paths
1346 $ui_index conf -state normal
1347 $ui_other conf -state normal
1349 $ui_index delete 0.0 end
1350 $ui_other delete 0.0 end
1353 set file_lists($ui_index) [list]
1354 set file_lists($ui_other) [list]
1356 foreach path [lsort [array names file_states]] {
1357 set s $file_states($path)
1359 set w [mapcol $m $path]
1360 lappend file_lists($w) $path
1361 set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
1362 $w image create end \
1363 -align center -padx 5 -pady 1 \
1364 -name [lindex $s 1] \
1365 -image [mapicon $m $path]
1366 $w insert end "[escape_path $path]\n"
1367 if {[catch {set in_sel $selected_paths($path)}]} {
1371 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
1375 $ui_index conf -state disabled
1376 $ui_other conf -state disabled
1379 proc update_indexinfo {msg pathList after} {
1380 global update_index_cp ui_status_value
1382 if {![lock_index update]} return
1384 set update_index_cp 0
1385 set pathList [lsort $pathList]
1386 set totalCnt [llength $pathList]
1387 set batch [expr {int($totalCnt * .01) + 1}]
1388 if {$batch > 25} {set batch 25}
1390 set ui_status_value [format \
1391 "$msg... %i/%i files (%.2f%%)" \
1395 set fd [open "| git update-index -z --index-info" w]
1401 fileevent $fd writable [list \
1402 write_update_indexinfo \
1412 proc write_update_indexinfo {fd pathList totalCnt batch msg after} {
1413 global update_index_cp ui_status_value
1414 global file_states current_diff
1416 if {$update_index_cp >= $totalCnt} {
1423 for {set i $batch} \
1424 {$update_index_cp < $totalCnt && $i > 0} \
1426 set path [lindex $pathList $update_index_cp]
1427 incr update_index_cp
1429 set s $file_states($path)
1430 switch -glob -- [lindex $s 0] {
1437 set info [lindex $s 2]
1438 if {$info eq {}} continue
1440 puts -nonewline $fd $info
1441 puts -nonewline $fd "\t"
1442 puts -nonewline $fd $path
1443 puts -nonewline $fd "\0"
1444 display_file $path $new
1447 set ui_status_value [format \
1448 "$msg... %i/%i files (%.2f%%)" \
1451 [expr {100.0 * $update_index_cp / $totalCnt}]]
1454 proc update_index {msg pathList after} {
1455 global update_index_cp ui_status_value
1457 if {![lock_index update]} return
1459 set update_index_cp 0
1460 set pathList [lsort $pathList]
1461 set totalCnt [llength $pathList]
1462 set batch [expr {int($totalCnt * .01) + 1}]
1463 if {$batch > 25} {set batch 25}
1465 set ui_status_value [format \
1466 "$msg... %i/%i files (%.2f%%)" \
1470 set fd [open "| git update-index --add --remove -z --stdin" w]
1476 fileevent $fd writable [list \
1477 write_update_index \
1487 proc write_update_index {fd pathList totalCnt batch msg after} {
1488 global update_index_cp ui_status_value
1489 global file_states current_diff
1491 if {$update_index_cp >= $totalCnt} {
1498 for {set i $batch} \
1499 {$update_index_cp < $totalCnt && $i > 0} \
1501 set path [lindex $pathList $update_index_cp]
1502 incr update_index_cp
1504 switch -glob -- [lindex $file_states($path) 0] {
1523 puts -nonewline $fd $path
1524 puts -nonewline $fd "\0"
1525 display_file $path $new
1528 set ui_status_value [format \
1529 "$msg... %i/%i files (%.2f%%)" \
1532 [expr {100.0 * $update_index_cp / $totalCnt}]]
1535 proc checkout_index {msg pathList after} {
1536 global update_index_cp ui_status_value
1538 if {![lock_index update]} return
1540 set update_index_cp 0
1541 set pathList [lsort $pathList]
1542 set totalCnt [llength $pathList]
1543 set batch [expr {int($totalCnt * .01) + 1}]
1544 if {$batch > 25} {set batch 25}
1546 set ui_status_value [format \
1547 "$msg... %i/%i files (%.2f%%)" \
1551 set cmd [list git checkout-index]
1557 set fd [open "| $cmd " w]
1563 fileevent $fd writable [list \
1564 write_checkout_index \
1574 proc write_checkout_index {fd pathList totalCnt batch msg after} {
1575 global update_index_cp ui_status_value
1576 global file_states current_diff
1578 if {$update_index_cp >= $totalCnt} {
1585 for {set i $batch} \
1586 {$update_index_cp < $totalCnt && $i > 0} \
1588 set path [lindex $pathList $update_index_cp]
1589 incr update_index_cp
1591 switch -glob -- [lindex $file_states($path) 0] {
1601 puts -nonewline $fd $path
1602 puts -nonewline $fd "\0"
1603 display_file $path $new
1606 set ui_status_value [format \
1607 "$msg... %i/%i files (%.2f%%)" \
1610 [expr {100.0 * $update_index_cp / $totalCnt}]]
1613 ######################################################################
1615 ## branch management
1617 proc load_all_heads {} {
1618 global all_heads tracking_branches
1620 set all_heads [list]
1621 set cmd [list git for-each-ref]
1622 lappend cmd --format=%(refname)
1623 lappend cmd refs/heads
1624 set fd [open "| $cmd" r]
1625 while {[gets $fd line] > 0} {
1626 if {![catch {set info $tracking_branches($line)}]} continue
1627 if {![regsub ^refs/heads/ $line {} name]} continue
1628 lappend all_heads $name
1632 set all_heads [lsort $all_heads]
1635 proc populate_branch_menu {m} {
1636 global all_heads disable_on_lock
1639 foreach b $all_heads {
1640 $m add radiobutton \
1642 -command [list switch_branch $b] \
1643 -variable current_branch \
1646 lappend disable_on_lock \
1647 [list $m entryconf [$m index last] -state]
1651 proc do_create_branch {} {
1652 error "NOT IMPLEMENTED"
1655 proc do_delete_branch {} {
1656 error "NOT IMPLEMENTED"
1659 proc switch_branch {b} {
1660 global HEAD commit_type file_states current_branch
1661 global selected_commit_type ui_comm
1663 if {![lock_index switch]} return
1665 # -- Backup the selected branch (repository_state resets it)
1667 set new_branch $current_branch
1669 # -- Our in memory state should match the repository.
1671 repository_state curType curHEAD curMERGE_HEAD
1672 if {[string match amend* $commit_type]
1673 && $curType eq {normal}
1674 && $curHEAD eq $HEAD} {
1675 } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
1676 info_popup {Last scanned state does not match repository state.
1678 Another Git program has modified this repository
1679 since the last scan. A rescan must be performed
1680 before the current branch can be changed.
1682 The rescan will be automatically started now.
1685 rescan {set ui_status_value {Ready.}}
1689 # -- Toss the message buffer if we are in amend mode.
1691 if {[string match amend* $curType]} {
1692 $ui_comm delete 0.0 end
1694 $ui_comm edit modified false
1697 set selected_commit_type new
1698 set current_branch $new_branch
1701 error "NOT FINISHED"
1704 ######################################################################
1706 ## remote management
1708 proc load_all_remotes {} {
1709 global gitdir repo_config
1710 global all_remotes tracking_branches
1712 set all_remotes [list]
1713 array unset tracking_branches
1715 set rm_dir [file join $gitdir remotes]
1716 if {[file isdirectory $rm_dir]} {
1717 set all_remotes [glob \
1721 -directory $rm_dir *]
1723 foreach name $all_remotes {
1725 set fd [open [file join $rm_dir $name] r]
1726 while {[gets $fd line] >= 0} {
1727 if {![regexp {^Pull:[ ]*([^:]+):(.+)$} \
1728 $line line src dst]} continue
1729 if {![regexp ^refs/ $dst]} {
1730 set dst "refs/heads/$dst"
1732 set tracking_branches($dst) [list $name $src]
1739 foreach line [array names repo_config remote.*.url] {
1740 if {![regexp ^remote\.(.*)\.url\$ $line line name]} continue
1741 lappend all_remotes $name
1743 if {[catch {set fl $repo_config(remote.$name.fetch)}]} {
1747 if {![regexp {^([^:]+):(.+)$} $line line src dst]} continue
1748 if {![regexp ^refs/ $dst]} {
1749 set dst "refs/heads/$dst"
1751 set tracking_branches($dst) [list $name $src]
1755 set all_remotes [lsort -unique $all_remotes]
1758 proc populate_fetch_menu {m} {
1759 global gitdir all_remotes repo_config
1761 foreach r $all_remotes {
1763 if {![catch {set a $repo_config(remote.$r.url)}]} {
1764 if {![catch {set a $repo_config(remote.$r.fetch)}]} {
1769 set fd [open [file join $gitdir remotes $r] r]
1770 while {[gets $fd n] >= 0} {
1771 if {[regexp {^Pull:[ \t]*([^:]+):} $n]} {
1782 -label "Fetch from $r..." \
1783 -command [list fetch_from $r] \
1789 proc populate_push_menu {m} {
1790 global gitdir all_remotes repo_config
1792 foreach r $all_remotes {
1794 if {![catch {set a $repo_config(remote.$r.url)}]} {
1795 if {![catch {set a $repo_config(remote.$r.push)}]} {
1800 set fd [open [file join $gitdir remotes $r] r]
1801 while {[gets $fd n] >= 0} {
1802 if {[regexp {^Push:[ \t]*([^:]+):} $n]} {
1813 -label "Push to $r..." \
1814 -command [list push_to $r] \
1820 proc populate_pull_menu {m} {
1821 global gitdir repo_config all_remotes disable_on_lock
1823 foreach remote $all_remotes {
1825 if {[array get repo_config remote.$remote.url] ne {}} {
1826 if {[array get repo_config remote.$remote.fetch] ne {}} {
1827 foreach line $repo_config(remote.$remote.fetch) {
1828 if {[regexp {^([^:]+):} $line line rb]} {
1835 set fd [open [file join $gitdir remotes $remote] r]
1836 while {[gets $fd line] >= 0} {
1837 if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} {
1845 foreach rb $rb_list {
1846 regsub ^refs/heads/ $rb {} rb_short
1848 -label "Branch $rb_short from $remote..." \
1849 -command [list pull_remote $remote $rb] \
1851 lappend disable_on_lock \
1852 [list $m entryconf [$m index last] -state]
1857 ######################################################################
1862 #define mask_width 14
1863 #define mask_height 15
1864 static unsigned char mask_bits[] = {
1865 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1866 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1867 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1870 image create bitmap file_plain -background white -foreground black -data {
1871 #define plain_width 14
1872 #define plain_height 15
1873 static unsigned char plain_bits[] = {
1874 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1875 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1876 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1877 } -maskdata $filemask
1879 image create bitmap file_mod -background white -foreground blue -data {
1880 #define mod_width 14
1881 #define mod_height 15
1882 static unsigned char mod_bits[] = {
1883 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1884 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1885 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1886 } -maskdata $filemask
1888 image create bitmap file_fulltick -background white -foreground "#007000" -data {
1889 #define file_fulltick_width 14
1890 #define file_fulltick_height 15
1891 static unsigned char file_fulltick_bits[] = {
1892 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1893 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1894 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1895 } -maskdata $filemask
1897 image create bitmap file_parttick -background white -foreground "#005050" -data {
1898 #define parttick_width 14
1899 #define parttick_height 15
1900 static unsigned char parttick_bits[] = {
1901 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1902 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1903 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1904 } -maskdata $filemask
1906 image create bitmap file_question -background white -foreground black -data {
1907 #define file_question_width 14
1908 #define file_question_height 15
1909 static unsigned char file_question_bits[] = {
1910 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1911 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1912 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1913 } -maskdata $filemask
1915 image create bitmap file_removed -background white -foreground red -data {
1916 #define file_removed_width 14
1917 #define file_removed_height 15
1918 static unsigned char file_removed_bits[] = {
1919 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1920 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1921 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1922 } -maskdata $filemask
1924 image create bitmap file_merge -background white -foreground blue -data {
1925 #define file_merge_width 14
1926 #define file_merge_height 15
1927 static unsigned char file_merge_bits[] = {
1928 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1929 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1930 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1931 } -maskdata $filemask
1933 set ui_index .vpane.files.index.list
1934 set ui_other .vpane.files.other.list
1935 set max_status_desc 0
1937 {__ i plain "Unmodified"}
1938 {_M i mod "Modified"}
1939 {M_ i fulltick "Added to commit"}
1940 {MM i parttick "Partially included"}
1941 {MD i question "Added (but gone)"}
1943 {_O o plain "Untracked"}
1944 {A_ o fulltick "Added by commit"}
1945 {AM o parttick "Partially added"}
1946 {AD o question "Added (but gone)"}
1948 {_D i question "Missing"}
1949 {DD i removed "Removed by commit"}
1950 {D_ i removed "Removed by commit"}
1951 {DO i removed "Removed (still exists)"}
1952 {DM i removed "Removed (but modified)"}
1954 {UD i merge "Merge conflicts"}
1955 {UM i merge "Merge conflicts"}
1956 {U_ i merge "Merge conflicts"}
1958 if {$max_status_desc < [string length [lindex $i 3]]} {
1959 set max_status_desc [string length [lindex $i 3]]
1961 if {[lindex $i 1] eq {i}} {
1962 set all_cols([lindex $i 0]) $ui_index
1964 set all_cols([lindex $i 0]) $ui_other
1966 set all_icons([lindex $i 0]) file_[lindex $i 2]
1967 set all_descs([lindex $i 0]) [lindex $i 3]
1971 ######################################################################
1976 global tcl_platform tk_library
1977 if {[tk windowingsystem] eq {aqua}} {
1983 proc is_Windows {} {
1985 if {$tcl_platform(platform) eq {windows}} {
1991 proc bind_button3 {w cmd} {
1992 bind $w <Any-Button-3> $cmd
1994 bind $w <Control-Button-1> $cmd
1998 proc incr_font_size {font {amt 1}} {
1999 set sz [font configure $font -size]
2001 font configure $font -size $sz
2002 font configure ${font}bold -size $sz
2005 proc hook_failed_popup {hook msg} {
2006 global gitdir appname
2012 label $w.m.l1 -text "$hook hook failed:" \
2017 -background white -borderwidth 1 \
2019 -width 80 -height 10 \
2021 -yscrollcommand [list $w.m.sby set]
2023 -text {You must correct the above errors before committing.} \
2027 scrollbar $w.m.sby -command [list $w.m.t yview]
2028 pack $w.m.l1 -side top -fill x
2029 pack $w.m.l2 -side bottom -fill x
2030 pack $w.m.sby -side right -fill y
2031 pack $w.m.t -side left -fill both -expand 1
2032 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
2034 $w.m.t insert 1.0 $msg
2035 $w.m.t conf -state disabled
2037 button $w.ok -text OK \
2040 -command "destroy $w"
2041 pack $w.ok -side bottom -anchor e -pady 10 -padx 10
2043 bind $w <Visibility> "grab $w; focus $w"
2044 bind $w <Key-Return> "destroy $w"
2045 wm title $w "$appname ([lindex [file split \
2046 [file normalize [file dirname $gitdir]]] \
2051 set next_console_id 0
2053 proc new_console {short_title long_title} {
2054 global next_console_id console_data
2055 set w .console[incr next_console_id]
2056 set console_data($w) [list $short_title $long_title]
2057 return [console_init $w]
2060 proc console_init {w} {
2061 global console_cr console_data
2062 global gitdir appname M1B
2064 set console_cr($w) 1.0
2067 label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
2072 -background white -borderwidth 1 \
2074 -width 80 -height 10 \
2077 -yscrollcommand [list $w.m.sby set]
2078 label $w.m.s -text {Working... please wait...} \
2082 scrollbar $w.m.sby -command [list $w.m.t yview]
2083 pack $w.m.l1 -side top -fill x
2084 pack $w.m.s -side bottom -fill x
2085 pack $w.m.sby -side right -fill y
2086 pack $w.m.t -side left -fill both -expand 1
2087 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
2089 menu $w.ctxm -tearoff 0
2090 $w.ctxm add command -label "Copy" \
2092 -command "tk_textCopy $w.m.t"
2093 $w.ctxm add command -label "Select All" \
2095 -command "$w.m.t tag add sel 0.0 end"
2096 $w.ctxm add command -label "Copy All" \
2099 $w.m.t tag add sel 0.0 end
2101 $w.m.t tag remove sel 0.0 end
2104 button $w.ok -text {Close} \
2107 -command "destroy $w"
2108 pack $w.ok -side bottom -anchor e -pady 10 -padx 10
2110 bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y"
2111 bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break"
2112 bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break"
2113 bind $w <Visibility> "focus $w"
2114 wm title $w "$appname ([lindex [file split \
2115 [file normalize [file dirname $gitdir]]] \
2116 end]): [lindex $console_data($w) 0]"
2120 proc console_exec {w cmd {after {}}} {
2121 # -- Windows tosses the enviroment when we exec our child.
2122 # But most users need that so we have to relogin. :-(
2125 set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
2128 # -- Tcl won't let us redirect both stdout and stderr to
2129 # the same pipe. So pass it through cat...
2131 set cmd [concat | $cmd |& cat]
2133 set fd_f [open $cmd r]
2134 fconfigure $fd_f -blocking 0 -translation binary
2135 fileevent $fd_f readable [list console_read $w $fd_f $after]
2138 proc console_read {w fd after} {
2139 global console_cr console_data
2143 if {![winfo exists $w]} {console_init $w}
2144 $w.m.t conf -state normal
2146 set n [string length $buf]
2148 set cr [string first "\r" $buf $c]
2149 set lf [string first "\n" $buf $c]
2150 if {$cr < 0} {set cr [expr {$n + 1}]}
2151 if {$lf < 0} {set lf [expr {$n + 1}]}
2154 $w.m.t insert end [string range $buf $c $lf]
2155 set console_cr($w) [$w.m.t index {end -1c}]
2159 $w.m.t delete $console_cr($w) end
2160 $w.m.t insert end "\n"
2161 $w.m.t insert end [string range $buf $c $cr]
2166 $w.m.t conf -state disabled
2170 fconfigure $fd -blocking 1
2172 if {[catch {close $fd}]} {
2173 if {![winfo exists $w]} {console_init $w}
2174 $w.m.s conf -background red -text {Error: Command Failed}
2175 $w.ok conf -state normal
2177 } elseif {[winfo exists $w]} {
2178 $w.m.s conf -background green -text {Success}
2179 $w.ok conf -state normal
2182 array unset console_cr $w
2183 array unset console_data $w
2185 uplevel #0 $after $ok
2189 fconfigure $fd -blocking 0
2192 ######################################################################
2196 set starting_gitk_msg {Please wait... Starting gitk...}
2198 proc do_gitk {revs} {
2199 global ui_status_value starting_gitk_msg
2207 set cmd "sh -c \"exec $cmd\""
2211 if {[catch {eval exec $cmd} err]} {
2212 error_popup "Failed to start gitk:\n\n$err"
2214 set ui_status_value $starting_gitk_msg
2216 if {$ui_status_value eq $starting_gitk_msg} {
2217 set ui_status_value {Ready.}
2224 set w [new_console {gc} {Compressing the object database}]
2225 console_exec $w {git gc}
2228 proc do_fsck_objects {} {
2229 set w [new_console {fsck-objects} \
2230 {Verifying the object database with fsck-objects}]
2231 set cmd [list git fsck-objects]
2234 lappend cmd --strict
2235 console_exec $w $cmd
2241 global gitdir ui_comm is_quitting repo_config commit_type
2243 if {$is_quitting} return
2246 # -- Stash our current commit buffer.
2248 set save [file join $gitdir GITGUI_MSG]
2249 set msg [string trim [$ui_comm get 0.0 end]]
2250 if {![string match amend* $commit_type]
2251 && [$ui_comm edit modified]
2254 set fd [open $save w]
2255 puts $fd [string trim [$ui_comm get 0.0 end]]
2259 catch {file delete $save}
2262 # -- Stash our current window geometry into this repository.
2264 set cfg_geometry [list]
2265 lappend cfg_geometry [wm geometry .]
2266 lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
2267 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
2268 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
2271 if {$cfg_geometry ne $rc_geometry} {
2272 catch {exec git repo-config gui.geometry $cfg_geometry}
2279 rescan {set ui_status_value {Ready.}}
2282 proc remove_helper {txt paths} {
2283 global file_states current_diff
2285 if {![lock_index begin-update]} return
2289 foreach path $paths {
2290 switch -glob -- [lindex $file_states($path) 0] {
2294 lappend pathList $path
2295 if {$path eq $current_diff} {
2296 set after {reshow_diff;}
2301 if {$pathList eq {}} {
2307 [concat $after {set ui_status_value {Ready.}}]
2311 proc do_remove_selection {} {
2312 global current_diff selected_paths
2314 if {[array size selected_paths] > 0} {
2316 {Removing selected files from commit} \
2317 [array names selected_paths]
2318 } elseif {$current_diff ne {}} {
2320 "Removing [short_path $current_diff] from commit" \
2321 [list $current_diff]
2325 proc include_helper {txt paths} {
2326 global file_states current_diff
2328 if {![lock_index begin-update]} return
2332 foreach path $paths {
2333 switch -glob -- [lindex $file_states($path) 0] {
2342 lappend pathList $path
2343 if {$path eq $current_diff} {
2344 set after {reshow_diff;}
2349 if {$pathList eq {}} {
2355 [concat $after {set ui_status_value {Ready to commit.}}]
2359 proc do_include_selection {} {
2360 global current_diff selected_paths
2362 if {[array size selected_paths] > 0} {
2364 {Adding selected files} \
2365 [array names selected_paths]
2366 } elseif {$current_diff ne {}} {
2368 "Adding [short_path $current_diff]" \
2369 [list $current_diff]
2373 proc do_include_all {} {
2377 foreach path [array names file_states] {
2378 switch -- [lindex $file_states($path) 0] {
2384 _D {lappend paths $path}
2388 {Adding all modified files} \
2392 proc revert_helper {txt paths} {
2393 global gitdir appname
2394 global file_states current_diff
2396 if {![lock_index begin-update]} return
2400 foreach path $paths {
2401 switch -glob -- [lindex $file_states($path) 0] {
2408 lappend pathList $path
2409 if {$path eq $current_diff} {
2410 set after {reshow_diff;}
2416 set n [llength $pathList]
2420 } elseif {$n == 1} {
2421 set s "[short_path [lindex $pathList]]"
2423 set s "these $n files"
2426 set reponame [lindex [file split \
2427 [file normalize [file dirname $gitdir]]] \
2430 set reply [tk_dialog \
2432 "$appname ($reponame)" \
2433 "Revert changes in $s?
2435 Any unadded changes will be permanently lost by the revert." \
2445 [concat $after {set ui_status_value {Ready.}}]
2451 proc do_revert_selection {} {
2452 global current_diff selected_paths
2454 if {[array size selected_paths] > 0} {
2456 {Reverting selected files} \
2457 [array names selected_paths]
2458 } elseif {$current_diff ne {}} {
2460 "Reverting [short_path $current_diff]" \
2461 [list $current_diff]
2465 proc do_signoff {} {
2468 set me [committer_ident]
2469 if {$me eq {}} return
2471 set sob "Signed-off-by: $me"
2472 set last [$ui_comm get {end -1c linestart} {end -1c}]
2473 if {$last ne $sob} {
2474 $ui_comm edit separator
2476 && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
2477 $ui_comm insert end "\n"
2479 $ui_comm insert end "\n$sob"
2480 $ui_comm edit separator
2485 proc do_select_commit_type {} {
2486 global commit_type selected_commit_type
2488 if {$selected_commit_type eq {new}
2489 && [string match amend* $commit_type]} {
2491 } elseif {$selected_commit_type eq {amend}
2492 && ![string match amend* $commit_type]} {
2495 # The amend request was rejected...
2497 if {![string match amend* $commit_type]} {
2498 set selected_commit_type new
2508 global appname copyright
2509 global tcl_patchLevel tk_patchLevel
2513 wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
2515 label $w.header -text "About $appname" \
2517 pack $w.header -side top -fill x
2520 button $w.buttons.close -text {Close} \
2522 -command [list destroy $w]
2523 pack $w.buttons.close -side right
2524 pack $w.buttons -side bottom -fill x -pady 10 -padx 10
2527 -text "$appname - a commit creation tool for Git.
2535 pack $w.desc -side top -fill x -padx 5 -pady 5
2537 set v [exec git --version]
2539 if {$tcl_patchLevel eq $tk_patchLevel} {
2540 append v "Tcl/Tk version $tcl_patchLevel"
2542 append v "Tcl version $tcl_patchLevel"
2543 append v ", Tk version $tk_patchLevel"
2554 pack $w.vers -side top -fill x -padx 5 -pady 5
2556 bind $w <Visibility> "grab $w; focus $w"
2557 bind $w <Key-Escape> "destroy $w"
2558 wm title $w "About $appname"
2562 proc do_options {} {
2563 global appname gitdir font_descs
2564 global repo_config global_config
2565 global repo_config_new global_config_new
2567 array unset repo_config_new
2568 array unset global_config_new
2569 foreach name [array names repo_config] {
2570 set repo_config_new($name) $repo_config($name)
2573 foreach name [array names repo_config] {
2575 gui.diffcontext {continue}
2577 set repo_config_new($name) $repo_config($name)
2579 foreach name [array names global_config] {
2580 set global_config_new($name) $global_config($name)
2582 set reponame [lindex [file split \
2583 [file normalize [file dirname $gitdir]]] \
2586 set w .options_editor
2588 wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
2590 label $w.header -text "$appname Options" \
2592 pack $w.header -side top -fill x
2595 button $w.buttons.restore -text {Restore Defaults} \
2597 -command do_restore_defaults
2598 pack $w.buttons.restore -side left
2599 button $w.buttons.save -text Save \
2601 -command [list do_save_config $w]
2602 pack $w.buttons.save -side right
2603 button $w.buttons.cancel -text {Cancel} \
2605 -command [list destroy $w]
2606 pack $w.buttons.cancel -side right
2607 pack $w.buttons -side bottom -fill x -pady 10 -padx 10
2609 labelframe $w.repo -text "$reponame Repository" \
2611 -relief raised -borderwidth 2
2612 labelframe $w.global -text {Global (All Repositories)} \
2614 -relief raised -borderwidth 2
2615 pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5
2616 pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5
2619 {b partialinclude {Allow Partially Added Files}}
2620 {b pullsummary {Show Pull Summary}}
2621 {b trustmtime {Trust File Modification Timestamps}}
2622 {i diffcontext {Number of Diff Context Lines}}
2624 set type [lindex $option 0]
2625 set name [lindex $option 1]
2626 set text [lindex $option 2]
2627 foreach f {repo global} {
2630 checkbutton $w.$f.$name -text $text \
2631 -variable ${f}_config_new(gui.$name) \
2635 pack $w.$f.$name -side top -anchor w
2639 label $w.$f.$name.l -text "$text:" -font font_ui
2640 pack $w.$f.$name.l -side left -anchor w -fill x
2641 spinbox $w.$f.$name.v \
2642 -textvariable ${f}_config_new(gui.$name) \
2643 -from 1 -to 99 -increment 1 \
2646 pack $w.$f.$name.v -side right -anchor e
2647 pack $w.$f.$name -side top -anchor w -fill x
2653 set all_fonts [lsort [font families]]
2654 foreach option $font_descs {
2655 set name [lindex $option 0]
2656 set font [lindex $option 1]
2657 set text [lindex $option 2]
2659 set global_config_new(gui.$font^^family) \
2660 [font configure $font -family]
2661 set global_config_new(gui.$font^^size) \
2662 [font configure $font -size]
2664 frame $w.global.$name
2665 label $w.global.$name.l -text "$text:" -font font_ui
2666 pack $w.global.$name.l -side left -anchor w -fill x
2667 eval tk_optionMenu $w.global.$name.family \
2668 global_config_new(gui.$font^^family) \
2670 spinbox $w.global.$name.size \
2671 -textvariable global_config_new(gui.$font^^size) \
2672 -from 2 -to 80 -increment 1 \
2675 pack $w.global.$name.size -side right -anchor e
2676 pack $w.global.$name.family -side right -anchor e
2677 pack $w.global.$name -side top -anchor w -fill x
2680 bind $w <Visibility> "grab $w; focus $w"
2681 bind $w <Key-Escape> "destroy $w"
2682 wm title $w "$appname ($reponame): Options"
2686 proc do_restore_defaults {} {
2687 global font_descs default_config repo_config
2688 global repo_config_new global_config_new
2690 foreach name [array names default_config] {
2691 set repo_config_new($name) $default_config($name)
2692 set global_config_new($name) $default_config($name)
2695 foreach option $font_descs {
2696 set name [lindex $option 0]
2697 set repo_config(gui.$name) $default_config(gui.$name)
2701 foreach option $font_descs {
2702 set name [lindex $option 0]
2703 set font [lindex $option 1]
2704 set global_config_new(gui.$font^^family) \
2705 [font configure $font -family]
2706 set global_config_new(gui.$font^^size) \
2707 [font configure $font -size]
2711 proc do_save_config {w} {
2712 if {[catch {save_config} err]} {
2713 error_popup "Failed to completely save options:\n\n$err"
2719 proc do_windows_shortcut {} {
2720 global gitdir appname argv0
2722 set reponame [lindex [file split \
2723 [file normalize [file dirname $gitdir]]] \
2727 set desktop [exec cygpath \
2735 set fn [tk_getSaveFile \
2737 -title "$appname ($reponame): Create Desktop Icon" \
2738 -initialdir $desktop \
2739 -initialfile "Git $reponame.bat"]
2743 set sh [exec cygpath \
2747 set me [exec cygpath \
2751 set gd [exec cygpath \
2755 regsub -all ' $me "'\\''" me
2756 regsub -all ' $gd "'\\''" gd
2757 puts $fd "@ECHO Starting git-gui... Please wait..."
2758 puts -nonewline $fd "@\"$sh\" --login -c \""
2759 puts -nonewline $fd "GIT_DIR='$gd'"
2760 puts -nonewline $fd " '$me'"
2764 error_popup "Cannot write script:\n\n$err"
2769 proc do_macosx_app {} {
2770 global gitdir appname argv0 env
2772 set reponame [lindex [file split \
2773 [file normalize [file dirname $gitdir]]] \
2776 set fn [tk_getSaveFile \
2778 -title "$appname ($reponame): Create Desktop Icon" \
2779 -initialdir [file join $env(HOME) Desktop] \
2780 -initialfile "Git $reponame.app"]
2783 set Contents [file join $fn Contents]
2784 set MacOS [file join $Contents MacOS]
2785 set exe [file join $MacOS git-gui]
2789 set fd [open [file join $Contents Info.plist] w]
2790 puts $fd {<?xml version="1.0" encoding="UTF-8"?>
2791 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
2792 <plist version="1.0">
2794 <key>CFBundleDevelopmentRegion</key>
2795 <string>English</string>
2796 <key>CFBundleExecutable</key>
2797 <string>git-gui</string>
2798 <key>CFBundleIdentifier</key>
2799 <string>org.spearce.git-gui</string>
2800 <key>CFBundleInfoDictionaryVersion</key>
2801 <string>6.0</string>
2802 <key>CFBundlePackageType</key>
2803 <string>APPL</string>
2804 <key>CFBundleSignature</key>
2805 <string>????</string>
2806 <key>CFBundleVersion</key>
2807 <string>1.0</string>
2808 <key>NSPrincipalClass</key>
2809 <string>NSApplication</string>
2814 set fd [open $exe w]
2815 set gd [file normalize $gitdir]
2816 set ep [file normalize [exec git --exec-path]]
2817 regsub -all ' $gd "'\\''" gd
2818 regsub -all ' $ep "'\\''" ep
2819 puts $fd "#!/bin/sh"
2820 foreach name [array names env] {
2821 if {[string match GIT_* $name]} {
2822 regsub -all ' $env($name) "'\\''" v
2823 puts $fd "export $name='$v'"
2826 puts $fd "export PATH='$ep':\$PATH"
2827 puts $fd "export GIT_DIR='$gd'"
2828 puts $fd "exec [file normalize $argv0]"
2831 file attributes $exe -permissions u+x,g+x,o+x
2833 error_popup "Cannot write icon:\n\n$err"
2838 proc toggle_or_diff {w x y} {
2839 global file_states file_lists current_diff ui_index ui_other
2840 global last_clicked selected_paths
2842 set pos [split [$w index @$x,$y] .]
2843 set lno [lindex $pos 0]
2844 set col [lindex $pos 1]
2845 set path [lindex $file_lists($w) [expr {$lno - 1}]]
2851 set last_clicked [list $w $lno]
2852 array unset selected_paths
2853 $ui_index tag remove in_sel 0.0 end
2854 $ui_other tag remove in_sel 0.0 end
2857 if {$current_diff eq $path} {
2858 set after {reshow_diff;}
2862 switch -glob -- [lindex $file_states($path) 0] {
2869 "Removing [short_path $path] from commit" \
2871 [concat $after {set ui_status_value {Ready.}}]
2875 "Adding [short_path $path]" \
2877 [concat $after {set ui_status_value {Ready.}}]
2881 show_diff $path $w $lno
2885 proc add_one_to_selection {w x y} {
2887 global last_clicked selected_paths
2889 set pos [split [$w index @$x,$y] .]
2890 set lno [lindex $pos 0]
2891 set col [lindex $pos 1]
2892 set path [lindex $file_lists($w) [expr {$lno - 1}]]
2898 set last_clicked [list $w $lno]
2899 if {[catch {set in_sel $selected_paths($path)}]} {
2903 unset selected_paths($path)
2904 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
2906 set selected_paths($path) 1
2907 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
2911 proc add_range_to_selection {w x y} {
2913 global last_clicked selected_paths
2915 if {[lindex $last_clicked 0] ne $w} {
2916 toggle_or_diff $w $x $y
2920 set pos [split [$w index @$x,$y] .]
2921 set lno [lindex $pos 0]
2922 set lc [lindex $last_clicked 1]
2931 foreach path [lrange $file_lists($w) \
2932 [expr {$begin - 1}] \
2933 [expr {$end - 1}]] {
2934 set selected_paths($path) 1
2936 $w tag add in_sel $begin.0 [expr {$end + 1}].0
2939 ######################################################################
2943 set cursor_ptr arrow
2944 font create font_diff -family Courier -size 10
2948 eval font configure font_ui [font actual [.dummy cget -font]]
2952 font create font_uibold
2953 font create font_diffbold
2958 } elseif {[is_MacOSX]} {
2966 proc apply_config {} {
2967 global repo_config font_descs
2969 foreach option $font_descs {
2970 set name [lindex $option 0]
2971 set font [lindex $option 1]
2973 foreach {cn cv} $repo_config(gui.$name) {
2974 font configure $font $cn $cv
2977 error_popup "Invalid font specified in gui.$name:\n\n$err"
2979 foreach {cn cv} [font configure $font] {
2980 font configure ${font}bold $cn $cv
2982 font configure ${font}bold -weight bold
2986 set default_config(gui.trustmtime) false
2987 set default_config(gui.pullsummary) true
2988 set default_config(gui.partialinclude) false
2989 set default_config(gui.diffcontext) 5
2990 set default_config(gui.fontui) [font configure font_ui]
2991 set default_config(gui.fontdiff) [font configure font_diff]
2993 {fontui font_ui {Main Font}}
2994 {fontdiff font_diff {Diff/Console Font}}
2999 ######################################################################
3005 menu .mbar -tearoff 0
3006 .mbar add cascade -label Repository -menu .mbar.repository
3007 .mbar add cascade -label Edit -menu .mbar.edit
3008 if {!$single_commit} {
3009 .mbar add cascade -label Branch -menu .mbar.branch
3011 .mbar add cascade -label Commit -menu .mbar.commit
3012 if {!$single_commit} {
3013 .mbar add cascade -label Fetch -menu .mbar.fetch
3014 .mbar add cascade -label Pull -menu .mbar.pull
3015 .mbar add cascade -label Push -menu .mbar.push
3017 . configure -menu .mbar
3019 # -- Repository Menu
3021 menu .mbar.repository
3022 .mbar.repository add command \
3023 -label {Visualize Current Branch} \
3024 -command {do_gitk {}} \
3027 .mbar.repository add command \
3028 -label {Visualize All Branches} \
3029 -command {do_gitk {--all}} \
3032 .mbar.repository add separator
3034 if {!$single_commit} {
3035 .mbar.repository add command -label {Compress Database} \
3039 .mbar.repository add command -label {Verify Database} \
3040 -command do_fsck_objects \
3043 .mbar.repository add separator
3046 .mbar.repository add command \
3047 -label {Create Desktop Icon} \
3048 -command do_windows_shortcut \
3050 } elseif {[is_MacOSX]} {
3051 .mbar.repository add command \
3052 -label {Create Desktop Icon} \
3053 -command do_macosx_app \
3058 .mbar.repository add command -label Quit \
3060 -accelerator $M1T-Q \
3066 .mbar.edit add command -label Undo \
3067 -command {catch {[focus] edit undo}} \
3068 -accelerator $M1T-Z \
3070 .mbar.edit add command -label Redo \
3071 -command {catch {[focus] edit redo}} \
3072 -accelerator $M1T-Y \
3074 .mbar.edit add separator
3075 .mbar.edit add command -label Cut \
3076 -command {catch {tk_textCut [focus]}} \
3077 -accelerator $M1T-X \
3079 .mbar.edit add command -label Copy \
3080 -command {catch {tk_textCopy [focus]}} \
3081 -accelerator $M1T-C \
3083 .mbar.edit add command -label Paste \
3084 -command {catch {tk_textPaste [focus]; [focus] see insert}} \
3085 -accelerator $M1T-V \
3087 .mbar.edit add command -label Delete \
3088 -command {catch {[focus] delete sel.first sel.last}} \
3091 .mbar.edit add separator
3092 .mbar.edit add command -label {Select All} \
3093 -command {catch {[focus] tag add sel 0.0 end}} \
3094 -accelerator $M1T-A \
3099 if {!$single_commit} {
3102 .mbar.branch add command -label {Create...} \
3103 -command do_create_branch \
3105 lappend disable_on_lock [list .mbar.branch entryconf \
3106 [.mbar.branch index last] -state]
3108 .mbar.branch add command -label {Delete...} \
3109 -command do_delete_branch \
3111 lappend disable_on_lock [list .mbar.branch entryconf \
3112 [.mbar.branch index last] -state]
3119 .mbar.commit add radiobutton \
3120 -label {New Commit} \
3121 -command do_select_commit_type \
3122 -variable selected_commit_type \
3125 lappend disable_on_lock \
3126 [list .mbar.commit entryconf [.mbar.commit index last] -state]
3128 .mbar.commit add radiobutton \
3129 -label {Amend Last Commit} \
3130 -command do_select_commit_type \
3131 -variable selected_commit_type \
3134 lappend disable_on_lock \
3135 [list .mbar.commit entryconf [.mbar.commit index last] -state]
3137 .mbar.commit add separator
3139 .mbar.commit add command -label Rescan \
3140 -command do_rescan \
3143 lappend disable_on_lock \
3144 [list .mbar.commit entryconf [.mbar.commit index last] -state]
3146 .mbar.commit add command -label {Add To Commit} \
3147 -command do_include_selection \
3149 lappend disable_on_lock \
3150 [list .mbar.commit entryconf [.mbar.commit index last] -state]
3152 .mbar.commit add command -label {Add All To Commit} \
3153 -command do_include_all \
3154 -accelerator $M1T-I \
3156 lappend disable_on_lock \
3157 [list .mbar.commit entryconf [.mbar.commit index last] -state]
3159 .mbar.commit add command -label {Remove From Commit} \
3160 -command do_remove_selection \
3162 lappend disable_on_lock \
3163 [list .mbar.commit entryconf [.mbar.commit index last] -state]
3165 .mbar.commit add command -label {Revert Changes} \
3166 -command do_revert_selection \
3168 lappend disable_on_lock \
3169 [list .mbar.commit entryconf [.mbar.commit index last] -state]
3171 .mbar.commit add separator
3173 .mbar.commit add command -label {Sign Off} \
3174 -command do_signoff \
3175 -accelerator $M1T-S \
3178 .mbar.commit add command -label Commit \
3179 -command do_commit \
3180 -accelerator $M1T-Return \
3182 lappend disable_on_lock \
3183 [list .mbar.commit entryconf [.mbar.commit index last] -state]
3185 # -- Transport menus
3187 if {!$single_commit} {
3194 # -- Apple Menu (Mac OS X only)
3196 .mbar add cascade -label Apple -menu .mbar.apple
3199 .mbar.apple add command -label "About $appname" \
3202 .mbar.apple add command -label "$appname Options..." \
3203 -command do_options \
3208 .mbar.edit add separator
3209 .mbar.edit add command -label {Options...} \
3210 -command do_options \
3215 if {[file exists /usr/local/miga/lib/gui-miga]} {
3217 global gitdir ui_status_value
3218 if {![lock_index update]} return
3219 set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""]
3220 set miga_fd [open "|$cmd" r]
3221 fconfigure $miga_fd -blocking 0
3222 fileevent $miga_fd readable [list miga_done $miga_fd]
3223 set ui_status_value {Running miga...}
3225 proc miga_done {fd} {
3230 rescan [list set ui_status_value {Ready.}]
3233 .mbar add cascade -label Tools -menu .mbar.tools
3235 .mbar.tools add command -label "Migrate" \
3238 lappend disable_on_lock \
3239 [list .mbar.tools entryconf [.mbar.tools index last] -state]
3244 .mbar add cascade -label Help -menu .mbar.help
3247 .mbar.help add command -label "About $appname" \
3259 -text {Current Branch:} \
3264 -textvariable current_branch \
3268 pack .branch.l1 -side left
3269 pack .branch.cb -side left -fill x
3270 pack .branch -side top -fill x
3272 # -- Main Window Layout
3274 panedwindow .vpane -orient vertical
3275 panedwindow .vpane.files -orient horizontal
3276 .vpane add .vpane.files -sticky nsew -height 100 -width 400
3277 pack .vpane -anchor n -side top -fill both -expand 1
3279 # -- Index File List
3281 frame .vpane.files.index -height 100 -width 400
3282 label .vpane.files.index.title -text {Modified Files} \
3285 text $ui_index -background white -borderwidth 0 \
3286 -width 40 -height 10 \
3288 -cursor $cursor_ptr \
3289 -yscrollcommand {.vpane.files.index.sb set} \
3291 scrollbar .vpane.files.index.sb -command [list $ui_index yview]
3292 pack .vpane.files.index.title -side top -fill x
3293 pack .vpane.files.index.sb -side right -fill y
3294 pack $ui_index -side left -fill both -expand 1
3295 .vpane.files add .vpane.files.index -sticky nsew
3297 # -- Other (Add) File List
3299 frame .vpane.files.other -height 100 -width 100
3300 label .vpane.files.other.title -text {Untracked Files} \
3303 text $ui_other -background white -borderwidth 0 \
3304 -width 40 -height 10 \
3306 -cursor $cursor_ptr \
3307 -yscrollcommand {.vpane.files.other.sb set} \
3309 scrollbar .vpane.files.other.sb -command [list $ui_other yview]
3310 pack .vpane.files.other.title -side top -fill x
3311 pack .vpane.files.other.sb -side right -fill y
3312 pack $ui_other -side left -fill both -expand 1
3313 .vpane.files add .vpane.files.other -sticky nsew
3315 foreach i [list $ui_index $ui_other] {
3316 $i tag conf in_diff -font font_uibold
3317 $i tag conf in_sel \
3318 -background [$i cget -foreground] \
3319 -foreground [$i cget -background]
3323 # -- Diff and Commit Area
3325 frame .vpane.lower -height 300 -width 400
3326 frame .vpane.lower.commarea
3327 frame .vpane.lower.diff -relief sunken -borderwidth 1
3328 pack .vpane.lower.commarea -side top -fill x
3329 pack .vpane.lower.diff -side bottom -fill both -expand 1
3330 .vpane add .vpane.lower -stick nsew
3332 # -- Commit Area Buttons
3334 frame .vpane.lower.commarea.buttons
3335 label .vpane.lower.commarea.buttons.l -text {} \
3339 pack .vpane.lower.commarea.buttons.l -side top -fill x
3340 pack .vpane.lower.commarea.buttons -side left -fill y
3342 button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
3343 -command do_rescan \
3345 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
3346 lappend disable_on_lock \
3347 {.vpane.lower.commarea.buttons.rescan conf -state}
3349 button .vpane.lower.commarea.buttons.incall -text {Add All} \
3350 -command do_include_all \
3352 pack .vpane.lower.commarea.buttons.incall -side top -fill x
3353 lappend disable_on_lock \
3354 {.vpane.lower.commarea.buttons.incall conf -state}
3356 button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
3357 -command do_signoff \
3359 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
3361 button .vpane.lower.commarea.buttons.commit -text {Commit} \
3362 -command do_commit \
3364 pack .vpane.lower.commarea.buttons.commit -side top -fill x
3365 lappend disable_on_lock \
3366 {.vpane.lower.commarea.buttons.commit conf -state}
3368 # -- Commit Message Buffer
3370 frame .vpane.lower.commarea.buffer
3371 frame .vpane.lower.commarea.buffer.header
3372 set ui_comm .vpane.lower.commarea.buffer.t
3373 set ui_coml .vpane.lower.commarea.buffer.header.l
3374 radiobutton .vpane.lower.commarea.buffer.header.new \
3375 -text {New Commit} \
3376 -command do_select_commit_type \
3377 -variable selected_commit_type \
3380 lappend disable_on_lock \
3381 [list .vpane.lower.commarea.buffer.header.new conf -state]
3382 radiobutton .vpane.lower.commarea.buffer.header.amend \
3383 -text {Amend Last Commit} \
3384 -command do_select_commit_type \
3385 -variable selected_commit_type \
3388 lappend disable_on_lock \
3389 [list .vpane.lower.commarea.buffer.header.amend conf -state]
3394 proc trace_commit_type {varname args} {
3395 global ui_coml commit_type
3396 switch -glob -- $commit_type {
3397 initial {set txt {Initial Commit Message:}}
3398 amend {set txt {Amended Commit Message:}}
3399 amend-initial {set txt {Amended Initial Commit Message:}}
3400 amend-merge {set txt {Amended Merge Commit Message:}}
3401 merge {set txt {Merge Commit Message:}}
3402 * {set txt {Commit Message:}}
3404 $ui_coml conf -text $txt
3406 trace add variable commit_type write trace_commit_type
3407 pack $ui_coml -side left -fill x
3408 pack .vpane.lower.commarea.buffer.header.amend -side right
3409 pack .vpane.lower.commarea.buffer.header.new -side right
3411 text $ui_comm -background white -borderwidth 1 \
3414 -autoseparators true \
3416 -width 75 -height 9 -wrap none \
3418 -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
3419 scrollbar .vpane.lower.commarea.buffer.sby \
3420 -command [list $ui_comm yview]
3421 pack .vpane.lower.commarea.buffer.header -side top -fill x
3422 pack .vpane.lower.commarea.buffer.sby -side right -fill y
3423 pack $ui_comm -side left -fill y
3424 pack .vpane.lower.commarea.buffer -side left -fill y
3426 # -- Commit Message Buffer Context Menu
3428 set ctxm .vpane.lower.commarea.buffer.ctxm
3429 menu $ctxm -tearoff 0
3433 -command {tk_textCut $ui_comm}
3437 -command {tk_textCopy $ui_comm}
3441 -command {tk_textPaste $ui_comm}
3445 -command {$ui_comm delete sel.first sel.last}
3448 -label {Select All} \
3450 -command {$ui_comm tag add sel 0.0 end}
3455 $ui_comm tag add sel 0.0 end
3456 tk_textCopy $ui_comm
3457 $ui_comm tag remove sel 0.0 end
3464 bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
3469 set diff_actions [list]
3470 proc trace_current_diff {varname args} {
3471 global current_diff diff_actions file_states
3472 if {$current_diff eq {}} {
3479 set s [mapdesc [lindex $file_states($p) 0] $p]
3481 set p [escape_path $p]
3485 .vpane.lower.diff.header.status configure -text $s
3486 .vpane.lower.diff.header.file configure -text $f
3487 .vpane.lower.diff.header.path configure -text $p
3488 foreach w $diff_actions {
3492 trace add variable current_diff write trace_current_diff
3494 frame .vpane.lower.diff.header -background orange
3495 label .vpane.lower.diff.header.status \
3496 -background orange \
3497 -width $max_status_desc \
3501 label .vpane.lower.diff.header.file \
3502 -background orange \
3506 label .vpane.lower.diff.header.path \
3507 -background orange \
3511 pack .vpane.lower.diff.header.status -side left
3512 pack .vpane.lower.diff.header.file -side left
3513 pack .vpane.lower.diff.header.path -fill x
3514 set ctxm .vpane.lower.diff.header.ctxm
3515 menu $ctxm -tearoff 0
3526 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3527 bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
3531 frame .vpane.lower.diff.body
3532 set ui_diff .vpane.lower.diff.body.t
3533 text $ui_diff -background white -borderwidth 0 \
3534 -width 80 -height 15 -wrap none \
3536 -xscrollcommand {.vpane.lower.diff.body.sbx set} \
3537 -yscrollcommand {.vpane.lower.diff.body.sby set} \
3539 scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
3540 -command [list $ui_diff xview]
3541 scrollbar .vpane.lower.diff.body.sby -orient vertical \
3542 -command [list $ui_diff yview]
3543 pack .vpane.lower.diff.body.sbx -side bottom -fill x
3544 pack .vpane.lower.diff.body.sby -side right -fill y
3545 pack $ui_diff -side left -fill both -expand 1
3546 pack .vpane.lower.diff.header -side top -fill x
3547 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
3549 $ui_diff tag conf d_@ -font font_diffbold
3550 $ui_diff tag conf d_+ -foreground blue
3551 $ui_diff tag conf d_- -foreground red
3552 $ui_diff tag conf d_++ -foreground {#00a000}
3553 $ui_diff tag conf d_-- -foreground {#a000a0}
3554 $ui_diff tag conf d_+- \
3556 -background {light goldenrod yellow}
3557 $ui_diff tag conf d_-+ \
3561 # -- Diff Body Context Menu
3563 set ctxm .vpane.lower.diff.body.ctxm
3564 menu $ctxm -tearoff 0
3568 -command {tk_textCopy $ui_diff}
3569 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3571 -label {Select All} \
3573 -command {$ui_diff tag add sel 0.0 end}
3574 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3579 $ui_diff tag add sel 0.0 end
3580 tk_textCopy $ui_diff
3581 $ui_diff tag remove sel 0.0 end
3583 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3586 -label {Decrease Font Size} \
3588 -command {incr_font_size font_diff -1}
3589 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3591 -label {Increase Font Size} \
3593 -command {incr_font_size font_diff 1}
3594 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3597 -label {Show Less Context} \
3599 -command {if {$repo_config(gui.diffcontext) >= 2} {
3600 incr repo_config(gui.diffcontext) -1
3603 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3605 -label {Show More Context} \
3608 incr repo_config(gui.diffcontext)
3611 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3613 $ctxm add command -label {Options...} \
3616 bind_button3 $ui_diff "tk_popup $ctxm %X %Y"
3620 set ui_status_value {Initializing...}
3621 label .status -textvariable ui_status_value \
3627 pack .status -anchor w -side bottom -fill x
3632 set gm $repo_config(gui.geometry)
3633 wm geometry . [lindex $gm 0]
3634 .vpane sash place 0 \
3635 [lindex [.vpane sash coord 0] 0] \
3637 .vpane.files sash place 0 \
3639 [lindex [.vpane.files sash coord 0] 1]
3645 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
3646 bind $ui_comm <$M1B-Key-i> {do_include_all;break}
3647 bind $ui_comm <$M1B-Key-I> {do_include_all;break}
3648 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
3649 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
3650 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
3651 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
3652 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
3653 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
3654 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3655 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3657 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
3658 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
3659 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
3660 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
3661 bind $ui_diff <$M1B-Key-v> {break}
3662 bind $ui_diff <$M1B-Key-V> {break}
3663 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3664 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3665 bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break}
3666 bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break}
3667 bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break}
3668 bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break}
3670 bind . <Destroy> do_quit
3671 bind all <Key-F5> do_rescan
3672 bind all <$M1B-Key-r> do_rescan
3673 bind all <$M1B-Key-R> do_rescan
3674 bind . <$M1B-Key-s> do_signoff
3675 bind . <$M1B-Key-S> do_signoff
3676 bind . <$M1B-Key-i> do_include_all
3677 bind . <$M1B-Key-I> do_include_all
3678 bind . <$M1B-Key-Return> do_commit
3679 bind all <$M1B-Key-q> do_quit
3680 bind all <$M1B-Key-Q> do_quit
3681 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
3682 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
3683 foreach i [list $ui_index $ui_other] {
3684 bind $i <Button-1> "toggle_or_diff $i %x %y; break"
3685 bind $i <$M1B-Button-1> "add_one_to_selection $i %x %y; break"
3686 bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
3690 set file_lists($ui_index) [list]
3691 set file_lists($ui_other) [list]
3695 set MERGE_HEAD [list]
3698 set current_branch {}
3700 set selected_commit_type new
3702 wm title . "$appname ([file normalize [file dirname $gitdir]])"
3703 focus -force $ui_comm
3705 # -- Warn the user about environmental problems. Cygwin's Tcl
3706 # does *not* pass its env array onto any processes it spawns.
3707 # This means that git processes get none of our environment.
3712 set msg "Possible environment issues exist.
3714 The following environment variables are probably
3715 going to be ignored by any Git subprocess run
3719 foreach name [array names env] {
3720 switch -regexp -- $name {
3721 {^GIT_INDEX_FILE$} -
3722 {^GIT_OBJECT_DIRECTORY$} -
3723 {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
3725 {^GIT_EXTERNAL_DIFF$} -
3729 {^GIT_CONFIG_LOCAL$} -
3730 {^GIT_(AUTHOR|COMMITTER)_DATE$} {
3731 append msg " - $name\n"
3734 {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
3735 append msg " - $name\n"
3737 set suggest_user $name
3741 if {$ignored_env > 0} {
3743 This is due to a known issue with the
3744 Tcl binary distributed by Cygwin."
3746 if {$suggest_user ne {}} {
3749 A good replacement for $suggest_user
3750 is placing values for the user.name and
3751 user.email settings into your personal
3757 unset ignored_env msg suggest_user name
3760 # -- Only initialize complex UI if we are going to stay running.
3762 if {!$single_commit} {
3766 populate_branch_menu .mbar.branch
3767 populate_fetch_menu .mbar.fetch
3768 populate_pull_menu .mbar.pull
3769 populate_push_menu .mbar.push
3772 lock_index begin-read