fbb3090ed16b851bbde11257c976bcb9d69a8db4
1#!/bin/sh
2# Tcl ignores the next line -*- tcl -*- \
3exec wish "$0" -- "$@"
4
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.
9
10set appname [lindex [file split $argv0] end]
11set gitdir {}
12
13######################################################################
14##
15## config
16
17proc is_many_config {name} {
18 switch -glob -- $name {
19 remote.*.fetch -
20 remote.*.push
21 {return 1}
22 *
23 {return 0}
24 }
25}
26
27proc load_config {include_global} {
28 global repo_config global_config default_config
29
30 array unset global_config
31 if {$include_global} {
32 catch {
33 set fd_rc [open "| git repo-config --global --list" r]
34 while {[gets $fd_rc line] >= 0} {
35 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
36 if {[is_many_config $name]} {
37 lappend global_config($name) $value
38 } else {
39 set global_config($name) $value
40 }
41 }
42 }
43 close $fd_rc
44 }
45 }
46
47 array unset repo_config
48 catch {
49 set fd_rc [open "| git repo-config --list" r]
50 while {[gets $fd_rc line] >= 0} {
51 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
52 if {[is_many_config $name]} {
53 lappend repo_config($name) $value
54 } else {
55 set repo_config($name) $value
56 }
57 }
58 }
59 close $fd_rc
60 }
61
62 foreach name [array names default_config] {
63 if {[catch {set v $global_config($name)}]} {
64 set global_config($name) $default_config($name)
65 }
66 if {[catch {set v $repo_config($name)}]} {
67 set repo_config($name) $default_config($name)
68 }
69 }
70}
71
72proc save_config {} {
73 global default_config font_descs
74 global repo_config global_config
75 global repo_config_new global_config_new
76
77 foreach option $font_descs {
78 set name [lindex $option 0]
79 set font [lindex $option 1]
80 font configure $font \
81 -family $global_config_new(gui.$font^^family) \
82 -size $global_config_new(gui.$font^^size)
83 font configure ${font}bold \
84 -family $global_config_new(gui.$font^^family) \
85 -size $global_config_new(gui.$font^^size)
86 set global_config_new(gui.$name) [font configure $font]
87 unset global_config_new(gui.$font^^family)
88 unset global_config_new(gui.$font^^size)
89 }
90
91 foreach name [array names default_config] {
92 set value $global_config_new($name)
93 if {$value != $global_config($name)} {
94 if {$value == $default_config($name)} {
95 catch {exec git repo-config --global --unset $name}
96 } else {
97 regsub -all "\[{}\]" $value {"} value
98 exec git repo-config --global $name $value
99 }
100 set global_config($name) $value
101 if {$value == $repo_config($name)} {
102 catch {exec git repo-config --unset $name}
103 set repo_config($name) $value
104 }
105 }
106 }
107
108 foreach name [array names default_config] {
109 set value $repo_config_new($name)
110 if {$value != $repo_config($name)} {
111 if {$value == $global_config($name)} {
112 catch {exec git repo-config --unset $name}
113 } else {
114 regsub -all "\[{}\]" $value {"} value
115 exec git repo-config $name $value
116 }
117 set repo_config($name) $value
118 }
119 }
120}
121
122proc error_popup {msg} {
123 global gitdir appname
124
125 set title $appname
126 if {$gitdir != {}} {
127 append title { (}
128 append title [lindex \
129 [file split [file normalize [file dirname $gitdir]]] \
130 end]
131 append title {)}
132 }
133 tk_messageBox \
134 -parent . \
135 -icon error \
136 -type ok \
137 -title "$title: error" \
138 -message $msg
139}
140
141proc info_popup {msg} {
142 global gitdir appname
143
144 set title $appname
145 if {$gitdir != {}} {
146 append title { (}
147 append title [lindex \
148 [file split [file normalize [file dirname $gitdir]]] \
149 end]
150 append title {)}
151 }
152 tk_messageBox \
153 -parent . \
154 -icon error \
155 -type ok \
156 -title $title \
157 -message $msg
158}
159
160######################################################################
161##
162## repository setup
163
164if { [catch {set cdup [exec git rev-parse --show-cdup]} err]
165 || [catch {set gitdir [exec git rev-parse --git-dir]} err]} {
166 catch {wm withdraw .}
167 error_popup "Cannot find the git directory:\n\n$err"
168 exit 1
169}
170if {$cdup != ""} {
171 cd $cdup
172}
173unset cdup
174
175set single_commit 0
176if {$appname == {git-citool}} {
177 set single_commit 1
178}
179
180######################################################################
181##
182## task management
183
184set status_active 0
185set diff_active 0
186set commit_active 0
187
188set disable_on_lock [list]
189set index_lock_type none
190
191set HEAD {}
192set PARENT {}
193set commit_type {}
194
195proc lock_index {type} {
196 global index_lock_type disable_on_lock
197
198 if {$index_lock_type == {none}} {
199 set index_lock_type $type
200 foreach w $disable_on_lock {
201 uplevel #0 $w disabled
202 }
203 return 1
204 } elseif {$index_lock_type == {begin-update} && $type == {update}} {
205 set index_lock_type $type
206 return 1
207 }
208 return 0
209}
210
211proc unlock_index {} {
212 global index_lock_type disable_on_lock
213
214 set index_lock_type none
215 foreach w $disable_on_lock {
216 uplevel #0 $w normal
217 }
218}
219
220######################################################################
221##
222## status
223
224proc repository_state {hdvar ctvar} {
225 global gitdir
226 upvar $hdvar hd $ctvar ct
227
228 if {[catch {set hd [exec git rev-parse --verify HEAD]}]} {
229 set ct initial
230 } elseif {[file exists [file join $gitdir MERGE_HEAD]]} {
231 set ct merge
232 } else {
233 set ct normal
234 }
235}
236
237proc update_status {{final Ready.}} {
238 global HEAD PARENT commit_type
239 global ui_index ui_other ui_status_value ui_comm
240 global status_active file_states
241 global repo_config
242
243 if {$status_active || ![lock_index read]} return
244
245 repository_state new_HEAD new_type
246 if {$commit_type == {amend}
247 && $new_type == {normal}
248 && $new_HEAD == $HEAD} {
249 } else {
250 set HEAD $new_HEAD
251 set PARENT $new_HEAD
252 set commit_type $new_type
253 }
254
255 array unset file_states
256
257 if {![$ui_comm edit modified]
258 || [string trim [$ui_comm get 0.0 end]] == {}} {
259 if {[load_message GITGUI_MSG]} {
260 } elseif {[load_message MERGE_MSG]} {
261 } elseif {[load_message SQUASH_MSG]} {
262 }
263 $ui_comm edit modified false
264 $ui_comm edit reset
265 }
266
267 if {$repo_config(gui.trustmtime) == {true}} {
268 update_status_stage2 {} $final
269 } else {
270 set status_active 1
271 set ui_status_value {Refreshing file status...}
272 set cmd [list git update-index]
273 lappend cmd -q
274 lappend cmd --unmerged
275 lappend cmd --ignore-missing
276 lappend cmd --refresh
277 set fd_rf [open "| $cmd" r]
278 fconfigure $fd_rf -blocking 0 -translation binary
279 fileevent $fd_rf readable \
280 [list update_status_stage2 $fd_rf $final]
281 }
282}
283
284proc update_status_stage2 {fd final} {
285 global gitdir PARENT commit_type
286 global ui_index ui_other ui_status_value ui_comm
287 global status_active
288 global buf_rdi buf_rdf buf_rlo
289
290 if {$fd != {}} {
291 read $fd
292 if {![eof $fd]} return
293 close $fd
294 }
295
296 set ls_others [list | git ls-files --others -z \
297 --exclude-per-directory=.gitignore]
298 set info_exclude [file join $gitdir info exclude]
299 if {[file readable $info_exclude]} {
300 lappend ls_others "--exclude-from=$info_exclude"
301 }
302
303 set buf_rdi {}
304 set buf_rdf {}
305 set buf_rlo {}
306
307 set status_active 3
308 set ui_status_value {Scanning for modified files ...}
309 set fd_di [open "| git diff-index --cached -z $PARENT" r]
310 set fd_df [open "| git diff-files -z" r]
311 set fd_lo [open $ls_others r]
312
313 fconfigure $fd_di -blocking 0 -translation binary
314 fconfigure $fd_df -blocking 0 -translation binary
315 fconfigure $fd_lo -blocking 0 -translation binary
316 fileevent $fd_di readable [list read_diff_index $fd_di $final]
317 fileevent $fd_df readable [list read_diff_files $fd_df $final]
318 fileevent $fd_lo readable [list read_ls_others $fd_lo $final]
319}
320
321proc load_message {file} {
322 global gitdir ui_comm
323
324 set f [file join $gitdir $file]
325 if {[file isfile $f]} {
326 if {[catch {set fd [open $f r]}]} {
327 return 0
328 }
329 set content [string trim [read $fd]]
330 close $fd
331 $ui_comm delete 0.0 end
332 $ui_comm insert end $content
333 return 1
334 }
335 return 0
336}
337
338proc read_diff_index {fd final} {
339 global buf_rdi
340
341 append buf_rdi [read $fd]
342 set c 0
343 set n [string length $buf_rdi]
344 while {$c < $n} {
345 set z1 [string first "\0" $buf_rdi $c]
346 if {$z1 == -1} break
347 incr z1
348 set z2 [string first "\0" $buf_rdi $z1]
349 if {$z2 == -1} break
350
351 set c $z2
352 incr z2 -1
353 display_file \
354 [string range $buf_rdi $z1 $z2] \
355 [string index $buf_rdi [expr $z1 - 2]]_
356 incr c
357 }
358 if {$c < $n} {
359 set buf_rdi [string range $buf_rdi $c end]
360 } else {
361 set buf_rdi {}
362 }
363
364 status_eof $fd buf_rdi $final
365}
366
367proc read_diff_files {fd final} {
368 global buf_rdf
369
370 append buf_rdf [read $fd]
371 set c 0
372 set n [string length $buf_rdf]
373 while {$c < $n} {
374 set z1 [string first "\0" $buf_rdf $c]
375 if {$z1 == -1} break
376 incr z1
377 set z2 [string first "\0" $buf_rdf $z1]
378 if {$z2 == -1} break
379
380 set c $z2
381 incr z2 -1
382 display_file \
383 [string range $buf_rdf $z1 $z2] \
384 _[string index $buf_rdf [expr $z1 - 2]]
385 incr c
386 }
387 if {$c < $n} {
388 set buf_rdf [string range $buf_rdf $c end]
389 } else {
390 set buf_rdf {}
391 }
392
393 status_eof $fd buf_rdf $final
394}
395
396proc read_ls_others {fd final} {
397 global buf_rlo
398
399 append buf_rlo [read $fd]
400 set pck [split $buf_rlo "\0"]
401 set buf_rlo [lindex $pck end]
402 foreach p [lrange $pck 0 end-1] {
403 display_file $p _O
404 }
405 status_eof $fd buf_rlo $final
406}
407
408proc status_eof {fd buf final} {
409 global status_active ui_status_value
410 upvar $buf to_clear
411
412 if {[eof $fd]} {
413 set to_clear {}
414 close $fd
415
416 if {[incr status_active -1] == 0} {
417 display_all_files
418 unlock_index
419 reshow_diff
420 set ui_status_value $final
421 }
422 }
423}
424
425######################################################################
426##
427## diff
428
429proc clear_diff {} {
430 global ui_diff ui_fname_value ui_fstatus_value ui_index ui_other
431
432 $ui_diff conf -state normal
433 $ui_diff delete 0.0 end
434 $ui_diff conf -state disabled
435
436 set ui_fname_value {}
437 set ui_fstatus_value {}
438
439 $ui_index tag remove in_diff 0.0 end
440 $ui_other tag remove in_diff 0.0 end
441}
442
443proc reshow_diff {} {
444 global ui_fname_value ui_status_value file_states
445
446 if {$ui_fname_value == {}
447 || [catch {set s $file_states($ui_fname_value)}]} {
448 clear_diff
449 } else {
450 show_diff $ui_fname_value
451 }
452}
453
454proc handle_empty_diff {} {
455 global ui_fname_value file_states file_lists
456
457 set path $ui_fname_value
458 set s $file_states($path)
459 if {[lindex $s 0] != {_M}} return
460
461 info_popup "No differences detected.
462
463[short_path $path] has no changes.
464
465The modification date of this file was updated by another
466application and you currently have the Trust File Modification
467Timestamps option enabled, so Git did not automatically detect
468that there are no content differences in this file.
469
470This file will now be removed from the modified files list, to
471prevent possible confusion.
472"
473 if {[catch {exec git update-index -- $path} err]} {
474 error_popup "Failed to refresh index:\n\n$err"
475 }
476
477 clear_diff
478 set old_w [mapcol [lindex $file_states($path) 0] $path]
479 set lno [lsearch -sorted $file_lists($old_w) $path]
480 if {$lno >= 0} {
481 set file_lists($old_w) \
482 [lreplace $file_lists($old_w) $lno $lno]
483 incr lno
484 $old_w conf -state normal
485 $old_w delete $lno.0 [expr $lno + 1].0
486 $old_w conf -state disabled
487 }
488}
489
490proc show_diff {path {w {}} {lno {}}} {
491 global file_states file_lists
492 global PARENT diff_3way diff_active
493 global ui_diff ui_fname_value ui_fstatus_value ui_status_value
494
495 if {$diff_active || ![lock_index read]} return
496
497 clear_diff
498 if {$w == {} || $lno == {}} {
499 foreach w [array names file_lists] {
500 set lno [lsearch -sorted $file_lists($w) $path]
501 if {$lno >= 0} {
502 incr lno
503 break
504 }
505 }
506 }
507 if {$w != {} && $lno >= 1} {
508 $w tag add in_diff $lno.0 [expr $lno + 1].0
509 }
510
511 set s $file_states($path)
512 set m [lindex $s 0]
513 set diff_3way 0
514 set diff_active 1
515 set ui_fname_value [escape_path $path]
516 set ui_fstatus_value [mapdesc $m $path]
517 set ui_status_value "Loading diff of [escape_path $path]..."
518
519 set cmd [list | git diff-index -p $PARENT -- $path]
520 switch $m {
521 MM {
522 set cmd [list | git diff-index -p -c $PARENT $path]
523 }
524 _O {
525 if {[catch {
526 set fd [open $path r]
527 set content [read $fd]
528 close $fd
529 } err ]} {
530 set diff_active 0
531 unlock_index
532 set ui_status_value "Unable to display [escape_path $path]"
533 error_popup "Error loading file:\n\n$err"
534 return
535 }
536 $ui_diff conf -state normal
537 $ui_diff insert end $content
538 $ui_diff conf -state disabled
539 set diff_active 0
540 unlock_index
541 set ui_status_value {Ready.}
542 return
543 }
544 }
545
546 if {[catch {set fd [open $cmd r]} err]} {
547 set diff_active 0
548 unlock_index
549 set ui_status_value "Unable to display [escape_path $path]"
550 error_popup "Error loading diff:\n\n$err"
551 return
552 }
553
554 fconfigure $fd -blocking 0 -translation auto
555 fileevent $fd readable [list read_diff $fd]
556}
557
558proc read_diff {fd} {
559 global ui_diff ui_status_value diff_3way diff_active
560 global repo_config
561
562 while {[gets $fd line] >= 0} {
563 if {[string match {diff --git *} $line]} continue
564 if {[string match {diff --combined *} $line]} continue
565 if {[string match {--- *} $line]} continue
566 if {[string match {+++ *} $line]} continue
567 if {[string match index* $line]} {
568 if {[string first , $line] >= 0} {
569 set diff_3way 1
570 }
571 }
572
573 $ui_diff conf -state normal
574 if {!$diff_3way} {
575 set x [string index $line 0]
576 switch -- $x {
577 "@" {set tags da}
578 "+" {set tags dp}
579 "-" {set tags dm}
580 default {set tags {}}
581 }
582 } else {
583 set x [string range $line 0 1]
584 switch -- $x {
585 default {set tags {}}
586 "@@" {set tags da}
587 "++" {set tags dp; set x " +"}
588 " +" {set tags {di bold}; set x "++"}
589 "+ " {set tags dni; set x "-+"}
590 "--" {set tags dm; set x " -"}
591 " -" {set tags {dm bold}; set x "--"}
592 "- " {set tags di; set x "+-"}
593 default {set tags {}}
594 }
595 set line [string replace $line 0 1 $x]
596 }
597 $ui_diff insert end $line $tags
598 $ui_diff insert end "\n"
599 $ui_diff conf -state disabled
600 }
601
602 if {[eof $fd]} {
603 close $fd
604 set diff_active 0
605 unlock_index
606 set ui_status_value {Ready.}
607
608 if {$repo_config(gui.trustmtime) == {true}
609 && [$ui_diff index end] == {2.0}} {
610 handle_empty_diff
611 }
612 }
613}
614
615######################################################################
616##
617## commit
618
619proc load_last_commit {} {
620 global HEAD PARENT commit_type ui_comm
621
622 if {$commit_type == {amend}} return
623 if {$commit_type != {normal}} {
624 error_popup "Can't amend a $commit_type commit."
625 return
626 }
627
628 set msg {}
629 set parent {}
630 set parent_count 0
631 if {[catch {
632 set fd [open "| git cat-file commit $HEAD" r]
633 while {[gets $fd line] > 0} {
634 if {[string match {parent *} $line]} {
635 set parent [string range $line 7 end]
636 incr parent_count
637 }
638 }
639 set msg [string trim [read $fd]]
640 close $fd
641 } err]} {
642 error_popup "Error loading commit data for amend:\n\n$err"
643 return
644 }
645
646 if {$parent_count == 0} {
647 set commit_type amend
648 set HEAD {}
649 set PARENT {}
650 update_status
651 } elseif {$parent_count == 1} {
652 set commit_type amend
653 set PARENT $parent
654 $ui_comm delete 0.0 end
655 $ui_comm insert end $msg
656 $ui_comm edit modified false
657 $ui_comm edit reset
658 update_status
659 } else {
660 error_popup {You can't amend a merge commit.}
661 return
662 }
663}
664
665proc commit_tree {} {
666 global tcl_platform HEAD gitdir commit_type file_states
667 global commit_active ui_status_value
668 global ui_comm
669
670 if {$commit_active || ![lock_index update]} return
671
672 # -- Our in memory state should match the repository.
673 #
674 repository_state curHEAD cur_type
675 if {$commit_type == {amend}
676 && $cur_type == {normal}
677 && $curHEAD == $HEAD} {
678 } elseif {$commit_type != $cur_type || $HEAD != $curHEAD} {
679 error_popup {Last scanned state does not match repository state.
680
681Its highly likely that another Git program modified the
682repository since our last scan. A rescan is required
683before committing.
684}
685 unlock_index
686 update_status
687 return
688 }
689
690 # -- At least one file should differ in the index.
691 #
692 set files_ready 0
693 foreach path [array names file_states] {
694 set s $file_states($path)
695 switch -glob -- [lindex $s 0] {
696 _? {continue}
697 A? -
698 D? -
699 M? {set files_ready 1; break}
700 U? {
701 error_popup "Unmerged files cannot be committed.
702
703File [short_path $path] has merge conflicts.
704You must resolve them and include the file before committing.
705"
706 unlock_index
707 return
708 }
709 default {
710 error_popup "Unknown file state [lindex $s 0] detected.
711
712File [short_path $path] cannot be committed by this program.
713"
714 }
715 }
716 }
717 if {!$files_ready} {
718 error_popup {No included files to commit.
719
720You must include at least 1 file before you can commit.
721}
722 unlock_index
723 return
724 }
725
726 # -- A message is required.
727 #
728 set msg [string trim [$ui_comm get 1.0 end]]
729 if {$msg == {}} {
730 error_popup {Please supply a commit message.
731
732A good commit message has the following format:
733
734- First line: Describe in one sentance what you did.
735- Second line: Blank
736- Remaining lines: Describe why this change is good.
737}
738 unlock_index
739 return
740 }
741
742 # -- Ask the pre-commit hook for the go-ahead.
743 #
744 set pchook [file join $gitdir hooks pre-commit]
745 if {$tcl_platform(platform) == {windows} && [file isfile $pchook]} {
746 set pchook [list sh -c \
747 "if test -x \"$pchook\"; then exec \"$pchook\"; fi"]
748 } elseif {[file executable $pchook]} {
749 set pchook [list $pchook]
750 } else {
751 set pchook {}
752 }
753 if {$pchook != {} && [catch {eval exec $pchook} err]} {
754 hook_failed_popup pre-commit $err
755 unlock_index
756 return
757 }
758
759 # -- Write the tree in the background.
760 #
761 set commit_active 1
762 set ui_status_value {Committing changes...}
763
764 set fd_wt [open "| git write-tree" r]
765 fileevent $fd_wt readable [list commit_stage2 $fd_wt $curHEAD $msg]
766}
767
768proc commit_stage2 {fd_wt curHEAD msg} {
769 global single_commit gitdir HEAD PARENT commit_type
770 global commit_active ui_status_value ui_comm
771 global file_states
772
773 gets $fd_wt tree_id
774 if {$tree_id == {} || [catch {close $fd_wt} err]} {
775 error_popup "write-tree failed:\n\n$err"
776 set commit_active 0
777 set ui_status_value {Commit failed.}
778 unlock_index
779 return
780 }
781
782 # -- Create the commit.
783 #
784 set cmd [list git commit-tree $tree_id]
785 if {$PARENT != {}} {
786 lappend cmd -p $PARENT
787 }
788 if {$commit_type == {merge}} {
789 if {[catch {
790 set fd_mh [open [file join $gitdir MERGE_HEAD] r]
791 while {[gets $fd_mh merge_head] >= 0} {
792 lappend cmd -p $merge_head
793 }
794 close $fd_mh
795 } err]} {
796 error_popup "Loading MERGE_HEAD failed:\n\n$err"
797 set commit_active 0
798 set ui_status_value {Commit failed.}
799 unlock_index
800 return
801 }
802 }
803 if {$PARENT == {}} {
804 # git commit-tree writes to stderr during initial commit.
805 lappend cmd 2>/dev/null
806 }
807 lappend cmd << $msg
808 if {[catch {set cmt_id [eval exec $cmd]} err]} {
809 error_popup "commit-tree failed:\n\n$err"
810 set commit_active 0
811 set ui_status_value {Commit failed.}
812 unlock_index
813 return
814 }
815
816 # -- Update the HEAD ref.
817 #
818 set reflogm commit
819 if {$commit_type != {normal}} {
820 append reflogm " ($commit_type)"
821 }
822 set i [string first "\n" $msg]
823 if {$i >= 0} {
824 append reflogm {: } [string range $msg 0 [expr $i - 1]]
825 } else {
826 append reflogm {: } $msg
827 }
828 set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD]
829 if {[catch {eval exec $cmd} err]} {
830 error_popup "update-ref failed:\n\n$err"
831 set commit_active 0
832 set ui_status_value {Commit failed.}
833 unlock_index
834 return
835 }
836
837 # -- Cleanup after ourselves.
838 #
839 catch {file delete [file join $gitdir MERGE_HEAD]}
840 catch {file delete [file join $gitdir MERGE_MSG]}
841 catch {file delete [file join $gitdir SQUASH_MSG]}
842 catch {file delete [file join $gitdir GITGUI_MSG]}
843
844 # -- Let rerere do its thing.
845 #
846 if {[file isdirectory [file join $gitdir rr-cache]]} {
847 catch {exec git rerere}
848 }
849
850 $ui_comm delete 0.0 end
851 $ui_comm edit modified false
852 $ui_comm edit reset
853
854 if {$single_commit} do_quit
855
856 # -- Update status without invoking any git commands.
857 #
858 set commit_active 0
859 set commit_type normal
860 set HEAD $cmt_id
861 set PARENT $cmt_id
862
863 foreach path [array names file_states] {
864 set s $file_states($path)
865 set m [lindex $s 0]
866 switch -glob -- $m {
867 A? -
868 M? -
869 D? {set m _[string index $m 1]}
870 }
871
872 if {$m == {__}} {
873 unset file_states($path)
874 } else {
875 lset file_states($path) 0 $m
876 }
877 }
878
879 display_all_files
880 unlock_index
881 reshow_diff
882 set ui_status_value \
883 "Changes committed as [string range $cmt_id 0 7]."
884}
885
886######################################################################
887##
888## fetch pull push
889
890proc fetch_from {remote} {
891 set w [new_console "fetch $remote" \
892 "Fetching new changes from $remote"]
893 set cmd [list git fetch]
894 lappend cmd $remote
895 console_exec $w $cmd
896}
897
898proc pull_remote {remote branch} {
899 global HEAD commit_type
900 global file_states
901
902 if {![lock_index update]} return
903
904 # -- Our in memory state should match the repository.
905 #
906 repository_state curHEAD cur_type
907 if {$commit_type != $cur_type || $HEAD != $curHEAD} {
908 error_popup {Last scanned state does not match repository state.
909
910Its highly likely that another Git program modified the
911repository since our last scan. A rescan is required
912before a pull can be started.
913}
914 unlock_index
915 update_status
916 return
917 }
918
919 # -- No differences should exist before a pull.
920 #
921 if {[array size file_states] != 0} {
922 error_popup {Uncommitted but modified files are present.
923
924You should not perform a pull with unmodified files in your working
925directory as Git would be unable to recover from an incorrect merge.
926
927Commit or throw away all changes before starting a pull operation.
928}
929 unlock_index
930 return
931 }
932
933 set w [new_console "pull $remote $branch" \
934 "Pulling new changes from branch $branch in $remote"]
935 set cmd [list git pull]
936 lappend cmd $remote
937 lappend cmd $branch
938 console_exec $w $cmd [list post_pull_remote $remote $branch]
939}
940
941proc post_pull_remote {remote branch success} {
942 global HEAD PARENT commit_type
943 global ui_status_value
944
945 unlock_index
946 if {$success} {
947 repository_state HEAD commit_type
948 set PARENT $HEAD
949 set $ui_status_value {Ready.}
950 } else {
951 update_status \
952 "Conflicts detected while pulling $branch from $remote."
953 }
954}
955
956proc push_to {remote} {
957 set w [new_console "push $remote" \
958 "Pushing changes to $remote"]
959 set cmd [list git push]
960 lappend cmd $remote
961 console_exec $w $cmd
962}
963
964######################################################################
965##
966## ui helpers
967
968proc mapcol {state path} {
969 global all_cols ui_other
970
971 if {[catch {set r $all_cols($state)}]} {
972 puts "error: no column for state={$state} $path"
973 return $ui_other
974 }
975 return $r
976}
977
978proc mapicon {state path} {
979 global all_icons
980
981 if {[catch {set r $all_icons($state)}]} {
982 puts "error: no icon for state={$state} $path"
983 return file_plain
984 }
985 return $r
986}
987
988proc mapdesc {state path} {
989 global all_descs
990
991 if {[catch {set r $all_descs($state)}]} {
992 puts "error: no desc for state={$state} $path"
993 return $state
994 }
995 return $r
996}
997
998proc escape_path {path} {
999 regsub -all "\n" $path "\\n" path
1000 return $path
1001}
1002
1003proc short_path {path} {
1004 return [escape_path [lindex [file split $path] end]]
1005}
1006
1007set next_icon_id 0
1008
1009proc merge_state {path new_state} {
1010 global file_states next_icon_id
1011
1012 set s0 [string index $new_state 0]
1013 set s1 [string index $new_state 1]
1014
1015 if {[catch {set info $file_states($path)}]} {
1016 set state __
1017 set icon n[incr next_icon_id]
1018 } else {
1019 set state [lindex $info 0]
1020 set icon [lindex $info 1]
1021 }
1022
1023 if {$s0 == {_}} {
1024 set s0 [string index $state 0]
1025 } elseif {$s0 == {*}} {
1026 set s0 _
1027 }
1028
1029 if {$s1 == {_}} {
1030 set s1 [string index $state 1]
1031 } elseif {$s1 == {*}} {
1032 set s1 _
1033 }
1034
1035 set file_states($path) [list $s0$s1 $icon]
1036 return $state
1037}
1038
1039proc display_file {path state} {
1040 global file_states file_lists status_active
1041
1042 set old_m [merge_state $path $state]
1043 if {$status_active} return
1044
1045 set s $file_states($path)
1046 set new_m [lindex $s 0]
1047 set new_w [mapcol $new_m $path]
1048 set old_w [mapcol $old_m $path]
1049 set new_icon [mapicon $new_m $path]
1050
1051 if {$new_w != $old_w} {
1052 set lno [lsearch -sorted $file_lists($old_w) $path]
1053 if {$lno >= 0} {
1054 incr lno
1055 $old_w conf -state normal
1056 $old_w delete $lno.0 [expr $lno + 1].0
1057 $old_w conf -state disabled
1058 }
1059
1060 lappend file_lists($new_w) $path
1061 set file_lists($new_w) [lsort $file_lists($new_w)]
1062 set lno [lsearch -sorted $file_lists($new_w) $path]
1063 incr lno
1064 $new_w conf -state normal
1065 $new_w image create $lno.0 \
1066 -align center -padx 5 -pady 1 \
1067 -name [lindex $s 1] \
1068 -image $new_icon
1069 $new_w insert $lno.1 "[escape_path $path]\n"
1070 $new_w conf -state disabled
1071 } elseif {$new_icon != [mapicon $old_m $path]} {
1072 $new_w conf -state normal
1073 $new_w image conf [lindex $s 1] -image $new_icon
1074 $new_w conf -state disabled
1075 }
1076}
1077
1078proc display_all_files {} {
1079 global ui_index ui_other file_states file_lists
1080
1081 $ui_index conf -state normal
1082 $ui_other conf -state normal
1083
1084 $ui_index delete 0.0 end
1085 $ui_other delete 0.0 end
1086
1087 set file_lists($ui_index) [list]
1088 set file_lists($ui_other) [list]
1089
1090 foreach path [lsort [array names file_states]] {
1091 set s $file_states($path)
1092 set m [lindex $s 0]
1093 set w [mapcol $m $path]
1094 lappend file_lists($w) $path
1095 $w image create end \
1096 -align center -padx 5 -pady 1 \
1097 -name [lindex $s 1] \
1098 -image [mapicon $m $path]
1099 $w insert end "[escape_path $path]\n"
1100 }
1101
1102 $ui_index conf -state disabled
1103 $ui_other conf -state disabled
1104}
1105
1106proc update_index {pathList} {
1107 global update_index_cp ui_status_value
1108
1109 if {![lock_index update]} return
1110
1111 set update_index_cp 0
1112 set totalCnt [llength $pathList]
1113 set batch [expr {int($totalCnt * .01) + 1}]
1114 if {$batch > 25} {set batch 25}
1115
1116 set ui_status_value "Including files ... 0/$totalCnt 0%"
1117 set ui_status_value [format \
1118 "Including files ... %i/%i files (%.2f%%)" \
1119 $update_index_cp \
1120 $totalCnt \
1121 0.0]
1122 set fd [open "| git update-index --add --remove -z --stdin" w]
1123 fconfigure $fd -blocking 0 -translation binary
1124 fileevent $fd writable [list \
1125 write_update_index \
1126 $fd \
1127 $pathList \
1128 $totalCnt \
1129 $batch \
1130 ]
1131}
1132
1133proc write_update_index {fd pathList totalCnt batch} {
1134 global update_index_cp ui_status_value
1135 global file_states ui_fname_value
1136
1137 if {$update_index_cp >= $totalCnt} {
1138 close $fd
1139 unlock_index
1140 set ui_status_value {Ready.}
1141 return
1142 }
1143
1144 for {set i $batch} \
1145 {$update_index_cp < $totalCnt && $i > 0} \
1146 {incr i -1} {
1147 set path [lindex $pathList $update_index_cp]
1148 incr update_index_cp
1149
1150 switch -- [lindex $file_states($path) 0] {
1151 AM -
1152 _O {set new A*}
1153 _M -
1154 MM {set new M*}
1155 AD -
1156 _D {set new D*}
1157 default {continue}
1158 }
1159
1160 puts -nonewline $fd $path
1161 puts -nonewline $fd "\0"
1162 display_file $path $new
1163 if {$ui_fname_value == $path} {
1164 show_diff $path
1165 }
1166 }
1167
1168 set ui_status_value [format \
1169 "Including files ... %i/%i files (%.2f%%)" \
1170 $update_index_cp \
1171 $totalCnt \
1172 [expr {100.0 * $update_index_cp / $totalCnt}]]
1173}
1174
1175######################################################################
1176##
1177## remote management
1178
1179proc load_all_remotes {} {
1180 global gitdir all_remotes repo_config
1181
1182 set all_remotes [list]
1183 set rm_dir [file join $gitdir remotes]
1184 if {[file isdirectory $rm_dir]} {
1185 set all_remotes [concat $all_remotes [glob \
1186 -types f \
1187 -tails \
1188 -nocomplain \
1189 -directory $rm_dir *]]
1190 }
1191
1192 foreach line [array names repo_config remote.*.url] {
1193 if {[regexp ^remote\.(.*)\.url\$ $line line name]} {
1194 lappend all_remotes $name
1195 }
1196 }
1197
1198 set all_remotes [lsort -unique $all_remotes]
1199}
1200
1201proc populate_remote_menu {m pfx op} {
1202 global all_remotes
1203
1204 foreach remote $all_remotes {
1205 $m add command -label "$pfx $remote..." \
1206 -command [list $op $remote] \
1207 -font font_ui
1208 }
1209}
1210
1211proc populate_pull_menu {m} {
1212 global gitdir repo_config all_remotes disable_on_lock
1213
1214 foreach remote $all_remotes {
1215 set rb {}
1216 if {[array get repo_config remote.$remote.url] != {}} {
1217 if {[array get repo_config remote.$remote.fetch] != {}} {
1218 regexp {^([^:]+):} \
1219 [lindex $repo_config(remote.$remote.fetch) 0] \
1220 line rb
1221 }
1222 } else {
1223 catch {
1224 set fd [open [file join $gitdir remotes $remote] r]
1225 while {[gets $fd line] >= 0} {
1226 if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} {
1227 break
1228 }
1229 }
1230 close $fd
1231 }
1232 }
1233
1234 set rb_short $rb
1235 regsub ^refs/heads/ $rb {} rb_short
1236 if {$rb_short != {}} {
1237 $m add command \
1238 -label "Branch $rb_short from $remote..." \
1239 -command [list pull_remote $remote $rb] \
1240 -font font_ui
1241 lappend disable_on_lock \
1242 [list $m entryconf [$m index last] -state]
1243 }
1244 }
1245}
1246
1247######################################################################
1248##
1249## icons
1250
1251set filemask {
1252#define mask_width 14
1253#define mask_height 15
1254static unsigned char mask_bits[] = {
1255 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1256 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1257 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1258}
1259
1260image create bitmap file_plain -background white -foreground black -data {
1261#define plain_width 14
1262#define plain_height 15
1263static unsigned char plain_bits[] = {
1264 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1265 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1266 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1267} -maskdata $filemask
1268
1269image create bitmap file_mod -background white -foreground blue -data {
1270#define mod_width 14
1271#define mod_height 15
1272static unsigned char mod_bits[] = {
1273 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1274 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1275 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1276} -maskdata $filemask
1277
1278image create bitmap file_fulltick -background white -foreground "#007000" -data {
1279#define file_fulltick_width 14
1280#define file_fulltick_height 15
1281static unsigned char file_fulltick_bits[] = {
1282 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1283 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1284 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1285} -maskdata $filemask
1286
1287image create bitmap file_parttick -background white -foreground "#005050" -data {
1288#define parttick_width 14
1289#define parttick_height 15
1290static unsigned char parttick_bits[] = {
1291 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1292 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1293 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1294} -maskdata $filemask
1295
1296image create bitmap file_question -background white -foreground black -data {
1297#define file_question_width 14
1298#define file_question_height 15
1299static unsigned char file_question_bits[] = {
1300 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1301 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1302 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1303} -maskdata $filemask
1304
1305image create bitmap file_removed -background white -foreground red -data {
1306#define file_removed_width 14
1307#define file_removed_height 15
1308static unsigned char file_removed_bits[] = {
1309 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1310 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1311 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1312} -maskdata $filemask
1313
1314image create bitmap file_merge -background white -foreground blue -data {
1315#define file_merge_width 14
1316#define file_merge_height 15
1317static unsigned char file_merge_bits[] = {
1318 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1319 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1320 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1321} -maskdata $filemask
1322
1323set ui_index .vpane.files.index.list
1324set ui_other .vpane.files.other.list
1325set max_status_desc 0
1326foreach i {
1327 {__ i plain "Unmodified"}
1328 {_M i mod "Modified"}
1329 {M_ i fulltick "Checked in"}
1330 {MM i parttick "Partially included"}
1331
1332 {_O o plain "Untracked"}
1333 {A_ o fulltick "Added"}
1334 {AM o parttick "Partially added"}
1335 {AD o question "Added (but now gone)"}
1336
1337 {_D i question "Missing"}
1338 {D_ i removed "Removed"}
1339 {DD i removed "Removed"}
1340 {DO i removed "Removed (still exists)"}
1341
1342 {UM i merge "Merge conflicts"}
1343 {U_ i merge "Merge conflicts"}
1344 } {
1345 if {$max_status_desc < [string length [lindex $i 3]]} {
1346 set max_status_desc [string length [lindex $i 3]]
1347 }
1348 if {[lindex $i 1] == {i}} {
1349 set all_cols([lindex $i 0]) $ui_index
1350 } else {
1351 set all_cols([lindex $i 0]) $ui_other
1352 }
1353 set all_icons([lindex $i 0]) file_[lindex $i 2]
1354 set all_descs([lindex $i 0]) [lindex $i 3]
1355}
1356unset filemask i
1357
1358######################################################################
1359##
1360## util
1361
1362proc is_MacOSX {} {
1363 global tcl_platform tk_library
1364 if {$tcl_platform(platform) == {unix}
1365 && $tcl_platform(os) == {Darwin}
1366 && [string match /Library/Frameworks/* $tk_library]} {
1367 return 1
1368 }
1369 return 0
1370}
1371
1372proc bind_button3 {w cmd} {
1373 bind $w <Any-Button-3> $cmd
1374 if {[is_MacOSX]} {
1375 bind $w <Control-Button-1> $cmd
1376 }
1377}
1378
1379proc incr_font_size {font {amt 1}} {
1380 set sz [font configure $font -size]
1381 incr sz $amt
1382 font configure $font -size $sz
1383 font configure ${font}bold -size $sz
1384}
1385
1386proc hook_failed_popup {hook msg} {
1387 global gitdir appname
1388
1389 set w .hookfail
1390 toplevel $w
1391
1392 frame $w.m
1393 label $w.m.l1 -text "$hook hook failed:" \
1394 -anchor w \
1395 -justify left \
1396 -font font_uibold
1397 text $w.m.t \
1398 -background white -borderwidth 1 \
1399 -relief sunken \
1400 -width 80 -height 10 \
1401 -font font_diff \
1402 -yscrollcommand [list $w.m.sby set]
1403 label $w.m.l2 \
1404 -text {You must correct the above errors before committing.} \
1405 -anchor w \
1406 -justify left \
1407 -font font_uibold
1408 scrollbar $w.m.sby -command [list $w.m.t yview]
1409 pack $w.m.l1 -side top -fill x
1410 pack $w.m.l2 -side bottom -fill x
1411 pack $w.m.sby -side right -fill y
1412 pack $w.m.t -side left -fill both -expand 1
1413 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1414
1415 $w.m.t insert 1.0 $msg
1416 $w.m.t conf -state disabled
1417
1418 button $w.ok -text OK \
1419 -width 15 \
1420 -font font_ui \
1421 -command "destroy $w"
1422 pack $w.ok -side bottom
1423
1424 bind $w <Visibility> "grab $w; focus $w"
1425 bind $w <Key-Return> "destroy $w"
1426 wm title $w "$appname ([lindex [file split \
1427 [file normalize [file dirname $gitdir]]] \
1428 end]): error"
1429 tkwait window $w
1430}
1431
1432set next_console_id 0
1433
1434proc new_console {short_title long_title} {
1435 global next_console_id console_data
1436 set w .console[incr next_console_id]
1437 set console_data($w) [list $short_title $long_title]
1438 return [console_init $w]
1439}
1440
1441proc console_init {w} {
1442 global console_cr console_data
1443 global gitdir appname M1B
1444
1445 set console_cr($w) 1.0
1446 toplevel $w
1447 frame $w.m
1448 label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
1449 -anchor w \
1450 -justify left \
1451 -font font_uibold
1452 text $w.m.t \
1453 -background white -borderwidth 1 \
1454 -relief sunken \
1455 -width 80 -height 10 \
1456 -font font_diff \
1457 -state disabled \
1458 -yscrollcommand [list $w.m.sby set]
1459 label $w.m.s -anchor w \
1460 -justify left \
1461 -font font_uibold
1462 scrollbar $w.m.sby -command [list $w.m.t yview]
1463 pack $w.m.l1 -side top -fill x
1464 pack $w.m.s -side bottom -fill x
1465 pack $w.m.sby -side right -fill y
1466 pack $w.m.t -side left -fill both -expand 1
1467 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1468
1469 menu $w.ctxm -tearoff 0
1470 $w.ctxm add command -label "Copy" \
1471 -font font_ui \
1472 -command "tk_textCopy $w.m.t"
1473 $w.ctxm add command -label "Select All" \
1474 -font font_ui \
1475 -command "$w.m.t tag add sel 0.0 end"
1476 $w.ctxm add command -label "Copy All" \
1477 -font font_ui \
1478 -command "
1479 $w.m.t tag add sel 0.0 end
1480 tk_textCopy $w.m.t
1481 $w.m.t tag remove sel 0.0 end
1482 "
1483
1484 button $w.ok -text {Running...} \
1485 -width 15 \
1486 -font font_ui \
1487 -state disabled \
1488 -command "destroy $w"
1489 pack $w.ok -side bottom
1490
1491 bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y"
1492 bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break"
1493 bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break"
1494 bind $w <Visibility> "focus $w"
1495 wm title $w "$appname ([lindex [file split \
1496 [file normalize [file dirname $gitdir]]] \
1497 end]): [lindex $console_data($w) 0]"
1498 return $w
1499}
1500
1501proc console_exec {w cmd {after {}}} {
1502 global tcl_platform
1503
1504 # -- Windows tosses the enviroment when we exec our child.
1505 # But most users need that so we have to relogin. :-(
1506 #
1507 if {$tcl_platform(platform) == {windows}} {
1508 set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
1509 }
1510
1511 # -- Tcl won't let us redirect both stdout and stderr to
1512 # the same pipe. So pass it through cat...
1513 #
1514 set cmd [concat | $cmd |& cat]
1515
1516 set fd_f [open $cmd r]
1517 fconfigure $fd_f -blocking 0 -translation binary
1518 fileevent $fd_f readable [list console_read $w $fd_f $after]
1519}
1520
1521proc console_read {w fd after} {
1522 global console_cr console_data
1523
1524 set buf [read $fd]
1525 if {$buf != {}} {
1526 if {![winfo exists $w]} {console_init $w}
1527 $w.m.t conf -state normal
1528 set c 0
1529 set n [string length $buf]
1530 while {$c < $n} {
1531 set cr [string first "\r" $buf $c]
1532 set lf [string first "\n" $buf $c]
1533 if {$cr < 0} {set cr [expr $n + 1]}
1534 if {$lf < 0} {set lf [expr $n + 1]}
1535
1536 if {$lf < $cr} {
1537 $w.m.t insert end [string range $buf $c $lf]
1538 set console_cr($w) [$w.m.t index {end -1c}]
1539 set c $lf
1540 incr c
1541 } else {
1542 $w.m.t delete $console_cr($w) end
1543 $w.m.t insert end "\n"
1544 $w.m.t insert end [string range $buf $c $cr]
1545 set c $cr
1546 incr c
1547 }
1548 }
1549 $w.m.t conf -state disabled
1550 $w.m.t see end
1551 }
1552
1553 fconfigure $fd -blocking 1
1554 if {[eof $fd]} {
1555 if {[catch {close $fd}]} {
1556 if {![winfo exists $w]} {console_init $w}
1557 $w.m.s conf -background red -text {Error: Command Failed}
1558 $w.ok conf -text Close
1559 $w.ok conf -state normal
1560 set ok 0
1561 } elseif {[winfo exists $w]} {
1562 $w.m.s conf -background green -text {Success}
1563 $w.ok conf -text Close
1564 $w.ok conf -state normal
1565 set ok 1
1566 }
1567 array unset console_cr $w
1568 array unset console_data $w
1569 if {$after != {}} {
1570 uplevel #0 $after $ok
1571 }
1572 return
1573 }
1574 fconfigure $fd -blocking 0
1575}
1576
1577######################################################################
1578##
1579## ui commands
1580
1581set starting_gitk_msg {Please wait... Starting gitk...}
1582
1583proc do_gitk {} {
1584 global tcl_platform ui_status_value starting_gitk_msg
1585
1586 set ui_status_value $starting_gitk_msg
1587 after 10000 {
1588 if {$ui_status_value == $starting_gitk_msg} {
1589 set ui_status_value {Ready.}
1590 }
1591 }
1592
1593 if {$tcl_platform(platform) == {windows}} {
1594 exec sh -c gitk &
1595 } else {
1596 exec gitk &
1597 }
1598}
1599
1600proc do_repack {} {
1601 set w [new_console "repack" "Repacking the object database"]
1602 set cmd [list git repack]
1603 lappend cmd -a
1604 lappend cmd -d
1605 console_exec $w $cmd
1606}
1607
1608set is_quitting 0
1609
1610proc do_quit {} {
1611 global gitdir ui_comm is_quitting repo_config
1612
1613 if {$is_quitting} return
1614 set is_quitting 1
1615
1616 # -- Stash our current commit buffer.
1617 #
1618 set save [file join $gitdir GITGUI_MSG]
1619 set msg [string trim [$ui_comm get 0.0 end]]
1620 if {[$ui_comm edit modified] && $msg != {}} {
1621 catch {
1622 set fd [open $save w]
1623 puts $fd [string trim [$ui_comm get 0.0 end]]
1624 close $fd
1625 }
1626 } elseif {$msg == {} && [file exists $save]} {
1627 file delete $save
1628 }
1629
1630 # -- Stash our current window geometry into this repository.
1631 #
1632 set cfg_geometry [list]
1633 lappend cfg_geometry [wm geometry .]
1634 lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
1635 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
1636 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1637 set rc_geometry {}
1638 }
1639 if {$cfg_geometry != $rc_geometry} {
1640 catch {exec git repo-config gui.geometry $cfg_geometry}
1641 }
1642
1643 destroy .
1644}
1645
1646proc do_rescan {} {
1647 update_status
1648}
1649
1650proc do_include_all {} {
1651 global file_states
1652
1653 if {![lock_index begin-update]} return
1654
1655 set pathList [list]
1656 foreach path [array names file_states] {
1657 set s $file_states($path)
1658 set m [lindex $s 0]
1659 switch -- $m {
1660 AM -
1661 MM -
1662 _M -
1663 _D {lappend pathList $path}
1664 }
1665 }
1666 if {$pathList == {}} {
1667 unlock_index
1668 } else {
1669 update_index $pathList
1670 }
1671}
1672
1673set GIT_COMMITTER_IDENT {}
1674
1675proc do_signoff {} {
1676 global ui_comm GIT_COMMITTER_IDENT
1677
1678 if {$GIT_COMMITTER_IDENT == {}} {
1679 if {[catch {set me [exec git var GIT_COMMITTER_IDENT]} err]} {
1680 error_popup "Unable to obtain your identity:\n\n$err"
1681 return
1682 }
1683 if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
1684 $me me GIT_COMMITTER_IDENT]} {
1685 error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me"
1686 return
1687 }
1688 }
1689
1690 set sob "Signed-off-by: $GIT_COMMITTER_IDENT"
1691 set last [$ui_comm get {end -1c linestart} {end -1c}]
1692 if {$last != $sob} {
1693 $ui_comm edit separator
1694 if {$last != {}
1695 && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
1696 $ui_comm insert end "\n"
1697 }
1698 $ui_comm insert end "\n$sob"
1699 $ui_comm edit separator
1700 $ui_comm see end
1701 }
1702}
1703
1704proc do_amend_last {} {
1705 load_last_commit
1706}
1707
1708proc do_commit {} {
1709 commit_tree
1710}
1711
1712proc do_options {} {
1713 global appname gitdir font_descs
1714 global repo_config global_config
1715 global repo_config_new global_config_new
1716
1717 load_config 1
1718 array unset repo_config_new
1719 array unset global_config_new
1720 foreach name [array names repo_config] {
1721 set repo_config_new($name) $repo_config($name)
1722 }
1723 foreach name [array names global_config] {
1724 set global_config_new($name) $global_config($name)
1725 }
1726 set reponame [lindex [file split \
1727 [file normalize [file dirname $gitdir]]] \
1728 end]
1729
1730 set w .options_editor
1731 toplevel $w
1732 wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
1733
1734 label $w.header -text "$appname Options" \
1735 -font font_uibold
1736 pack $w.header -side top -fill x
1737
1738 frame $w.buttons
1739 button $w.buttons.restore -text {Restore Defaults} \
1740 -font font_ui \
1741 -command do_restore_defaults
1742 pack $w.buttons.restore -side left
1743 button $w.buttons.save -text Save \
1744 -font font_ui \
1745 -command [list do_save_config $w]
1746 pack $w.buttons.save -side right
1747 button $w.buttons.cancel -text {Cancel} \
1748 -font font_ui \
1749 -command [list destroy $w]
1750 pack $w.buttons.cancel -side right
1751 pack $w.buttons -side bottom -fill x -pady 10 -padx 10
1752
1753 labelframe $w.repo -text "$reponame Repository" \
1754 -font font_ui \
1755 -relief raised -borderwidth 2
1756 labelframe $w.global -text {Global (All Repositories)} \
1757 -font font_ui \
1758 -relief raised -borderwidth 2
1759 pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5
1760 pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5
1761
1762 foreach option {
1763 {trustmtime {Trust File Modification Timestamps}}
1764 } {
1765 set name [lindex $option 0]
1766 set text [lindex $option 1]
1767 foreach f {repo global} {
1768 checkbutton $w.$f.$name -text $text \
1769 -variable ${f}_config_new(gui.$name) \
1770 -onvalue true \
1771 -offvalue false \
1772 -font font_ui
1773 pack $w.$f.$name -side top -anchor w
1774 }
1775 }
1776
1777 set all_fonts [lsort [font families]]
1778 foreach option $font_descs {
1779 set name [lindex $option 0]
1780 set font [lindex $option 1]
1781 set text [lindex $option 2]
1782
1783 set global_config_new(gui.$font^^family) \
1784 [font configure $font -family]
1785 set global_config_new(gui.$font^^size) \
1786 [font configure $font -size]
1787
1788 frame $w.global.$name
1789 label $w.global.$name.l -text "$text:" -font font_ui
1790 pack $w.global.$name.l -side left -anchor w -fill x
1791 eval tk_optionMenu $w.global.$name.family \
1792 global_config_new(gui.$font^^family) \
1793 $all_fonts
1794 spinbox $w.global.$name.size \
1795 -textvariable global_config_new(gui.$font^^size) \
1796 -from 2 -to 80 -increment 1 \
1797 -width 3 \
1798 -font font_ui
1799 pack $w.global.$name.size -side right -anchor e
1800 pack $w.global.$name.family -side right -anchor e
1801 pack $w.global.$name -side top -anchor w -fill x
1802 }
1803
1804 bind $w <Visibility> "grab $w; focus $w"
1805 bind $w <Key-Escape> "destroy $w"
1806 wm title $w "$appname ($reponame): Options"
1807 tkwait window $w
1808}
1809
1810proc do_restore_defaults {} {
1811 global font_descs default_config repo_config
1812 global repo_config_new global_config_new
1813
1814 foreach name [array names default_config] {
1815 set repo_config_new($name) $default_config($name)
1816 set global_config_new($name) $default_config($name)
1817 }
1818
1819 foreach option $font_descs {
1820 set name [lindex $option 0]
1821 set repo_config(gui.$name) $default_config(gui.$name)
1822 }
1823 apply_config
1824
1825 foreach option $font_descs {
1826 set name [lindex $option 0]
1827 set font [lindex $option 1]
1828 set global_config_new(gui.$font^^family) \
1829 [font configure $font -family]
1830 set global_config_new(gui.$font^^size) \
1831 [font configure $font -size]
1832 }
1833}
1834
1835proc do_save_config {w} {
1836 if {[catch {save_config} err]} {
1837 error_popup "Failed to completely save options:\n\n$err"
1838 }
1839 destroy $w
1840}
1841
1842# shift == 1: left click
1843# 3: right click
1844proc click {w x y shift wx wy} {
1845 global ui_index ui_other file_lists
1846
1847 set pos [split [$w index @$x,$y] .]
1848 set lno [lindex $pos 0]
1849 set col [lindex $pos 1]
1850 set path [lindex $file_lists($w) [expr $lno - 1]]
1851 if {$path == {}} return
1852
1853 if {$col > 0 && $shift == 1} {
1854 show_diff $path $w $lno
1855 }
1856}
1857
1858proc unclick {w x y} {
1859 global file_lists
1860
1861 set pos [split [$w index @$x,$y] .]
1862 set lno [lindex $pos 0]
1863 set col [lindex $pos 1]
1864 set path [lindex $file_lists($w) [expr $lno - 1]]
1865 if {$path == {}} return
1866
1867 if {$col == 0} {
1868 update_index [list $path]
1869 }
1870}
1871
1872######################################################################
1873##
1874## config defaults
1875
1876set cursor_ptr arrow
1877font create font_diff -family Courier -size 10
1878font create font_ui
1879catch {
1880 label .dummy
1881 eval font configure font_ui [font actual [.dummy cget -font]]
1882 destroy .dummy
1883}
1884
1885font create font_uibold
1886font create font_diffbold
1887
1888set M1B M1
1889set M1T M1
1890if {$tcl_platform(platform) == {windows}} {
1891 set M1B Control
1892 set M1T Ctrl
1893} elseif {[is_MacOSX]} {
1894 set M1B M1
1895 set M1T Cmd
1896}
1897
1898proc apply_config {} {
1899 global repo_config font_descs
1900
1901 foreach option $font_descs {
1902 set name [lindex $option 0]
1903 set font [lindex $option 1]
1904 if {[catch {
1905 foreach {cn cv} $repo_config(gui.$name) {
1906 font configure $font $cn $cv
1907 }
1908 } err]} {
1909 error_popup "Invalid font specified in gui.$name:\n\n$err"
1910 }
1911 foreach {cn cv} [font configure $font] {
1912 font configure ${font}bold $cn $cv
1913 }
1914 font configure ${font}bold -weight bold
1915 }
1916}
1917
1918set default_config(gui.trustmtime) false
1919set default_config(gui.fontui) [font configure font_ui]
1920set default_config(gui.fontdiff) [font configure font_diff]
1921set font_descs {
1922 {fontui font_ui {Main Font}}
1923 {fontdiff font_diff {Diff/Console Font}}
1924}
1925load_config 0
1926apply_config
1927
1928######################################################################
1929##
1930## ui construction
1931
1932# -- Menu Bar
1933menu .mbar -tearoff 0
1934.mbar add cascade -label Project -menu .mbar.project
1935.mbar add cascade -label Edit -menu .mbar.edit
1936.mbar add cascade -label Commit -menu .mbar.commit
1937if {!$single_commit} {
1938 .mbar add cascade -label Fetch -menu .mbar.fetch
1939 .mbar add cascade -label Pull -menu .mbar.pull
1940 .mbar add cascade -label Push -menu .mbar.push
1941}
1942. configure -menu .mbar
1943
1944# -- Project Menu
1945menu .mbar.project
1946.mbar.project add command -label Visualize \
1947 -command do_gitk \
1948 -font font_ui
1949if {!$single_commit} {
1950 .mbar.project add command -label {Repack Database} \
1951 -command do_repack \
1952 -font font_ui
1953}
1954.mbar.project add command -label Quit \
1955 -command do_quit \
1956 -accelerator $M1T-Q \
1957 -font font_ui
1958
1959# -- Edit Menu
1960#
1961menu .mbar.edit
1962.mbar.edit add command -label Undo \
1963 -command {catch {[focus] edit undo}} \
1964 -accelerator $M1T-Z \
1965 -font font_ui
1966.mbar.edit add command -label Redo \
1967 -command {catch {[focus] edit redo}} \
1968 -accelerator $M1T-Y \
1969 -font font_ui
1970.mbar.edit add separator
1971.mbar.edit add command -label Cut \
1972 -command {catch {tk_textCut [focus]}} \
1973 -accelerator $M1T-X \
1974 -font font_ui
1975.mbar.edit add command -label Copy \
1976 -command {catch {tk_textCopy [focus]}} \
1977 -accelerator $M1T-C \
1978 -font font_ui
1979.mbar.edit add command -label Paste \
1980 -command {catch {tk_textPaste [focus]; [focus] see insert}} \
1981 -accelerator $M1T-V \
1982 -font font_ui
1983.mbar.edit add command -label Delete \
1984 -command {catch {[focus] delete sel.first sel.last}} \
1985 -accelerator Del \
1986 -font font_ui
1987.mbar.edit add separator
1988.mbar.edit add command -label {Select All} \
1989 -command {catch {[focus] tag add sel 0.0 end}} \
1990 -accelerator $M1T-A \
1991 -font font_ui
1992.mbar.edit add separator
1993.mbar.edit add command -label {Options...} \
1994 -command do_options \
1995 -font font_ui
1996
1997# -- Commit Menu
1998menu .mbar.commit
1999.mbar.commit add command -label Rescan \
2000 -command do_rescan \
2001 -accelerator F5 \
2002 -font font_ui
2003lappend disable_on_lock \
2004 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2005.mbar.commit add command -label {Amend Last Commit} \
2006 -command do_amend_last \
2007 -font font_ui
2008lappend disable_on_lock \
2009 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2010.mbar.commit add command -label {Include All Files} \
2011 -command do_include_all \
2012 -accelerator $M1T-I \
2013 -font font_ui
2014lappend disable_on_lock \
2015 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2016.mbar.commit add command -label {Sign Off} \
2017 -command do_signoff \
2018 -accelerator $M1T-S \
2019 -font font_ui
2020.mbar.commit add command -label Commit \
2021 -command do_commit \
2022 -accelerator $M1T-Return \
2023 -font font_ui
2024lappend disable_on_lock \
2025 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2026
2027if {!$single_commit} {
2028 # -- Fetch Menu
2029 menu .mbar.fetch
2030
2031 # -- Pull Menu
2032 menu .mbar.pull
2033
2034 # -- Push Menu
2035 menu .mbar.push
2036}
2037
2038# -- Main Window Layout
2039panedwindow .vpane -orient vertical
2040panedwindow .vpane.files -orient horizontal
2041.vpane add .vpane.files -sticky nsew -height 100 -width 400
2042pack .vpane -anchor n -side top -fill both -expand 1
2043
2044# -- Index File List
2045frame .vpane.files.index -height 100 -width 400
2046label .vpane.files.index.title -text {Modified Files} \
2047 -background green \
2048 -font font_ui
2049text $ui_index -background white -borderwidth 0 \
2050 -width 40 -height 10 \
2051 -font font_ui \
2052 -cursor $cursor_ptr \
2053 -yscrollcommand {.vpane.files.index.sb set} \
2054 -state disabled
2055scrollbar .vpane.files.index.sb -command [list $ui_index yview]
2056pack .vpane.files.index.title -side top -fill x
2057pack .vpane.files.index.sb -side right -fill y
2058pack $ui_index -side left -fill both -expand 1
2059.vpane.files add .vpane.files.index -sticky nsew
2060
2061# -- Other (Add) File List
2062frame .vpane.files.other -height 100 -width 100
2063label .vpane.files.other.title -text {Untracked Files} \
2064 -background red \
2065 -font font_ui
2066text $ui_other -background white -borderwidth 0 \
2067 -width 40 -height 10 \
2068 -font font_ui \
2069 -cursor $cursor_ptr \
2070 -yscrollcommand {.vpane.files.other.sb set} \
2071 -state disabled
2072scrollbar .vpane.files.other.sb -command [list $ui_other yview]
2073pack .vpane.files.other.title -side top -fill x
2074pack .vpane.files.other.sb -side right -fill y
2075pack $ui_other -side left -fill both -expand 1
2076.vpane.files add .vpane.files.other -sticky nsew
2077
2078$ui_index tag conf in_diff -font font_uibold
2079$ui_other tag conf in_diff -font font_uibold
2080
2081# -- Diff and Commit Area
2082frame .vpane.lower -height 300 -width 400
2083frame .vpane.lower.commarea
2084frame .vpane.lower.diff -relief sunken -borderwidth 1
2085pack .vpane.lower.commarea -side top -fill x
2086pack .vpane.lower.diff -side bottom -fill both -expand 1
2087.vpane add .vpane.lower -stick nsew
2088
2089# -- Commit Area Buttons
2090frame .vpane.lower.commarea.buttons
2091label .vpane.lower.commarea.buttons.l -text {} \
2092 -anchor w \
2093 -justify left \
2094 -font font_ui
2095pack .vpane.lower.commarea.buttons.l -side top -fill x
2096pack .vpane.lower.commarea.buttons -side left -fill y
2097
2098button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
2099 -command do_rescan \
2100 -font font_ui
2101pack .vpane.lower.commarea.buttons.rescan -side top -fill x
2102lappend disable_on_lock \
2103 {.vpane.lower.commarea.buttons.rescan conf -state}
2104
2105button .vpane.lower.commarea.buttons.amend -text {Amend Last} \
2106 -command do_amend_last \
2107 -font font_ui
2108pack .vpane.lower.commarea.buttons.amend -side top -fill x
2109lappend disable_on_lock \
2110 {.vpane.lower.commarea.buttons.amend conf -state}
2111
2112button .vpane.lower.commarea.buttons.incall -text {Include All} \
2113 -command do_include_all \
2114 -font font_ui
2115pack .vpane.lower.commarea.buttons.incall -side top -fill x
2116lappend disable_on_lock \
2117 {.vpane.lower.commarea.buttons.incall conf -state}
2118
2119button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
2120 -command do_signoff \
2121 -font font_ui
2122pack .vpane.lower.commarea.buttons.signoff -side top -fill x
2123
2124button .vpane.lower.commarea.buttons.commit -text {Commit} \
2125 -command do_commit \
2126 -font font_ui
2127pack .vpane.lower.commarea.buttons.commit -side top -fill x
2128lappend disable_on_lock \
2129 {.vpane.lower.commarea.buttons.commit conf -state}
2130
2131# -- Commit Message Buffer
2132frame .vpane.lower.commarea.buffer
2133set ui_comm .vpane.lower.commarea.buffer.t
2134set ui_coml .vpane.lower.commarea.buffer.l
2135label $ui_coml -text {Commit Message:} \
2136 -anchor w \
2137 -justify left \
2138 -font font_ui
2139trace add variable commit_type write {uplevel #0 {
2140 switch -glob $commit_type \
2141 initial {$ui_coml conf -text {Initial Commit Message:}} \
2142 amend {$ui_coml conf -text {Amended Commit Message:}} \
2143 merge {$ui_coml conf -text {Merge Commit Message:}} \
2144 * {$ui_coml conf -text {Commit Message:}}
2145}}
2146text $ui_comm -background white -borderwidth 1 \
2147 -undo true \
2148 -maxundo 20 \
2149 -autoseparators true \
2150 -relief sunken \
2151 -width 75 -height 9 -wrap none \
2152 -font font_diff \
2153 -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
2154scrollbar .vpane.lower.commarea.buffer.sby \
2155 -command [list $ui_comm yview]
2156pack $ui_coml -side top -fill x
2157pack .vpane.lower.commarea.buffer.sby -side right -fill y
2158pack $ui_comm -side left -fill y
2159pack .vpane.lower.commarea.buffer -side left -fill y
2160
2161# -- Commit Message Buffer Context Menu
2162#
2163menu $ui_comm.ctxm -tearoff 0
2164$ui_comm.ctxm add command -label "Cut" \
2165 -font font_ui \
2166 -command "tk_textCut $ui_comm"
2167$ui_comm.ctxm add command -label "Copy" \
2168 -font font_ui \
2169 -command "tk_textCopy $ui_comm"
2170$ui_comm.ctxm add command -label "Paste" \
2171 -font font_ui \
2172 -command "tk_textPaste $ui_comm"
2173$ui_comm.ctxm add command -label "Delete" \
2174 -font font_ui \
2175 -command "$ui_comm delete sel.first sel.last"
2176$ui_comm.ctxm add separator
2177$ui_comm.ctxm add command -label "Select All" \
2178 -font font_ui \
2179 -command "$ui_comm tag add sel 0.0 end"
2180$ui_comm.ctxm add command -label "Copy All" \
2181 -font font_ui \
2182 -command "
2183 $ui_comm tag add sel 0.0 end
2184 tk_textCopy $ui_comm
2185 $ui_comm tag remove sel 0.0 end
2186 "
2187$ui_comm.ctxm add separator
2188$ui_comm.ctxm add command -label "Sign Off" \
2189 -font font_ui \
2190 -command do_signoff
2191bind_button3 $ui_comm "tk_popup $ui_comm.ctxm %X %Y"
2192
2193# -- Diff Header
2194set ui_fname_value {}
2195set ui_fstatus_value {}
2196frame .vpane.lower.diff.header -background orange
2197label .vpane.lower.diff.header.l1 -text {File:} \
2198 -background orange \
2199 -font font_ui
2200label .vpane.lower.diff.header.l2 -textvariable ui_fname_value \
2201 -background orange \
2202 -anchor w \
2203 -justify left \
2204 -font font_ui
2205label .vpane.lower.diff.header.l3 -text {Status:} \
2206 -background orange \
2207 -font font_ui
2208label .vpane.lower.diff.header.l4 -textvariable ui_fstatus_value \
2209 -background orange \
2210 -width $max_status_desc \
2211 -anchor w \
2212 -justify left \
2213 -font font_ui
2214pack .vpane.lower.diff.header.l1 -side left
2215pack .vpane.lower.diff.header.l2 -side left -fill x
2216pack .vpane.lower.diff.header.l4 -side right
2217pack .vpane.lower.diff.header.l3 -side right
2218
2219# -- Diff Body
2220frame .vpane.lower.diff.body
2221set ui_diff .vpane.lower.diff.body.t
2222text $ui_diff -background white -borderwidth 0 \
2223 -width 80 -height 15 -wrap none \
2224 -font font_diff \
2225 -xscrollcommand {.vpane.lower.diff.body.sbx set} \
2226 -yscrollcommand {.vpane.lower.diff.body.sby set} \
2227 -state disabled
2228scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
2229 -command [list $ui_diff xview]
2230scrollbar .vpane.lower.diff.body.sby -orient vertical \
2231 -command [list $ui_diff yview]
2232pack .vpane.lower.diff.body.sbx -side bottom -fill x
2233pack .vpane.lower.diff.body.sby -side right -fill y
2234pack $ui_diff -side left -fill both -expand 1
2235pack .vpane.lower.diff.header -side top -fill x
2236pack .vpane.lower.diff.body -side bottom -fill both -expand 1
2237
2238$ui_diff tag conf dm -foreground red
2239$ui_diff tag conf dp -foreground blue
2240$ui_diff tag conf di -foreground {#00a000}
2241$ui_diff tag conf dni -foreground {#a000a0}
2242$ui_diff tag conf da -font font_diffbold
2243$ui_diff tag conf bold -font font_diffbold
2244
2245# -- Diff Body Context Menu
2246#
2247menu $ui_diff.ctxm -tearoff 0
2248$ui_diff.ctxm add command -label "Copy" \
2249 -font font_ui \
2250 -command "tk_textCopy $ui_diff"
2251$ui_diff.ctxm add command -label "Select All" \
2252 -font font_ui \
2253 -command "$ui_diff tag add sel 0.0 end"
2254$ui_diff.ctxm add command -label "Copy All" \
2255 -font font_ui \
2256 -command "
2257 $ui_diff tag add sel 0.0 end
2258 tk_textCopy $ui_diff
2259 $ui_diff tag remove sel 0.0 end
2260 "
2261$ui_diff.ctxm add separator
2262$ui_diff.ctxm add command -label "Decrease Font Size" \
2263 -font font_ui \
2264 -command {incr_font_size font_diff -1}
2265$ui_diff.ctxm add command -label "Increase Font Size" \
2266 -font font_ui \
2267 -command {incr_font_size font_diff 1}
2268$ui_diff.ctxm add command -label {Options...} \
2269 -font font_ui \
2270 -command do_options
2271bind_button3 $ui_diff "tk_popup $ui_diff.ctxm %X %Y"
2272
2273# -- Status Bar
2274set ui_status_value {Initializing...}
2275label .status -textvariable ui_status_value \
2276 -anchor w \
2277 -justify left \
2278 -borderwidth 1 \
2279 -relief sunken \
2280 -font font_ui
2281pack .status -anchor w -side bottom -fill x
2282
2283# -- Load geometry
2284catch {
2285set gm $repo_config(gui.geometry)
2286wm geometry . [lindex $gm 0]
2287.vpane sash place 0 \
2288 [lindex [.vpane sash coord 0] 0] \
2289 [lindex $gm 1]
2290.vpane.files sash place 0 \
2291 [lindex $gm 2] \
2292 [lindex [.vpane.files sash coord 0] 1]
2293unset gm
2294}
2295
2296# -- Key Bindings
2297bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2298bind $ui_comm <$M1B-Key-i> {do_include_all;break}
2299bind $ui_comm <$M1B-Key-I> {do_include_all;break}
2300bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2301bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2302bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2303bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2304bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2305bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2306bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2307bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2308
2309bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2310bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2311bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2312bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2313bind $ui_diff <$M1B-Key-v> {break}
2314bind $ui_diff <$M1B-Key-V> {break}
2315bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2316bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2317bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break}
2318bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break}
2319bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break}
2320bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break}
2321
2322bind . <Destroy> do_quit
2323bind all <Key-F5> do_rescan
2324bind all <$M1B-Key-r> do_rescan
2325bind all <$M1B-Key-R> do_rescan
2326bind . <$M1B-Key-s> do_signoff
2327bind . <$M1B-Key-S> do_signoff
2328bind . <$M1B-Key-i> do_include_all
2329bind . <$M1B-Key-I> do_include_all
2330bind . <$M1B-Key-Return> do_commit
2331bind all <$M1B-Key-q> do_quit
2332bind all <$M1B-Key-Q> do_quit
2333bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
2334bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
2335foreach i [list $ui_index $ui_other] {
2336 bind $i <Button-1> {click %W %x %y 1 %X %Y; break}
2337 bind $i <ButtonRelease-1> {unclick %W %x %y; break}
2338 bind_button3 $i {click %W %x %y 3 %X %Y; break}
2339}
2340unset i
2341
2342set file_lists($ui_index) [list]
2343set file_lists($ui_other) [list]
2344
2345wm title . "$appname ([file normalize [file dirname $gitdir]])"
2346focus -force $ui_comm
2347if {!$single_commit} {
2348 load_all_remotes
2349 populate_remote_menu .mbar.fetch From fetch_from
2350 populate_remote_menu .mbar.push To push_to
2351 populate_pull_menu .mbar.pull
2352}
2353after 1 update_status