git-gui: Modified makefile to embed version into git-gui script.
[git] / git-gui.sh
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "$@"
4
5 set copyright {
6 Copyright © 2006, 2007 Shawn Pearce, Paul Mackerras.
7
8 All rights reserved.
9
10 This program is free software; it may be used, copied, modified
11 and distributed under the terms of the GNU General Public Licence,
12 either version 2, or (at your option) any later version.}
13
14 set appvers {@@GITGUI_VERSION@@}
15 set appname [lindex [file split $argv0] end]
16 set gitdir {}
17
18 ######################################################################
19 ##
20 ## config
21
22 proc is_many_config {name} {
23         switch -glob -- $name {
24         remote.*.fetch -
25         remote.*.push
26                 {return 1}
27         *
28                 {return 0}
29         }
30 }
31
32 proc 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
77 proc 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
127 proc 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
149 proc 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
171 proc 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
194 if {   [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 }
200 if {![file isdirectory $gitdir]} {
201         catch {wm withdraw .}
202         error_popup "Git directory not found:\n\n$gitdir"
203         exit 1
204 }
205 if {[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 }
210 if {[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
216 set single_commit 0
217 if {$appname eq {git-citool}} {
218         set single_commit 1
219 }
220
221 ######################################################################
222 ##
223 ## task management
224
225 set rescan_active 0
226 set diff_active 0
227 set last_clicked {}
228
229 set disable_on_lock [list]
230 set index_lock_type none
231
232 proc 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
248 proc 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
261 proc 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
296 proc 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
309 proc 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
357 proc 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
392 proc 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
409 proc 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
441 proc 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
473 proc 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
485 proc 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
520 proc 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
534 proc 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
547 proc 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
558 proc 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
569 The modification date of this file was updated
570 by another application and you currently have
571 the Trust File Modification Timestamps option
572 enabled, so Git did not automatically detect
573 that there are no content differences in this
574 file.
575
576 This file will now be removed from the modified
577 files 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
596 proc 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
673 proc 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
738 proc 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
744 You are about to create the initial commit.
745 There 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
754 You are currently in the middle of a merge that
755 has not been fully completed.  You cannot amend
756 the prior commit unless you first abort the
757 current 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
794 proc 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
804 set GIT_COMMITTER_IDENT {}
805
806 proc 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
824 proc 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
839 Another Git program has modified this repository
840 since the last scan.  A rescan must be performed
841 before another commit can be created.
842
843 The 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
862 File [short_path $path] has merge conflicts.
863 You 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
871 File [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
879 You 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
891 A 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
925 proc 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
953 proc 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
972 proc 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
981 proc 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
1119 proc 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
1127 proc 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
1138 Another Git program has modified this repository
1139 since the last scan.  A rescan must be performed
1140 before a pull operation can be started.
1141
1142 The 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
1154 You should not perform a pull with unmodified
1155 files in your working directory as Git will be
1156 unable to recover from an incorrect merge.
1157
1158 You should commit or revert all changes before
1159 starting 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
1176 proc 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
1192 proc 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
1204 proc 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
1214 proc 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
1224 proc 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
1234 proc escape_path {path} {
1235         regsub -all "\n" $path "\\n" path
1236         return $path
1237 }
1238
1239 proc short_path {path} {
1240         return [escape_path [lindex [file split $path] end]]
1241 }
1242
1243 set next_icon_id 0
1244 set null_sha1 [string repeat 0 40]
1245
1246 proc 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
1281 proc 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
1341 proc 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
1379 proc 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
1412 proc 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
1454 proc 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
1487 proc 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
1535 proc 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
1574 proc 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
1617 proc 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
1635 proc 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
1651 proc do_create_branch {} {
1652         error "NOT IMPLEMENTED"
1653 }
1654
1655 proc do_delete_branch {} {
1656         error "NOT IMPLEMENTED"
1657 }
1658
1659 proc 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
1678 Another Git program has modified this repository
1679 since the last scan.  A rescan must be performed
1680 before the current branch can be changed.
1681
1682 The 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
1708 proc 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
1758 proc 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
1789 proc 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
1820 proc 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
1861 set filemask {
1862 #define mask_width 14
1863 #define mask_height 15
1864 static 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
1870 image create bitmap file_plain -background white -foreground black -data {
1871 #define plain_width 14
1872 #define plain_height 15
1873 static 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
1879 image create bitmap file_mod -background white -foreground blue -data {
1880 #define mod_width 14
1881 #define mod_height 15
1882 static 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
1888 image create bitmap file_fulltick -background white -foreground "#007000" -data {
1889 #define file_fulltick_width 14
1890 #define file_fulltick_height 15
1891 static 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
1897 image create bitmap file_parttick -background white -foreground "#005050" -data {
1898 #define parttick_width 14
1899 #define parttick_height 15
1900 static 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
1906 image create bitmap file_question -background white -foreground black -data {
1907 #define file_question_width 14
1908 #define file_question_height 15
1909 static 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
1915 image create bitmap file_removed -background white -foreground red -data {
1916 #define file_removed_width 14
1917 #define file_removed_height 15
1918 static 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
1924 image create bitmap file_merge -background white -foreground blue -data {
1925 #define file_merge_width 14
1926 #define file_merge_height 15
1927 static 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
1933 set ui_index .vpane.files.index.list
1934 set ui_other .vpane.files.other.list
1935 set max_status_desc 0
1936 foreach 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 }
1969 unset filemask i
1970
1971 ######################################################################
1972 ##
1973 ## util
1974
1975 proc is_MacOSX {} {
1976         global tcl_platform tk_library
1977         if {[tk windowingsystem] eq {aqua}} {
1978                 return 1
1979         }
1980         return 0
1981 }
1982
1983 proc is_Windows {} {
1984         global tcl_platform
1985         if {$tcl_platform(platform) eq {windows}} {
1986                 return 1
1987         }
1988         return 0
1989 }
1990
1991 proc 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
1998 proc 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
2005 proc 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
2051 set next_console_id 0
2052
2053 proc 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
2060 proc 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
2120 proc 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
2138 proc 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
2196 set starting_gitk_msg {Please wait... Starting gitk...}
2197
2198 proc 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
2223 proc do_gc {} {
2224         set w [new_console {gc} {Compressing the object database}]
2225         console_exec $w {git gc}
2226 }
2227
2228 proc 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
2238 set is_quitting 0
2239
2240 proc 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
2278 proc do_rescan {} {
2279         rescan {set ui_status_value {Ready.}}
2280 }
2281
2282 proc 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
2311 proc 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
2325 proc 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
2359 proc 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
2373 proc 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
2392 proc 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
2435 Any 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
2451 proc 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
2465 proc 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
2485 proc 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
2503 proc do_commit {} {
2504         commit_tree
2505 }
2506
2507 proc do_about {} {
2508         global appname 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 [exec git --version]
2538         append v "\n\n"
2539         if {$tcl_patchLevel eq $tk_patchLevel} {
2540                 append v "Tcl/Tk version $tcl_patchLevel"
2541         } else {
2542                 append v "Tcl version $tcl_patchLevel"
2543                 append v ", Tk version $tk_patchLevel"
2544         }
2545
2546         label $w.vers \
2547                 -text $v \
2548                 -padx 5 -pady 5 \
2549                 -justify left \
2550                 -anchor w \
2551                 -borderwidth 1 \
2552                 -relief solid \
2553                 -font font_ui
2554         pack $w.vers -side top -fill x -padx 5 -pady 5
2555
2556         bind $w <Visibility> "grab $w; focus $w"
2557         bind $w <Key-Escape> "destroy $w"
2558         wm title $w "About $appname"
2559         tkwait window $w
2560 }
2561
2562 proc do_options {} {
2563         global appname gitdir font_descs
2564         global repo_config global_config
2565         global repo_config_new global_config_new
2566
2567         array unset repo_config_new
2568         array unset global_config_new
2569         foreach name [array names repo_config] {
2570                 set repo_config_new($name) $repo_config($name)
2571         }
2572         load_config 1
2573         foreach name [array names repo_config] {
2574                 switch -- $name {
2575                 gui.diffcontext {continue}
2576                 }
2577                 set repo_config_new($name) $repo_config($name)
2578         }
2579         foreach name [array names global_config] {
2580                 set global_config_new($name) $global_config($name)
2581         }
2582         set reponame [lindex [file split \
2583                 [file normalize [file dirname $gitdir]]] \
2584                 end]
2585
2586         set w .options_editor
2587         toplevel $w
2588         wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
2589
2590         label $w.header -text "$appname Options" \
2591                 -font font_uibold
2592         pack $w.header -side top -fill x
2593
2594         frame $w.buttons
2595         button $w.buttons.restore -text {Restore Defaults} \
2596                 -font font_ui \
2597                 -command do_restore_defaults
2598         pack $w.buttons.restore -side left
2599         button $w.buttons.save -text Save \
2600                 -font font_ui \
2601                 -command [list do_save_config $w]
2602         pack $w.buttons.save -side right
2603         button $w.buttons.cancel -text {Cancel} \
2604                 -font font_ui \
2605                 -command [list destroy $w]
2606         pack $w.buttons.cancel -side right
2607         pack $w.buttons -side bottom -fill x -pady 10 -padx 10
2608
2609         labelframe $w.repo -text "$reponame Repository" \
2610                 -font font_ui \
2611                 -relief raised -borderwidth 2
2612         labelframe $w.global -text {Global (All Repositories)} \
2613                 -font font_ui \
2614                 -relief raised -borderwidth 2
2615         pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5
2616         pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5
2617
2618         foreach option {
2619                 {b partialinclude {Allow Partially Added Files}}
2620                 {b pullsummary {Show Pull Summary}}
2621                 {b trustmtime  {Trust File Modification Timestamps}}
2622                 {i diffcontext {Number of Diff Context Lines}}
2623                 } {
2624                 set type [lindex $option 0]
2625                 set name [lindex $option 1]
2626                 set text [lindex $option 2]
2627                 foreach f {repo global} {
2628                         switch $type {
2629                         b {
2630                                 checkbutton $w.$f.$name -text $text \
2631                                         -variable ${f}_config_new(gui.$name) \
2632                                         -onvalue true \
2633                                         -offvalue false \
2634                                         -font font_ui
2635                                 pack $w.$f.$name -side top -anchor w
2636                         }
2637                         i {
2638                                 frame $w.$f.$name
2639                                 label $w.$f.$name.l -text "$text:" -font font_ui
2640                                 pack $w.$f.$name.l -side left -anchor w -fill x
2641                                 spinbox $w.$f.$name.v \
2642                                         -textvariable ${f}_config_new(gui.$name) \
2643                                         -from 1 -to 99 -increment 1 \
2644                                         -width 3 \
2645                                         -font font_ui
2646                                 pack $w.$f.$name.v -side right -anchor e
2647                                 pack $w.$f.$name -side top -anchor w -fill x
2648                         }
2649                         }
2650                 }
2651         }
2652
2653         set all_fonts [lsort [font families]]
2654         foreach option $font_descs {
2655                 set name [lindex $option 0]
2656                 set font [lindex $option 1]
2657                 set text [lindex $option 2]
2658
2659                 set global_config_new(gui.$font^^family) \
2660                         [font configure $font -family]
2661                 set global_config_new(gui.$font^^size) \
2662                         [font configure $font -size]
2663
2664                 frame $w.global.$name
2665                 label $w.global.$name.l -text "$text:" -font font_ui
2666                 pack $w.global.$name.l -side left -anchor w -fill x
2667                 eval tk_optionMenu $w.global.$name.family \
2668                         global_config_new(gui.$font^^family) \
2669                         $all_fonts
2670                 spinbox $w.global.$name.size \
2671                         -textvariable global_config_new(gui.$font^^size) \
2672                         -from 2 -to 80 -increment 1 \
2673                         -width 3 \
2674                         -font font_ui
2675                 pack $w.global.$name.size -side right -anchor e
2676                 pack $w.global.$name.family -side right -anchor e
2677                 pack $w.global.$name -side top -anchor w -fill x
2678         }
2679
2680         bind $w <Visibility> "grab $w; focus $w"
2681         bind $w <Key-Escape> "destroy $w"
2682         wm title $w "$appname ($reponame): Options"
2683         tkwait window $w
2684 }
2685
2686 proc do_restore_defaults {} {
2687         global font_descs default_config repo_config
2688         global repo_config_new global_config_new
2689
2690         foreach name [array names default_config] {
2691                 set repo_config_new($name) $default_config($name)
2692                 set global_config_new($name) $default_config($name)
2693         }
2694
2695         foreach option $font_descs {
2696                 set name [lindex $option 0]
2697                 set repo_config(gui.$name) $default_config(gui.$name)
2698         }
2699         apply_config
2700
2701         foreach option $font_descs {
2702                 set name [lindex $option 0]
2703                 set font [lindex $option 1]
2704                 set global_config_new(gui.$font^^family) \
2705                         [font configure $font -family]
2706                 set global_config_new(gui.$font^^size) \
2707                         [font configure $font -size]
2708         }
2709 }
2710
2711 proc do_save_config {w} {
2712         if {[catch {save_config} err]} {
2713                 error_popup "Failed to completely save options:\n\n$err"
2714         }
2715         reshow_diff
2716         destroy $w
2717 }
2718
2719 proc do_windows_shortcut {} {
2720         global gitdir appname argv0
2721
2722         set reponame [lindex [file split \
2723                 [file normalize [file dirname $gitdir]]] \
2724                 end]
2725
2726         if {[catch {
2727                 set desktop [exec cygpath \
2728                         --windows \
2729                         --absolute \
2730                         --long-name \
2731                         --desktop]
2732                 }]} {
2733                         set desktop .
2734         }
2735         set fn [tk_getSaveFile \
2736                 -parent . \
2737                 -title "$appname ($reponame): Create Desktop Icon" \
2738                 -initialdir $desktop \
2739                 -initialfile "Git $reponame.bat"]
2740         if {$fn != {}} {
2741                 if {[catch {
2742                                 set fd [open $fn w]
2743                                 set sh [exec cygpath \
2744                                         --windows \
2745                                         --absolute \
2746                                         /bin/sh]
2747                                 set me [exec cygpath \
2748                                         --unix \
2749                                         --absolute \
2750                                         $argv0]
2751                                 set gd [exec cygpath \
2752                                         --unix \
2753                                         --absolute \
2754                                         $gitdir]
2755                                 regsub -all ' $me "'\\''" me
2756                                 regsub -all ' $gd "'\\''" gd
2757                                 puts $fd "@ECHO Starting git-gui... Please wait..."
2758                                 puts -nonewline $fd "@\"$sh\" --login -c \""
2759                                 puts -nonewline $fd "GIT_DIR='$gd'"
2760                                 puts -nonewline $fd " '$me'"
2761                                 puts $fd "&\""
2762                                 close $fd
2763                         } err]} {
2764                         error_popup "Cannot write script:\n\n$err"
2765                 }
2766         }
2767 }
2768
2769 proc do_macosx_app {} {
2770         global gitdir appname argv0 env
2771
2772         set reponame [lindex [file split \
2773                 [file normalize [file dirname $gitdir]]] \
2774                 end]
2775
2776         set fn [tk_getSaveFile \
2777                 -parent . \
2778                 -title "$appname ($reponame): Create Desktop Icon" \
2779                 -initialdir [file join $env(HOME) Desktop] \
2780                 -initialfile "Git $reponame.app"]
2781         if {$fn != {}} {
2782                 if {[catch {
2783                                 set Contents [file join $fn Contents]
2784                                 set MacOS [file join $Contents MacOS]
2785                                 set exe [file join $MacOS git-gui]
2786
2787                                 file mkdir $MacOS
2788
2789                                 set fd [open [file join $Contents Info.plist] w]
2790                                 puts $fd {<?xml version="1.0" encoding="UTF-8"?>
2791 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
2792 <plist version="1.0">
2793 <dict>
2794         <key>CFBundleDevelopmentRegion</key>
2795         <string>English</string>
2796         <key>CFBundleExecutable</key>
2797         <string>git-gui</string>
2798         <key>CFBundleIdentifier</key>
2799         <string>org.spearce.git-gui</string>
2800         <key>CFBundleInfoDictionaryVersion</key>
2801         <string>6.0</string>
2802         <key>CFBundlePackageType</key>
2803         <string>APPL</string>
2804         <key>CFBundleSignature</key>
2805         <string>????</string>
2806         <key>CFBundleVersion</key>
2807         <string>1.0</string>
2808         <key>NSPrincipalClass</key>
2809         <string>NSApplication</string>
2810 </dict>
2811 </plist>}
2812                                 close $fd
2813
2814                                 set fd [open $exe w]
2815                                 set gd [file normalize $gitdir]
2816                                 set ep [file normalize [exec git --exec-path]]
2817                                 regsub -all ' $gd "'\\''" gd
2818                                 regsub -all ' $ep "'\\''" ep
2819                                 puts $fd "#!/bin/sh"
2820                                 foreach name [array names env] {
2821                                         if {[string match GIT_* $name]} {
2822                                                 regsub -all ' $env($name) "'\\''" v
2823                                                 puts $fd "export $name='$v'"
2824                                         }
2825                                 }
2826                                 puts $fd "export PATH='$ep':\$PATH"
2827                                 puts $fd "export GIT_DIR='$gd'"
2828                                 puts $fd "exec [file normalize $argv0]"
2829                                 close $fd
2830
2831                                 file attributes $exe -permissions u+x,g+x,o+x
2832                         } err]} {
2833                         error_popup "Cannot write icon:\n\n$err"
2834                 }
2835         }
2836 }
2837
2838 proc toggle_or_diff {w x y} {
2839         global file_states file_lists current_diff ui_index ui_other
2840         global last_clicked selected_paths
2841
2842         set pos [split [$w index @$x,$y] .]
2843         set lno [lindex $pos 0]
2844         set col [lindex $pos 1]
2845         set path [lindex $file_lists($w) [expr {$lno - 1}]]
2846         if {$path eq {}} {
2847                 set last_clicked {}
2848                 return
2849         }
2850
2851         set last_clicked [list $w $lno]
2852         array unset selected_paths
2853         $ui_index tag remove in_sel 0.0 end
2854         $ui_other tag remove in_sel 0.0 end
2855
2856         if {$col == 0} {
2857                 if {$current_diff eq $path} {
2858                         set after {reshow_diff;}
2859                 } else {
2860                         set after {}
2861                 }
2862                 switch -glob -- [lindex $file_states($path) 0] {
2863                 A_ -
2864                 M_ -
2865                 DD -
2866                 DO -
2867                 DM {
2868                         update_indexinfo \
2869                                 "Removing [short_path $path] from commit" \
2870                                 [list $path] \
2871                                 [concat $after {set ui_status_value {Ready.}}]
2872                 }
2873                 ?? {
2874                         update_index \
2875                                 "Adding [short_path $path]" \
2876                                 [list $path] \
2877                                 [concat $after {set ui_status_value {Ready.}}]
2878                 }
2879                 }
2880         } else {
2881                 show_diff $path $w $lno
2882         }
2883 }
2884
2885 proc add_one_to_selection {w x y} {
2886         global file_lists
2887         global last_clicked selected_paths
2888
2889         set pos [split [$w index @$x,$y] .]
2890         set lno [lindex $pos 0]
2891         set col [lindex $pos 1]
2892         set path [lindex $file_lists($w) [expr {$lno - 1}]]
2893         if {$path eq {}} {
2894                 set last_clicked {}
2895                 return
2896         }
2897
2898         set last_clicked [list $w $lno]
2899         if {[catch {set in_sel $selected_paths($path)}]} {
2900                 set in_sel 0
2901         }
2902         if {$in_sel} {
2903                 unset selected_paths($path)
2904                 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
2905         } else {
2906                 set selected_paths($path) 1
2907                 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
2908         }
2909 }
2910
2911 proc add_range_to_selection {w x y} {
2912         global file_lists
2913         global last_clicked selected_paths
2914
2915         if {[lindex $last_clicked 0] ne $w} {
2916                 toggle_or_diff $w $x $y
2917                 return
2918         }
2919
2920         set pos [split [$w index @$x,$y] .]
2921         set lno [lindex $pos 0]
2922         set lc [lindex $last_clicked 1]
2923         if {$lc < $lno} {
2924                 set begin $lc
2925                 set end $lno
2926         } else {
2927                 set begin $lno
2928                 set end $lc
2929         }
2930
2931         foreach path [lrange $file_lists($w) \
2932                 [expr {$begin - 1}] \
2933                 [expr {$end - 1}]] {
2934                 set selected_paths($path) 1
2935         }
2936         $w tag add in_sel $begin.0 [expr {$end + 1}].0
2937 }
2938
2939 ######################################################################
2940 ##
2941 ## config defaults
2942
2943 set cursor_ptr arrow
2944 font create font_diff -family Courier -size 10
2945 font create font_ui
2946 catch {
2947         label .dummy
2948         eval font configure font_ui [font actual [.dummy cget -font]]
2949         destroy .dummy
2950 }
2951
2952 font create font_uibold
2953 font create font_diffbold
2954
2955 if {[is_Windows]} {
2956         set M1B Control
2957         set M1T Ctrl
2958 } elseif {[is_MacOSX]} {
2959         set M1B M1
2960         set M1T Cmd
2961 } else {
2962         set M1B M1
2963         set M1T M1
2964 }
2965
2966 proc apply_config {} {
2967         global repo_config font_descs
2968
2969         foreach option $font_descs {
2970                 set name [lindex $option 0]
2971                 set font [lindex $option 1]
2972                 if {[catch {
2973                         foreach {cn cv} $repo_config(gui.$name) {
2974                                 font configure $font $cn $cv
2975                         }
2976                         } err]} {
2977                         error_popup "Invalid font specified in gui.$name:\n\n$err"
2978                 }
2979                 foreach {cn cv} [font configure $font] {
2980                         font configure ${font}bold $cn $cv
2981                 }
2982                 font configure ${font}bold -weight bold
2983         }
2984 }
2985
2986 set default_config(gui.trustmtime) false
2987 set default_config(gui.pullsummary) true
2988 set default_config(gui.partialinclude) false
2989 set default_config(gui.diffcontext) 5
2990 set default_config(gui.fontui) [font configure font_ui]
2991 set default_config(gui.fontdiff) [font configure font_diff]
2992 set font_descs {
2993         {fontui   font_ui   {Main Font}}
2994         {fontdiff font_diff {Diff/Console Font}}
2995 }
2996 load_config 0
2997 apply_config
2998
2999 ######################################################################
3000 ##
3001 ## ui construction
3002
3003 # -- Menu Bar
3004 #
3005 menu .mbar -tearoff 0
3006 .mbar add cascade -label Repository -menu .mbar.repository
3007 .mbar add cascade -label Edit -menu .mbar.edit
3008 if {!$single_commit} {
3009         .mbar add cascade -label Branch -menu .mbar.branch
3010 }
3011 .mbar add cascade -label Commit -menu .mbar.commit
3012 if {!$single_commit} {
3013         .mbar add cascade -label Fetch -menu .mbar.fetch
3014         .mbar add cascade -label Pull -menu .mbar.pull
3015         .mbar add cascade -label Push -menu .mbar.push
3016 }
3017 . configure -menu .mbar
3018
3019 # -- Repository Menu
3020 #
3021 menu .mbar.repository
3022 .mbar.repository add command \
3023         -label {Visualize Current Branch} \
3024         -command {do_gitk {}} \
3025         -font font_ui
3026 if {![is_MacOSX]} {
3027         .mbar.repository add command \
3028                 -label {Visualize All Branches} \
3029                 -command {do_gitk {--all}} \
3030                 -font font_ui
3031 }
3032 .mbar.repository add separator
3033
3034 if {!$single_commit} {
3035         .mbar.repository add command -label {Compress Database} \
3036                 -command do_gc \
3037                 -font font_ui
3038
3039         .mbar.repository add command -label {Verify Database} \
3040                 -command do_fsck_objects \
3041                 -font font_ui
3042
3043         .mbar.repository add separator
3044
3045         if {[is_Windows]} {
3046                 .mbar.repository add command \
3047                         -label {Create Desktop Icon} \
3048                         -command do_windows_shortcut \
3049                         -font font_ui
3050         } elseif {[is_MacOSX]} {
3051                 .mbar.repository add command \
3052                         -label {Create Desktop Icon} \
3053                         -command do_macosx_app \
3054                         -font font_ui
3055         }
3056 }
3057
3058 .mbar.repository add command -label Quit \
3059         -command do_quit \
3060         -accelerator $M1T-Q \
3061         -font font_ui
3062
3063 # -- Edit Menu
3064 #
3065 menu .mbar.edit
3066 .mbar.edit add command -label Undo \
3067         -command {catch {[focus] edit undo}} \
3068         -accelerator $M1T-Z \
3069         -font font_ui
3070 .mbar.edit add command -label Redo \
3071         -command {catch {[focus] edit redo}} \
3072         -accelerator $M1T-Y \
3073         -font font_ui
3074 .mbar.edit add separator
3075 .mbar.edit add command -label Cut \
3076         -command {catch {tk_textCut [focus]}} \
3077         -accelerator $M1T-X \
3078         -font font_ui
3079 .mbar.edit add command -label Copy \
3080         -command {catch {tk_textCopy [focus]}} \
3081         -accelerator $M1T-C \
3082         -font font_ui
3083 .mbar.edit add command -label Paste \
3084         -command {catch {tk_textPaste [focus]; [focus] see insert}} \
3085         -accelerator $M1T-V \
3086         -font font_ui
3087 .mbar.edit add command -label Delete \
3088         -command {catch {[focus] delete sel.first sel.last}} \
3089         -accelerator Del \
3090         -font font_ui
3091 .mbar.edit add separator
3092 .mbar.edit add command -label {Select All} \
3093         -command {catch {[focus] tag add sel 0.0 end}} \
3094         -accelerator $M1T-A \
3095         -font font_ui
3096
3097 # -- Branch Menu
3098 #
3099 if {!$single_commit} {
3100         menu .mbar.branch
3101
3102         .mbar.branch add command -label {Create...} \
3103                 -command do_create_branch \
3104                 -font font_ui
3105         lappend disable_on_lock [list .mbar.branch entryconf \
3106                 [.mbar.branch index last] -state]
3107
3108         .mbar.branch add command -label {Delete...} \
3109                 -command do_delete_branch \
3110                 -font font_ui
3111         lappend disable_on_lock [list .mbar.branch entryconf \
3112                 [.mbar.branch index last] -state]
3113 }
3114
3115 # -- Commit Menu
3116 #
3117 menu .mbar.commit
3118
3119 .mbar.commit add radiobutton \
3120         -label {New Commit} \
3121         -command do_select_commit_type \
3122         -variable selected_commit_type \
3123         -value new \
3124         -font font_ui
3125 lappend disable_on_lock \
3126         [list .mbar.commit entryconf [.mbar.commit index last] -state]
3127
3128 .mbar.commit add radiobutton \
3129         -label {Amend Last Commit} \
3130         -command do_select_commit_type \
3131         -variable selected_commit_type \
3132         -value amend \
3133         -font font_ui
3134 lappend disable_on_lock \
3135         [list .mbar.commit entryconf [.mbar.commit index last] -state]
3136
3137 .mbar.commit add separator
3138
3139 .mbar.commit add command -label Rescan \
3140         -command do_rescan \
3141         -accelerator F5 \
3142         -font font_ui
3143 lappend disable_on_lock \
3144         [list .mbar.commit entryconf [.mbar.commit index last] -state]
3145
3146 .mbar.commit add command -label {Add To Commit} \
3147         -command do_include_selection \
3148         -font font_ui
3149 lappend disable_on_lock \
3150         [list .mbar.commit entryconf [.mbar.commit index last] -state]
3151
3152 .mbar.commit add command -label {Add All To Commit} \
3153         -command do_include_all \
3154         -accelerator $M1T-I \
3155         -font font_ui
3156 lappend disable_on_lock \
3157         [list .mbar.commit entryconf [.mbar.commit index last] -state]
3158
3159 .mbar.commit add command -label {Remove From Commit} \
3160         -command do_remove_selection \
3161         -font font_ui
3162 lappend disable_on_lock \
3163         [list .mbar.commit entryconf [.mbar.commit index last] -state]
3164
3165 .mbar.commit add command -label {Revert Changes} \
3166         -command do_revert_selection \
3167         -font font_ui
3168 lappend disable_on_lock \
3169         [list .mbar.commit entryconf [.mbar.commit index last] -state]
3170
3171 .mbar.commit add separator
3172
3173 .mbar.commit add command -label {Sign Off} \
3174         -command do_signoff \
3175         -accelerator $M1T-S \
3176         -font font_ui
3177
3178 .mbar.commit add command -label Commit \
3179         -command do_commit \
3180         -accelerator $M1T-Return \
3181         -font font_ui
3182 lappend disable_on_lock \
3183         [list .mbar.commit entryconf [.mbar.commit index last] -state]
3184
3185 # -- Transport menus
3186 #
3187 if {!$single_commit} {
3188         menu .mbar.fetch
3189         menu .mbar.pull
3190         menu .mbar.push
3191 }
3192
3193 if {[is_MacOSX]} {
3194         # -- Apple Menu (Mac OS X only)
3195         #
3196         .mbar add cascade -label Apple -menu .mbar.apple
3197         menu .mbar.apple
3198
3199         .mbar.apple add command -label "About $appname" \
3200                 -command do_about \
3201                 -font font_ui
3202         .mbar.apple add command -label "$appname Options..." \
3203                 -command do_options \
3204                 -font font_ui
3205 } else {
3206         # -- Edit Menu
3207         #
3208         .mbar.edit add separator
3209         .mbar.edit add command -label {Options...} \
3210                 -command do_options \
3211                 -font font_ui
3212
3213         # -- Tools Menu
3214         #
3215         if {[file exists /usr/local/miga/lib/gui-miga]} {
3216         proc do_miga {} {
3217                 global gitdir ui_status_value
3218                 if {![lock_index update]} return
3219                 set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""]
3220                 set miga_fd [open "|$cmd" r]
3221                 fconfigure $miga_fd -blocking 0
3222                 fileevent $miga_fd readable [list miga_done $miga_fd]
3223                 set ui_status_value {Running miga...}
3224         }
3225         proc miga_done {fd} {
3226                 read $fd 512
3227                 if {[eof $fd]} {
3228                         close $fd
3229                         unlock_index
3230                         rescan [list set ui_status_value {Ready.}]
3231                 }
3232         }
3233         .mbar add cascade -label Tools -menu .mbar.tools
3234         menu .mbar.tools
3235         .mbar.tools add command -label "Migrate" \
3236                 -command do_miga \
3237                 -font font_ui
3238         lappend disable_on_lock \
3239                 [list .mbar.tools entryconf [.mbar.tools index last] -state]
3240         }
3241
3242         # -- Help Menu
3243         #
3244         .mbar add cascade -label Help -menu .mbar.help
3245         menu .mbar.help
3246
3247         .mbar.help add command -label "About $appname" \
3248                 -command do_about \
3249                 -font font_ui
3250 }
3251
3252
3253 # -- Branch Control
3254 #
3255 frame .branch \
3256         -borderwidth 1 \
3257         -relief sunken
3258 label .branch.l1 \
3259         -text {Current Branch:} \
3260         -anchor w \
3261         -justify left \
3262         -font font_ui
3263 label .branch.cb \
3264         -textvariable current_branch \
3265         -anchor w \
3266         -justify left \
3267         -font font_ui
3268 pack .branch.l1 -side left
3269 pack .branch.cb -side left -fill x
3270 pack .branch -side top -fill x
3271
3272 # -- Main Window Layout
3273 #
3274 panedwindow .vpane -orient vertical
3275 panedwindow .vpane.files -orient horizontal
3276 .vpane add .vpane.files -sticky nsew -height 100 -width 400
3277 pack .vpane -anchor n -side top -fill both -expand 1
3278
3279 # -- Index File List
3280 #
3281 frame .vpane.files.index -height 100 -width 400
3282 label .vpane.files.index.title -text {Modified Files} \
3283         -background green \
3284         -font font_ui
3285 text $ui_index -background white -borderwidth 0 \
3286         -width 40 -height 10 \
3287         -font font_ui \
3288         -cursor $cursor_ptr \
3289         -yscrollcommand {.vpane.files.index.sb set} \
3290         -state disabled
3291 scrollbar .vpane.files.index.sb -command [list $ui_index yview]
3292 pack .vpane.files.index.title -side top -fill x
3293 pack .vpane.files.index.sb -side right -fill y
3294 pack $ui_index -side left -fill both -expand 1
3295 .vpane.files add .vpane.files.index -sticky nsew
3296
3297 # -- Other (Add) File List
3298 #
3299 frame .vpane.files.other -height 100 -width 100
3300 label .vpane.files.other.title -text {Untracked Files} \
3301         -background red \
3302         -font font_ui
3303 text $ui_other -background white -borderwidth 0 \
3304         -width 40 -height 10 \
3305         -font font_ui \
3306         -cursor $cursor_ptr \
3307         -yscrollcommand {.vpane.files.other.sb set} \
3308         -state disabled
3309 scrollbar .vpane.files.other.sb -command [list $ui_other yview]
3310 pack .vpane.files.other.title -side top -fill x
3311 pack .vpane.files.other.sb -side right -fill y
3312 pack $ui_other -side left -fill both -expand 1
3313 .vpane.files add .vpane.files.other -sticky nsew
3314
3315 foreach i [list $ui_index $ui_other] {
3316         $i tag conf in_diff -font font_uibold
3317         $i tag conf in_sel \
3318                 -background [$i cget -foreground] \
3319                 -foreground [$i cget -background]
3320 }
3321 unset i
3322
3323 # -- Diff and Commit Area
3324 #
3325 frame .vpane.lower -height 300 -width 400
3326 frame .vpane.lower.commarea
3327 frame .vpane.lower.diff -relief sunken -borderwidth 1
3328 pack .vpane.lower.commarea -side top -fill x
3329 pack .vpane.lower.diff -side bottom -fill both -expand 1
3330 .vpane add .vpane.lower -stick nsew
3331
3332 # -- Commit Area Buttons
3333 #
3334 frame .vpane.lower.commarea.buttons
3335 label .vpane.lower.commarea.buttons.l -text {} \
3336         -anchor w \
3337         -justify left \
3338         -font font_ui
3339 pack .vpane.lower.commarea.buttons.l -side top -fill x
3340 pack .vpane.lower.commarea.buttons -side left -fill y
3341
3342 button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
3343         -command do_rescan \
3344         -font font_ui
3345 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
3346 lappend disable_on_lock \
3347         {.vpane.lower.commarea.buttons.rescan conf -state}
3348
3349 button .vpane.lower.commarea.buttons.incall -text {Add All} \
3350         -command do_include_all \
3351         -font font_ui
3352 pack .vpane.lower.commarea.buttons.incall -side top -fill x
3353 lappend disable_on_lock \
3354         {.vpane.lower.commarea.buttons.incall conf -state}
3355
3356 button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
3357         -command do_signoff \
3358         -font font_ui
3359 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
3360
3361 button .vpane.lower.commarea.buttons.commit -text {Commit} \
3362         -command do_commit \
3363         -font font_ui
3364 pack .vpane.lower.commarea.buttons.commit -side top -fill x
3365 lappend disable_on_lock \
3366         {.vpane.lower.commarea.buttons.commit conf -state}
3367
3368 # -- Commit Message Buffer
3369 #
3370 frame .vpane.lower.commarea.buffer
3371 frame .vpane.lower.commarea.buffer.header
3372 set ui_comm .vpane.lower.commarea.buffer.t
3373 set ui_coml .vpane.lower.commarea.buffer.header.l
3374 radiobutton .vpane.lower.commarea.buffer.header.new \
3375         -text {New Commit} \
3376         -command do_select_commit_type \
3377         -variable selected_commit_type \
3378         -value new \
3379         -font font_ui
3380 lappend disable_on_lock \
3381         [list .vpane.lower.commarea.buffer.header.new conf -state]
3382 radiobutton .vpane.lower.commarea.buffer.header.amend \
3383         -text {Amend Last Commit} \
3384         -command do_select_commit_type \
3385         -variable selected_commit_type \
3386         -value amend \
3387         -font font_ui
3388 lappend disable_on_lock \
3389         [list .vpane.lower.commarea.buffer.header.amend conf -state]
3390 label $ui_coml \
3391         -anchor w \
3392         -justify left \
3393         -font font_ui
3394 proc trace_commit_type {varname args} {
3395         global ui_coml commit_type
3396         switch -glob -- $commit_type {
3397         initial       {set txt {Initial Commit Message:}}
3398         amend         {set txt {Amended Commit Message:}}
3399         amend-initial {set txt {Amended Initial Commit Message:}}
3400         amend-merge   {set txt {Amended Merge Commit Message:}}
3401         merge         {set txt {Merge Commit Message:}}
3402         *             {set txt {Commit Message:}}
3403         }
3404         $ui_coml conf -text $txt
3405 }
3406 trace add variable commit_type write trace_commit_type
3407 pack $ui_coml -side left -fill x
3408 pack .vpane.lower.commarea.buffer.header.amend -side right
3409 pack .vpane.lower.commarea.buffer.header.new -side right
3410
3411 text $ui_comm -background white -borderwidth 1 \
3412         -undo true \
3413         -maxundo 20 \
3414         -autoseparators true \
3415         -relief sunken \
3416         -width 75 -height 9 -wrap none \
3417         -font font_diff \
3418         -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
3419 scrollbar .vpane.lower.commarea.buffer.sby \
3420         -command [list $ui_comm yview]
3421 pack .vpane.lower.commarea.buffer.header -side top -fill x
3422 pack .vpane.lower.commarea.buffer.sby -side right -fill y
3423 pack $ui_comm -side left -fill y
3424 pack .vpane.lower.commarea.buffer -side left -fill y
3425
3426 # -- Commit Message Buffer Context Menu
3427 #
3428 set ctxm .vpane.lower.commarea.buffer.ctxm
3429 menu $ctxm -tearoff 0
3430 $ctxm add command \
3431         -label {Cut} \
3432         -font font_ui \
3433         -command {tk_textCut $ui_comm}
3434 $ctxm add command \
3435         -label {Copy} \
3436         -font font_ui \
3437         -command {tk_textCopy $ui_comm}
3438 $ctxm add command \
3439         -label {Paste} \
3440         -font font_ui \
3441         -command {tk_textPaste $ui_comm}
3442 $ctxm add command \
3443         -label {Delete} \
3444         -font font_ui \
3445         -command {$ui_comm delete sel.first sel.last}
3446 $ctxm add separator
3447 $ctxm add command \
3448         -label {Select All} \
3449         -font font_ui \
3450         -command {$ui_comm tag add sel 0.0 end}
3451 $ctxm add command \
3452         -label {Copy All} \
3453         -font font_ui \
3454         -command {
3455                 $ui_comm tag add sel 0.0 end
3456                 tk_textCopy $ui_comm
3457                 $ui_comm tag remove sel 0.0 end
3458         }
3459 $ctxm add separator
3460 $ctxm add command \
3461         -label {Sign Off} \
3462         -font font_ui \
3463         -command do_signoff
3464 bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
3465
3466 # -- Diff Header
3467 #
3468 set current_diff {}
3469 set diff_actions [list]
3470 proc trace_current_diff {varname args} {
3471         global current_diff diff_actions file_states
3472         if {$current_diff eq {}} {
3473                 set s {}
3474                 set f {}
3475                 set p {}
3476                 set o disabled
3477         } else {
3478                 set p $current_diff
3479                 set s [mapdesc [lindex $file_states($p) 0] $p]
3480                 set f {File:}
3481                 set p [escape_path $p]
3482                 set o normal
3483         }
3484
3485         .vpane.lower.diff.header.status configure -text $s
3486         .vpane.lower.diff.header.file configure -text $f
3487         .vpane.lower.diff.header.path configure -text $p
3488         foreach w $diff_actions {
3489                 uplevel #0 $w $o
3490         }
3491 }
3492 trace add variable current_diff write trace_current_diff
3493
3494 frame .vpane.lower.diff.header -background orange
3495 label .vpane.lower.diff.header.status \
3496         -background orange \
3497         -width $max_status_desc \
3498         -anchor w \
3499         -justify left \
3500         -font font_ui
3501 label .vpane.lower.diff.header.file \
3502         -background orange \
3503         -anchor w \
3504         -justify left \
3505         -font font_ui
3506 label .vpane.lower.diff.header.path \
3507         -background orange \
3508         -anchor w \
3509         -justify left \
3510         -font font_ui
3511 pack .vpane.lower.diff.header.status -side left
3512 pack .vpane.lower.diff.header.file -side left
3513 pack .vpane.lower.diff.header.path -fill x
3514 set ctxm .vpane.lower.diff.header.ctxm
3515 menu $ctxm -tearoff 0
3516 $ctxm add command \
3517         -label {Copy} \
3518         -font font_ui \
3519         -command {
3520                 clipboard clear
3521                 clipboard append \
3522                         -format STRING \
3523                         -type STRING \
3524                         -- $current_diff
3525         }
3526 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3527 bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
3528
3529 # -- Diff Body
3530 #
3531 frame .vpane.lower.diff.body
3532 set ui_diff .vpane.lower.diff.body.t
3533 text $ui_diff -background white -borderwidth 0 \
3534         -width 80 -height 15 -wrap none \
3535         -font font_diff \
3536         -xscrollcommand {.vpane.lower.diff.body.sbx set} \
3537         -yscrollcommand {.vpane.lower.diff.body.sby set} \
3538         -state disabled
3539 scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
3540         -command [list $ui_diff xview]
3541 scrollbar .vpane.lower.diff.body.sby -orient vertical \
3542         -command [list $ui_diff yview]
3543 pack .vpane.lower.diff.body.sbx -side bottom -fill x
3544 pack .vpane.lower.diff.body.sby -side right -fill y
3545 pack $ui_diff -side left -fill both -expand 1
3546 pack .vpane.lower.diff.header -side top -fill x
3547 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
3548
3549 $ui_diff tag conf d_@ -font font_diffbold
3550 $ui_diff tag conf d_+  -foreground blue
3551 $ui_diff tag conf d_-  -foreground red
3552 $ui_diff tag conf d_++ -foreground {#00a000}
3553 $ui_diff tag conf d_-- -foreground {#a000a0}
3554 $ui_diff tag conf d_+- \
3555         -foreground red \
3556         -background {light goldenrod yellow}
3557 $ui_diff tag conf d_-+ \
3558         -foreground blue \
3559         -background azure2
3560
3561 # -- Diff Body Context Menu
3562 #
3563 set ctxm .vpane.lower.diff.body.ctxm
3564 menu $ctxm -tearoff 0
3565 $ctxm add command \
3566         -label {Copy} \
3567         -font font_ui \
3568         -command {tk_textCopy $ui_diff}
3569 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3570 $ctxm add command \
3571         -label {Select All} \
3572         -font font_ui \
3573         -command {$ui_diff tag add sel 0.0 end}
3574 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3575 $ctxm add command \
3576         -label {Copy All} \
3577         -font font_ui \
3578         -command {
3579                 $ui_diff tag add sel 0.0 end
3580                 tk_textCopy $ui_diff
3581                 $ui_diff tag remove sel 0.0 end
3582         }
3583 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3584 $ctxm add separator
3585 $ctxm add command \
3586         -label {Decrease Font Size} \
3587         -font font_ui \
3588         -command {incr_font_size font_diff -1}
3589 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3590 $ctxm add command \
3591         -label {Increase Font Size} \
3592         -font font_ui \
3593         -command {incr_font_size font_diff 1}
3594 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3595 $ctxm add separator
3596 $ctxm add command \
3597         -label {Show Less Context} \
3598         -font font_ui \
3599         -command {if {$repo_config(gui.diffcontext) >= 2} {
3600                 incr repo_config(gui.diffcontext) -1
3601                 reshow_diff
3602         }}
3603 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3604 $ctxm add command \
3605         -label {Show More Context} \
3606         -font font_ui \
3607         -command {
3608                 incr repo_config(gui.diffcontext)
3609                 reshow_diff
3610         }
3611 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3612 $ctxm add separator
3613 $ctxm add command -label {Options...} \
3614         -font font_ui \
3615         -command do_options
3616 bind_button3 $ui_diff "tk_popup $ctxm %X %Y"
3617
3618 # -- Status Bar
3619 #
3620 set ui_status_value {Initializing...}
3621 label .status -textvariable ui_status_value \
3622         -anchor w \
3623         -justify left \
3624         -borderwidth 1 \
3625         -relief sunken \
3626         -font font_ui
3627 pack .status -anchor w -side bottom -fill x
3628
3629 # -- Load geometry
3630 #
3631 catch {
3632 set gm $repo_config(gui.geometry)
3633 wm geometry . [lindex $gm 0]
3634 .vpane sash place 0 \
3635         [lindex [.vpane sash coord 0] 0] \
3636         [lindex $gm 1]
3637 .vpane.files sash place 0 \
3638         [lindex $gm 2] \
3639         [lindex [.vpane.files sash coord 0] 1]
3640 unset gm
3641 }
3642
3643 # -- Key Bindings
3644 #
3645 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
3646 bind $ui_comm <$M1B-Key-i> {do_include_all;break}
3647 bind $ui_comm <$M1B-Key-I> {do_include_all;break}
3648 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
3649 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
3650 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
3651 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
3652 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
3653 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
3654 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3655 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3656
3657 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
3658 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
3659 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
3660 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
3661 bind $ui_diff <$M1B-Key-v> {break}
3662 bind $ui_diff <$M1B-Key-V> {break}
3663 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3664 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3665 bind $ui_diff <Key-Up>     {catch {%W yview scroll -1 units};break}
3666 bind $ui_diff <Key-Down>   {catch {%W yview scroll  1 units};break}
3667 bind $ui_diff <Key-Left>   {catch {%W xview scroll -1 units};break}
3668 bind $ui_diff <Key-Right>  {catch {%W xview scroll  1 units};break}
3669
3670 bind .   <Destroy> do_quit
3671 bind all <Key-F5> do_rescan
3672 bind all <$M1B-Key-r> do_rescan
3673 bind all <$M1B-Key-R> do_rescan
3674 bind .   <$M1B-Key-s> do_signoff
3675 bind .   <$M1B-Key-S> do_signoff
3676 bind .   <$M1B-Key-i> do_include_all
3677 bind .   <$M1B-Key-I> do_include_all
3678 bind .   <$M1B-Key-Return> do_commit
3679 bind all <$M1B-Key-q> do_quit
3680 bind all <$M1B-Key-Q> do_quit
3681 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
3682 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
3683 foreach i [list $ui_index $ui_other] {
3684         bind $i <Button-1>       "toggle_or_diff         $i %x %y; break"
3685         bind $i <$M1B-Button-1>  "add_one_to_selection   $i %x %y; break"
3686         bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
3687 }
3688 unset i
3689
3690 set file_lists($ui_index) [list]
3691 set file_lists($ui_other) [list]
3692
3693 set HEAD {}
3694 set PARENT {}
3695 set MERGE_HEAD [list]
3696 set commit_type {}
3697 set empty_tree {}
3698 set current_branch {}
3699 set current_diff {}
3700 set selected_commit_type new
3701
3702 wm title . "$appname ([file normalize [file dirname $gitdir]])"
3703 focus -force $ui_comm
3704
3705 # -- Warn the user about environmental problems.  Cygwin's Tcl
3706 #    does *not* pass its env array onto any processes it spawns.
3707 #    This means that git processes get none of our environment.
3708 #
3709 if {[is_Windows]} {
3710         set ignored_env 0
3711         set suggest_user {}
3712         set msg "Possible environment issues exist.
3713
3714 The following environment variables are probably
3715 going to be ignored by any Git subprocess run
3716 by $appname:
3717
3718 "
3719         foreach name [array names env] {
3720                 switch -regexp -- $name {
3721                 {^GIT_INDEX_FILE$} -
3722                 {^GIT_OBJECT_DIRECTORY$} -
3723                 {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
3724                 {^GIT_DIFF_OPTS$} -
3725                 {^GIT_EXTERNAL_DIFF$} -
3726                 {^GIT_PAGER$} -
3727                 {^GIT_TRACE$} -
3728                 {^GIT_CONFIG$} -
3729                 {^GIT_CONFIG_LOCAL$} -
3730                 {^GIT_(AUTHOR|COMMITTER)_DATE$} {
3731                         append msg " - $name\n"
3732                         incr ignored_env
3733                 }
3734                 {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
3735                         append msg " - $name\n"
3736                         incr ignored_env
3737                         set suggest_user $name
3738                 }
3739                 }
3740         }
3741         if {$ignored_env > 0} {
3742                 append msg "
3743 This is due to a known issue with the
3744 Tcl binary distributed by Cygwin."
3745
3746                 if {$suggest_user ne {}} {
3747                         append msg "
3748
3749 A good replacement for $suggest_user
3750 is placing values for the user.name and
3751 user.email settings into your personal
3752 ~/.gitconfig file.
3753 "
3754                 }
3755                 warn_popup $msg
3756         }
3757         unset ignored_env msg suggest_user name
3758 }
3759
3760 # -- Only initialize complex UI if we are going to stay running.
3761 #
3762 if {!$single_commit} {
3763         load_all_remotes
3764         load_all_heads
3765
3766         populate_branch_menu .mbar.branch
3767         populate_fetch_menu .mbar.fetch
3768         populate_pull_menu .mbar.pull
3769         populate_push_menu .mbar.push
3770 }
3771
3772 lock_index begin-read
3773 after 1 do_rescan