1#!/bin/sh
2# Tcl ignores the next line -*- tcl -*- \
3exec wish "$0" -- "$@"
4
5set appvers {@@GIT_VERSION@@}
6set copyright {
7Copyright © 2006, 2007 Shawn Pearce, Paul Mackerras.
8
9This program is free software; you can redistribute it and/or modify
10it under the terms of the GNU General Public License as published by
11the Free Software Foundation; either version 2 of the License, or
12(at your option) any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17GNU General Public License for more details.
18
19You should have received a copy of the GNU General Public License
20along with this program; if not, write to the Free Software
21Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA}
22
23######################################################################
24##
25## read only globals
26
27set _appname [lindex [file split $argv0] end]
28set _gitdir {}
29set _reponame {}
30
31proc appname {} {
32 global _appname
33 return $_appname
34}
35
36proc gitdir {args} {
37 global _gitdir
38 if {$args eq {}} {
39 return $_gitdir
40 }
41 return [eval [concat [list file join $_gitdir] $args]]
42}
43
44proc reponame {} {
45 global _reponame
46 return $_reponame
47}
48
49######################################################################
50##
51## config
52
53proc is_many_config {name} {
54 switch -glob -- $name {
55 remote.*.fetch -
56 remote.*.push
57 {return 1}
58 *
59 {return 0}
60 }
61}
62
63proc load_config {include_global} {
64 global repo_config global_config default_config
65
66 array unset global_config
67 if {$include_global} {
68 catch {
69 set fd_rc [open "| git repo-config --global --list" r]
70 while {[gets $fd_rc line] >= 0} {
71 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
72 if {[is_many_config $name]} {
73 lappend global_config($name) $value
74 } else {
75 set global_config($name) $value
76 }
77 }
78 }
79 close $fd_rc
80 }
81 }
82
83 array unset repo_config
84 catch {
85 set fd_rc [open "| git repo-config --list" r]
86 while {[gets $fd_rc line] >= 0} {
87 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
88 if {[is_many_config $name]} {
89 lappend repo_config($name) $value
90 } else {
91 set repo_config($name) $value
92 }
93 }
94 }
95 close $fd_rc
96 }
97
98 foreach name [array names default_config] {
99 if {[catch {set v $global_config($name)}]} {
100 set global_config($name) $default_config($name)
101 }
102 if {[catch {set v $repo_config($name)}]} {
103 set repo_config($name) $default_config($name)
104 }
105 }
106}
107
108proc save_config {} {
109 global default_config font_descs
110 global repo_config global_config
111 global repo_config_new global_config_new
112
113 foreach option $font_descs {
114 set name [lindex $option 0]
115 set font [lindex $option 1]
116 font configure $font \
117 -family $global_config_new(gui.$font^^family) \
118 -size $global_config_new(gui.$font^^size)
119 font configure ${font}bold \
120 -family $global_config_new(gui.$font^^family) \
121 -size $global_config_new(gui.$font^^size)
122 set global_config_new(gui.$name) [font configure $font]
123 unset global_config_new(gui.$font^^family)
124 unset global_config_new(gui.$font^^size)
125 }
126
127 foreach name [array names default_config] {
128 set value $global_config_new($name)
129 if {$value ne $global_config($name)} {
130 if {$value eq $default_config($name)} {
131 catch {exec git repo-config --global --unset $name}
132 } else {
133 regsub -all "\[{}\]" $value {"} value
134 exec git repo-config --global $name $value
135 }
136 set global_config($name) $value
137 if {$value eq $repo_config($name)} {
138 catch {exec git repo-config --unset $name}
139 set repo_config($name) $value
140 }
141 }
142 }
143
144 foreach name [array names default_config] {
145 set value $repo_config_new($name)
146 if {$value ne $repo_config($name)} {
147 if {$value eq $global_config($name)} {
148 catch {exec git repo-config --unset $name}
149 } else {
150 regsub -all "\[{}\]" $value {"} value
151 exec git repo-config $name $value
152 }
153 set repo_config($name) $value
154 }
155 }
156}
157
158proc error_popup {msg} {
159 set title [appname]
160 if {[reponame] ne {}} {
161 append title " ([reponame])"
162 }
163 set cmd [list tk_messageBox \
164 -icon error \
165 -type ok \
166 -title "$title: error" \
167 -message $msg]
168 if {[winfo ismapped .]} {
169 lappend cmd -parent .
170 }
171 eval $cmd
172}
173
174proc warn_popup {msg} {
175 set title [appname]
176 if {[reponame] ne {}} {
177 append title " ([reponame])"
178 }
179 set cmd [list tk_messageBox \
180 -icon warning \
181 -type ok \
182 -title "$title: warning" \
183 -message $msg]
184 if {[winfo ismapped .]} {
185 lappend cmd -parent .
186 }
187 eval $cmd
188}
189
190proc info_popup {msg} {
191 set title [appname]
192 if {[reponame] ne {}} {
193 append title " ([reponame])"
194 }
195 tk_messageBox \
196 -parent . \
197 -icon info \
198 -type ok \
199 -title $title \
200 -message $msg
201}
202
203proc ask_popup {msg} {
204 set title [appname]
205 if {[reponame] ne {}} {
206 append title " ([reponame])"
207 }
208 return [tk_messageBox \
209 -parent . \
210 -icon question \
211 -type yesno \
212 -title $title \
213 -message $msg]
214}
215
216######################################################################
217##
218## repository setup
219
220if { [catch {set _gitdir $env(GIT_DIR)}]
221 && [catch {set _gitdir [exec git rev-parse --git-dir]} err]} {
222 catch {wm withdraw .}
223 error_popup "Cannot find the git directory:\n\n$err"
224 exit 1
225}
226if {![file isdirectory $_gitdir]} {
227 catch {wm withdraw .}
228 error_popup "Git directory not found:\n\n$_gitdir"
229 exit 1
230}
231if {[lindex [file split $_gitdir] end] ne {.git}} {
232 catch {wm withdraw .}
233 error_popup "Cannot use funny .git directory:\n\n$gitdir"
234 exit 1
235}
236if {[catch {cd [file dirname $_gitdir]} err]} {
237 catch {wm withdraw .}
238 error_popup "No working directory [file dirname $_gitdir]:\n\n$err"
239 exit 1
240}
241set _reponame [lindex [file split \
242 [file normalize [file dirname $_gitdir]]] \
243 end]
244
245set single_commit 0
246if {[appname] eq {git-citool}} {
247 set single_commit 1
248}
249
250######################################################################
251##
252## task management
253
254set rescan_active 0
255set diff_active 0
256set last_clicked {}
257
258set disable_on_lock [list]
259set index_lock_type none
260
261proc lock_index {type} {
262 global index_lock_type disable_on_lock
263
264 if {$index_lock_type eq {none}} {
265 set index_lock_type $type
266 foreach w $disable_on_lock {
267 uplevel #0 $w disabled
268 }
269 return 1
270 } elseif {$index_lock_type eq "begin-$type"} {
271 set index_lock_type $type
272 return 1
273 }
274 return 0
275}
276
277proc unlock_index {} {
278 global index_lock_type disable_on_lock
279
280 set index_lock_type none
281 foreach w $disable_on_lock {
282 uplevel #0 $w normal
283 }
284}
285
286######################################################################
287##
288## status
289
290proc repository_state {ctvar hdvar mhvar} {
291 global current_branch
292 upvar $ctvar ct $hdvar hd $mhvar mh
293
294 set mh [list]
295
296 if {[catch {set current_branch [exec git symbolic-ref HEAD]}]} {
297 set current_branch {}
298 } else {
299 regsub ^refs/((heads|tags|remotes)/)? \
300 $current_branch \
301 {} \
302 current_branch
303 }
304
305 if {[catch {set hd [exec git rev-parse --verify HEAD]}]} {
306 set hd {}
307 set ct initial
308 return
309 }
310
311 set merge_head [gitdir MERGE_HEAD]
312 if {[file exists $merge_head]} {
313 set ct merge
314 set fd_mh [open $merge_head r]
315 while {[gets $fd_mh line] >= 0} {
316 lappend mh $line
317 }
318 close $fd_mh
319 return
320 }
321
322 set ct normal
323}
324
325proc PARENT {} {
326 global PARENT empty_tree
327
328 set p [lindex $PARENT 0]
329 if {$p ne {}} {
330 return $p
331 }
332 if {$empty_tree eq {}} {
333 set empty_tree [exec git mktree << {}]
334 }
335 return $empty_tree
336}
337
338proc rescan {after {honor_trustmtime 1}} {
339 global HEAD PARENT MERGE_HEAD commit_type
340 global ui_index ui_workdir ui_status_value ui_comm
341 global rescan_active file_states
342 global repo_config
343
344 if {$rescan_active > 0 || ![lock_index read]} return
345
346 repository_state newType newHEAD newMERGE_HEAD
347 if {[string match amend* $commit_type]
348 && $newType eq {normal}
349 && $newHEAD eq $HEAD} {
350 } else {
351 set HEAD $newHEAD
352 set PARENT $newHEAD
353 set MERGE_HEAD $newMERGE_HEAD
354 set commit_type $newType
355 }
356
357 array unset file_states
358
359 if {![$ui_comm edit modified]
360 || [string trim [$ui_comm get 0.0 end]] eq {}} {
361 if {[load_message GITGUI_MSG]} {
362 } elseif {[load_message MERGE_MSG]} {
363 } elseif {[load_message SQUASH_MSG]} {
364 }
365 $ui_comm edit reset
366 $ui_comm edit modified false
367 }
368
369 if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
370 rescan_stage2 {} $after
371 } else {
372 set rescan_active 1
373 set ui_status_value {Refreshing file status...}
374 set cmd [list git update-index]
375 lappend cmd -q
376 lappend cmd --unmerged
377 lappend cmd --ignore-missing
378 lappend cmd --refresh
379 set fd_rf [open "| $cmd" r]
380 fconfigure $fd_rf -blocking 0 -translation binary
381 fileevent $fd_rf readable \
382 [list rescan_stage2 $fd_rf $after]
383 }
384}
385
386proc rescan_stage2 {fd after} {
387 global ui_status_value
388 global rescan_active buf_rdi buf_rdf buf_rlo
389
390 if {$fd ne {}} {
391 read $fd
392 if {![eof $fd]} return
393 close $fd
394 }
395
396 set ls_others [list | git ls-files --others -z \
397 --exclude-per-directory=.gitignore]
398 set info_exclude [gitdir info exclude]
399 if {[file readable $info_exclude]} {
400 lappend ls_others "--exclude-from=$info_exclude"
401 }
402
403 set buf_rdi {}
404 set buf_rdf {}
405 set buf_rlo {}
406
407 set rescan_active 3
408 set ui_status_value {Scanning for modified files ...}
409 set fd_di [open "| git diff-index --cached -z [PARENT]" r]
410 set fd_df [open "| git diff-files -z" r]
411 set fd_lo [open $ls_others r]
412
413 fconfigure $fd_di -blocking 0 -translation binary
414 fconfigure $fd_df -blocking 0 -translation binary
415 fconfigure $fd_lo -blocking 0 -translation binary
416 fileevent $fd_di readable [list read_diff_index $fd_di $after]
417 fileevent $fd_df readable [list read_diff_files $fd_df $after]
418 fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
419}
420
421proc load_message {file} {
422 global ui_comm
423
424 set f [gitdir $file]
425 if {[file isfile $f]} {
426 if {[catch {set fd [open $f r]}]} {
427 return 0
428 }
429 set content [string trim [read $fd]]
430 close $fd
431 $ui_comm delete 0.0 end
432 $ui_comm insert end $content
433 return 1
434 }
435 return 0
436}
437
438proc read_diff_index {fd after} {
439 global buf_rdi
440
441 append buf_rdi [read $fd]
442 set c 0
443 set n [string length $buf_rdi]
444 while {$c < $n} {
445 set z1 [string first "\0" $buf_rdi $c]
446 if {$z1 == -1} break
447 incr z1
448 set z2 [string first "\0" $buf_rdi $z1]
449 if {$z2 == -1} break
450
451 incr c
452 set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
453 merge_state \
454 [string range $buf_rdi $z1 [expr {$z2 - 1}]] \
455 [lindex $i 4]? \
456 [list [lindex $i 0] [lindex $i 2]] \
457 [list]
458 set c $z2
459 incr c
460 }
461 if {$c < $n} {
462 set buf_rdi [string range $buf_rdi $c end]
463 } else {
464 set buf_rdi {}
465 }
466
467 rescan_done $fd buf_rdi $after
468}
469
470proc read_diff_files {fd after} {
471 global buf_rdf
472
473 append buf_rdf [read $fd]
474 set c 0
475 set n [string length $buf_rdf]
476 while {$c < $n} {
477 set z1 [string first "\0" $buf_rdf $c]
478 if {$z1 == -1} break
479 incr z1
480 set z2 [string first "\0" $buf_rdf $z1]
481 if {$z2 == -1} break
482
483 incr c
484 set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
485 merge_state \
486 [string range $buf_rdf $z1 [expr {$z2 - 1}]] \
487 ?[lindex $i 4] \
488 [list] \
489 [list [lindex $i 0] [lindex $i 2]]
490 set c $z2
491 incr c
492 }
493 if {$c < $n} {
494 set buf_rdf [string range $buf_rdf $c end]
495 } else {
496 set buf_rdf {}
497 }
498
499 rescan_done $fd buf_rdf $after
500}
501
502proc read_ls_others {fd after} {
503 global buf_rlo
504
505 append buf_rlo [read $fd]
506 set pck [split $buf_rlo "\0"]
507 set buf_rlo [lindex $pck end]
508 foreach p [lrange $pck 0 end-1] {
509 merge_state $p ?O
510 }
511 rescan_done $fd buf_rlo $after
512}
513
514proc rescan_done {fd buf after} {
515 global rescan_active
516 global file_states repo_config
517 upvar $buf to_clear
518
519 if {![eof $fd]} return
520 set to_clear {}
521 close $fd
522 if {[incr rescan_active -1] > 0} return
523
524 prune_selection
525 unlock_index
526 display_all_files
527 reshow_diff
528 uplevel #0 $after
529}
530
531proc prune_selection {} {
532 global file_states selected_paths
533
534 foreach path [array names selected_paths] {
535 if {[catch {set still_here $file_states($path)}]} {
536 unset selected_paths($path)
537 }
538 }
539}
540
541######################################################################
542##
543## diff
544
545proc clear_diff {} {
546 global ui_diff current_diff_path ui_index ui_workdir
547
548 $ui_diff conf -state normal
549 $ui_diff delete 0.0 end
550 $ui_diff conf -state disabled
551
552 set current_diff_path {}
553
554 $ui_index tag remove in_diff 0.0 end
555 $ui_workdir tag remove in_diff 0.0 end
556}
557
558proc reshow_diff {} {
559 global ui_status_value file_states file_lists
560 global current_diff_path current_diff_side
561
562 set p $current_diff_path
563 if {$p eq {}
564 || $current_diff_side eq {}
565 || [catch {set s $file_states($p)}]
566 || [lsearch -sorted $file_lists($current_diff_side) $p] == -1} {
567 clear_diff
568 } else {
569 show_diff $p $current_diff_side
570 }
571}
572
573proc handle_empty_diff {} {
574 global current_diff_path file_states file_lists
575
576 set path $current_diff_path
577 set s $file_states($path)
578 if {[lindex $s 0] ne {_M}} return
579
580 info_popup "No differences detected.
581
582[short_path $path] has no changes.
583
584The modification date of this file was updated
585by another application and you currently have
586the Trust File Modification Timestamps option
587enabled, so Git did not automatically detect
588that there are no content differences in this
589file."
590
591 clear_diff
592 display_file $path __
593 rescan {set ui_status_value {Ready.}} 0
594}
595
596proc show_diff {path w {lno {}}} {
597 global file_states file_lists
598 global is_3way_diff diff_active repo_config
599 global ui_diff ui_status_value ui_index ui_workdir
600 global current_diff_path current_diff_side
601
602 if {$diff_active || ![lock_index read]} return
603
604 clear_diff
605 if {$w eq {} || $lno == {}} {
606 foreach w [array names file_lists] {
607 set lno [lsearch -sorted $file_lists($w) $path]
608 if {$lno >= 0} {
609 incr lno
610 break
611 }
612 }
613 }
614 if {$w ne {} && $lno >= 1} {
615 $w tag add in_diff $lno.0 [expr {$lno + 1}].0
616 }
617
618 set s $file_states($path)
619 set m [lindex $s 0]
620 set is_3way_diff 0
621 set diff_active 1
622 set current_diff_path $path
623 set current_diff_side $w
624 set ui_status_value "Loading diff of [escape_path $path]..."
625
626 # - Git won't give us the diff, there's nothing to compare to!
627 #
628 if {$m eq {_O}} {
629 if {[catch {
630 set fd [open $path r]
631 set content [read $fd]
632 close $fd
633 } err ]} {
634 set diff_active 0
635 unlock_index
636 set ui_status_value "Unable to display [escape_path $path]"
637 error_popup "Error loading file:\n\n$err"
638 return
639 }
640 $ui_diff conf -state normal
641 $ui_diff insert end $content
642 $ui_diff conf -state disabled
643 set diff_active 0
644 unlock_index
645 set ui_status_value {Ready.}
646 return
647 }
648
649 set cmd [list | git]
650 if {$w eq $ui_index} {
651 lappend cmd diff-index
652 lappend cmd --cached
653 } elseif {$w eq $ui_workdir} {
654 if {[string index $m 0] eq {U}} {
655 lappend cmd diff
656 } else {
657 lappend cmd diff-files
658 }
659 }
660
661 lappend cmd -p
662 lappend cmd --no-color
663 if {$repo_config(gui.diffcontext) > 0} {
664 lappend cmd "-U$repo_config(gui.diffcontext)"
665 }
666 if {$w eq $ui_index} {
667 lappend cmd [PARENT]
668 }
669 lappend cmd --
670 lappend cmd $path
671
672 if {[catch {set fd [open $cmd r]} err]} {
673 set diff_active 0
674 unlock_index
675 set ui_status_value "Unable to display [escape_path $path]"
676 error_popup "Error loading diff:\n\n$err"
677 return
678 }
679
680 fconfigure $fd -blocking 0 -translation auto
681 fileevent $fd readable [list read_diff $fd]
682}
683
684proc read_diff {fd} {
685 global ui_diff ui_status_value is_3way_diff diff_active
686 global repo_config
687
688 $ui_diff conf -state normal
689 while {[gets $fd line] >= 0} {
690 # -- Cleanup uninteresting diff header lines.
691 #
692 if {[string match {diff --git *} $line]} continue
693 if {[string match {diff --cc *} $line]} continue
694 if {[string match {diff --combined *} $line]} continue
695 if {[string match {--- *} $line]} continue
696 if {[string match {+++ *} $line]} continue
697 if {$line eq {deleted file mode 120000}} {
698 set line "deleted symlink"
699 }
700
701 # -- Automatically detect if this is a 3 way diff.
702 #
703 if {[string match {@@@ *} $line]} {set is_3way_diff 1}
704
705 if {[string match {index *} $line]
706 || [string match {mode *} $line]
707 || [string match {new file *} $line]
708 || [string match {deleted file *} $line]
709 || [regexp {^\* Unmerged path } $line]} {
710 set tags {}
711 } elseif {$is_3way_diff} {
712 set op [string range $line 0 1]
713 switch -- $op {
714 { } {set tags {}}
715 {@@} {set tags d_@}
716 { +} {set tags d_s+}
717 { -} {set tags d_s-}
718 {+ } {set tags d_+s}
719 {- } {set tags d_-s}
720 {--} {set tags d_--}
721 {++} {
722 if {[regexp {^\+\+([<>]{7} |={7})} $line _g op]} {
723 set line [string replace $line 0 1 { }]
724 set tags d$op
725 } else {
726 set tags d_++
727 }
728 }
729 default {
730 puts "error: Unhandled 3 way diff marker: {$op}"
731 set tags {}
732 }
733 }
734 } else {
735 set op [string index $line 0]
736 switch -- $op {
737 { } {set tags {}}
738 {@} {set tags d_@}
739 {-} {set tags d_-}
740 {+} {
741 if {[regexp {^\+([<>]{7} |={7})} $line _g op]} {
742 set line [string replace $line 0 0 { }]
743 set tags d$op
744 } else {
745 set tags d_+
746 }
747 }
748 default {
749 puts "error: Unhandled 2 way diff marker: {$op}"
750 set tags {}
751 }
752 }
753 }
754 $ui_diff insert end $line $tags
755 $ui_diff insert end "\n" $tags
756 }
757 $ui_diff conf -state disabled
758
759 if {[eof $fd]} {
760 close $fd
761 set diff_active 0
762 unlock_index
763 set ui_status_value {Ready.}
764
765 if {$repo_config(gui.trustmtime) eq {true}
766 && [$ui_diff index end] eq {2.0}} {
767 handle_empty_diff
768 }
769 }
770}
771
772######################################################################
773##
774## commit
775
776proc load_last_commit {} {
777 global HEAD PARENT MERGE_HEAD commit_type ui_comm
778
779 if {[llength $PARENT] == 0} {
780 error_popup {There is nothing to amend.
781
782You are about to create the initial commit.
783There is no commit before this to amend.
784}
785 return
786 }
787
788 repository_state curType curHEAD curMERGE_HEAD
789 if {$curType eq {merge}} {
790 error_popup {Cannot amend while merging.
791
792You are currently in the middle of a merge that
793has not been fully completed. You cannot amend
794the prior commit unless you first abort the
795current merge activity.
796}
797 return
798 }
799
800 set msg {}
801 set parents [list]
802 if {[catch {
803 set fd [open "| git cat-file commit $curHEAD" r]
804 while {[gets $fd line] > 0} {
805 if {[string match {parent *} $line]} {
806 lappend parents [string range $line 7 end]
807 }
808 }
809 set msg [string trim [read $fd]]
810 close $fd
811 } err]} {
812 error_popup "Error loading commit data for amend:\n\n$err"
813 return
814 }
815
816 set HEAD $curHEAD
817 set PARENT $parents
818 set MERGE_HEAD [list]
819 switch -- [llength $parents] {
820 0 {set commit_type amend-initial}
821 1 {set commit_type amend}
822 default {set commit_type amend-merge}
823 }
824
825 $ui_comm delete 0.0 end
826 $ui_comm insert end $msg
827 $ui_comm edit reset
828 $ui_comm edit modified false
829 rescan {set ui_status_value {Ready.}}
830}
831
832proc create_new_commit {} {
833 global commit_type ui_comm
834
835 set commit_type normal
836 $ui_comm delete 0.0 end
837 $ui_comm edit reset
838 $ui_comm edit modified false
839 rescan {set ui_status_value {Ready.}}
840}
841
842set GIT_COMMITTER_IDENT {}
843
844proc committer_ident {} {
845 global GIT_COMMITTER_IDENT
846
847 if {$GIT_COMMITTER_IDENT eq {}} {
848 if {[catch {set me [exec git var GIT_COMMITTER_IDENT]} err]} {
849 error_popup "Unable to obtain your identity:\n\n$err"
850 return {}
851 }
852 if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
853 $me me GIT_COMMITTER_IDENT]} {
854 error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me"
855 return {}
856 }
857 }
858
859 return $GIT_COMMITTER_IDENT
860}
861
862proc commit_tree {} {
863 global HEAD commit_type file_states ui_comm repo_config
864 global ui_status_value pch_error
865
866 if {![lock_index update]} return
867 if {[committer_ident] eq {}} return
868
869 # -- Our in memory state should match the repository.
870 #
871 repository_state curType curHEAD curMERGE_HEAD
872 if {[string match amend* $commit_type]
873 && $curType eq {normal}
874 && $curHEAD eq $HEAD} {
875 } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
876 info_popup {Last scanned state does not match repository state.
877
878Another Git program has modified this repository
879since the last scan. A rescan must be performed
880before another commit can be created.
881
882The rescan will be automatically started now.
883}
884 unlock_index
885 rescan {set ui_status_value {Ready.}}
886 return
887 }
888
889 # -- At least one file should differ in the index.
890 #
891 set files_ready 0
892 foreach path [array names file_states] {
893 switch -glob -- [lindex $file_states($path) 0] {
894 _? {continue}
895 A? -
896 D? -
897 M? {set files_ready 1}
898 U? {
899 error_popup "Unmerged files cannot be committed.
900
901File [short_path $path] has merge conflicts.
902You must resolve them and add the file before committing.
903"
904 unlock_index
905 return
906 }
907 default {
908 error_popup "Unknown file state [lindex $s 0] detected.
909
910File [short_path $path] cannot be committed by this program.
911"
912 }
913 }
914 }
915 if {!$files_ready} {
916 info_popup {No changes to commit.
917
918You must add at least 1 file before you can commit.
919}
920 unlock_index
921 return
922 }
923
924 # -- A message is required.
925 #
926 set msg [string trim [$ui_comm get 1.0 end]]
927 if {$msg eq {}} {
928 error_popup {Please supply a commit message.
929
930A good commit message has the following format:
931
932- First line: Describe in one sentance what you did.
933- Second line: Blank
934- Remaining lines: Describe why this change is good.
935}
936 unlock_index
937 return
938 }
939
940 # -- Run the pre-commit hook.
941 #
942 set pchook [gitdir hooks pre-commit]
943
944 # On Cygwin [file executable] might lie so we need to ask
945 # the shell if the hook is executable. Yes that's annoying.
946 #
947 if {[is_Windows] && [file isfile $pchook]} {
948 set pchook [list sh -c [concat \
949 "if test -x \"$pchook\";" \
950 "then exec \"$pchook\" 2>&1;" \
951 "fi"]]
952 } elseif {[file executable $pchook]} {
953 set pchook [list $pchook |& cat]
954 } else {
955 commit_writetree $curHEAD $msg
956 return
957 }
958
959 set ui_status_value {Calling pre-commit hook...}
960 set pch_error {}
961 set fd_ph [open "| $pchook" r]
962 fconfigure $fd_ph -blocking 0 -translation binary
963 fileevent $fd_ph readable \
964 [list commit_prehook_wait $fd_ph $curHEAD $msg]
965}
966
967proc commit_prehook_wait {fd_ph curHEAD msg} {
968 global pch_error ui_status_value
969
970 append pch_error [read $fd_ph]
971 fconfigure $fd_ph -blocking 1
972 if {[eof $fd_ph]} {
973 if {[catch {close $fd_ph}]} {
974 set ui_status_value {Commit declined by pre-commit hook.}
975 hook_failed_popup pre-commit $pch_error
976 unlock_index
977 } else {
978 commit_writetree $curHEAD $msg
979 }
980 set pch_error {}
981 return
982 }
983 fconfigure $fd_ph -blocking 0
984}
985
986proc commit_writetree {curHEAD msg} {
987 global ui_status_value
988
989 set ui_status_value {Committing changes...}
990 set fd_wt [open "| git write-tree" r]
991 fileevent $fd_wt readable \
992 [list commit_committree $fd_wt $curHEAD $msg]
993}
994
995proc commit_committree {fd_wt curHEAD msg} {
996 global HEAD PARENT MERGE_HEAD commit_type
997 global single_commit all_heads current_branch
998 global ui_status_value ui_comm selected_commit_type
999 global file_states selected_paths rescan_active
1000
1001 gets $fd_wt tree_id
1002 if {$tree_id eq {} || [catch {close $fd_wt} err]} {
1003 error_popup "write-tree failed:\n\n$err"
1004 set ui_status_value {Commit failed.}
1005 unlock_index
1006 return
1007 }
1008
1009 # -- Create the commit.
1010 #
1011 set cmd [list git commit-tree $tree_id]
1012 set parents [concat $PARENT $MERGE_HEAD]
1013 if {[llength $parents] > 0} {
1014 foreach p $parents {
1015 lappend cmd -p $p
1016 }
1017 } else {
1018 # git commit-tree writes to stderr during initial commit.
1019 lappend cmd 2>/dev/null
1020 }
1021 lappend cmd << $msg
1022 if {[catch {set cmt_id [eval exec $cmd]} err]} {
1023 error_popup "commit-tree failed:\n\n$err"
1024 set ui_status_value {Commit failed.}
1025 unlock_index
1026 return
1027 }
1028
1029 # -- Update the HEAD ref.
1030 #
1031 set reflogm commit
1032 if {$commit_type ne {normal}} {
1033 append reflogm " ($commit_type)"
1034 }
1035 set i [string first "\n" $msg]
1036 if {$i >= 0} {
1037 append reflogm {: } [string range $msg 0 [expr {$i - 1}]]
1038 } else {
1039 append reflogm {: } $msg
1040 }
1041 set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD]
1042 if {[catch {eval exec $cmd} err]} {
1043 error_popup "update-ref failed:\n\n$err"
1044 set ui_status_value {Commit failed.}
1045 unlock_index
1046 return
1047 }
1048
1049 # -- Make sure our current branch exists.
1050 #
1051 if {$commit_type eq {initial}} {
1052 lappend all_heads $current_branch
1053 set all_heads [lsort -unique $all_heads]
1054 populate_branch_menu
1055 }
1056
1057 # -- Cleanup after ourselves.
1058 #
1059 catch {file delete [gitdir MERGE_HEAD]}
1060 catch {file delete [gitdir MERGE_MSG]}
1061 catch {file delete [gitdir SQUASH_MSG]}
1062 catch {file delete [gitdir GITGUI_MSG]}
1063
1064 # -- Let rerere do its thing.
1065 #
1066 if {[file isdirectory [gitdir rr-cache]]} {
1067 catch {exec git rerere}
1068 }
1069
1070 # -- Run the post-commit hook.
1071 #
1072 set pchook [gitdir hooks post-commit]
1073 if {[is_Windows] && [file isfile $pchook]} {
1074 set pchook [list sh -c [concat \
1075 "if test -x \"$pchook\";" \
1076 "then exec \"$pchook\";" \
1077 "fi"]]
1078 } elseif {![file executable $pchook]} {
1079 set pchook {}
1080 }
1081 if {$pchook ne {}} {
1082 catch {exec $pchook &}
1083 }
1084
1085 $ui_comm delete 0.0 end
1086 $ui_comm edit reset
1087 $ui_comm edit modified false
1088
1089 if {$single_commit} do_quit
1090
1091 # -- Update in memory status
1092 #
1093 set selected_commit_type new
1094 set commit_type normal
1095 set HEAD $cmt_id
1096 set PARENT $cmt_id
1097 set MERGE_HEAD [list]
1098
1099 foreach path [array names file_states] {
1100 set s $file_states($path)
1101 set m [lindex $s 0]
1102 switch -glob -- $m {
1103 _O -
1104 _M -
1105 _D {continue}
1106 __ -
1107 A_ -
1108 M_ -
1109 D_ {
1110 unset file_states($path)
1111 catch {unset selected_paths($path)}
1112 }
1113 DO {
1114 set file_states($path) [list _O [lindex $s 1] {} {}]
1115 }
1116 AM -
1117 AD -
1118 MM -
1119 MD {
1120 set file_states($path) [list \
1121 _[string index $m 1] \
1122 [lindex $s 1] \
1123 [lindex $s 3] \
1124 {}]
1125 }
1126 }
1127 }
1128
1129 display_all_files
1130 unlock_index
1131 reshow_diff
1132 set ui_status_value \
1133 "Changes committed as [string range $cmt_id 0 7]."
1134}
1135
1136######################################################################
1137##
1138## fetch pull push
1139
1140proc fetch_from {remote} {
1141 set w [new_console "fetch $remote" \
1142 "Fetching new changes from $remote"]
1143 set cmd [list git fetch]
1144 lappend cmd $remote
1145 console_exec $w $cmd
1146}
1147
1148proc pull_remote {remote branch} {
1149 global HEAD commit_type file_states repo_config
1150
1151 if {![lock_index update]} return
1152
1153 # -- Our in memory state should match the repository.
1154 #
1155 repository_state curType curHEAD curMERGE_HEAD
1156 if {$commit_type ne $curType || $HEAD ne $curHEAD} {
1157 info_popup {Last scanned state does not match repository state.
1158
1159Another Git program has modified this repository
1160since the last scan. A rescan must be performed
1161before a pull operation can be started.
1162
1163The rescan will be automatically started now.
1164}
1165 unlock_index
1166 rescan {set ui_status_value {Ready.}}
1167 return
1168 }
1169
1170 # -- No differences should exist before a pull.
1171 #
1172 if {[array size file_states] != 0} {
1173 error_popup {Uncommitted but modified files are present.
1174
1175You should not perform a pull with unmodified
1176files in your working directory as Git will be
1177unable to recover from an incorrect merge.
1178
1179You should commit or revert all changes before
1180starting a pull operation.
1181}
1182 unlock_index
1183 return
1184 }
1185
1186 set w [new_console "pull $remote $branch" \
1187 "Pulling new changes from branch $branch in $remote"]
1188 set cmd [list git pull]
1189 if {$repo_config(gui.pullsummary) eq {false}} {
1190 lappend cmd --no-summary
1191 }
1192 lappend cmd $remote
1193 lappend cmd $branch
1194 console_exec $w $cmd [list post_pull_remote $remote $branch]
1195}
1196
1197proc post_pull_remote {remote branch success} {
1198 global HEAD PARENT MERGE_HEAD commit_type selected_commit_type
1199 global ui_status_value
1200
1201 unlock_index
1202 if {$success} {
1203 repository_state commit_type HEAD MERGE_HEAD
1204 set PARENT $HEAD
1205 set selected_commit_type new
1206 set ui_status_value "Pulling $branch from $remote complete."
1207 } else {
1208 rescan [list set ui_status_value \
1209 "Conflicts detected while pulling $branch from $remote."]
1210 }
1211}
1212
1213proc push_to {remote} {
1214 set w [new_console "push $remote" \
1215 "Pushing changes to $remote"]
1216 set cmd [list git push]
1217 lappend cmd $remote
1218 console_exec $w $cmd
1219}
1220
1221######################################################################
1222##
1223## ui helpers
1224
1225proc mapicon {w state path} {
1226 global all_icons
1227
1228 if {[catch {set r $all_icons($state$w)}]} {
1229 puts "error: no icon for $w state={$state} $path"
1230 return file_plain
1231 }
1232 return $r
1233}
1234
1235proc mapdesc {state path} {
1236 global all_descs
1237
1238 if {[catch {set r $all_descs($state)}]} {
1239 puts "error: no desc for state={$state} $path"
1240 return $state
1241 }
1242 return $r
1243}
1244
1245proc escape_path {path} {
1246 regsub -all "\n" $path "\\n" path
1247 return $path
1248}
1249
1250proc short_path {path} {
1251 return [escape_path [lindex [file split $path] end]]
1252}
1253
1254set next_icon_id 0
1255set null_sha1 [string repeat 0 40]
1256
1257proc merge_state {path new_state {head_info {}} {index_info {}}} {
1258 global file_states next_icon_id null_sha1
1259
1260 set s0 [string index $new_state 0]
1261 set s1 [string index $new_state 1]
1262
1263 if {[catch {set info $file_states($path)}]} {
1264 set state __
1265 set icon n[incr next_icon_id]
1266 } else {
1267 set state [lindex $info 0]
1268 set icon [lindex $info 1]
1269 if {$head_info eq {}} {set head_info [lindex $info 2]}
1270 if {$index_info eq {}} {set index_info [lindex $info 3]}
1271 }
1272
1273 if {$s0 eq {?}} {set s0 [string index $state 0]} \
1274 elseif {$s0 eq {_}} {set s0 _}
1275
1276 if {$s1 eq {?}} {set s1 [string index $state 1]} \
1277 elseif {$s1 eq {_}} {set s1 _}
1278
1279 if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
1280 set head_info [list 0 $null_sha1]
1281 } elseif {$s0 ne {_} && [string index $state 0] eq {_}
1282 && $head_info eq {}} {
1283 set head_info $index_info
1284 }
1285
1286 set file_states($path) [list $s0$s1 $icon \
1287 $head_info $index_info \
1288 ]
1289 return $state
1290}
1291
1292proc display_file_helper {w path icon_name old_m new_m} {
1293 global file_lists
1294
1295 if {$new_m eq {_}} {
1296 set lno [lsearch -sorted $file_lists($w) $path]
1297 if {$lno >= 0} {
1298 set file_lists($w) [lreplace $file_lists($w) $lno $lno]
1299 incr lno
1300 $w conf -state normal
1301 $w delete $lno.0 [expr {$lno + 1}].0
1302 $w conf -state disabled
1303 }
1304 } elseif {$old_m eq {_} && $new_m ne {_}} {
1305 lappend file_lists($w) $path
1306 set file_lists($w) [lsort -unique $file_lists($w)]
1307 set lno [lsearch -sorted $file_lists($w) $path]
1308 incr lno
1309 $w conf -state normal
1310 $w image create $lno.0 \
1311 -align center -padx 5 -pady 1 \
1312 -name $icon_name \
1313 -image [mapicon $w $new_m $path]
1314 $w insert $lno.1 "[escape_path $path]\n"
1315 $w conf -state disabled
1316 } elseif {$old_m ne $new_m} {
1317 $w conf -state normal
1318 $w image conf $icon_name -image [mapicon $w $new_m $path]
1319 $w conf -state disabled
1320 }
1321}
1322
1323proc display_file {path state} {
1324 global file_states selected_paths
1325 global ui_index ui_workdir
1326
1327 set old_m [merge_state $path $state]
1328 set s $file_states($path)
1329 set new_m [lindex $s 0]
1330 set icon_name [lindex $s 1]
1331
1332 set o [string index $old_m 0]
1333 set n [string index $new_m 0]
1334 if {$o eq {U}} {
1335 set o _
1336 }
1337 if {$n eq {U}} {
1338 set n _
1339 }
1340 display_file_helper $ui_index $path $icon_name $o $n
1341
1342 if {[string index $old_m 0] eq {U}} {
1343 set o U
1344 } else {
1345 set o [string index $old_m 1]
1346 }
1347 if {[string index $new_m 0] eq {U}} {
1348 set n U
1349 } else {
1350 set n [string index $new_m 1]
1351 }
1352 display_file_helper $ui_workdir $path $icon_name $o $n
1353
1354 if {$new_m eq {__}} {
1355 unset file_states($path)
1356 catch {unset selected_paths($path)}
1357 }
1358}
1359
1360proc display_all_files_helper {w path icon_name m} {
1361 global file_lists
1362
1363 lappend file_lists($w) $path
1364 set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
1365 $w image create end \
1366 -align center -padx 5 -pady 1 \
1367 -name $icon_name \
1368 -image [mapicon $w $m $path]
1369 $w insert end "[escape_path $path]\n"
1370}
1371
1372proc display_all_files {} {
1373 global ui_index ui_workdir
1374 global file_states file_lists
1375 global last_clicked
1376
1377 $ui_index conf -state normal
1378 $ui_workdir conf -state normal
1379
1380 $ui_index delete 0.0 end
1381 $ui_workdir delete 0.0 end
1382 set last_clicked {}
1383
1384 set file_lists($ui_index) [list]
1385 set file_lists($ui_workdir) [list]
1386
1387 foreach path [lsort [array names file_states]] {
1388 set s $file_states($path)
1389 set m [lindex $s 0]
1390 set icon_name [lindex $s 1]
1391
1392 set s [string index $m 0]
1393 if {$s ne {U} && $s ne {_}} {
1394 display_all_files_helper $ui_index $path \
1395 $icon_name $s
1396 }
1397
1398 if {[string index $m 0] eq {U}} {
1399 set s U
1400 } else {
1401 set s [string index $m 1]
1402 }
1403 if {$s ne {_}} {
1404 display_all_files_helper $ui_workdir $path \
1405 $icon_name $s
1406 }
1407 }
1408
1409 $ui_index conf -state disabled
1410 $ui_workdir conf -state disabled
1411}
1412
1413proc update_indexinfo {msg pathList after} {
1414 global update_index_cp ui_status_value
1415
1416 if {![lock_index update]} return
1417
1418 set update_index_cp 0
1419 set pathList [lsort $pathList]
1420 set totalCnt [llength $pathList]
1421 set batch [expr {int($totalCnt * .01) + 1}]
1422 if {$batch > 25} {set batch 25}
1423
1424 set ui_status_value [format \
1425 "$msg... %i/%i files (%.2f%%)" \
1426 $update_index_cp \
1427 $totalCnt \
1428 0.0]
1429 set fd [open "| git update-index -z --index-info" w]
1430 fconfigure $fd \
1431 -blocking 0 \
1432 -buffering full \
1433 -buffersize 512 \
1434 -translation binary
1435 fileevent $fd writable [list \
1436 write_update_indexinfo \
1437 $fd \
1438 $pathList \
1439 $totalCnt \
1440 $batch \
1441 $msg \
1442 $after \
1443 ]
1444}
1445
1446proc write_update_indexinfo {fd pathList totalCnt batch msg after} {
1447 global update_index_cp ui_status_value
1448 global file_states current_diff_path
1449
1450 if {$update_index_cp >= $totalCnt} {
1451 close $fd
1452 unlock_index
1453 uplevel #0 $after
1454 return
1455 }
1456
1457 for {set i $batch} \
1458 {$update_index_cp < $totalCnt && $i > 0} \
1459 {incr i -1} {
1460 set path [lindex $pathList $update_index_cp]
1461 incr update_index_cp
1462
1463 set s $file_states($path)
1464 switch -glob -- [lindex $s 0] {
1465 A? {set new _O}
1466 M? {set new _M}
1467 D_ {set new _D}
1468 D? {set new _?}
1469 ?? {continue}
1470 }
1471 set info [lindex $s 2]
1472 if {$info eq {}} continue
1473
1474 puts -nonewline $fd "$info\t$path\0"
1475 display_file $path $new
1476 }
1477
1478 set ui_status_value [format \
1479 "$msg... %i/%i files (%.2f%%)" \
1480 $update_index_cp \
1481 $totalCnt \
1482 [expr {100.0 * $update_index_cp / $totalCnt}]]
1483}
1484
1485proc update_index {msg pathList after} {
1486 global update_index_cp ui_status_value
1487
1488 if {![lock_index update]} return
1489
1490 set update_index_cp 0
1491 set pathList [lsort $pathList]
1492 set totalCnt [llength $pathList]
1493 set batch [expr {int($totalCnt * .01) + 1}]
1494 if {$batch > 25} {set batch 25}
1495
1496 set ui_status_value [format \
1497 "$msg... %i/%i files (%.2f%%)" \
1498 $update_index_cp \
1499 $totalCnt \
1500 0.0]
1501 set fd [open "| git update-index --add --remove -z --stdin" w]
1502 fconfigure $fd \
1503 -blocking 0 \
1504 -buffering full \
1505 -buffersize 512 \
1506 -translation binary
1507 fileevent $fd writable [list \
1508 write_update_index \
1509 $fd \
1510 $pathList \
1511 $totalCnt \
1512 $batch \
1513 $msg \
1514 $after \
1515 ]
1516}
1517
1518proc write_update_index {fd pathList totalCnt batch msg after} {
1519 global update_index_cp ui_status_value
1520 global file_states current_diff_path
1521
1522 if {$update_index_cp >= $totalCnt} {
1523 close $fd
1524 unlock_index
1525 uplevel #0 $after
1526 return
1527 }
1528
1529 for {set i $batch} \
1530 {$update_index_cp < $totalCnt && $i > 0} \
1531 {incr i -1} {
1532 set path [lindex $pathList $update_index_cp]
1533 incr update_index_cp
1534
1535 switch -glob -- [lindex $file_states($path) 0] {
1536 AD {set new __}
1537 ?D {set new D_}
1538 _O -
1539 AM {set new A_}
1540 U? {
1541 if {[file exists $path]} {
1542 set new M_
1543 } else {
1544 set new D_
1545 }
1546 }
1547 ?M {set new M_}
1548 ?? {continue}
1549 }
1550 puts -nonewline $fd "$path\0"
1551 display_file $path $new
1552 }
1553
1554 set ui_status_value [format \
1555 "$msg... %i/%i files (%.2f%%)" \
1556 $update_index_cp \
1557 $totalCnt \
1558 [expr {100.0 * $update_index_cp / $totalCnt}]]
1559}
1560
1561proc checkout_index {msg pathList after} {
1562 global update_index_cp ui_status_value
1563
1564 if {![lock_index update]} return
1565
1566 set update_index_cp 0
1567 set pathList [lsort $pathList]
1568 set totalCnt [llength $pathList]
1569 set batch [expr {int($totalCnt * .01) + 1}]
1570 if {$batch > 25} {set batch 25}
1571
1572 set ui_status_value [format \
1573 "$msg... %i/%i files (%.2f%%)" \
1574 $update_index_cp \
1575 $totalCnt \
1576 0.0]
1577 set cmd [list git checkout-index]
1578 lappend cmd --index
1579 lappend cmd --quiet
1580 lappend cmd --force
1581 lappend cmd -z
1582 lappend cmd --stdin
1583 set fd [open "| $cmd " w]
1584 fconfigure $fd \
1585 -blocking 0 \
1586 -buffering full \
1587 -buffersize 512 \
1588 -translation binary
1589 fileevent $fd writable [list \
1590 write_checkout_index \
1591 $fd \
1592 $pathList \
1593 $totalCnt \
1594 $batch \
1595 $msg \
1596 $after \
1597 ]
1598}
1599
1600proc write_checkout_index {fd pathList totalCnt batch msg after} {
1601 global update_index_cp ui_status_value
1602 global file_states current_diff_path
1603
1604 if {$update_index_cp >= $totalCnt} {
1605 close $fd
1606 unlock_index
1607 uplevel #0 $after
1608 return
1609 }
1610
1611 for {set i $batch} \
1612 {$update_index_cp < $totalCnt && $i > 0} \
1613 {incr i -1} {
1614 set path [lindex $pathList $update_index_cp]
1615 incr update_index_cp
1616 switch -glob -- [lindex $file_states($path) 0] {
1617 U? {continue}
1618 ?M -
1619 ?D {
1620 puts -nonewline $fd "$path\0"
1621 display_file $path ?_
1622 }
1623 }
1624 }
1625
1626 set ui_status_value [format \
1627 "$msg... %i/%i files (%.2f%%)" \
1628 $update_index_cp \
1629 $totalCnt \
1630 [expr {100.0 * $update_index_cp / $totalCnt}]]
1631}
1632
1633######################################################################
1634##
1635## branch management
1636
1637proc is_tracking_branch {name} {
1638 global tracking_branches
1639
1640 if {![catch {set info $tracking_branches($name)}]} {
1641 return 1
1642 }
1643 foreach t [array names tracking_branches] {
1644 if {[string match {*/\*} $t] && [string match $t $name]} {
1645 return 1
1646 }
1647 }
1648 return 0
1649}
1650
1651proc load_all_heads {} {
1652 global all_heads
1653
1654 set all_heads [list]
1655 set fd [open "| git for-each-ref --format=%(refname) refs/heads" r]
1656 while {[gets $fd line] > 0} {
1657 if {[is_tracking_branch $line]} continue
1658 if {![regsub ^refs/heads/ $line {} name]} continue
1659 lappend all_heads $name
1660 }
1661 close $fd
1662
1663 set all_heads [lsort $all_heads]
1664}
1665
1666proc populate_branch_menu {} {
1667 global all_heads disable_on_lock
1668
1669 set m .mbar.branch
1670 set last [$m index last]
1671 for {set i 0} {$i <= $last} {incr i} {
1672 if {[$m type $i] eq {separator}} {
1673 $m delete $i last
1674 set new_dol [list]
1675 foreach a $disable_on_lock {
1676 if {[lindex $a 0] ne $m || [lindex $a 2] < $i} {
1677 lappend new_dol $a
1678 }
1679 }
1680 set disable_on_lock $new_dol
1681 break
1682 }
1683 }
1684
1685 $m add separator
1686 foreach b $all_heads {
1687 $m add radiobutton \
1688 -label $b \
1689 -command [list switch_branch $b] \
1690 -variable current_branch \
1691 -value $b \
1692 -font font_ui
1693 lappend disable_on_lock \
1694 [list $m entryconf [$m index last] -state]
1695 }
1696}
1697
1698proc all_tracking_branches {} {
1699 global tracking_branches
1700
1701 set all_trackings {}
1702 set cmd {}
1703 foreach name [array names tracking_branches] {
1704 if {[regsub {/\*$} $name {} name]} {
1705 lappend cmd $name
1706 } else {
1707 regsub ^refs/(heads|remotes)/ $name {} name
1708 lappend all_trackings $name
1709 }
1710 }
1711
1712 if {$cmd ne {}} {
1713 set fd [open "| git for-each-ref --format=%(refname) $cmd" r]
1714 while {[gets $fd name] > 0} {
1715 regsub ^refs/(heads|remotes)/ $name {} name
1716 lappend all_trackings $name
1717 }
1718 close $fd
1719 }
1720
1721 return [lsort -unique $all_trackings]
1722}
1723
1724proc do_create_branch_action {w} {
1725 global all_heads null_sha1 repo_config
1726 global create_branch_checkout create_branch_revtype
1727 global create_branch_head create_branch_trackinghead
1728
1729 set newbranch [string trim [$w.desc.name_t get 0.0 end]]
1730 if {$newbranch eq {}
1731 || $newbranch eq $repo_config(gui.newbranchtemplate)} {
1732 tk_messageBox \
1733 -icon error \
1734 -type ok \
1735 -title [wm title $w] \
1736 -parent $w \
1737 -message "Please supply a branch name."
1738 focus $w.desc.name_t
1739 return
1740 }
1741 if {![catch {exec git show-ref --verify -- "refs/heads/$newbranch"}]} {
1742 tk_messageBox \
1743 -icon error \
1744 -type ok \
1745 -title [wm title $w] \
1746 -parent $w \
1747 -message "Branch '$newbranch' already exists."
1748 focus $w.desc.name_t
1749 return
1750 }
1751 if {[catch {exec git check-ref-format "heads/$newbranch"}]} {
1752 tk_messageBox \
1753 -icon error \
1754 -type ok \
1755 -title [wm title $w] \
1756 -parent $w \
1757 -message "We do not like '$newbranch' as a branch name."
1758 focus $w.desc.name_t
1759 return
1760 }
1761
1762 set rev {}
1763 switch -- $create_branch_revtype {
1764 head {set rev $create_branch_head}
1765 tracking {set rev $create_branch_trackinghead}
1766 expression {set rev [string trim [$w.from.exp_t get 0.0 end]]}
1767 }
1768 if {[catch {set cmt [exec git rev-parse --verify "${rev}^0"]}]} {
1769 tk_messageBox \
1770 -icon error \
1771 -type ok \
1772 -title [wm title $w] \
1773 -parent $w \
1774 -message "Invalid starting revision: $rev"
1775 return
1776 }
1777 set cmd [list git update-ref]
1778 lappend cmd -m
1779 lappend cmd "branch: Created from $rev"
1780 lappend cmd "refs/heads/$newbranch"
1781 lappend cmd $cmt
1782 lappend cmd $null_sha1
1783 if {[catch {eval exec $cmd} err]} {
1784 tk_messageBox \
1785 -icon error \
1786 -type ok \
1787 -title [wm title $w] \
1788 -parent $w \
1789 -message "Failed to create '$newbranch'.\n\n$err"
1790 return
1791 }
1792
1793 lappend all_heads $newbranch
1794 set all_heads [lsort $all_heads]
1795 populate_branch_menu
1796 destroy $w
1797 if {$create_branch_checkout} {
1798 switch_branch $newbranch
1799 }
1800}
1801
1802proc radio_selector {varname value args} {
1803 upvar #0 $varname var
1804 set var $value
1805}
1806
1807trace add variable create_branch_head write \
1808 [list radio_selector create_branch_revtype head]
1809trace add variable create_branch_trackinghead write \
1810 [list radio_selector create_branch_revtype tracking]
1811
1812trace add variable delete_branch_head write \
1813 [list radio_selector delete_branch_checktype head]
1814trace add variable delete_branch_trackinghead write \
1815 [list radio_selector delete_branch_checktype tracking]
1816
1817proc do_create_branch {} {
1818 global all_heads current_branch repo_config
1819 global create_branch_checkout create_branch_revtype
1820 global create_branch_head create_branch_trackinghead
1821
1822 set w .branch_editor
1823 toplevel $w
1824 wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
1825
1826 label $w.header -text {Create New Branch} \
1827 -font font_uibold
1828 pack $w.header -side top -fill x
1829
1830 frame $w.buttons
1831 button $w.buttons.create -text Create \
1832 -font font_ui \
1833 -default active \
1834 -command [list do_create_branch_action $w]
1835 pack $w.buttons.create -side right
1836 button $w.buttons.cancel -text {Cancel} \
1837 -font font_ui \
1838 -command [list destroy $w]
1839 pack $w.buttons.cancel -side right -padx 5
1840 pack $w.buttons -side bottom -fill x -pady 10 -padx 10
1841
1842 labelframe $w.desc \
1843 -text {Branch Description} \
1844 -font font_ui
1845 label $w.desc.name_l -text {Name:} -font font_ui
1846 text $w.desc.name_t \
1847 -borderwidth 1 \
1848 -relief sunken \
1849 -height 1 \
1850 -width 40 \
1851 -font font_ui
1852 $w.desc.name_t insert 0.0 $repo_config(gui.newbranchtemplate)
1853 grid $w.desc.name_l $w.desc.name_t -stick we -padx {0 5}
1854 bind $w.desc.name_t <Shift-Key-Tab> {focus [tk_focusPrev %W];break}
1855 bind $w.desc.name_t <Key-Tab> {focus [tk_focusNext %W];break}
1856 bind $w.desc.name_t <Key-Return> "do_create_branch_action $w;break"
1857 bind $w.desc.name_t <Key> {
1858 if {{%K} ne {BackSpace}
1859 && {%K} ne {Tab}
1860 && {%K} ne {Escape}
1861 && {%K} ne {Return}} {
1862 if {%k <= 32} break
1863 if {[string first %A {~^:?*[}] >= 0} break
1864 }
1865 }
1866 grid columnconfigure $w.desc 1 -weight 1
1867 pack $w.desc -anchor nw -fill x -pady 5 -padx 5
1868
1869 labelframe $w.from \
1870 -text {Starting Revision} \
1871 -font font_ui
1872 radiobutton $w.from.head_r \
1873 -text {Local Branch:} \
1874 -value head \
1875 -variable create_branch_revtype \
1876 -font font_ui
1877 eval tk_optionMenu $w.from.head_m create_branch_head $all_heads
1878 grid $w.from.head_r $w.from.head_m -sticky w
1879 set all_trackings [all_tracking_branches]
1880 if {$all_trackings ne {}} {
1881 set create_branch_trackinghead [lindex $all_trackings 0]
1882 radiobutton $w.from.tracking_r \
1883 -text {Tracking Branch:} \
1884 -value tracking \
1885 -variable create_branch_revtype \
1886 -font font_ui
1887 eval tk_optionMenu $w.from.tracking_m \
1888 create_branch_trackinghead \
1889 $all_trackings
1890 grid $w.from.tracking_r $w.from.tracking_m -sticky w
1891 }
1892 radiobutton $w.from.exp_r \
1893 -text {Revision Expression:} \
1894 -value expression \
1895 -variable create_branch_revtype \
1896 -font font_ui
1897 text $w.from.exp_t \
1898 -borderwidth 1 \
1899 -relief sunken \
1900 -height 1 \
1901 -width 50 \
1902 -font font_ui
1903 grid $w.from.exp_r $w.from.exp_t -stick we -padx {0 5}
1904 bind $w.from.exp_t <Shift-Key-Tab> {focus [tk_focusPrev %W];break}
1905 bind $w.from.exp_t <Key-Tab> {focus [tk_focusNext %W];break}
1906 bind $w.from.exp_t <Key-Return> "do_create_branch_action $w;break"
1907 bind $w.from.exp_t <Key-space> break
1908 bind $w.from.exp_t <Key> {set create_branch_revtype expression}
1909 grid columnconfigure $w.from 1 -weight 1
1910 pack $w.from -anchor nw -fill x -pady 5 -padx 5
1911
1912 labelframe $w.postActions \
1913 -text {Post Creation Actions} \
1914 -font font_ui
1915 checkbutton $w.postActions.checkout \
1916 -text {Checkout after creation} \
1917 -variable create_branch_checkout \
1918 -font font_ui
1919 pack $w.postActions.checkout -anchor nw
1920 pack $w.postActions -anchor nw -fill x -pady 5 -padx 5
1921
1922 set create_branch_checkout 1
1923 set create_branch_head $current_branch
1924 set create_branch_revtype head
1925
1926 bind $w <Visibility> "grab $w; focus $w.desc.name_t"
1927 bind $w <Key-Escape> "destroy $w"
1928 bind $w <Key-Return> "do_create_branch_action $w;break"
1929 wm title $w "[appname] ([reponame]): Create Branch"
1930 tkwait window $w
1931}
1932
1933proc do_delete_branch_action {w} {
1934 global all_heads
1935 global delete_branch_checktype delete_branch_head delete_branch_trackinghead
1936
1937 set check_rev {}
1938 switch -- $delete_branch_checktype {
1939 head {set check_rev $delete_branch_head}
1940 tracking {set check_rev $delete_branch_trackinghead}
1941 always {set check_rev {:none}}
1942 }
1943 if {$check_rev eq {:none}} {
1944 set check_cmt {}
1945 } elseif {[catch {set check_cmt [exec git rev-parse --verify "${check_rev}^0"]}]} {
1946 tk_messageBox \
1947 -icon error \
1948 -type ok \
1949 -title [wm title $w] \
1950 -parent $w \
1951 -message "Invalid check revision: $check_rev"
1952 return
1953 }
1954
1955 set to_delete [list]
1956 set not_merged [list]
1957 foreach i [$w.list.l curselection] {
1958 set b [$w.list.l get $i]
1959 if {[catch {set o [exec git rev-parse --verify $b]}]} continue
1960 if {$check_cmt ne {}} {
1961 if {$b eq $check_rev} continue
1962 if {[catch {set m [exec git merge-base $o $check_cmt]}]} continue
1963 if {$o ne $m} {
1964 lappend not_merged $b
1965 continue
1966 }
1967 }
1968 lappend to_delete [list $b $o]
1969 }
1970 if {$not_merged ne {}} {
1971 set msg "The following branches are not completely merged into $check_rev:
1972
1973 - [join $not_merged "\n - "]"
1974 tk_messageBox \
1975 -icon info \
1976 -type ok \
1977 -title [wm title $w] \
1978 -parent $w \
1979 -message $msg
1980 }
1981 if {$to_delete eq {}} return
1982 if {$delete_branch_checktype eq {always}} {
1983 set msg {Recovering deleted branches is difficult.
1984
1985Delete the selected branches?}
1986 if {[tk_messageBox \
1987 -icon warning \
1988 -type yesno \
1989 -title [wm title $w] \
1990 -parent $w \
1991 -message $msg] ne yes} {
1992 return
1993 }
1994 }
1995
1996 set failed {}
1997 foreach i $to_delete {
1998 set b [lindex $i 0]
1999 set o [lindex $i 1]
2000 if {[catch {exec git update-ref -d "refs/heads/$b" $o} err]} {
2001 append failed " - $b: $err\n"
2002 } else {
2003 set x [lsearch -sorted $all_heads $b]
2004 if {$x >= 0} {
2005 set all_heads [lreplace $all_heads $x $x]
2006 }
2007 }
2008 }
2009
2010 if {$failed ne {}} {
2011 tk_messageBox \
2012 -icon error \
2013 -type ok \
2014 -title [wm title $w] \
2015 -parent $w \
2016 -message "Failed to delete branches:\n$failed"
2017 }
2018
2019 set all_heads [lsort $all_heads]
2020 populate_branch_menu
2021 destroy $w
2022}
2023
2024proc do_delete_branch {} {
2025 global all_heads tracking_branches current_branch
2026 global delete_branch_checktype delete_branch_head delete_branch_trackinghead
2027
2028 set w .branch_editor
2029 toplevel $w
2030 wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
2031
2032 label $w.header -text {Delete Local Branch} \
2033 -font font_uibold
2034 pack $w.header -side top -fill x
2035
2036 frame $w.buttons
2037 button $w.buttons.create -text Delete \
2038 -font font_ui \
2039 -command [list do_delete_branch_action $w]
2040 pack $w.buttons.create -side right
2041 button $w.buttons.cancel -text {Cancel} \
2042 -font font_ui \
2043 -command [list destroy $w]
2044 pack $w.buttons.cancel -side right -padx 5
2045 pack $w.buttons -side bottom -fill x -pady 10 -padx 10
2046
2047 labelframe $w.list \
2048 -text {Local Branches} \
2049 -font font_ui
2050 listbox $w.list.l \
2051 -height 10 \
2052 -width 50 \
2053 -selectmode extended \
2054 -font font_ui
2055 foreach h $all_heads {
2056 if {$h ne $current_branch} {
2057 $w.list.l insert end $h
2058 }
2059 }
2060 pack $w.list.l -fill both -pady 5 -padx 5
2061 pack $w.list -fill both -pady 5 -padx 5
2062
2063 labelframe $w.validate \
2064 -text {Delete Only If} \
2065 -font font_ui
2066 radiobutton $w.validate.head_r \
2067 -text {Merged Into Local Branch:} \
2068 -value head \
2069 -variable delete_branch_checktype \
2070 -font font_ui
2071 eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads
2072 grid $w.validate.head_r $w.validate.head_m -sticky w
2073 set all_trackings [all_tracking_branches]
2074 if {$all_trackings ne {}} {
2075 set delete_branch_trackinghead [lindex $all_trackings 0]
2076 radiobutton $w.validate.tracking_r \
2077 -text {Merged Into Tracking Branch:} \
2078 -value tracking \
2079 -variable delete_branch_checktype \
2080 -font font_ui
2081 eval tk_optionMenu $w.validate.tracking_m \
2082 delete_branch_trackinghead \
2083 $all_trackings
2084 grid $w.validate.tracking_r $w.validate.tracking_m -sticky w
2085 }
2086 radiobutton $w.validate.always_r \
2087 -text {Always (Do not perform merge checks)} \
2088 -value always \
2089 -variable delete_branch_checktype \
2090 -font font_ui
2091 grid $w.validate.always_r -columnspan 2 -sticky w
2092 grid columnconfigure $w.validate 1 -weight 1
2093 pack $w.validate -anchor nw -fill x -pady 5 -padx 5
2094
2095 set delete_branch_head $current_branch
2096 set delete_branch_checktype head
2097
2098 bind $w <Visibility> "grab $w; focus $w"
2099 bind $w <Key-Escape> "destroy $w"
2100 wm title $w "[appname] ([reponame]): Delete Branch"
2101 tkwait window $w
2102}
2103
2104proc switch_branch {b} {
2105 global HEAD commit_type file_states current_branch
2106 global selected_commit_type ui_comm
2107
2108 if {![lock_index switch]} return
2109
2110 # -- Backup the selected branch (repository_state resets it)
2111 #
2112 set new_branch $current_branch
2113
2114 # -- Our in memory state should match the repository.
2115 #
2116 repository_state curType curHEAD curMERGE_HEAD
2117 if {[string match amend* $commit_type]
2118 && $curType eq {normal}
2119 && $curHEAD eq $HEAD} {
2120 } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
2121 info_popup {Last scanned state does not match repository state.
2122
2123Another Git program has modified this repository
2124since the last scan. A rescan must be performed
2125before the current branch can be changed.
2126
2127The rescan will be automatically started now.
2128}
2129 unlock_index
2130 rescan {set ui_status_value {Ready.}}
2131 return
2132 }
2133
2134 # -- Toss the message buffer if we are in amend mode.
2135 #
2136 if {[string match amend* $curType]} {
2137 $ui_comm delete 0.0 end
2138 $ui_comm edit reset
2139 $ui_comm edit modified false
2140 }
2141
2142 set selected_commit_type new
2143 set current_branch $new_branch
2144
2145 unlock_index
2146 error "NOT FINISHED"
2147}
2148
2149######################################################################
2150##
2151## remote management
2152
2153proc load_all_remotes {} {
2154 global repo_config
2155 global all_remotes tracking_branches
2156
2157 set all_remotes [list]
2158 array unset tracking_branches
2159
2160 set rm_dir [gitdir remotes]
2161 if {[file isdirectory $rm_dir]} {
2162 set all_remotes [glob \
2163 -types f \
2164 -tails \
2165 -nocomplain \
2166 -directory $rm_dir *]
2167
2168 foreach name $all_remotes {
2169 catch {
2170 set fd [open [file join $rm_dir $name] r]
2171 while {[gets $fd line] >= 0} {
2172 if {![regexp {^Pull:[ ]*([^:]+):(.+)$} \
2173 $line line src dst]} continue
2174 if {![regexp ^refs/ $dst]} {
2175 set dst "refs/heads/$dst"
2176 }
2177 set tracking_branches($dst) [list $name $src]
2178 }
2179 close $fd
2180 }
2181 }
2182 }
2183
2184 foreach line [array names repo_config remote.*.url] {
2185 if {![regexp ^remote\.(.*)\.url\$ $line line name]} continue
2186 lappend all_remotes $name
2187
2188 if {[catch {set fl $repo_config(remote.$name.fetch)}]} {
2189 set fl {}
2190 }
2191 foreach line $fl {
2192 if {![regexp {^([^:]+):(.+)$} $line line src dst]} continue
2193 if {![regexp ^refs/ $dst]} {
2194 set dst "refs/heads/$dst"
2195 }
2196 set tracking_branches($dst) [list $name $src]
2197 }
2198 }
2199
2200 set all_remotes [lsort -unique $all_remotes]
2201}
2202
2203proc populate_fetch_menu {m} {
2204 global all_remotes repo_config
2205
2206 foreach r $all_remotes {
2207 set enable 0
2208 if {![catch {set a $repo_config(remote.$r.url)}]} {
2209 if {![catch {set a $repo_config(remote.$r.fetch)}]} {
2210 set enable 1
2211 }
2212 } else {
2213 catch {
2214 set fd [open [gitdir remotes $r] r]
2215 while {[gets $fd n] >= 0} {
2216 if {[regexp {^Pull:[ \t]*([^:]+):} $n]} {
2217 set enable 1
2218 break
2219 }
2220 }
2221 close $fd
2222 }
2223 }
2224
2225 if {$enable} {
2226 $m add command \
2227 -label "Fetch from $r..." \
2228 -command [list fetch_from $r] \
2229 -font font_ui
2230 }
2231 }
2232}
2233
2234proc populate_push_menu {m} {
2235 global all_remotes repo_config
2236
2237 foreach r $all_remotes {
2238 set enable 0
2239 if {![catch {set a $repo_config(remote.$r.url)}]} {
2240 if {![catch {set a $repo_config(remote.$r.push)}]} {
2241 set enable 1
2242 }
2243 } else {
2244 catch {
2245 set fd [open [gitdir remotes $r] r]
2246 while {[gets $fd n] >= 0} {
2247 if {[regexp {^Push:[ \t]*([^:]+):} $n]} {
2248 set enable 1
2249 break
2250 }
2251 }
2252 close $fd
2253 }
2254 }
2255
2256 if {$enable} {
2257 $m add command \
2258 -label "Push to $r..." \
2259 -command [list push_to $r] \
2260 -font font_ui
2261 }
2262 }
2263}
2264
2265proc populate_pull_menu {m} {
2266 global repo_config all_remotes disable_on_lock
2267
2268 foreach remote $all_remotes {
2269 set rb_list [list]
2270 if {[array get repo_config remote.$remote.url] ne {}} {
2271 if {[array get repo_config remote.$remote.fetch] ne {}} {
2272 foreach line $repo_config(remote.$remote.fetch) {
2273 if {[regexp {^([^:]+):} $line line rb]} {
2274 lappend rb_list $rb
2275 }
2276 }
2277 }
2278 } else {
2279 catch {
2280 set fd [open [gitdir remotes $remote] r]
2281 while {[gets $fd line] >= 0} {
2282 if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} {
2283 lappend rb_list $rb
2284 }
2285 }
2286 close $fd
2287 }
2288 }
2289
2290 foreach rb $rb_list {
2291 regsub ^refs/heads/ $rb {} rb_short
2292 $m add command \
2293 -label "Branch $rb_short from $remote..." \
2294 -command [list pull_remote $remote $rb] \
2295 -font font_ui
2296 lappend disable_on_lock \
2297 [list $m entryconf [$m index last] -state]
2298 }
2299 }
2300}
2301
2302######################################################################
2303##
2304## icons
2305
2306set filemask {
2307#define mask_width 14
2308#define mask_height 15
2309static unsigned char mask_bits[] = {
2310 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
2311 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
2312 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
2313}
2314
2315image create bitmap file_plain -background white -foreground black -data {
2316#define plain_width 14
2317#define plain_height 15
2318static unsigned char plain_bits[] = {
2319 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
2320 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
2321 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
2322} -maskdata $filemask
2323
2324image create bitmap file_mod -background white -foreground blue -data {
2325#define mod_width 14
2326#define mod_height 15
2327static unsigned char mod_bits[] = {
2328 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
2329 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
2330 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
2331} -maskdata $filemask
2332
2333image create bitmap file_fulltick -background white -foreground "#007000" -data {
2334#define file_fulltick_width 14
2335#define file_fulltick_height 15
2336static unsigned char file_fulltick_bits[] = {
2337 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
2338 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
2339 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
2340} -maskdata $filemask
2341
2342image create bitmap file_parttick -background white -foreground "#005050" -data {
2343#define parttick_width 14
2344#define parttick_height 15
2345static unsigned char parttick_bits[] = {
2346 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
2347 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
2348 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
2349} -maskdata $filemask
2350
2351image create bitmap file_question -background white -foreground black -data {
2352#define file_question_width 14
2353#define file_question_height 15
2354static unsigned char file_question_bits[] = {
2355 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
2356 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
2357 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
2358} -maskdata $filemask
2359
2360image create bitmap file_removed -background white -foreground red -data {
2361#define file_removed_width 14
2362#define file_removed_height 15
2363static unsigned char file_removed_bits[] = {
2364 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
2365 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
2366 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
2367} -maskdata $filemask
2368
2369image create bitmap file_merge -background white -foreground blue -data {
2370#define file_merge_width 14
2371#define file_merge_height 15
2372static unsigned char file_merge_bits[] = {
2373 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
2374 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
2375 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
2376} -maskdata $filemask
2377
2378set ui_index .vpane.files.index.list
2379set ui_workdir .vpane.files.workdir.list
2380
2381set all_icons(_$ui_index) file_plain
2382set all_icons(A$ui_index) file_fulltick
2383set all_icons(M$ui_index) file_fulltick
2384set all_icons(D$ui_index) file_removed
2385set all_icons(U$ui_index) file_merge
2386
2387set all_icons(_$ui_workdir) file_plain
2388set all_icons(M$ui_workdir) file_mod
2389set all_icons(D$ui_workdir) file_question
2390set all_icons(U$ui_workdir) file_merge
2391set all_icons(O$ui_workdir) file_plain
2392
2393set max_status_desc 0
2394foreach i {
2395 {__ "Unmodified"}
2396
2397 {_M "Modified, not staged"}
2398 {M_ "Staged for commit"}
2399 {MM "Portions staged for commit"}
2400 {MD "Staged for commit, missing"}
2401
2402 {_O "Untracked, not staged"}
2403 {A_ "Staged for commit"}
2404 {AM "Portions staged for commit"}
2405 {AD "Staged for commit, missing"}
2406
2407 {_D "Missing"}
2408 {D_ "Staged for removal"}
2409 {DO "Staged for removal, still present"}
2410
2411 {U_ "Requires merge resolution"}
2412 {UU "Requires merge resolution"}
2413 {UM "Requires merge resolution"}
2414 {UD "Requires merge resolution"}
2415 } {
2416 if {$max_status_desc < [string length [lindex $i 1]]} {
2417 set max_status_desc [string length [lindex $i 1]]
2418 }
2419 set all_descs([lindex $i 0]) [lindex $i 1]
2420}
2421unset i
2422
2423######################################################################
2424##
2425## util
2426
2427proc is_MacOSX {} {
2428 global tcl_platform tk_library
2429 if {[tk windowingsystem] eq {aqua}} {
2430 return 1
2431 }
2432 return 0
2433}
2434
2435proc is_Windows {} {
2436 global tcl_platform
2437 if {$tcl_platform(platform) eq {windows}} {
2438 return 1
2439 }
2440 return 0
2441}
2442
2443proc bind_button3 {w cmd} {
2444 bind $w <Any-Button-3> $cmd
2445 if {[is_MacOSX]} {
2446 bind $w <Control-Button-1> $cmd
2447 }
2448}
2449
2450proc incr_font_size {font {amt 1}} {
2451 set sz [font configure $font -size]
2452 incr sz $amt
2453 font configure $font -size $sz
2454 font configure ${font}bold -size $sz
2455}
2456
2457proc hook_failed_popup {hook msg} {
2458 set w .hookfail
2459 toplevel $w
2460
2461 frame $w.m
2462 label $w.m.l1 -text "$hook hook failed:" \
2463 -anchor w \
2464 -justify left \
2465 -font font_uibold
2466 text $w.m.t \
2467 -background white -borderwidth 1 \
2468 -relief sunken \
2469 -width 80 -height 10 \
2470 -font font_diff \
2471 -yscrollcommand [list $w.m.sby set]
2472 label $w.m.l2 \
2473 -text {You must correct the above errors before committing.} \
2474 -anchor w \
2475 -justify left \
2476 -font font_uibold
2477 scrollbar $w.m.sby -command [list $w.m.t yview]
2478 pack $w.m.l1 -side top -fill x
2479 pack $w.m.l2 -side bottom -fill x
2480 pack $w.m.sby -side right -fill y
2481 pack $w.m.t -side left -fill both -expand 1
2482 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
2483
2484 $w.m.t insert 1.0 $msg
2485 $w.m.t conf -state disabled
2486
2487 button $w.ok -text OK \
2488 -width 15 \
2489 -font font_ui \
2490 -command "destroy $w"
2491 pack $w.ok -side bottom -anchor e -pady 10 -padx 10
2492
2493 bind $w <Visibility> "grab $w; focus $w"
2494 bind $w <Key-Return> "destroy $w"
2495 wm title $w "[appname] ([reponame]): error"
2496 tkwait window $w
2497}
2498
2499set next_console_id 0
2500
2501proc new_console {short_title long_title} {
2502 global next_console_id console_data
2503 set w .console[incr next_console_id]
2504 set console_data($w) [list $short_title $long_title]
2505 return [console_init $w]
2506}
2507
2508proc console_init {w} {
2509 global console_cr console_data M1B
2510
2511 set console_cr($w) 1.0
2512 toplevel $w
2513 frame $w.m
2514 label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
2515 -anchor w \
2516 -justify left \
2517 -font font_uibold
2518 text $w.m.t \
2519 -background white -borderwidth 1 \
2520 -relief sunken \
2521 -width 80 -height 10 \
2522 -font font_diff \
2523 -state disabled \
2524 -yscrollcommand [list $w.m.sby set]
2525 label $w.m.s -text {Working... please wait...} \
2526 -anchor w \
2527 -justify left \
2528 -font font_uibold
2529 scrollbar $w.m.sby -command [list $w.m.t yview]
2530 pack $w.m.l1 -side top -fill x
2531 pack $w.m.s -side bottom -fill x
2532 pack $w.m.sby -side right -fill y
2533 pack $w.m.t -side left -fill both -expand 1
2534 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
2535
2536 menu $w.ctxm -tearoff 0
2537 $w.ctxm add command -label "Copy" \
2538 -font font_ui \
2539 -command "tk_textCopy $w.m.t"
2540 $w.ctxm add command -label "Select All" \
2541 -font font_ui \
2542 -command "$w.m.t tag add sel 0.0 end"
2543 $w.ctxm add command -label "Copy All" \
2544 -font font_ui \
2545 -command "
2546 $w.m.t tag add sel 0.0 end
2547 tk_textCopy $w.m.t
2548 $w.m.t tag remove sel 0.0 end
2549 "
2550
2551 button $w.ok -text {Close} \
2552 -font font_ui \
2553 -state disabled \
2554 -command "destroy $w"
2555 pack $w.ok -side bottom -anchor e -pady 10 -padx 10
2556
2557 bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y"
2558 bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break"
2559 bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break"
2560 bind $w <Visibility> "focus $w"
2561 wm title $w "[appname] ([reponame]): [lindex $console_data($w) 0]"
2562 return $w
2563}
2564
2565proc console_exec {w cmd {after {}}} {
2566 # -- Windows tosses the enviroment when we exec our child.
2567 # But most users need that so we have to relogin. :-(
2568 #
2569 if {[is_Windows]} {
2570 set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
2571 }
2572
2573 # -- Tcl won't let us redirect both stdout and stderr to
2574 # the same pipe. So pass it through cat...
2575 #
2576 set cmd [concat | $cmd |& cat]
2577
2578 set fd_f [open $cmd r]
2579 fconfigure $fd_f -blocking 0 -translation binary
2580 fileevent $fd_f readable [list console_read $w $fd_f $after]
2581}
2582
2583proc console_read {w fd after} {
2584 global console_cr console_data
2585
2586 set buf [read $fd]
2587 if {$buf ne {}} {
2588 if {![winfo exists $w]} {console_init $w}
2589 $w.m.t conf -state normal
2590 set c 0
2591 set n [string length $buf]
2592 while {$c < $n} {
2593 set cr [string first "\r" $buf $c]
2594 set lf [string first "\n" $buf $c]
2595 if {$cr < 0} {set cr [expr {$n + 1}]}
2596 if {$lf < 0} {set lf [expr {$n + 1}]}
2597
2598 if {$lf < $cr} {
2599 $w.m.t insert end [string range $buf $c $lf]
2600 set console_cr($w) [$w.m.t index {end -1c}]
2601 set c $lf
2602 incr c
2603 } else {
2604 $w.m.t delete $console_cr($w) end
2605 $w.m.t insert end "\n"
2606 $w.m.t insert end [string range $buf $c $cr]
2607 set c $cr
2608 incr c
2609 }
2610 }
2611 $w.m.t conf -state disabled
2612 $w.m.t see end
2613 }
2614
2615 fconfigure $fd -blocking 1
2616 if {[eof $fd]} {
2617 if {[catch {close $fd}]} {
2618 if {![winfo exists $w]} {console_init $w}
2619 $w.m.s conf -background red -text {Error: Command Failed}
2620 $w.ok conf -state normal
2621 set ok 0
2622 } elseif {[winfo exists $w]} {
2623 $w.m.s conf -background green -text {Success}
2624 $w.ok conf -state normal
2625 set ok 1
2626 }
2627 array unset console_cr $w
2628 array unset console_data $w
2629 if {$after ne {}} {
2630 uplevel #0 $after $ok
2631 }
2632 return
2633 }
2634 fconfigure $fd -blocking 0
2635}
2636
2637######################################################################
2638##
2639## ui commands
2640
2641set starting_gitk_msg {Starting gitk... please wait...}
2642
2643proc do_gitk {revs} {
2644 global ui_status_value starting_gitk_msg
2645
2646 set cmd gitk
2647 if {$revs ne {}} {
2648 append cmd { }
2649 append cmd $revs
2650 }
2651 if {[is_Windows]} {
2652 set cmd "sh -c \"exec $cmd\""
2653 }
2654 append cmd { &}
2655
2656 if {[catch {eval exec $cmd} err]} {
2657 error_popup "Failed to start gitk:\n\n$err"
2658 } else {
2659 set ui_status_value $starting_gitk_msg
2660 after 10000 {
2661 if {$ui_status_value eq $starting_gitk_msg} {
2662 set ui_status_value {Ready.}
2663 }
2664 }
2665 }
2666}
2667
2668proc do_gc {} {
2669 set w [new_console {gc} {Compressing the object database}]
2670 console_exec $w {git gc}
2671}
2672
2673proc do_fsck_objects {} {
2674 set w [new_console {fsck-objects} \
2675 {Verifying the object database with fsck-objects}]
2676 set cmd [list git fsck-objects]
2677 lappend cmd --full
2678 lappend cmd --cache
2679 lappend cmd --strict
2680 console_exec $w $cmd
2681}
2682
2683set is_quitting 0
2684
2685proc do_quit {} {
2686 global ui_comm is_quitting repo_config commit_type
2687
2688 if {$is_quitting} return
2689 set is_quitting 1
2690
2691 # -- Stash our current commit buffer.
2692 #
2693 set save [gitdir GITGUI_MSG]
2694 set msg [string trim [$ui_comm get 0.0 end]]
2695 if {![string match amend* $commit_type]
2696 && [$ui_comm edit modified]
2697 && $msg ne {}} {
2698 catch {
2699 set fd [open $save w]
2700 puts $fd [string trim [$ui_comm get 0.0 end]]
2701 close $fd
2702 }
2703 } else {
2704 catch {file delete $save}
2705 }
2706
2707 # -- Stash our current window geometry into this repository.
2708 #
2709 set cfg_geometry [list]
2710 lappend cfg_geometry [wm geometry .]
2711 lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
2712 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
2713 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
2714 set rc_geometry {}
2715 }
2716 if {$cfg_geometry ne $rc_geometry} {
2717 catch {exec git repo-config gui.geometry $cfg_geometry}
2718 }
2719
2720 destroy .
2721}
2722
2723proc do_rescan {} {
2724 rescan {set ui_status_value {Ready.}}
2725}
2726
2727proc unstage_helper {txt paths} {
2728 global file_states current_diff_path
2729
2730 if {![lock_index begin-update]} return
2731
2732 set pathList [list]
2733 set after {}
2734 foreach path $paths {
2735 switch -glob -- [lindex $file_states($path) 0] {
2736 A? -
2737 M? -
2738 D? {
2739 lappend pathList $path
2740 if {$path eq $current_diff_path} {
2741 set after {reshow_diff;}
2742 }
2743 }
2744 }
2745 }
2746 if {$pathList eq {}} {
2747 unlock_index
2748 } else {
2749 update_indexinfo \
2750 $txt \
2751 $pathList \
2752 [concat $after {set ui_status_value {Ready.}}]
2753 }
2754}
2755
2756proc do_unstage_selection {} {
2757 global current_diff_path selected_paths
2758
2759 if {[array size selected_paths] > 0} {
2760 unstage_helper \
2761 {Unstaging selected files from commit} \
2762 [array names selected_paths]
2763 } elseif {$current_diff_path ne {}} {
2764 unstage_helper \
2765 "Unstaging [short_path $current_diff_path] from commit" \
2766 [list $current_diff_path]
2767 }
2768}
2769
2770proc add_helper {txt paths} {
2771 global file_states current_diff_path
2772
2773 if {![lock_index begin-update]} return
2774
2775 set pathList [list]
2776 set after {}
2777 foreach path $paths {
2778 switch -glob -- [lindex $file_states($path) 0] {
2779 _O -
2780 ?M -
2781 ?D -
2782 U? {
2783 lappend pathList $path
2784 if {$path eq $current_diff_path} {
2785 set after {reshow_diff;}
2786 }
2787 }
2788 }
2789 }
2790 if {$pathList eq {}} {
2791 unlock_index
2792 } else {
2793 update_index \
2794 $txt \
2795 $pathList \
2796 [concat $after {set ui_status_value {Ready to commit.}}]
2797 }
2798}
2799
2800proc do_add_selection {} {
2801 global current_diff_path selected_paths
2802
2803 if {[array size selected_paths] > 0} {
2804 add_helper \
2805 {Adding selected files} \
2806 [array names selected_paths]
2807 } elseif {$current_diff_path ne {}} {
2808 add_helper \
2809 "Adding [short_path $current_diff_path]" \
2810 [list $current_diff_path]
2811 }
2812}
2813
2814proc do_add_all {} {
2815 global file_states
2816
2817 set paths [list]
2818 foreach path [array names file_states] {
2819 switch -glob -- [lindex $file_states($path) 0] {
2820 U? {continue}
2821 ?M -
2822 ?D {lappend paths $path}
2823 }
2824 }
2825 add_helper {Adding all changed files} $paths
2826}
2827
2828proc revert_helper {txt paths} {
2829 global file_states current_diff_path
2830
2831 if {![lock_index begin-update]} return
2832
2833 set pathList [list]
2834 set after {}
2835 foreach path $paths {
2836 switch -glob -- [lindex $file_states($path) 0] {
2837 U? {continue}
2838 ?M -
2839 ?D {
2840 lappend pathList $path
2841 if {$path eq $current_diff_path} {
2842 set after {reshow_diff;}
2843 }
2844 }
2845 }
2846 }
2847
2848 set n [llength $pathList]
2849 if {$n == 0} {
2850 unlock_index
2851 return
2852 } elseif {$n == 1} {
2853 set s "[short_path [lindex $pathList]]"
2854 } else {
2855 set s "these $n files"
2856 }
2857
2858 set reply [tk_dialog \
2859 .confirm_revert \
2860 "[appname] ([reponame])" \
2861 "Revert changes in $s?
2862
2863Any unadded changes will be permanently lost by the revert." \
2864 question \
2865 1 \
2866 {Do Nothing} \
2867 {Revert Changes} \
2868 ]
2869 if {$reply == 1} {
2870 checkout_index \
2871 $txt \
2872 $pathList \
2873 [concat $after {set ui_status_value {Ready.}}]
2874 } else {
2875 unlock_index
2876 }
2877}
2878
2879proc do_revert_selection {} {
2880 global current_diff_path selected_paths
2881
2882 if {[array size selected_paths] > 0} {
2883 revert_helper \
2884 {Reverting selected files} \
2885 [array names selected_paths]
2886 } elseif {$current_diff_path ne {}} {
2887 revert_helper \
2888 "Reverting [short_path $current_diff_path]" \
2889 [list $current_diff_path]
2890 }
2891}
2892
2893proc do_signoff {} {
2894 global ui_comm
2895
2896 set me [committer_ident]
2897 if {$me eq {}} return
2898
2899 set sob "Signed-off-by: $me"
2900 set last [$ui_comm get {end -1c linestart} {end -1c}]
2901 if {$last ne $sob} {
2902 $ui_comm edit separator
2903 if {$last ne {}
2904 && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
2905 $ui_comm insert end "\n"
2906 }
2907 $ui_comm insert end "\n$sob"
2908 $ui_comm edit separator
2909 $ui_comm see end
2910 }
2911}
2912
2913proc do_select_commit_type {} {
2914 global commit_type selected_commit_type
2915
2916 if {$selected_commit_type eq {new}
2917 && [string match amend* $commit_type]} {
2918 create_new_commit
2919 } elseif {$selected_commit_type eq {amend}
2920 && ![string match amend* $commit_type]} {
2921 load_last_commit
2922
2923 # The amend request was rejected...
2924 #
2925 if {![string match amend* $commit_type]} {
2926 set selected_commit_type new
2927 }
2928 }
2929}
2930
2931proc do_commit {} {
2932 commit_tree
2933}
2934
2935proc do_about {} {
2936 global appvers copyright
2937 global tcl_patchLevel tk_patchLevel
2938
2939 set w .about_dialog
2940 toplevel $w
2941 wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
2942
2943 label $w.header -text "About [appname]" \
2944 -font font_uibold
2945 pack $w.header -side top -fill x
2946
2947 frame $w.buttons
2948 button $w.buttons.close -text {Close} \
2949 -font font_ui \
2950 -command [list destroy $w]
2951 pack $w.buttons.close -side right
2952 pack $w.buttons -side bottom -fill x -pady 10 -padx 10
2953
2954 label $w.desc \
2955 -text "[appname] - a commit creation tool for Git.
2956$copyright" \
2957 -padx 5 -pady 5 \
2958 -justify left \
2959 -anchor w \
2960 -borderwidth 1 \
2961 -relief solid \
2962 -font font_ui
2963 pack $w.desc -side top -fill x -padx 5 -pady 5
2964
2965 set v {}
2966 append v "[appname] version $appvers\n"
2967 append v "[exec git version]\n"
2968 append v "\n"
2969 if {$tcl_patchLevel eq $tk_patchLevel} {
2970 append v "Tcl/Tk version $tcl_patchLevel"
2971 } else {
2972 append v "Tcl version $tcl_patchLevel"
2973 append v ", Tk version $tk_patchLevel"
2974 }
2975
2976 label $w.vers \
2977 -text $v \
2978 -padx 5 -pady 5 \
2979 -justify left \
2980 -anchor w \
2981 -borderwidth 1 \
2982 -relief solid \
2983 -font font_ui
2984 pack $w.vers -side top -fill x -padx 5 -pady 5
2985
2986 menu $w.ctxm -tearoff 0
2987 $w.ctxm add command \
2988 -label {Copy} \
2989 -font font_ui \
2990 -command "
2991 clipboard clear
2992 clipboard append -format STRING -type STRING -- \[$w.vers cget -text\]
2993 "
2994
2995 bind $w <Visibility> "grab $w; focus $w"
2996 bind $w <Key-Escape> "destroy $w"
2997 bind_button3 $w.vers "tk_popup $w.ctxm %X %Y; grab $w; focus $w"
2998 wm title $w "About [appname]"
2999 tkwait window $w
3000}
3001
3002proc do_options {} {
3003 global repo_config global_config font_descs
3004 global repo_config_new global_config_new
3005
3006 array unset repo_config_new
3007 array unset global_config_new
3008 foreach name [array names repo_config] {
3009 set repo_config_new($name) $repo_config($name)
3010 }
3011 load_config 1
3012 foreach name [array names repo_config] {
3013 switch -- $name {
3014 gui.diffcontext {continue}
3015 }
3016 set repo_config_new($name) $repo_config($name)
3017 }
3018 foreach name [array names global_config] {
3019 set global_config_new($name) $global_config($name)
3020 }
3021
3022 set w .options_editor
3023 toplevel $w
3024 wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
3025
3026 label $w.header -text "[appname] Options" \
3027 -font font_uibold
3028 pack $w.header -side top -fill x
3029
3030 frame $w.buttons
3031 button $w.buttons.restore -text {Restore Defaults} \
3032 -font font_ui \
3033 -command do_restore_defaults
3034 pack $w.buttons.restore -side left
3035 button $w.buttons.save -text Save \
3036 -font font_ui \
3037 -command "
3038 catch {eval \[bind \[focus -displayof $w\] <FocusOut>\]}
3039 do_save_config $w
3040 "
3041 pack $w.buttons.save -side right
3042 button $w.buttons.cancel -text {Cancel} \
3043 -font font_ui \
3044 -command [list destroy $w]
3045 pack $w.buttons.cancel -side right -padx 5
3046 pack $w.buttons -side bottom -fill x -pady 10 -padx 10
3047
3048 labelframe $w.repo -text "[reponame] Repository" \
3049 -font font_ui \
3050 -relief raised -borderwidth 2
3051 labelframe $w.global -text {Global (All Repositories)} \
3052 -font font_ui \
3053 -relief raised -borderwidth 2
3054 pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5
3055 pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5
3056
3057 foreach option {
3058 {b pullsummary {Show Pull Summary}}
3059 {b trustmtime {Trust File Modification Timestamps}}
3060 {i diffcontext {Number of Diff Context Lines}}
3061 {t newbranchtemplate {New Branch Name Template}}
3062 } {
3063 set type [lindex $option 0]
3064 set name [lindex $option 1]
3065 set text [lindex $option 2]
3066 foreach f {repo global} {
3067 switch $type {
3068 b {
3069 checkbutton $w.$f.$name -text $text \
3070 -variable ${f}_config_new(gui.$name) \
3071 -onvalue true \
3072 -offvalue false \
3073 -font font_ui
3074 pack $w.$f.$name -side top -anchor w
3075 }
3076 i {
3077 frame $w.$f.$name
3078 label $w.$f.$name.l -text "$text:" -font font_ui
3079 pack $w.$f.$name.l -side left -anchor w -fill x
3080 spinbox $w.$f.$name.v \
3081 -textvariable ${f}_config_new(gui.$name) \
3082 -from 1 -to 99 -increment 1 \
3083 -width 3 \
3084 -font font_ui
3085 bind $w.$f.$name.v <FocusIn> {%W selection range 0 end}
3086 pack $w.$f.$name.v -side right -anchor e -padx 5
3087 pack $w.$f.$name -side top -anchor w -fill x
3088 }
3089 t {
3090 frame $w.$f.$name
3091 label $w.$f.$name.l -text "$text:" -font font_ui
3092 text $w.$f.$name.v \
3093 -borderwidth 1 \
3094 -relief sunken \
3095 -height 1 \
3096 -width 20 \
3097 -font font_ui
3098 $w.$f.$name.v insert 0.0 [set ${f}_config_new(gui.$name)]
3099 bind $w.$f.$name.v <Shift-Key-Tab> {focus [tk_focusPrev %W];break}
3100 bind $w.$f.$name.v <Key-Tab> {focus [tk_focusNext %W];break}
3101 bind $w.$f.$name.v <Key-Return> break
3102 bind $w.$f.$name.v <FocusIn> "$w.$f.$name.v tag add sel 0.0 end"
3103 bind $w.$f.$name.v <FocusOut> "
3104 set ${f}_config_new(gui.$name) \
3105 \[string trim \[$w.$f.$name.v get 0.0 end\]\]
3106 "
3107 pack $w.$f.$name.l -side left -anchor w
3108 pack $w.$f.$name.v -side left -anchor w \
3109 -fill x -expand 1 \
3110 -padx 5
3111 pack $w.$f.$name -side top -anchor w -fill x
3112 }
3113 }
3114 }
3115 }
3116
3117 set all_fonts [lsort [font families]]
3118 foreach option $font_descs {
3119 set name [lindex $option 0]
3120 set font [lindex $option 1]
3121 set text [lindex $option 2]
3122
3123 set global_config_new(gui.$font^^family) \
3124 [font configure $font -family]
3125 set global_config_new(gui.$font^^size) \
3126 [font configure $font -size]
3127
3128 frame $w.global.$name
3129 label $w.global.$name.l -text "$text:" -font font_ui
3130 pack $w.global.$name.l -side left -anchor w -fill x
3131 eval tk_optionMenu $w.global.$name.family \
3132 global_config_new(gui.$font^^family) \
3133 $all_fonts
3134 spinbox $w.global.$name.size \
3135 -textvariable global_config_new(gui.$font^^size) \
3136 -from 2 -to 80 -increment 1 \
3137 -width 3 \
3138 -font font_ui
3139 bind $w.global.$name.size <FocusIn> {%W selection range 0 end}
3140 pack $w.global.$name.size -side right -anchor e
3141 pack $w.global.$name.family -side right -anchor e
3142 pack $w.global.$name -side top -anchor w -fill x
3143 }
3144
3145 bind $w <Visibility> "grab $w; focus $w"
3146 bind $w <Key-Escape> "destroy $w"
3147 wm title $w "[appname] ([reponame]): Options"
3148 tkwait window $w
3149}
3150
3151proc do_restore_defaults {} {
3152 global font_descs default_config repo_config
3153 global repo_config_new global_config_new
3154
3155 foreach name [array names default_config] {
3156 set repo_config_new($name) $default_config($name)
3157 set global_config_new($name) $default_config($name)
3158 }
3159
3160 foreach option $font_descs {
3161 set name [lindex $option 0]
3162 set repo_config(gui.$name) $default_config(gui.$name)
3163 }
3164 apply_config
3165
3166 foreach option $font_descs {
3167 set name [lindex $option 0]
3168 set font [lindex $option 1]
3169 set global_config_new(gui.$font^^family) \
3170 [font configure $font -family]
3171 set global_config_new(gui.$font^^size) \
3172 [font configure $font -size]
3173 }
3174}
3175
3176proc do_save_config {w} {
3177 if {[catch {save_config} err]} {
3178 error_popup "Failed to completely save options:\n\n$err"
3179 }
3180 reshow_diff
3181 destroy $w
3182}
3183
3184proc do_windows_shortcut {} {
3185 global argv0
3186
3187 if {[catch {
3188 set desktop [exec cygpath \
3189 --windows \
3190 --absolute \
3191 --long-name \
3192 --desktop]
3193 }]} {
3194 set desktop .
3195 }
3196 set fn [tk_getSaveFile \
3197 -parent . \
3198 -title "[appname] ([reponame]): Create Desktop Icon" \
3199 -initialdir $desktop \
3200 -initialfile "Git [reponame].bat"]
3201 if {$fn != {}} {
3202 if {[catch {
3203 set fd [open $fn w]
3204 set sh [exec cygpath \
3205 --windows \
3206 --absolute \
3207 /bin/sh]
3208 set me [exec cygpath \
3209 --unix \
3210 --absolute \
3211 $argv0]
3212 set gd [exec cygpath \
3213 --unix \
3214 --absolute \
3215 [gitdir]]
3216 set gw [exec cygpath \
3217 --windows \
3218 --absolute \
3219 [file dirname [gitdir]]]
3220 regsub -all ' $me "'\\''" me
3221 regsub -all ' $gd "'\\''" gd
3222 puts $fd "@ECHO Entering $gw"
3223 puts $fd "@ECHO Starting git-gui... please wait..."
3224 puts -nonewline $fd "@\"$sh\" --login -c \""
3225 puts -nonewline $fd "GIT_DIR='$gd'"
3226 puts -nonewline $fd " '$me'"
3227 puts $fd "&\""
3228 close $fd
3229 } err]} {
3230 error_popup "Cannot write script:\n\n$err"
3231 }
3232 }
3233}
3234
3235proc do_macosx_app {} {
3236 global argv0 env
3237
3238 set fn [tk_getSaveFile \
3239 -parent . \
3240 -title "[appname] ([reponame]): Create Desktop Icon" \
3241 -initialdir [file join $env(HOME) Desktop] \
3242 -initialfile "Git [reponame].app"]
3243 if {$fn != {}} {
3244 if {[catch {
3245 set Contents [file join $fn Contents]
3246 set MacOS [file join $Contents MacOS]
3247 set exe [file join $MacOS git-gui]
3248
3249 file mkdir $MacOS
3250
3251 set fd [open [file join $Contents Info.plist] w]
3252 puts $fd {<?xml version="1.0" encoding="UTF-8"?>
3253<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3254<plist version="1.0">
3255<dict>
3256 <key>CFBundleDevelopmentRegion</key>
3257 <string>English</string>
3258 <key>CFBundleExecutable</key>
3259 <string>git-gui</string>
3260 <key>CFBundleIdentifier</key>
3261 <string>org.spearce.git-gui</string>
3262 <key>CFBundleInfoDictionaryVersion</key>
3263 <string>6.0</string>
3264 <key>CFBundlePackageType</key>
3265 <string>APPL</string>
3266 <key>CFBundleSignature</key>
3267 <string>????</string>
3268 <key>CFBundleVersion</key>
3269 <string>1.0</string>
3270 <key>NSPrincipalClass</key>
3271 <string>NSApplication</string>
3272</dict>
3273</plist>}
3274 close $fd
3275
3276 set fd [open $exe w]
3277 set gd [file normalize [gitdir]]
3278 set ep [file normalize [exec git --exec-path]]
3279 regsub -all ' $gd "'\\''" gd
3280 regsub -all ' $ep "'\\''" ep
3281 puts $fd "#!/bin/sh"
3282 foreach name [array names env] {
3283 if {[string match GIT_* $name]} {
3284 regsub -all ' $env($name) "'\\''" v
3285 puts $fd "export $name='$v'"
3286 }
3287 }
3288 puts $fd "export PATH='$ep':\$PATH"
3289 puts $fd "export GIT_DIR='$gd'"
3290 puts $fd "exec [file normalize $argv0]"
3291 close $fd
3292
3293 file attributes $exe -permissions u+x,g+x,o+x
3294 } err]} {
3295 error_popup "Cannot write icon:\n\n$err"
3296 }
3297 }
3298}
3299
3300proc toggle_or_diff {w x y} {
3301 global file_states file_lists current_diff_path ui_index ui_workdir
3302 global last_clicked selected_paths
3303
3304 set pos [split [$w index @$x,$y] .]
3305 set lno [lindex $pos 0]
3306 set col [lindex $pos 1]
3307 set path [lindex $file_lists($w) [expr {$lno - 1}]]
3308 if {$path eq {}} {
3309 set last_clicked {}
3310 return
3311 }
3312
3313 set last_clicked [list $w $lno]
3314 array unset selected_paths
3315 $ui_index tag remove in_sel 0.0 end
3316 $ui_workdir tag remove in_sel 0.0 end
3317
3318 if {$col == 0} {
3319 if {$current_diff_path eq $path} {
3320 set after {reshow_diff;}
3321 } else {
3322 set after {}
3323 }
3324 if {$w eq $ui_index} {
3325 update_indexinfo \
3326 "Unstaging [short_path $path] from commit" \
3327 [list $path] \
3328 [concat $after {set ui_status_value {Ready.}}]
3329 } elseif {$w eq $ui_workdir} {
3330 update_index \
3331 "Adding [short_path $path]" \
3332 [list $path] \
3333 [concat $after {set ui_status_value {Ready.}}]
3334 }
3335 } else {
3336 show_diff $path $w $lno
3337 }
3338}
3339
3340proc add_one_to_selection {w x y} {
3341 global file_lists last_clicked selected_paths
3342
3343 set lno [lindex [split [$w index @$x,$y] .] 0]
3344 set path [lindex $file_lists($w) [expr {$lno - 1}]]
3345 if {$path eq {}} {
3346 set last_clicked {}
3347 return
3348 }
3349
3350 if {$last_clicked ne {}
3351 && [lindex $last_clicked 0] ne $w} {
3352 array unset selected_paths
3353 [lindex $last_clicked 0] tag remove in_sel 0.0 end
3354 }
3355
3356 set last_clicked [list $w $lno]
3357 if {[catch {set in_sel $selected_paths($path)}]} {
3358 set in_sel 0
3359 }
3360 if {$in_sel} {
3361 unset selected_paths($path)
3362 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
3363 } else {
3364 set selected_paths($path) 1
3365 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
3366 }
3367}
3368
3369proc add_range_to_selection {w x y} {
3370 global file_lists last_clicked selected_paths
3371
3372 if {[lindex $last_clicked 0] ne $w} {
3373 toggle_or_diff $w $x $y
3374 return
3375 }
3376
3377 set lno [lindex [split [$w index @$x,$y] .] 0]
3378 set lc [lindex $last_clicked 1]
3379 if {$lc < $lno} {
3380 set begin $lc
3381 set end $lno
3382 } else {
3383 set begin $lno
3384 set end $lc
3385 }
3386
3387 foreach path [lrange $file_lists($w) \
3388 [expr {$begin - 1}] \
3389 [expr {$end - 1}]] {
3390 set selected_paths($path) 1
3391 }
3392 $w tag add in_sel $begin.0 [expr {$end + 1}].0
3393}
3394
3395######################################################################
3396##
3397## config defaults
3398
3399set cursor_ptr arrow
3400font create font_diff -family Courier -size 10
3401font create font_ui
3402catch {
3403 label .dummy
3404 eval font configure font_ui [font actual [.dummy cget -font]]
3405 destroy .dummy
3406}
3407
3408font create font_uibold
3409font create font_diffbold
3410
3411if {[is_Windows]} {
3412 set M1B Control
3413 set M1T Ctrl
3414} elseif {[is_MacOSX]} {
3415 set M1B M1
3416 set M1T Cmd
3417} else {
3418 set M1B M1
3419 set M1T M1
3420}
3421
3422proc apply_config {} {
3423 global repo_config font_descs
3424
3425 foreach option $font_descs {
3426 set name [lindex $option 0]
3427 set font [lindex $option 1]
3428 if {[catch {
3429 foreach {cn cv} $repo_config(gui.$name) {
3430 font configure $font $cn $cv
3431 }
3432 } err]} {
3433 error_popup "Invalid font specified in gui.$name:\n\n$err"
3434 }
3435 foreach {cn cv} [font configure $font] {
3436 font configure ${font}bold $cn $cv
3437 }
3438 font configure ${font}bold -weight bold
3439 }
3440}
3441
3442set default_config(gui.trustmtime) false
3443set default_config(gui.pullsummary) true
3444set default_config(gui.diffcontext) 5
3445set default_config(gui.newbranchtemplate) {}
3446set default_config(gui.fontui) [font configure font_ui]
3447set default_config(gui.fontdiff) [font configure font_diff]
3448set font_descs {
3449 {fontui font_ui {Main Font}}
3450 {fontdiff font_diff {Diff/Console Font}}
3451}
3452load_config 0
3453apply_config
3454
3455######################################################################
3456##
3457## ui construction
3458
3459# -- Menu Bar
3460#
3461menu .mbar -tearoff 0
3462.mbar add cascade -label Repository -menu .mbar.repository
3463.mbar add cascade -label Edit -menu .mbar.edit
3464if {!$single_commit} {
3465 .mbar add cascade -label Branch -menu .mbar.branch
3466}
3467.mbar add cascade -label Commit -menu .mbar.commit
3468if {!$single_commit} {
3469 .mbar add cascade -label Fetch -menu .mbar.fetch
3470 .mbar add cascade -label Pull -menu .mbar.pull
3471 .mbar add cascade -label Push -menu .mbar.push
3472}
3473. configure -menu .mbar
3474
3475# -- Repository Menu
3476#
3477menu .mbar.repository
3478.mbar.repository add command \
3479 -label {Visualize Current Branch} \
3480 -command {do_gitk {}} \
3481 -font font_ui
3482if {![is_MacOSX]} {
3483 .mbar.repository add command \
3484 -label {Visualize All Branches} \
3485 -command {do_gitk {--all}} \
3486 -font font_ui
3487}
3488.mbar.repository add separator
3489
3490if {!$single_commit} {
3491 .mbar.repository add command -label {Compress Database} \
3492 -command do_gc \
3493 -font font_ui
3494
3495 .mbar.repository add command -label {Verify Database} \
3496 -command do_fsck_objects \
3497 -font font_ui
3498
3499 .mbar.repository add separator
3500
3501 if {[is_Windows]} {
3502 .mbar.repository add command \
3503 -label {Create Desktop Icon} \
3504 -command do_windows_shortcut \
3505 -font font_ui
3506 } elseif {[is_MacOSX]} {
3507 .mbar.repository add command \
3508 -label {Create Desktop Icon} \
3509 -command do_macosx_app \
3510 -font font_ui
3511 }
3512}
3513
3514.mbar.repository add command -label Quit \
3515 -command do_quit \
3516 -accelerator $M1T-Q \
3517 -font font_ui
3518
3519# -- Edit Menu
3520#
3521menu .mbar.edit
3522.mbar.edit add command -label Undo \
3523 -command {catch {[focus] edit undo}} \
3524 -accelerator $M1T-Z \
3525 -font font_ui
3526.mbar.edit add command -label Redo \
3527 -command {catch {[focus] edit redo}} \
3528 -accelerator $M1T-Y \
3529 -font font_ui
3530.mbar.edit add separator
3531.mbar.edit add command -label Cut \
3532 -command {catch {tk_textCut [focus]}} \
3533 -accelerator $M1T-X \
3534 -font font_ui
3535.mbar.edit add command -label Copy \
3536 -command {catch {tk_textCopy [focus]}} \
3537 -accelerator $M1T-C \
3538 -font font_ui
3539.mbar.edit add command -label Paste \
3540 -command {catch {tk_textPaste [focus]; [focus] see insert}} \
3541 -accelerator $M1T-V \
3542 -font font_ui
3543.mbar.edit add command -label Delete \
3544 -command {catch {[focus] delete sel.first sel.last}} \
3545 -accelerator Del \
3546 -font font_ui
3547.mbar.edit add separator
3548.mbar.edit add command -label {Select All} \
3549 -command {catch {[focus] tag add sel 0.0 end}} \
3550 -accelerator $M1T-A \
3551 -font font_ui
3552
3553# -- Branch Menu
3554#
3555if {!$single_commit} {
3556 menu .mbar.branch
3557
3558 .mbar.branch add command -label {Create...} \
3559 -command do_create_branch \
3560 -accelerator $M1T-N \
3561 -font font_ui
3562 lappend disable_on_lock [list .mbar.branch entryconf \
3563 [.mbar.branch index last] -state]
3564
3565 .mbar.branch add command -label {Delete...} \
3566 -command do_delete_branch \
3567 -font font_ui
3568 lappend disable_on_lock [list .mbar.branch entryconf \
3569 [.mbar.branch index last] -state]
3570}
3571
3572# -- Commit Menu
3573#
3574menu .mbar.commit
3575
3576.mbar.commit add radiobutton \
3577 -label {New Commit} \
3578 -command do_select_commit_type \
3579 -variable selected_commit_type \
3580 -value new \
3581 -font font_ui
3582lappend disable_on_lock \
3583 [list .mbar.commit entryconf [.mbar.commit index last] -state]
3584
3585.mbar.commit add radiobutton \
3586 -label {Amend Last Commit} \
3587 -command do_select_commit_type \
3588 -variable selected_commit_type \
3589 -value amend \
3590 -font font_ui
3591lappend disable_on_lock \
3592 [list .mbar.commit entryconf [.mbar.commit index last] -state]
3593
3594.mbar.commit add separator
3595
3596.mbar.commit add command -label Rescan \
3597 -command do_rescan \
3598 -accelerator F5 \
3599 -font font_ui
3600lappend disable_on_lock \
3601 [list .mbar.commit entryconf [.mbar.commit index last] -state]
3602
3603.mbar.commit add command -label {Add To Commit} \
3604 -command do_add_selection \
3605 -font font_ui
3606lappend disable_on_lock \
3607 [list .mbar.commit entryconf [.mbar.commit index last] -state]
3608
3609.mbar.commit add command -label {Add All To Commit} \
3610 -command do_add_all \
3611 -accelerator $M1T-I \
3612 -font font_ui
3613lappend disable_on_lock \
3614 [list .mbar.commit entryconf [.mbar.commit index last] -state]
3615
3616.mbar.commit add command -label {Unstage From Commit} \
3617 -command do_unstage_selection \
3618 -font font_ui
3619lappend disable_on_lock \
3620 [list .mbar.commit entryconf [.mbar.commit index last] -state]
3621
3622.mbar.commit add command -label {Revert Changes} \
3623 -command do_revert_selection \
3624 -font font_ui
3625lappend disable_on_lock \
3626 [list .mbar.commit entryconf [.mbar.commit index last] -state]
3627
3628.mbar.commit add separator
3629
3630.mbar.commit add command -label {Sign Off} \
3631 -command do_signoff \
3632 -accelerator $M1T-S \
3633 -font font_ui
3634
3635.mbar.commit add command -label Commit \
3636 -command do_commit \
3637 -accelerator $M1T-Return \
3638 -font font_ui
3639lappend disable_on_lock \
3640 [list .mbar.commit entryconf [.mbar.commit index last] -state]
3641
3642# -- Transport menus
3643#
3644if {!$single_commit} {
3645 menu .mbar.fetch
3646 menu .mbar.pull
3647 menu .mbar.push
3648}
3649
3650if {[is_MacOSX]} {
3651 # -- Apple Menu (Mac OS X only)
3652 #
3653 .mbar add cascade -label Apple -menu .mbar.apple
3654 menu .mbar.apple
3655
3656 .mbar.apple add command -label "About [appname]" \
3657 -command do_about \
3658 -font font_ui
3659 .mbar.apple add command -label "[appname] Options..." \
3660 -command do_options \
3661 -font font_ui
3662} else {
3663 # -- Edit Menu
3664 #
3665 .mbar.edit add separator
3666 .mbar.edit add command -label {Options...} \
3667 -command do_options \
3668 -font font_ui
3669
3670 # -- Tools Menu
3671 #
3672 if {[file exists /usr/local/miga/lib/gui-miga]
3673 && [file exists .pvcsrc]} {
3674 proc do_miga {} {
3675 global ui_status_value
3676 if {![lock_index update]} return
3677 set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""]
3678 set miga_fd [open "|$cmd" r]
3679 fconfigure $miga_fd -blocking 0
3680 fileevent $miga_fd readable [list miga_done $miga_fd]
3681 set ui_status_value {Running miga...}
3682 }
3683 proc miga_done {fd} {
3684 read $fd 512
3685 if {[eof $fd]} {
3686 close $fd
3687 unlock_index
3688 rescan [list set ui_status_value {Ready.}]
3689 }
3690 }
3691 .mbar add cascade -label Tools -menu .mbar.tools
3692 menu .mbar.tools
3693 .mbar.tools add command -label "Migrate" \
3694 -command do_miga \
3695 -font font_ui
3696 lappend disable_on_lock \
3697 [list .mbar.tools entryconf [.mbar.tools index last] -state]
3698 }
3699
3700 # -- Help Menu
3701 #
3702 .mbar add cascade -label Help -menu .mbar.help
3703 menu .mbar.help
3704
3705 .mbar.help add command -label "About [appname]" \
3706 -command do_about \
3707 -font font_ui
3708}
3709
3710
3711# -- Branch Control
3712#
3713frame .branch \
3714 -borderwidth 1 \
3715 -relief sunken
3716label .branch.l1 \
3717 -text {Current Branch:} \
3718 -anchor w \
3719 -justify left \
3720 -font font_ui
3721label .branch.cb \
3722 -textvariable current_branch \
3723 -anchor w \
3724 -justify left \
3725 -font font_ui
3726pack .branch.l1 -side left
3727pack .branch.cb -side left -fill x
3728pack .branch -side top -fill x
3729
3730# -- Main Window Layout
3731#
3732panedwindow .vpane -orient vertical
3733panedwindow .vpane.files -orient horizontal
3734.vpane add .vpane.files -sticky nsew -height 100 -width 200
3735pack .vpane -anchor n -side top -fill both -expand 1
3736
3737# -- Index File List
3738#
3739frame .vpane.files.index -height 100 -width 200
3740label .vpane.files.index.title -text {Changes To Be Committed} \
3741 -background green \
3742 -font font_ui
3743text $ui_index -background white -borderwidth 0 \
3744 -width 20 -height 10 \
3745 -wrap none \
3746 -font font_ui \
3747 -cursor $cursor_ptr \
3748 -xscrollcommand {.vpane.files.index.sx set} \
3749 -yscrollcommand {.vpane.files.index.sy set} \
3750 -state disabled
3751scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
3752scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
3753pack .vpane.files.index.title -side top -fill x
3754pack .vpane.files.index.sx -side bottom -fill x
3755pack .vpane.files.index.sy -side right -fill y
3756pack $ui_index -side left -fill both -expand 1
3757.vpane.files add .vpane.files.index -sticky nsew
3758
3759# -- Working Directory File List
3760#
3761frame .vpane.files.workdir -height 100 -width 200
3762label .vpane.files.workdir.title -text {Changed But Not Updated} \
3763 -background red \
3764 -font font_ui
3765text $ui_workdir -background white -borderwidth 0 \
3766 -width 20 -height 10 \
3767 -wrap none \
3768 -font font_ui \
3769 -cursor $cursor_ptr \
3770 -xscrollcommand {.vpane.files.workdir.sx set} \
3771 -yscrollcommand {.vpane.files.workdir.sy set} \
3772 -state disabled
3773scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
3774scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
3775pack .vpane.files.workdir.title -side top -fill x
3776pack .vpane.files.workdir.sx -side bottom -fill x
3777pack .vpane.files.workdir.sy -side right -fill y
3778pack $ui_workdir -side left -fill both -expand 1
3779.vpane.files add .vpane.files.workdir -sticky nsew
3780
3781foreach i [list $ui_index $ui_workdir] {
3782 $i tag conf in_diff -font font_uibold
3783 $i tag conf in_sel \
3784 -background [$i cget -foreground] \
3785 -foreground [$i cget -background]
3786}
3787unset i
3788
3789# -- Diff and Commit Area
3790#
3791frame .vpane.lower -height 300 -width 400
3792frame .vpane.lower.commarea
3793frame .vpane.lower.diff -relief sunken -borderwidth 1
3794pack .vpane.lower.commarea -side top -fill x
3795pack .vpane.lower.diff -side bottom -fill both -expand 1
3796.vpane add .vpane.lower -stick nsew
3797
3798# -- Commit Area Buttons
3799#
3800frame .vpane.lower.commarea.buttons
3801label .vpane.lower.commarea.buttons.l -text {} \
3802 -anchor w \
3803 -justify left \
3804 -font font_ui
3805pack .vpane.lower.commarea.buttons.l -side top -fill x
3806pack .vpane.lower.commarea.buttons -side left -fill y
3807
3808button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
3809 -command do_rescan \
3810 -font font_ui
3811pack .vpane.lower.commarea.buttons.rescan -side top -fill x
3812lappend disable_on_lock \
3813 {.vpane.lower.commarea.buttons.rescan conf -state}
3814
3815button .vpane.lower.commarea.buttons.incall -text {Add All} \
3816 -command do_add_all \
3817 -font font_ui
3818pack .vpane.lower.commarea.buttons.incall -side top -fill x
3819lappend disable_on_lock \
3820 {.vpane.lower.commarea.buttons.incall conf -state}
3821
3822button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
3823 -command do_signoff \
3824 -font font_ui
3825pack .vpane.lower.commarea.buttons.signoff -side top -fill x
3826
3827button .vpane.lower.commarea.buttons.commit -text {Commit} \
3828 -command do_commit \
3829 -font font_ui
3830pack .vpane.lower.commarea.buttons.commit -side top -fill x
3831lappend disable_on_lock \
3832 {.vpane.lower.commarea.buttons.commit conf -state}
3833
3834# -- Commit Message Buffer
3835#
3836frame .vpane.lower.commarea.buffer
3837frame .vpane.lower.commarea.buffer.header
3838set ui_comm .vpane.lower.commarea.buffer.t
3839set ui_coml .vpane.lower.commarea.buffer.header.l
3840radiobutton .vpane.lower.commarea.buffer.header.new \
3841 -text {New Commit} \
3842 -command do_select_commit_type \
3843 -variable selected_commit_type \
3844 -value new \
3845 -font font_ui
3846lappend disable_on_lock \
3847 [list .vpane.lower.commarea.buffer.header.new conf -state]
3848radiobutton .vpane.lower.commarea.buffer.header.amend \
3849 -text {Amend Last Commit} \
3850 -command do_select_commit_type \
3851 -variable selected_commit_type \
3852 -value amend \
3853 -font font_ui
3854lappend disable_on_lock \
3855 [list .vpane.lower.commarea.buffer.header.amend conf -state]
3856label $ui_coml \
3857 -anchor w \
3858 -justify left \
3859 -font font_ui
3860proc trace_commit_type {varname args} {
3861 global ui_coml commit_type
3862 switch -glob -- $commit_type {
3863 initial {set txt {Initial Commit Message:}}
3864 amend {set txt {Amended Commit Message:}}
3865 amend-initial {set txt {Amended Initial Commit Message:}}
3866 amend-merge {set txt {Amended Merge Commit Message:}}
3867 merge {set txt {Merge Commit Message:}}
3868 * {set txt {Commit Message:}}
3869 }
3870 $ui_coml conf -text $txt
3871}
3872trace add variable commit_type write trace_commit_type
3873pack $ui_coml -side left -fill x
3874pack .vpane.lower.commarea.buffer.header.amend -side right
3875pack .vpane.lower.commarea.buffer.header.new -side right
3876
3877text $ui_comm -background white -borderwidth 1 \
3878 -undo true \
3879 -maxundo 20 \
3880 -autoseparators true \
3881 -relief sunken \
3882 -width 75 -height 9 -wrap none \
3883 -font font_diff \
3884 -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
3885scrollbar .vpane.lower.commarea.buffer.sby \
3886 -command [list $ui_comm yview]
3887pack .vpane.lower.commarea.buffer.header -side top -fill x
3888pack .vpane.lower.commarea.buffer.sby -side right -fill y
3889pack $ui_comm -side left -fill y
3890pack .vpane.lower.commarea.buffer -side left -fill y
3891
3892# -- Commit Message Buffer Context Menu
3893#
3894set ctxm .vpane.lower.commarea.buffer.ctxm
3895menu $ctxm -tearoff 0
3896$ctxm add command \
3897 -label {Cut} \
3898 -font font_ui \
3899 -command {tk_textCut $ui_comm}
3900$ctxm add command \
3901 -label {Copy} \
3902 -font font_ui \
3903 -command {tk_textCopy $ui_comm}
3904$ctxm add command \
3905 -label {Paste} \
3906 -font font_ui \
3907 -command {tk_textPaste $ui_comm}
3908$ctxm add command \
3909 -label {Delete} \
3910 -font font_ui \
3911 -command {$ui_comm delete sel.first sel.last}
3912$ctxm add separator
3913$ctxm add command \
3914 -label {Select All} \
3915 -font font_ui \
3916 -command {$ui_comm tag add sel 0.0 end}
3917$ctxm add command \
3918 -label {Copy All} \
3919 -font font_ui \
3920 -command {
3921 $ui_comm tag add sel 0.0 end
3922 tk_textCopy $ui_comm
3923 $ui_comm tag remove sel 0.0 end
3924 }
3925$ctxm add separator
3926$ctxm add command \
3927 -label {Sign Off} \
3928 -font font_ui \
3929 -command do_signoff
3930bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
3931
3932# -- Diff Header
3933#
3934set current_diff_path {}
3935set diff_actions [list]
3936proc trace_current_diff_path {varname args} {
3937 global current_diff_path diff_actions file_states
3938 if {$current_diff_path eq {}} {
3939 set s {}
3940 set f {}
3941 set p {}
3942 set o disabled
3943 } else {
3944 set p $current_diff_path
3945 set s [mapdesc [lindex $file_states($p) 0] $p]
3946 set f {File:}
3947 set p [escape_path $p]
3948 set o normal
3949 }
3950
3951 .vpane.lower.diff.header.status configure -text $s
3952 .vpane.lower.diff.header.file configure -text $f
3953 .vpane.lower.diff.header.path configure -text $p
3954 foreach w $diff_actions {
3955 uplevel #0 $w $o
3956 }
3957}
3958trace add variable current_diff_path write trace_current_diff_path
3959
3960frame .vpane.lower.diff.header -background orange
3961label .vpane.lower.diff.header.status \
3962 -background orange \
3963 -width $max_status_desc \
3964 -anchor w \
3965 -justify left \
3966 -font font_ui
3967label .vpane.lower.diff.header.file \
3968 -background orange \
3969 -anchor w \
3970 -justify left \
3971 -font font_ui
3972label .vpane.lower.diff.header.path \
3973 -background orange \
3974 -anchor w \
3975 -justify left \
3976 -font font_ui
3977pack .vpane.lower.diff.header.status -side left
3978pack .vpane.lower.diff.header.file -side left
3979pack .vpane.lower.diff.header.path -fill x
3980set ctxm .vpane.lower.diff.header.ctxm
3981menu $ctxm -tearoff 0
3982$ctxm add command \
3983 -label {Copy} \
3984 -font font_ui \
3985 -command {
3986 clipboard clear
3987 clipboard append \
3988 -format STRING \
3989 -type STRING \
3990 -- $current_diff_path
3991 }
3992lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3993bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
3994
3995# -- Diff Body
3996#
3997frame .vpane.lower.diff.body
3998set ui_diff .vpane.lower.diff.body.t
3999text $ui_diff -background white -borderwidth 0 \
4000 -width 80 -height 15 -wrap none \
4001 -font font_diff \
4002 -xscrollcommand {.vpane.lower.diff.body.sbx set} \
4003 -yscrollcommand {.vpane.lower.diff.body.sby set} \
4004 -state disabled
4005scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
4006 -command [list $ui_diff xview]
4007scrollbar .vpane.lower.diff.body.sby -orient vertical \
4008 -command [list $ui_diff yview]
4009pack .vpane.lower.diff.body.sbx -side bottom -fill x
4010pack .vpane.lower.diff.body.sby -side right -fill y
4011pack $ui_diff -side left -fill both -expand 1
4012pack .vpane.lower.diff.header -side top -fill x
4013pack .vpane.lower.diff.body -side bottom -fill both -expand 1
4014
4015$ui_diff tag conf d_@ -foreground blue -font font_diffbold
4016$ui_diff tag conf d_+ -foreground {#00a000}
4017$ui_diff tag conf d_- -foreground red
4018
4019$ui_diff tag conf d_++ -foreground {#00a000}
4020$ui_diff tag conf d_-- -foreground red
4021$ui_diff tag conf d_+s \
4022 -foreground {#00a000} \
4023 -background {#e2effa}
4024$ui_diff tag conf d_-s \
4025 -foreground red \
4026 -background {#e2effa}
4027$ui_diff tag conf d_s+ \
4028 -foreground {#00a000} \
4029 -background ivory1
4030$ui_diff tag conf d_s- \
4031 -foreground red \
4032 -background ivory1
4033
4034$ui_diff tag conf d<<<<<<< \
4035 -foreground orange \
4036 -font font_diffbold
4037$ui_diff tag conf d======= \
4038 -foreground orange \
4039 -font font_diffbold
4040$ui_diff tag conf d>>>>>>> \
4041 -foreground orange \
4042 -font font_diffbold
4043
4044$ui_diff tag raise sel
4045
4046# -- Diff Body Context Menu
4047#
4048set ctxm .vpane.lower.diff.body.ctxm
4049menu $ctxm -tearoff 0
4050$ctxm add command \
4051 -label {Refresh} \
4052 -font font_ui \
4053 -command reshow_diff
4054$ctxm add command \
4055 -label {Copy} \
4056 -font font_ui \
4057 -command {tk_textCopy $ui_diff}
4058lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
4059$ctxm add command \
4060 -label {Select All} \
4061 -font font_ui \
4062 -command {$ui_diff tag add sel 0.0 end}
4063lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
4064$ctxm add command \
4065 -label {Copy All} \
4066 -font font_ui \
4067 -command {
4068 $ui_diff tag add sel 0.0 end
4069 tk_textCopy $ui_diff
4070 $ui_diff tag remove sel 0.0 end
4071 }
4072lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
4073$ctxm add separator
4074$ctxm add command \
4075 -label {Decrease Font Size} \
4076 -font font_ui \
4077 -command {incr_font_size font_diff -1}
4078lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
4079$ctxm add command \
4080 -label {Increase Font Size} \
4081 -font font_ui \
4082 -command {incr_font_size font_diff 1}
4083lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
4084$ctxm add separator
4085$ctxm add command \
4086 -label {Show Less Context} \
4087 -font font_ui \
4088 -command {if {$repo_config(gui.diffcontext) >= 2} {
4089 incr repo_config(gui.diffcontext) -1
4090 reshow_diff
4091 }}
4092lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
4093$ctxm add command \
4094 -label {Show More Context} \
4095 -font font_ui \
4096 -command {
4097 incr repo_config(gui.diffcontext)
4098 reshow_diff
4099 }
4100lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
4101$ctxm add separator
4102$ctxm add command -label {Options...} \
4103 -font font_ui \
4104 -command do_options
4105bind_button3 $ui_diff "tk_popup $ctxm %X %Y"
4106
4107# -- Status Bar
4108#
4109set ui_status_value {Initializing...}
4110label .status -textvariable ui_status_value \
4111 -anchor w \
4112 -justify left \
4113 -borderwidth 1 \
4114 -relief sunken \
4115 -font font_ui
4116pack .status -anchor w -side bottom -fill x
4117
4118# -- Load geometry
4119#
4120catch {
4121set gm $repo_config(gui.geometry)
4122wm geometry . [lindex $gm 0]
4123.vpane sash place 0 \
4124 [lindex [.vpane sash coord 0] 0] \
4125 [lindex $gm 1]
4126.vpane.files sash place 0 \
4127 [lindex $gm 2] \
4128 [lindex [.vpane.files sash coord 0] 1]
4129unset gm
4130}
4131
4132# -- Key Bindings
4133#
4134bind $ui_comm <$M1B-Key-Return> {do_commit;break}
4135bind $ui_comm <$M1B-Key-i> {do_add_all;break}
4136bind $ui_comm <$M1B-Key-I> {do_add_all;break}
4137bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
4138bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
4139bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
4140bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
4141bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
4142bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
4143bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
4144bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
4145
4146bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
4147bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
4148bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
4149bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
4150bind $ui_diff <$M1B-Key-v> {break}
4151bind $ui_diff <$M1B-Key-V> {break}
4152bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
4153bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
4154bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break}
4155bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break}
4156bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break}
4157bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break}
4158
4159if {!$single_commit} {
4160 bind . <$M1B-Key-n> do_create_branch
4161 bind . <$M1B-Key-N> do_create_branch
4162}
4163
4164bind . <Destroy> do_quit
4165bind all <Key-F5> do_rescan
4166bind all <$M1B-Key-r> do_rescan
4167bind all <$M1B-Key-R> do_rescan
4168bind . <$M1B-Key-s> do_signoff
4169bind . <$M1B-Key-S> do_signoff
4170bind . <$M1B-Key-i> do_add_all
4171bind . <$M1B-Key-I> do_add_all
4172bind . <$M1B-Key-Return> do_commit
4173bind all <$M1B-Key-q> do_quit
4174bind all <$M1B-Key-Q> do_quit
4175bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
4176bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
4177foreach i [list $ui_index $ui_workdir] {
4178 bind $i <Button-1> "toggle_or_diff $i %x %y; break"
4179 bind $i <$M1B-Button-1> "add_one_to_selection $i %x %y; break"
4180 bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
4181}
4182unset i
4183
4184set file_lists($ui_index) [list]
4185set file_lists($ui_workdir) [list]
4186
4187set HEAD {}
4188set PARENT {}
4189set MERGE_HEAD [list]
4190set commit_type {}
4191set empty_tree {}
4192set current_branch {}
4193set current_diff_path {}
4194set selected_commit_type new
4195
4196wm title . "[appname] ([file normalize [file dirname [gitdir]]])"
4197focus -force $ui_comm
4198
4199# -- Warn the user about environmental problems. Cygwin's Tcl
4200# does *not* pass its env array onto any processes it spawns.
4201# This means that git processes get none of our environment.
4202#
4203if {[is_Windows]} {
4204 set ignored_env 0
4205 set suggest_user {}
4206 set msg "Possible environment issues exist.
4207
4208The following environment variables are probably
4209going to be ignored by any Git subprocess run
4210by [appname]:
4211
4212"
4213 foreach name [array names env] {
4214 switch -regexp -- $name {
4215 {^GIT_INDEX_FILE$} -
4216 {^GIT_OBJECT_DIRECTORY$} -
4217 {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
4218 {^GIT_DIFF_OPTS$} -
4219 {^GIT_EXTERNAL_DIFF$} -
4220 {^GIT_PAGER$} -
4221 {^GIT_TRACE$} -
4222 {^GIT_CONFIG$} -
4223 {^GIT_CONFIG_LOCAL$} -
4224 {^GIT_(AUTHOR|COMMITTER)_DATE$} {
4225 append msg " - $name\n"
4226 incr ignored_env
4227 }
4228 {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
4229 append msg " - $name\n"
4230 incr ignored_env
4231 set suggest_user $name
4232 }
4233 }
4234 }
4235 if {$ignored_env > 0} {
4236 append msg "
4237This is due to a known issue with the
4238Tcl binary distributed by Cygwin."
4239
4240 if {$suggest_user ne {}} {
4241 append msg "
4242
4243A good replacement for $suggest_user
4244is placing values for the user.name and
4245user.email settings into your personal
4246~/.gitconfig file.
4247"
4248 }
4249 warn_popup $msg
4250 }
4251 unset ignored_env msg suggest_user name
4252}
4253
4254# -- Only initialize complex UI if we are going to stay running.
4255#
4256if {!$single_commit} {
4257 load_all_remotes
4258 load_all_heads
4259
4260 populate_branch_menu
4261 populate_fetch_menu .mbar.fetch
4262 populate_pull_menu .mbar.pull
4263 populate_push_menu .mbar.push
4264}
4265
4266# -- Only suggest a gc run if we are going to stay running.
4267#
4268if {!$single_commit} {
4269 set object_limit 2000
4270 if {[is_Windows]} {set object_limit 200}
4271 regexp {^([0-9]+) objects,} [exec git count-objects] _junk objects_current
4272 if {$objects_current >= $object_limit} {
4273 if {[ask_popup \
4274 "This repository currently has $objects_current loose objects.
4275
4276To maintain optimal performance it is strongly
4277recommended that you compress the database
4278when more than $object_limit loose objects exist.
4279
4280Compress the database now?"] eq yes} {
4281 do_gc
4282 }
4283 }
4284 unset object_limit _junk objects_current
4285}
4286
4287lock_index begin-read
4288after 1 do_rescan