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