]> asedeno.scripts.mit.edu Git - git.git/blob - git-gui.sh
Merge branch 'maint'
[git.git] / git-gui.sh
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "$@"
4
5 set appvers {@@GITGUI_VERSION@@}
6 set copyright {
7 Copyright © 2006, 2007 Shawn Pearce, et. al.
8
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA}
22
23 ######################################################################
24 ##
25 ## enable verbose loading?
26
27 if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
28         unset _verbose
29         rename auto_load real__auto_load
30         proc auto_load {name args} {
31                 puts stderr "auto_load $name"
32                 return [uplevel 1 real__auto_load $name $args]
33         }
34         rename source real__source
35         proc source {name} {
36                 puts stderr "source    $name"
37                 uplevel 1 real__source $name
38         }
39 }
40
41 ######################################################################
42 ##
43 ## configure our library
44
45 set oguilib {@@GITGUI_LIBDIR@@}
46 set oguirel {@@GITGUI_RELATIVE@@}
47 if {$oguirel eq {1}} {
48         set oguilib [file dirname [file dirname [file normalize $argv0]]]
49         set oguilib [file join $oguilib share git-gui lib]
50 } elseif {[string match @@* $oguirel]} {
51         set oguilib [file join [file dirname [file normalize $argv0]] lib]
52 }
53
54 set idx [file join $oguilib tclIndex]
55 if {[catch {set fd [open $idx r]} err]} {
56         catch {wm withdraw .}
57         tk_messageBox \
58                 -icon error \
59                 -type ok \
60                 -title "git-gui: fatal error" \
61                 -message $err
62         exit 1
63 }
64 if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
65         set idx [list]
66         while {[gets $fd n] >= 0} {
67                 if {$n ne {} && ![string match #* $n]} {
68                         lappend idx $n
69                 }
70         }
71 } else {
72         set idx {}
73 }
74 close $fd
75
76 if {$idx ne {}} {
77         set loaded [list]
78         foreach p $idx {
79                 if {[lsearch -exact $loaded $p] >= 0} continue
80                 source [file join $oguilib $p]
81                 lappend loaded $p
82         }
83         unset loaded p
84 } else {
85         set auto_path [concat [list $oguilib] $auto_path]
86 }
87 unset -nocomplain oguirel idx fd
88
89 ######################################################################
90 ##
91 ## read only globals
92
93 set _appname [lindex [file split $argv0] end]
94 set _gitdir {}
95 set _gitexec {}
96 set _reponame {}
97 set _iscygwin {}
98
99 proc appname {} {
100         global _appname
101         return $_appname
102 }
103
104 proc gitdir {args} {
105         global _gitdir
106         if {$args eq {}} {
107                 return $_gitdir
108         }
109         return [eval [concat [list file join $_gitdir] $args]]
110 }
111
112 proc gitexec {args} {
113         global _gitexec
114         if {$_gitexec eq {}} {
115                 if {[catch {set _gitexec [git --exec-path]} err]} {
116                         error "Git not installed?\n\n$err"
117                 }
118         }
119         if {$args eq {}} {
120                 return $_gitexec
121         }
122         return [eval [concat [list file join $_gitexec] $args]]
123 }
124
125 proc reponame {} {
126         global _reponame
127         return $_reponame
128 }
129
130 proc is_MacOSX {} {
131         global tcl_platform tk_library
132         if {[tk windowingsystem] eq {aqua}} {
133                 return 1
134         }
135         return 0
136 }
137
138 proc is_Windows {} {
139         global tcl_platform
140         if {$tcl_platform(platform) eq {windows}} {
141                 return 1
142         }
143         return 0
144 }
145
146 proc is_Cygwin {} {
147         global tcl_platform _iscygwin
148         if {$_iscygwin eq {}} {
149                 if {$tcl_platform(platform) eq {windows}} {
150                         if {[catch {set p [exec cygpath --windir]} err]} {
151                                 set _iscygwin 0
152                         } else {
153                                 set _iscygwin 1
154                         }
155                 } else {
156                         set _iscygwin 0
157                 }
158         }
159         return $_iscygwin
160 }
161
162 proc is_enabled {option} {
163         global enabled_options
164         if {[catch {set on $enabled_options($option)}]} {return 0}
165         return $on
166 }
167
168 proc enable_option {option} {
169         global enabled_options
170         set enabled_options($option) 1
171 }
172
173 proc disable_option {option} {
174         global enabled_options
175         set enabled_options($option) 0
176 }
177
178 ######################################################################
179 ##
180 ## config
181
182 proc is_many_config {name} {
183         switch -glob -- $name {
184         remote.*.fetch -
185         remote.*.push
186                 {return 1}
187         *
188                 {return 0}
189         }
190 }
191
192 proc is_config_true {name} {
193         global repo_config
194         if {[catch {set v $repo_config($name)}]} {
195                 return 0
196         } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
197                 return 1
198         } else {
199                 return 0
200         }
201 }
202
203 proc get_config {name} {
204         global repo_config
205         if {[catch {set v $repo_config($name)}]} {
206                 return {}
207         } else {
208                 return $v
209         }
210 }
211
212 proc load_config {include_global} {
213         global repo_config global_config default_config
214
215         array unset global_config
216         if {$include_global} {
217                 catch {
218                         set fd_rc [open "| git config --global --list" r]
219                         while {[gets $fd_rc line] >= 0} {
220                                 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
221                                         if {[is_many_config $name]} {
222                                                 lappend global_config($name) $value
223                                         } else {
224                                                 set global_config($name) $value
225                                         }
226                                 }
227                         }
228                         close $fd_rc
229                 }
230         }
231
232         array unset repo_config
233         catch {
234                 set fd_rc [open "| git config --list" r]
235                 while {[gets $fd_rc line] >= 0} {
236                         if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
237                                 if {[is_many_config $name]} {
238                                         lappend repo_config($name) $value
239                                 } else {
240                                         set repo_config($name) $value
241                                 }
242                         }
243                 }
244                 close $fd_rc
245         }
246
247         foreach name [array names default_config] {
248                 if {[catch {set v $global_config($name)}]} {
249                         set global_config($name) $default_config($name)
250                 }
251                 if {[catch {set v $repo_config($name)}]} {
252                         set repo_config($name) $default_config($name)
253                 }
254         }
255 }
256
257 ######################################################################
258 ##
259 ## handy utils
260
261 proc git {args} {
262         return [eval exec git $args]
263 }
264
265 proc current-branch {} {
266         set ref {}
267         set fd [open [gitdir HEAD] r]
268         if {[gets $fd ref] <16
269          || ![regsub {^ref: refs/heads/} $ref {} ref]} {
270                 set ref {}
271         }
272         close $fd
273         return $ref
274 }
275
276 auto_load tk_optionMenu
277 rename tk_optionMenu real__tkOptionMenu
278 proc tk_optionMenu {w varName args} {
279         set m [eval real__tkOptionMenu $w $varName $args]
280         $m configure -font font_ui
281         $w configure -font font_ui
282         return $m
283 }
284
285 ######################################################################
286 ##
287 ## version check
288
289 if {{--version} eq $argv || {version} eq $argv} {
290         puts "git-gui version $appvers"
291         exit
292 }
293
294 set req_maj 1
295 set req_min 5
296
297 if {[catch {set v [git --version]} err]} {
298         catch {wm withdraw .}
299         error_popup "Cannot determine Git version:
300
301 $err
302
303 [appname] requires Git $req_maj.$req_min or later."
304         exit 1
305 }
306 if {[regexp {^git version (\d+)\.(\d+)} $v _junk act_maj act_min]} {
307         if {$act_maj < $req_maj
308                 || ($act_maj == $req_maj && $act_min < $req_min)} {
309                 catch {wm withdraw .}
310                 error_popup "[appname] requires Git $req_maj.$req_min or later.
311
312 You are using $v."
313                 exit 1
314         }
315 } else {
316         catch {wm withdraw .}
317         error_popup "Cannot parse Git version string:\n\n$v"
318         exit 1
319 }
320 unset -nocomplain v _junk act_maj act_min req_maj req_min
321
322 ######################################################################
323 ##
324 ## repository setup
325
326 if {[catch {
327                 set _gitdir $env(GIT_DIR)
328                 set _prefix {}
329                 }]
330         && [catch {
331                 set _gitdir [git rev-parse --git-dir]
332                 set _prefix [git rev-parse --show-prefix]
333         } err]} {
334         catch {wm withdraw .}
335         error_popup "Cannot find the git directory:\n\n$err"
336         exit 1
337 }
338 if {![file isdirectory $_gitdir] && [is_Cygwin]} {
339         catch {set _gitdir [exec cygpath --unix $_gitdir]}
340 }
341 if {![file isdirectory $_gitdir]} {
342         catch {wm withdraw .}
343         error_popup "Git directory not found:\n\n$_gitdir"
344         exit 1
345 }
346 if {[lindex [file split $_gitdir] end] ne {.git}} {
347         catch {wm withdraw .}
348         error_popup "Cannot use funny .git directory:\n\n$_gitdir"
349         exit 1
350 }
351 if {[catch {cd [file dirname $_gitdir]} err]} {
352         catch {wm withdraw .}
353         error_popup "No working directory [file dirname $_gitdir]:\n\n$err"
354         exit 1
355 }
356 set _reponame [lindex [file split \
357         [file normalize [file dirname $_gitdir]]] \
358         end]
359
360 ######################################################################
361 ##
362 ## global init
363
364 set current_diff_path {}
365 set current_diff_side {}
366 set diff_actions [list]
367 set ui_status_value {Initializing...}
368
369 set HEAD {}
370 set PARENT {}
371 set MERGE_HEAD [list]
372 set commit_type {}
373 set empty_tree {}
374 set current_branch {}
375 set current_diff_path {}
376 set selected_commit_type new
377
378 ######################################################################
379 ##
380 ## task management
381
382 set rescan_active 0
383 set diff_active 0
384 set last_clicked {}
385
386 set disable_on_lock [list]
387 set index_lock_type none
388
389 proc lock_index {type} {
390         global index_lock_type disable_on_lock
391
392         if {$index_lock_type eq {none}} {
393                 set index_lock_type $type
394                 foreach w $disable_on_lock {
395                         uplevel #0 $w disabled
396                 }
397                 return 1
398         } elseif {$index_lock_type eq "begin-$type"} {
399                 set index_lock_type $type
400                 return 1
401         }
402         return 0
403 }
404
405 proc unlock_index {} {
406         global index_lock_type disable_on_lock
407
408         set index_lock_type none
409         foreach w $disable_on_lock {
410                 uplevel #0 $w normal
411         }
412 }
413
414 ######################################################################
415 ##
416 ## status
417
418 proc repository_state {ctvar hdvar mhvar} {
419         global current_branch
420         upvar $ctvar ct $hdvar hd $mhvar mh
421
422         set mh [list]
423
424         set current_branch [current-branch]
425         if {[catch {set hd [git rev-parse --verify HEAD]}]} {
426                 set hd {}
427                 set ct initial
428                 return
429         }
430
431         set merge_head [gitdir MERGE_HEAD]
432         if {[file exists $merge_head]} {
433                 set ct merge
434                 set fd_mh [open $merge_head r]
435                 while {[gets $fd_mh line] >= 0} {
436                         lappend mh $line
437                 }
438                 close $fd_mh
439                 return
440         }
441
442         set ct normal
443 }
444
445 proc PARENT {} {
446         global PARENT empty_tree
447
448         set p [lindex $PARENT 0]
449         if {$p ne {}} {
450                 return $p
451         }
452         if {$empty_tree eq {}} {
453                 set empty_tree [git mktree << {}]
454         }
455         return $empty_tree
456 }
457
458 proc rescan {after {honor_trustmtime 1}} {
459         global HEAD PARENT MERGE_HEAD commit_type
460         global ui_index ui_workdir ui_status_value ui_comm
461         global rescan_active file_states
462         global repo_config
463
464         if {$rescan_active > 0 || ![lock_index read]} return
465
466         repository_state newType newHEAD newMERGE_HEAD
467         if {[string match amend* $commit_type]
468                 && $newType eq {normal}
469                 && $newHEAD eq $HEAD} {
470         } else {
471                 set HEAD $newHEAD
472                 set PARENT $newHEAD
473                 set MERGE_HEAD $newMERGE_HEAD
474                 set commit_type $newType
475         }
476
477         array unset file_states
478
479         if {![$ui_comm edit modified]
480                 || [string trim [$ui_comm get 0.0 end]] eq {}} {
481                 if {[load_message GITGUI_MSG]} {
482                 } elseif {[load_message MERGE_MSG]} {
483                 } elseif {[load_message SQUASH_MSG]} {
484                 }
485                 $ui_comm edit reset
486                 $ui_comm edit modified false
487         }
488
489         if {[is_enabled branch]} {
490                 load_all_heads
491                 populate_branch_menu
492         }
493
494         if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
495                 rescan_stage2 {} $after
496         } else {
497                 set rescan_active 1
498                 set ui_status_value {Refreshing file status...}
499                 set cmd [list git update-index]
500                 lappend cmd -q
501                 lappend cmd --unmerged
502                 lappend cmd --ignore-missing
503                 lappend cmd --refresh
504                 set fd_rf [open "| $cmd" r]
505                 fconfigure $fd_rf -blocking 0 -translation binary
506                 fileevent $fd_rf readable \
507                         [list rescan_stage2 $fd_rf $after]
508         }
509 }
510
511 proc rescan_stage2 {fd after} {
512         global ui_status_value
513         global rescan_active buf_rdi buf_rdf buf_rlo
514
515         if {$fd ne {}} {
516                 read $fd
517                 if {![eof $fd]} return
518                 close $fd
519         }
520
521         set ls_others [list | git ls-files --others -z \
522                 --exclude-per-directory=.gitignore]
523         set info_exclude [gitdir info exclude]
524         if {[file readable $info_exclude]} {
525                 lappend ls_others "--exclude-from=$info_exclude"
526         }
527
528         set buf_rdi {}
529         set buf_rdf {}
530         set buf_rlo {}
531
532         set rescan_active 3
533         set ui_status_value {Scanning for modified files ...}
534         set fd_di [open "| git diff-index --cached -z [PARENT]" r]
535         set fd_df [open "| git diff-files -z" r]
536         set fd_lo [open $ls_others r]
537
538         fconfigure $fd_di -blocking 0 -translation binary -encoding binary
539         fconfigure $fd_df -blocking 0 -translation binary -encoding binary
540         fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
541         fileevent $fd_di readable [list read_diff_index $fd_di $after]
542         fileevent $fd_df readable [list read_diff_files $fd_df $after]
543         fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
544 }
545
546 proc load_message {file} {
547         global ui_comm
548
549         set f [gitdir $file]
550         if {[file isfile $f]} {
551                 if {[catch {set fd [open $f r]}]} {
552                         return 0
553                 }
554                 set content [string trim [read $fd]]
555                 close $fd
556                 regsub -all -line {[ \r\t]+$} $content {} content
557                 $ui_comm delete 0.0 end
558                 $ui_comm insert end $content
559                 return 1
560         }
561         return 0
562 }
563
564 proc read_diff_index {fd after} {
565         global buf_rdi
566
567         append buf_rdi [read $fd]
568         set c 0
569         set n [string length $buf_rdi]
570         while {$c < $n} {
571                 set z1 [string first "\0" $buf_rdi $c]
572                 if {$z1 == -1} break
573                 incr z1
574                 set z2 [string first "\0" $buf_rdi $z1]
575                 if {$z2 == -1} break
576
577                 incr c
578                 set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
579                 set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
580                 merge_state \
581                         [encoding convertfrom $p] \
582                         [lindex $i 4]? \
583                         [list [lindex $i 0] [lindex $i 2]] \
584                         [list]
585                 set c $z2
586                 incr c
587         }
588         if {$c < $n} {
589                 set buf_rdi [string range $buf_rdi $c end]
590         } else {
591                 set buf_rdi {}
592         }
593
594         rescan_done $fd buf_rdi $after
595 }
596
597 proc read_diff_files {fd after} {
598         global buf_rdf
599
600         append buf_rdf [read $fd]
601         set c 0
602         set n [string length $buf_rdf]
603         while {$c < $n} {
604                 set z1 [string first "\0" $buf_rdf $c]
605                 if {$z1 == -1} break
606                 incr z1
607                 set z2 [string first "\0" $buf_rdf $z1]
608                 if {$z2 == -1} break
609
610                 incr c
611                 set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
612                 set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
613                 merge_state \
614                         [encoding convertfrom $p] \
615                         ?[lindex $i 4] \
616                         [list] \
617                         [list [lindex $i 0] [lindex $i 2]]
618                 set c $z2
619                 incr c
620         }
621         if {$c < $n} {
622                 set buf_rdf [string range $buf_rdf $c end]
623         } else {
624                 set buf_rdf {}
625         }
626
627         rescan_done $fd buf_rdf $after
628 }
629
630 proc read_ls_others {fd after} {
631         global buf_rlo
632
633         append buf_rlo [read $fd]
634         set pck [split $buf_rlo "\0"]
635         set buf_rlo [lindex $pck end]
636         foreach p [lrange $pck 0 end-1] {
637                 merge_state [encoding convertfrom $p] ?O
638         }
639         rescan_done $fd buf_rlo $after
640 }
641
642 proc rescan_done {fd buf after} {
643         global rescan_active current_diff_path
644         global file_states repo_config
645         upvar $buf to_clear
646
647         if {![eof $fd]} return
648         set to_clear {}
649         close $fd
650         if {[incr rescan_active -1] > 0} return
651
652         prune_selection
653         unlock_index
654         display_all_files
655         if {$current_diff_path ne {}} reshow_diff
656         uplevel #0 $after
657 }
658
659 proc prune_selection {} {
660         global file_states selected_paths
661
662         foreach path [array names selected_paths] {
663                 if {[catch {set still_here $file_states($path)}]} {
664                         unset selected_paths($path)
665                 }
666         }
667 }
668
669 ######################################################################
670 ##
671 ## ui helpers
672
673 proc mapicon {w state path} {
674         global all_icons
675
676         if {[catch {set r $all_icons($state$w)}]} {
677                 puts "error: no icon for $w state={$state} $path"
678                 return file_plain
679         }
680         return $r
681 }
682
683 proc mapdesc {state path} {
684         global all_descs
685
686         if {[catch {set r $all_descs($state)}]} {
687                 puts "error: no desc for state={$state} $path"
688                 return $state
689         }
690         return $r
691 }
692
693 proc escape_path {path} {
694         regsub -all {\\} $path "\\\\" path
695         regsub -all "\n" $path "\\n" path
696         return $path
697 }
698
699 proc short_path {path} {
700         return [escape_path [lindex [file split $path] end]]
701 }
702
703 set next_icon_id 0
704 set null_sha1 [string repeat 0 40]
705
706 proc merge_state {path new_state {head_info {}} {index_info {}}} {
707         global file_states next_icon_id null_sha1
708
709         set s0 [string index $new_state 0]
710         set s1 [string index $new_state 1]
711
712         if {[catch {set info $file_states($path)}]} {
713                 set state __
714                 set icon n[incr next_icon_id]
715         } else {
716                 set state [lindex $info 0]
717                 set icon [lindex $info 1]
718                 if {$head_info eq {}}  {set head_info  [lindex $info 2]}
719                 if {$index_info eq {}} {set index_info [lindex $info 3]}
720         }
721
722         if     {$s0 eq {?}} {set s0 [string index $state 0]} \
723         elseif {$s0 eq {_}} {set s0 _}
724
725         if     {$s1 eq {?}} {set s1 [string index $state 1]} \
726         elseif {$s1 eq {_}} {set s1 _}
727
728         if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
729                 set head_info [list 0 $null_sha1]
730         } elseif {$s0 ne {_} && [string index $state 0] eq {_}
731                 && $head_info eq {}} {
732                 set head_info $index_info
733         }
734
735         set file_states($path) [list $s0$s1 $icon \
736                 $head_info $index_info \
737                 ]
738         return $state
739 }
740
741 proc display_file_helper {w path icon_name old_m new_m} {
742         global file_lists
743
744         if {$new_m eq {_}} {
745                 set lno [lsearch -sorted -exact $file_lists($w) $path]
746                 if {$lno >= 0} {
747                         set file_lists($w) [lreplace $file_lists($w) $lno $lno]
748                         incr lno
749                         $w conf -state normal
750                         $w delete $lno.0 [expr {$lno + 1}].0
751                         $w conf -state disabled
752                 }
753         } elseif {$old_m eq {_} && $new_m ne {_}} {
754                 lappend file_lists($w) $path
755                 set file_lists($w) [lsort -unique $file_lists($w)]
756                 set lno [lsearch -sorted -exact $file_lists($w) $path]
757                 incr lno
758                 $w conf -state normal
759                 $w image create $lno.0 \
760                         -align center -padx 5 -pady 1 \
761                         -name $icon_name \
762                         -image [mapicon $w $new_m $path]
763                 $w insert $lno.1 "[escape_path $path]\n"
764                 $w conf -state disabled
765         } elseif {$old_m ne $new_m} {
766                 $w conf -state normal
767                 $w image conf $icon_name -image [mapicon $w $new_m $path]
768                 $w conf -state disabled
769         }
770 }
771
772 proc display_file {path state} {
773         global file_states selected_paths
774         global ui_index ui_workdir
775
776         set old_m [merge_state $path $state]
777         set s $file_states($path)
778         set new_m [lindex $s 0]
779         set icon_name [lindex $s 1]
780
781         set o [string index $old_m 0]
782         set n [string index $new_m 0]
783         if {$o eq {U}} {
784                 set o _
785         }
786         if {$n eq {U}} {
787                 set n _
788         }
789         display_file_helper     $ui_index $path $icon_name $o $n
790
791         if {[string index $old_m 0] eq {U}} {
792                 set o U
793         } else {
794                 set o [string index $old_m 1]
795         }
796         if {[string index $new_m 0] eq {U}} {
797                 set n U
798         } else {
799                 set n [string index $new_m 1]
800         }
801         display_file_helper     $ui_workdir $path $icon_name $o $n
802
803         if {$new_m eq {__}} {
804                 unset file_states($path)
805                 catch {unset selected_paths($path)}
806         }
807 }
808
809 proc display_all_files_helper {w path icon_name m} {
810         global file_lists
811
812         lappend file_lists($w) $path
813         set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
814         $w image create end \
815                 -align center -padx 5 -pady 1 \
816                 -name $icon_name \
817                 -image [mapicon $w $m $path]
818         $w insert end "[escape_path $path]\n"
819 }
820
821 proc display_all_files {} {
822         global ui_index ui_workdir
823         global file_states file_lists
824         global last_clicked
825
826         $ui_index conf -state normal
827         $ui_workdir conf -state normal
828
829         $ui_index delete 0.0 end
830         $ui_workdir delete 0.0 end
831         set last_clicked {}
832
833         set file_lists($ui_index) [list]
834         set file_lists($ui_workdir) [list]
835
836         foreach path [lsort [array names file_states]] {
837                 set s $file_states($path)
838                 set m [lindex $s 0]
839                 set icon_name [lindex $s 1]
840
841                 set s [string index $m 0]
842                 if {$s ne {U} && $s ne {_}} {
843                         display_all_files_helper $ui_index $path \
844                                 $icon_name $s
845                 }
846
847                 if {[string index $m 0] eq {U}} {
848                         set s U
849                 } else {
850                         set s [string index $m 1]
851                 }
852                 if {$s ne {_}} {
853                         display_all_files_helper $ui_workdir $path \
854                                 $icon_name $s
855                 }
856         }
857
858         $ui_index conf -state disabled
859         $ui_workdir conf -state disabled
860 }
861
862 ######################################################################
863 ##
864 ## icons
865
866 set filemask {
867 #define mask_width 14
868 #define mask_height 15
869 static unsigned char mask_bits[] = {
870    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
871    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
872    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
873 }
874
875 image create bitmap file_plain -background white -foreground black -data {
876 #define plain_width 14
877 #define plain_height 15
878 static unsigned char plain_bits[] = {
879    0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
880    0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
881    0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
882 } -maskdata $filemask
883
884 image create bitmap file_mod -background white -foreground blue -data {
885 #define mod_width 14
886 #define mod_height 15
887 static unsigned char mod_bits[] = {
888    0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
889    0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
890    0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
891 } -maskdata $filemask
892
893 image create bitmap file_fulltick -background white -foreground "#007000" -data {
894 #define file_fulltick_width 14
895 #define file_fulltick_height 15
896 static unsigned char file_fulltick_bits[] = {
897    0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
898    0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
899    0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
900 } -maskdata $filemask
901
902 image create bitmap file_parttick -background white -foreground "#005050" -data {
903 #define parttick_width 14
904 #define parttick_height 15
905 static unsigned char parttick_bits[] = {
906    0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
907    0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
908    0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
909 } -maskdata $filemask
910
911 image create bitmap file_question -background white -foreground black -data {
912 #define file_question_width 14
913 #define file_question_height 15
914 static unsigned char file_question_bits[] = {
915    0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
916    0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
917    0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
918 } -maskdata $filemask
919
920 image create bitmap file_removed -background white -foreground red -data {
921 #define file_removed_width 14
922 #define file_removed_height 15
923 static unsigned char file_removed_bits[] = {
924    0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
925    0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
926    0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
927 } -maskdata $filemask
928
929 image create bitmap file_merge -background white -foreground blue -data {
930 #define file_merge_width 14
931 #define file_merge_height 15
932 static unsigned char file_merge_bits[] = {
933    0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
934    0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
935    0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
936 } -maskdata $filemask
937
938 set file_dir_data {
939 #define file_width 18
940 #define file_height 18
941 static unsigned char file_bits[] = {
942   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00,
943   0x0c, 0x03, 0x00, 0x04, 0xfe, 0x00, 0x06, 0x80, 0x00, 0xff, 0x9f, 0x00,
944   0x03, 0x98, 0x00, 0x02, 0x90, 0x00, 0x06, 0xb0, 0x00, 0x04, 0xa0, 0x00,
945   0x0c, 0xe0, 0x00, 0x08, 0xc0, 0x00, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00,
946   0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
947 }
948 image create bitmap file_dir -background white -foreground blue \
949         -data $file_dir_data -maskdata $file_dir_data
950 unset file_dir_data
951
952 set file_uplevel_data {
953 #define up_width 15
954 #define up_height 15
955 static unsigned char up_bits[] = {
956   0x80, 0x00, 0xc0, 0x01, 0xe0, 0x03, 0xf0, 0x07, 0xf8, 0x0f, 0xfc, 0x1f,
957   0xfe, 0x3f, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01,
958   0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00};
959 }
960 image create bitmap file_uplevel -background white -foreground red \
961         -data $file_uplevel_data -maskdata $file_uplevel_data
962 unset file_uplevel_data
963
964 set ui_index .vpane.files.index.list
965 set ui_workdir .vpane.files.workdir.list
966
967 set all_icons(_$ui_index)   file_plain
968 set all_icons(A$ui_index)   file_fulltick
969 set all_icons(M$ui_index)   file_fulltick
970 set all_icons(D$ui_index)   file_removed
971 set all_icons(U$ui_index)   file_merge
972
973 set all_icons(_$ui_workdir) file_plain
974 set all_icons(M$ui_workdir) file_mod
975 set all_icons(D$ui_workdir) file_question
976 set all_icons(U$ui_workdir) file_merge
977 set all_icons(O$ui_workdir) file_plain
978
979 set max_status_desc 0
980 foreach i {
981                 {__ "Unmodified"}
982
983                 {_M "Modified, not staged"}
984                 {M_ "Staged for commit"}
985                 {MM "Portions staged for commit"}
986                 {MD "Staged for commit, missing"}
987
988                 {_O "Untracked, not staged"}
989                 {A_ "Staged for commit"}
990                 {AM "Portions staged for commit"}
991                 {AD "Staged for commit, missing"}
992
993                 {_D "Missing"}
994                 {D_ "Staged for removal"}
995                 {DO "Staged for removal, still present"}
996
997                 {U_ "Requires merge resolution"}
998                 {UU "Requires merge resolution"}
999                 {UM "Requires merge resolution"}
1000                 {UD "Requires merge resolution"}
1001         } {
1002         if {$max_status_desc < [string length [lindex $i 1]]} {
1003                 set max_status_desc [string length [lindex $i 1]]
1004         }
1005         set all_descs([lindex $i 0]) [lindex $i 1]
1006 }
1007 unset i
1008
1009 ######################################################################
1010 ##
1011 ## util
1012
1013 proc bind_button3 {w cmd} {
1014         bind $w <Any-Button-3> $cmd
1015         if {[is_MacOSX]} {
1016                 bind $w <Control-Button-1> $cmd
1017         }
1018 }
1019
1020 proc scrollbar2many {list mode args} {
1021         foreach w $list {eval $w $mode $args}
1022 }
1023
1024 proc many2scrollbar {list mode sb top bottom} {
1025         $sb set $top $bottom
1026         foreach w $list {$w $mode moveto $top}
1027 }
1028
1029 proc incr_font_size {font {amt 1}} {
1030         set sz [font configure $font -size]
1031         incr sz $amt
1032         font configure $font -size $sz
1033         font configure ${font}bold -size $sz
1034 }
1035
1036 ######################################################################
1037 ##
1038 ## ui commands
1039
1040 set starting_gitk_msg {Starting gitk... please wait...}
1041
1042 proc do_gitk {revs} {
1043         global env ui_status_value starting_gitk_msg
1044
1045         # -- Always start gitk through whatever we were loaded with.  This
1046         #    lets us bypass using shell process on Windows systems.
1047         #
1048         set cmd [list [info nameofexecutable]]
1049         lappend cmd [gitexec gitk]
1050         if {$revs ne {}} {
1051                 append cmd { }
1052                 append cmd $revs
1053         }
1054
1055         if {[catch {eval exec $cmd &} err]} {
1056                 error_popup "Failed to start gitk:\n\n$err"
1057         } else {
1058                 set ui_status_value $starting_gitk_msg
1059                 after 10000 {
1060                         if {$ui_status_value eq $starting_gitk_msg} {
1061                                 set ui_status_value {Ready.}
1062                         }
1063                 }
1064         }
1065 }
1066
1067 set is_quitting 0
1068
1069 proc do_quit {} {
1070         global ui_comm is_quitting repo_config commit_type
1071
1072         if {$is_quitting} return
1073         set is_quitting 1
1074
1075         if {[winfo exists $ui_comm]} {
1076                 # -- Stash our current commit buffer.
1077                 #
1078                 set save [gitdir GITGUI_MSG]
1079                 set msg [string trim [$ui_comm get 0.0 end]]
1080                 regsub -all -line {[ \r\t]+$} $msg {} msg
1081                 if {(![string match amend* $commit_type]
1082                         || [$ui_comm edit modified])
1083                         && $msg ne {}} {
1084                         catch {
1085                                 set fd [open $save w]
1086                                 puts -nonewline $fd $msg
1087                                 close $fd
1088                         }
1089                 } else {
1090                         catch {file delete $save}
1091                 }
1092
1093                 # -- Stash our current window geometry into this repository.
1094                 #
1095                 set cfg_geometry [list]
1096                 lappend cfg_geometry [wm geometry .]
1097                 lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
1098                 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
1099                 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1100                         set rc_geometry {}
1101                 }
1102                 if {$cfg_geometry ne $rc_geometry} {
1103                         catch {git config gui.geometry $cfg_geometry}
1104                 }
1105         }
1106
1107         destroy .
1108 }
1109
1110 proc do_rescan {} {
1111         rescan {set ui_status_value {Ready.}}
1112 }
1113
1114 proc do_commit {} {
1115         commit_tree
1116 }
1117
1118 proc toggle_or_diff {w x y} {
1119         global file_states file_lists current_diff_path ui_index ui_workdir
1120         global last_clicked selected_paths
1121
1122         set pos [split [$w index @$x,$y] .]
1123         set lno [lindex $pos 0]
1124         set col [lindex $pos 1]
1125         set path [lindex $file_lists($w) [expr {$lno - 1}]]
1126         if {$path eq {}} {
1127                 set last_clicked {}
1128                 return
1129         }
1130
1131         set last_clicked [list $w $lno]
1132         array unset selected_paths
1133         $ui_index tag remove in_sel 0.0 end
1134         $ui_workdir tag remove in_sel 0.0 end
1135
1136         if {$col == 0} {
1137                 if {$current_diff_path eq $path} {
1138                         set after {reshow_diff;}
1139                 } else {
1140                         set after {}
1141                 }
1142                 if {$w eq $ui_index} {
1143                         update_indexinfo \
1144                                 "Unstaging [short_path $path] from commit" \
1145                                 [list $path] \
1146                                 [concat $after {set ui_status_value {Ready.}}]
1147                 } elseif {$w eq $ui_workdir} {
1148                         update_index \
1149                                 "Adding [short_path $path]" \
1150                                 [list $path] \
1151                                 [concat $after {set ui_status_value {Ready.}}]
1152                 }
1153         } else {
1154                 show_diff $path $w $lno
1155         }
1156 }
1157
1158 proc add_one_to_selection {w x y} {
1159         global file_lists last_clicked selected_paths
1160
1161         set lno [lindex [split [$w index @$x,$y] .] 0]
1162         set path [lindex $file_lists($w) [expr {$lno - 1}]]
1163         if {$path eq {}} {
1164                 set last_clicked {}
1165                 return
1166         }
1167
1168         if {$last_clicked ne {}
1169                 && [lindex $last_clicked 0] ne $w} {
1170                 array unset selected_paths
1171                 [lindex $last_clicked 0] tag remove in_sel 0.0 end
1172         }
1173
1174         set last_clicked [list $w $lno]
1175         if {[catch {set in_sel $selected_paths($path)}]} {
1176                 set in_sel 0
1177         }
1178         if {$in_sel} {
1179                 unset selected_paths($path)
1180                 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
1181         } else {
1182                 set selected_paths($path) 1
1183                 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
1184         }
1185 }
1186
1187 proc add_range_to_selection {w x y} {
1188         global file_lists last_clicked selected_paths
1189
1190         if {[lindex $last_clicked 0] ne $w} {
1191                 toggle_or_diff $w $x $y
1192                 return
1193         }
1194
1195         set lno [lindex [split [$w index @$x,$y] .] 0]
1196         set lc [lindex $last_clicked 1]
1197         if {$lc < $lno} {
1198                 set begin $lc
1199                 set end $lno
1200         } else {
1201                 set begin $lno
1202                 set end $lc
1203         }
1204
1205         foreach path [lrange $file_lists($w) \
1206                 [expr {$begin - 1}] \
1207                 [expr {$end - 1}]] {
1208                 set selected_paths($path) 1
1209         }
1210         $w tag add in_sel $begin.0 [expr {$end + 1}].0
1211 }
1212
1213 ######################################################################
1214 ##
1215 ## config defaults
1216
1217 set cursor_ptr arrow
1218 font create font_diff -family Courier -size 10
1219 font create font_ui
1220 catch {
1221         label .dummy
1222         eval font configure font_ui [font actual [.dummy cget -font]]
1223         destroy .dummy
1224 }
1225
1226 font create font_uibold
1227 font create font_diffbold
1228
1229 foreach class {Button Checkbutton Entry Label
1230                 Labelframe Listbox Menu Message
1231                 Radiobutton Text} {
1232         option add *$class.font font_ui
1233 }
1234 unset class
1235
1236 if {[is_Windows] || [is_MacOSX]} {
1237         option add *Menu.tearOff 0
1238 }
1239
1240 if {[is_MacOSX]} {
1241         set M1B M1
1242         set M1T Cmd
1243 } else {
1244         set M1B Control
1245         set M1T Ctrl
1246 }
1247
1248 proc apply_config {} {
1249         global repo_config font_descs
1250
1251         foreach option $font_descs {
1252                 set name [lindex $option 0]
1253                 set font [lindex $option 1]
1254                 if {[catch {
1255                         foreach {cn cv} $repo_config(gui.$name) {
1256                                 font configure $font $cn $cv
1257                         }
1258                         } err]} {
1259                         error_popup "Invalid font specified in gui.$name:\n\n$err"
1260                 }
1261                 foreach {cn cv} [font configure $font] {
1262                         font configure ${font}bold $cn $cv
1263                 }
1264                 font configure ${font}bold -weight bold
1265         }
1266 }
1267
1268 set default_config(merge.diffstat) true
1269 set default_config(merge.summary) false
1270 set default_config(merge.verbosity) 2
1271 set default_config(user.name) {}
1272 set default_config(user.email) {}
1273
1274 set default_config(gui.pruneduringfetch) false
1275 set default_config(gui.trustmtime) false
1276 set default_config(gui.diffcontext) 5
1277 set default_config(gui.newbranchtemplate) {}
1278 set default_config(gui.fontui) [font configure font_ui]
1279 set default_config(gui.fontdiff) [font configure font_diff]
1280 set font_descs {
1281         {fontui   font_ui   {Main Font}}
1282         {fontdiff font_diff {Diff/Console Font}}
1283 }
1284 load_config 0
1285 apply_config
1286
1287 ######################################################################
1288 ##
1289 ## feature option selection
1290
1291 if {[regexp {^git-(.+)$} [appname] _junk subcommand]} {
1292         unset _junk
1293 } else {
1294         set subcommand gui
1295 }
1296 if {$subcommand eq {gui.sh}} {
1297         set subcommand gui
1298 }
1299 if {$subcommand eq {gui} && [llength $argv] > 0} {
1300         set subcommand [lindex $argv 0]
1301         set argv [lrange $argv 1 end]
1302 }
1303
1304 enable_option multicommit
1305 enable_option branch
1306 enable_option transport
1307
1308 switch -- $subcommand {
1309 browser -
1310 blame {
1311         disable_option multicommit
1312         disable_option branch
1313         disable_option transport
1314 }
1315 citool {
1316         enable_option singlecommit
1317
1318         disable_option multicommit
1319         disable_option branch
1320         disable_option transport
1321 }
1322 }
1323
1324 ######################################################################
1325 ##
1326 ## ui construction
1327
1328 set ui_comm {}
1329
1330 # -- Menu Bar
1331 #
1332 menu .mbar -tearoff 0
1333 .mbar add cascade -label Repository -menu .mbar.repository
1334 .mbar add cascade -label Edit -menu .mbar.edit
1335 if {[is_enabled branch]} {
1336         .mbar add cascade -label Branch -menu .mbar.branch
1337 }
1338 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1339         .mbar add cascade -label Commit -menu .mbar.commit
1340 }
1341 if {[is_enabled transport]} {
1342         .mbar add cascade -label Merge -menu .mbar.merge
1343         .mbar add cascade -label Fetch -menu .mbar.fetch
1344         .mbar add cascade -label Push -menu .mbar.push
1345 }
1346 . configure -menu .mbar
1347
1348 # -- Repository Menu
1349 #
1350 menu .mbar.repository
1351
1352 .mbar.repository add command \
1353         -label {Browse Current Branch} \
1354         -command {browser::new $current_branch}
1355 trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#"
1356 .mbar.repository add separator
1357
1358 .mbar.repository add command \
1359         -label {Visualize Current Branch} \
1360         -command {do_gitk $current_branch}
1361 trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch\" ;#"
1362 .mbar.repository add command \
1363         -label {Visualize All Branches} \
1364         -command {do_gitk --all}
1365 .mbar.repository add separator
1366
1367 if {[is_enabled multicommit]} {
1368         .mbar.repository add command -label {Database Statistics} \
1369                 -command do_stats
1370
1371         .mbar.repository add command -label {Compress Database} \
1372                 -command do_gc
1373
1374         .mbar.repository add command -label {Verify Database} \
1375                 -command do_fsck_objects
1376
1377         .mbar.repository add separator
1378
1379         if {[is_Cygwin]} {
1380                 .mbar.repository add command \
1381                         -label {Create Desktop Icon} \
1382                         -command do_cygwin_shortcut
1383         } elseif {[is_Windows]} {
1384                 .mbar.repository add command \
1385                         -label {Create Desktop Icon} \
1386                         -command do_windows_shortcut
1387         } elseif {[is_MacOSX]} {
1388                 .mbar.repository add command \
1389                         -label {Create Desktop Icon} \
1390                         -command do_macosx_app
1391         }
1392 }
1393
1394 .mbar.repository add command -label Quit \
1395         -command do_quit \
1396         -accelerator $M1T-Q
1397
1398 # -- Edit Menu
1399 #
1400 menu .mbar.edit
1401 .mbar.edit add command -label Undo \
1402         -command {catch {[focus] edit undo}} \
1403         -accelerator $M1T-Z
1404 .mbar.edit add command -label Redo \
1405         -command {catch {[focus] edit redo}} \
1406         -accelerator $M1T-Y
1407 .mbar.edit add separator
1408 .mbar.edit add command -label Cut \
1409         -command {catch {tk_textCut [focus]}} \
1410         -accelerator $M1T-X
1411 .mbar.edit add command -label Copy \
1412         -command {catch {tk_textCopy [focus]}} \
1413         -accelerator $M1T-C
1414 .mbar.edit add command -label Paste \
1415         -command {catch {tk_textPaste [focus]; [focus] see insert}} \
1416         -accelerator $M1T-V
1417 .mbar.edit add command -label Delete \
1418         -command {catch {[focus] delete sel.first sel.last}} \
1419         -accelerator Del
1420 .mbar.edit add separator
1421 .mbar.edit add command -label {Select All} \
1422         -command {catch {[focus] tag add sel 0.0 end}} \
1423         -accelerator $M1T-A
1424
1425 # -- Branch Menu
1426 #
1427 if {[is_enabled branch]} {
1428         menu .mbar.branch
1429
1430         .mbar.branch add command -label {Create...} \
1431                 -command do_create_branch \
1432                 -accelerator $M1T-N
1433         lappend disable_on_lock [list .mbar.branch entryconf \
1434                 [.mbar.branch index last] -state]
1435
1436         .mbar.branch add command -label {Rename...} \
1437                 -command branch_rename::dialog
1438         lappend disable_on_lock [list .mbar.branch entryconf \
1439                 [.mbar.branch index last] -state]
1440
1441         .mbar.branch add command -label {Delete...} \
1442                 -command do_delete_branch
1443         lappend disable_on_lock [list .mbar.branch entryconf \
1444                 [.mbar.branch index last] -state]
1445
1446         .mbar.branch add command -label {Reset...} \
1447                 -command merge::reset_hard
1448         lappend disable_on_lock [list .mbar.branch entryconf \
1449                 [.mbar.branch index last] -state]
1450 }
1451
1452 # -- Commit Menu
1453 #
1454 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1455         menu .mbar.commit
1456
1457         .mbar.commit add radiobutton \
1458                 -label {New Commit} \
1459                 -command do_select_commit_type \
1460                 -variable selected_commit_type \
1461                 -value new
1462         lappend disable_on_lock \
1463                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1464
1465         .mbar.commit add radiobutton \
1466                 -label {Amend Last Commit} \
1467                 -command do_select_commit_type \
1468                 -variable selected_commit_type \
1469                 -value amend
1470         lappend disable_on_lock \
1471                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1472
1473         .mbar.commit add separator
1474
1475         .mbar.commit add command -label Rescan \
1476                 -command do_rescan \
1477                 -accelerator F5
1478         lappend disable_on_lock \
1479                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1480
1481         .mbar.commit add command -label {Add To Commit} \
1482                 -command do_add_selection
1483         lappend disable_on_lock \
1484                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1485
1486         .mbar.commit add command -label {Add Existing To Commit} \
1487                 -command do_add_all \
1488                 -accelerator $M1T-I
1489         lappend disable_on_lock \
1490                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1491
1492         .mbar.commit add command -label {Unstage From Commit} \
1493                 -command do_unstage_selection
1494         lappend disable_on_lock \
1495                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1496
1497         .mbar.commit add command -label {Revert Changes} \
1498                 -command do_revert_selection
1499         lappend disable_on_lock \
1500                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1501
1502         .mbar.commit add separator
1503
1504         .mbar.commit add command -label {Sign Off} \
1505                 -command do_signoff \
1506                 -accelerator $M1T-S
1507
1508         .mbar.commit add command -label Commit \
1509                 -command do_commit \
1510                 -accelerator $M1T-Return
1511         lappend disable_on_lock \
1512                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1513 }
1514
1515 # -- Merge Menu
1516 #
1517 if {[is_enabled branch]} {
1518         menu .mbar.merge
1519         .mbar.merge add command -label {Local Merge...} \
1520                 -command merge::dialog
1521         lappend disable_on_lock \
1522                 [list .mbar.merge entryconf [.mbar.merge index last] -state]
1523         .mbar.merge add command -label {Abort Merge...} \
1524                 -command merge::reset_hard
1525         lappend disable_on_lock \
1526                 [list .mbar.merge entryconf [.mbar.merge index last] -state]
1527
1528 }
1529
1530 # -- Transport Menu
1531 #
1532 if {[is_enabled transport]} {
1533         menu .mbar.fetch
1534
1535         menu .mbar.push
1536         .mbar.push add command -label {Push...} \
1537                 -command do_push_anywhere
1538         .mbar.push add command -label {Delete...} \
1539                 -command remote_branch_delete::dialog
1540 }
1541
1542 if {[is_MacOSX]} {
1543         # -- Apple Menu (Mac OS X only)
1544         #
1545         .mbar add cascade -label Apple -menu .mbar.apple
1546         menu .mbar.apple
1547
1548         .mbar.apple add command -label "About [appname]" \
1549                 -command do_about
1550         .mbar.apple add command -label "Options..." \
1551                 -command do_options
1552 } else {
1553         # -- Edit Menu
1554         #
1555         .mbar.edit add separator
1556         .mbar.edit add command -label {Options...} \
1557                 -command do_options
1558
1559         # -- Tools Menu
1560         #
1561         if {[file exists /usr/local/miga/lib/gui-miga]
1562                 && [file exists .pvcsrc]} {
1563         proc do_miga {} {
1564                 global ui_status_value
1565                 if {![lock_index update]} return
1566                 set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""]
1567                 set miga_fd [open "|$cmd" r]
1568                 fconfigure $miga_fd -blocking 0
1569                 fileevent $miga_fd readable [list miga_done $miga_fd]
1570                 set ui_status_value {Running miga...}
1571         }
1572         proc miga_done {fd} {
1573                 read $fd 512
1574                 if {[eof $fd]} {
1575                         close $fd
1576                         unlock_index
1577                         rescan [list set ui_status_value {Ready.}]
1578                 }
1579         }
1580         .mbar add cascade -label Tools -menu .mbar.tools
1581         menu .mbar.tools
1582         .mbar.tools add command -label "Migrate" \
1583                 -command do_miga
1584         lappend disable_on_lock \
1585                 [list .mbar.tools entryconf [.mbar.tools index last] -state]
1586         }
1587 }
1588
1589 # -- Help Menu
1590 #
1591 .mbar add cascade -label Help -menu .mbar.help
1592 menu .mbar.help
1593
1594 if {![is_MacOSX]} {
1595         .mbar.help add command -label "About [appname]" \
1596                 -command do_about
1597 }
1598
1599 set browser {}
1600 catch {set browser $repo_config(instaweb.browser)}
1601 set doc_path [file dirname [gitexec]]
1602 set doc_path [file join $doc_path Documentation index.html]
1603
1604 if {[is_Cygwin]} {
1605         set doc_path [exec cygpath --mixed $doc_path]
1606 }
1607
1608 if {$browser eq {}} {
1609         if {[is_MacOSX]} {
1610                 set browser open
1611         } elseif {[is_Cygwin]} {
1612                 set program_files [file dirname [exec cygpath --windir]]
1613                 set program_files [file join $program_files {Program Files}]
1614                 set firefox [file join $program_files {Mozilla Firefox} firefox.exe]
1615                 set ie [file join $program_files {Internet Explorer} IEXPLORE.EXE]
1616                 if {[file exists $firefox]} {
1617                         set browser $firefox
1618                 } elseif {[file exists $ie]} {
1619                         set browser $ie
1620                 }
1621                 unset program_files firefox ie
1622         }
1623 }
1624
1625 if {[file isfile $doc_path]} {
1626         set doc_url "file:$doc_path"
1627 } else {
1628         set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
1629 }
1630
1631 if {$browser ne {}} {
1632         .mbar.help add command -label {Online Documentation} \
1633                 -command [list exec $browser $doc_url &]
1634 }
1635 unset browser doc_path doc_url
1636
1637 # -- Standard bindings
1638 #
1639 bind .   <Destroy> {if {{%W} eq {.}} do_quit}
1640 bind all <$M1B-Key-q> do_quit
1641 bind all <$M1B-Key-Q> do_quit
1642 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
1643 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
1644
1645 set subcommand_args {}
1646 proc usage {} {
1647         puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
1648         exit 1
1649 }
1650
1651 # -- Not a normal commit type invocation?  Do that instead!
1652 #
1653 switch -- $subcommand {
1654 browser {
1655         set subcommand_args {rev?}
1656         switch [llength $argv] {
1657         0 { set current_branch [current-branch] }
1658         1 { set current_branch [lindex $argv 0] }
1659         default usage
1660         }
1661         browser::new $current_branch
1662         return
1663 }
1664 blame {
1665         set subcommand_args {rev? path?}
1666         set head {}
1667         set path {}
1668         set is_path 0
1669         foreach a $argv {
1670                 if {$is_path || [file exists $_prefix$a]} {
1671                         if {$path ne {}} usage
1672                         set path $_prefix$a
1673                         break
1674                 } elseif {$a eq {--}} {
1675                         if {$path ne {}} {
1676                                 if {$head ne {}} usage
1677                                 set head $path
1678                                 set path {}
1679                         }
1680                         set is_path 1
1681                 } elseif {$head eq {}} {
1682                         if {$head ne {}} usage
1683                         set head $a
1684                 } else {
1685                         usage
1686                 }
1687         }
1688         unset is_path
1689
1690         if {$head eq {}} {
1691                 set current_branch [current-branch]
1692         } else {
1693                 set current_branch $head
1694         }
1695
1696         if {$path eq {}} usage
1697         blame::new $head $path
1698         return
1699 }
1700 citool -
1701 gui {
1702         if {[llength $argv] != 0} {
1703                 puts -nonewline stderr "usage: $argv0"
1704                 if {$subcommand ne {gui} && [appname] ne "git-$subcommand"} {
1705                         puts -nonewline stderr " $subcommand"
1706                 }
1707                 puts stderr {}
1708                 exit 1
1709         }
1710         # fall through to setup UI for commits
1711 }
1712 default {
1713         puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
1714         exit 1
1715 }
1716 }
1717
1718 # -- Branch Control
1719 #
1720 frame .branch \
1721         -borderwidth 1 \
1722         -relief sunken
1723 label .branch.l1 \
1724         -text {Current Branch:} \
1725         -anchor w \
1726         -justify left
1727 label .branch.cb \
1728         -textvariable current_branch \
1729         -anchor w \
1730         -justify left
1731 pack .branch.l1 -side left
1732 pack .branch.cb -side left -fill x
1733 pack .branch -side top -fill x
1734
1735 # -- Main Window Layout
1736 #
1737 panedwindow .vpane -orient vertical
1738 panedwindow .vpane.files -orient horizontal
1739 .vpane add .vpane.files -sticky nsew -height 100 -width 200
1740 pack .vpane -anchor n -side top -fill both -expand 1
1741
1742 # -- Index File List
1743 #
1744 frame .vpane.files.index -height 100 -width 200
1745 label .vpane.files.index.title -text {Staged Changes (Will Be Committed)} \
1746         -background green
1747 text $ui_index -background white -borderwidth 0 \
1748         -width 20 -height 10 \
1749         -wrap none \
1750         -cursor $cursor_ptr \
1751         -xscrollcommand {.vpane.files.index.sx set} \
1752         -yscrollcommand {.vpane.files.index.sy set} \
1753         -state disabled
1754 scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
1755 scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
1756 pack .vpane.files.index.title -side top -fill x
1757 pack .vpane.files.index.sx -side bottom -fill x
1758 pack .vpane.files.index.sy -side right -fill y
1759 pack $ui_index -side left -fill both -expand 1
1760 .vpane.files add .vpane.files.index -sticky nsew
1761
1762 # -- Working Directory File List
1763 #
1764 frame .vpane.files.workdir -height 100 -width 200
1765 label .vpane.files.workdir.title -text {Unstaged Changes (Will Not Be Committed)} \
1766         -background red
1767 text $ui_workdir -background white -borderwidth 0 \
1768         -width 20 -height 10 \
1769         -wrap none \
1770         -cursor $cursor_ptr \
1771         -xscrollcommand {.vpane.files.workdir.sx set} \
1772         -yscrollcommand {.vpane.files.workdir.sy set} \
1773         -state disabled
1774 scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
1775 scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
1776 pack .vpane.files.workdir.title -side top -fill x
1777 pack .vpane.files.workdir.sx -side bottom -fill x
1778 pack .vpane.files.workdir.sy -side right -fill y
1779 pack $ui_workdir -side left -fill both -expand 1
1780 .vpane.files add .vpane.files.workdir -sticky nsew
1781
1782 foreach i [list $ui_index $ui_workdir] {
1783         $i tag conf in_diff -font font_uibold
1784         $i tag conf in_sel \
1785                 -background [$i cget -foreground] \
1786                 -foreground [$i cget -background]
1787 }
1788 unset i
1789
1790 # -- Diff and Commit Area
1791 #
1792 frame .vpane.lower -height 300 -width 400
1793 frame .vpane.lower.commarea
1794 frame .vpane.lower.diff -relief sunken -borderwidth 1
1795 pack .vpane.lower.commarea -side top -fill x
1796 pack .vpane.lower.diff -side bottom -fill both -expand 1
1797 .vpane add .vpane.lower -sticky nsew
1798
1799 # -- Commit Area Buttons
1800 #
1801 frame .vpane.lower.commarea.buttons
1802 label .vpane.lower.commarea.buttons.l -text {} \
1803         -anchor w \
1804         -justify left
1805 pack .vpane.lower.commarea.buttons.l -side top -fill x
1806 pack .vpane.lower.commarea.buttons -side left -fill y
1807
1808 button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
1809         -command do_rescan
1810 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
1811 lappend disable_on_lock \
1812         {.vpane.lower.commarea.buttons.rescan conf -state}
1813
1814 button .vpane.lower.commarea.buttons.incall -text {Add Existing} \
1815         -command do_add_all
1816 pack .vpane.lower.commarea.buttons.incall -side top -fill x
1817 lappend disable_on_lock \
1818         {.vpane.lower.commarea.buttons.incall conf -state}
1819
1820 button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
1821         -command do_signoff
1822 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
1823
1824 button .vpane.lower.commarea.buttons.commit -text {Commit} \
1825         -command do_commit
1826 pack .vpane.lower.commarea.buttons.commit -side top -fill x
1827 lappend disable_on_lock \
1828         {.vpane.lower.commarea.buttons.commit conf -state}
1829
1830 # -- Commit Message Buffer
1831 #
1832 frame .vpane.lower.commarea.buffer
1833 frame .vpane.lower.commarea.buffer.header
1834 set ui_comm .vpane.lower.commarea.buffer.t
1835 set ui_coml .vpane.lower.commarea.buffer.header.l
1836 radiobutton .vpane.lower.commarea.buffer.header.new \
1837         -text {New Commit} \
1838         -command do_select_commit_type \
1839         -variable selected_commit_type \
1840         -value new
1841 lappend disable_on_lock \
1842         [list .vpane.lower.commarea.buffer.header.new conf -state]
1843 radiobutton .vpane.lower.commarea.buffer.header.amend \
1844         -text {Amend Last Commit} \
1845         -command do_select_commit_type \
1846         -variable selected_commit_type \
1847         -value amend
1848 lappend disable_on_lock \
1849         [list .vpane.lower.commarea.buffer.header.amend conf -state]
1850 label $ui_coml \
1851         -anchor w \
1852         -justify left
1853 proc trace_commit_type {varname args} {
1854         global ui_coml commit_type
1855         switch -glob -- $commit_type {
1856         initial       {set txt {Initial Commit Message:}}
1857         amend         {set txt {Amended Commit Message:}}
1858         amend-initial {set txt {Amended Initial Commit Message:}}
1859         amend-merge   {set txt {Amended Merge Commit Message:}}
1860         merge         {set txt {Merge Commit Message:}}
1861         *             {set txt {Commit Message:}}
1862         }
1863         $ui_coml conf -text $txt
1864 }
1865 trace add variable commit_type write trace_commit_type
1866 pack $ui_coml -side left -fill x
1867 pack .vpane.lower.commarea.buffer.header.amend -side right
1868 pack .vpane.lower.commarea.buffer.header.new -side right
1869
1870 text $ui_comm -background white -borderwidth 1 \
1871         -undo true \
1872         -maxundo 20 \
1873         -autoseparators true \
1874         -relief sunken \
1875         -width 75 -height 9 -wrap none \
1876         -font font_diff \
1877         -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
1878 scrollbar .vpane.lower.commarea.buffer.sby \
1879         -command [list $ui_comm yview]
1880 pack .vpane.lower.commarea.buffer.header -side top -fill x
1881 pack .vpane.lower.commarea.buffer.sby -side right -fill y
1882 pack $ui_comm -side left -fill y
1883 pack .vpane.lower.commarea.buffer -side left -fill y
1884
1885 # -- Commit Message Buffer Context Menu
1886 #
1887 set ctxm .vpane.lower.commarea.buffer.ctxm
1888 menu $ctxm -tearoff 0
1889 $ctxm add command \
1890         -label {Cut} \
1891         -command {tk_textCut $ui_comm}
1892 $ctxm add command \
1893         -label {Copy} \
1894         -command {tk_textCopy $ui_comm}
1895 $ctxm add command \
1896         -label {Paste} \
1897         -command {tk_textPaste $ui_comm}
1898 $ctxm add command \
1899         -label {Delete} \
1900         -command {$ui_comm delete sel.first sel.last}
1901 $ctxm add separator
1902 $ctxm add command \
1903         -label {Select All} \
1904         -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
1905 $ctxm add command \
1906         -label {Copy All} \
1907         -command {
1908                 $ui_comm tag add sel 0.0 end
1909                 tk_textCopy $ui_comm
1910                 $ui_comm tag remove sel 0.0 end
1911         }
1912 $ctxm add separator
1913 $ctxm add command \
1914         -label {Sign Off} \
1915         -command do_signoff
1916 bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
1917
1918 # -- Diff Header
1919 #
1920 proc trace_current_diff_path {varname args} {
1921         global current_diff_path diff_actions file_states
1922         if {$current_diff_path eq {}} {
1923                 set s {}
1924                 set f {}
1925                 set p {}
1926                 set o disabled
1927         } else {
1928                 set p $current_diff_path
1929                 set s [mapdesc [lindex $file_states($p) 0] $p]
1930                 set f {File:}
1931                 set p [escape_path $p]
1932                 set o normal
1933         }
1934
1935         .vpane.lower.diff.header.status configure -text $s
1936         .vpane.lower.diff.header.file configure -text $f
1937         .vpane.lower.diff.header.path configure -text $p
1938         foreach w $diff_actions {
1939                 uplevel #0 $w $o
1940         }
1941 }
1942 trace add variable current_diff_path write trace_current_diff_path
1943
1944 frame .vpane.lower.diff.header -background orange
1945 label .vpane.lower.diff.header.status \
1946         -background orange \
1947         -width $max_status_desc \
1948         -anchor w \
1949         -justify left
1950 label .vpane.lower.diff.header.file \
1951         -background orange \
1952         -anchor w \
1953         -justify left
1954 label .vpane.lower.diff.header.path \
1955         -background orange \
1956         -anchor w \
1957         -justify left
1958 pack .vpane.lower.diff.header.status -side left
1959 pack .vpane.lower.diff.header.file -side left
1960 pack .vpane.lower.diff.header.path -fill x
1961 set ctxm .vpane.lower.diff.header.ctxm
1962 menu $ctxm -tearoff 0
1963 $ctxm add command \
1964         -label {Copy} \
1965         -command {
1966                 clipboard clear
1967                 clipboard append \
1968                         -format STRING \
1969                         -type STRING \
1970                         -- $current_diff_path
1971         }
1972 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
1973 bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
1974
1975 # -- Diff Body
1976 #
1977 frame .vpane.lower.diff.body
1978 set ui_diff .vpane.lower.diff.body.t
1979 text $ui_diff -background white -borderwidth 0 \
1980         -width 80 -height 15 -wrap none \
1981         -font font_diff \
1982         -xscrollcommand {.vpane.lower.diff.body.sbx set} \
1983         -yscrollcommand {.vpane.lower.diff.body.sby set} \
1984         -state disabled
1985 scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
1986         -command [list $ui_diff xview]
1987 scrollbar .vpane.lower.diff.body.sby -orient vertical \
1988         -command [list $ui_diff yview]
1989 pack .vpane.lower.diff.body.sbx -side bottom -fill x
1990 pack .vpane.lower.diff.body.sby -side right -fill y
1991 pack $ui_diff -side left -fill both -expand 1
1992 pack .vpane.lower.diff.header -side top -fill x
1993 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
1994
1995 $ui_diff tag conf d_cr -elide true
1996 $ui_diff tag conf d_@ -foreground blue -font font_diffbold
1997 $ui_diff tag conf d_+ -foreground {#00a000}
1998 $ui_diff tag conf d_- -foreground red
1999
2000 $ui_diff tag conf d_++ -foreground {#00a000}
2001 $ui_diff tag conf d_-- -foreground red
2002 $ui_diff tag conf d_+s \
2003         -foreground {#00a000} \
2004         -background {#e2effa}
2005 $ui_diff tag conf d_-s \
2006         -foreground red \
2007         -background {#e2effa}
2008 $ui_diff tag conf d_s+ \
2009         -foreground {#00a000} \
2010         -background ivory1
2011 $ui_diff tag conf d_s- \
2012         -foreground red \
2013         -background ivory1
2014
2015 $ui_diff tag conf d<<<<<<< \
2016         -foreground orange \
2017         -font font_diffbold
2018 $ui_diff tag conf d======= \
2019         -foreground orange \
2020         -font font_diffbold
2021 $ui_diff tag conf d>>>>>>> \
2022         -foreground orange \
2023         -font font_diffbold
2024
2025 $ui_diff tag raise sel
2026
2027 # -- Diff Body Context Menu
2028 #
2029 set ctxm .vpane.lower.diff.body.ctxm
2030 menu $ctxm -tearoff 0
2031 $ctxm add command \
2032         -label {Refresh} \
2033         -command reshow_diff
2034 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2035 $ctxm add command \
2036         -label {Copy} \
2037         -command {tk_textCopy $ui_diff}
2038 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2039 $ctxm add command \
2040         -label {Select All} \
2041         -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
2042 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2043 $ctxm add command \
2044         -label {Copy All} \
2045         -command {
2046                 $ui_diff tag add sel 0.0 end
2047                 tk_textCopy $ui_diff
2048                 $ui_diff tag remove sel 0.0 end
2049         }
2050 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2051 $ctxm add separator
2052 $ctxm add command \
2053         -label {Apply/Reverse Hunk} \
2054         -command {apply_hunk $cursorX $cursorY}
2055 set ui_diff_applyhunk [$ctxm index last]
2056 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
2057 $ctxm add separator
2058 $ctxm add command \
2059         -label {Decrease Font Size} \
2060         -command {incr_font_size font_diff -1}
2061 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2062 $ctxm add command \
2063         -label {Increase Font Size} \
2064         -command {incr_font_size font_diff 1}
2065 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2066 $ctxm add separator
2067 $ctxm add command \
2068         -label {Show Less Context} \
2069         -command {if {$repo_config(gui.diffcontext) >= 1} {
2070                 incr repo_config(gui.diffcontext) -1
2071                 reshow_diff
2072         }}
2073 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2074 $ctxm add command \
2075         -label {Show More Context} \
2076         -command {if {$repo_config(gui.diffcontext) < 99} {
2077                 incr repo_config(gui.diffcontext)
2078                 reshow_diff
2079         }}
2080 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2081 $ctxm add separator
2082 $ctxm add command -label {Options...} \
2083         -command do_options
2084 bind_button3 $ui_diff "
2085         set cursorX %x
2086         set cursorY %y
2087         if {\$ui_index eq \$current_diff_side} {
2088                 $ctxm entryconf $ui_diff_applyhunk -label {Unstage Hunk From Commit}
2089         } else {
2090                 $ctxm entryconf $ui_diff_applyhunk -label {Stage Hunk For Commit}
2091         }
2092         tk_popup $ctxm %X %Y
2093 "
2094 unset ui_diff_applyhunk
2095
2096 # -- Status Bar
2097 #
2098 label .status -textvariable ui_status_value \
2099         -anchor w \
2100         -justify left \
2101         -borderwidth 1 \
2102         -relief sunken
2103 pack .status -anchor w -side bottom -fill x
2104
2105 # -- Load geometry
2106 #
2107 catch {
2108 set gm $repo_config(gui.geometry)
2109 wm geometry . [lindex $gm 0]
2110 .vpane sash place 0 \
2111         [lindex [.vpane sash coord 0] 0] \
2112         [lindex $gm 1]
2113 .vpane.files sash place 0 \
2114         [lindex $gm 2] \
2115         [lindex [.vpane.files sash coord 0] 1]
2116 unset gm
2117 }
2118
2119 # -- Key Bindings
2120 #
2121 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2122 bind $ui_comm <$M1B-Key-i> {do_add_all;break}
2123 bind $ui_comm <$M1B-Key-I> {do_add_all;break}
2124 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2125 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2126 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2127 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2128 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2129 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2130 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2131 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2132
2133 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2134 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2135 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2136 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2137 bind $ui_diff <$M1B-Key-v> {break}
2138 bind $ui_diff <$M1B-Key-V> {break}
2139 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2140 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2141 bind $ui_diff <Key-Up>     {catch {%W yview scroll -1 units};break}
2142 bind $ui_diff <Key-Down>   {catch {%W yview scroll  1 units};break}
2143 bind $ui_diff <Key-Left>   {catch {%W xview scroll -1 units};break}
2144 bind $ui_diff <Key-Right>  {catch {%W xview scroll  1 units};break}
2145 bind $ui_diff <Key-k>         {catch {%W yview scroll -1 units};break}
2146 bind $ui_diff <Key-j>         {catch {%W yview scroll  1 units};break}
2147 bind $ui_diff <Key-h>         {catch {%W xview scroll -1 units};break}
2148 bind $ui_diff <Key-l>         {catch {%W xview scroll  1 units};break}
2149 bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
2150 bind $ui_diff <Control-Key-f> {catch {%W yview scroll  1 pages};break}
2151 bind $ui_diff <Button-1>   {focus %W}
2152
2153 if {[is_enabled branch]} {
2154         bind . <$M1B-Key-n> do_create_branch
2155         bind . <$M1B-Key-N> do_create_branch
2156 }
2157
2158 bind all <Key-F5> do_rescan
2159 bind all <$M1B-Key-r> do_rescan
2160 bind all <$M1B-Key-R> do_rescan
2161 bind .   <$M1B-Key-s> do_signoff
2162 bind .   <$M1B-Key-S> do_signoff
2163 bind .   <$M1B-Key-i> do_add_all
2164 bind .   <$M1B-Key-I> do_add_all
2165 bind .   <$M1B-Key-Return> do_commit
2166 foreach i [list $ui_index $ui_workdir] {
2167         bind $i <Button-1>       "toggle_or_diff         $i %x %y; break"
2168         bind $i <$M1B-Button-1>  "add_one_to_selection   $i %x %y; break"
2169         bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
2170 }
2171 unset i
2172
2173 set file_lists($ui_index) [list]
2174 set file_lists($ui_workdir) [list]
2175
2176 wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
2177 focus -force $ui_comm
2178
2179 # -- Warn the user about environmental problems.  Cygwin's Tcl
2180 #    does *not* pass its env array onto any processes it spawns.
2181 #    This means that git processes get none of our environment.
2182 #
2183 if {[is_Cygwin]} {
2184         set ignored_env 0
2185         set suggest_user {}
2186         set msg "Possible environment issues exist.
2187
2188 The following environment variables are probably
2189 going to be ignored by any Git subprocess run
2190 by [appname]:
2191
2192 "
2193         foreach name [array names env] {
2194                 switch -regexp -- $name {
2195                 {^GIT_INDEX_FILE$} -
2196                 {^GIT_OBJECT_DIRECTORY$} -
2197                 {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
2198                 {^GIT_DIFF_OPTS$} -
2199                 {^GIT_EXTERNAL_DIFF$} -
2200                 {^GIT_PAGER$} -
2201                 {^GIT_TRACE$} -
2202                 {^GIT_CONFIG$} -
2203                 {^GIT_CONFIG_LOCAL$} -
2204                 {^GIT_(AUTHOR|COMMITTER)_DATE$} {
2205                         append msg " - $name\n"
2206                         incr ignored_env
2207                 }
2208                 {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
2209                         append msg " - $name\n"
2210                         incr ignored_env
2211                         set suggest_user $name
2212                 }
2213                 }
2214         }
2215         if {$ignored_env > 0} {
2216                 append msg "
2217 This is due to a known issue with the
2218 Tcl binary distributed by Cygwin."
2219
2220                 if {$suggest_user ne {}} {
2221                         append msg "
2222
2223 A good replacement for $suggest_user
2224 is placing values for the user.name and
2225 user.email settings into your personal
2226 ~/.gitconfig file.
2227 "
2228                 }
2229                 warn_popup $msg
2230         }
2231         unset ignored_env msg suggest_user name
2232 }
2233
2234 # -- Only initialize complex UI if we are going to stay running.
2235 #
2236 if {[is_enabled transport]} {
2237         load_all_remotes
2238         load_all_heads
2239
2240         populate_branch_menu
2241         populate_fetch_menu
2242         populate_push_menu
2243 }
2244
2245 # -- Only suggest a gc run if we are going to stay running.
2246 #
2247 if {[is_enabled multicommit]} {
2248         set object_limit 2000
2249         if {[is_Windows]} {set object_limit 200}
2250         regexp {^([0-9]+) objects,} [git count-objects] _junk objects_current
2251         if {$objects_current >= $object_limit} {
2252                 if {[ask_popup \
2253                         "This repository currently has $objects_current loose objects.
2254
2255 To maintain optimal performance it is strongly recommended that you compress the database when more than $object_limit loose objects exist.
2256
2257 Compress the database now?"] eq yes} {
2258                         do_gc
2259                 }
2260         }
2261         unset object_limit _junk objects_current
2262 }
2263
2264 lock_index begin-read
2265 after 1 do_rescan