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 pch_error
668 global ui_status_value 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 set commit_active 1
743
744 # -- Ask the pre-commit hook for the go-ahead.
745 #
746 set pchook [file join $gitdir hooks pre-commit]
747 if {$tcl_platform(platform) == {windows} && [file isfile $pchook]} {
748 set pchook [list sh -c [concat \
749 "if test -x \"$pchook\";" \
750 "then exec \"$pchook\" 2>&1;" \
751 "fi"]]
752 } elseif {[file executable $pchook]} {
753 set pchook [list $pchook |& cat]
754 } else {
755 set pchook {}
756 }
757 if {$pchook != {}} {
758 set ui_status_value {Calling pre-commit hook...}
759 set pch_error {}
760 set fd_ph [open "| $pchook" r]
761 fconfigure $fd_ph -blocking 0 -translation binary
762 fileevent $fd_ph readable \
763 [list commit_stage1 $fd_ph $curHEAD $msg]
764 } else {
765 commit_stage2 $curHEAD $msg
766 }
767}
768
769proc commit_stage1 {fd_ph curHEAD msg} {
770 global commit_active pch_error ui_status_value
771
772 append pch_error [read $fd_ph]
773 fconfigure $fd_ph -blocking 1
774 if {[eof $fd_ph]} {
775 if {[catch {close $fd_ph}]} {
776 set ui_status_value {Commit declined by pre-commit hook.}
777 hook_failed_popup pre-commit $pch_error
778 unlock_index
779 set commit_active 0
780 set pch_error {}
781 return
782 }
783 commit_stage2 $curHEAD $msg
784 return
785 }
786 fconfigure $fd_ph -blocking 0
787}
788
789proc commit_stage2 {curHEAD msg} {
790 global ui_status_value
791
792 # -- Write the tree in the background.
793 #
794 set ui_status_value {Committing changes...}
795 set fd_wt [open "| git write-tree" r]
796 fileevent $fd_wt readable [list commit_stage3 $fd_wt $curHEAD $msg]
797}
798
799proc commit_stage3 {fd_wt curHEAD msg} {
800 global single_commit gitdir HEAD PARENT commit_type
801 global commit_active ui_status_value ui_comm
802 global file_states
803
804 gets $fd_wt tree_id
805 if {$tree_id == {} || [catch {close $fd_wt} err]} {
806 error_popup "write-tree failed:\n\n$err"
807 set commit_active 0
808 set ui_status_value {Commit failed.}
809 unlock_index
810 return
811 }
812
813 # -- Create the commit.
814 #
815 set cmd [list git commit-tree $tree_id]
816 if {$PARENT != {}} {
817 lappend cmd -p $PARENT
818 }
819 if {$commit_type == {merge}} {
820 if {[catch {
821 set fd_mh [open [file join $gitdir MERGE_HEAD] r]
822 while {[gets $fd_mh merge_head] >= 0} {
823 lappend cmd -p $merge_head
824 }
825 close $fd_mh
826 } err]} {
827 error_popup "Loading MERGE_HEAD failed:\n\n$err"
828 set commit_active 0
829 set ui_status_value {Commit failed.}
830 unlock_index
831 return
832 }
833 }
834 if {$PARENT == {}} {
835 # git commit-tree writes to stderr during initial commit.
836 lappend cmd 2>/dev/null
837 }
838 lappend cmd << $msg
839 if {[catch {set cmt_id [eval exec $cmd]} err]} {
840 error_popup "commit-tree failed:\n\n$err"
841 set commit_active 0
842 set ui_status_value {Commit failed.}
843 unlock_index
844 return
845 }
846
847 # -- Update the HEAD ref.
848 #
849 set reflogm commit
850 if {$commit_type != {normal}} {
851 append reflogm " ($commit_type)"
852 }
853 set i [string first "\n" $msg]
854 if {$i >= 0} {
855 append reflogm {: } [string range $msg 0 [expr $i - 1]]
856 } else {
857 append reflogm {: } $msg
858 }
859 set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD]
860 if {[catch {eval exec $cmd} err]} {
861 error_popup "update-ref failed:\n\n$err"
862 set commit_active 0
863 set ui_status_value {Commit failed.}
864 unlock_index
865 return
866 }
867
868 # -- Cleanup after ourselves.
869 #
870 catch {file delete [file join $gitdir MERGE_HEAD]}
871 catch {file delete [file join $gitdir MERGE_MSG]}
872 catch {file delete [file join $gitdir SQUASH_MSG]}
873 catch {file delete [file join $gitdir GITGUI_MSG]}
874
875 # -- Let rerere do its thing.
876 #
877 if {[file isdirectory [file join $gitdir rr-cache]]} {
878 catch {exec git rerere}
879 }
880
881 $ui_comm delete 0.0 end
882 $ui_comm edit modified false
883 $ui_comm edit reset
884
885 if {$single_commit} do_quit
886
887 # -- Update status without invoking any git commands.
888 #
889 set commit_active 0
890 set commit_type normal
891 set HEAD $cmt_id
892 set PARENT $cmt_id
893
894 foreach path [array names file_states] {
895 set s $file_states($path)
896 set m [lindex $s 0]
897 switch -glob -- $m {
898 A? -
899 M? -
900 D? {set m _[string index $m 1]}
901 }
902
903 if {$m == {__}} {
904 unset file_states($path)
905 } else {
906 lset file_states($path) 0 $m
907 }
908 }
909
910 display_all_files
911 unlock_index
912 reshow_diff
913 set ui_status_value \
914 "Changes committed as [string range $cmt_id 0 7]."
915}
916
917######################################################################
918##
919## fetch pull push
920
921proc fetch_from {remote} {
922 set w [new_console "fetch $remote" \
923 "Fetching new changes from $remote"]
924 set cmd [list git fetch]
925 lappend cmd $remote
926 console_exec $w $cmd
927}
928
929proc pull_remote {remote branch} {
930 global HEAD commit_type file_states repo_config
931
932 if {![lock_index update]} return
933
934 # -- Our in memory state should match the repository.
935 #
936 repository_state curHEAD cur_type
937 if {$commit_type != $cur_type || $HEAD != $curHEAD} {
938 error_popup {Last scanned state does not match repository state.
939
940Its highly likely that another Git program modified the
941repository since our last scan. A rescan is required
942before a pull can be started.
943}
944 unlock_index
945 update_status
946 return
947 }
948
949 # -- No differences should exist before a pull.
950 #
951 if {[array size file_states] != 0} {
952 error_popup {Uncommitted but modified files are present.
953
954You should not perform a pull with unmodified files in your working
955directory as Git would be unable to recover from an incorrect merge.
956
957Commit or throw away all changes before starting a pull operation.
958}
959 unlock_index
960 return
961 }
962
963 set w [new_console "pull $remote $branch" \
964 "Pulling new changes from branch $branch in $remote"]
965 set cmd [list git pull]
966 if {$repo_config(gui.pullsummary) == {false}} {
967 lappend cmd --no-summary
968 }
969 lappend cmd $remote
970 lappend cmd $branch
971 console_exec $w $cmd [list post_pull_remote $remote $branch]
972}
973
974proc post_pull_remote {remote branch success} {
975 global HEAD PARENT commit_type
976 global ui_status_value
977
978 unlock_index
979 if {$success} {
980 repository_state HEAD commit_type
981 set PARENT $HEAD
982 set $ui_status_value {Ready.}
983 } else {
984 update_status \
985 "Conflicts detected while pulling $branch from $remote."
986 }
987}
988
989proc push_to {remote} {
990 set w [new_console "push $remote" \
991 "Pushing changes to $remote"]
992 set cmd [list git push]
993 lappend cmd $remote
994 console_exec $w $cmd
995}
996
997######################################################################
998##
999## ui helpers
1000
1001proc mapcol {state path} {
1002 global all_cols ui_other
1003
1004 if {[catch {set r $all_cols($state)}]} {
1005 puts "error: no column for state={$state} $path"
1006 return $ui_other
1007 }
1008 return $r
1009}
1010
1011proc mapicon {state path} {
1012 global all_icons
1013
1014 if {[catch {set r $all_icons($state)}]} {
1015 puts "error: no icon for state={$state} $path"
1016 return file_plain
1017 }
1018 return $r
1019}
1020
1021proc mapdesc {state path} {
1022 global all_descs
1023
1024 if {[catch {set r $all_descs($state)}]} {
1025 puts "error: no desc for state={$state} $path"
1026 return $state
1027 }
1028 return $r
1029}
1030
1031proc escape_path {path} {
1032 regsub -all "\n" $path "\\n" path
1033 return $path
1034}
1035
1036proc short_path {path} {
1037 return [escape_path [lindex [file split $path] end]]
1038}
1039
1040set next_icon_id 0
1041
1042proc merge_state {path new_state} {
1043 global file_states next_icon_id
1044
1045 set s0 [string index $new_state 0]
1046 set s1 [string index $new_state 1]
1047
1048 if {[catch {set info $file_states($path)}]} {
1049 set state __
1050 set icon n[incr next_icon_id]
1051 } else {
1052 set state [lindex $info 0]
1053 set icon [lindex $info 1]
1054 }
1055
1056 if {$s0 == {_}} {
1057 set s0 [string index $state 0]
1058 } elseif {$s0 == {*}} {
1059 set s0 _
1060 }
1061
1062 if {$s1 == {_}} {
1063 set s1 [string index $state 1]
1064 } elseif {$s1 == {*}} {
1065 set s1 _
1066 }
1067
1068 set file_states($path) [list $s0$s1 $icon]
1069 return $state
1070}
1071
1072proc display_file {path state} {
1073 global file_states file_lists status_active
1074
1075 set old_m [merge_state $path $state]
1076 if {$status_active} return
1077
1078 set s $file_states($path)
1079 set new_m [lindex $s 0]
1080 set new_w [mapcol $new_m $path]
1081 set old_w [mapcol $old_m $path]
1082 set new_icon [mapicon $new_m $path]
1083
1084 if {$new_w != $old_w} {
1085 set lno [lsearch -sorted $file_lists($old_w) $path]
1086 if {$lno >= 0} {
1087 incr lno
1088 $old_w conf -state normal
1089 $old_w delete $lno.0 [expr $lno + 1].0
1090 $old_w conf -state disabled
1091 }
1092
1093 lappend file_lists($new_w) $path
1094 set file_lists($new_w) [lsort $file_lists($new_w)]
1095 set lno [lsearch -sorted $file_lists($new_w) $path]
1096 incr lno
1097 $new_w conf -state normal
1098 $new_w image create $lno.0 \
1099 -align center -padx 5 -pady 1 \
1100 -name [lindex $s 1] \
1101 -image $new_icon
1102 $new_w insert $lno.1 "[escape_path $path]\n"
1103 $new_w conf -state disabled
1104 } elseif {$new_icon != [mapicon $old_m $path]} {
1105 $new_w conf -state normal
1106 $new_w image conf [lindex $s 1] -image $new_icon
1107 $new_w conf -state disabled
1108 }
1109}
1110
1111proc display_all_files {} {
1112 global ui_index ui_other file_states file_lists
1113
1114 $ui_index conf -state normal
1115 $ui_other conf -state normal
1116
1117 $ui_index delete 0.0 end
1118 $ui_other delete 0.0 end
1119
1120 set file_lists($ui_index) [list]
1121 set file_lists($ui_other) [list]
1122
1123 foreach path [lsort [array names file_states]] {
1124 set s $file_states($path)
1125 set m [lindex $s 0]
1126 set w [mapcol $m $path]
1127 lappend file_lists($w) $path
1128 $w image create end \
1129 -align center -padx 5 -pady 1 \
1130 -name [lindex $s 1] \
1131 -image [mapicon $m $path]
1132 $w insert end "[escape_path $path]\n"
1133 }
1134
1135 $ui_index conf -state disabled
1136 $ui_other conf -state disabled
1137}
1138
1139proc update_index {pathList} {
1140 global update_index_cp ui_status_value
1141
1142 if {![lock_index update]} return
1143
1144 set update_index_cp 0
1145 set totalCnt [llength $pathList]
1146 set batch [expr {int($totalCnt * .01) + 1}]
1147 if {$batch > 25} {set batch 25}
1148
1149 set ui_status_value "Including files ... 0/$totalCnt 0%"
1150 set ui_status_value [format \
1151 "Including files ... %i/%i files (%.2f%%)" \
1152 $update_index_cp \
1153 $totalCnt \
1154 0.0]
1155 set fd [open "| git update-index --add --remove -z --stdin" w]
1156 fconfigure $fd -blocking 0 -translation binary
1157 fileevent $fd writable [list \
1158 write_update_index \
1159 $fd \
1160 $pathList \
1161 $totalCnt \
1162 $batch \
1163 ]
1164}
1165
1166proc write_update_index {fd pathList totalCnt batch} {
1167 global update_index_cp ui_status_value
1168 global file_states ui_fname_value
1169
1170 if {$update_index_cp >= $totalCnt} {
1171 close $fd
1172 unlock_index
1173 set ui_status_value {Ready.}
1174 return
1175 }
1176
1177 for {set i $batch} \
1178 {$update_index_cp < $totalCnt && $i > 0} \
1179 {incr i -1} {
1180 set path [lindex $pathList $update_index_cp]
1181 incr update_index_cp
1182
1183 switch -- [lindex $file_states($path) 0] {
1184 AM -
1185 _O {set new A*}
1186 _M -
1187 MM {set new M*}
1188 AD -
1189 _D {set new D*}
1190 default {continue}
1191 }
1192
1193 puts -nonewline $fd $path
1194 puts -nonewline $fd "\0"
1195 display_file $path $new
1196 if {$ui_fname_value == $path} {
1197 show_diff $path
1198 }
1199 }
1200
1201 set ui_status_value [format \
1202 "Including files ... %i/%i files (%.2f%%)" \
1203 $update_index_cp \
1204 $totalCnt \
1205 [expr {100.0 * $update_index_cp / $totalCnt}]]
1206}
1207
1208######################################################################
1209##
1210## remote management
1211
1212proc load_all_remotes {} {
1213 global gitdir all_remotes repo_config
1214
1215 set all_remotes [list]
1216 set rm_dir [file join $gitdir remotes]
1217 if {[file isdirectory $rm_dir]} {
1218 set all_remotes [concat $all_remotes [glob \
1219 -types f \
1220 -tails \
1221 -nocomplain \
1222 -directory $rm_dir *]]
1223 }
1224
1225 foreach line [array names repo_config remote.*.url] {
1226 if {[regexp ^remote\.(.*)\.url\$ $line line name]} {
1227 lappend all_remotes $name
1228 }
1229 }
1230
1231 set all_remotes [lsort -unique $all_remotes]
1232}
1233
1234proc populate_remote_menu {m pfx op} {
1235 global all_remotes
1236
1237 foreach remote $all_remotes {
1238 $m add command -label "$pfx $remote..." \
1239 -command [list $op $remote] \
1240 -font font_ui
1241 }
1242}
1243
1244proc populate_pull_menu {m} {
1245 global gitdir repo_config all_remotes disable_on_lock
1246
1247 foreach remote $all_remotes {
1248 set rb {}
1249 if {[array get repo_config remote.$remote.url] != {}} {
1250 if {[array get repo_config remote.$remote.fetch] != {}} {
1251 regexp {^([^:]+):} \
1252 [lindex $repo_config(remote.$remote.fetch) 0] \
1253 line rb
1254 }
1255 } else {
1256 catch {
1257 set fd [open [file join $gitdir remotes $remote] r]
1258 while {[gets $fd line] >= 0} {
1259 if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} {
1260 break
1261 }
1262 }
1263 close $fd
1264 }
1265 }
1266
1267 set rb_short $rb
1268 regsub ^refs/heads/ $rb {} rb_short
1269 if {$rb_short != {}} {
1270 $m add command \
1271 -label "Branch $rb_short from $remote..." \
1272 -command [list pull_remote $remote $rb] \
1273 -font font_ui
1274 lappend disable_on_lock \
1275 [list $m entryconf [$m index last] -state]
1276 }
1277 }
1278}
1279
1280######################################################################
1281##
1282## icons
1283
1284set filemask {
1285#define mask_width 14
1286#define mask_height 15
1287static unsigned char mask_bits[] = {
1288 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1289 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1290 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1291}
1292
1293image create bitmap file_plain -background white -foreground black -data {
1294#define plain_width 14
1295#define plain_height 15
1296static unsigned char plain_bits[] = {
1297 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1298 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1299 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1300} -maskdata $filemask
1301
1302image create bitmap file_mod -background white -foreground blue -data {
1303#define mod_width 14
1304#define mod_height 15
1305static unsigned char mod_bits[] = {
1306 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1307 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1308 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1309} -maskdata $filemask
1310
1311image create bitmap file_fulltick -background white -foreground "#007000" -data {
1312#define file_fulltick_width 14
1313#define file_fulltick_height 15
1314static unsigned char file_fulltick_bits[] = {
1315 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1316 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1317 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1318} -maskdata $filemask
1319
1320image create bitmap file_parttick -background white -foreground "#005050" -data {
1321#define parttick_width 14
1322#define parttick_height 15
1323static unsigned char parttick_bits[] = {
1324 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1325 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1326 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1327} -maskdata $filemask
1328
1329image create bitmap file_question -background white -foreground black -data {
1330#define file_question_width 14
1331#define file_question_height 15
1332static unsigned char file_question_bits[] = {
1333 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1334 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1335 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1336} -maskdata $filemask
1337
1338image create bitmap file_removed -background white -foreground red -data {
1339#define file_removed_width 14
1340#define file_removed_height 15
1341static unsigned char file_removed_bits[] = {
1342 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1343 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1344 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1345} -maskdata $filemask
1346
1347image create bitmap file_merge -background white -foreground blue -data {
1348#define file_merge_width 14
1349#define file_merge_height 15
1350static unsigned char file_merge_bits[] = {
1351 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1352 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1353 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1354} -maskdata $filemask
1355
1356set ui_index .vpane.files.index.list
1357set ui_other .vpane.files.other.list
1358set max_status_desc 0
1359foreach i {
1360 {__ i plain "Unmodified"}
1361 {_M i mod "Modified"}
1362 {M_ i fulltick "Checked in"}
1363 {MM i parttick "Partially included"}
1364
1365 {_O o plain "Untracked"}
1366 {A_ o fulltick "Added"}
1367 {AM o parttick "Partially added"}
1368 {AD o question "Added (but now gone)"}
1369
1370 {_D i question "Missing"}
1371 {D_ i removed "Removed"}
1372 {DD i removed "Removed"}
1373 {DO i removed "Removed (still exists)"}
1374
1375 {UM i merge "Merge conflicts"}
1376 {U_ i merge "Merge conflicts"}
1377 } {
1378 if {$max_status_desc < [string length [lindex $i 3]]} {
1379 set max_status_desc [string length [lindex $i 3]]
1380 }
1381 if {[lindex $i 1] == {i}} {
1382 set all_cols([lindex $i 0]) $ui_index
1383 } else {
1384 set all_cols([lindex $i 0]) $ui_other
1385 }
1386 set all_icons([lindex $i 0]) file_[lindex $i 2]
1387 set all_descs([lindex $i 0]) [lindex $i 3]
1388}
1389unset filemask i
1390
1391######################################################################
1392##
1393## util
1394
1395proc is_MacOSX {} {
1396 global tcl_platform tk_library
1397 if {$tcl_platform(platform) == {unix}
1398 && $tcl_platform(os) == {Darwin}
1399 && [string match /Library/Frameworks/* $tk_library]} {
1400 return 1
1401 }
1402 return 0
1403}
1404
1405proc bind_button3 {w cmd} {
1406 bind $w <Any-Button-3> $cmd
1407 if {[is_MacOSX]} {
1408 bind $w <Control-Button-1> $cmd
1409 }
1410}
1411
1412proc incr_font_size {font {amt 1}} {
1413 set sz [font configure $font -size]
1414 incr sz $amt
1415 font configure $font -size $sz
1416 font configure ${font}bold -size $sz
1417}
1418
1419proc hook_failed_popup {hook msg} {
1420 global gitdir appname
1421
1422 set w .hookfail
1423 toplevel $w
1424
1425 frame $w.m
1426 label $w.m.l1 -text "$hook hook failed:" \
1427 -anchor w \
1428 -justify left \
1429 -font font_uibold
1430 text $w.m.t \
1431 -background white -borderwidth 1 \
1432 -relief sunken \
1433 -width 80 -height 10 \
1434 -font font_diff \
1435 -yscrollcommand [list $w.m.sby set]
1436 label $w.m.l2 \
1437 -text {You must correct the above errors before committing.} \
1438 -anchor w \
1439 -justify left \
1440 -font font_uibold
1441 scrollbar $w.m.sby -command [list $w.m.t yview]
1442 pack $w.m.l1 -side top -fill x
1443 pack $w.m.l2 -side bottom -fill x
1444 pack $w.m.sby -side right -fill y
1445 pack $w.m.t -side left -fill both -expand 1
1446 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1447
1448 $w.m.t insert 1.0 $msg
1449 $w.m.t conf -state disabled
1450
1451 button $w.ok -text OK \
1452 -width 15 \
1453 -font font_ui \
1454 -command "destroy $w"
1455 pack $w.ok -side bottom
1456
1457 bind $w <Visibility> "grab $w; focus $w"
1458 bind $w <Key-Return> "destroy $w"
1459 wm title $w "$appname ([lindex [file split \
1460 [file normalize [file dirname $gitdir]]] \
1461 end]): error"
1462 tkwait window $w
1463}
1464
1465set next_console_id 0
1466
1467proc new_console {short_title long_title} {
1468 global next_console_id console_data
1469 set w .console[incr next_console_id]
1470 set console_data($w) [list $short_title $long_title]
1471 return [console_init $w]
1472}
1473
1474proc console_init {w} {
1475 global console_cr console_data
1476 global gitdir appname M1B
1477
1478 set console_cr($w) 1.0
1479 toplevel $w
1480 frame $w.m
1481 label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
1482 -anchor w \
1483 -justify left \
1484 -font font_uibold
1485 text $w.m.t \
1486 -background white -borderwidth 1 \
1487 -relief sunken \
1488 -width 80 -height 10 \
1489 -font font_diff \
1490 -state disabled \
1491 -yscrollcommand [list $w.m.sby set]
1492 label $w.m.s -anchor w \
1493 -justify left \
1494 -font font_uibold
1495 scrollbar $w.m.sby -command [list $w.m.t yview]
1496 pack $w.m.l1 -side top -fill x
1497 pack $w.m.s -side bottom -fill x
1498 pack $w.m.sby -side right -fill y
1499 pack $w.m.t -side left -fill both -expand 1
1500 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1501
1502 menu $w.ctxm -tearoff 0
1503 $w.ctxm add command -label "Copy" \
1504 -font font_ui \
1505 -command "tk_textCopy $w.m.t"
1506 $w.ctxm add command -label "Select All" \
1507 -font font_ui \
1508 -command "$w.m.t tag add sel 0.0 end"
1509 $w.ctxm add command -label "Copy All" \
1510 -font font_ui \
1511 -command "
1512 $w.m.t tag add sel 0.0 end
1513 tk_textCopy $w.m.t
1514 $w.m.t tag remove sel 0.0 end
1515 "
1516
1517 button $w.ok -text {Running...} \
1518 -width 15 \
1519 -font font_ui \
1520 -state disabled \
1521 -command "destroy $w"
1522 pack $w.ok -side bottom
1523
1524 bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y"
1525 bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break"
1526 bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break"
1527 bind $w <Visibility> "focus $w"
1528 wm title $w "$appname ([lindex [file split \
1529 [file normalize [file dirname $gitdir]]] \
1530 end]): [lindex $console_data($w) 0]"
1531 return $w
1532}
1533
1534proc console_exec {w cmd {after {}}} {
1535 global tcl_platform
1536
1537 # -- Windows tosses the enviroment when we exec our child.
1538 # But most users need that so we have to relogin. :-(
1539 #
1540 if {$tcl_platform(platform) == {windows}} {
1541 set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
1542 }
1543
1544 # -- Tcl won't let us redirect both stdout and stderr to
1545 # the same pipe. So pass it through cat...
1546 #
1547 set cmd [concat | $cmd |& cat]
1548
1549 set fd_f [open $cmd r]
1550 fconfigure $fd_f -blocking 0 -translation binary
1551 fileevent $fd_f readable [list console_read $w $fd_f $after]
1552}
1553
1554proc console_read {w fd after} {
1555 global console_cr console_data
1556
1557 set buf [read $fd]
1558 if {$buf != {}} {
1559 if {![winfo exists $w]} {console_init $w}
1560 $w.m.t conf -state normal
1561 set c 0
1562 set n [string length $buf]
1563 while {$c < $n} {
1564 set cr [string first "\r" $buf $c]
1565 set lf [string first "\n" $buf $c]
1566 if {$cr < 0} {set cr [expr $n + 1]}
1567 if {$lf < 0} {set lf [expr $n + 1]}
1568
1569 if {$lf < $cr} {
1570 $w.m.t insert end [string range $buf $c $lf]
1571 set console_cr($w) [$w.m.t index {end -1c}]
1572 set c $lf
1573 incr c
1574 } else {
1575 $w.m.t delete $console_cr($w) end
1576 $w.m.t insert end "\n"
1577 $w.m.t insert end [string range $buf $c $cr]
1578 set c $cr
1579 incr c
1580 }
1581 }
1582 $w.m.t conf -state disabled
1583 $w.m.t see end
1584 }
1585
1586 fconfigure $fd -blocking 1
1587 if {[eof $fd]} {
1588 if {[catch {close $fd}]} {
1589 if {![winfo exists $w]} {console_init $w}
1590 $w.m.s conf -background red -text {Error: Command Failed}
1591 $w.ok conf -text Close
1592 $w.ok conf -state normal
1593 set ok 0
1594 } elseif {[winfo exists $w]} {
1595 $w.m.s conf -background green -text {Success}
1596 $w.ok conf -text Close
1597 $w.ok conf -state normal
1598 set ok 1
1599 }
1600 array unset console_cr $w
1601 array unset console_data $w
1602 if {$after != {}} {
1603 uplevel #0 $after $ok
1604 }
1605 return
1606 }
1607 fconfigure $fd -blocking 0
1608}
1609
1610######################################################################
1611##
1612## ui commands
1613
1614set starting_gitk_msg {Please wait... Starting gitk...}
1615
1616proc do_gitk {} {
1617 global tcl_platform ui_status_value starting_gitk_msg
1618
1619 set ui_status_value $starting_gitk_msg
1620 after 10000 {
1621 if {$ui_status_value == $starting_gitk_msg} {
1622 set ui_status_value {Ready.}
1623 }
1624 }
1625
1626 if {$tcl_platform(platform) == {windows}} {
1627 exec sh -c gitk &
1628 } else {
1629 exec gitk &
1630 }
1631}
1632
1633proc do_repack {} {
1634 set w [new_console "repack" "Repacking the object database"]
1635 set cmd [list git repack]
1636 lappend cmd -a
1637 lappend cmd -d
1638 console_exec $w $cmd
1639}
1640
1641set is_quitting 0
1642
1643proc do_quit {} {
1644 global gitdir ui_comm is_quitting repo_config
1645
1646 if {$is_quitting} return
1647 set is_quitting 1
1648
1649 # -- Stash our current commit buffer.
1650 #
1651 set save [file join $gitdir GITGUI_MSG]
1652 set msg [string trim [$ui_comm get 0.0 end]]
1653 if {[$ui_comm edit modified] && $msg != {}} {
1654 catch {
1655 set fd [open $save w]
1656 puts $fd [string trim [$ui_comm get 0.0 end]]
1657 close $fd
1658 }
1659 } elseif {$msg == {} && [file exists $save]} {
1660 file delete $save
1661 }
1662
1663 # -- Stash our current window geometry into this repository.
1664 #
1665 set cfg_geometry [list]
1666 lappend cfg_geometry [wm geometry .]
1667 lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
1668 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
1669 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1670 set rc_geometry {}
1671 }
1672 if {$cfg_geometry != $rc_geometry} {
1673 catch {exec git repo-config gui.geometry $cfg_geometry}
1674 }
1675
1676 destroy .
1677}
1678
1679proc do_rescan {} {
1680 update_status
1681}
1682
1683proc do_include_all {} {
1684 global file_states
1685
1686 if {![lock_index begin-update]} return
1687
1688 set pathList [list]
1689 foreach path [array names file_states] {
1690 set s $file_states($path)
1691 set m [lindex $s 0]
1692 switch -- $m {
1693 AM -
1694 MM -
1695 _M -
1696 _D {lappend pathList $path}
1697 }
1698 }
1699 if {$pathList == {}} {
1700 unlock_index
1701 } else {
1702 update_index $pathList
1703 }
1704}
1705
1706set GIT_COMMITTER_IDENT {}
1707
1708proc do_signoff {} {
1709 global ui_comm GIT_COMMITTER_IDENT
1710
1711 if {$GIT_COMMITTER_IDENT == {}} {
1712 if {[catch {set me [exec git var GIT_COMMITTER_IDENT]} err]} {
1713 error_popup "Unable to obtain your identity:\n\n$err"
1714 return
1715 }
1716 if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
1717 $me me GIT_COMMITTER_IDENT]} {
1718 error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me"
1719 return
1720 }
1721 }
1722
1723 set sob "Signed-off-by: $GIT_COMMITTER_IDENT"
1724 set last [$ui_comm get {end -1c linestart} {end -1c}]
1725 if {$last != $sob} {
1726 $ui_comm edit separator
1727 if {$last != {}
1728 && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
1729 $ui_comm insert end "\n"
1730 }
1731 $ui_comm insert end "\n$sob"
1732 $ui_comm edit separator
1733 $ui_comm see end
1734 }
1735}
1736
1737proc do_amend_last {} {
1738 load_last_commit
1739}
1740
1741proc do_commit {} {
1742 commit_tree
1743}
1744
1745proc do_options {} {
1746 global appname gitdir font_descs
1747 global repo_config global_config
1748 global repo_config_new global_config_new
1749
1750 load_config 1
1751 array unset repo_config_new
1752 array unset global_config_new
1753 foreach name [array names repo_config] {
1754 set repo_config_new($name) $repo_config($name)
1755 }
1756 foreach name [array names global_config] {
1757 set global_config_new($name) $global_config($name)
1758 }
1759 set reponame [lindex [file split \
1760 [file normalize [file dirname $gitdir]]] \
1761 end]
1762
1763 set w .options_editor
1764 toplevel $w
1765 wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
1766
1767 label $w.header -text "$appname Options" \
1768 -font font_uibold
1769 pack $w.header -side top -fill x
1770
1771 frame $w.buttons
1772 button $w.buttons.restore -text {Restore Defaults} \
1773 -font font_ui \
1774 -command do_restore_defaults
1775 pack $w.buttons.restore -side left
1776 button $w.buttons.save -text Save \
1777 -font font_ui \
1778 -command [list do_save_config $w]
1779 pack $w.buttons.save -side right
1780 button $w.buttons.cancel -text {Cancel} \
1781 -font font_ui \
1782 -command [list destroy $w]
1783 pack $w.buttons.cancel -side right
1784 pack $w.buttons -side bottom -fill x -pady 10 -padx 10
1785
1786 labelframe $w.repo -text "$reponame Repository" \
1787 -font font_ui \
1788 -relief raised -borderwidth 2
1789 labelframe $w.global -text {Global (All Repositories)} \
1790 -font font_ui \
1791 -relief raised -borderwidth 2
1792 pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5
1793 pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5
1794
1795 foreach option {
1796 {pullsummary {Show Pull Summary}}
1797 {trustmtime {Trust File Modification Timestamps}}
1798 } {
1799 set name [lindex $option 0]
1800 set text [lindex $option 1]
1801 foreach f {repo global} {
1802 checkbutton $w.$f.$name -text $text \
1803 -variable ${f}_config_new(gui.$name) \
1804 -onvalue true \
1805 -offvalue false \
1806 -font font_ui
1807 pack $w.$f.$name -side top -anchor w
1808 }
1809 }
1810
1811 set all_fonts [lsort [font families]]
1812 foreach option $font_descs {
1813 set name [lindex $option 0]
1814 set font [lindex $option 1]
1815 set text [lindex $option 2]
1816
1817 set global_config_new(gui.$font^^family) \
1818 [font configure $font -family]
1819 set global_config_new(gui.$font^^size) \
1820 [font configure $font -size]
1821
1822 frame $w.global.$name
1823 label $w.global.$name.l -text "$text:" -font font_ui
1824 pack $w.global.$name.l -side left -anchor w -fill x
1825 eval tk_optionMenu $w.global.$name.family \
1826 global_config_new(gui.$font^^family) \
1827 $all_fonts
1828 spinbox $w.global.$name.size \
1829 -textvariable global_config_new(gui.$font^^size) \
1830 -from 2 -to 80 -increment 1 \
1831 -width 3 \
1832 -font font_ui
1833 pack $w.global.$name.size -side right -anchor e
1834 pack $w.global.$name.family -side right -anchor e
1835 pack $w.global.$name -side top -anchor w -fill x
1836 }
1837
1838 bind $w <Visibility> "grab $w; focus $w"
1839 bind $w <Key-Escape> "destroy $w"
1840 wm title $w "$appname ($reponame): Options"
1841 tkwait window $w
1842}
1843
1844proc do_restore_defaults {} {
1845 global font_descs default_config repo_config
1846 global repo_config_new global_config_new
1847
1848 foreach name [array names default_config] {
1849 set repo_config_new($name) $default_config($name)
1850 set global_config_new($name) $default_config($name)
1851 }
1852
1853 foreach option $font_descs {
1854 set name [lindex $option 0]
1855 set repo_config(gui.$name) $default_config(gui.$name)
1856 }
1857 apply_config
1858
1859 foreach option $font_descs {
1860 set name [lindex $option 0]
1861 set font [lindex $option 1]
1862 set global_config_new(gui.$font^^family) \
1863 [font configure $font -family]
1864 set global_config_new(gui.$font^^size) \
1865 [font configure $font -size]
1866 }
1867}
1868
1869proc do_save_config {w} {
1870 if {[catch {save_config} err]} {
1871 error_popup "Failed to completely save options:\n\n$err"
1872 }
1873 destroy $w
1874}
1875
1876# shift == 1: left click
1877# 3: right click
1878proc click {w x y shift wx wy} {
1879 global ui_index ui_other file_lists
1880
1881 set pos [split [$w index @$x,$y] .]
1882 set lno [lindex $pos 0]
1883 set col [lindex $pos 1]
1884 set path [lindex $file_lists($w) [expr $lno - 1]]
1885 if {$path == {}} return
1886
1887 if {$col > 0 && $shift == 1} {
1888 show_diff $path $w $lno
1889 }
1890}
1891
1892proc unclick {w x y} {
1893 global file_lists
1894
1895 set pos [split [$w index @$x,$y] .]
1896 set lno [lindex $pos 0]
1897 set col [lindex $pos 1]
1898 set path [lindex $file_lists($w) [expr $lno - 1]]
1899 if {$path == {}} return
1900
1901 if {$col == 0} {
1902 update_index [list $path]
1903 }
1904}
1905
1906######################################################################
1907##
1908## config defaults
1909
1910set cursor_ptr arrow
1911font create font_diff -family Courier -size 10
1912font create font_ui
1913catch {
1914 label .dummy
1915 eval font configure font_ui [font actual [.dummy cget -font]]
1916 destroy .dummy
1917}
1918
1919font create font_uibold
1920font create font_diffbold
1921
1922set M1B M1
1923set M1T M1
1924if {$tcl_platform(platform) == {windows}} {
1925 set M1B Control
1926 set M1T Ctrl
1927} elseif {[is_MacOSX]} {
1928 set M1B M1
1929 set M1T Cmd
1930}
1931
1932proc apply_config {} {
1933 global repo_config font_descs
1934
1935 foreach option $font_descs {
1936 set name [lindex $option 0]
1937 set font [lindex $option 1]
1938 if {[catch {
1939 foreach {cn cv} $repo_config(gui.$name) {
1940 font configure $font $cn $cv
1941 }
1942 } err]} {
1943 error_popup "Invalid font specified in gui.$name:\n\n$err"
1944 }
1945 foreach {cn cv} [font configure $font] {
1946 font configure ${font}bold $cn $cv
1947 }
1948 font configure ${font}bold -weight bold
1949 }
1950}
1951
1952set default_config(gui.trustmtime) false
1953set default_config(gui.pullsummary) true
1954set default_config(gui.fontui) [font configure font_ui]
1955set default_config(gui.fontdiff) [font configure font_diff]
1956set font_descs {
1957 {fontui font_ui {Main Font}}
1958 {fontdiff font_diff {Diff/Console Font}}
1959}
1960load_config 0
1961apply_config
1962
1963######################################################################
1964##
1965## ui construction
1966
1967# -- Menu Bar
1968menu .mbar -tearoff 0
1969.mbar add cascade -label Project -menu .mbar.project
1970.mbar add cascade -label Edit -menu .mbar.edit
1971.mbar add cascade -label Commit -menu .mbar.commit
1972if {!$single_commit} {
1973 .mbar add cascade -label Fetch -menu .mbar.fetch
1974 .mbar add cascade -label Pull -menu .mbar.pull
1975 .mbar add cascade -label Push -menu .mbar.push
1976}
1977. configure -menu .mbar
1978
1979# -- Project Menu
1980menu .mbar.project
1981.mbar.project add command -label Visualize \
1982 -command do_gitk \
1983 -font font_ui
1984if {!$single_commit} {
1985 .mbar.project add command -label {Repack Database} \
1986 -command do_repack \
1987 -font font_ui
1988}
1989.mbar.project add command -label Quit \
1990 -command do_quit \
1991 -accelerator $M1T-Q \
1992 -font font_ui
1993
1994# -- Edit Menu
1995#
1996menu .mbar.edit
1997.mbar.edit add command -label Undo \
1998 -command {catch {[focus] edit undo}} \
1999 -accelerator $M1T-Z \
2000 -font font_ui
2001.mbar.edit add command -label Redo \
2002 -command {catch {[focus] edit redo}} \
2003 -accelerator $M1T-Y \
2004 -font font_ui
2005.mbar.edit add separator
2006.mbar.edit add command -label Cut \
2007 -command {catch {tk_textCut [focus]}} \
2008 -accelerator $M1T-X \
2009 -font font_ui
2010.mbar.edit add command -label Copy \
2011 -command {catch {tk_textCopy [focus]}} \
2012 -accelerator $M1T-C \
2013 -font font_ui
2014.mbar.edit add command -label Paste \
2015 -command {catch {tk_textPaste [focus]; [focus] see insert}} \
2016 -accelerator $M1T-V \
2017 -font font_ui
2018.mbar.edit add command -label Delete \
2019 -command {catch {[focus] delete sel.first sel.last}} \
2020 -accelerator Del \
2021 -font font_ui
2022.mbar.edit add separator
2023.mbar.edit add command -label {Select All} \
2024 -command {catch {[focus] tag add sel 0.0 end}} \
2025 -accelerator $M1T-A \
2026 -font font_ui
2027.mbar.edit add separator
2028.mbar.edit add command -label {Options...} \
2029 -command do_options \
2030 -font font_ui
2031
2032# -- Commit Menu
2033menu .mbar.commit
2034.mbar.commit add command -label Rescan \
2035 -command do_rescan \
2036 -accelerator F5 \
2037 -font font_ui
2038lappend disable_on_lock \
2039 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2040.mbar.commit add command -label {Amend Last Commit} \
2041 -command do_amend_last \
2042 -font font_ui
2043lappend disable_on_lock \
2044 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2045.mbar.commit add command -label {Include All Files} \
2046 -command do_include_all \
2047 -accelerator $M1T-I \
2048 -font font_ui
2049lappend disable_on_lock \
2050 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2051.mbar.commit add command -label {Sign Off} \
2052 -command do_signoff \
2053 -accelerator $M1T-S \
2054 -font font_ui
2055.mbar.commit add command -label Commit \
2056 -command do_commit \
2057 -accelerator $M1T-Return \
2058 -font font_ui
2059lappend disable_on_lock \
2060 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2061
2062if {!$single_commit} {
2063 # -- Fetch Menu
2064 menu .mbar.fetch
2065
2066 # -- Pull Menu
2067 menu .mbar.pull
2068
2069 # -- Push Menu
2070 menu .mbar.push
2071}
2072
2073# -- Main Window Layout
2074panedwindow .vpane -orient vertical
2075panedwindow .vpane.files -orient horizontal
2076.vpane add .vpane.files -sticky nsew -height 100 -width 400
2077pack .vpane -anchor n -side top -fill both -expand 1
2078
2079# -- Index File List
2080frame .vpane.files.index -height 100 -width 400
2081label .vpane.files.index.title -text {Modified Files} \
2082 -background green \
2083 -font font_ui
2084text $ui_index -background white -borderwidth 0 \
2085 -width 40 -height 10 \
2086 -font font_ui \
2087 -cursor $cursor_ptr \
2088 -yscrollcommand {.vpane.files.index.sb set} \
2089 -state disabled
2090scrollbar .vpane.files.index.sb -command [list $ui_index yview]
2091pack .vpane.files.index.title -side top -fill x
2092pack .vpane.files.index.sb -side right -fill y
2093pack $ui_index -side left -fill both -expand 1
2094.vpane.files add .vpane.files.index -sticky nsew
2095
2096# -- Other (Add) File List
2097frame .vpane.files.other -height 100 -width 100
2098label .vpane.files.other.title -text {Untracked Files} \
2099 -background red \
2100 -font font_ui
2101text $ui_other -background white -borderwidth 0 \
2102 -width 40 -height 10 \
2103 -font font_ui \
2104 -cursor $cursor_ptr \
2105 -yscrollcommand {.vpane.files.other.sb set} \
2106 -state disabled
2107scrollbar .vpane.files.other.sb -command [list $ui_other yview]
2108pack .vpane.files.other.title -side top -fill x
2109pack .vpane.files.other.sb -side right -fill y
2110pack $ui_other -side left -fill both -expand 1
2111.vpane.files add .vpane.files.other -sticky nsew
2112
2113$ui_index tag conf in_diff -font font_uibold
2114$ui_other tag conf in_diff -font font_uibold
2115
2116# -- Diff and Commit Area
2117frame .vpane.lower -height 300 -width 400
2118frame .vpane.lower.commarea
2119frame .vpane.lower.diff -relief sunken -borderwidth 1
2120pack .vpane.lower.commarea -side top -fill x
2121pack .vpane.lower.diff -side bottom -fill both -expand 1
2122.vpane add .vpane.lower -stick nsew
2123
2124# -- Commit Area Buttons
2125frame .vpane.lower.commarea.buttons
2126label .vpane.lower.commarea.buttons.l -text {} \
2127 -anchor w \
2128 -justify left \
2129 -font font_ui
2130pack .vpane.lower.commarea.buttons.l -side top -fill x
2131pack .vpane.lower.commarea.buttons -side left -fill y
2132
2133button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
2134 -command do_rescan \
2135 -font font_ui
2136pack .vpane.lower.commarea.buttons.rescan -side top -fill x
2137lappend disable_on_lock \
2138 {.vpane.lower.commarea.buttons.rescan conf -state}
2139
2140button .vpane.lower.commarea.buttons.amend -text {Amend Last} \
2141 -command do_amend_last \
2142 -font font_ui
2143pack .vpane.lower.commarea.buttons.amend -side top -fill x
2144lappend disable_on_lock \
2145 {.vpane.lower.commarea.buttons.amend conf -state}
2146
2147button .vpane.lower.commarea.buttons.incall -text {Include All} \
2148 -command do_include_all \
2149 -font font_ui
2150pack .vpane.lower.commarea.buttons.incall -side top -fill x
2151lappend disable_on_lock \
2152 {.vpane.lower.commarea.buttons.incall conf -state}
2153
2154button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
2155 -command do_signoff \
2156 -font font_ui
2157pack .vpane.lower.commarea.buttons.signoff -side top -fill x
2158
2159button .vpane.lower.commarea.buttons.commit -text {Commit} \
2160 -command do_commit \
2161 -font font_ui
2162pack .vpane.lower.commarea.buttons.commit -side top -fill x
2163lappend disable_on_lock \
2164 {.vpane.lower.commarea.buttons.commit conf -state}
2165
2166# -- Commit Message Buffer
2167frame .vpane.lower.commarea.buffer
2168set ui_comm .vpane.lower.commarea.buffer.t
2169set ui_coml .vpane.lower.commarea.buffer.l
2170label $ui_coml -text {Commit Message:} \
2171 -anchor w \
2172 -justify left \
2173 -font font_ui
2174trace add variable commit_type write {uplevel #0 {
2175 switch -glob $commit_type \
2176 initial {$ui_coml conf -text {Initial Commit Message:}} \
2177 amend {$ui_coml conf -text {Amended Commit Message:}} \
2178 merge {$ui_coml conf -text {Merge Commit Message:}} \
2179 * {$ui_coml conf -text {Commit Message:}}
2180}}
2181text $ui_comm -background white -borderwidth 1 \
2182 -undo true \
2183 -maxundo 20 \
2184 -autoseparators true \
2185 -relief sunken \
2186 -width 75 -height 9 -wrap none \
2187 -font font_diff \
2188 -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
2189scrollbar .vpane.lower.commarea.buffer.sby \
2190 -command [list $ui_comm yview]
2191pack $ui_coml -side top -fill x
2192pack .vpane.lower.commarea.buffer.sby -side right -fill y
2193pack $ui_comm -side left -fill y
2194pack .vpane.lower.commarea.buffer -side left -fill y
2195
2196# -- Commit Message Buffer Context Menu
2197#
2198menu $ui_comm.ctxm -tearoff 0
2199$ui_comm.ctxm add command -label "Cut" \
2200 -font font_ui \
2201 -command "tk_textCut $ui_comm"
2202$ui_comm.ctxm add command -label "Copy" \
2203 -font font_ui \
2204 -command "tk_textCopy $ui_comm"
2205$ui_comm.ctxm add command -label "Paste" \
2206 -font font_ui \
2207 -command "tk_textPaste $ui_comm"
2208$ui_comm.ctxm add command -label "Delete" \
2209 -font font_ui \
2210 -command "$ui_comm delete sel.first sel.last"
2211$ui_comm.ctxm add separator
2212$ui_comm.ctxm add command -label "Select All" \
2213 -font font_ui \
2214 -command "$ui_comm tag add sel 0.0 end"
2215$ui_comm.ctxm add command -label "Copy All" \
2216 -font font_ui \
2217 -command "
2218 $ui_comm tag add sel 0.0 end
2219 tk_textCopy $ui_comm
2220 $ui_comm tag remove sel 0.0 end
2221 "
2222$ui_comm.ctxm add separator
2223$ui_comm.ctxm add command -label "Sign Off" \
2224 -font font_ui \
2225 -command do_signoff
2226bind_button3 $ui_comm "tk_popup $ui_comm.ctxm %X %Y"
2227
2228# -- Diff Header
2229set ui_fname_value {}
2230set ui_fstatus_value {}
2231frame .vpane.lower.diff.header -background orange
2232label .vpane.lower.diff.header.l1 -text {File:} \
2233 -background orange \
2234 -font font_ui
2235label .vpane.lower.diff.header.l2 -textvariable ui_fname_value \
2236 -background orange \
2237 -anchor w \
2238 -justify left \
2239 -font font_ui
2240label .vpane.lower.diff.header.l3 -text {Status:} \
2241 -background orange \
2242 -font font_ui
2243label .vpane.lower.diff.header.l4 -textvariable ui_fstatus_value \
2244 -background orange \
2245 -width $max_status_desc \
2246 -anchor w \
2247 -justify left \
2248 -font font_ui
2249pack .vpane.lower.diff.header.l1 -side left
2250pack .vpane.lower.diff.header.l2 -side left -fill x
2251pack .vpane.lower.diff.header.l4 -side right
2252pack .vpane.lower.diff.header.l3 -side right
2253
2254# -- Diff Body
2255frame .vpane.lower.diff.body
2256set ui_diff .vpane.lower.diff.body.t
2257text $ui_diff -background white -borderwidth 0 \
2258 -width 80 -height 15 -wrap none \
2259 -font font_diff \
2260 -xscrollcommand {.vpane.lower.diff.body.sbx set} \
2261 -yscrollcommand {.vpane.lower.diff.body.sby set} \
2262 -state disabled
2263scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
2264 -command [list $ui_diff xview]
2265scrollbar .vpane.lower.diff.body.sby -orient vertical \
2266 -command [list $ui_diff yview]
2267pack .vpane.lower.diff.body.sbx -side bottom -fill x
2268pack .vpane.lower.diff.body.sby -side right -fill y
2269pack $ui_diff -side left -fill both -expand 1
2270pack .vpane.lower.diff.header -side top -fill x
2271pack .vpane.lower.diff.body -side bottom -fill both -expand 1
2272
2273$ui_diff tag conf dm -foreground red
2274$ui_diff tag conf dp -foreground blue
2275$ui_diff tag conf di -foreground {#00a000}
2276$ui_diff tag conf dni -foreground {#a000a0}
2277$ui_diff tag conf da -font font_diffbold
2278$ui_diff tag conf bold -font font_diffbold
2279
2280# -- Diff Body Context Menu
2281#
2282menu $ui_diff.ctxm -tearoff 0
2283$ui_diff.ctxm add command -label "Copy" \
2284 -font font_ui \
2285 -command "tk_textCopy $ui_diff"
2286$ui_diff.ctxm add command -label "Select All" \
2287 -font font_ui \
2288 -command "$ui_diff tag add sel 0.0 end"
2289$ui_diff.ctxm add command -label "Copy All" \
2290 -font font_ui \
2291 -command "
2292 $ui_diff tag add sel 0.0 end
2293 tk_textCopy $ui_diff
2294 $ui_diff tag remove sel 0.0 end
2295 "
2296$ui_diff.ctxm add separator
2297$ui_diff.ctxm add command -label "Decrease Font Size" \
2298 -font font_ui \
2299 -command {incr_font_size font_diff -1}
2300$ui_diff.ctxm add command -label "Increase Font Size" \
2301 -font font_ui \
2302 -command {incr_font_size font_diff 1}
2303$ui_diff.ctxm add command -label {Options...} \
2304 -font font_ui \
2305 -command do_options
2306bind_button3 $ui_diff "tk_popup $ui_diff.ctxm %X %Y"
2307
2308# -- Status Bar
2309set ui_status_value {Initializing...}
2310label .status -textvariable ui_status_value \
2311 -anchor w \
2312 -justify left \
2313 -borderwidth 1 \
2314 -relief sunken \
2315 -font font_ui
2316pack .status -anchor w -side bottom -fill x
2317
2318# -- Load geometry
2319catch {
2320set gm $repo_config(gui.geometry)
2321wm geometry . [lindex $gm 0]
2322.vpane sash place 0 \
2323 [lindex [.vpane sash coord 0] 0] \
2324 [lindex $gm 1]
2325.vpane.files sash place 0 \
2326 [lindex $gm 2] \
2327 [lindex [.vpane.files sash coord 0] 1]
2328unset gm
2329}
2330
2331# -- Key Bindings
2332bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2333bind $ui_comm <$M1B-Key-i> {do_include_all;break}
2334bind $ui_comm <$M1B-Key-I> {do_include_all;break}
2335bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2336bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2337bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2338bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2339bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2340bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2341bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2342bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2343
2344bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2345bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2346bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2347bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2348bind $ui_diff <$M1B-Key-v> {break}
2349bind $ui_diff <$M1B-Key-V> {break}
2350bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2351bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2352bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break}
2353bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break}
2354bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break}
2355bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break}
2356
2357bind . <Destroy> do_quit
2358bind all <Key-F5> do_rescan
2359bind all <$M1B-Key-r> do_rescan
2360bind all <$M1B-Key-R> do_rescan
2361bind . <$M1B-Key-s> do_signoff
2362bind . <$M1B-Key-S> do_signoff
2363bind . <$M1B-Key-i> do_include_all
2364bind . <$M1B-Key-I> do_include_all
2365bind . <$M1B-Key-Return> do_commit
2366bind all <$M1B-Key-q> do_quit
2367bind all <$M1B-Key-Q> do_quit
2368bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
2369bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
2370foreach i [list $ui_index $ui_other] {
2371 bind $i <Button-1> {click %W %x %y 1 %X %Y; break}
2372 bind $i <ButtonRelease-1> {unclick %W %x %y; break}
2373 bind_button3 $i {click %W %x %y 3 %X %Y; break}
2374}
2375unset i
2376
2377set file_lists($ui_index) [list]
2378set file_lists($ui_other) [list]
2379
2380wm title . "$appname ([file normalize [file dirname $gitdir]])"
2381focus -force $ui_comm
2382if {!$single_commit} {
2383 load_all_remotes
2384 populate_remote_menu .mbar.fetch From fetch_from
2385 populate_remote_menu .mbar.push To push_to
2386 populate_pull_menu .mbar.pull
2387}
2388after 1 update_status