Code

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