Code

git-rerere.txt: grammatical fixups and cleanups
[git.git] / git-gui / 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  argv0=$0; \
10  exec wish "$argv0" -- "$@"
12 set appvers {@@GITGUI_VERSION@@}
13 set copyright [encoding convertfrom utf-8 {
14 Copyright © 2006, 2007 Shawn Pearce, et. al.
16 This program is free software; you can redistribute it and/or modify
17 it under the terms of the GNU General Public License as published by
18 the Free Software Foundation; either version 2 of the License, or
19 (at your option) any later version.
21 This program is distributed in the hope that it will be useful,
22 but WITHOUT ANY WARRANTY; without even the implied warranty of
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24 GNU General Public License for more details.
26 You should have received a copy of the GNU General Public License
27 along with this program; if not, write to the Free Software
28 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA}]
30 ######################################################################
31 ##
32 ## Tcl/Tk sanity check
34 if {[catch {package require Tcl 8.4} err]
35  || [catch {package require Tk  8.4} err]
36 } {
37         catch {wm withdraw .}
38         tk_messageBox \
39                 -icon error \
40                 -type ok \
41                 -title [mc "git-gui: fatal error"] \
42                 -message $err
43         exit 1
44 }
46 catch {rename send {}} ; # What an evil concept...
48 ######################################################################
49 ##
50 ## locate our library
52 set oguilib {@@GITGUI_LIBDIR@@}
53 set oguirel {@@GITGUI_RELATIVE@@}
54 if {$oguirel eq {1}} {
55         set oguilib [file dirname [file normalize $argv0]]
56         if {[file tail $oguilib] eq {git-core}} {
57                 set oguilib [file dirname $oguilib]
58         }
59         set oguilib [file dirname $oguilib]
60         set oguilib [file join $oguilib share git-gui lib]
61         set oguimsg [file join $oguilib msgs]
62 } elseif {[string match @@* $oguirel]} {
63         set oguilib [file join [file dirname [file normalize $argv0]] lib]
64         set oguimsg [file join [file dirname [file normalize $argv0]] po]
65 } else {
66         set oguimsg [file join $oguilib msgs]
67 }
68 unset oguirel
70 ######################################################################
71 ##
72 ## enable verbose loading?
74 if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
75         unset _verbose
76         rename auto_load real__auto_load
77         proc auto_load {name args} {
78                 puts stderr "auto_load $name"
79                 return [uplevel 1 real__auto_load $name $args]
80         }
81         rename source real__source
82         proc source {name} {
83                 puts stderr "source    $name"
84                 uplevel 1 real__source $name
85         }
86 }
88 ######################################################################
89 ##
90 ## Internationalization (i18n) through msgcat and gettext. See
91 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
93 package require msgcat
95 proc _mc_trim {fmt} {
96         set cmk [string first @@ $fmt]
97         if {$cmk > 0} {
98                 return [string range $fmt 0 [expr {$cmk - 1}]]
99         }
100         return $fmt
103 proc mc {en_fmt args} {
104         set fmt [_mc_trim [::msgcat::mc $en_fmt]]
105         if {[catch {set msg [eval [list format $fmt] $args]} err]} {
106                 set msg [eval [list format [_mc_trim $en_fmt]] $args]
107         }
108         return $msg
111 proc strcat {args} {
112         return [join $args {}]
115 ::msgcat::mcload $oguimsg
116 unset oguimsg
118 ######################################################################
119 ##
120 ## read only globals
122 set _appname {Git Gui}
123 set _gitdir {}
124 set _gitexec {}
125 set _reponame {}
126 set _iscygwin {}
127 set _search_path {}
129 set _trace [lsearch -exact $argv --trace]
130 if {$_trace >= 0} {
131         set argv [lreplace $argv $_trace $_trace]
132         set _trace 1
133 } else {
134         set _trace 0
137 proc appname {} {
138         global _appname
139         return $_appname
142 proc gitdir {args} {
143         global _gitdir
144         if {$args eq {}} {
145                 return $_gitdir
146         }
147         return [eval [list file join $_gitdir] $args]
150 proc gitexec {args} {
151         global _gitexec
152         if {$_gitexec eq {}} {
153                 if {[catch {set _gitexec [git --exec-path]} err]} {
154                         error "Git not installed?\n\n$err"
155                 }
156                 if {[is_Cygwin]} {
157                         set _gitexec [exec cygpath \
158                                 --windows \
159                                 --absolute \
160                                 $_gitexec]
161                 } else {
162                         set _gitexec [file normalize $_gitexec]
163                 }
164         }
165         if {$args eq {}} {
166                 return $_gitexec
167         }
168         return [eval [list file join $_gitexec] $args]
171 proc reponame {} {
172         return $::_reponame
175 proc is_MacOSX {} {
176         if {[tk windowingsystem] eq {aqua}} {
177                 return 1
178         }
179         return 0
182 proc is_Windows {} {
183         if {$::tcl_platform(platform) eq {windows}} {
184                 return 1
185         }
186         return 0
189 proc is_Cygwin {} {
190         global _iscygwin
191         if {$_iscygwin eq {}} {
192                 if {$::tcl_platform(platform) eq {windows}} {
193                         if {[catch {set p [exec cygpath --windir]} err]} {
194                                 set _iscygwin 0
195                         } else {
196                                 set _iscygwin 1
197                         }
198                 } else {
199                         set _iscygwin 0
200                 }
201         }
202         return $_iscygwin
205 proc is_enabled {option} {
206         global enabled_options
207         if {[catch {set on $enabled_options($option)}]} {return 0}
208         return $on
211 proc enable_option {option} {
212         global enabled_options
213         set enabled_options($option) 1
216 proc disable_option {option} {
217         global enabled_options
218         set enabled_options($option) 0
221 ######################################################################
222 ##
223 ## config
225 proc is_many_config {name} {
226         switch -glob -- $name {
227         gui.recentrepo -
228         remote.*.fetch -
229         remote.*.push
230                 {return 1}
231         *
232                 {return 0}
233         }
236 proc is_config_true {name} {
237         global repo_config
238         if {[catch {set v $repo_config($name)}]} {
239                 return 0
240         } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
241                 return 1
242         } else {
243                 return 0
244         }
247 proc get_config {name} {
248         global repo_config
249         if {[catch {set v $repo_config($name)}]} {
250                 return {}
251         } else {
252                 return $v
253         }
256 ######################################################################
257 ##
258 ## handy utils
260 proc _trace_exec {cmd} {
261         if {!$::_trace} return
262         set d {}
263         foreach v $cmd {
264                 if {$d ne {}} {
265                         append d { }
266                 }
267                 if {[regexp {[ \t\r\n'"$?*]} $v]} {
268                         set v [sq $v]
269                 }
270                 append d $v
271         }
272         puts stderr $d
275 proc _git_cmd {name} {
276         global _git_cmd_path
278         if {[catch {set v $_git_cmd_path($name)}]} {
279                 switch -- $name {
280                   version   -
281                 --version   -
282                 --exec-path { return [list $::_git $name] }
283                 }
285                 set p [gitexec git-$name$::_search_exe]
286                 if {[file exists $p]} {
287                         set v [list $p]
288                 } elseif {[is_Windows] && [file exists [gitexec git-$name]]} {
289                         # Try to determine what sort of magic will make
290                         # git-$name go and do its thing, because native
291                         # Tcl on Windows doesn't know it.
292                         #
293                         set p [gitexec git-$name]
294                         set f [open $p r]
295                         set s [gets $f]
296                         close $f
298                         switch -glob -- [lindex $s 0] {
299                         #!*sh     { set i sh     }
300                         #!*perl   { set i perl   }
301                         #!*python { set i python }
302                         default   { error "git-$name is not supported: $s" }
303                         }
305                         upvar #0 _$i interp
306                         if {![info exists interp]} {
307                                 set interp [_which $i]
308                         }
309                         if {$interp eq {}} {
310                                 error "git-$name requires $i (not in PATH)"
311                         }
312                         set v [concat [list $interp] [lrange $s 1 end] [list $p]]
313                 } else {
314                         # Assume it is builtin to git somehow and we
315                         # aren't actually able to see a file for it.
316                         #
317                         set v [list $::_git $name]
318                 }
319                 set _git_cmd_path($name) $v
320         }
321         return $v
324 proc _which {what args} {
325         global env _search_exe _search_path
327         if {$_search_path eq {}} {
328                 if {[is_Cygwin] && [regexp {^(/|\.:)} $env(PATH)]} {
329                         set _search_path [split [exec cygpath \
330                                 --windows \
331                                 --path \
332                                 --absolute \
333                                 $env(PATH)] {;}]
334                         set _search_exe .exe
335                 } elseif {[is_Windows]} {
336                         set gitguidir [file dirname [info script]]
337                         regsub -all ";" $gitguidir "\\;" gitguidir
338                         set env(PATH) "$gitguidir;$env(PATH)"
339                         set _search_path [split $env(PATH) {;}]
340                         set _search_exe .exe
341                 } else {
342                         set _search_path [split $env(PATH) :]
343                         set _search_exe {}
344                 }
345         }
347         if {[is_Windows] && [lsearch -exact $args -script] >= 0} {
348                 set suffix {}
349         } else {
350                 set suffix $_search_exe
351         }
353         foreach p $_search_path {
354                 set p [file join $p $what$suffix]
355                 if {[file exists $p]} {
356                         return [file normalize $p]
357                 }
358         }
359         return {}
362 proc _lappend_nice {cmd_var} {
363         global _nice
364         upvar $cmd_var cmd
366         if {![info exists _nice]} {
367                 set _nice [_which nice]
368         }
369         if {$_nice ne {}} {
370                 lappend cmd $_nice
371         }
374 proc git {args} {
375         set opt [list]
377         while {1} {
378                 switch -- [lindex $args 0] {
379                 --nice {
380                         _lappend_nice opt
381                 }
383                 default {
384                         break
385                 }
387                 }
389                 set args [lrange $args 1 end]
390         }
392         set cmdp [_git_cmd [lindex $args 0]]
393         set args [lrange $args 1 end]
395         _trace_exec [concat $opt $cmdp $args]
396         set result [eval exec $opt $cmdp $args]
397         if {$::_trace} {
398                 puts stderr "< $result"
399         }
400         return $result
403 proc _open_stdout_stderr {cmd} {
404         _trace_exec $cmd
405         if {[catch {
406                         set fd [open [concat [list | ] $cmd] r]
407                 } err]} {
408                 if {   [lindex $cmd end] eq {2>@1}
409                     && $err eq {can not find channel named "1"}
410                         } {
411                         # Older versions of Tcl 8.4 don't have this 2>@1 IO
412                         # redirect operator.  Fallback to |& cat for those.
413                         # The command was not actually started, so its safe
414                         # to try to start it a second time.
415                         #
416                         set fd [open [concat \
417                                 [list | ] \
418                                 [lrange $cmd 0 end-1] \
419                                 [list |& cat] \
420                                 ] r]
421                 } else {
422                         error $err
423                 }
424         }
425         fconfigure $fd -eofchar {}
426         return $fd
429 proc git_read {args} {
430         set opt [list]
432         while {1} {
433                 switch -- [lindex $args 0] {
434                 --nice {
435                         _lappend_nice opt
436                 }
438                 --stderr {
439                         lappend args 2>@1
440                 }
442                 default {
443                         break
444                 }
446                 }
448                 set args [lrange $args 1 end]
449         }
451         set cmdp [_git_cmd [lindex $args 0]]
452         set args [lrange $args 1 end]
454         return [_open_stdout_stderr [concat $opt $cmdp $args]]
457 proc git_write {args} {
458         set opt [list]
460         while {1} {
461                 switch -- [lindex $args 0] {
462                 --nice {
463                         _lappend_nice opt
464                 }
466                 default {
467                         break
468                 }
470                 }
472                 set args [lrange $args 1 end]
473         }
475         set cmdp [_git_cmd [lindex $args 0]]
476         set args [lrange $args 1 end]
478         _trace_exec [concat $opt $cmdp $args]
479         return [open [concat [list | ] $opt $cmdp $args] w]
482 proc githook_read {hook_name args} {
483         set pchook [gitdir hooks $hook_name]
484         lappend args 2>@1
486         # On Windows [file executable] might lie so we need to ask
487         # the shell if the hook is executable.  Yes that's annoying.
488         #
489         if {[is_Windows]} {
490                 upvar #0 _sh interp
491                 if {![info exists interp]} {
492                         set interp [_which sh]
493                 }
494                 if {$interp eq {}} {
495                         error "hook execution requires sh (not in PATH)"
496                 }
498                 set scr {if test -x "$1";then exec "$@";fi}
499                 set sh_c [list $interp -c $scr $interp $pchook]
500                 return [_open_stdout_stderr [concat $sh_c $args]]
501         }
503         if {[file executable $pchook]} {
504                 return [_open_stdout_stderr [concat [list $pchook] $args]]
505         }
507         return {}
510 proc kill_file_process {fd} {
511         set process [pid $fd]
513         catch {
514                 if {[is_Windows]} {
515                         # Use a Cygwin-specific flag to allow killing
516                         # native Windows processes
517                         exec kill -f $process
518                 } else {
519                         exec kill $process
520                 }
521         }
524 proc sq {value} {
525         regsub -all ' $value "'\\''" value
526         return "'$value'"
529 proc load_current_branch {} {
530         global current_branch is_detached
532         set fd [open [gitdir HEAD] r]
533         if {[gets $fd ref] < 1} {
534                 set ref {}
535         }
536         close $fd
538         set pfx {ref: refs/heads/}
539         set len [string length $pfx]
540         if {[string equal -length $len $pfx $ref]} {
541                 # We're on a branch.  It might not exist.  But
542                 # HEAD looks good enough to be a branch.
543                 #
544                 set current_branch [string range $ref $len end]
545                 set is_detached 0
546         } else {
547                 # Assume this is a detached head.
548                 #
549                 set current_branch HEAD
550                 set is_detached 1
551         }
554 auto_load tk_optionMenu
555 rename tk_optionMenu real__tkOptionMenu
556 proc tk_optionMenu {w varName args} {
557         set m [eval real__tkOptionMenu $w $varName $args]
558         $m configure -font font_ui
559         $w configure -font font_ui
560         return $m
563 proc rmsel_tag {text} {
564         $text tag conf sel \
565                 -background [$text cget -background] \
566                 -foreground [$text cget -foreground] \
567                 -borderwidth 0
568         $text tag conf in_sel -background lightgray
569         bind $text <Motion> break
570         return $text
573 set root_exists 0
574 bind . <Visibility> {
575         bind . <Visibility> {}
576         set root_exists 1
579 if {[is_Windows]} {
580         wm iconbitmap . -default $oguilib/git-gui.ico
583 ######################################################################
584 ##
585 ## config defaults
587 set cursor_ptr arrow
588 font create font_diff -family Courier -size 10
589 font create font_ui
590 catch {
591         label .dummy
592         eval font configure font_ui [font actual [.dummy cget -font]]
593         destroy .dummy
596 font create font_uiitalic
597 font create font_uibold
598 font create font_diffbold
599 font create font_diffitalic
601 foreach class {Button Checkbutton Entry Label
602                 Labelframe Listbox Menu Message
603                 Radiobutton Spinbox Text} {
604         option add *$class.font font_ui
606 unset class
608 if {[is_Windows] || [is_MacOSX]} {
609         option add *Menu.tearOff 0
612 if {[is_MacOSX]} {
613         set M1B M1
614         set M1T Cmd
615 } else {
616         set M1B Control
617         set M1T Ctrl
620 proc bind_button3 {w cmd} {
621         bind $w <Any-Button-3> $cmd
622         if {[is_MacOSX]} {
623                 # Mac OS X sends Button-2 on right click through three-button mouse,
624                 # or through trackpad right-clicking (two-finger touch + click).
625                 bind $w <Any-Button-2> $cmd
626                 bind $w <Control-Button-1> $cmd
627         }
630 proc apply_config {} {
631         global repo_config font_descs
633         foreach option $font_descs {
634                 set name [lindex $option 0]
635                 set font [lindex $option 1]
636                 if {[catch {
637                         set need_weight 1
638                         foreach {cn cv} $repo_config(gui.$name) {
639                                 if {$cn eq {-weight}} {
640                                         set need_weight 0
641                                 }
642                                 font configure $font $cn $cv
643                         }
644                         if {$need_weight} {
645                                 font configure $font -weight normal
646                         }
647                         } err]} {
648                         error_popup [strcat [mc "Invalid font specified in %s:" "gui.$name"] "\n\n$err"]
649                 }
650                 foreach {cn cv} [font configure $font] {
651                         font configure ${font}bold $cn $cv
652                         font configure ${font}italic $cn $cv
653                 }
654                 font configure ${font}bold -weight bold
655                 font configure ${font}italic -slant italic
656         }
659 set default_config(branch.autosetupmerge) true
660 set default_config(merge.diffstat) true
661 set default_config(merge.summary) false
662 set default_config(merge.verbosity) 2
663 set default_config(user.name) {}
664 set default_config(user.email) {}
666 set default_config(gui.matchtrackingbranch) false
667 set default_config(gui.pruneduringfetch) false
668 set default_config(gui.trustmtime) false
669 set default_config(gui.fastcopyblame) false
670 set default_config(gui.copyblamethreshold) 40
671 set default_config(gui.diffcontext) 5
672 set default_config(gui.commitmsgwidth) 75
673 set default_config(gui.newbranchtemplate) {}
674 set default_config(gui.spellingdictionary) {}
675 set default_config(gui.fontui) [font configure font_ui]
676 set default_config(gui.fontdiff) [font configure font_diff]
677 set font_descs {
678         {fontui   font_ui   {mc "Main Font"}}
679         {fontdiff font_diff {mc "Diff/Console Font"}}
682 ######################################################################
683 ##
684 ## find git
686 set _git  [_which git]
687 if {$_git eq {}} {
688         catch {wm withdraw .}
689         tk_messageBox \
690                 -icon error \
691                 -type ok \
692                 -title [mc "git-gui: fatal error"] \
693                 -message [mc "Cannot find git in PATH."]
694         exit 1
697 ######################################################################
698 ##
699 ## version check
701 if {[catch {set _git_version [git --version]} err]} {
702         catch {wm withdraw .}
703         tk_messageBox \
704                 -icon error \
705                 -type ok \
706                 -title [mc "git-gui: fatal error"] \
707                 -message "Cannot determine Git version:
709 $err
711 [appname] requires Git 1.5.0 or later."
712         exit 1
714 if {![regsub {^git version } $_git_version {} _git_version]} {
715         catch {wm withdraw .}
716         tk_messageBox \
717                 -icon error \
718                 -type ok \
719                 -title [mc "git-gui: fatal error"] \
720                 -message [strcat [mc "Cannot parse Git version string:"] "\n\n$_git_version"]
721         exit 1
724 set _real_git_version $_git_version
725 regsub -- {[\-\.]dirty$} $_git_version {} _git_version
726 regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version
727 regsub {\.rc[0-9]+$} $_git_version {} _git_version
728 regsub {\.GIT$} $_git_version {} _git_version
729 regsub {\.[a-zA-Z]+\.[0-9]+$} $_git_version {} _git_version
731 if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} {
732         catch {wm withdraw .}
733         if {[tk_messageBox \
734                 -icon warning \
735                 -type yesno \
736                 -default no \
737                 -title "[appname]: warning" \
738                  -message [mc "Git version cannot be determined.
740 %s claims it is version '%s'.
742 %s requires at least Git 1.5.0 or later.
744 Assume '%s' is version 1.5.0?
745 " $_git $_real_git_version [appname] $_real_git_version]] eq {yes}} {
746                 set _git_version 1.5.0
747         } else {
748                 exit 1
749         }
751 unset _real_git_version
753 proc git-version {args} {
754         global _git_version
756         switch [llength $args] {
757         0 {
758                 return $_git_version
759         }
761         2 {
762                 set op [lindex $args 0]
763                 set vr [lindex $args 1]
764                 set cm [package vcompare $_git_version $vr]
765                 return [expr $cm $op 0]
766         }
768         4 {
769                 set type [lindex $args 0]
770                 set name [lindex $args 1]
771                 set parm [lindex $args 2]
772                 set body [lindex $args 3]
774                 if {($type ne {proc} && $type ne {method})} {
775                         error "Invalid arguments to git-version"
776                 }
777                 if {[llength $body] < 2 || [lindex $body end-1] ne {default}} {
778                         error "Last arm of $type $name must be default"
779                 }
781                 foreach {op vr cb} [lrange $body 0 end-2] {
782                         if {[git-version $op $vr]} {
783                                 return [uplevel [list $type $name $parm $cb]]
784                         }
785                 }
787                 return [uplevel [list $type $name $parm [lindex $body end]]]
788         }
790         default {
791                 error "git-version >= x"
792         }
794         }
797 if {[git-version < 1.5]} {
798         catch {wm withdraw .}
799         tk_messageBox \
800                 -icon error \
801                 -type ok \
802                 -title [mc "git-gui: fatal error"] \
803                 -message "[appname] requires Git 1.5.0 or later.
805 You are using [git-version]:
807 [git --version]"
808         exit 1
811 ######################################################################
812 ##
813 ## configure our library
815 set idx [file join $oguilib tclIndex]
816 if {[catch {set fd [open $idx r]} err]} {
817         catch {wm withdraw .}
818         tk_messageBox \
819                 -icon error \
820                 -type ok \
821                 -title [mc "git-gui: fatal error"] \
822                 -message $err
823         exit 1
825 if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
826         set idx [list]
827         while {[gets $fd n] >= 0} {
828                 if {$n ne {} && ![string match #* $n]} {
829                         lappend idx $n
830                 }
831         }
832 } else {
833         set idx {}
835 close $fd
837 if {$idx ne {}} {
838         set loaded [list]
839         foreach p $idx {
840                 if {[lsearch -exact $loaded $p] >= 0} continue
841                 source [file join $oguilib $p]
842                 lappend loaded $p
843         }
844         unset loaded p
845 } else {
846         set auto_path [concat [list $oguilib] $auto_path]
848 unset -nocomplain idx fd
850 ######################################################################
851 ##
852 ## config file parsing
854 git-version proc _parse_config {arr_name args} {
855         >= 1.5.3 {
856                 upvar $arr_name arr
857                 array unset arr
858                 set buf {}
859                 catch {
860                         set fd_rc [eval \
861                                 [list git_read config] \
862                                 $args \
863                                 [list --null --list]]
864                         fconfigure $fd_rc -translation binary
865                         set buf [read $fd_rc]
866                         close $fd_rc
867                 }
868                 foreach line [split $buf "\0"] {
869                         if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} {
870                                 if {[is_many_config $name]} {
871                                         lappend arr($name) $value
872                                 } else {
873                                         set arr($name) $value
874                                 }
875                         }
876                 }
877         }
878         default {
879                 upvar $arr_name arr
880                 array unset arr
881                 catch {
882                         set fd_rc [eval [list git_read config --list] $args]
883                         while {[gets $fd_rc line] >= 0} {
884                                 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
885                                         if {[is_many_config $name]} {
886                                                 lappend arr($name) $value
887                                         } else {
888                                                 set arr($name) $value
889                                         }
890                                 }
891                         }
892                         close $fd_rc
893                 }
894         }
897 proc load_config {include_global} {
898         global repo_config global_config default_config
900         if {$include_global} {
901                 _parse_config global_config --global
902         }
903         _parse_config repo_config
905         foreach name [array names default_config] {
906                 if {[catch {set v $global_config($name)}]} {
907                         set global_config($name) $default_config($name)
908                 }
909                 if {[catch {set v $repo_config($name)}]} {
910                         set repo_config($name) $default_config($name)
911                 }
912         }
915 ######################################################################
916 ##
917 ## feature option selection
919 if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} {
920         unset _junk
921 } else {
922         set subcommand gui
924 if {$subcommand eq {gui.sh}} {
925         set subcommand gui
927 if {$subcommand eq {gui} && [llength $argv] > 0} {
928         set subcommand [lindex $argv 0]
929         set argv [lrange $argv 1 end]
932 enable_option multicommit
933 enable_option branch
934 enable_option transport
935 disable_option bare
937 switch -- $subcommand {
938 browser -
939 blame {
940         enable_option bare
942         disable_option multicommit
943         disable_option branch
944         disable_option transport
946 citool {
947         enable_option singlecommit
949         disable_option multicommit
950         disable_option branch
951         disable_option transport
955 ######################################################################
956 ##
957 ## repository setup
959 if {[catch {
960                 set _gitdir $env(GIT_DIR)
961                 set _prefix {}
962                 }]
963         && [catch {
964                 set _gitdir [git rev-parse --git-dir]
965                 set _prefix [git rev-parse --show-prefix]
966         } err]} {
967         load_config 1
968         apply_config
969         choose_repository::pick
971 if {![file isdirectory $_gitdir] && [is_Cygwin]} {
972         catch {set _gitdir [exec cygpath --windows $_gitdir]}
974 if {![file isdirectory $_gitdir]} {
975         catch {wm withdraw .}
976         error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"]
977         exit 1
979 if {$_prefix ne {}} {
980         regsub -all {[^/]+/} $_prefix ../ cdup
981         if {[catch {cd $cdup} err]} {
982                 catch {wm withdraw .}
983                 error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"]
984                 exit 1
985         }
986         unset cdup
987 } elseif {![is_enabled bare]} {
988         if {[lindex [file split $_gitdir] end] ne {.git}} {
989                 catch {wm withdraw .}
990                 error_popup [strcat [mc "Cannot use funny .git directory:"] "\n\n$_gitdir"]
991                 exit 1
992         }
993         if {[catch {cd [file dirname $_gitdir]} err]} {
994                 catch {wm withdraw .}
995                 error_popup [strcat [mc "No working directory"] " [file dirname $_gitdir]:\n\n$err"]
996                 exit 1
997         }
999 set _reponame [file split [file normalize $_gitdir]]
1000 if {[lindex $_reponame end] eq {.git}} {
1001         set _reponame [lindex $_reponame end-1]
1002 } else {
1003         set _reponame [lindex $_reponame end]
1006 ######################################################################
1007 ##
1008 ## global init
1010 set current_diff_path {}
1011 set current_diff_side {}
1012 set diff_actions [list]
1014 set HEAD {}
1015 set PARENT {}
1016 set MERGE_HEAD [list]
1017 set commit_type {}
1018 set empty_tree {}
1019 set current_branch {}
1020 set is_detached 0
1021 set current_diff_path {}
1022 set is_3way_diff 0
1023 set selected_commit_type new
1025 ######################################################################
1026 ##
1027 ## task management
1029 set rescan_active 0
1030 set diff_active 0
1031 set last_clicked {}
1033 set disable_on_lock [list]
1034 set index_lock_type none
1036 proc lock_index {type} {
1037         global index_lock_type disable_on_lock
1039         if {$index_lock_type eq {none}} {
1040                 set index_lock_type $type
1041                 foreach w $disable_on_lock {
1042                         uplevel #0 $w disabled
1043                 }
1044                 return 1
1045         } elseif {$index_lock_type eq "begin-$type"} {
1046                 set index_lock_type $type
1047                 return 1
1048         }
1049         return 0
1052 proc unlock_index {} {
1053         global index_lock_type disable_on_lock
1055         set index_lock_type none
1056         foreach w $disable_on_lock {
1057                 uplevel #0 $w normal
1058         }
1061 ######################################################################
1062 ##
1063 ## status
1065 proc repository_state {ctvar hdvar mhvar} {
1066         global current_branch
1067         upvar $ctvar ct $hdvar hd $mhvar mh
1069         set mh [list]
1071         load_current_branch
1072         if {[catch {set hd [git rev-parse --verify HEAD]}]} {
1073                 set hd {}
1074                 set ct initial
1075                 return
1076         }
1078         set merge_head [gitdir MERGE_HEAD]
1079         if {[file exists $merge_head]} {
1080                 set ct merge
1081                 set fd_mh [open $merge_head r]
1082                 while {[gets $fd_mh line] >= 0} {
1083                         lappend mh $line
1084                 }
1085                 close $fd_mh
1086                 return
1087         }
1089         set ct normal
1092 proc PARENT {} {
1093         global PARENT empty_tree
1095         set p [lindex $PARENT 0]
1096         if {$p ne {}} {
1097                 return $p
1098         }
1099         if {$empty_tree eq {}} {
1100                 set empty_tree [git mktree << {}]
1101         }
1102         return $empty_tree
1105 proc rescan {after {honor_trustmtime 1}} {
1106         global HEAD PARENT MERGE_HEAD commit_type
1107         global ui_index ui_workdir ui_comm
1108         global rescan_active file_states
1109         global repo_config
1111         if {$rescan_active > 0 || ![lock_index read]} return
1113         repository_state newType newHEAD newMERGE_HEAD
1114         if {[string match amend* $commit_type]
1115                 && $newType eq {normal}
1116                 && $newHEAD eq $HEAD} {
1117         } else {
1118                 set HEAD $newHEAD
1119                 set PARENT $newHEAD
1120                 set MERGE_HEAD $newMERGE_HEAD
1121                 set commit_type $newType
1122         }
1124         array unset file_states
1126         if {!$::GITGUI_BCK_exists &&
1127                 (![$ui_comm edit modified]
1128                 || [string trim [$ui_comm get 0.0 end]] eq {})} {
1129                 if {[string match amend* $commit_type]} {
1130                 } elseif {[load_message GITGUI_MSG]} {
1131                 } elseif {[load_message MERGE_MSG]} {
1132                 } elseif {[load_message SQUASH_MSG]} {
1133                 }
1134                 $ui_comm edit reset
1135                 $ui_comm edit modified false
1136         }
1138         if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
1139                 rescan_stage2 {} $after
1140         } else {
1141                 set rescan_active 1
1142                 ui_status [mc "Refreshing file status..."]
1143                 set fd_rf [git_read update-index \
1144                         -q \
1145                         --unmerged \
1146                         --ignore-missing \
1147                         --refresh \
1148                         ]
1149                 fconfigure $fd_rf -blocking 0 -translation binary
1150                 fileevent $fd_rf readable \
1151                         [list rescan_stage2 $fd_rf $after]
1152         }
1155 if {[is_Cygwin]} {
1156         set is_git_info_exclude {}
1157         proc have_info_exclude {} {
1158                 global is_git_info_exclude
1160                 if {$is_git_info_exclude eq {}} {
1161                         if {[catch {exec test -f [gitdir info exclude]}]} {
1162                                 set is_git_info_exclude 0
1163                         } else {
1164                                 set is_git_info_exclude 1
1165                         }
1166                 }
1167                 return $is_git_info_exclude
1168         }
1169 } else {
1170         proc have_info_exclude {} {
1171                 return [file readable [gitdir info exclude]]
1172         }
1175 proc rescan_stage2 {fd after} {
1176         global rescan_active buf_rdi buf_rdf buf_rlo
1178         if {$fd ne {}} {
1179                 read $fd
1180                 if {![eof $fd]} return
1181                 close $fd
1182         }
1184         set ls_others [list --exclude-per-directory=.gitignore]
1185         if {[have_info_exclude]} {
1186                 lappend ls_others "--exclude-from=[gitdir info exclude]"
1187         }
1188         set user_exclude [get_config core.excludesfile]
1189         if {$user_exclude ne {} && [file readable $user_exclude]} {
1190                 lappend ls_others "--exclude-from=$user_exclude"
1191         }
1193         set buf_rdi {}
1194         set buf_rdf {}
1195         set buf_rlo {}
1197         set rescan_active 3
1198         ui_status [mc "Scanning for modified files ..."]
1199         set fd_di [git_read diff-index --cached -z [PARENT]]
1200         set fd_df [git_read diff-files -z]
1201         set fd_lo [eval git_read ls-files --others -z $ls_others]
1203         fconfigure $fd_di -blocking 0 -translation binary -encoding binary
1204         fconfigure $fd_df -blocking 0 -translation binary -encoding binary
1205         fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
1206         fileevent $fd_di readable [list read_diff_index $fd_di $after]
1207         fileevent $fd_df readable [list read_diff_files $fd_df $after]
1208         fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
1211 proc load_message {file} {
1212         global ui_comm
1214         set f [gitdir $file]
1215         if {[file isfile $f]} {
1216                 if {[catch {set fd [open $f r]}]} {
1217                         return 0
1218                 }
1219                 fconfigure $fd -eofchar {}
1220                 set content [string trim [read $fd]]
1221                 close $fd
1222                 regsub -all -line {[ \r\t]+$} $content {} content
1223                 $ui_comm delete 0.0 end
1224                 $ui_comm insert end $content
1225                 return 1
1226         }
1227         return 0
1230 proc read_diff_index {fd after} {
1231         global buf_rdi
1233         append buf_rdi [read $fd]
1234         set c 0
1235         set n [string length $buf_rdi]
1236         while {$c < $n} {
1237                 set z1 [string first "\0" $buf_rdi $c]
1238                 if {$z1 == -1} break
1239                 incr z1
1240                 set z2 [string first "\0" $buf_rdi $z1]
1241                 if {$z2 == -1} break
1243                 incr c
1244                 set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
1245                 set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
1246                 merge_state \
1247                         [encoding convertfrom $p] \
1248                         [lindex $i 4]? \
1249                         [list [lindex $i 0] [lindex $i 2]] \
1250                         [list]
1251                 set c $z2
1252                 incr c
1253         }
1254         if {$c < $n} {
1255                 set buf_rdi [string range $buf_rdi $c end]
1256         } else {
1257                 set buf_rdi {}
1258         }
1260         rescan_done $fd buf_rdi $after
1263 proc read_diff_files {fd after} {
1264         global buf_rdf
1266         append buf_rdf [read $fd]
1267         set c 0
1268         set n [string length $buf_rdf]
1269         while {$c < $n} {
1270                 set z1 [string first "\0" $buf_rdf $c]
1271                 if {$z1 == -1} break
1272                 incr z1
1273                 set z2 [string first "\0" $buf_rdf $z1]
1274                 if {$z2 == -1} break
1276                 incr c
1277                 set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
1278                 set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
1279                 merge_state \
1280                         [encoding convertfrom $p] \
1281                         ?[lindex $i 4] \
1282                         [list] \
1283                         [list [lindex $i 0] [lindex $i 2]]
1284                 set c $z2
1285                 incr c
1286         }
1287         if {$c < $n} {
1288                 set buf_rdf [string range $buf_rdf $c end]
1289         } else {
1290                 set buf_rdf {}
1291         }
1293         rescan_done $fd buf_rdf $after
1296 proc read_ls_others {fd after} {
1297         global buf_rlo
1299         append buf_rlo [read $fd]
1300         set pck [split $buf_rlo "\0"]
1301         set buf_rlo [lindex $pck end]
1302         foreach p [lrange $pck 0 end-1] {
1303                 set p [encoding convertfrom $p]
1304                 if {[string index $p end] eq {/}} {
1305                         set p [string range $p 0 end-1]
1306                 }
1307                 merge_state $p ?O
1308         }
1309         rescan_done $fd buf_rlo $after
1312 proc rescan_done {fd buf after} {
1313         global rescan_active current_diff_path
1314         global file_states repo_config
1315         upvar $buf to_clear
1317         if {![eof $fd]} return
1318         set to_clear {}
1319         close $fd
1320         if {[incr rescan_active -1] > 0} return
1322         prune_selection
1323         unlock_index
1324         display_all_files
1325         if {$current_diff_path ne {}} reshow_diff
1326         uplevel #0 $after
1329 proc prune_selection {} {
1330         global file_states selected_paths
1332         foreach path [array names selected_paths] {
1333                 if {[catch {set still_here $file_states($path)}]} {
1334                         unset selected_paths($path)
1335                 }
1336         }
1339 ######################################################################
1340 ##
1341 ## ui helpers
1343 proc mapicon {w state path} {
1344         global all_icons
1346         if {[catch {set r $all_icons($state$w)}]} {
1347                 puts "error: no icon for $w state={$state} $path"
1348                 return file_plain
1349         }
1350         return $r
1353 proc mapdesc {state path} {
1354         global all_descs
1356         if {[catch {set r $all_descs($state)}]} {
1357                 puts "error: no desc for state={$state} $path"
1358                 return $state
1359         }
1360         return $r
1363 proc ui_status {msg} {
1364         global main_status
1365         if {[info exists main_status]} {
1366                 $main_status show $msg
1367         }
1370 proc ui_ready {{test {}}} {
1371         global main_status
1372         if {[info exists main_status]} {
1373                 $main_status show [mc "Ready."] $test
1374         }
1377 proc escape_path {path} {
1378         regsub -all {\\} $path "\\\\" path
1379         regsub -all "\n" $path "\\n" path
1380         return $path
1383 proc short_path {path} {
1384         return [escape_path [lindex [file split $path] end]]
1387 set next_icon_id 0
1388 set null_sha1 [string repeat 0 40]
1390 proc merge_state {path new_state {head_info {}} {index_info {}}} {
1391         global file_states next_icon_id null_sha1
1393         set s0 [string index $new_state 0]
1394         set s1 [string index $new_state 1]
1396         if {[catch {set info $file_states($path)}]} {
1397                 set state __
1398                 set icon n[incr next_icon_id]
1399         } else {
1400                 set state [lindex $info 0]
1401                 set icon [lindex $info 1]
1402                 if {$head_info eq {}}  {set head_info  [lindex $info 2]}
1403                 if {$index_info eq {}} {set index_info [lindex $info 3]}
1404         }
1406         if     {$s0 eq {?}} {set s0 [string index $state 0]} \
1407         elseif {$s0 eq {_}} {set s0 _}
1409         if     {$s1 eq {?}} {set s1 [string index $state 1]} \
1410         elseif {$s1 eq {_}} {set s1 _}
1412         if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
1413                 set head_info [list 0 $null_sha1]
1414         } elseif {$s0 ne {_} && [string index $state 0] eq {_}
1415                 && $head_info eq {}} {
1416                 set head_info $index_info
1417         }
1419         set file_states($path) [list $s0$s1 $icon \
1420                 $head_info $index_info \
1421                 ]
1422         return $state
1425 proc display_file_helper {w path icon_name old_m new_m} {
1426         global file_lists
1428         if {$new_m eq {_}} {
1429                 set lno [lsearch -sorted -exact $file_lists($w) $path]
1430                 if {$lno >= 0} {
1431                         set file_lists($w) [lreplace $file_lists($w) $lno $lno]
1432                         incr lno
1433                         $w conf -state normal
1434                         $w delete $lno.0 [expr {$lno + 1}].0
1435                         $w conf -state disabled
1436                 }
1437         } elseif {$old_m eq {_} && $new_m ne {_}} {
1438                 lappend file_lists($w) $path
1439                 set file_lists($w) [lsort -unique $file_lists($w)]
1440                 set lno [lsearch -sorted -exact $file_lists($w) $path]
1441                 incr lno
1442                 $w conf -state normal
1443                 $w image create $lno.0 \
1444                         -align center -padx 5 -pady 1 \
1445                         -name $icon_name \
1446                         -image [mapicon $w $new_m $path]
1447                 $w insert $lno.1 "[escape_path $path]\n"
1448                 $w conf -state disabled
1449         } elseif {$old_m ne $new_m} {
1450                 $w conf -state normal
1451                 $w image conf $icon_name -image [mapicon $w $new_m $path]
1452                 $w conf -state disabled
1453         }
1456 proc display_file {path state} {
1457         global file_states selected_paths
1458         global ui_index ui_workdir
1460         set old_m [merge_state $path $state]
1461         set s $file_states($path)
1462         set new_m [lindex $s 0]
1463         set icon_name [lindex $s 1]
1465         set o [string index $old_m 0]
1466         set n [string index $new_m 0]
1467         if {$o eq {U}} {
1468                 set o _
1469         }
1470         if {$n eq {U}} {
1471                 set n _
1472         }
1473         display_file_helper     $ui_index $path $icon_name $o $n
1475         if {[string index $old_m 0] eq {U}} {
1476                 set o U
1477         } else {
1478                 set o [string index $old_m 1]
1479         }
1480         if {[string index $new_m 0] eq {U}} {
1481                 set n U
1482         } else {
1483                 set n [string index $new_m 1]
1484         }
1485         display_file_helper     $ui_workdir $path $icon_name $o $n
1487         if {$new_m eq {__}} {
1488                 unset file_states($path)
1489                 catch {unset selected_paths($path)}
1490         }
1493 proc display_all_files_helper {w path icon_name m} {
1494         global file_lists
1496         lappend file_lists($w) $path
1497         set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
1498         $w image create end \
1499                 -align center -padx 5 -pady 1 \
1500                 -name $icon_name \
1501                 -image [mapicon $w $m $path]
1502         $w insert end "[escape_path $path]\n"
1505 proc display_all_files {} {
1506         global ui_index ui_workdir
1507         global file_states file_lists
1508         global last_clicked
1510         $ui_index conf -state normal
1511         $ui_workdir conf -state normal
1513         $ui_index delete 0.0 end
1514         $ui_workdir delete 0.0 end
1515         set last_clicked {}
1517         set file_lists($ui_index) [list]
1518         set file_lists($ui_workdir) [list]
1520         foreach path [lsort [array names file_states]] {
1521                 set s $file_states($path)
1522                 set m [lindex $s 0]
1523                 set icon_name [lindex $s 1]
1525                 set s [string index $m 0]
1526                 if {$s ne {U} && $s ne {_}} {
1527                         display_all_files_helper $ui_index $path \
1528                                 $icon_name $s
1529                 }
1531                 if {[string index $m 0] eq {U}} {
1532                         set s U
1533                 } else {
1534                         set s [string index $m 1]
1535                 }
1536                 if {$s ne {_}} {
1537                         display_all_files_helper $ui_workdir $path \
1538                                 $icon_name $s
1539                 }
1540         }
1542         $ui_index conf -state disabled
1543         $ui_workdir conf -state disabled
1546 ######################################################################
1547 ##
1548 ## icons
1550 set filemask {
1551 #define mask_width 14
1552 #define mask_height 15
1553 static unsigned char mask_bits[] = {
1554    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1555    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1556    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1559 image create bitmap file_plain -background white -foreground black -data {
1560 #define plain_width 14
1561 #define plain_height 15
1562 static unsigned char plain_bits[] = {
1563    0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1564    0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1565    0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1566 } -maskdata $filemask
1568 image create bitmap file_mod -background white -foreground blue -data {
1569 #define mod_width 14
1570 #define mod_height 15
1571 static unsigned char mod_bits[] = {
1572    0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1573    0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1574    0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1575 } -maskdata $filemask
1577 image create bitmap file_fulltick -background white -foreground "#007000" -data {
1578 #define file_fulltick_width 14
1579 #define file_fulltick_height 15
1580 static unsigned char file_fulltick_bits[] = {
1581    0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1582    0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1583    0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1584 } -maskdata $filemask
1586 image create bitmap file_parttick -background white -foreground "#005050" -data {
1587 #define parttick_width 14
1588 #define parttick_height 15
1589 static unsigned char parttick_bits[] = {
1590    0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1591    0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1592    0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1593 } -maskdata $filemask
1595 image create bitmap file_question -background white -foreground black -data {
1596 #define file_question_width 14
1597 #define file_question_height 15
1598 static unsigned char file_question_bits[] = {
1599    0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1600    0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1601    0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1602 } -maskdata $filemask
1604 image create bitmap file_removed -background white -foreground red -data {
1605 #define file_removed_width 14
1606 #define file_removed_height 15
1607 static unsigned char file_removed_bits[] = {
1608    0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1609    0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1610    0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1611 } -maskdata $filemask
1613 image create bitmap file_merge -background white -foreground blue -data {
1614 #define file_merge_width 14
1615 #define file_merge_height 15
1616 static unsigned char file_merge_bits[] = {
1617    0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1618    0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1619    0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1620 } -maskdata $filemask
1622 set ui_index .vpane.files.index.list
1623 set ui_workdir .vpane.files.workdir.list
1625 set all_icons(_$ui_index)   file_plain
1626 set all_icons(A$ui_index)   file_fulltick
1627 set all_icons(M$ui_index)   file_fulltick
1628 set all_icons(D$ui_index)   file_removed
1629 set all_icons(U$ui_index)   file_merge
1631 set all_icons(_$ui_workdir) file_plain
1632 set all_icons(M$ui_workdir) file_mod
1633 set all_icons(D$ui_workdir) file_question
1634 set all_icons(U$ui_workdir) file_merge
1635 set all_icons(O$ui_workdir) file_plain
1637 set max_status_desc 0
1638 foreach i {
1639                 {__ {mc "Unmodified"}}
1641                 {_M {mc "Modified, not staged"}}
1642                 {M_ {mc "Staged for commit"}}
1643                 {MM {mc "Portions staged for commit"}}
1644                 {MD {mc "Staged for commit, missing"}}
1646                 {_O {mc "Untracked, not staged"}}
1647                 {A_ {mc "Staged for commit"}}
1648                 {AM {mc "Portions staged for commit"}}
1649                 {AD {mc "Staged for commit, missing"}}
1651                 {_D {mc "Missing"}}
1652                 {D_ {mc "Staged for removal"}}
1653                 {DO {mc "Staged for removal, still present"}}
1655                 {U_ {mc "Requires merge resolution"}}
1656                 {UU {mc "Requires merge resolution"}}
1657                 {UM {mc "Requires merge resolution"}}
1658                 {UD {mc "Requires merge resolution"}}
1659         } {
1660         set text [eval [lindex $i 1]]
1661         if {$max_status_desc < [string length $text]} {
1662                 set max_status_desc [string length $text]
1663         }
1664         set all_descs([lindex $i 0]) $text
1666 unset i
1668 ######################################################################
1669 ##
1670 ## util
1672 proc scrollbar2many {list mode args} {
1673         foreach w $list {eval $w $mode $args}
1676 proc many2scrollbar {list mode sb top bottom} {
1677         $sb set $top $bottom
1678         foreach w $list {$w $mode moveto $top}
1681 proc incr_font_size {font {amt 1}} {
1682         set sz [font configure $font -size]
1683         incr sz $amt
1684         font configure $font -size $sz
1685         font configure ${font}bold -size $sz
1686         font configure ${font}italic -size $sz
1689 ######################################################################
1690 ##
1691 ## ui commands
1693 set starting_gitk_msg [mc "Starting gitk... please wait..."]
1695 proc do_gitk {revs} {
1696         # -- Always start gitk through whatever we were loaded with.  This
1697         #    lets us bypass using shell process on Windows systems.
1698         #
1699         set exe [_which gitk -script]
1700         set cmd [list [info nameofexecutable] $exe]
1701         if {$exe eq {}} {
1702                 error_popup [mc "Couldn't find gitk in PATH"]
1703         } else {
1704                 global env
1706                 if {[info exists env(GIT_DIR)]} {
1707                         set old_GIT_DIR $env(GIT_DIR)
1708                 } else {
1709                         set old_GIT_DIR {}
1710                 }
1712                 set pwd [pwd]
1713                 cd [file dirname [gitdir]]
1714                 set env(GIT_DIR) [file tail [gitdir]]
1716                 eval exec $cmd $revs &
1718                 if {$old_GIT_DIR eq {}} {
1719                         unset env(GIT_DIR)
1720                 } else {
1721                         set env(GIT_DIR) $old_GIT_DIR
1722                 }
1723                 cd $pwd
1725                 ui_status $::starting_gitk_msg
1726                 after 10000 {
1727                         ui_ready $starting_gitk_msg
1728                 }
1729         }
1732 set is_quitting 0
1734 proc do_quit {} {
1735         global ui_comm is_quitting repo_config commit_type
1736         global GITGUI_BCK_exists GITGUI_BCK_i
1737         global ui_comm_spell
1739         if {$is_quitting} return
1740         set is_quitting 1
1742         if {[winfo exists $ui_comm]} {
1743                 # -- Stash our current commit buffer.
1744                 #
1745                 set save [gitdir GITGUI_MSG]
1746                 if {$GITGUI_BCK_exists && ![$ui_comm edit modified]} {
1747                         file rename -force [gitdir GITGUI_BCK] $save
1748                         set GITGUI_BCK_exists 0
1749                 } else {
1750                         set msg [string trim [$ui_comm get 0.0 end]]
1751                         regsub -all -line {[ \r\t]+$} $msg {} msg
1752                         if {(![string match amend* $commit_type]
1753                                 || [$ui_comm edit modified])
1754                                 && $msg ne {}} {
1755                                 catch {
1756                                         set fd [open $save w]
1757                                         puts -nonewline $fd $msg
1758                                         close $fd
1759                                 }
1760                         } else {
1761                                 catch {file delete $save}
1762                         }
1763                 }
1765                 # -- Cancel our spellchecker if its running.
1766                 #
1767                 if {[info exists ui_comm_spell]} {
1768                         $ui_comm_spell stop
1769                 }
1771                 # -- Remove our editor backup, its not needed.
1772                 #
1773                 after cancel $GITGUI_BCK_i
1774                 if {$GITGUI_BCK_exists} {
1775                         catch {file delete [gitdir GITGUI_BCK]}
1776                 }
1778                 # -- Stash our current window geometry into this repository.
1779                 #
1780                 set cfg_geometry [list]
1781                 lappend cfg_geometry [wm geometry .]
1782                 lappend cfg_geometry [lindex [.vpane sash coord 0] 0]
1783                 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 1]
1784                 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1785                         set rc_geometry {}
1786                 }
1787                 if {$cfg_geometry ne $rc_geometry} {
1788                         catch {git config gui.geometry $cfg_geometry}
1789                 }
1790         }
1792         destroy .
1795 proc do_rescan {} {
1796         rescan ui_ready
1799 proc do_commit {} {
1800         commit_tree
1803 proc next_diff {} {
1804         global next_diff_p next_diff_w next_diff_i
1805         show_diff $next_diff_p $next_diff_w $next_diff_i
1808 proc toggle_or_diff {w x y} {
1809         global file_states file_lists current_diff_path ui_index ui_workdir
1810         global last_clicked selected_paths
1812         set pos [split [$w index @$x,$y] .]
1813         set lno [lindex $pos 0]
1814         set col [lindex $pos 1]
1815         set path [lindex $file_lists($w) [expr {$lno - 1}]]
1816         if {$path eq {}} {
1817                 set last_clicked {}
1818                 return
1819         }
1821         set last_clicked [list $w $lno]
1822         array unset selected_paths
1823         $ui_index tag remove in_sel 0.0 end
1824         $ui_workdir tag remove in_sel 0.0 end
1826         if {$col == 0 && $y > 1} {
1827                 set i [expr {$lno-1}]
1828                 set ll [expr {[llength $file_lists($w)]-1}]
1830                 if {$i == $ll && $i == 0} {
1831                         set after {reshow_diff;}
1832                 } else {
1833                         global next_diff_p next_diff_w next_diff_i
1835                         set next_diff_w $w
1837                         if {$i < $ll} {
1838                                 set i [expr {$i + 1}]
1839                                 set next_diff_i $i
1840                         } else {
1841                                 set next_diff_i $i
1842                                 set i [expr {$i - 1}]
1843                         }
1845                         set next_diff_p [lindex $file_lists($w) $i]
1847                         if {$next_diff_p ne {} && $current_diff_path ne {}} {
1848                                 set after {next_diff;}
1849                         } else {
1850                                 set after {}
1851                         }
1852                 }
1854                 if {$w eq $ui_index} {
1855                         update_indexinfo \
1856                                 "Unstaging [short_path $path] from commit" \
1857                                 [list $path] \
1858                                 [concat $after [list ui_ready]]
1859                 } elseif {$w eq $ui_workdir} {
1860                         update_index \
1861                                 "Adding [short_path $path]" \
1862                                 [list $path] \
1863                                 [concat $after [list ui_ready]]
1864                 }
1865         } else {
1866                 show_diff $path $w $lno
1867         }
1870 proc add_one_to_selection {w x y} {
1871         global file_lists last_clicked selected_paths
1873         set lno [lindex [split [$w index @$x,$y] .] 0]
1874         set path [lindex $file_lists($w) [expr {$lno - 1}]]
1875         if {$path eq {}} {
1876                 set last_clicked {}
1877                 return
1878         }
1880         if {$last_clicked ne {}
1881                 && [lindex $last_clicked 0] ne $w} {
1882                 array unset selected_paths
1883                 [lindex $last_clicked 0] tag remove in_sel 0.0 end
1884         }
1886         set last_clicked [list $w $lno]
1887         if {[catch {set in_sel $selected_paths($path)}]} {
1888                 set in_sel 0
1889         }
1890         if {$in_sel} {
1891                 unset selected_paths($path)
1892                 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
1893         } else {
1894                 set selected_paths($path) 1
1895                 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
1896         }
1899 proc add_range_to_selection {w x y} {
1900         global file_lists last_clicked selected_paths
1902         if {[lindex $last_clicked 0] ne $w} {
1903                 toggle_or_diff $w $x $y
1904                 return
1905         }
1907         set lno [lindex [split [$w index @$x,$y] .] 0]
1908         set lc [lindex $last_clicked 1]
1909         if {$lc < $lno} {
1910                 set begin $lc
1911                 set end $lno
1912         } else {
1913                 set begin $lno
1914                 set end $lc
1915         }
1917         foreach path [lrange $file_lists($w) \
1918                 [expr {$begin - 1}] \
1919                 [expr {$end - 1}]] {
1920                 set selected_paths($path) 1
1921         }
1922         $w tag add in_sel $begin.0 [expr {$end + 1}].0
1925 proc show_more_context {} {
1926         global repo_config
1927         if {$repo_config(gui.diffcontext) < 99} {
1928                 incr repo_config(gui.diffcontext)
1929                 reshow_diff
1930         }
1933 proc show_less_context {} {
1934         global repo_config
1935         if {$repo_config(gui.diffcontext) > 1} {
1936                 incr repo_config(gui.diffcontext) -1
1937                 reshow_diff
1938         }
1941 ######################################################################
1942 ##
1943 ## ui construction
1945 load_config 0
1946 apply_config
1947 set ui_comm {}
1949 # -- Menu Bar
1951 menu .mbar -tearoff 0
1952 .mbar add cascade -label [mc Repository] -menu .mbar.repository
1953 .mbar add cascade -label [mc Edit] -menu .mbar.edit
1954 if {[is_enabled branch]} {
1955         .mbar add cascade -label [mc Branch] -menu .mbar.branch
1957 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1958         .mbar add cascade -label [mc Commit@@noun] -menu .mbar.commit
1960 if {[is_enabled transport]} {
1961         .mbar add cascade -label [mc Merge] -menu .mbar.merge
1962         .mbar add cascade -label [mc Remote] -menu .mbar.remote
1964 . configure -menu .mbar
1966 # -- Repository Menu
1968 menu .mbar.repository
1970 .mbar.repository add command \
1971         -label [mc "Browse Current Branch's Files"] \
1972         -command {browser::new $current_branch}
1973 set ui_browse_current [.mbar.repository index last]
1974 .mbar.repository add command \
1975         -label [mc "Browse Branch Files..."] \
1976         -command browser_open::dialog
1977 .mbar.repository add separator
1979 .mbar.repository add command \
1980         -label [mc "Visualize Current Branch's History"] \
1981         -command {do_gitk $current_branch}
1982 set ui_visualize_current [.mbar.repository index last]
1983 .mbar.repository add command \
1984         -label [mc "Visualize All Branch History"] \
1985         -command {do_gitk --all}
1986 .mbar.repository add separator
1988 proc current_branch_write {args} {
1989         global current_branch
1990         .mbar.repository entryconf $::ui_browse_current \
1991                 -label [mc "Browse %s's Files" $current_branch]
1992         .mbar.repository entryconf $::ui_visualize_current \
1993                 -label [mc "Visualize %s's History" $current_branch]
1995 trace add variable current_branch write current_branch_write
1997 if {[is_enabled multicommit]} {
1998         .mbar.repository add command -label [mc "Database Statistics"] \
1999                 -command do_stats
2001         .mbar.repository add command -label [mc "Compress Database"] \
2002                 -command do_gc
2004         .mbar.repository add command -label [mc "Verify Database"] \
2005                 -command do_fsck_objects
2007         .mbar.repository add separator
2009         if {[is_Cygwin]} {
2010                 .mbar.repository add command \
2011                         -label [mc "Create Desktop Icon"] \
2012                         -command do_cygwin_shortcut
2013         } elseif {[is_Windows]} {
2014                 .mbar.repository add command \
2015                         -label [mc "Create Desktop Icon"] \
2016                         -command do_windows_shortcut
2017         } elseif {[is_MacOSX]} {
2018                 .mbar.repository add command \
2019                         -label [mc "Create Desktop Icon"] \
2020                         -command do_macosx_app
2021         }
2024 if {[is_MacOSX]} {
2025         proc ::tk::mac::Quit {args} { do_quit }
2026 } else {
2027         .mbar.repository add command -label [mc Quit] \
2028                 -command do_quit \
2029                 -accelerator $M1T-Q
2032 # -- Edit Menu
2034 menu .mbar.edit
2035 .mbar.edit add command -label [mc Undo] \
2036         -command {catch {[focus] edit undo}} \
2037         -accelerator $M1T-Z
2038 .mbar.edit add command -label [mc Redo] \
2039         -command {catch {[focus] edit redo}} \
2040         -accelerator $M1T-Y
2041 .mbar.edit add separator
2042 .mbar.edit add command -label [mc Cut] \
2043         -command {catch {tk_textCut [focus]}} \
2044         -accelerator $M1T-X
2045 .mbar.edit add command -label [mc Copy] \
2046         -command {catch {tk_textCopy [focus]}} \
2047         -accelerator $M1T-C
2048 .mbar.edit add command -label [mc Paste] \
2049         -command {catch {tk_textPaste [focus]; [focus] see insert}} \
2050         -accelerator $M1T-V
2051 .mbar.edit add command -label [mc Delete] \
2052         -command {catch {[focus] delete sel.first sel.last}} \
2053         -accelerator Del
2054 .mbar.edit add separator
2055 .mbar.edit add command -label [mc "Select All"] \
2056         -command {catch {[focus] tag add sel 0.0 end}} \
2057         -accelerator $M1T-A
2059 # -- Branch Menu
2061 if {[is_enabled branch]} {
2062         menu .mbar.branch
2064         .mbar.branch add command -label [mc "Create..."] \
2065                 -command branch_create::dialog \
2066                 -accelerator $M1T-N
2067         lappend disable_on_lock [list .mbar.branch entryconf \
2068                 [.mbar.branch index last] -state]
2070         .mbar.branch add command -label [mc "Checkout..."] \
2071                 -command branch_checkout::dialog \
2072                 -accelerator $M1T-O
2073         lappend disable_on_lock [list .mbar.branch entryconf \
2074                 [.mbar.branch index last] -state]
2076         .mbar.branch add command -label [mc "Rename..."] \
2077                 -command branch_rename::dialog
2078         lappend disable_on_lock [list .mbar.branch entryconf \
2079                 [.mbar.branch index last] -state]
2081         .mbar.branch add command -label [mc "Delete..."] \
2082                 -command branch_delete::dialog
2083         lappend disable_on_lock [list .mbar.branch entryconf \
2084                 [.mbar.branch index last] -state]
2086         .mbar.branch add command -label [mc "Reset..."] \
2087                 -command merge::reset_hard
2088         lappend disable_on_lock [list .mbar.branch entryconf \
2089                 [.mbar.branch index last] -state]
2092 # -- Commit Menu
2094 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2095         menu .mbar.commit
2097         .mbar.commit add radiobutton \
2098                 -label [mc "New Commit"] \
2099                 -command do_select_commit_type \
2100                 -variable selected_commit_type \
2101                 -value new
2102         lappend disable_on_lock \
2103                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2105         .mbar.commit add radiobutton \
2106                 -label [mc "Amend Last Commit"] \
2107                 -command do_select_commit_type \
2108                 -variable selected_commit_type \
2109                 -value amend
2110         lappend disable_on_lock \
2111                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2113         .mbar.commit add separator
2115         .mbar.commit add command -label [mc Rescan] \
2116                 -command do_rescan \
2117                 -accelerator F5
2118         lappend disable_on_lock \
2119                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2121         .mbar.commit add command -label [mc "Stage To Commit"] \
2122                 -command do_add_selection \
2123                 -accelerator $M1T-T
2124         lappend disable_on_lock \
2125                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2127         .mbar.commit add command -label [mc "Stage Changed Files To Commit"] \
2128                 -command do_add_all \
2129                 -accelerator $M1T-I
2130         lappend disable_on_lock \
2131                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2133         .mbar.commit add command -label [mc "Unstage From Commit"] \
2134                 -command do_unstage_selection
2135         lappend disable_on_lock \
2136                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2138         .mbar.commit add command -label [mc "Revert Changes"] \
2139                 -command do_revert_selection
2140         lappend disable_on_lock \
2141                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2143         .mbar.commit add separator
2145         .mbar.commit add command -label [mc "Show Less Context"] \
2146                 -command show_less_context \
2147                 -accelerator $M1T-\-
2149         .mbar.commit add command -label [mc "Show More Context"] \
2150                 -command show_more_context \
2151                 -accelerator $M1T-=
2153         .mbar.commit add separator
2155         .mbar.commit add command -label [mc "Sign Off"] \
2156                 -command do_signoff \
2157                 -accelerator $M1T-S
2159         .mbar.commit add command -label [mc Commit@@verb] \
2160                 -command do_commit \
2161                 -accelerator $M1T-Return
2162         lappend disable_on_lock \
2163                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2166 # -- Merge Menu
2168 if {[is_enabled branch]} {
2169         menu .mbar.merge
2170         .mbar.merge add command -label [mc "Local Merge..."] \
2171                 -command merge::dialog \
2172                 -accelerator $M1T-M
2173         lappend disable_on_lock \
2174                 [list .mbar.merge entryconf [.mbar.merge index last] -state]
2175         .mbar.merge add command -label [mc "Abort Merge..."] \
2176                 -command merge::reset_hard
2177         lappend disable_on_lock \
2178                 [list .mbar.merge entryconf [.mbar.merge index last] -state]
2181 # -- Transport Menu
2183 if {[is_enabled transport]} {
2184         menu .mbar.remote
2186         .mbar.remote add command \
2187                 -label [mc "Push..."] \
2188                 -command do_push_anywhere \
2189                 -accelerator $M1T-P
2190         .mbar.remote add command \
2191                 -label [mc "Delete..."] \
2192                 -command remote_branch_delete::dialog
2195 if {[is_MacOSX]} {
2196         # -- Apple Menu (Mac OS X only)
2197         #
2198         .mbar add cascade -label Apple -menu .mbar.apple
2199         menu .mbar.apple
2201         .mbar.apple add command -label [mc "About %s" [appname]] \
2202                 -command do_about
2203         .mbar.apple add separator
2204         .mbar.apple add command \
2205                 -label [mc "Preferences..."] \
2206                 -command do_options \
2207                 -accelerator $M1T-,
2208         bind . <$M1B-,> do_options
2209 } else {
2210         # -- Edit Menu
2211         #
2212         .mbar.edit add separator
2213         .mbar.edit add command -label [mc "Options..."] \
2214                 -command do_options
2217 # -- Help Menu
2219 .mbar add cascade -label [mc Help] -menu .mbar.help
2220 menu .mbar.help
2222 if {![is_MacOSX]} {
2223         .mbar.help add command -label [mc "About %s" [appname]] \
2224                 -command do_about
2227 set browser {}
2228 catch {set browser $repo_config(instaweb.browser)}
2229 set doc_path [file dirname [gitexec]]
2230 set doc_path [file join $doc_path Documentation index.html]
2232 if {[is_Cygwin]} {
2233         set doc_path [exec cygpath --mixed $doc_path]
2236 if {$browser eq {}} {
2237         if {[is_MacOSX]} {
2238                 set browser open
2239         } elseif {[is_Cygwin]} {
2240                 set program_files [file dirname [exec cygpath --windir]]
2241                 set program_files [file join $program_files {Program Files}]
2242                 set firefox [file join $program_files {Mozilla Firefox} firefox.exe]
2243                 set ie [file join $program_files {Internet Explorer} IEXPLORE.EXE]
2244                 if {[file exists $firefox]} {
2245                         set browser $firefox
2246                 } elseif {[file exists $ie]} {
2247                         set browser $ie
2248                 }
2249                 unset program_files firefox ie
2250         }
2253 if {[file isfile $doc_path]} {
2254         set doc_url "file:$doc_path"
2255 } else {
2256         set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
2259 if {$browser ne {}} {
2260         .mbar.help add command -label [mc "Online Documentation"] \
2261                 -command [list exec $browser $doc_url &]
2263 unset browser doc_path doc_url
2265 # -- Standard bindings
2267 wm protocol . WM_DELETE_WINDOW do_quit
2268 bind all <$M1B-Key-q> do_quit
2269 bind all <$M1B-Key-Q> do_quit
2270 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
2271 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
2273 set subcommand_args {}
2274 proc usage {} {
2275         puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
2276         exit 1
2279 # -- Not a normal commit type invocation?  Do that instead!
2281 switch -- $subcommand {
2282 browser -
2283 blame {
2284         set subcommand_args {rev? path}
2285         if {$argv eq {}} usage
2286         set head {}
2287         set path {}
2288         set is_path 0
2289         foreach a $argv {
2290                 if {$is_path || [file exists $_prefix$a]} {
2291                         if {$path ne {}} usage
2292                         set path $_prefix$a
2293                         break
2294                 } elseif {$a eq {--}} {
2295                         if {$path ne {}} {
2296                                 if {$head ne {}} usage
2297                                 set head $path
2298                                 set path {}
2299                         }
2300                         set is_path 1
2301                 } elseif {$head eq {}} {
2302                         if {$head ne {}} usage
2303                         set head $a
2304                         set is_path 1
2305                 } else {
2306                         usage
2307                 }
2308         }
2309         unset is_path
2311         if {$head ne {} && $path eq {}} {
2312                 set path $_prefix$head
2313                 set head {}
2314         }
2316         if {$head eq {}} {
2317                 load_current_branch
2318         } else {
2319                 if {[regexp {^[0-9a-f]{1,39}$} $head]} {
2320                         if {[catch {
2321                                         set head [git rev-parse --verify $head]
2322                                 } err]} {
2323                                 puts stderr $err
2324                                 exit 1
2325                         }
2326                 }
2327                 set current_branch $head
2328         }
2330         switch -- $subcommand {
2331         browser {
2332                 if {$head eq {}} {
2333                         if {$path ne {} && [file isdirectory $path]} {
2334                                 set head $current_branch
2335                         } else {
2336                                 set head $path
2337                                 set path {}
2338                         }
2339                 }
2340                 browser::new $head $path
2341         }
2342         blame   {
2343                 if {$head eq {} && ![file exists $path]} {
2344                         puts stderr [mc "fatal: cannot stat path %s: No such file or directory" $path]
2345                         exit 1
2346                 }
2347                 blame::new $head $path
2348         }
2349         }
2350         return
2352 citool -
2353 gui {
2354         if {[llength $argv] != 0} {
2355                 puts -nonewline stderr "usage: $argv0"
2356                 if {$subcommand ne {gui}
2357                         && [file tail $argv0] ne "git-$subcommand"} {
2358                         puts -nonewline stderr " $subcommand"
2359                 }
2360                 puts stderr {}
2361                 exit 1
2362         }
2363         # fall through to setup UI for commits
2365 default {
2366         puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
2367         exit 1
2371 # -- Branch Control
2373 frame .branch \
2374         -borderwidth 1 \
2375         -relief sunken
2376 label .branch.l1 \
2377         -text [mc "Current Branch:"] \
2378         -anchor w \
2379         -justify left
2380 label .branch.cb \
2381         -textvariable current_branch \
2382         -anchor w \
2383         -justify left
2384 pack .branch.l1 -side left
2385 pack .branch.cb -side left -fill x
2386 pack .branch -side top -fill x
2388 # -- Main Window Layout
2390 panedwindow .vpane -orient horizontal
2391 panedwindow .vpane.files -orient vertical
2392 .vpane add .vpane.files -sticky nsew -height 100 -width 200
2393 pack .vpane -anchor n -side top -fill both -expand 1
2395 # -- Index File List
2397 frame .vpane.files.index -height 100 -width 200
2398 label .vpane.files.index.title -text [mc "Staged Changes (Will Commit)"] \
2399         -background lightgreen -foreground black
2400 text $ui_index -background white -foreground black \
2401         -borderwidth 0 \
2402         -width 20 -height 10 \
2403         -wrap none \
2404         -cursor $cursor_ptr \
2405         -xscrollcommand {.vpane.files.index.sx set} \
2406         -yscrollcommand {.vpane.files.index.sy set} \
2407         -state disabled
2408 scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
2409 scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
2410 pack .vpane.files.index.title -side top -fill x
2411 pack .vpane.files.index.sx -side bottom -fill x
2412 pack .vpane.files.index.sy -side right -fill y
2413 pack $ui_index -side left -fill both -expand 1
2415 # -- Working Directory File List
2417 frame .vpane.files.workdir -height 100 -width 200
2418 label .vpane.files.workdir.title -text [mc "Unstaged Changes"] \
2419         -background lightsalmon -foreground black
2420 text $ui_workdir -background white -foreground black \
2421         -borderwidth 0 \
2422         -width 20 -height 10 \
2423         -wrap none \
2424         -cursor $cursor_ptr \
2425         -xscrollcommand {.vpane.files.workdir.sx set} \
2426         -yscrollcommand {.vpane.files.workdir.sy set} \
2427         -state disabled
2428 scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
2429 scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
2430 pack .vpane.files.workdir.title -side top -fill x
2431 pack .vpane.files.workdir.sx -side bottom -fill x
2432 pack .vpane.files.workdir.sy -side right -fill y
2433 pack $ui_workdir -side left -fill both -expand 1
2435 .vpane.files add .vpane.files.workdir -sticky nsew
2436 .vpane.files add .vpane.files.index -sticky nsew
2438 foreach i [list $ui_index $ui_workdir] {
2439         rmsel_tag $i
2440         $i tag conf in_diff -background [$i tag cget in_sel -background]
2442 unset i
2444 # -- Diff and Commit Area
2446 frame .vpane.lower -height 300 -width 400
2447 frame .vpane.lower.commarea
2448 frame .vpane.lower.diff -relief sunken -borderwidth 1
2449 pack .vpane.lower.diff -fill both -expand 1
2450 pack .vpane.lower.commarea -side bottom -fill x
2451 .vpane add .vpane.lower -sticky nsew
2453 # -- Commit Area Buttons
2455 frame .vpane.lower.commarea.buttons
2456 label .vpane.lower.commarea.buttons.l -text {} \
2457         -anchor w \
2458         -justify left
2459 pack .vpane.lower.commarea.buttons.l -side top -fill x
2460 pack .vpane.lower.commarea.buttons -side left -fill y
2462 button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \
2463         -command do_rescan
2464 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
2465 lappend disable_on_lock \
2466         {.vpane.lower.commarea.buttons.rescan conf -state}
2468 button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \
2469         -command do_add_all
2470 pack .vpane.lower.commarea.buttons.incall -side top -fill x
2471 lappend disable_on_lock \
2472         {.vpane.lower.commarea.buttons.incall conf -state}
2474 button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \
2475         -command do_signoff
2476 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
2478 button .vpane.lower.commarea.buttons.commit -text [mc Commit@@verb] \
2479         -command do_commit
2480 pack .vpane.lower.commarea.buttons.commit -side top -fill x
2481 lappend disable_on_lock \
2482         {.vpane.lower.commarea.buttons.commit conf -state}
2484 button .vpane.lower.commarea.buttons.push -text [mc Push] \
2485         -command do_push_anywhere
2486 pack .vpane.lower.commarea.buttons.push -side top -fill x
2488 # -- Commit Message Buffer
2490 frame .vpane.lower.commarea.buffer
2491 frame .vpane.lower.commarea.buffer.header
2492 set ui_comm .vpane.lower.commarea.buffer.t
2493 set ui_coml .vpane.lower.commarea.buffer.header.l
2494 radiobutton .vpane.lower.commarea.buffer.header.new \
2495         -text [mc "New Commit"] \
2496         -command do_select_commit_type \
2497         -variable selected_commit_type \
2498         -value new
2499 lappend disable_on_lock \
2500         [list .vpane.lower.commarea.buffer.header.new conf -state]
2501 radiobutton .vpane.lower.commarea.buffer.header.amend \
2502         -text [mc "Amend Last Commit"] \
2503         -command do_select_commit_type \
2504         -variable selected_commit_type \
2505         -value amend
2506 lappend disable_on_lock \
2507         [list .vpane.lower.commarea.buffer.header.amend conf -state]
2508 label $ui_coml \
2509         -anchor w \
2510         -justify left
2511 proc trace_commit_type {varname args} {
2512         global ui_coml commit_type
2513         switch -glob -- $commit_type {
2514         initial       {set txt [mc "Initial Commit Message:"]}
2515         amend         {set txt [mc "Amended Commit Message:"]}
2516         amend-initial {set txt [mc "Amended Initial Commit Message:"]}
2517         amend-merge   {set txt [mc "Amended Merge Commit Message:"]}
2518         merge         {set txt [mc "Merge Commit Message:"]}
2519         *             {set txt [mc "Commit Message:"]}
2520         }
2521         $ui_coml conf -text $txt
2523 trace add variable commit_type write trace_commit_type
2524 pack $ui_coml -side left -fill x
2525 pack .vpane.lower.commarea.buffer.header.amend -side right
2526 pack .vpane.lower.commarea.buffer.header.new -side right
2528 text $ui_comm -background white -foreground black \
2529         -borderwidth 1 \
2530         -undo true \
2531         -maxundo 20 \
2532         -autoseparators true \
2533         -relief sunken \
2534         -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \
2535         -font font_diff \
2536         -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
2537 scrollbar .vpane.lower.commarea.buffer.sby \
2538         -command [list $ui_comm yview]
2539 pack .vpane.lower.commarea.buffer.header -side top -fill x
2540 pack .vpane.lower.commarea.buffer.sby -side right -fill y
2541 pack $ui_comm -side left -fill y
2542 pack .vpane.lower.commarea.buffer -side left -fill y
2544 # -- Commit Message Buffer Context Menu
2546 set ctxm .vpane.lower.commarea.buffer.ctxm
2547 menu $ctxm -tearoff 0
2548 $ctxm add command \
2549         -label [mc Cut] \
2550         -command {tk_textCut $ui_comm}
2551 $ctxm add command \
2552         -label [mc Copy] \
2553         -command {tk_textCopy $ui_comm}
2554 $ctxm add command \
2555         -label [mc Paste] \
2556         -command {tk_textPaste $ui_comm}
2557 $ctxm add command \
2558         -label [mc Delete] \
2559         -command {$ui_comm delete sel.first sel.last}
2560 $ctxm add separator
2561 $ctxm add command \
2562         -label [mc "Select All"] \
2563         -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
2564 $ctxm add command \
2565         -label [mc "Copy All"] \
2566         -command {
2567                 $ui_comm tag add sel 0.0 end
2568                 tk_textCopy $ui_comm
2569                 $ui_comm tag remove sel 0.0 end
2570         }
2571 $ctxm add separator
2572 $ctxm add command \
2573         -label [mc "Sign Off"] \
2574         -command do_signoff
2575 set ui_comm_ctxm $ctxm
2577 # -- Diff Header
2579 proc trace_current_diff_path {varname args} {
2580         global current_diff_path diff_actions file_states
2581         if {$current_diff_path eq {}} {
2582                 set s {}
2583                 set f {}
2584                 set p {}
2585                 set o disabled
2586         } else {
2587                 set p $current_diff_path
2588                 set s [mapdesc [lindex $file_states($p) 0] $p]
2589                 set f [mc "File:"]
2590                 set p [escape_path $p]
2591                 set o normal
2592         }
2594         .vpane.lower.diff.header.status configure -text $s
2595         .vpane.lower.diff.header.file configure -text $f
2596         .vpane.lower.diff.header.path configure -text $p
2597         foreach w $diff_actions {
2598                 uplevel #0 $w $o
2599         }
2601 trace add variable current_diff_path write trace_current_diff_path
2603 frame .vpane.lower.diff.header -background gold
2604 label .vpane.lower.diff.header.status \
2605         -background gold \
2606         -foreground black \
2607         -width $max_status_desc \
2608         -anchor w \
2609         -justify left
2610 label .vpane.lower.diff.header.file \
2611         -background gold \
2612         -foreground black \
2613         -anchor w \
2614         -justify left
2615 label .vpane.lower.diff.header.path \
2616         -background gold \
2617         -foreground black \
2618         -anchor w \
2619         -justify left
2620 pack .vpane.lower.diff.header.status -side left
2621 pack .vpane.lower.diff.header.file -side left
2622 pack .vpane.lower.diff.header.path -fill x
2623 set ctxm .vpane.lower.diff.header.ctxm
2624 menu $ctxm -tearoff 0
2625 $ctxm add command \
2626         -label [mc Copy] \
2627         -command {
2628                 clipboard clear
2629                 clipboard append \
2630                         -format STRING \
2631                         -type STRING \
2632                         -- $current_diff_path
2633         }
2634 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2635 bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
2637 # -- Diff Body
2639 frame .vpane.lower.diff.body
2640 set ui_diff .vpane.lower.diff.body.t
2641 text $ui_diff -background white -foreground black \
2642         -borderwidth 0 \
2643         -width 80 -height 15 -wrap none \
2644         -font font_diff \
2645         -xscrollcommand {.vpane.lower.diff.body.sbx set} \
2646         -yscrollcommand {.vpane.lower.diff.body.sby set} \
2647         -state disabled
2648 scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
2649         -command [list $ui_diff xview]
2650 scrollbar .vpane.lower.diff.body.sby -orient vertical \
2651         -command [list $ui_diff yview]
2652 pack .vpane.lower.diff.body.sbx -side bottom -fill x
2653 pack .vpane.lower.diff.body.sby -side right -fill y
2654 pack $ui_diff -side left -fill both -expand 1
2655 pack .vpane.lower.diff.header -side top -fill x
2656 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
2658 $ui_diff tag conf d_cr -elide true
2659 $ui_diff tag conf d_@ -foreground blue -font font_diffbold
2660 $ui_diff tag conf d_+ -foreground {#00a000}
2661 $ui_diff tag conf d_- -foreground red
2663 $ui_diff tag conf d_++ -foreground {#00a000}
2664 $ui_diff tag conf d_-- -foreground red
2665 $ui_diff tag conf d_+s \
2666         -foreground {#00a000} \
2667         -background {#e2effa}
2668 $ui_diff tag conf d_-s \
2669         -foreground red \
2670         -background {#e2effa}
2671 $ui_diff tag conf d_s+ \
2672         -foreground {#00a000} \
2673         -background ivory1
2674 $ui_diff tag conf d_s- \
2675         -foreground red \
2676         -background ivory1
2678 $ui_diff tag conf d<<<<<<< \
2679         -foreground orange \
2680         -font font_diffbold
2681 $ui_diff tag conf d======= \
2682         -foreground orange \
2683         -font font_diffbold
2684 $ui_diff tag conf d>>>>>>> \
2685         -foreground orange \
2686         -font font_diffbold
2688 $ui_diff tag raise sel
2690 # -- Diff Body Context Menu
2692 set ctxm .vpane.lower.diff.body.ctxm
2693 menu $ctxm -tearoff 0
2694 $ctxm add command \
2695         -label [mc "Apply/Reverse Hunk"] \
2696         -command {apply_hunk $cursorX $cursorY}
2697 set ui_diff_applyhunk [$ctxm index last]
2698 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
2699 $ctxm add command \
2700         -label [mc "Apply/Reverse Line"] \
2701         -command {apply_line $cursorX $cursorY; do_rescan}
2702 set ui_diff_applyline [$ctxm index last]
2703 lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state]
2704 $ctxm add separator
2705 $ctxm add command \
2706         -label [mc "Show Less Context"] \
2707         -command show_less_context
2708 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2709 $ctxm add command \
2710         -label [mc "Show More Context"] \
2711         -command show_more_context
2712 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2713 $ctxm add separator
2714 $ctxm add command \
2715         -label [mc Refresh] \
2716         -command reshow_diff
2717 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2718 $ctxm add command \
2719         -label [mc Copy] \
2720         -command {tk_textCopy $ui_diff}
2721 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2722 $ctxm add command \
2723         -label [mc "Select All"] \
2724         -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
2725 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2726 $ctxm add command \
2727         -label [mc "Copy All"] \
2728         -command {
2729                 $ui_diff tag add sel 0.0 end
2730                 tk_textCopy $ui_diff
2731                 $ui_diff tag remove sel 0.0 end
2732         }
2733 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2734 $ctxm add separator
2735 $ctxm add command \
2736         -label [mc "Decrease Font Size"] \
2737         -command {incr_font_size font_diff -1}
2738 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2739 $ctxm add command \
2740         -label [mc "Increase Font Size"] \
2741         -command {incr_font_size font_diff 1}
2742 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2743 $ctxm add separator
2744 $ctxm add command -label [mc "Options..."] \
2745         -command do_options
2746 proc popup_diff_menu {ctxm x y X Y} {
2747         global current_diff_path file_states
2748         set ::cursorX $x
2749         set ::cursorY $y
2750         if {$::ui_index eq $::current_diff_side} {
2751                 set l [mc "Unstage Hunk From Commit"]
2752                 set t [mc "Unstage Line From Commit"]
2753         } else {
2754                 set l [mc "Stage Hunk For Commit"]
2755                 set t [mc "Stage Line For Commit"]
2756         }
2757         if {$::is_3way_diff
2758                 || $current_diff_path eq {}
2759                 || ![info exists file_states($current_diff_path)]
2760                 || {_O} eq [lindex $file_states($current_diff_path) 0]} {
2761                 set s disabled
2762         } else {
2763                 set s normal
2764         }
2765         $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
2766         $ctxm entryconf $::ui_diff_applyline -state $s -label $t
2767         tk_popup $ctxm $X $Y
2769 bind_button3 $ui_diff [list popup_diff_menu $ctxm %x %y %X %Y]
2771 # -- Status Bar
2773 set main_status [::status_bar::new .status]
2774 pack .status -anchor w -side bottom -fill x
2775 $main_status show [mc "Initializing..."]
2777 # -- Load geometry
2779 catch {
2780 set gm $repo_config(gui.geometry)
2781 wm geometry . [lindex $gm 0]
2782 .vpane sash place 0 \
2783         [lindex $gm 1] \
2784         [lindex [.vpane sash coord 0] 1]
2785 .vpane.files sash place 0 \
2786         [lindex [.vpane.files sash coord 0] 0] \
2787         [lindex $gm 2]
2788 unset gm
2791 # -- Key Bindings
2793 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2794 bind $ui_comm <$M1B-Key-t> {do_add_selection;break}
2795 bind $ui_comm <$M1B-Key-T> {do_add_selection;break}
2796 bind $ui_comm <$M1B-Key-i> {do_add_all;break}
2797 bind $ui_comm <$M1B-Key-I> {do_add_all;break}
2798 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2799 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2800 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2801 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2802 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2803 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2804 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2805 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2806 bind $ui_comm <$M1B-Key-minus> {show_less_context;break}
2807 bind $ui_comm <$M1B-Key-KP_Subtract> {show_less_context;break}
2808 bind $ui_comm <$M1B-Key-equal> {show_more_context;break}
2809 bind $ui_comm <$M1B-Key-plus> {show_more_context;break}
2810 bind $ui_comm <$M1B-Key-KP_Add> {show_more_context;break}
2812 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2813 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2814 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2815 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2816 bind $ui_diff <$M1B-Key-v> {break}
2817 bind $ui_diff <$M1B-Key-V> {break}
2818 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2819 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2820 bind $ui_diff <Key-Up>     {catch {%W yview scroll -1 units};break}
2821 bind $ui_diff <Key-Down>   {catch {%W yview scroll  1 units};break}
2822 bind $ui_diff <Key-Left>   {catch {%W xview scroll -1 units};break}
2823 bind $ui_diff <Key-Right>  {catch {%W xview scroll  1 units};break}
2824 bind $ui_diff <Key-k>         {catch {%W yview scroll -1 units};break}
2825 bind $ui_diff <Key-j>         {catch {%W yview scroll  1 units};break}
2826 bind $ui_diff <Key-h>         {catch {%W xview scroll -1 units};break}
2827 bind $ui_diff <Key-l>         {catch {%W xview scroll  1 units};break}
2828 bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
2829 bind $ui_diff <Control-Key-f> {catch {%W yview scroll  1 pages};break}
2830 bind $ui_diff <Button-1>   {focus %W}
2832 if {[is_enabled branch]} {
2833         bind . <$M1B-Key-n> branch_create::dialog
2834         bind . <$M1B-Key-N> branch_create::dialog
2835         bind . <$M1B-Key-o> branch_checkout::dialog
2836         bind . <$M1B-Key-O> branch_checkout::dialog
2837         bind . <$M1B-Key-m> merge::dialog
2838         bind . <$M1B-Key-M> merge::dialog
2840 if {[is_enabled transport]} {
2841         bind . <$M1B-Key-p> do_push_anywhere
2842         bind . <$M1B-Key-P> do_push_anywhere
2845 bind .   <Key-F5>     do_rescan
2846 bind .   <$M1B-Key-r> do_rescan
2847 bind .   <$M1B-Key-R> do_rescan
2848 bind .   <$M1B-Key-s> do_signoff
2849 bind .   <$M1B-Key-S> do_signoff
2850 bind .   <$M1B-Key-t> do_add_selection
2851 bind .   <$M1B-Key-T> do_add_selection
2852 bind .   <$M1B-Key-i> do_add_all
2853 bind .   <$M1B-Key-I> do_add_all
2854 bind .   <$M1B-Key-minus> {show_less_context;break}
2855 bind .   <$M1B-Key-KP_Subtract> {show_less_context;break}
2856 bind .   <$M1B-Key-equal> {show_more_context;break}
2857 bind .   <$M1B-Key-plus> {show_more_context;break}
2858 bind .   <$M1B-Key-KP_Add> {show_more_context;break}
2859 bind .   <$M1B-Key-Return> do_commit
2860 foreach i [list $ui_index $ui_workdir] {
2861         bind $i <Button-1>       "toggle_or_diff         $i %x %y; break"
2862         bind $i <$M1B-Button-1>  "add_one_to_selection   $i %x %y; break"
2863         bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
2865 unset i
2867 set file_lists($ui_index) [list]
2868 set file_lists($ui_workdir) [list]
2870 wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
2871 focus -force $ui_comm
2873 # -- Warn the user about environmental problems.  Cygwin's Tcl
2874 #    does *not* pass its env array onto any processes it spawns.
2875 #    This means that git processes get none of our environment.
2877 if {[is_Cygwin]} {
2878         set ignored_env 0
2879         set suggest_user {}
2880         set msg [mc "Possible environment issues exist.
2882 The following environment variables are probably
2883 going to be ignored by any Git subprocess run
2884 by %s:
2886 " [appname]]
2887         foreach name [array names env] {
2888                 switch -regexp -- $name {
2889                 {^GIT_INDEX_FILE$} -
2890                 {^GIT_OBJECT_DIRECTORY$} -
2891                 {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
2892                 {^GIT_DIFF_OPTS$} -
2893                 {^GIT_EXTERNAL_DIFF$} -
2894                 {^GIT_PAGER$} -
2895                 {^GIT_TRACE$} -
2896                 {^GIT_CONFIG$} -
2897                 {^GIT_CONFIG_LOCAL$} -
2898                 {^GIT_(AUTHOR|COMMITTER)_DATE$} {
2899                         append msg " - $name\n"
2900                         incr ignored_env
2901                 }
2902                 {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
2903                         append msg " - $name\n"
2904                         incr ignored_env
2905                         set suggest_user $name
2906                 }
2907                 }
2908         }
2909         if {$ignored_env > 0} {
2910                 append msg [mc "
2911 This is due to a known issue with the
2912 Tcl binary distributed by Cygwin."]
2914                 if {$suggest_user ne {}} {
2915                         append msg [mc "
2917 A good replacement for %s
2918 is placing values for the user.name and
2919 user.email settings into your personal
2920 ~/.gitconfig file.
2921 " $suggest_user]
2922                 }
2923                 warn_popup $msg
2924         }
2925         unset ignored_env msg suggest_user name
2928 # -- Only initialize complex UI if we are going to stay running.
2930 if {[is_enabled transport]} {
2931         load_all_remotes
2933         set n [.mbar.remote index end]
2934         populate_push_menu
2935         populate_fetch_menu
2936         set n [expr {[.mbar.remote index end] - $n}]
2937         if {$n > 0} {
2938                 if {[.mbar.remote type 0] eq "tearoff"} { incr n }
2939                 .mbar.remote insert $n separator
2940         }
2941         unset n
2944 if {[winfo exists $ui_comm]} {
2945         set GITGUI_BCK_exists [load_message GITGUI_BCK]
2947         # -- If both our backup and message files exist use the
2948         #    newer of the two files to initialize the buffer.
2949         #
2950         if {$GITGUI_BCK_exists} {
2951                 set m [gitdir GITGUI_MSG]
2952                 if {[file isfile $m]} {
2953                         if {[file mtime [gitdir GITGUI_BCK]] > [file mtime $m]} {
2954                                 catch {file delete [gitdir GITGUI_MSG]}
2955                         } else {
2956                                 $ui_comm delete 0.0 end
2957                                 $ui_comm edit reset
2958                                 $ui_comm edit modified false
2959                                 catch {file delete [gitdir GITGUI_BCK]}
2960                                 set GITGUI_BCK_exists 0
2961                         }
2962                 }
2963                 unset m
2964         }
2966         proc backup_commit_buffer {} {
2967                 global ui_comm GITGUI_BCK_exists
2969                 set m [$ui_comm edit modified]
2970                 if {$m || $GITGUI_BCK_exists} {
2971                         set msg [string trim [$ui_comm get 0.0 end]]
2972                         regsub -all -line {[ \r\t]+$} $msg {} msg
2974                         if {$msg eq {}} {
2975                                 if {$GITGUI_BCK_exists} {
2976                                         catch {file delete [gitdir GITGUI_BCK]}
2977                                         set GITGUI_BCK_exists 0
2978                                 }
2979                         } elseif {$m} {
2980                                 catch {
2981                                         set fd [open [gitdir GITGUI_BCK] w]
2982                                         puts -nonewline $fd $msg
2983                                         close $fd
2984                                         set GITGUI_BCK_exists 1
2985                                 }
2986                         }
2988                         $ui_comm edit modified false
2989                 }
2991                 set ::GITGUI_BCK_i [after 2000 backup_commit_buffer]
2992         }
2994         backup_commit_buffer
2996         # -- If the user has aspell available we can drive it
2997         #    in pipe mode to spellcheck the commit message.
2998         #
2999         set spell_cmd [list |]
3000         set spell_dict [get_config gui.spellingdictionary]
3001         lappend spell_cmd aspell
3002         if {$spell_dict ne {}} {
3003                 lappend spell_cmd --master=$spell_dict
3004         }
3005         lappend spell_cmd --mode=none
3006         lappend spell_cmd --encoding=utf-8
3007         lappend spell_cmd pipe
3008         if {$spell_dict eq {none}
3009          || [catch {set spell_fd [open $spell_cmd r+]} spell_err]} {
3010                 bind_button3 $ui_comm [list tk_popup $ui_comm_ctxm %X %Y]
3011         } else {
3012                 set ui_comm_spell [spellcheck::init \
3013                         $spell_fd \
3014                         $ui_comm \
3015                         $ui_comm_ctxm \
3016                 ]
3017         }
3018         unset -nocomplain spell_cmd spell_fd spell_err spell_dict
3021 lock_index begin-read
3022 if {![winfo ismapped .]} {
3023         wm deiconify .
3025 after 1 do_rescan
3026 if {[is_enabled multicommit]} {
3027         after 1000 hint_gc