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