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 set cmd [list tk_messageBox \
134 -icon error \
135 -type ok \
136 -title "$title: error" \
137 -message $msg]
138 if {[winfo ismapped .]} {
139 lappend cmd -parent .
140 }
141 eval $cmd
142}
143
144proc info_popup {msg} {
145 global gitdir appname
146
147 set title $appname
148 if {$gitdir ne {}} {
149 append title { (}
150 append title [lindex \
151 [file split [file normalize [file dirname $gitdir]]] \
152 end]
153 append title {)}
154 }
155 tk_messageBox \
156 -parent . \
157 -icon error \
158 -type ok \
159 -title $title \
160 -message $msg
161}
162
163######################################################################
164##
165## repository setup
166
167if { [catch {set gitdir $env(GIT_DIR)}]
168 && [catch {set gitdir [exec git rev-parse --git-dir]} err]} {
169 catch {wm withdraw .}
170 error_popup "Cannot find the git directory:\n\n$err"
171 exit 1
172}
173if {![file isdirectory $gitdir]} {
174 catch {wm withdraw .}
175 error_popup "Git directory not found:\n\n$gitdir"
176 exit 1
177}
178if {[lindex [file split $gitdir] end] ne {.git}} {
179 catch {wm withdraw .}
180 error_popup "Cannot use funny .git directory:\n\n$gitdir"
181 exit 1
182}
183if {[catch {cd [file dirname $gitdir]} err]} {
184 catch {wm withdraw .}
185 error_popup "No working directory [file dirname $gitdir]:\n\n$err"
186 exit 1
187}
188
189set single_commit 0
190if {$appname eq {git-citool}} {
191 set single_commit 1
192}
193
194######################################################################
195##
196## task management
197
198set rescan_active 0
199set diff_active 0
200set last_clicked {}
201
202set disable_on_lock [list]
203set index_lock_type none
204
205proc lock_index {type} {
206 global index_lock_type disable_on_lock
207
208 if {$index_lock_type eq {none}} {
209 set index_lock_type $type
210 foreach w $disable_on_lock {
211 uplevel #0 $w disabled
212 }
213 return 1
214 } elseif {$index_lock_type eq "begin-$type"} {
215 set index_lock_type $type
216 return 1
217 }
218 return 0
219}
220
221proc unlock_index {} {
222 global index_lock_type disable_on_lock
223
224 set index_lock_type none
225 foreach w $disable_on_lock {
226 uplevel #0 $w normal
227 }
228}
229
230######################################################################
231##
232## status
233
234proc repository_state {ctvar hdvar mhvar} {
235 global gitdir
236 upvar $ctvar ct $hdvar hd $mhvar mh
237
238 set mh [list]
239
240 if {[catch {set hd [exec git rev-parse --verify HEAD]}]} {
241 set hd {}
242 set ct initial
243 return
244 }
245
246 set merge_head [file join $gitdir MERGE_HEAD]
247 if {[file exists $merge_head]} {
248 set ct merge
249 set fd_mh [open $merge_head r]
250 while {[gets $fd_mh line] >= 0} {
251 lappend mh $line
252 }
253 close $fd_mh
254 return
255 }
256
257 set ct normal
258}
259
260proc PARENT {} {
261 global PARENT empty_tree
262
263 set p [lindex $PARENT 0]
264 if {$p ne {}} {
265 return $p
266 }
267 if {$empty_tree eq {}} {
268 set empty_tree [exec git mktree << {}]
269 }
270 return $empty_tree
271}
272
273proc rescan {after} {
274 global HEAD PARENT MERGE_HEAD commit_type
275 global ui_index ui_other ui_status_value ui_comm
276 global rescan_active file_states
277 global repo_config
278
279 if {$rescan_active > 0 || ![lock_index read]} return
280
281 repository_state newType newHEAD newMERGE_HEAD
282 if {[string match amend* $commit_type]
283 && $newType eq {normal}
284 && $newHEAD eq $HEAD} {
285 } else {
286 set HEAD $newHEAD
287 set PARENT $newHEAD
288 set MERGE_HEAD $newMERGE_HEAD
289 set commit_type $newType
290 }
291
292 array unset file_states
293
294 if {![$ui_comm edit modified]
295 || [string trim [$ui_comm get 0.0 end]] eq {}} {
296 if {[load_message GITGUI_MSG]} {
297 } elseif {[load_message MERGE_MSG]} {
298 } elseif {[load_message SQUASH_MSG]} {
299 }
300 $ui_comm edit reset
301 $ui_comm edit modified false
302 }
303
304 if {$repo_config(gui.trustmtime) eq {true}} {
305 rescan_stage2 {} $after
306 } else {
307 set rescan_active 1
308 set ui_status_value {Refreshing file status...}
309 set cmd [list git update-index]
310 lappend cmd -q
311 lappend cmd --unmerged
312 lappend cmd --ignore-missing
313 lappend cmd --refresh
314 set fd_rf [open "| $cmd" r]
315 fconfigure $fd_rf -blocking 0 -translation binary
316 fileevent $fd_rf readable \
317 [list rescan_stage2 $fd_rf $after]
318 }
319}
320
321proc rescan_stage2 {fd after} {
322 global gitdir ui_status_value
323 global rescan_active buf_rdi buf_rdf buf_rlo
324
325 if {$fd ne {}} {
326 read $fd
327 if {![eof $fd]} return
328 close $fd
329 }
330
331 set ls_others [list | git ls-files --others -z \
332 --exclude-per-directory=.gitignore]
333 set info_exclude [file join $gitdir info exclude]
334 if {[file readable $info_exclude]} {
335 lappend ls_others "--exclude-from=$info_exclude"
336 }
337
338 set buf_rdi {}
339 set buf_rdf {}
340 set buf_rlo {}
341
342 set rescan_active 3
343 set ui_status_value {Scanning for modified files ...}
344 set fd_di [open "| git diff-index --cached -z [PARENT]" r]
345 set fd_df [open "| git diff-files -z" r]
346 set fd_lo [open $ls_others r]
347
348 fconfigure $fd_di -blocking 0 -translation binary
349 fconfigure $fd_df -blocking 0 -translation binary
350 fconfigure $fd_lo -blocking 0 -translation binary
351 fileevent $fd_di readable [list read_diff_index $fd_di $after]
352 fileevent $fd_df readable [list read_diff_files $fd_df $after]
353 fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
354}
355
356proc load_message {file} {
357 global gitdir ui_comm
358
359 set f [file join $gitdir $file]
360 if {[file isfile $f]} {
361 if {[catch {set fd [open $f r]}]} {
362 return 0
363 }
364 set content [string trim [read $fd]]
365 close $fd
366 $ui_comm delete 0.0 end
367 $ui_comm insert end $content
368 return 1
369 }
370 return 0
371}
372
373proc read_diff_index {fd after} {
374 global buf_rdi
375
376 append buf_rdi [read $fd]
377 set c 0
378 set n [string length $buf_rdi]
379 while {$c < $n} {
380 set z1 [string first "\0" $buf_rdi $c]
381 if {$z1 == -1} break
382 incr z1
383 set z2 [string first "\0" $buf_rdi $z1]
384 if {$z2 == -1} break
385
386 incr c
387 set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
388 merge_state \
389 [string range $buf_rdi $z1 [expr {$z2 - 1}]] \
390 [lindex $i 4]? \
391 [list [lindex $i 0] [lindex $i 2]] \
392 [list]
393 set c $z2
394 incr c
395 }
396 if {$c < $n} {
397 set buf_rdi [string range $buf_rdi $c end]
398 } else {
399 set buf_rdi {}
400 }
401
402 rescan_done $fd buf_rdi $after
403}
404
405proc read_diff_files {fd after} {
406 global buf_rdf
407
408 append buf_rdf [read $fd]
409 set c 0
410 set n [string length $buf_rdf]
411 while {$c < $n} {
412 set z1 [string first "\0" $buf_rdf $c]
413 if {$z1 == -1} break
414 incr z1
415 set z2 [string first "\0" $buf_rdf $z1]
416 if {$z2 == -1} break
417
418 incr c
419 set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
420 merge_state \
421 [string range $buf_rdf $z1 [expr {$z2 - 1}]] \
422 ?[lindex $i 4] \
423 [list] \
424 [list [lindex $i 0] [lindex $i 2]]
425 set c $z2
426 incr c
427 }
428 if {$c < $n} {
429 set buf_rdf [string range $buf_rdf $c end]
430 } else {
431 set buf_rdf {}
432 }
433
434 rescan_done $fd buf_rdf $after
435}
436
437proc read_ls_others {fd after} {
438 global buf_rlo
439
440 append buf_rlo [read $fd]
441 set pck [split $buf_rlo "\0"]
442 set buf_rlo [lindex $pck end]
443 foreach p [lrange $pck 0 end-1] {
444 merge_state $p ?O
445 }
446 rescan_done $fd buf_rlo $after
447}
448
449proc rescan_done {fd buf after} {
450 global rescan_active
451 global file_states repo_config
452 upvar $buf to_clear
453
454 if {![eof $fd]} return
455 set to_clear {}
456 close $fd
457 if {[incr rescan_active -1] > 0} return
458
459 prune_selection
460 unlock_index
461 display_all_files
462
463 if {$repo_config(gui.partialinclude) ne {true}} {
464 set pathList [list]
465 foreach path [array names file_states] {
466 switch -- [lindex $file_states($path) 0] {
467 AM -
468 MM {lappend pathList $path}
469 }
470 }
471 if {$pathList ne {}} {
472 update_index \
473 "Updating included files" \
474 $pathList \
475 [concat {reshow_diff;} $after]
476 return
477 }
478 }
479
480 reshow_diff
481 uplevel #0 $after
482}
483
484proc prune_selection {} {
485 global file_states selected_paths
486
487 foreach path [array names selected_paths] {
488 if {[catch {set still_here $file_states($path)}]} {
489 unset selected_paths($path)
490 }
491 }
492}
493
494######################################################################
495##
496## diff
497
498proc clear_diff {} {
499 global ui_diff current_diff ui_index ui_other
500
501 $ui_diff conf -state normal
502 $ui_diff delete 0.0 end
503 $ui_diff conf -state disabled
504
505 set current_diff {}
506
507 $ui_index tag remove in_diff 0.0 end
508 $ui_other tag remove in_diff 0.0 end
509}
510
511proc reshow_diff {} {
512 global current_diff ui_status_value file_states
513
514 if {$current_diff eq {}
515 || [catch {set s $file_states($current_diff)}]} {
516 clear_diff
517 } else {
518 show_diff $current_diff
519 }
520}
521
522proc handle_empty_diff {} {
523 global current_diff file_states file_lists
524
525 set path $current_diff
526 set s $file_states($path)
527 if {[lindex $s 0] ne {_M}} return
528
529 info_popup "No differences detected.
530
531[short_path $path] has no changes.
532
533The modification date of this file was updated
534by another application and you currently have
535the Trust File Modification Timestamps option
536enabled, so Git did not automatically detect
537that there are no content differences in this
538file.
539
540This file will now be removed from the modified
541files list, to prevent possible confusion.
542"
543 if {[catch {exec git update-index -- $path} err]} {
544 error_popup "Failed to refresh index:\n\n$err"
545 }
546
547 clear_diff
548 set old_w [mapcol [lindex $file_states($path) 0] $path]
549 set lno [lsearch -sorted $file_lists($old_w) $path]
550 if {$lno >= 0} {
551 set file_lists($old_w) \
552 [lreplace $file_lists($old_w) $lno $lno]
553 incr lno
554 $old_w conf -state normal
555 $old_w delete $lno.0 [expr {$lno + 1}].0
556 $old_w conf -state disabled
557 }
558}
559
560proc show_diff {path {w {}} {lno {}}} {
561 global file_states file_lists
562 global is_3way_diff diff_active repo_config
563 global ui_diff current_diff ui_status_value
564
565 if {$diff_active || ![lock_index read]} return
566
567 clear_diff
568 if {$w eq {} || $lno == {}} {
569 foreach w [array names file_lists] {
570 set lno [lsearch -sorted $file_lists($w) $path]
571 if {$lno >= 0} {
572 incr lno
573 break
574 }
575 }
576 }
577 if {$w ne {} && $lno >= 1} {
578 $w tag add in_diff $lno.0 [expr {$lno + 1}].0
579 }
580
581 set s $file_states($path)
582 set m [lindex $s 0]
583 set is_3way_diff 0
584 set diff_active 1
585 set current_diff $path
586 set ui_status_value "Loading diff of [escape_path $path]..."
587
588 set cmd [list | git diff-index]
589 lappend cmd --no-color
590 if {$repo_config(gui.diffcontext) > 0} {
591 lappend cmd "-U$repo_config(gui.diffcontext)"
592 }
593 lappend cmd -p
594
595 switch $m {
596 MM {
597 lappend cmd -c
598 }
599 _O {
600 if {[catch {
601 set fd [open $path r]
602 set content [read $fd]
603 close $fd
604 } err ]} {
605 set diff_active 0
606 unlock_index
607 set ui_status_value "Unable to display [escape_path $path]"
608 error_popup "Error loading file:\n\n$err"
609 return
610 }
611 $ui_diff conf -state normal
612 $ui_diff insert end $content
613 $ui_diff conf -state disabled
614 set diff_active 0
615 unlock_index
616 set ui_status_value {Ready.}
617 return
618 }
619 }
620
621 lappend cmd [PARENT]
622 lappend cmd --
623 lappend cmd $path
624
625 if {[catch {set fd [open $cmd r]} err]} {
626 set diff_active 0
627 unlock_index
628 set ui_status_value "Unable to display [escape_path $path]"
629 error_popup "Error loading diff:\n\n$err"
630 return
631 }
632
633 fconfigure $fd -blocking 0 -translation auto
634 fileevent $fd readable [list read_diff $fd]
635}
636
637proc read_diff {fd} {
638 global ui_diff ui_status_value is_3way_diff diff_active
639 global repo_config
640
641 $ui_diff conf -state normal
642 while {[gets $fd line] >= 0} {
643 # -- Cleanup uninteresting diff header lines.
644 #
645 if {[string match {diff --git *} $line]} continue
646 if {[string match {diff --combined *} $line]} continue
647 if {[string match {--- *} $line]} continue
648 if {[string match {+++ *} $line]} continue
649 if {$line eq {deleted file mode 120000}} {
650 set line "deleted symlink"
651 }
652
653 # -- Automatically detect if this is a 3 way diff.
654 #
655 if {[string match {@@@ *} $line]} {set is_3way_diff 1}
656
657 # -- Reformat a 3 way diff, 'cause its too weird.
658 #
659 if {$is_3way_diff} {
660 set op [string range $line 0 1]
661 switch -- $op {
662 {@@} {set tags d_@}
663 {++} {set tags d_+ ; set op { +}}
664 {--} {set tags d_- ; set op { -}}
665 { +} {set tags d_++; set op {++}}
666 { -} {set tags d_--; set op {--}}
667 {+ } {set tags d_-+; set op {-+}}
668 {- } {set tags d_+-; set op {+-}}
669 default {set tags {}}
670 }
671 set line [string replace $line 0 1 $op]
672 } else {
673 switch -- [string index $line 0] {
674 @ {set tags d_@}
675 + {set tags d_+}
676 - {set tags d_-}
677 default {set tags {}}
678 }
679 }
680 $ui_diff insert end $line $tags
681 $ui_diff insert end "\n" $tags
682 }
683 $ui_diff conf -state disabled
684
685 if {[eof $fd]} {
686 close $fd
687 set diff_active 0
688 unlock_index
689 set ui_status_value {Ready.}
690
691 if {$repo_config(gui.trustmtime) eq {true}
692 && [$ui_diff index end] eq {2.0}} {
693 handle_empty_diff
694 }
695 }
696}
697
698######################################################################
699##
700## commit
701
702proc load_last_commit {} {
703 global HEAD PARENT MERGE_HEAD commit_type ui_comm
704
705 if {[llength $PARENT] == 0} {
706 error_popup {There is nothing to amend.
707
708You are about to create the initial commit.
709There is no commit before this to amend.
710}
711 return
712 }
713
714 repository_state curType curHEAD curMERGE_HEAD
715 if {$curType eq {merge}} {
716 error_popup {Cannot amend while merging.
717
718You are currently in the middle of a merge that
719has not been fully completed. You cannot amend
720the prior commit unless you first abort the
721current merge activity.
722}
723 return
724 }
725
726 set msg {}
727 set parents [list]
728 if {[catch {
729 set fd [open "| git cat-file commit $curHEAD" r]
730 while {[gets $fd line] > 0} {
731 if {[string match {parent *} $line]} {
732 lappend parents [string range $line 7 end]
733 }
734 }
735 set msg [string trim [read $fd]]
736 close $fd
737 } err]} {
738 error_popup "Error loading commit data for amend:\n\n$err"
739 return
740 }
741
742 set HEAD $curHEAD
743 set PARENT $parents
744 set MERGE_HEAD [list]
745 switch -- [llength $parents] {
746 0 {set commit_type amend-initial}
747 1 {set commit_type amend}
748 default {set commit_type amend-merge}
749 }
750
751 $ui_comm delete 0.0 end
752 $ui_comm insert end $msg
753 $ui_comm edit reset
754 $ui_comm edit modified false
755 rescan {set ui_status_value {Ready.}}
756}
757
758proc create_new_commit {} {
759 global commit_type ui_comm
760
761 set commit_type normal
762 $ui_comm delete 0.0 end
763 $ui_comm edit reset
764 $ui_comm edit modified false
765 rescan {set ui_status_value {Ready.}}
766}
767
768set GIT_COMMITTER_IDENT {}
769
770proc committer_ident {} {
771 global GIT_COMMITTER_IDENT
772
773 if {$GIT_COMMITTER_IDENT eq {}} {
774 if {[catch {set me [exec git var GIT_COMMITTER_IDENT]} err]} {
775 error_popup "Unable to obtain your identity:\n\n$err"
776 return {}
777 }
778 if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
779 $me me GIT_COMMITTER_IDENT]} {
780 error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me"
781 return {}
782 }
783 }
784
785 return $GIT_COMMITTER_IDENT
786}
787
788proc commit_tree {} {
789 global HEAD commit_type file_states ui_comm repo_config
790
791 if {![lock_index update]} return
792 if {[committer_ident] eq {}} return
793
794 # -- Our in memory state should match the repository.
795 #
796 repository_state curType curHEAD curMERGE_HEAD
797 if {[string match amend* $commit_type]
798 && $curType eq {normal}
799 && $curHEAD eq $HEAD} {
800 } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
801 info_popup {Last scanned state does not match repository state.
802
803Another Git program has modified this repository
804since the last scan. A rescan must be performed
805before another commit can be created.
806
807The rescan will be automatically started now.
808}
809 unlock_index
810 rescan {set ui_status_value {Ready.}}
811 return
812 }
813
814 # -- At least one file should differ in the index.
815 #
816 set files_ready 0
817 foreach path [array names file_states] {
818 switch -glob -- [lindex $file_states($path) 0] {
819 _? {continue}
820 A? -
821 D? -
822 M? {set files_ready 1; break}
823 U? {
824 error_popup "Unmerged files cannot be committed.
825
826File [short_path $path] has merge conflicts.
827You must resolve them and include the file before committing.
828"
829 unlock_index
830 return
831 }
832 default {
833 error_popup "Unknown file state [lindex $s 0] detected.
834
835File [short_path $path] cannot be committed by this program.
836"
837 }
838 }
839 }
840 if {!$files_ready} {
841 error_popup {No included files to commit.
842
843You must include at least 1 file before you can commit.
844}
845 unlock_index
846 return
847 }
848
849 # -- A message is required.
850 #
851 set msg [string trim [$ui_comm get 1.0 end]]
852 if {$msg eq {}} {
853 error_popup {Please supply a commit message.
854
855A good commit message has the following format:
856
857- First line: Describe in one sentance what you did.
858- Second line: Blank
859- Remaining lines: Describe why this change is good.
860}
861 unlock_index
862 return
863 }
864
865 # -- Update included files if partialincludes are off.
866 #
867 if {$repo_config(gui.partialinclude) ne {true}} {
868 set pathList [list]
869 foreach path [array names file_states] {
870 switch -glob -- [lindex $file_states($path) 0] {
871 A? -
872 M? {lappend pathList $path}
873 }
874 }
875 if {$pathList ne {}} {
876 unlock_index
877 update_index \
878 "Updating included files" \
879 $pathList \
880 [concat {lock_index update;} \
881 [list commit_prehook $curHEAD $msg]]
882 return
883 }
884 }
885
886 commit_prehook $curHEAD $msg
887}
888
889proc commit_prehook {curHEAD msg} {
890 global tcl_platform gitdir ui_status_value pch_error
891
892 # On Cygwin [file executable] might lie so we need to ask
893 # the shell if the hook is executable. Yes that's annoying.
894
895 set pchook [file join $gitdir hooks pre-commit]
896 if {$tcl_platform(platform) eq {windows}
897 && [file isfile $pchook]} {
898 set pchook [list sh -c [concat \
899 "if test -x \"$pchook\";" \
900 "then exec \"$pchook\" 2>&1;" \
901 "fi"]]
902 } elseif {[file executable $pchook]} {
903 set pchook [list $pchook |& cat]
904 } else {
905 commit_writetree $curHEAD $msg
906 return
907 }
908
909 set ui_status_value {Calling pre-commit hook...}
910 set pch_error {}
911 set fd_ph [open "| $pchook" r]
912 fconfigure $fd_ph -blocking 0 -translation binary
913 fileevent $fd_ph readable \
914 [list commit_prehook_wait $fd_ph $curHEAD $msg]
915}
916
917proc commit_prehook_wait {fd_ph curHEAD msg} {
918 global pch_error ui_status_value
919
920 append pch_error [read $fd_ph]
921 fconfigure $fd_ph -blocking 1
922 if {[eof $fd_ph]} {
923 if {[catch {close $fd_ph}]} {
924 set ui_status_value {Commit declined by pre-commit hook.}
925 hook_failed_popup pre-commit $pch_error
926 unlock_index
927 } else {
928 commit_writetree $curHEAD $msg
929 }
930 set pch_error {}
931 return
932 }
933 fconfigure $fd_ph -blocking 0
934}
935
936proc commit_writetree {curHEAD msg} {
937 global ui_status_value
938
939 set ui_status_value {Committing changes...}
940 set fd_wt [open "| git write-tree" r]
941 fileevent $fd_wt readable \
942 [list commit_committree $fd_wt $curHEAD $msg]
943}
944
945proc commit_committree {fd_wt curHEAD msg} {
946 global HEAD PARENT MERGE_HEAD commit_type
947 global single_commit gitdir tcl_platform
948 global ui_status_value ui_comm selected_commit_type
949 global file_states selected_paths rescan_active
950
951 gets $fd_wt tree_id
952 if {$tree_id eq {} || [catch {close $fd_wt} err]} {
953 error_popup "write-tree failed:\n\n$err"
954 set ui_status_value {Commit failed.}
955 unlock_index
956 return
957 }
958
959 # -- Create the commit.
960 #
961 set cmd [list git commit-tree $tree_id]
962 set parents [concat $PARENT $MERGE_HEAD]
963 if {[llength $parents] > 0} {
964 foreach p $parents {
965 lappend cmd -p $p
966 }
967 } else {
968 # git commit-tree writes to stderr during initial commit.
969 lappend cmd 2>/dev/null
970 }
971 lappend cmd << $msg
972 if {[catch {set cmt_id [eval exec $cmd]} err]} {
973 error_popup "commit-tree failed:\n\n$err"
974 set ui_status_value {Commit failed.}
975 unlock_index
976 return
977 }
978
979 # -- Update the HEAD ref.
980 #
981 set reflogm commit
982 if {$commit_type ne {normal}} {
983 append reflogm " ($commit_type)"
984 }
985 set i [string first "\n" $msg]
986 if {$i >= 0} {
987 append reflogm {: } [string range $msg 0 [expr {$i - 1}]]
988 } else {
989 append reflogm {: } $msg
990 }
991 set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD]
992 if {[catch {eval exec $cmd} err]} {
993 error_popup "update-ref failed:\n\n$err"
994 set ui_status_value {Commit failed.}
995 unlock_index
996 return
997 }
998
999 # -- Cleanup after ourselves.
1000 #
1001 catch {file delete [file join $gitdir MERGE_HEAD]}
1002 catch {file delete [file join $gitdir MERGE_MSG]}
1003 catch {file delete [file join $gitdir SQUASH_MSG]}
1004 catch {file delete [file join $gitdir GITGUI_MSG]}
1005
1006 # -- Let rerere do its thing.
1007 #
1008 if {[file isdirectory [file join $gitdir rr-cache]]} {
1009 catch {exec git rerere}
1010 }
1011
1012 # -- Run the post-commit hook.
1013 #
1014 set pchook [file join $gitdir hooks post-commit]
1015 if {$tcl_platform(platform) eq {windows} && [file isfile $pchook]} {
1016 set pchook [list sh -c [concat \
1017 "if test -x \"$pchook\";" \
1018 "then exec \"$pchook\";" \
1019 "fi"]]
1020 } elseif {![file executable $pchook]} {
1021 set pchook {}
1022 }
1023 if {$pchook ne {}} {
1024 catch {exec $pchook &}
1025 }
1026
1027 $ui_comm delete 0.0 end
1028 $ui_comm edit reset
1029 $ui_comm edit modified false
1030
1031 if {$single_commit} do_quit
1032
1033 # -- Update in memory status
1034 #
1035 set selected_commit_type new
1036 set commit_type normal
1037 set HEAD $cmt_id
1038 set PARENT $cmt_id
1039 set MERGE_HEAD [list]
1040
1041 foreach path [array names file_states] {
1042 set s $file_states($path)
1043 set m [lindex $s 0]
1044 switch -glob -- $m {
1045 _O -
1046 _M -
1047 _D {continue}
1048 __ -
1049 A_ -
1050 M_ -
1051 DD {
1052 unset file_states($path)
1053 catch {unset selected_paths($path)}
1054 }
1055 DO {
1056 set file_states($path) [list _O [lindex $s 1] {} {}]
1057 }
1058 AM -
1059 AD -
1060 MM -
1061 DM {
1062 set file_states($path) [list \
1063 _[string index $m 1] \
1064 [lindex $s 1] \
1065 [lindex $s 3] \
1066 {}]
1067 }
1068 }
1069 }
1070
1071 display_all_files
1072 unlock_index
1073 reshow_diff
1074 set ui_status_value \
1075 "Changes committed as [string range $cmt_id 0 7]."
1076}
1077
1078######################################################################
1079##
1080## fetch pull push
1081
1082proc fetch_from {remote} {
1083 set w [new_console "fetch $remote" \
1084 "Fetching new changes from $remote"]
1085 set cmd [list git fetch]
1086 lappend cmd $remote
1087 console_exec $w $cmd
1088}
1089
1090proc pull_remote {remote branch} {
1091 global HEAD commit_type file_states repo_config
1092
1093 if {![lock_index update]} return
1094
1095 # -- Our in memory state should match the repository.
1096 #
1097 repository_state curType curHEAD curMERGE_HEAD
1098 if {$commit_type ne $curType || $HEAD ne $curHEAD} {
1099 error_popup {Last scanned state does not match repository state.
1100
1101Its highly likely that another Git program modified the
1102repository since our last scan. A rescan is required
1103before a pull can be started.
1104}
1105 unlock_index
1106 rescan {set ui_status_value {Ready.}}
1107 return
1108 }
1109
1110 # -- No differences should exist before a pull.
1111 #
1112 if {[array size file_states] != 0} {
1113 error_popup {Uncommitted but modified files are present.
1114
1115You should not perform a pull with unmodified files in your working
1116directory as Git would be unable to recover from an incorrect merge.
1117
1118Commit or throw away all changes before starting a pull operation.
1119}
1120 unlock_index
1121 return
1122 }
1123
1124 set w [new_console "pull $remote $branch" \
1125 "Pulling new changes from branch $branch in $remote"]
1126 set cmd [list git pull]
1127 if {$repo_config(gui.pullsummary) eq {false}} {
1128 lappend cmd --no-summary
1129 }
1130 lappend cmd $remote
1131 lappend cmd $branch
1132 console_exec $w $cmd [list post_pull_remote $remote $branch]
1133}
1134
1135proc post_pull_remote {remote branch success} {
1136 global HEAD PARENT MERGE_HEAD commit_type selected_commit_type
1137 global ui_status_value
1138
1139 unlock_index
1140 if {$success} {
1141 repository_state commit_type HEAD MERGE_HEAD
1142 set PARENT $HEAD
1143 set selected_commit_type new
1144 set ui_status_value "Pulling $branch from $remote complete."
1145 } else {
1146 rescan [list set ui_status_value \
1147 "Conflicts detected while pulling $branch from $remote."]
1148 }
1149}
1150
1151proc push_to {remote} {
1152 set w [new_console "push $remote" \
1153 "Pushing changes to $remote"]
1154 set cmd [list git push]
1155 lappend cmd $remote
1156 console_exec $w $cmd
1157}
1158
1159######################################################################
1160##
1161## ui helpers
1162
1163proc mapcol {state path} {
1164 global all_cols ui_other
1165
1166 if {[catch {set r $all_cols($state)}]} {
1167 puts "error: no column for state={$state} $path"
1168 return $ui_other
1169 }
1170 return $r
1171}
1172
1173proc mapicon {state path} {
1174 global all_icons
1175
1176 if {[catch {set r $all_icons($state)}]} {
1177 puts "error: no icon for state={$state} $path"
1178 return file_plain
1179 }
1180 return $r
1181}
1182
1183proc mapdesc {state path} {
1184 global all_descs
1185
1186 if {[catch {set r $all_descs($state)}]} {
1187 puts "error: no desc for state={$state} $path"
1188 return $state
1189 }
1190 return $r
1191}
1192
1193proc escape_path {path} {
1194 regsub -all "\n" $path "\\n" path
1195 return $path
1196}
1197
1198proc short_path {path} {
1199 return [escape_path [lindex [file split $path] end]]
1200}
1201
1202set next_icon_id 0
1203set null_sha1 [string repeat 0 40]
1204
1205proc merge_state {path new_state {head_info {}} {index_info {}}} {
1206 global file_states next_icon_id null_sha1
1207
1208 set s0 [string index $new_state 0]
1209 set s1 [string index $new_state 1]
1210
1211 if {[catch {set info $file_states($path)}]} {
1212 set state __
1213 set icon n[incr next_icon_id]
1214 } else {
1215 set state [lindex $info 0]
1216 set icon [lindex $info 1]
1217 if {$head_info eq {}} {set head_info [lindex $info 2]}
1218 if {$index_info eq {}} {set index_info [lindex $info 3]}
1219 }
1220
1221 if {$s0 eq {?}} {set s0 [string index $state 0]} \
1222 elseif {$s0 eq {_}} {set s0 _}
1223
1224 if {$s1 eq {?}} {set s1 [string index $state 1]} \
1225 elseif {$s1 eq {_}} {set s1 _}
1226
1227 if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
1228 set head_info [list 0 $null_sha1]
1229 } elseif {$s0 ne {_} && [string index $state 0] eq {_}
1230 && $head_info eq {}} {
1231 set head_info $index_info
1232 }
1233
1234 set file_states($path) [list $s0$s1 $icon \
1235 $head_info $index_info \
1236 ]
1237 return $state
1238}
1239
1240proc display_file {path state} {
1241 global file_states file_lists selected_paths
1242
1243 set old_m [merge_state $path $state]
1244 set s $file_states($path)
1245 set new_m [lindex $s 0]
1246 set new_w [mapcol $new_m $path]
1247 set old_w [mapcol $old_m $path]
1248 set new_icon [mapicon $new_m $path]
1249
1250 if {$new_w ne $old_w} {
1251 set lno [lsearch -sorted $file_lists($old_w) $path]
1252 if {$lno >= 0} {
1253 incr lno
1254 $old_w conf -state normal
1255 $old_w delete $lno.0 [expr {$lno + 1}].0
1256 $old_w conf -state disabled
1257 }
1258
1259 lappend file_lists($new_w) $path
1260 set file_lists($new_w) [lsort $file_lists($new_w)]
1261 set lno [lsearch -sorted $file_lists($new_w) $path]
1262 incr lno
1263 $new_w conf -state normal
1264 $new_w image create $lno.0 \
1265 -align center -padx 5 -pady 1 \
1266 -name [lindex $s 1] \
1267 -image $new_icon
1268 $new_w insert $lno.1 "[escape_path $path]\n"
1269 if {[catch {set in_sel $selected_paths($path)}]} {
1270 set in_sel 0
1271 }
1272 if {$in_sel} {
1273 $new_w tag add in_sel $lno.0 [expr {$lno + 1}].0
1274 }
1275 $new_w conf -state disabled
1276 } elseif {$new_icon ne [mapicon $old_m $path]} {
1277 $new_w conf -state normal
1278 $new_w image conf [lindex $s 1] -image $new_icon
1279 $new_w conf -state disabled
1280 }
1281}
1282
1283proc display_all_files {} {
1284 global ui_index ui_other
1285 global file_states file_lists
1286 global last_clicked selected_paths
1287
1288 $ui_index conf -state normal
1289 $ui_other conf -state normal
1290
1291 $ui_index delete 0.0 end
1292 $ui_other delete 0.0 end
1293 set last_clicked {}
1294
1295 set file_lists($ui_index) [list]
1296 set file_lists($ui_other) [list]
1297
1298 foreach path [lsort [array names file_states]] {
1299 set s $file_states($path)
1300 set m [lindex $s 0]
1301 set w [mapcol $m $path]
1302 lappend file_lists($w) $path
1303 set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
1304 $w image create end \
1305 -align center -padx 5 -pady 1 \
1306 -name [lindex $s 1] \
1307 -image [mapicon $m $path]
1308 $w insert end "[escape_path $path]\n"
1309 if {[catch {set in_sel $selected_paths($path)}]} {
1310 set in_sel 0
1311 }
1312 if {$in_sel} {
1313 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
1314 }
1315 }
1316
1317 $ui_index conf -state disabled
1318 $ui_other conf -state disabled
1319}
1320
1321proc update_indexinfo {msg pathList after} {
1322 global update_index_cp ui_status_value
1323
1324 if {![lock_index update]} return
1325
1326 set update_index_cp 0
1327 set pathList [lsort $pathList]
1328 set totalCnt [llength $pathList]
1329 set batch [expr {int($totalCnt * .01) + 1}]
1330 if {$batch > 25} {set batch 25}
1331
1332 set ui_status_value [format \
1333 "$msg... %i/%i files (%.2f%%)" \
1334 $update_index_cp \
1335 $totalCnt \
1336 0.0]
1337 set fd [open "| git update-index -z --index-info" w]
1338 fconfigure $fd \
1339 -blocking 0 \
1340 -buffering full \
1341 -buffersize 512 \
1342 -translation binary
1343 fileevent $fd writable [list \
1344 write_update_indexinfo \
1345 $fd \
1346 $pathList \
1347 $totalCnt \
1348 $batch \
1349 $msg \
1350 $after \
1351 ]
1352}
1353
1354proc write_update_indexinfo {fd pathList totalCnt batch msg after} {
1355 global update_index_cp ui_status_value
1356 global file_states current_diff
1357
1358 if {$update_index_cp >= $totalCnt} {
1359 close $fd
1360 unlock_index
1361 uplevel #0 $after
1362 return
1363 }
1364
1365 for {set i $batch} \
1366 {$update_index_cp < $totalCnt && $i > 0} \
1367 {incr i -1} {
1368 set path [lindex $pathList $update_index_cp]
1369 incr update_index_cp
1370
1371 set s $file_states($path)
1372 switch -glob -- [lindex $s 0] {
1373 A? {set new _O}
1374 M? {set new _M}
1375 D? {set new _?}
1376 ?? {continue}
1377 }
1378 set info [lindex $s 2]
1379 if {$info eq {}} continue
1380
1381 puts -nonewline $fd $info
1382 puts -nonewline $fd "\t"
1383 puts -nonewline $fd $path
1384 puts -nonewline $fd "\0"
1385 display_file $path $new
1386 }
1387
1388 set ui_status_value [format \
1389 "$msg... %i/%i files (%.2f%%)" \
1390 $update_index_cp \
1391 $totalCnt \
1392 [expr {100.0 * $update_index_cp / $totalCnt}]]
1393}
1394
1395proc update_index {msg pathList after} {
1396 global update_index_cp ui_status_value
1397
1398 if {![lock_index update]} return
1399
1400 set update_index_cp 0
1401 set pathList [lsort $pathList]
1402 set totalCnt [llength $pathList]
1403 set batch [expr {int($totalCnt * .01) + 1}]
1404 if {$batch > 25} {set batch 25}
1405
1406 set ui_status_value [format \
1407 "$msg... %i/%i files (%.2f%%)" \
1408 $update_index_cp \
1409 $totalCnt \
1410 0.0]
1411 set fd [open "| git update-index --add --remove -z --stdin" w]
1412 fconfigure $fd \
1413 -blocking 0 \
1414 -buffering full \
1415 -buffersize 512 \
1416 -translation binary
1417 fileevent $fd writable [list \
1418 write_update_index \
1419 $fd \
1420 $pathList \
1421 $totalCnt \
1422 $batch \
1423 $msg \
1424 $after \
1425 ]
1426}
1427
1428proc write_update_index {fd pathList totalCnt batch msg after} {
1429 global update_index_cp ui_status_value
1430 global file_states current_diff
1431
1432 if {$update_index_cp >= $totalCnt} {
1433 close $fd
1434 unlock_index
1435 uplevel #0 $after
1436 return
1437 }
1438
1439 for {set i $batch} \
1440 {$update_index_cp < $totalCnt && $i > 0} \
1441 {incr i -1} {
1442 set path [lindex $pathList $update_index_cp]
1443 incr update_index_cp
1444
1445 switch -glob -- [lindex $file_states($path) 0] {
1446 AD -
1447 MD -
1448 _D {set new DD}
1449
1450 _M -
1451 MM -
1452 M_ {set new M_}
1453
1454 _O -
1455 AM -
1456 A_ {set new A_}
1457
1458 ?? {continue}
1459 }
1460
1461 puts -nonewline $fd $path
1462 puts -nonewline $fd "\0"
1463 display_file $path $new
1464 }
1465
1466 set ui_status_value [format \
1467 "$msg... %i/%i files (%.2f%%)" \
1468 $update_index_cp \
1469 $totalCnt \
1470 [expr {100.0 * $update_index_cp / $totalCnt}]]
1471}
1472
1473######################################################################
1474##
1475## remote management
1476
1477proc load_all_remotes {} {
1478 global gitdir all_remotes repo_config
1479
1480 set all_remotes [list]
1481 set rm_dir [file join $gitdir remotes]
1482 if {[file isdirectory $rm_dir]} {
1483 set all_remotes [concat $all_remotes [glob \
1484 -types f \
1485 -tails \
1486 -nocomplain \
1487 -directory $rm_dir *]]
1488 }
1489
1490 foreach line [array names repo_config remote.*.url] {
1491 if {[regexp ^remote\.(.*)\.url\$ $line line name]} {
1492 lappend all_remotes $name
1493 }
1494 }
1495
1496 set all_remotes [lsort -unique $all_remotes]
1497}
1498
1499proc populate_fetch_menu {m} {
1500 global gitdir all_remotes repo_config
1501
1502 foreach r $all_remotes {
1503 set enable 0
1504 if {![catch {set a $repo_config(remote.$r.url)}]} {
1505 if {![catch {set a $repo_config(remote.$r.fetch)}]} {
1506 set enable 1
1507 }
1508 } else {
1509 catch {
1510 set fd [open [file join $gitdir remotes $r] r]
1511 while {[gets $fd n] >= 0} {
1512 if {[regexp {^Pull:[ \t]*([^:]+):} $n]} {
1513 set enable 1
1514 break
1515 }
1516 }
1517 close $fd
1518 }
1519 }
1520
1521 if {$enable} {
1522 $m add command \
1523 -label "Fetch from $r..." \
1524 -command [list fetch_from $r] \
1525 -font font_ui
1526 }
1527 }
1528}
1529
1530proc populate_push_menu {m} {
1531 global gitdir all_remotes repo_config
1532
1533 foreach r $all_remotes {
1534 set enable 0
1535 if {![catch {set a $repo_config(remote.$r.url)}]} {
1536 if {![catch {set a $repo_config(remote.$r.push)}]} {
1537 set enable 1
1538 }
1539 } else {
1540 catch {
1541 set fd [open [file join $gitdir remotes $r] r]
1542 while {[gets $fd n] >= 0} {
1543 if {[regexp {^Push:[ \t]*([^:]+):} $n]} {
1544 set enable 1
1545 break
1546 }
1547 }
1548 close $fd
1549 }
1550 }
1551
1552 if {$enable} {
1553 $m add command \
1554 -label "Push to $r..." \
1555 -command [list push_to $r] \
1556 -font font_ui
1557 }
1558 }
1559}
1560
1561proc populate_pull_menu {m} {
1562 global gitdir repo_config all_remotes disable_on_lock
1563
1564 foreach remote $all_remotes {
1565 set rb {}
1566 if {[array get repo_config remote.$remote.url] ne {}} {
1567 if {[array get repo_config remote.$remote.fetch] ne {}} {
1568 regexp {^([^:]+):} \
1569 [lindex $repo_config(remote.$remote.fetch) 0] \
1570 line rb
1571 }
1572 } else {
1573 catch {
1574 set fd [open [file join $gitdir remotes $remote] r]
1575 while {[gets $fd line] >= 0} {
1576 if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} {
1577 break
1578 }
1579 }
1580 close $fd
1581 }
1582 }
1583
1584 set rb_short $rb
1585 regsub ^refs/heads/ $rb {} rb_short
1586 if {$rb_short ne {}} {
1587 $m add command \
1588 -label "Branch $rb_short from $remote..." \
1589 -command [list pull_remote $remote $rb] \
1590 -font font_ui
1591 lappend disable_on_lock \
1592 [list $m entryconf [$m index last] -state]
1593 }
1594 }
1595}
1596
1597######################################################################
1598##
1599## icons
1600
1601set filemask {
1602#define mask_width 14
1603#define mask_height 15
1604static unsigned char mask_bits[] = {
1605 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1606 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1607 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1608}
1609
1610image create bitmap file_plain -background white -foreground black -data {
1611#define plain_width 14
1612#define plain_height 15
1613static unsigned char plain_bits[] = {
1614 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1615 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1616 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1617} -maskdata $filemask
1618
1619image create bitmap file_mod -background white -foreground blue -data {
1620#define mod_width 14
1621#define mod_height 15
1622static unsigned char mod_bits[] = {
1623 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1624 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1625 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1626} -maskdata $filemask
1627
1628image create bitmap file_fulltick -background white -foreground "#007000" -data {
1629#define file_fulltick_width 14
1630#define file_fulltick_height 15
1631static unsigned char file_fulltick_bits[] = {
1632 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1633 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1634 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1635} -maskdata $filemask
1636
1637image create bitmap file_parttick -background white -foreground "#005050" -data {
1638#define parttick_width 14
1639#define parttick_height 15
1640static unsigned char parttick_bits[] = {
1641 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1642 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1643 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1644} -maskdata $filemask
1645
1646image create bitmap file_question -background white -foreground black -data {
1647#define file_question_width 14
1648#define file_question_height 15
1649static unsigned char file_question_bits[] = {
1650 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1651 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1652 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1653} -maskdata $filemask
1654
1655image create bitmap file_removed -background white -foreground red -data {
1656#define file_removed_width 14
1657#define file_removed_height 15
1658static unsigned char file_removed_bits[] = {
1659 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1660 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1661 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1662} -maskdata $filemask
1663
1664image create bitmap file_merge -background white -foreground blue -data {
1665#define file_merge_width 14
1666#define file_merge_height 15
1667static unsigned char file_merge_bits[] = {
1668 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1669 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1670 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1671} -maskdata $filemask
1672
1673set ui_index .vpane.files.index.list
1674set ui_other .vpane.files.other.list
1675set max_status_desc 0
1676foreach i {
1677 {__ i plain "Unmodified"}
1678 {_M i mod "Modified"}
1679 {M_ i fulltick "Included in commit"}
1680 {MM i parttick "Partially included"}
1681
1682 {_O o plain "Untracked"}
1683 {A_ o fulltick "Added by commit"}
1684 {AM o parttick "Partially added"}
1685 {AD o question "Added (but now gone)"}
1686
1687 {_D i question "Missing"}
1688 {DD i removed "Removed by commit"}
1689 {DO i removed "Removed (still exists)"}
1690 {DM i removed "Removed (but modified)"}
1691
1692 {UD i merge "Merge conflicts"}
1693 {UM i merge "Merge conflicts"}
1694 {U_ i merge "Merge conflicts"}
1695 } {
1696 if {$max_status_desc < [string length [lindex $i 3]]} {
1697 set max_status_desc [string length [lindex $i 3]]
1698 }
1699 if {[lindex $i 1] eq {i}} {
1700 set all_cols([lindex $i 0]) $ui_index
1701 } else {
1702 set all_cols([lindex $i 0]) $ui_other
1703 }
1704 set all_icons([lindex $i 0]) file_[lindex $i 2]
1705 set all_descs([lindex $i 0]) [lindex $i 3]
1706}
1707unset filemask i
1708
1709######################################################################
1710##
1711## util
1712
1713proc is_MacOSX {} {
1714 global tcl_platform tk_library
1715 if {$tcl_platform(platform) eq {unix}
1716 && $tcl_platform(os) eq {Darwin}
1717 && [string match /Library/Frameworks/* $tk_library]} {
1718 return 1
1719 }
1720 return 0
1721}
1722
1723proc bind_button3 {w cmd} {
1724 bind $w <Any-Button-3> $cmd
1725 if {[is_MacOSX]} {
1726 bind $w <Control-Button-1> $cmd
1727 }
1728}
1729
1730proc incr_font_size {font {amt 1}} {
1731 set sz [font configure $font -size]
1732 incr sz $amt
1733 font configure $font -size $sz
1734 font configure ${font}bold -size $sz
1735}
1736
1737proc hook_failed_popup {hook msg} {
1738 global gitdir appname
1739
1740 set w .hookfail
1741 toplevel $w
1742
1743 frame $w.m
1744 label $w.m.l1 -text "$hook hook failed:" \
1745 -anchor w \
1746 -justify left \
1747 -font font_uibold
1748 text $w.m.t \
1749 -background white -borderwidth 1 \
1750 -relief sunken \
1751 -width 80 -height 10 \
1752 -font font_diff \
1753 -yscrollcommand [list $w.m.sby set]
1754 label $w.m.l2 \
1755 -text {You must correct the above errors before committing.} \
1756 -anchor w \
1757 -justify left \
1758 -font font_uibold
1759 scrollbar $w.m.sby -command [list $w.m.t yview]
1760 pack $w.m.l1 -side top -fill x
1761 pack $w.m.l2 -side bottom -fill x
1762 pack $w.m.sby -side right -fill y
1763 pack $w.m.t -side left -fill both -expand 1
1764 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1765
1766 $w.m.t insert 1.0 $msg
1767 $w.m.t conf -state disabled
1768
1769 button $w.ok -text OK \
1770 -width 15 \
1771 -font font_ui \
1772 -command "destroy $w"
1773 pack $w.ok -side bottom -anchor e -pady 10 -padx 10
1774
1775 bind $w <Visibility> "grab $w; focus $w"
1776 bind $w <Key-Return> "destroy $w"
1777 wm title $w "$appname ([lindex [file split \
1778 [file normalize [file dirname $gitdir]]] \
1779 end]): error"
1780 tkwait window $w
1781}
1782
1783set next_console_id 0
1784
1785proc new_console {short_title long_title} {
1786 global next_console_id console_data
1787 set w .console[incr next_console_id]
1788 set console_data($w) [list $short_title $long_title]
1789 return [console_init $w]
1790}
1791
1792proc console_init {w} {
1793 global console_cr console_data
1794 global gitdir appname M1B
1795
1796 set console_cr($w) 1.0
1797 toplevel $w
1798 frame $w.m
1799 label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
1800 -anchor w \
1801 -justify left \
1802 -font font_uibold
1803 text $w.m.t \
1804 -background white -borderwidth 1 \
1805 -relief sunken \
1806 -width 80 -height 10 \
1807 -font font_diff \
1808 -state disabled \
1809 -yscrollcommand [list $w.m.sby set]
1810 label $w.m.s -text {Working... please wait...} \
1811 -anchor w \
1812 -justify left \
1813 -font font_uibold
1814 scrollbar $w.m.sby -command [list $w.m.t yview]
1815 pack $w.m.l1 -side top -fill x
1816 pack $w.m.s -side bottom -fill x
1817 pack $w.m.sby -side right -fill y
1818 pack $w.m.t -side left -fill both -expand 1
1819 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1820
1821 menu $w.ctxm -tearoff 0
1822 $w.ctxm add command -label "Copy" \
1823 -font font_ui \
1824 -command "tk_textCopy $w.m.t"
1825 $w.ctxm add command -label "Select All" \
1826 -font font_ui \
1827 -command "$w.m.t tag add sel 0.0 end"
1828 $w.ctxm add command -label "Copy All" \
1829 -font font_ui \
1830 -command "
1831 $w.m.t tag add sel 0.0 end
1832 tk_textCopy $w.m.t
1833 $w.m.t tag remove sel 0.0 end
1834 "
1835
1836 button $w.ok -text {Close} \
1837 -font font_ui \
1838 -state disabled \
1839 -command "destroy $w"
1840 pack $w.ok -side bottom -anchor e -pady 10 -padx 10
1841
1842 bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y"
1843 bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break"
1844 bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break"
1845 bind $w <Visibility> "focus $w"
1846 wm title $w "$appname ([lindex [file split \
1847 [file normalize [file dirname $gitdir]]] \
1848 end]): [lindex $console_data($w) 0]"
1849 return $w
1850}
1851
1852proc console_exec {w cmd {after {}}} {
1853 global tcl_platform
1854
1855 # -- Windows tosses the enviroment when we exec our child.
1856 # But most users need that so we have to relogin. :-(
1857 #
1858 if {$tcl_platform(platform) eq {windows}} {
1859 set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
1860 }
1861
1862 # -- Tcl won't let us redirect both stdout and stderr to
1863 # the same pipe. So pass it through cat...
1864 #
1865 set cmd [concat | $cmd |& cat]
1866
1867 set fd_f [open $cmd r]
1868 fconfigure $fd_f -blocking 0 -translation binary
1869 fileevent $fd_f readable [list console_read $w $fd_f $after]
1870}
1871
1872proc console_read {w fd after} {
1873 global console_cr console_data
1874
1875 set buf [read $fd]
1876 if {$buf ne {}} {
1877 if {![winfo exists $w]} {console_init $w}
1878 $w.m.t conf -state normal
1879 set c 0
1880 set n [string length $buf]
1881 while {$c < $n} {
1882 set cr [string first "\r" $buf $c]
1883 set lf [string first "\n" $buf $c]
1884 if {$cr < 0} {set cr [expr {$n + 1}]}
1885 if {$lf < 0} {set lf [expr {$n + 1}]}
1886
1887 if {$lf < $cr} {
1888 $w.m.t insert end [string range $buf $c $lf]
1889 set console_cr($w) [$w.m.t index {end -1c}]
1890 set c $lf
1891 incr c
1892 } else {
1893 $w.m.t delete $console_cr($w) end
1894 $w.m.t insert end "\n"
1895 $w.m.t insert end [string range $buf $c $cr]
1896 set c $cr
1897 incr c
1898 }
1899 }
1900 $w.m.t conf -state disabled
1901 $w.m.t see end
1902 }
1903
1904 fconfigure $fd -blocking 1
1905 if {[eof $fd]} {
1906 if {[catch {close $fd}]} {
1907 if {![winfo exists $w]} {console_init $w}
1908 $w.m.s conf -background red -text {Error: Command Failed}
1909 $w.ok conf -state normal
1910 set ok 0
1911 } elseif {[winfo exists $w]} {
1912 $w.m.s conf -background green -text {Success}
1913 $w.ok conf -state normal
1914 set ok 1
1915 }
1916 array unset console_cr $w
1917 array unset console_data $w
1918 if {$after ne {}} {
1919 uplevel #0 $after $ok
1920 }
1921 return
1922 }
1923 fconfigure $fd -blocking 0
1924}
1925
1926######################################################################
1927##
1928## ui commands
1929
1930set starting_gitk_msg {Please wait... Starting gitk...}
1931
1932proc do_gitk {} {
1933 global tcl_platform ui_status_value starting_gitk_msg
1934
1935 set ui_status_value $starting_gitk_msg
1936 after 10000 {
1937 if {$ui_status_value eq $starting_gitk_msg} {
1938 set ui_status_value {Ready.}
1939 }
1940 }
1941
1942 if {$tcl_platform(platform) eq {windows}} {
1943 exec sh -c gitk &
1944 } else {
1945 exec gitk &
1946 }
1947}
1948
1949proc do_repack {} {
1950 set w [new_console {repack} \
1951 {Repacking the object database}]
1952 set cmd [list git repack]
1953 lappend cmd -a
1954 lappend cmd -d
1955 console_exec $w $cmd
1956}
1957
1958proc do_fsck_objects {} {
1959 set w [new_console {fsck-objects} \
1960 {Verifying the object database with fsck-objects}]
1961 set cmd [list git fsck-objects]
1962 lappend cmd --full
1963 lappend cmd --cache
1964 lappend cmd --strict
1965 console_exec $w $cmd
1966}
1967
1968set is_quitting 0
1969
1970proc do_quit {} {
1971 global gitdir ui_comm is_quitting repo_config commit_type
1972
1973 if {$is_quitting} return
1974 set is_quitting 1
1975
1976 # -- Stash our current commit buffer.
1977 #
1978 set save [file join $gitdir GITGUI_MSG]
1979 set msg [string trim [$ui_comm get 0.0 end]]
1980 if {![string match amend* $commit_type]
1981 && [$ui_comm edit modified]
1982 && $msg ne {}} {
1983 catch {
1984 set fd [open $save w]
1985 puts $fd [string trim [$ui_comm get 0.0 end]]
1986 close $fd
1987 }
1988 } else {
1989 catch {file delete $save}
1990 }
1991
1992 # -- Stash our current window geometry into this repository.
1993 #
1994 set cfg_geometry [list]
1995 lappend cfg_geometry [wm geometry .]
1996 lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
1997 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
1998 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1999 set rc_geometry {}
2000 }
2001 if {$cfg_geometry ne $rc_geometry} {
2002 catch {exec git repo-config gui.geometry $cfg_geometry}
2003 }
2004
2005 destroy .
2006}
2007
2008proc do_rescan {} {
2009 rescan {set ui_status_value {Ready.}}
2010}
2011
2012proc remove_helper {txt paths} {
2013 global file_states current_diff
2014
2015 if {![lock_index begin-update]} return
2016
2017 set pathList [list]
2018 set after {}
2019 foreach path $paths {
2020 switch -glob -- [lindex $file_states($path) 0] {
2021 A? -
2022 M? -
2023 D? {
2024 lappend pathList $path
2025 if {$path eq $current_diff} {
2026 set after {reshow_diff;}
2027 }
2028 }
2029 }
2030 }
2031 if {$pathList eq {}} {
2032 unlock_index
2033 } else {
2034 update_indexinfo \
2035 $txt \
2036 $pathList \
2037 [concat $after {set ui_status_value {Ready.}}]
2038 }
2039}
2040
2041proc do_remove_selection {} {
2042 global current_diff selected_paths
2043
2044 if {[array size selected_paths] > 0} {
2045 remove_helper \
2046 {Removing selected files from commit} \
2047 [array names selected_paths]
2048 } elseif {$current_diff ne {}} {
2049 remove_helper \
2050 "Removing [short_path $current_diff] from commit" \
2051 [list $current_diff]
2052 }
2053}
2054
2055proc include_helper {txt paths} {
2056 global file_states current_diff
2057
2058 if {![lock_index begin-update]} return
2059
2060 set pathList [list]
2061 set after {}
2062 foreach path $paths {
2063 switch -glob -- [lindex $file_states($path) 0] {
2064 AM -
2065 AD -
2066 MM -
2067 U? -
2068 _M -
2069 _D -
2070 _O {
2071 lappend pathList $path
2072 if {$path eq $current_diff} {
2073 set after {reshow_diff;}
2074 }
2075 }
2076 }
2077 }
2078 if {$pathList eq {}} {
2079 unlock_index
2080 } else {
2081 update_index \
2082 $txt \
2083 $pathList \
2084 [concat $after {set ui_status_value {Ready to commit.}}]
2085 }
2086}
2087
2088proc do_include_selection {} {
2089 global current_diff selected_paths
2090
2091 if {[array size selected_paths] > 0} {
2092 include_helper \
2093 {Including selected files} \
2094 [array names selected_paths]
2095 } elseif {$current_diff ne {}} {
2096 include_helper \
2097 "Including [short_path $current_diff]" \
2098 [list $current_diff]
2099 }
2100}
2101
2102proc do_include_all {} {
2103 global file_states
2104
2105 set paths [list]
2106 foreach path [array names file_states] {
2107 switch -- [lindex $file_states($path) 0] {
2108 AM -
2109 AD -
2110 MM -
2111 _M -
2112 _D {lappend paths $path}
2113 }
2114 }
2115 include_helper \
2116 {Including all modified files} \
2117 $paths
2118}
2119
2120proc do_signoff {} {
2121 global ui_comm
2122
2123 set me [committer_ident]
2124 if {$me eq {}} return
2125
2126 set sob "Signed-off-by: $me"
2127 set last [$ui_comm get {end -1c linestart} {end -1c}]
2128 if {$last ne $sob} {
2129 $ui_comm edit separator
2130 if {$last ne {}
2131 && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
2132 $ui_comm insert end "\n"
2133 }
2134 $ui_comm insert end "\n$sob"
2135 $ui_comm edit separator
2136 $ui_comm see end
2137 }
2138}
2139
2140proc do_select_commit_type {} {
2141 global commit_type selected_commit_type
2142
2143 if {$selected_commit_type eq {new}
2144 && [string match amend* $commit_type]} {
2145 create_new_commit
2146 } elseif {$selected_commit_type eq {amend}
2147 && ![string match amend* $commit_type]} {
2148 load_last_commit
2149
2150 # The amend request was rejected...
2151 #
2152 if {![string match amend* $commit_type]} {
2153 set selected_commit_type new
2154 }
2155 }
2156}
2157
2158proc do_commit {} {
2159 commit_tree
2160}
2161
2162proc do_about {} {
2163 global appname
2164
2165 set w .about_dialog
2166 toplevel $w
2167 wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
2168
2169 label $w.header -text "About $appname" \
2170 -font font_uibold
2171 pack $w.header -side top -fill x
2172
2173 frame $w.buttons
2174 button $w.buttons.close -text {Close} \
2175 -font font_ui \
2176 -command [list destroy $w]
2177 pack $w.buttons.close -side right
2178 pack $w.buttons -side bottom -fill x -pady 10 -padx 10
2179
2180 label $w.desc \
2181 -text "$appname - a commit creation tool for Git.
2182
2183Copyright © 2006 Shawn Pearce, Paul Mackerras
2184
2185Use and redistribute under the terms of the
2186GNU General Public License, v. 2.0 or later." \
2187 -padx 5 -pady 5 \
2188 -justify left \
2189 -anchor w \
2190 -borderwidth 1 \
2191 -relief solid \
2192 -font font_ui
2193 pack $w.desc -side top -fill x -padx 5 -pady 5
2194
2195 label $w.vers \
2196 -text [exec git --version] \
2197 -padx 5 -pady 5 \
2198 -justify left \
2199 -anchor w \
2200 -borderwidth 1 \
2201 -relief solid \
2202 -font font_ui
2203 pack $w.vers -side top -fill x -padx 5 -pady 5
2204
2205 bind $w <Visibility> "grab $w; focus $w"
2206 bind $w <Key-Escape> "destroy $w"
2207 wm title $w "About $appname"
2208 tkwait window $w
2209}
2210
2211proc do_options {} {
2212 global appname gitdir font_descs
2213 global repo_config global_config
2214 global repo_config_new global_config_new
2215
2216 array unset repo_config_new
2217 array unset global_config_new
2218 foreach name [array names repo_config] {
2219 set repo_config_new($name) $repo_config($name)
2220 }
2221 load_config 1
2222 foreach name [array names repo_config] {
2223 switch -- $name {
2224 gui.diffcontext {continue}
2225 }
2226 set repo_config_new($name) $repo_config($name)
2227 }
2228 foreach name [array names global_config] {
2229 set global_config_new($name) $global_config($name)
2230 }
2231 set reponame [lindex [file split \
2232 [file normalize [file dirname $gitdir]]] \
2233 end]
2234
2235 set w .options_editor
2236 toplevel $w
2237 wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
2238
2239 label $w.header -text "$appname Options" \
2240 -font font_uibold
2241 pack $w.header -side top -fill x
2242
2243 frame $w.buttons
2244 button $w.buttons.restore -text {Restore Defaults} \
2245 -font font_ui \
2246 -command do_restore_defaults
2247 pack $w.buttons.restore -side left
2248 button $w.buttons.save -text Save \
2249 -font font_ui \
2250 -command [list do_save_config $w]
2251 pack $w.buttons.save -side right
2252 button $w.buttons.cancel -text {Cancel} \
2253 -font font_ui \
2254 -command [list destroy $w]
2255 pack $w.buttons.cancel -side right
2256 pack $w.buttons -side bottom -fill x -pady 10 -padx 10
2257
2258 labelframe $w.repo -text "$reponame Repository" \
2259 -font font_ui \
2260 -relief raised -borderwidth 2
2261 labelframe $w.global -text {Global (All Repositories)} \
2262 -font font_ui \
2263 -relief raised -borderwidth 2
2264 pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5
2265 pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5
2266
2267 foreach option {
2268 {b partialinclude {Allow Partially Included Files}}
2269 {b pullsummary {Show Pull Summary}}
2270 {b trustmtime {Trust File Modification Timestamps}}
2271 {i diffcontext {Number of Diff Context Lines}}
2272 } {
2273 set type [lindex $option 0]
2274 set name [lindex $option 1]
2275 set text [lindex $option 2]
2276 foreach f {repo global} {
2277 switch $type {
2278 b {
2279 checkbutton $w.$f.$name -text $text \
2280 -variable ${f}_config_new(gui.$name) \
2281 -onvalue true \
2282 -offvalue false \
2283 -font font_ui
2284 pack $w.$f.$name -side top -anchor w
2285 }
2286 i {
2287 frame $w.$f.$name
2288 label $w.$f.$name.l -text "$text:" -font font_ui
2289 pack $w.$f.$name.l -side left -anchor w -fill x
2290 spinbox $w.$f.$name.v \
2291 -textvariable ${f}_config_new(gui.$name) \
2292 -from 1 -to 99 -increment 1 \
2293 -width 3 \
2294 -font font_ui
2295 pack $w.$f.$name.v -side right -anchor e
2296 pack $w.$f.$name -side top -anchor w -fill x
2297 }
2298 }
2299 }
2300 }
2301
2302 set all_fonts [lsort [font families]]
2303 foreach option $font_descs {
2304 set name [lindex $option 0]
2305 set font [lindex $option 1]
2306 set text [lindex $option 2]
2307
2308 set global_config_new(gui.$font^^family) \
2309 [font configure $font -family]
2310 set global_config_new(gui.$font^^size) \
2311 [font configure $font -size]
2312
2313 frame $w.global.$name
2314 label $w.global.$name.l -text "$text:" -font font_ui
2315 pack $w.global.$name.l -side left -anchor w -fill x
2316 eval tk_optionMenu $w.global.$name.family \
2317 global_config_new(gui.$font^^family) \
2318 $all_fonts
2319 spinbox $w.global.$name.size \
2320 -textvariable global_config_new(gui.$font^^size) \
2321 -from 2 -to 80 -increment 1 \
2322 -width 3 \
2323 -font font_ui
2324 pack $w.global.$name.size -side right -anchor e
2325 pack $w.global.$name.family -side right -anchor e
2326 pack $w.global.$name -side top -anchor w -fill x
2327 }
2328
2329 bind $w <Visibility> "grab $w; focus $w"
2330 bind $w <Key-Escape> "destroy $w"
2331 wm title $w "$appname ($reponame): Options"
2332 tkwait window $w
2333}
2334
2335proc do_restore_defaults {} {
2336 global font_descs default_config repo_config
2337 global repo_config_new global_config_new
2338
2339 foreach name [array names default_config] {
2340 set repo_config_new($name) $default_config($name)
2341 set global_config_new($name) $default_config($name)
2342 }
2343
2344 foreach option $font_descs {
2345 set name [lindex $option 0]
2346 set repo_config(gui.$name) $default_config(gui.$name)
2347 }
2348 apply_config
2349
2350 foreach option $font_descs {
2351 set name [lindex $option 0]
2352 set font [lindex $option 1]
2353 set global_config_new(gui.$font^^family) \
2354 [font configure $font -family]
2355 set global_config_new(gui.$font^^size) \
2356 [font configure $font -size]
2357 }
2358}
2359
2360proc do_save_config {w} {
2361 if {[catch {save_config} err]} {
2362 error_popup "Failed to completely save options:\n\n$err"
2363 }
2364 reshow_diff
2365 destroy $w
2366}
2367
2368proc do_windows_shortcut {} {
2369 global gitdir appname argv0
2370
2371 set reponame [lindex [file split \
2372 [file normalize [file dirname $gitdir]]] \
2373 end]
2374
2375 if {[catch {
2376 set desktop [exec cygpath \
2377 --windows \
2378 --absolute \
2379 --long-name \
2380 --desktop]
2381 }]} {
2382 set desktop .
2383 }
2384 set fn [tk_getSaveFile \
2385 -parent . \
2386 -title "$appname ($reponame): Create Desktop Icon" \
2387 -initialdir $desktop \
2388 -initialfile "Git $reponame.bat"]
2389 if {$fn != {}} {
2390 if {[catch {
2391 set fd [open $fn w]
2392 set sh [exec cygpath \
2393 --windows \
2394 --absolute \
2395 --long-name \
2396 /bin/sh]
2397 set me [exec cygpath \
2398 --unix \
2399 --absolute \
2400 $argv0]
2401 set gd [exec cygpath \
2402 --unix \
2403 --absolute \
2404 $gitdir]
2405 regsub -all ' $me "'\\''" me
2406 regsub -all ' $gd "'\\''" gd
2407 puts -nonewline $fd "\"$sh\" --login -c \""
2408 puts -nonewline $fd "GIT_DIR='$gd'"
2409 puts -nonewline $fd " '$me'"
2410 puts $fd "&\""
2411 close $fd
2412 } err]} {
2413 error_popup "Cannot write script:\n\n$err"
2414 }
2415 }
2416}
2417
2418proc do_macosx_app {} {
2419 global gitdir appname argv0 env
2420
2421 set reponame [lindex [file split \
2422 [file normalize [file dirname $gitdir]]] \
2423 end]
2424
2425 set fn [tk_getSaveFile \
2426 -parent . \
2427 -title "$appname ($reponame): Create Desktop Icon" \
2428 -initialdir [file join $env(HOME) Desktop] \
2429 -initialfile "Git $reponame.app"]
2430 if {$fn != {}} {
2431 if {[catch {
2432 set Contents [file join $fn Contents]
2433 set MacOS [file join $Contents MacOS]
2434 set exe [file join $MacOS git-gui]
2435
2436 file mkdir $MacOS
2437
2438 set fd [open [file join $Contents Info.plist] w]
2439 puts $fd {<?xml version="1.0" encoding="UTF-8"?>
2440<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
2441<plist version="1.0">
2442<dict>
2443 <key>CFBundleDevelopmentRegion</key>
2444 <string>English</string>
2445 <key>CFBundleExecutable</key>
2446 <string>git-gui</string>
2447 <key>CFBundleIdentifier</key>
2448 <string>org.spearce.git-gui</string>
2449 <key>CFBundleInfoDictionaryVersion</key>
2450 <string>6.0</string>
2451 <key>CFBundlePackageType</key>
2452 <string>APPL</string>
2453 <key>CFBundleSignature</key>
2454 <string>????</string>
2455 <key>CFBundleVersion</key>
2456 <string>1.0</string>
2457 <key>NSPrincipalClass</key>
2458 <string>NSApplication</string>
2459</dict>
2460</plist>}
2461 close $fd
2462
2463 set fd [open $exe w]
2464 set gd [file normalize $gitdir]
2465 set ep [file normalize [exec git --exec-path]]
2466 regsub -all ' $gd "'\\''" gd
2467 regsub -all ' $ep "'\\''" ep
2468 puts $fd "#!/bin/sh"
2469 foreach name [array names env] {
2470 if {[string match GIT_* $name]} {
2471 regsub -all ' $env($name) "'\\''" v
2472 puts $fd "export $name='$v'"
2473 }
2474 }
2475 puts $fd "export PATH='$ep':\$PATH"
2476 puts $fd "export GIT_DIR='$gd'"
2477 puts $fd "exec [file normalize $argv0]"
2478 close $fd
2479
2480 file attributes $exe -permissions u+x,g+x,o+x
2481 } err]} {
2482 error_popup "Cannot write icon:\n\n$err"
2483 }
2484 }
2485}
2486
2487proc toggle_or_diff {w x y} {
2488 global file_states file_lists current_diff ui_index ui_other
2489 global last_clicked selected_paths
2490
2491 set pos [split [$w index @$x,$y] .]
2492 set lno [lindex $pos 0]
2493 set col [lindex $pos 1]
2494 set path [lindex $file_lists($w) [expr {$lno - 1}]]
2495 if {$path eq {}} {
2496 set last_clicked {}
2497 return
2498 }
2499
2500 set last_clicked [list $w $lno]
2501 array unset selected_paths
2502 $ui_index tag remove in_sel 0.0 end
2503 $ui_other tag remove in_sel 0.0 end
2504
2505 if {$col == 0} {
2506 if {$current_diff eq $path} {
2507 set after {reshow_diff;}
2508 } else {
2509 set after {}
2510 }
2511 switch -glob -- [lindex $file_states($path) 0] {
2512 A_ -
2513 M_ -
2514 DD -
2515 DO -
2516 DM {
2517 update_indexinfo \
2518 "Removing [short_path $path] from commit" \
2519 [list $path] \
2520 [concat $after {set ui_status_value {Ready.}}]
2521 }
2522 ?? {
2523 update_index \
2524 "Including [short_path $path]" \
2525 [list $path] \
2526 [concat $after {set ui_status_value {Ready.}}]
2527 }
2528 }
2529 } else {
2530 show_diff $path $w $lno
2531 }
2532}
2533
2534proc add_one_to_selection {w x y} {
2535 global file_lists
2536 global last_clicked selected_paths
2537
2538 set pos [split [$w index @$x,$y] .]
2539 set lno [lindex $pos 0]
2540 set col [lindex $pos 1]
2541 set path [lindex $file_lists($w) [expr {$lno - 1}]]
2542 if {$path eq {}} {
2543 set last_clicked {}
2544 return
2545 }
2546
2547 set last_clicked [list $w $lno]
2548 if {[catch {set in_sel $selected_paths($path)}]} {
2549 set in_sel 0
2550 }
2551 if {$in_sel} {
2552 unset selected_paths($path)
2553 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
2554 } else {
2555 set selected_paths($path) 1
2556 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
2557 }
2558}
2559
2560proc add_range_to_selection {w x y} {
2561 global file_lists
2562 global last_clicked selected_paths
2563
2564 if {[lindex $last_clicked 0] ne $w} {
2565 toggle_or_diff $w $x $y
2566 return
2567 }
2568
2569 set pos [split [$w index @$x,$y] .]
2570 set lno [lindex $pos 0]
2571 set lc [lindex $last_clicked 1]
2572 if {$lc < $lno} {
2573 set begin $lc
2574 set end $lno
2575 } else {
2576 set begin $lno
2577 set end $lc
2578 }
2579
2580 foreach path [lrange $file_lists($w) \
2581 [expr {$begin - 1}] \
2582 [expr {$end - 1}]] {
2583 set selected_paths($path) 1
2584 }
2585 $w tag add in_sel $begin.0 [expr {$end + 1}].0
2586}
2587
2588######################################################################
2589##
2590## config defaults
2591
2592set cursor_ptr arrow
2593font create font_diff -family Courier -size 10
2594font create font_ui
2595catch {
2596 label .dummy
2597 eval font configure font_ui [font actual [.dummy cget -font]]
2598 destroy .dummy
2599}
2600
2601font create font_uibold
2602font create font_diffbold
2603
2604set M1B M1
2605set M1T M1
2606if {$tcl_platform(platform) eq {windows}} {
2607 set M1B Control
2608 set M1T Ctrl
2609} elseif {[is_MacOSX]} {
2610 set M1B M1
2611 set M1T Cmd
2612}
2613
2614proc apply_config {} {
2615 global repo_config font_descs
2616
2617 foreach option $font_descs {
2618 set name [lindex $option 0]
2619 set font [lindex $option 1]
2620 if {[catch {
2621 foreach {cn cv} $repo_config(gui.$name) {
2622 font configure $font $cn $cv
2623 }
2624 } err]} {
2625 error_popup "Invalid font specified in gui.$name:\n\n$err"
2626 }
2627 foreach {cn cv} [font configure $font] {
2628 font configure ${font}bold $cn $cv
2629 }
2630 font configure ${font}bold -weight bold
2631 }
2632}
2633
2634set default_config(gui.trustmtime) false
2635set default_config(gui.pullsummary) true
2636set default_config(gui.partialinclude) false
2637set default_config(gui.diffcontext) 5
2638set default_config(gui.fontui) [font configure font_ui]
2639set default_config(gui.fontdiff) [font configure font_diff]
2640set font_descs {
2641 {fontui font_ui {Main Font}}
2642 {fontdiff font_diff {Diff/Console Font}}
2643}
2644load_config 0
2645apply_config
2646
2647######################################################################
2648##
2649## ui construction
2650
2651# -- Menu Bar
2652#
2653menu .mbar -tearoff 0
2654.mbar add cascade -label Repository -menu .mbar.repository
2655.mbar add cascade -label Edit -menu .mbar.edit
2656.mbar add cascade -label Commit -menu .mbar.commit
2657if {!$single_commit} {
2658 .mbar add cascade -label Fetch -menu .mbar.fetch
2659 .mbar add cascade -label Pull -menu .mbar.pull
2660 .mbar add cascade -label Push -menu .mbar.push
2661}
2662.mbar add cascade -label Help -menu .mbar.help
2663. configure -menu .mbar
2664
2665# -- Repository Menu
2666#
2667menu .mbar.repository
2668.mbar.repository add command -label Visualize \
2669 -command do_gitk \
2670 -font font_ui
2671if {!$single_commit} {
2672 .mbar.repository add separator
2673
2674 .mbar.repository add command -label {Repack Database} \
2675 -command do_repack \
2676 -font font_ui
2677
2678 .mbar.repository add command -label {Verify Database} \
2679 -command do_fsck_objects \
2680 -font font_ui
2681
2682 .mbar.repository add separator
2683
2684 if {$tcl_platform(platform) eq {windows}} {
2685 .mbar.repository add command \
2686 -label {Create Desktop Icon} \
2687 -command do_windows_shortcut \
2688 -font font_ui
2689 } elseif {[is_MacOSX]} {
2690 .mbar.repository add command \
2691 -label {Create Desktop Icon} \
2692 -command do_macosx_app \
2693 -font font_ui
2694 }
2695}
2696.mbar.repository add command -label Quit \
2697 -command do_quit \
2698 -accelerator $M1T-Q \
2699 -font font_ui
2700
2701# -- Edit Menu
2702#
2703menu .mbar.edit
2704.mbar.edit add command -label Undo \
2705 -command {catch {[focus] edit undo}} \
2706 -accelerator $M1T-Z \
2707 -font font_ui
2708.mbar.edit add command -label Redo \
2709 -command {catch {[focus] edit redo}} \
2710 -accelerator $M1T-Y \
2711 -font font_ui
2712.mbar.edit add separator
2713.mbar.edit add command -label Cut \
2714 -command {catch {tk_textCut [focus]}} \
2715 -accelerator $M1T-X \
2716 -font font_ui
2717.mbar.edit add command -label Copy \
2718 -command {catch {tk_textCopy [focus]}} \
2719 -accelerator $M1T-C \
2720 -font font_ui
2721.mbar.edit add command -label Paste \
2722 -command {catch {tk_textPaste [focus]; [focus] see insert}} \
2723 -accelerator $M1T-V \
2724 -font font_ui
2725.mbar.edit add command -label Delete \
2726 -command {catch {[focus] delete sel.first sel.last}} \
2727 -accelerator Del \
2728 -font font_ui
2729.mbar.edit add separator
2730.mbar.edit add command -label {Select All} \
2731 -command {catch {[focus] tag add sel 0.0 end}} \
2732 -accelerator $M1T-A \
2733 -font font_ui
2734.mbar.edit add separator
2735.mbar.edit add command -label {Options...} \
2736 -command do_options \
2737 -font font_ui
2738
2739# -- Commit Menu
2740#
2741menu .mbar.commit
2742
2743.mbar.commit add radiobutton \
2744 -label {New Commit} \
2745 -command do_select_commit_type \
2746 -variable selected_commit_type \
2747 -value new \
2748 -font font_ui
2749lappend disable_on_lock \
2750 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2751
2752.mbar.commit add radiobutton \
2753 -label {Amend Last Commit} \
2754 -command do_select_commit_type \
2755 -variable selected_commit_type \
2756 -value amend \
2757 -font font_ui
2758lappend disable_on_lock \
2759 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2760
2761.mbar.commit add separator
2762
2763.mbar.commit add command -label Rescan \
2764 -command do_rescan \
2765 -accelerator F5 \
2766 -font font_ui
2767lappend disable_on_lock \
2768 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2769
2770.mbar.commit add command -label {Remove From Commit} \
2771 -command do_remove_selection \
2772 -font font_ui
2773lappend disable_on_lock \
2774 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2775
2776.mbar.commit add command -label {Include In Commit} \
2777 -command do_include_selection \
2778 -font font_ui
2779lappend disable_on_lock \
2780 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2781
2782.mbar.commit add command -label {Include All In Commit} \
2783 -command do_include_all \
2784 -accelerator $M1T-I \
2785 -font font_ui
2786lappend disable_on_lock \
2787 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2788
2789.mbar.commit add separator
2790
2791.mbar.commit add command -label {Sign Off} \
2792 -command do_signoff \
2793 -accelerator $M1T-S \
2794 -font font_ui
2795
2796.mbar.commit add command -label Commit \
2797 -command do_commit \
2798 -accelerator $M1T-Return \
2799 -font font_ui
2800lappend disable_on_lock \
2801 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2802
2803# -- Transport menus
2804#
2805if {!$single_commit} {
2806 menu .mbar.fetch
2807 menu .mbar.pull
2808 menu .mbar.push
2809}
2810
2811# -- Help Menm
2812#
2813menu .mbar.help
2814
2815.mbar.help add command -label "About $appname" \
2816 -command do_about \
2817 -font font_ui
2818
2819# -- Main Window Layout
2820#
2821panedwindow .vpane -orient vertical
2822panedwindow .vpane.files -orient horizontal
2823.vpane add .vpane.files -sticky nsew -height 100 -width 400
2824pack .vpane -anchor n -side top -fill both -expand 1
2825
2826# -- Index File List
2827#
2828frame .vpane.files.index -height 100 -width 400
2829label .vpane.files.index.title -text {Modified Files} \
2830 -background green \
2831 -font font_ui
2832text $ui_index -background white -borderwidth 0 \
2833 -width 40 -height 10 \
2834 -font font_ui \
2835 -cursor $cursor_ptr \
2836 -yscrollcommand {.vpane.files.index.sb set} \
2837 -state disabled
2838scrollbar .vpane.files.index.sb -command [list $ui_index yview]
2839pack .vpane.files.index.title -side top -fill x
2840pack .vpane.files.index.sb -side right -fill y
2841pack $ui_index -side left -fill both -expand 1
2842.vpane.files add .vpane.files.index -sticky nsew
2843
2844# -- Other (Add) File List
2845#
2846frame .vpane.files.other -height 100 -width 100
2847label .vpane.files.other.title -text {Untracked Files} \
2848 -background red \
2849 -font font_ui
2850text $ui_other -background white -borderwidth 0 \
2851 -width 40 -height 10 \
2852 -font font_ui \
2853 -cursor $cursor_ptr \
2854 -yscrollcommand {.vpane.files.other.sb set} \
2855 -state disabled
2856scrollbar .vpane.files.other.sb -command [list $ui_other yview]
2857pack .vpane.files.other.title -side top -fill x
2858pack .vpane.files.other.sb -side right -fill y
2859pack $ui_other -side left -fill both -expand 1
2860.vpane.files add .vpane.files.other -sticky nsew
2861
2862foreach i [list $ui_index $ui_other] {
2863 $i tag conf in_diff -font font_uibold
2864 $i tag conf in_sel \
2865 -background [$i cget -foreground] \
2866 -foreground [$i cget -background]
2867}
2868unset i
2869
2870# -- Diff and Commit Area
2871#
2872frame .vpane.lower -height 300 -width 400
2873frame .vpane.lower.commarea
2874frame .vpane.lower.diff -relief sunken -borderwidth 1
2875pack .vpane.lower.commarea -side top -fill x
2876pack .vpane.lower.diff -side bottom -fill both -expand 1
2877.vpane add .vpane.lower -stick nsew
2878
2879# -- Commit Area Buttons
2880#
2881frame .vpane.lower.commarea.buttons
2882label .vpane.lower.commarea.buttons.l -text {} \
2883 -anchor w \
2884 -justify left \
2885 -font font_ui
2886pack .vpane.lower.commarea.buttons.l -side top -fill x
2887pack .vpane.lower.commarea.buttons -side left -fill y
2888
2889button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
2890 -command do_rescan \
2891 -font font_ui
2892pack .vpane.lower.commarea.buttons.rescan -side top -fill x
2893lappend disable_on_lock \
2894 {.vpane.lower.commarea.buttons.rescan conf -state}
2895
2896button .vpane.lower.commarea.buttons.incall -text {Include All} \
2897 -command do_include_all \
2898 -font font_ui
2899pack .vpane.lower.commarea.buttons.incall -side top -fill x
2900lappend disable_on_lock \
2901 {.vpane.lower.commarea.buttons.incall conf -state}
2902
2903button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
2904 -command do_signoff \
2905 -font font_ui
2906pack .vpane.lower.commarea.buttons.signoff -side top -fill x
2907
2908button .vpane.lower.commarea.buttons.commit -text {Commit} \
2909 -command do_commit \
2910 -font font_ui
2911pack .vpane.lower.commarea.buttons.commit -side top -fill x
2912lappend disable_on_lock \
2913 {.vpane.lower.commarea.buttons.commit conf -state}
2914
2915# -- Commit Message Buffer
2916#
2917frame .vpane.lower.commarea.buffer
2918frame .vpane.lower.commarea.buffer.header
2919set ui_comm .vpane.lower.commarea.buffer.t
2920set ui_coml .vpane.lower.commarea.buffer.header.l
2921radiobutton .vpane.lower.commarea.buffer.header.new \
2922 -text {New Commit} \
2923 -command do_select_commit_type \
2924 -variable selected_commit_type \
2925 -value new \
2926 -font font_ui
2927lappend disable_on_lock \
2928 [list .vpane.lower.commarea.buffer.header.new conf -state]
2929radiobutton .vpane.lower.commarea.buffer.header.amend \
2930 -text {Amend Last Commit} \
2931 -command do_select_commit_type \
2932 -variable selected_commit_type \
2933 -value amend \
2934 -font font_ui
2935lappend disable_on_lock \
2936 [list .vpane.lower.commarea.buffer.header.amend conf -state]
2937label $ui_coml \
2938 -anchor w \
2939 -justify left \
2940 -font font_ui
2941proc trace_commit_type {varname args} {
2942 global ui_coml commit_type
2943 switch -glob -- $commit_type {
2944 initial {set txt {Initial Commit Message:}}
2945 amend {set txt {Amended Commit Message:}}
2946 amend-initial {set txt {Amended Initial Commit Message:}}
2947 amend-merge {set txt {Amended Merge Commit Message:}}
2948 merge {set txt {Merge Commit Message:}}
2949 * {set txt {Commit Message:}}
2950 }
2951 $ui_coml conf -text $txt
2952}
2953trace add variable commit_type write trace_commit_type
2954pack $ui_coml -side left -fill x
2955pack .vpane.lower.commarea.buffer.header.amend -side right
2956pack .vpane.lower.commarea.buffer.header.new -side right
2957
2958text $ui_comm -background white -borderwidth 1 \
2959 -undo true \
2960 -maxundo 20 \
2961 -autoseparators true \
2962 -relief sunken \
2963 -width 75 -height 9 -wrap none \
2964 -font font_diff \
2965 -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
2966scrollbar .vpane.lower.commarea.buffer.sby \
2967 -command [list $ui_comm yview]
2968pack .vpane.lower.commarea.buffer.header -side top -fill x
2969pack .vpane.lower.commarea.buffer.sby -side right -fill y
2970pack $ui_comm -side left -fill y
2971pack .vpane.lower.commarea.buffer -side left -fill y
2972
2973# -- Commit Message Buffer Context Menu
2974#
2975set ctxm .vpane.lower.commarea.buffer.ctxm
2976menu $ctxm -tearoff 0
2977$ctxm add command \
2978 -label {Cut} \
2979 -font font_ui \
2980 -command {tk_textCut $ui_comm}
2981$ctxm add command \
2982 -label {Copy} \
2983 -font font_ui \
2984 -command {tk_textCopy $ui_comm}
2985$ctxm add command \
2986 -label {Paste} \
2987 -font font_ui \
2988 -command {tk_textPaste $ui_comm}
2989$ctxm add command \
2990 -label {Delete} \
2991 -font font_ui \
2992 -command {$ui_comm delete sel.first sel.last}
2993$ctxm add separator
2994$ctxm add command \
2995 -label {Select All} \
2996 -font font_ui \
2997 -command {$ui_comm tag add sel 0.0 end}
2998$ctxm add command \
2999 -label {Copy All} \
3000 -font font_ui \
3001 -command {
3002 $ui_comm tag add sel 0.0 end
3003 tk_textCopy $ui_comm
3004 $ui_comm tag remove sel 0.0 end
3005 }
3006$ctxm add separator
3007$ctxm add command \
3008 -label {Sign Off} \
3009 -font font_ui \
3010 -command do_signoff
3011bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
3012
3013# -- Diff Header
3014#
3015set current_diff {}
3016set diff_actions [list]
3017proc trace_current_diff {varname args} {
3018 global current_diff diff_actions file_states
3019 if {$current_diff eq {}} {
3020 set s {}
3021 set f {}
3022 set p {}
3023 set o disabled
3024 } else {
3025 set p $current_diff
3026 set s [mapdesc [lindex $file_states($p) 0] $p]
3027 set f {File:}
3028 set p [escape_path $p]
3029 set o normal
3030 }
3031
3032 .vpane.lower.diff.header.status configure -text $s
3033 .vpane.lower.diff.header.file configure -text $f
3034 .vpane.lower.diff.header.path configure -text $p
3035 foreach w $diff_actions {
3036 uplevel #0 $w $o
3037 }
3038}
3039trace add variable current_diff write trace_current_diff
3040
3041frame .vpane.lower.diff.header -background orange
3042label .vpane.lower.diff.header.status \
3043 -background orange \
3044 -width $max_status_desc \
3045 -anchor w \
3046 -justify left \
3047 -font font_ui
3048label .vpane.lower.diff.header.file \
3049 -background orange \
3050 -anchor w \
3051 -justify left \
3052 -font font_ui
3053label .vpane.lower.diff.header.path \
3054 -background orange \
3055 -anchor w \
3056 -justify left \
3057 -font font_ui
3058pack .vpane.lower.diff.header.status -side left
3059pack .vpane.lower.diff.header.file -side left
3060pack .vpane.lower.diff.header.path -fill x
3061set ctxm .vpane.lower.diff.header.ctxm
3062menu $ctxm -tearoff 0
3063$ctxm add command \
3064 -label {Copy} \
3065 -font font_ui \
3066 -command {
3067 clipboard clear
3068 clipboard append \
3069 -format STRING \
3070 -type STRING \
3071 -- $current_diff
3072 }
3073lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3074bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
3075
3076# -- Diff Body
3077#
3078frame .vpane.lower.diff.body
3079set ui_diff .vpane.lower.diff.body.t
3080text $ui_diff -background white -borderwidth 0 \
3081 -width 80 -height 15 -wrap none \
3082 -font font_diff \
3083 -xscrollcommand {.vpane.lower.diff.body.sbx set} \
3084 -yscrollcommand {.vpane.lower.diff.body.sby set} \
3085 -state disabled
3086scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
3087 -command [list $ui_diff xview]
3088scrollbar .vpane.lower.diff.body.sby -orient vertical \
3089 -command [list $ui_diff yview]
3090pack .vpane.lower.diff.body.sbx -side bottom -fill x
3091pack .vpane.lower.diff.body.sby -side right -fill y
3092pack $ui_diff -side left -fill both -expand 1
3093pack .vpane.lower.diff.header -side top -fill x
3094pack .vpane.lower.diff.body -side bottom -fill both -expand 1
3095
3096$ui_diff tag conf d_@ -font font_diffbold
3097$ui_diff tag conf d_+ -foreground blue
3098$ui_diff tag conf d_- -foreground red
3099$ui_diff tag conf d_++ -foreground {#00a000}
3100$ui_diff tag conf d_-- -foreground {#a000a0}
3101$ui_diff tag conf d_+- \
3102 -foreground red \
3103 -background {light goldenrod yellow}
3104$ui_diff tag conf d_-+ \
3105 -foreground blue \
3106 -background azure2
3107
3108# -- Diff Body Context Menu
3109#
3110set ctxm .vpane.lower.diff.body.ctxm
3111menu $ctxm -tearoff 0
3112$ctxm add command \
3113 -label {Copy} \
3114 -font font_ui \
3115 -command {tk_textCopy $ui_diff}
3116lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3117$ctxm add command \
3118 -label {Select All} \
3119 -font font_ui \
3120 -command {$ui_diff tag add sel 0.0 end}
3121lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3122$ctxm add command \
3123 -label {Copy All} \
3124 -font font_ui \
3125 -command {
3126 $ui_diff tag add sel 0.0 end
3127 tk_textCopy $ui_diff
3128 $ui_diff tag remove sel 0.0 end
3129 }
3130lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3131$ctxm add separator
3132$ctxm add command \
3133 -label {Decrease Font Size} \
3134 -font font_ui \
3135 -command {incr_font_size font_diff -1}
3136lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3137$ctxm add command \
3138 -label {Increase Font Size} \
3139 -font font_ui \
3140 -command {incr_font_size font_diff 1}
3141lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3142$ctxm add separator
3143$ctxm add command \
3144 -label {Show Less Context} \
3145 -font font_ui \
3146 -command {if {$repo_config(gui.diffcontext) >= 2} {
3147 incr repo_config(gui.diffcontext) -1
3148 reshow_diff
3149 }}
3150lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3151$ctxm add command \
3152 -label {Show More Context} \
3153 -font font_ui \
3154 -command {
3155 incr repo_config(gui.diffcontext)
3156 reshow_diff
3157 }
3158lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3159$ctxm add separator
3160$ctxm add command -label {Options...} \
3161 -font font_ui \
3162 -command do_options
3163bind_button3 $ui_diff "tk_popup $ctxm %X %Y"
3164
3165# -- Status Bar
3166#
3167set ui_status_value {Initializing...}
3168label .status -textvariable ui_status_value \
3169 -anchor w \
3170 -justify left \
3171 -borderwidth 1 \
3172 -relief sunken \
3173 -font font_ui
3174pack .status -anchor w -side bottom -fill x
3175
3176# -- Load geometry
3177#
3178catch {
3179set gm $repo_config(gui.geometry)
3180wm geometry . [lindex $gm 0]
3181.vpane sash place 0 \
3182 [lindex [.vpane sash coord 0] 0] \
3183 [lindex $gm 1]
3184.vpane.files sash place 0 \
3185 [lindex $gm 2] \
3186 [lindex [.vpane.files sash coord 0] 1]
3187unset gm
3188}
3189
3190# -- Key Bindings
3191#
3192bind $ui_comm <$M1B-Key-Return> {do_commit;break}
3193bind $ui_comm <$M1B-Key-i> {do_include_all;break}
3194bind $ui_comm <$M1B-Key-I> {do_include_all;break}
3195bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
3196bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
3197bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
3198bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
3199bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
3200bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
3201bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3202bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3203
3204bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
3205bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
3206bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
3207bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
3208bind $ui_diff <$M1B-Key-v> {break}
3209bind $ui_diff <$M1B-Key-V> {break}
3210bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3211bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3212bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break}
3213bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break}
3214bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break}
3215bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break}
3216
3217bind . <Destroy> do_quit
3218bind all <Key-F5> do_rescan
3219bind all <$M1B-Key-r> do_rescan
3220bind all <$M1B-Key-R> do_rescan
3221bind . <$M1B-Key-s> do_signoff
3222bind . <$M1B-Key-S> do_signoff
3223bind . <$M1B-Key-i> do_include_all
3224bind . <$M1B-Key-I> do_include_all
3225bind . <$M1B-Key-Return> do_commit
3226bind all <$M1B-Key-q> do_quit
3227bind all <$M1B-Key-Q> do_quit
3228bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
3229bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
3230foreach i [list $ui_index $ui_other] {
3231 bind $i <Button-1> "toggle_or_diff $i %x %y; break"
3232 bind $i <$M1B-Button-1> "add_one_to_selection $i %x %y; break"
3233 bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
3234}
3235unset i
3236
3237set file_lists($ui_index) [list]
3238set file_lists($ui_other) [list]
3239
3240set HEAD {}
3241set PARENT {}
3242set MERGE_HEAD [list]
3243set commit_type {}
3244set empty_tree {}
3245set current_diff {}
3246set selected_commit_type new
3247
3248wm title . "$appname ([file normalize [file dirname $gitdir]])"
3249focus -force $ui_comm
3250if {!$single_commit} {
3251 load_all_remotes
3252 populate_fetch_menu .mbar.fetch
3253 populate_pull_menu .mbar.pull
3254 populate_push_menu .mbar.push
3255}
3256lock_index begin-read
3257after 1 do_rescan