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