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