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