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