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