Code

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