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