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