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