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