Code

git-gui: generic version trimming
[git.git] / git-gui.sh
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3  if test "z$*" = zversion \
4  || test "z$*" = z--version; \
5  then \
6         echo 'git-gui version @@GITGUI_VERSION@@'; \
7         exit; \
8  fi; \
9  argv0=$0; \
10  exec wish "$argv0" -- "$@"
12 set appvers {@@GITGUI_VERSION@@}
13 set copyright [string map [list (c) \u00a9] {
14 Copyright (c) 2006-2010 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 "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         if {[tk windowingsystem] eq "win32"} { console show }
87 }
89 ######################################################################
90 ##
91 ## Internationalization (i18n) through msgcat and gettext. See
92 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
94 package require msgcat
96 proc _mc_trim {fmt} {
97         set cmk [string first @@ $fmt]
98         if {$cmk > 0} {
99                 return [string range $fmt 0 [expr {$cmk - 1}]]
100         }
101         return $fmt
104 proc mc {en_fmt args} {
105         set fmt [_mc_trim [::msgcat::mc $en_fmt]]
106         if {[catch {set msg [eval [list format $fmt] $args]} err]} {
107                 set msg [eval [list format [_mc_trim $en_fmt]] $args]
108         }
109         return $msg
112 proc strcat {args} {
113         return [join $args {}]
116 ::msgcat::mcload $oguimsg
117 unset oguimsg
119 ######################################################################
120 ##
121 ## read only globals
123 set _appname {Git Gui}
124 set _gitdir {}
125 set _gitworktree {}
126 set _isbare {}
127 set _gitexec {}
128 set _githtmldir {}
129 set _reponame {}
130 set _iscygwin {}
131 set _search_path {}
132 set _shellpath {@@SHELL_PATH@@}
134 set _trace [lsearch -exact $argv --trace]
135 if {$_trace >= 0} {
136         set argv [lreplace $argv $_trace $_trace]
137         set _trace 1
138 } else {
139         set _trace 0
142 proc shellpath {} {
143         global _shellpath env
144         if {[string match @@* $_shellpath]} {
145                 if {[info exists env(SHELL)]} {
146                         return $env(SHELL)
147                 } else {
148                         return /bin/sh
149                 }
150         }
151         return $_shellpath
154 proc appname {} {
155         global _appname
156         return $_appname
159 proc gitdir {args} {
160         global _gitdir
161         if {$args eq {}} {
162                 return $_gitdir
163         }
164         return [eval [list file join $_gitdir] $args]
167 proc gitexec {args} {
168         global _gitexec
169         if {$_gitexec eq {}} {
170                 if {[catch {set _gitexec [git --exec-path]} err]} {
171                         error "Git not installed?\n\n$err"
172                 }
173                 if {[is_Cygwin]} {
174                         set _gitexec [exec cygpath \
175                                 --windows \
176                                 --absolute \
177                                 $_gitexec]
178                 } else {
179                         set _gitexec [file normalize $_gitexec]
180                 }
181         }
182         if {$args eq {}} {
183                 return $_gitexec
184         }
185         return [eval [list file join $_gitexec] $args]
188 proc githtmldir {args} {
189         global _githtmldir
190         if {$_githtmldir eq {}} {
191                 if {[catch {set _githtmldir [git --html-path]}]} {
192                         # Git not installed or option not yet supported
193                         return {}
194                 }
195                 if {[is_Cygwin]} {
196                         set _githtmldir [exec cygpath \
197                                 --windows \
198                                 --absolute \
199                                 $_githtmldir]
200                 } else {
201                         set _githtmldir [file normalize $_githtmldir]
202                 }
203         }
204         if {$args eq {}} {
205                 return $_githtmldir
206         }
207         return [eval [list file join $_githtmldir] $args]
210 proc reponame {} {
211         return $::_reponame
214 proc is_MacOSX {} {
215         if {[tk windowingsystem] eq {aqua}} {
216                 return 1
217         }
218         return 0
221 proc is_Windows {} {
222         if {$::tcl_platform(platform) eq {windows}} {
223                 return 1
224         }
225         return 0
228 proc is_Cygwin {} {
229         global _iscygwin
230         if {$_iscygwin eq {}} {
231                 if {$::tcl_platform(platform) eq {windows}} {
232                         if {[catch {set p [exec cygpath --windir]} err]} {
233                                 set _iscygwin 0
234                         } else {
235                                 set _iscygwin 1
236                         }
237                 } else {
238                         set _iscygwin 0
239                 }
240         }
241         return $_iscygwin
244 proc is_enabled {option} {
245         global enabled_options
246         if {[catch {set on $enabled_options($option)}]} {return 0}
247         return $on
250 proc enable_option {option} {
251         global enabled_options
252         set enabled_options($option) 1
255 proc disable_option {option} {
256         global enabled_options
257         set enabled_options($option) 0
260 ######################################################################
261 ##
262 ## config
264 proc is_many_config {name} {
265         switch -glob -- $name {
266         gui.recentrepo -
267         remote.*.fetch -
268         remote.*.push
269                 {return 1}
270         *
271                 {return 0}
272         }
275 proc is_config_true {name} {
276         global repo_config
277         if {[catch {set v $repo_config($name)}]} {
278                 return 0
279         } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
280                 return 1
281         } else {
282                 return 0
283         }
286 proc is_config_false {name} {
287         global repo_config
288         if {[catch {set v $repo_config($name)}]} {
289                 return 0
290         } elseif {$v eq {false} || $v eq {0} || $v eq {no}} {
291                 return 1
292         } else {
293                 return 0
294         }
297 proc get_config {name} {
298         global repo_config
299         if {[catch {set v $repo_config($name)}]} {
300                 return {}
301         } else {
302                 return $v
303         }
306 proc is_bare {} {
307         global _isbare
308         global _gitdir
309         global _gitworktree
311         if {$_isbare eq {}} {
312                 if {[catch {
313                         set _bare [git rev-parse --is-bare-repository]
314                         switch  -- $_bare {
315                         true { set _isbare 1 }
316                         false { set _isbare 0}
317                         default { throw }
318                         }
319                 }]} {
320                         if {[is_config_true core.bare]
321                                 || ($_gitworktree eq {}
322                                         && [lindex [file split $_gitdir] end] ne {.git})} {
323                                 set _isbare 1
324                         } else {
325                                 set _isbare 0
326                         }
327                 }
328         }
329         return $_isbare
332 ######################################################################
333 ##
334 ## handy utils
336 proc _trace_exec {cmd} {
337         if {!$::_trace} return
338         set d {}
339         foreach v $cmd {
340                 if {$d ne {}} {
341                         append d { }
342                 }
343                 if {[regexp {[ \t\r\n'"$?*]} $v]} {
344                         set v [sq $v]
345                 }
346                 append d $v
347         }
348         puts stderr $d
351 #'"  fix poor old emacs font-lock mode
353 proc _git_cmd {name} {
354         global _git_cmd_path
356         if {[catch {set v $_git_cmd_path($name)}]} {
357                 switch -- $name {
358                   version   -
359                 --version   -
360                 --exec-path { return [list $::_git $name] }
361                 }
363                 set p [gitexec git-$name$::_search_exe]
364                 if {[file exists $p]} {
365                         set v [list $p]
366                 } elseif {[is_Windows] && [file exists [gitexec git-$name]]} {
367                         # Try to determine what sort of magic will make
368                         # git-$name go and do its thing, because native
369                         # Tcl on Windows doesn't know it.
370                         #
371                         set p [gitexec git-$name]
372                         set f [open $p r]
373                         set s [gets $f]
374                         close $f
376                         switch -glob -- [lindex $s 0] {
377                         #!*sh     { set i sh     }
378                         #!*perl   { set i perl   }
379                         #!*python { set i python }
380                         default   { error "git-$name is not supported: $s" }
381                         }
383                         upvar #0 _$i interp
384                         if {![info exists interp]} {
385                                 set interp [_which $i]
386                         }
387                         if {$interp eq {}} {
388                                 error "git-$name requires $i (not in PATH)"
389                         }
390                         set v [concat [list $interp] [lrange $s 1 end] [list $p]]
391                 } else {
392                         # Assume it is builtin to git somehow and we
393                         # aren't actually able to see a file for it.
394                         #
395                         set v [list $::_git $name]
396                 }
397                 set _git_cmd_path($name) $v
398         }
399         return $v
402 proc _which {what args} {
403         global env _search_exe _search_path
405         if {$_search_path eq {}} {
406                 if {[is_Cygwin] && [regexp {^(/|\.:)} $env(PATH)]} {
407                         set _search_path [split [exec cygpath \
408                                 --windows \
409                                 --path \
410                                 --absolute \
411                                 $env(PATH)] {;}]
412                         set _search_exe .exe
413                 } elseif {[is_Windows]} {
414                         set gitguidir [file dirname [info script]]
415                         regsub -all ";" $gitguidir "\\;" gitguidir
416                         set env(PATH) "$gitguidir;$env(PATH)"
417                         set _search_path [split $env(PATH) {;}]
418                         set _search_exe .exe
419                 } else {
420                         set _search_path [split $env(PATH) :]
421                         set _search_exe {}
422                 }
423         }
425         if {[is_Windows] && [lsearch -exact $args -script] >= 0} {
426                 set suffix {}
427         } else {
428                 set suffix $_search_exe
429         }
431         foreach p $_search_path {
432                 set p [file join $p $what$suffix]
433                 if {[file exists $p]} {
434                         return [file normalize $p]
435                 }
436         }
437         return {}
440 proc _lappend_nice {cmd_var} {
441         global _nice
442         upvar $cmd_var cmd
444         if {![info exists _nice]} {
445                 set _nice [_which nice]
446                 if {[catch {exec $_nice git version}]} {
447                         set _nice {}
448                 } elseif {[is_Windows] && [file dirname $_nice] ne [file dirname $::_git]} {
449                         set _nice {}
450                 }
451         }
452         if {$_nice ne {}} {
453                 lappend cmd $_nice
454         }
457 proc git {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         set result [eval exec $opt $cmdp $args]
480         if {$::_trace} {
481                 puts stderr "< $result"
482         }
483         return $result
486 proc _open_stdout_stderr {cmd} {
487         _trace_exec $cmd
488         if {[catch {
489                         set fd [open [concat [list | ] $cmd] r]
490                 } err]} {
491                 if {   [lindex $cmd end] eq {2>@1}
492                     && $err eq {can not find channel named "1"}
493                         } {
494                         # Older versions of Tcl 8.4 don't have this 2>@1 IO
495                         # redirect operator.  Fallback to |& cat for those.
496                         # The command was not actually started, so its safe
497                         # to try to start it a second time.
498                         #
499                         set fd [open [concat \
500                                 [list | ] \
501                                 [lrange $cmd 0 end-1] \
502                                 [list |& cat] \
503                                 ] r]
504                 } else {
505                         error $err
506                 }
507         }
508         fconfigure $fd -eofchar {}
509         return $fd
512 proc git_read {args} {
513         set opt [list]
515         while {1} {
516                 switch -- [lindex $args 0] {
517                 --nice {
518                         _lappend_nice opt
519                 }
521                 --stderr {
522                         lappend args 2>@1
523                 }
525                 default {
526                         break
527                 }
529                 }
531                 set args [lrange $args 1 end]
532         }
534         set cmdp [_git_cmd [lindex $args 0]]
535         set args [lrange $args 1 end]
537         return [_open_stdout_stderr [concat $opt $cmdp $args]]
540 proc git_write {args} {
541         set opt [list]
543         while {1} {
544                 switch -- [lindex $args 0] {
545                 --nice {
546                         _lappend_nice opt
547                 }
549                 default {
550                         break
551                 }
553                 }
555                 set args [lrange $args 1 end]
556         }
558         set cmdp [_git_cmd [lindex $args 0]]
559         set args [lrange $args 1 end]
561         _trace_exec [concat $opt $cmdp $args]
562         return [open [concat [list | ] $opt $cmdp $args] w]
565 proc githook_read {hook_name args} {
566         set pchook [gitdir hooks $hook_name]
567         lappend args 2>@1
569         # On Windows [file executable] might lie so we need to ask
570         # the shell if the hook is executable.  Yes that's annoying.
571         #
572         if {[is_Windows]} {
573                 upvar #0 _sh interp
574                 if {![info exists interp]} {
575                         set interp [_which sh]
576                 }
577                 if {$interp eq {}} {
578                         error "hook execution requires sh (not in PATH)"
579                 }
581                 set scr {if test -x "$1";then exec "$@";fi}
582                 set sh_c [list $interp -c $scr $interp $pchook]
583                 return [_open_stdout_stderr [concat $sh_c $args]]
584         }
586         if {[file executable $pchook]} {
587                 return [_open_stdout_stderr [concat [list $pchook] $args]]
588         }
590         return {}
593 proc kill_file_process {fd} {
594         set process [pid $fd]
596         catch {
597                 if {[is_Windows]} {
598                         # Use a Cygwin-specific flag to allow killing
599                         # native Windows processes
600                         exec kill -f $process
601                 } else {
602                         exec kill $process
603                 }
604         }
607 proc gitattr {path attr default} {
608         if {[catch {set r [git check-attr $attr -- $path]}]} {
609                 set r unspecified
610         } else {
611                 set r [join [lrange [split $r :] 2 end] :]
612                 regsub {^ } $r {} r
613         }
614         if {$r eq {unspecified}} {
615                 return $default
616         }
617         return $r
620 proc sq {value} {
621         regsub -all ' $value "'\\''" value
622         return "'$value'"
625 proc load_current_branch {} {
626         global current_branch is_detached
628         set fd [open [gitdir HEAD] r]
629         if {[gets $fd ref] < 1} {
630                 set ref {}
631         }
632         close $fd
634         set pfx {ref: refs/heads/}
635         set len [string length $pfx]
636         if {[string equal -length $len $pfx $ref]} {
637                 # We're on a branch.  It might not exist.  But
638                 # HEAD looks good enough to be a branch.
639                 #
640                 set current_branch [string range $ref $len end]
641                 set is_detached 0
642         } else {
643                 # Assume this is a detached head.
644                 #
645                 set current_branch HEAD
646                 set is_detached 1
647         }
650 auto_load tk_optionMenu
651 rename tk_optionMenu real__tkOptionMenu
652 proc tk_optionMenu {w varName args} {
653         set m [eval real__tkOptionMenu $w $varName $args]
654         $m configure -font font_ui
655         $w configure -font font_ui
656         return $m
659 proc rmsel_tag {text} {
660         $text tag conf sel \
661                 -background [$text cget -background] \
662                 -foreground [$text cget -foreground] \
663                 -borderwidth 0
664         $text tag conf in_sel -background lightgray
665         bind $text <Motion> break
666         return $text
669 wm withdraw .
670 set root_exists 0
671 bind . <Visibility> {
672         bind . <Visibility> {}
673         set root_exists 1
676 if {[is_Windows]} {
677         wm iconbitmap . -default $oguilib/git-gui.ico
678         set ::tk::AlwaysShowSelection 1
679         bind . <Control-F2> {console show}
681         # Spoof an X11 display for SSH
682         if {![info exists env(DISPLAY)]} {
683                 set env(DISPLAY) :9999
684         }
685 } else {
686         catch {
687                 image create photo gitlogo -width 16 -height 16
689                 gitlogo put #33CC33 -to  7  0  9  2
690                 gitlogo put #33CC33 -to  4  2 12  4
691                 gitlogo put #33CC33 -to  7  4  9  6
692                 gitlogo put #CC3333 -to  4  6 12  8
693                 gitlogo put gray26  -to  4  9  6 10
694                 gitlogo put gray26  -to  3 10  6 12
695                 gitlogo put gray26  -to  8  9 13 11
696                 gitlogo put gray26  -to  8 11 10 12
697                 gitlogo put gray26  -to 11 11 13 14
698                 gitlogo put gray26  -to  3 12  5 14
699                 gitlogo put gray26  -to  5 13
700                 gitlogo put gray26  -to 10 13
701                 gitlogo put gray26  -to  4 14 12 15
702                 gitlogo put gray26  -to  5 15 11 16
703                 gitlogo redither
705                 wm iconphoto . -default gitlogo
706         }
709 ######################################################################
710 ##
711 ## config defaults
713 set cursor_ptr arrow
714 font create font_ui
715 if {[lsearch -exact [font names] TkDefaultFont] != -1} {
716         eval [linsert [font actual TkDefaultFont] 0 font configure font_ui]
717         eval [linsert [font actual TkFixedFont] 0 font create font_diff]
718 } else {
719         font create font_diff -family Courier -size 10
720         catch {
721                 label .dummy
722                 eval font configure font_ui [font actual [.dummy cget -font]]
723                 destroy .dummy
724         }
727 font create font_uiitalic
728 font create font_uibold
729 font create font_diffbold
730 font create font_diffitalic
732 foreach class {Button Checkbutton Entry Label
733                 Labelframe Listbox Message
734                 Radiobutton Spinbox Text} {
735         option add *$class.font font_ui
737 if {![is_MacOSX]} {
738         option add *Menu.font font_ui
739         option add *Entry.borderWidth 1 startupFile
740         option add *Entry.relief sunken startupFile
741         option add *RadioButton.anchor w startupFile
743 unset class
745 if {[is_Windows] || [is_MacOSX]} {
746         option add *Menu.tearOff 0
749 if {[is_MacOSX]} {
750         set M1B M1
751         set M1T Cmd
752 } else {
753         set M1B Control
754         set M1T Ctrl
757 proc bind_button3 {w cmd} {
758         bind $w <Any-Button-3> $cmd
759         if {[is_MacOSX]} {
760                 # Mac OS X sends Button-2 on right click through three-button mouse,
761                 # or through trackpad right-clicking (two-finger touch + click).
762                 bind $w <Any-Button-2> $cmd
763                 bind $w <Control-Button-1> $cmd
764         }
767 proc apply_config {} {
768         global repo_config font_descs
770         foreach option $font_descs {
771                 set name [lindex $option 0]
772                 set font [lindex $option 1]
773                 if {[catch {
774                         set need_weight 1
775                         foreach {cn cv} $repo_config(gui.$name) {
776                                 if {$cn eq {-weight}} {
777                                         set need_weight 0
778                                 }
779                                 font configure $font $cn $cv
780                         }
781                         if {$need_weight} {
782                                 font configure $font -weight normal
783                         }
784                         } err]} {
785                         error_popup [strcat [mc "Invalid font specified in %s:" "gui.$name"] "\n\n$err"]
786                 }
787                 foreach {cn cv} [font configure $font] {
788                         font configure ${font}bold $cn $cv
789                         font configure ${font}italic $cn $cv
790                 }
791                 font configure ${font}bold -weight bold
792                 font configure ${font}italic -slant italic
793         }
795         global use_ttk NS
796         set use_ttk 0
797         set NS {}
798         if {$repo_config(gui.usettk)} {
799                 set use_ttk [package vsatisfies [package provide Tk] 8.5]
800                 if {$use_ttk} {
801                         set NS ttk
802                         bind [winfo class .] <<ThemeChanged>> [list InitTheme]
803                         pave_toplevel .
804                 }
805         }
808 set default_config(branch.autosetupmerge) true
809 set default_config(merge.tool) {}
810 set default_config(mergetool.keepbackup) true
811 set default_config(merge.diffstat) true
812 set default_config(merge.summary) false
813 set default_config(merge.verbosity) 2
814 set default_config(user.name) {}
815 set default_config(user.email) {}
817 set default_config(gui.encoding) [encoding system]
818 set default_config(gui.matchtrackingbranch) false
819 set default_config(gui.textconv) true
820 set default_config(gui.pruneduringfetch) false
821 set default_config(gui.trustmtime) false
822 set default_config(gui.fastcopyblame) false
823 set default_config(gui.copyblamethreshold) 40
824 set default_config(gui.blamehistoryctx) 7
825 set default_config(gui.diffcontext) 5
826 set default_config(gui.commitmsgwidth) 75
827 set default_config(gui.newbranchtemplate) {}
828 set default_config(gui.spellingdictionary) {}
829 set default_config(gui.fontui) [font configure font_ui]
830 set default_config(gui.fontdiff) [font configure font_diff]
831 # TODO: this option should be added to the git-config documentation
832 set default_config(gui.maxfilesdisplayed) 5000
833 set default_config(gui.usettk) 1
834 set font_descs {
835         {fontui   font_ui   {mc "Main Font"}}
836         {fontdiff font_diff {mc "Diff/Console Font"}}
839 ######################################################################
840 ##
841 ## find git
843 set _git  [_which git]
844 if {$_git eq {}} {
845         catch {wm withdraw .}
846         tk_messageBox \
847                 -icon error \
848                 -type ok \
849                 -title [mc "git-gui: fatal error"] \
850                 -message [mc "Cannot find git in PATH."]
851         exit 1
854 ######################################################################
855 ##
856 ## version check
858 if {[catch {set _git_version [git --version]} err]} {
859         catch {wm withdraw .}
860         tk_messageBox \
861                 -icon error \
862                 -type ok \
863                 -title [mc "git-gui: fatal error"] \
864                 -message "Cannot determine Git version:
866 $err
868 [appname] requires Git 1.5.0 or later."
869         exit 1
871 if {![regsub {^git version } $_git_version {} _git_version]} {
872         catch {wm withdraw .}
873         tk_messageBox \
874                 -icon error \
875                 -type ok \
876                 -title [mc "git-gui: fatal error"] \
877                 -message [strcat [mc "Cannot parse Git version string:"] "\n\n$_git_version"]
878         exit 1
881 proc get_trimmed_version {s} {
882     set r {}
883     foreach x [split $s -._] {
884         if {[string is integer -strict $x]} {
885             lappend r $x
886         } else {
887             break
888         }
889     }
890     return [join $r .]
892 set _real_git_version $_git_version
893 set _git_version [get_trimmed_version $_git_version]
895 if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} {
896         catch {wm withdraw .}
897         if {[tk_messageBox \
898                 -icon warning \
899                 -type yesno \
900                 -default no \
901                 -title "[appname]: warning" \
902                  -message [mc "Git version cannot be determined.
904 %s claims it is version '%s'.
906 %s requires at least Git 1.5.0 or later.
908 Assume '%s' is version 1.5.0?
909 " $_git $_real_git_version [appname] $_real_git_version]] eq {yes}} {
910                 set _git_version 1.5.0
911         } else {
912                 exit 1
913         }
915 unset _real_git_version
917 proc git-version {args} {
918         global _git_version
920         switch [llength $args] {
921         0 {
922                 return $_git_version
923         }
925         2 {
926                 set op [lindex $args 0]
927                 set vr [lindex $args 1]
928                 set cm [package vcompare $_git_version $vr]
929                 return [expr $cm $op 0]
930         }
932         4 {
933                 set type [lindex $args 0]
934                 set name [lindex $args 1]
935                 set parm [lindex $args 2]
936                 set body [lindex $args 3]
938                 if {($type ne {proc} && $type ne {method})} {
939                         error "Invalid arguments to git-version"
940                 }
941                 if {[llength $body] < 2 || [lindex $body end-1] ne {default}} {
942                         error "Last arm of $type $name must be default"
943                 }
945                 foreach {op vr cb} [lrange $body 0 end-2] {
946                         if {[git-version $op $vr]} {
947                                 return [uplevel [list $type $name $parm $cb]]
948                         }
949                 }
951                 return [uplevel [list $type $name $parm [lindex $body end]]]
952         }
954         default {
955                 error "git-version >= x"
956         }
958         }
961 if {[git-version < 1.5]} {
962         catch {wm withdraw .}
963         tk_messageBox \
964                 -icon error \
965                 -type ok \
966                 -title [mc "git-gui: fatal error"] \
967                 -message "[appname] requires Git 1.5.0 or later.
969 You are using [git-version]:
971 [git --version]"
972         exit 1
975 ######################################################################
976 ##
977 ## configure our library
979 set idx [file join $oguilib tclIndex]
980 if {[catch {set fd [open $idx r]} err]} {
981         catch {wm withdraw .}
982         tk_messageBox \
983                 -icon error \
984                 -type ok \
985                 -title [mc "git-gui: fatal error"] \
986                 -message $err
987         exit 1
989 if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
990         set idx [list]
991         while {[gets $fd n] >= 0} {
992                 if {$n ne {} && ![string match #* $n]} {
993                         lappend idx $n
994                 }
995         }
996 } else {
997         set idx {}
999 close $fd
1001 if {$idx ne {}} {
1002         set loaded [list]
1003         foreach p $idx {
1004                 if {[lsearch -exact $loaded $p] >= 0} continue
1005                 source [file join $oguilib $p]
1006                 lappend loaded $p
1007         }
1008         unset loaded p
1009 } else {
1010         set auto_path [concat [list $oguilib] $auto_path]
1012 unset -nocomplain idx fd
1014 ######################################################################
1015 ##
1016 ## config file parsing
1018 git-version proc _parse_config {arr_name args} {
1019         >= 1.5.3 {
1020                 upvar $arr_name arr
1021                 array unset arr
1022                 set buf {}
1023                 catch {
1024                         set fd_rc [eval \
1025                                 [list git_read config] \
1026                                 $args \
1027                                 [list --null --list]]
1028                         fconfigure $fd_rc -translation binary
1029                         set buf [read $fd_rc]
1030                         close $fd_rc
1031                 }
1032                 foreach line [split $buf "\0"] {
1033                         if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} {
1034                                 if {[is_many_config $name]} {
1035                                         lappend arr($name) $value
1036                                 } else {
1037                                         set arr($name) $value
1038                                 }
1039                         }
1040                 }
1041         }
1042         default {
1043                 upvar $arr_name arr
1044                 array unset arr
1045                 catch {
1046                         set fd_rc [eval [list git_read config --list] $args]
1047                         while {[gets $fd_rc line] >= 0} {
1048                                 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
1049                                         if {[is_many_config $name]} {
1050                                                 lappend arr($name) $value
1051                                         } else {
1052                                                 set arr($name) $value
1053                                         }
1054                                 }
1055                         }
1056                         close $fd_rc
1057                 }
1058         }
1061 proc load_config {include_global} {
1062         global repo_config global_config system_config default_config
1064         if {$include_global} {
1065                 _parse_config system_config --system
1066                 _parse_config global_config --global
1067         }
1068         _parse_config repo_config
1070         foreach name [array names default_config] {
1071                 if {[catch {set v $system_config($name)}]} {
1072                         set system_config($name) $default_config($name)
1073                 }
1074         }
1075         foreach name [array names system_config] {
1076                 if {[catch {set v $global_config($name)}]} {
1077                         set global_config($name) $system_config($name)
1078                 }
1079                 if {[catch {set v $repo_config($name)}]} {
1080                         set repo_config($name) $system_config($name)
1081                 }
1082         }
1085 ######################################################################
1086 ##
1087 ## feature option selection
1089 if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} {
1090         unset _junk
1091 } else {
1092         set subcommand gui
1094 if {$subcommand eq {gui.sh}} {
1095         set subcommand gui
1097 if {$subcommand eq {gui} && [llength $argv] > 0} {
1098         set subcommand [lindex $argv 0]
1099         set argv [lrange $argv 1 end]
1102 enable_option multicommit
1103 enable_option branch
1104 enable_option transport
1105 disable_option bare
1107 switch -- $subcommand {
1108 browser -
1109 blame {
1110         enable_option bare
1112         disable_option multicommit
1113         disable_option branch
1114         disable_option transport
1116 citool {
1117         enable_option singlecommit
1118         enable_option retcode
1120         disable_option multicommit
1121         disable_option branch
1122         disable_option transport
1124         while {[llength $argv] > 0} {
1125                 set a [lindex $argv 0]
1126                 switch -- $a {
1127                 --amend {
1128                         enable_option initialamend
1129                 }
1130                 --nocommit {
1131                         enable_option nocommit
1132                         enable_option nocommitmsg
1133                 }
1134                 --commitmsg {
1135                         disable_option nocommitmsg
1136                 }
1137                 default {
1138                         break
1139                 }
1140                 }
1142                 set argv [lrange $argv 1 end]
1143         }
1147 ######################################################################
1148 ##
1149 ## execution environment
1151 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
1153 # Suggest our implementation of askpass, if none is set
1154 if {![info exists env(SSH_ASKPASS)]} {
1155         set env(SSH_ASKPASS) [gitexec git-gui--askpass]
1158 ######################################################################
1159 ##
1160 ## repository setup
1162 set picked 0
1163 if {[catch {
1164                 set _gitdir $env(GIT_DIR)
1165                 set _prefix {}
1166                 }]
1167         && [catch {
1168                 # beware that from the .git dir this sets _gitdir to .
1169                 # and _prefix to the empty string
1170                 set _gitdir [git rev-parse --git-dir]
1171                 set _prefix [git rev-parse --show-prefix]
1172         } err]} {
1173         load_config 1
1174         apply_config
1175         choose_repository::pick
1176         set picked 1
1179 # we expand the _gitdir when it's just a single dot (i.e. when we're being
1180 # run from the .git dir itself) lest the routines to find the worktree
1181 # get confused
1182 if {$_gitdir eq "."} {
1183         set _gitdir [pwd]
1186 if {![file isdirectory $_gitdir] && [is_Cygwin]} {
1187         catch {set _gitdir [exec cygpath --windows $_gitdir]}
1189 if {![file isdirectory $_gitdir]} {
1190         catch {wm withdraw .}
1191         error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"]
1192         exit 1
1194 # _gitdir exists, so try loading the config
1195 load_config 0
1196 apply_config
1197 # try to set work tree from environment, falling back to core.worktree
1198 if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} {
1199         set _gitworktree [get_config core.worktree]
1200         if {$_gitworktree eq ""} {
1201                 set _gitworktree [file dirname [file normalize $_gitdir]]
1202         }
1204 if {$_prefix ne {}} {
1205         if {$_gitworktree eq {}} {
1206                 regsub -all {[^/]+/} $_prefix ../ cdup
1207         } else {
1208                 set cdup $_gitworktree
1209         }
1210         if {[catch {cd $cdup} err]} {
1211                 catch {wm withdraw .}
1212                 error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"]
1213                 exit 1
1214         }
1215         set _gitworktree [pwd]
1216         unset cdup
1217 } elseif {![is_enabled bare]} {
1218         if {[is_bare]} {
1219                 catch {wm withdraw .}
1220                 error_popup [strcat [mc "Cannot use bare repository:"] "\n\n$_gitdir"]
1221                 exit 1
1222         }
1223         if {$_gitworktree eq {}} {
1224                 set _gitworktree [file dirname $_gitdir]
1225         }
1226         if {[catch {cd $_gitworktree} err]} {
1227                 catch {wm withdraw .}
1228                 error_popup [strcat [mc "No working directory"] " $_gitworktree:\n\n$err"]
1229                 exit 1
1230         }
1231         set _gitworktree [pwd]
1233 set _reponame [file split [file normalize $_gitdir]]
1234 if {[lindex $_reponame end] eq {.git}} {
1235         set _reponame [lindex $_reponame end-1]
1236 } else {
1237         set _reponame [lindex $_reponame end]
1240 set env(GIT_DIR) $_gitdir
1241 set env(GIT_WORK_TREE) $_gitworktree
1243 ######################################################################
1244 ##
1245 ## global init
1247 set current_diff_path {}
1248 set current_diff_side {}
1249 set diff_actions [list]
1251 set HEAD {}
1252 set PARENT {}
1253 set MERGE_HEAD [list]
1254 set commit_type {}
1255 set empty_tree {}
1256 set current_branch {}
1257 set is_detached 0
1258 set current_diff_path {}
1259 set is_3way_diff 0
1260 set is_submodule_diff 0
1261 set is_conflict_diff 0
1262 set selected_commit_type new
1263 set diff_empty_count 0
1265 set nullid "0000000000000000000000000000000000000000"
1266 set nullid2 "0000000000000000000000000000000000000001"
1268 ######################################################################
1269 ##
1270 ## task management
1272 set rescan_active 0
1273 set diff_active 0
1274 set last_clicked {}
1276 set disable_on_lock [list]
1277 set index_lock_type none
1279 proc lock_index {type} {
1280         global index_lock_type disable_on_lock
1282         if {$index_lock_type eq {none}} {
1283                 set index_lock_type $type
1284                 foreach w $disable_on_lock {
1285                         uplevel #0 $w disabled
1286                 }
1287                 return 1
1288         } elseif {$index_lock_type eq "begin-$type"} {
1289                 set index_lock_type $type
1290                 return 1
1291         }
1292         return 0
1295 proc unlock_index {} {
1296         global index_lock_type disable_on_lock
1298         set index_lock_type none
1299         foreach w $disable_on_lock {
1300                 uplevel #0 $w normal
1301         }
1304 ######################################################################
1305 ##
1306 ## status
1308 proc repository_state {ctvar hdvar mhvar} {
1309         global current_branch
1310         upvar $ctvar ct $hdvar hd $mhvar mh
1312         set mh [list]
1314         load_current_branch
1315         if {[catch {set hd [git rev-parse --verify HEAD]}]} {
1316                 set hd {}
1317                 set ct initial
1318                 return
1319         }
1321         set merge_head [gitdir MERGE_HEAD]
1322         if {[file exists $merge_head]} {
1323                 set ct merge
1324                 set fd_mh [open $merge_head r]
1325                 while {[gets $fd_mh line] >= 0} {
1326                         lappend mh $line
1327                 }
1328                 close $fd_mh
1329                 return
1330         }
1332         set ct normal
1335 proc PARENT {} {
1336         global PARENT empty_tree
1338         set p [lindex $PARENT 0]
1339         if {$p ne {}} {
1340                 return $p
1341         }
1342         if {$empty_tree eq {}} {
1343                 set empty_tree [git mktree << {}]
1344         }
1345         return $empty_tree
1348 proc force_amend {} {
1349         global selected_commit_type
1350         global HEAD PARENT MERGE_HEAD commit_type
1352         repository_state newType newHEAD newMERGE_HEAD
1353         set HEAD $newHEAD
1354         set PARENT $newHEAD
1355         set MERGE_HEAD $newMERGE_HEAD
1356         set commit_type $newType
1358         set selected_commit_type amend
1359         do_select_commit_type
1362 proc rescan {after {honor_trustmtime 1}} {
1363         global HEAD PARENT MERGE_HEAD commit_type
1364         global ui_index ui_workdir ui_comm
1365         global rescan_active file_states
1366         global repo_config
1368         if {$rescan_active > 0 || ![lock_index read]} return
1370         repository_state newType newHEAD newMERGE_HEAD
1371         if {[string match amend* $commit_type]
1372                 && $newType eq {normal}
1373                 && $newHEAD eq $HEAD} {
1374         } else {
1375                 set HEAD $newHEAD
1376                 set PARENT $newHEAD
1377                 set MERGE_HEAD $newMERGE_HEAD
1378                 set commit_type $newType
1379         }
1381         array unset file_states
1383         if {!$::GITGUI_BCK_exists &&
1384                 (![$ui_comm edit modified]
1385                 || [string trim [$ui_comm get 0.0 end]] eq {})} {
1386                 if {[string match amend* $commit_type]} {
1387                 } elseif {[load_message GITGUI_MSG]} {
1388                 } elseif {[run_prepare_commit_msg_hook]} {
1389                 } elseif {[load_message MERGE_MSG]} {
1390                 } elseif {[load_message SQUASH_MSG]} {
1391                 }
1392                 $ui_comm edit reset
1393                 $ui_comm edit modified false
1394         }
1396         if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
1397                 rescan_stage2 {} $after
1398         } else {
1399                 set rescan_active 1
1400                 ui_status [mc "Refreshing file status..."]
1401                 set fd_rf [git_read update-index \
1402                         -q \
1403                         --unmerged \
1404                         --ignore-missing \
1405                         --refresh \
1406                         ]
1407                 fconfigure $fd_rf -blocking 0 -translation binary
1408                 fileevent $fd_rf readable \
1409                         [list rescan_stage2 $fd_rf $after]
1410         }
1413 if {[is_Cygwin]} {
1414         set is_git_info_exclude {}
1415         proc have_info_exclude {} {
1416                 global is_git_info_exclude
1418                 if {$is_git_info_exclude eq {}} {
1419                         if {[catch {exec test -f [gitdir info exclude]}]} {
1420                                 set is_git_info_exclude 0
1421                         } else {
1422                                 set is_git_info_exclude 1
1423                         }
1424                 }
1425                 return $is_git_info_exclude
1426         }
1427 } else {
1428         proc have_info_exclude {} {
1429                 return [file readable [gitdir info exclude]]
1430         }
1433 proc rescan_stage2 {fd after} {
1434         global rescan_active buf_rdi buf_rdf buf_rlo
1436         if {$fd ne {}} {
1437                 read $fd
1438                 if {![eof $fd]} return
1439                 close $fd
1440         }
1442         set ls_others [list --exclude-per-directory=.gitignore]
1443         if {[have_info_exclude]} {
1444                 lappend ls_others "--exclude-from=[gitdir info exclude]"
1445         }
1446         set user_exclude [get_config core.excludesfile]
1447         if {$user_exclude ne {} && [file readable $user_exclude]} {
1448                 lappend ls_others "--exclude-from=$user_exclude"
1449         }
1451         set buf_rdi {}
1452         set buf_rdf {}
1453         set buf_rlo {}
1455         set rescan_active 3
1456         ui_status [mc "Scanning for modified files ..."]
1457         set fd_di [git_read diff-index --cached -z [PARENT]]
1458         set fd_df [git_read diff-files -z]
1459         set fd_lo [eval git_read ls-files --others -z $ls_others]
1461         fconfigure $fd_di -blocking 0 -translation binary -encoding binary
1462         fconfigure $fd_df -blocking 0 -translation binary -encoding binary
1463         fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
1464         fileevent $fd_di readable [list read_diff_index $fd_di $after]
1465         fileevent $fd_df readable [list read_diff_files $fd_df $after]
1466         fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
1469 proc load_message {file} {
1470         global ui_comm
1472         set f [gitdir $file]
1473         if {[file isfile $f]} {
1474                 if {[catch {set fd [open $f r]}]} {
1475                         return 0
1476                 }
1477                 fconfigure $fd -eofchar {}
1478                 set content [string trim [read $fd]]
1479                 close $fd
1480                 regsub -all -line {[ \r\t]+$} $content {} content
1481                 $ui_comm delete 0.0 end
1482                 $ui_comm insert end $content
1483                 return 1
1484         }
1485         return 0
1488 proc run_prepare_commit_msg_hook {} {
1489         global pch_error
1491         # prepare-commit-msg requires PREPARE_COMMIT_MSG exist.  From git-gui
1492         # it will be .git/MERGE_MSG (merge), .git/SQUASH_MSG (squash), or an
1493         # empty file but existant file.
1495         set fd_pcm [open [gitdir PREPARE_COMMIT_MSG] a]
1497         if {[file isfile [gitdir MERGE_MSG]]} {
1498                 set pcm_source "merge"
1499                 set fd_mm [open [gitdir MERGE_MSG] r]
1500                 puts -nonewline $fd_pcm [read $fd_mm]
1501                 close $fd_mm
1502         } elseif {[file isfile [gitdir SQUASH_MSG]]} {
1503                 set pcm_source "squash"
1504                 set fd_sm [open [gitdir SQUASH_MSG] r]
1505                 puts -nonewline $fd_pcm [read $fd_sm]
1506                 close $fd_sm
1507         } else {
1508                 set pcm_source ""
1509         }
1511         close $fd_pcm
1513         set fd_ph [githook_read prepare-commit-msg \
1514                         [gitdir PREPARE_COMMIT_MSG] $pcm_source]
1515         if {$fd_ph eq {}} {
1516                 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1517                 return 0;
1518         }
1520         ui_status [mc "Calling prepare-commit-msg hook..."]
1521         set pch_error {}
1523         fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
1524         fileevent $fd_ph readable \
1525                 [list prepare_commit_msg_hook_wait $fd_ph]
1527         return 1;
1530 proc prepare_commit_msg_hook_wait {fd_ph} {
1531         global pch_error
1533         append pch_error [read $fd_ph]
1534         fconfigure $fd_ph -blocking 1
1535         if {[eof $fd_ph]} {
1536                 if {[catch {close $fd_ph}]} {
1537                         ui_status [mc "Commit declined by prepare-commit-msg hook."]
1538                         hook_failed_popup prepare-commit-msg $pch_error
1539                         catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1540                         exit 1
1541                 } else {
1542                         load_message PREPARE_COMMIT_MSG
1543                 }
1544                 set pch_error {}
1545                 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1546                 return
1547         }
1548         fconfigure $fd_ph -blocking 0
1549         catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1552 proc read_diff_index {fd after} {
1553         global buf_rdi
1555         append buf_rdi [read $fd]
1556         set c 0
1557         set n [string length $buf_rdi]
1558         while {$c < $n} {
1559                 set z1 [string first "\0" $buf_rdi $c]
1560                 if {$z1 == -1} break
1561                 incr z1
1562                 set z2 [string first "\0" $buf_rdi $z1]
1563                 if {$z2 == -1} break
1565                 incr c
1566                 set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
1567                 set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
1568                 merge_state \
1569                         [encoding convertfrom $p] \
1570                         [lindex $i 4]? \
1571                         [list [lindex $i 0] [lindex $i 2]] \
1572                         [list]
1573                 set c $z2
1574                 incr c
1575         }
1576         if {$c < $n} {
1577                 set buf_rdi [string range $buf_rdi $c end]
1578         } else {
1579                 set buf_rdi {}
1580         }
1582         rescan_done $fd buf_rdi $after
1585 proc read_diff_files {fd after} {
1586         global buf_rdf
1588         append buf_rdf [read $fd]
1589         set c 0
1590         set n [string length $buf_rdf]
1591         while {$c < $n} {
1592                 set z1 [string first "\0" $buf_rdf $c]
1593                 if {$z1 == -1} break
1594                 incr z1
1595                 set z2 [string first "\0" $buf_rdf $z1]
1596                 if {$z2 == -1} break
1598                 incr c
1599                 set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
1600                 set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
1601                 merge_state \
1602                         [encoding convertfrom $p] \
1603                         ?[lindex $i 4] \
1604                         [list] \
1605                         [list [lindex $i 0] [lindex $i 2]]
1606                 set c $z2
1607                 incr c
1608         }
1609         if {$c < $n} {
1610                 set buf_rdf [string range $buf_rdf $c end]
1611         } else {
1612                 set buf_rdf {}
1613         }
1615         rescan_done $fd buf_rdf $after
1618 proc read_ls_others {fd after} {
1619         global buf_rlo
1621         append buf_rlo [read $fd]
1622         set pck [split $buf_rlo "\0"]
1623         set buf_rlo [lindex $pck end]
1624         foreach p [lrange $pck 0 end-1] {
1625                 set p [encoding convertfrom $p]
1626                 if {[string index $p end] eq {/}} {
1627                         set p [string range $p 0 end-1]
1628                 }
1629                 merge_state $p ?O
1630         }
1631         rescan_done $fd buf_rlo $after
1634 proc rescan_done {fd buf after} {
1635         global rescan_active current_diff_path
1636         global file_states repo_config
1637         upvar $buf to_clear
1639         if {![eof $fd]} return
1640         set to_clear {}
1641         close $fd
1642         if {[incr rescan_active -1] > 0} return
1644         prune_selection
1645         unlock_index
1646         display_all_files
1647         if {$current_diff_path ne {}} { reshow_diff $after }
1648         if {$current_diff_path eq {}} { select_first_diff $after }
1651 proc prune_selection {} {
1652         global file_states selected_paths
1654         foreach path [array names selected_paths] {
1655                 if {[catch {set still_here $file_states($path)}]} {
1656                         unset selected_paths($path)
1657                 }
1658         }
1661 ######################################################################
1662 ##
1663 ## ui helpers
1665 proc mapicon {w state path} {
1666         global all_icons
1668         if {[catch {set r $all_icons($state$w)}]} {
1669                 puts "error: no icon for $w state={$state} $path"
1670                 return file_plain
1671         }
1672         return $r
1675 proc mapdesc {state path} {
1676         global all_descs
1678         if {[catch {set r $all_descs($state)}]} {
1679                 puts "error: no desc for state={$state} $path"
1680                 return $state
1681         }
1682         return $r
1685 proc ui_status {msg} {
1686         global main_status
1687         if {[info exists main_status]} {
1688                 $main_status show $msg
1689         }
1692 proc ui_ready {{test {}}} {
1693         global main_status
1694         if {[info exists main_status]} {
1695                 $main_status show [mc "Ready."] $test
1696         }
1699 proc escape_path {path} {
1700         regsub -all {\\} $path "\\\\" path
1701         regsub -all "\n" $path "\\n" path
1702         return $path
1705 proc short_path {path} {
1706         return [escape_path [lindex [file split $path] end]]
1709 set next_icon_id 0
1710 set null_sha1 [string repeat 0 40]
1712 proc merge_state {path new_state {head_info {}} {index_info {}}} {
1713         global file_states next_icon_id null_sha1
1715         set s0 [string index $new_state 0]
1716         set s1 [string index $new_state 1]
1718         if {[catch {set info $file_states($path)}]} {
1719                 set state __
1720                 set icon n[incr next_icon_id]
1721         } else {
1722                 set state [lindex $info 0]
1723                 set icon [lindex $info 1]
1724                 if {$head_info eq {}}  {set head_info  [lindex $info 2]}
1725                 if {$index_info eq {}} {set index_info [lindex $info 3]}
1726         }
1728         if     {$s0 eq {?}} {set s0 [string index $state 0]} \
1729         elseif {$s0 eq {_}} {set s0 _}
1731         if     {$s1 eq {?}} {set s1 [string index $state 1]} \
1732         elseif {$s1 eq {_}} {set s1 _}
1734         if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
1735                 set head_info [list 0 $null_sha1]
1736         } elseif {$s0 ne {_} && [string index $state 0] eq {_}
1737                 && $head_info eq {}} {
1738                 set head_info $index_info
1739         } elseif {$s0 eq {_} && [string index $state 0] ne {_}} {
1740                 set index_info $head_info
1741                 set head_info {}
1742         }
1744         set file_states($path) [list $s0$s1 $icon \
1745                 $head_info $index_info \
1746                 ]
1747         return $state
1750 proc display_file_helper {w path icon_name old_m new_m} {
1751         global file_lists
1753         if {$new_m eq {_}} {
1754                 set lno [lsearch -sorted -exact $file_lists($w) $path]
1755                 if {$lno >= 0} {
1756                         set file_lists($w) [lreplace $file_lists($w) $lno $lno]
1757                         incr lno
1758                         $w conf -state normal
1759                         $w delete $lno.0 [expr {$lno + 1}].0
1760                         $w conf -state disabled
1761                 }
1762         } elseif {$old_m eq {_} && $new_m ne {_}} {
1763                 lappend file_lists($w) $path
1764                 set file_lists($w) [lsort -unique $file_lists($w)]
1765                 set lno [lsearch -sorted -exact $file_lists($w) $path]
1766                 incr lno
1767                 $w conf -state normal
1768                 $w image create $lno.0 \
1769                         -align center -padx 5 -pady 1 \
1770                         -name $icon_name \
1771                         -image [mapicon $w $new_m $path]
1772                 $w insert $lno.1 "[escape_path $path]\n"
1773                 $w conf -state disabled
1774         } elseif {$old_m ne $new_m} {
1775                 $w conf -state normal
1776                 $w image conf $icon_name -image [mapicon $w $new_m $path]
1777                 $w conf -state disabled
1778         }
1781 proc display_file {path state} {
1782         global file_states selected_paths
1783         global ui_index ui_workdir
1785         set old_m [merge_state $path $state]
1786         set s $file_states($path)
1787         set new_m [lindex $s 0]
1788         set icon_name [lindex $s 1]
1790         set o [string index $old_m 0]
1791         set n [string index $new_m 0]
1792         if {$o eq {U}} {
1793                 set o _
1794         }
1795         if {$n eq {U}} {
1796                 set n _
1797         }
1798         display_file_helper     $ui_index $path $icon_name $o $n
1800         if {[string index $old_m 0] eq {U}} {
1801                 set o U
1802         } else {
1803                 set o [string index $old_m 1]
1804         }
1805         if {[string index $new_m 0] eq {U}} {
1806                 set n U
1807         } else {
1808                 set n [string index $new_m 1]
1809         }
1810         display_file_helper     $ui_workdir $path $icon_name $o $n
1812         if {$new_m eq {__}} {
1813                 unset file_states($path)
1814                 catch {unset selected_paths($path)}
1815         }
1818 proc display_all_files_helper {w path icon_name m} {
1819         global file_lists
1821         lappend file_lists($w) $path
1822         set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
1823         $w image create end \
1824                 -align center -padx 5 -pady 1 \
1825                 -name $icon_name \
1826                 -image [mapicon $w $m $path]
1827         $w insert end "[escape_path $path]\n"
1830 set files_warning 0
1831 proc display_all_files {} {
1832         global ui_index ui_workdir
1833         global file_states file_lists
1834         global last_clicked
1835         global files_warning
1837         $ui_index conf -state normal
1838         $ui_workdir conf -state normal
1840         $ui_index delete 0.0 end
1841         $ui_workdir delete 0.0 end
1842         set last_clicked {}
1844         set file_lists($ui_index) [list]
1845         set file_lists($ui_workdir) [list]
1847         set to_display [lsort [array names file_states]]
1848         set display_limit [get_config gui.maxfilesdisplayed]
1849         if {[llength $to_display] > $display_limit} {
1850                 if {!$files_warning} {
1851                         # do not repeatedly warn:
1852                         set files_warning 1
1853                         info_popup [mc "Displaying only %s of %s files." \
1854                                 $display_limit [llength $to_display]]
1855                 }
1856                 set to_display [lrange $to_display 0 [expr {$display_limit-1}]]
1857         }
1858         foreach path $to_display {
1859                 set s $file_states($path)
1860                 set m [lindex $s 0]
1861                 set icon_name [lindex $s 1]
1863                 set s [string index $m 0]
1864                 if {$s ne {U} && $s ne {_}} {
1865                         display_all_files_helper $ui_index $path \
1866                                 $icon_name $s
1867                 }
1869                 if {[string index $m 0] eq {U}} {
1870                         set s U
1871                 } else {
1872                         set s [string index $m 1]
1873                 }
1874                 if {$s ne {_}} {
1875                         display_all_files_helper $ui_workdir $path \
1876                                 $icon_name $s
1877                 }
1878         }
1880         $ui_index conf -state disabled
1881         $ui_workdir conf -state disabled
1884 ######################################################################
1885 ##
1886 ## icons
1888 set filemask {
1889 #define mask_width 14
1890 #define mask_height 15
1891 static unsigned char mask_bits[] = {
1892    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1893    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1894    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1897 image create bitmap file_plain -background white -foreground black -data {
1898 #define plain_width 14
1899 #define plain_height 15
1900 static unsigned char plain_bits[] = {
1901    0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1902    0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1903    0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1904 } -maskdata $filemask
1906 image create bitmap file_mod -background white -foreground blue -data {
1907 #define mod_width 14
1908 #define mod_height 15
1909 static unsigned char mod_bits[] = {
1910    0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1911    0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1912    0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1913 } -maskdata $filemask
1915 image create bitmap file_fulltick -background white -foreground "#007000" -data {
1916 #define file_fulltick_width 14
1917 #define file_fulltick_height 15
1918 static unsigned char file_fulltick_bits[] = {
1919    0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1920    0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1921    0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1922 } -maskdata $filemask
1924 image create bitmap file_question -background white -foreground black -data {
1925 #define file_question_width 14
1926 #define file_question_height 15
1927 static unsigned char file_question_bits[] = {
1928    0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1929    0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1930    0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1931 } -maskdata $filemask
1933 image create bitmap file_removed -background white -foreground red -data {
1934 #define file_removed_width 14
1935 #define file_removed_height 15
1936 static unsigned char file_removed_bits[] = {
1937    0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1938    0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1939    0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1940 } -maskdata $filemask
1942 image create bitmap file_merge -background white -foreground blue -data {
1943 #define file_merge_width 14
1944 #define file_merge_height 15
1945 static unsigned char file_merge_bits[] = {
1946    0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1947    0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1948    0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1949 } -maskdata $filemask
1951 image create bitmap file_statechange -background white -foreground green -data {
1952 #define file_merge_width 14
1953 #define file_merge_height 15
1954 static unsigned char file_statechange_bits[] = {
1955    0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x62, 0x10,
1956    0x62, 0x10, 0xba, 0x11, 0xba, 0x11, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10,
1957    0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1958 } -maskdata $filemask
1960 set ui_index .vpane.files.index.list
1961 set ui_workdir .vpane.files.workdir.list
1963 set all_icons(_$ui_index)   file_plain
1964 set all_icons(A$ui_index)   file_plain
1965 set all_icons(M$ui_index)   file_fulltick
1966 set all_icons(D$ui_index)   file_removed
1967 set all_icons(U$ui_index)   file_merge
1968 set all_icons(T$ui_index)   file_statechange
1970 set all_icons(_$ui_workdir) file_plain
1971 set all_icons(M$ui_workdir) file_mod
1972 set all_icons(D$ui_workdir) file_question
1973 set all_icons(U$ui_workdir) file_merge
1974 set all_icons(O$ui_workdir) file_plain
1975 set all_icons(T$ui_workdir) file_statechange
1977 set max_status_desc 0
1978 foreach i {
1979                 {__ {mc "Unmodified"}}
1981                 {_M {mc "Modified, not staged"}}
1982                 {M_ {mc "Staged for commit"}}
1983                 {MM {mc "Portions staged for commit"}}
1984                 {MD {mc "Staged for commit, missing"}}
1986                 {_T {mc "File type changed, not staged"}}
1987                 {T_ {mc "File type changed, staged"}}
1989                 {_O {mc "Untracked, not staged"}}
1990                 {A_ {mc "Staged for commit"}}
1991                 {AM {mc "Portions staged for commit"}}
1992                 {AD {mc "Staged for commit, missing"}}
1994                 {_D {mc "Missing"}}
1995                 {D_ {mc "Staged for removal"}}
1996                 {DO {mc "Staged for removal, still present"}}
1998                 {_U {mc "Requires merge resolution"}}
1999                 {U_ {mc "Requires merge resolution"}}
2000                 {UU {mc "Requires merge resolution"}}
2001                 {UM {mc "Requires merge resolution"}}
2002                 {UD {mc "Requires merge resolution"}}
2003                 {UT {mc "Requires merge resolution"}}
2004         } {
2005         set text [eval [lindex $i 1]]
2006         if {$max_status_desc < [string length $text]} {
2007                 set max_status_desc [string length $text]
2008         }
2009         set all_descs([lindex $i 0]) $text
2011 unset i
2013 ######################################################################
2014 ##
2015 ## util
2017 proc scrollbar2many {list mode args} {
2018         foreach w $list {eval $w $mode $args}
2021 proc many2scrollbar {list mode sb top bottom} {
2022         $sb set $top $bottom
2023         foreach w $list {$w $mode moveto $top}
2026 proc incr_font_size {font {amt 1}} {
2027         set sz [font configure $font -size]
2028         incr sz $amt
2029         font configure $font -size $sz
2030         font configure ${font}bold -size $sz
2031         font configure ${font}italic -size $sz
2034 ######################################################################
2035 ##
2036 ## ui commands
2038 set starting_gitk_msg [mc "Starting gitk... please wait..."]
2040 proc do_gitk {revs {is_submodule false}} {
2041         global current_diff_path file_states current_diff_side ui_index
2042         global _gitdir _gitworktree
2044         # -- Always start gitk through whatever we were loaded with.  This
2045         #    lets us bypass using shell process on Windows systems.
2046         #
2047         set exe [_which gitk -script]
2048         set cmd [list [info nameofexecutable] $exe]
2049         if {$exe eq {}} {
2050                 error_popup [mc "Couldn't find gitk in PATH"]
2051         } else {
2052                 global env
2054                 set pwd [pwd]
2056                 if {!$is_submodule} {
2057                         if {![is_bare]} {
2058                                 cd $_gitworktree
2059                         }
2060                 } else {
2061                         cd $current_diff_path
2062                         if {$revs eq {--}} {
2063                                 set s $file_states($current_diff_path)
2064                                 set old_sha1 {}
2065                                 set new_sha1 {}
2066                                 switch -glob -- [lindex $s 0] {
2067                                 M_ { set old_sha1 [lindex [lindex $s 2] 1] }
2068                                 _M { set old_sha1 [lindex [lindex $s 3] 1] }
2069                                 MM {
2070                                         if {$current_diff_side eq $ui_index} {
2071                                                 set old_sha1 [lindex [lindex $s 2] 1]
2072                                                 set new_sha1 [lindex [lindex $s 3] 1]
2073                                         } else {
2074                                                 set old_sha1 [lindex [lindex $s 3] 1]
2075                                         }
2076                                 }
2077                                 }
2078                                 set revs $old_sha1...$new_sha1
2079                         }
2080                         # GIT_DIR and GIT_WORK_TREE for the submodule are not the ones
2081                         # we've been using for the main repository, so unset them.
2082                         # TODO we could make life easier (start up faster?) for gitk
2083                         # by setting these to the appropriate values to allow gitk
2084                         # to skip the heuristics to find their proper value
2085                         unset env(GIT_DIR)
2086                         unset env(GIT_WORK_TREE)
2087                 }
2088                 eval exec $cmd $revs "--" "--" &
2090                 set env(GIT_DIR) $_gitdir
2091                 set env(GIT_WORK_TREE) $_gitworktree
2092                 cd $pwd
2094                 ui_status $::starting_gitk_msg
2095                 after 10000 {
2096                         ui_ready $starting_gitk_msg
2097                 }
2098         }
2101 proc do_git_gui {} {
2102         global current_diff_path
2104         # -- Always start git gui through whatever we were loaded with.  This
2105         #    lets us bypass using shell process on Windows systems.
2106         #
2107         set exe [list [_which git]]
2108         if {$exe eq {}} {
2109                 error_popup [mc "Couldn't find git gui in PATH"]
2110         } else {
2111                 global env
2112                 global _gitdir _gitworktree
2114                 # see note in do_gitk about unsetting these vars when
2115                 # running tools in a submodule
2116                 unset env(GIT_DIR)
2117                 unset env(GIT_WORK_TREE)
2119                 set pwd [pwd]
2120                 cd $current_diff_path
2122                 eval exec $exe gui &
2124                 set env(GIT_DIR) $_gitdir
2125                 set env(GIT_WORK_TREE) $_gitworktree
2126                 cd $pwd
2128                 ui_status $::starting_gitk_msg
2129                 after 10000 {
2130                         ui_ready $starting_gitk_msg
2131                 }
2132         }
2135 proc do_explore {} {
2136         global _gitworktree
2137         set explorer {}
2138         if {[is_Cygwin] || [is_Windows]} {
2139                 set explorer "explorer.exe"
2140         } elseif {[is_MacOSX]} {
2141                 set explorer "open"
2142         } else {
2143                 # freedesktop.org-conforming system is our best shot
2144                 set explorer "xdg-open"
2145         }
2146         eval exec $explorer [list [file nativename $_gitworktree]] &
2149 set is_quitting 0
2150 set ret_code    1
2152 proc terminate_me {win} {
2153         global ret_code
2154         if {$win ne {.}} return
2155         exit $ret_code
2158 proc do_quit {{rc {1}}} {
2159         global ui_comm is_quitting repo_config commit_type
2160         global GITGUI_BCK_exists GITGUI_BCK_i
2161         global ui_comm_spell
2162         global ret_code use_ttk
2164         if {$is_quitting} return
2165         set is_quitting 1
2167         if {[winfo exists $ui_comm]} {
2168                 # -- Stash our current commit buffer.
2169                 #
2170                 set save [gitdir GITGUI_MSG]
2171                 if {$GITGUI_BCK_exists && ![$ui_comm edit modified]} {
2172                         file rename -force [gitdir GITGUI_BCK] $save
2173                         set GITGUI_BCK_exists 0
2174                 } else {
2175                         set msg [string trim [$ui_comm get 0.0 end]]
2176                         regsub -all -line {[ \r\t]+$} $msg {} msg
2177                         if {(![string match amend* $commit_type]
2178                                 || [$ui_comm edit modified])
2179                                 && $msg ne {}} {
2180                                 catch {
2181                                         set fd [open $save w]
2182                                         puts -nonewline $fd $msg
2183                                         close $fd
2184                                 }
2185                         } else {
2186                                 catch {file delete $save}
2187                         }
2188                 }
2190                 # -- Cancel our spellchecker if its running.
2191                 #
2192                 if {[info exists ui_comm_spell]} {
2193                         $ui_comm_spell stop
2194                 }
2196                 # -- Remove our editor backup, its not needed.
2197                 #
2198                 after cancel $GITGUI_BCK_i
2199                 if {$GITGUI_BCK_exists} {
2200                         catch {file delete [gitdir GITGUI_BCK]}
2201                 }
2203                 # -- Stash our current window geometry into this repository.
2204                 #
2205                 set cfg_wmstate [wm state .]
2206                 if {[catch {set rc_wmstate $repo_config(gui.wmstate)}]} {
2207                         set rc_wmstate {}
2208                 }
2209                 if {$cfg_wmstate ne $rc_wmstate} {
2210                         catch {git config gui.wmstate $cfg_wmstate}
2211                 }
2212                 if {$cfg_wmstate eq {zoomed}} {
2213                         # on Windows wm geometry will lie about window
2214                         # position (but not size) when window is zoomed
2215                         # restore the window before querying wm geometry
2216                         wm state . normal
2217                 }
2218                 set cfg_geometry [list]
2219                 lappend cfg_geometry [wm geometry .]
2220                 if {$use_ttk} {
2221                         lappend cfg_geometry [.vpane sashpos 0]
2222                         lappend cfg_geometry [.vpane.files sashpos 0]
2223                 } else {
2224                         lappend cfg_geometry [lindex [.vpane sash coord 0] 0]
2225                         lappend cfg_geometry [lindex [.vpane.files sash coord 0] 1]
2226                 }
2227                 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
2228                         set rc_geometry {}
2229                 }
2230                 if {$cfg_geometry ne $rc_geometry} {
2231                         catch {git config gui.geometry $cfg_geometry}
2232                 }
2233         }
2235         set ret_code $rc
2237         # Briefly enable send again, working around Tk bug
2238         # http://sourceforge.net/tracker/?func=detail&atid=112997&aid=1821174&group_id=12997
2239         tk appname [appname]
2241         destroy .
2244 proc do_rescan {} {
2245         rescan ui_ready
2248 proc ui_do_rescan {} {
2249         rescan {force_first_diff ui_ready}
2252 proc do_commit {} {
2253         commit_tree
2256 proc next_diff {{after {}}} {
2257         global next_diff_p next_diff_w next_diff_i
2258         show_diff $next_diff_p $next_diff_w {} {} $after
2261 proc find_anchor_pos {lst name} {
2262         set lid [lsearch -sorted -exact $lst $name]
2264         if {$lid == -1} {
2265                 set lid 0
2266                 foreach lname $lst {
2267                         if {$lname >= $name} break
2268                         incr lid
2269                 }
2270         }
2272         return $lid
2275 proc find_file_from {flist idx delta path mmask} {
2276         global file_states
2278         set len [llength $flist]
2279         while {$idx >= 0 && $idx < $len} {
2280                 set name [lindex $flist $idx]
2282                 if {$name ne $path && [info exists file_states($name)]} {
2283                         set state [lindex $file_states($name) 0]
2285                         if {$mmask eq {} || [regexp $mmask $state]} {
2286                                 return $idx
2287                         }
2288                 }
2290                 incr idx $delta
2291         }
2293         return {}
2296 proc find_next_diff {w path {lno {}} {mmask {}}} {
2297         global next_diff_p next_diff_w next_diff_i
2298         global file_lists ui_index ui_workdir
2300         set flist $file_lists($w)
2301         if {$lno eq {}} {
2302                 set lno [find_anchor_pos $flist $path]
2303         } else {
2304                 incr lno -1
2305         }
2307         if {$mmask ne {} && ![regexp {(^\^)|(\$$)} $mmask]} {
2308                 if {$w eq $ui_index} {
2309                         set mmask "^$mmask"
2310                 } else {
2311                         set mmask "$mmask\$"
2312                 }
2313         }
2315         set idx [find_file_from $flist $lno 1 $path $mmask]
2316         if {$idx eq {}} {
2317                 incr lno -1
2318                 set idx [find_file_from $flist $lno -1 $path $mmask]
2319         }
2321         if {$idx ne {}} {
2322                 set next_diff_w $w
2323                 set next_diff_p [lindex $flist $idx]
2324                 set next_diff_i [expr {$idx+1}]
2325                 return 1
2326         } else {
2327                 return 0
2328         }
2331 proc next_diff_after_action {w path {lno {}} {mmask {}}} {
2332         global current_diff_path
2334         if {$path ne $current_diff_path} {
2335                 return {}
2336         } elseif {[find_next_diff $w $path $lno $mmask]} {
2337                 return {next_diff;}
2338         } else {
2339                 return {reshow_diff;}
2340         }
2343 proc select_first_diff {after} {
2344         global ui_workdir
2346         if {[find_next_diff $ui_workdir {} 1 {^_?U}] ||
2347             [find_next_diff $ui_workdir {} 1 {[^O]$}]} {
2348                 next_diff $after
2349         } else {
2350                 uplevel #0 $after
2351         }
2354 proc force_first_diff {after} {
2355         global ui_workdir current_diff_path file_states
2357         if {[info exists file_states($current_diff_path)]} {
2358                 set state [lindex $file_states($current_diff_path) 0]
2359         } else {
2360                 set state {OO}
2361         }
2363         set reselect 0
2364         if {[string first {U} $state] >= 0} {
2365                 # Already a conflict, do nothing
2366         } elseif {[find_next_diff $ui_workdir $current_diff_path {} {^_?U}]} {
2367                 set reselect 1
2368         } elseif {[string index $state 1] ne {O}} {
2369                 # Already a diff & no conflicts, do nothing
2370         } elseif {[find_next_diff $ui_workdir $current_diff_path {} {[^O]$}]} {
2371                 set reselect 1
2372         }
2374         if {$reselect} {
2375                 next_diff $after
2376         } else {
2377                 uplevel #0 $after
2378         }
2381 proc toggle_or_diff {w x y} {
2382         global file_states file_lists current_diff_path ui_index ui_workdir
2383         global last_clicked selected_paths
2385         set pos [split [$w index @$x,$y] .]
2386         set lno [lindex $pos 0]
2387         set col [lindex $pos 1]
2388         set path [lindex $file_lists($w) [expr {$lno - 1}]]
2389         if {$path eq {}} {
2390                 set last_clicked {}
2391                 return
2392         }
2394         set last_clicked [list $w $lno]
2395         array unset selected_paths
2396         $ui_index tag remove in_sel 0.0 end
2397         $ui_workdir tag remove in_sel 0.0 end
2399         # Determine the state of the file
2400         if {[info exists file_states($path)]} {
2401                 set state [lindex $file_states($path) 0]
2402         } else {
2403                 set state {__}
2404         }
2406         # Restage the file, or simply show the diff
2407         if {$col == 0 && $y > 1} {
2408                 # Conflicts need special handling
2409                 if {[string first {U} $state] >= 0} {
2410                         # $w must always be $ui_workdir, but...
2411                         if {$w ne $ui_workdir} { set lno {} }
2412                         merge_stage_workdir $path $lno
2413                         return
2414                 }
2416                 if {[string index $state 1] eq {O}} {
2417                         set mmask {}
2418                 } else {
2419                         set mmask {[^O]}
2420                 }
2422                 set after [next_diff_after_action $w $path $lno $mmask]
2424                 if {$w eq $ui_index} {
2425                         update_indexinfo \
2426                                 "Unstaging [short_path $path] from commit" \
2427                                 [list $path] \
2428                                 [concat $after [list ui_ready]]
2429                 } elseif {$w eq $ui_workdir} {
2430                         update_index \
2431                                 "Adding [short_path $path]" \
2432                                 [list $path] \
2433                                 [concat $after [list ui_ready]]
2434                 }
2435         } else {
2436                 show_diff $path $w $lno
2437         }
2440 proc add_one_to_selection {w x y} {
2441         global file_lists last_clicked selected_paths
2443         set lno [lindex [split [$w index @$x,$y] .] 0]
2444         set path [lindex $file_lists($w) [expr {$lno - 1}]]
2445         if {$path eq {}} {
2446                 set last_clicked {}
2447                 return
2448         }
2450         if {$last_clicked ne {}
2451                 && [lindex $last_clicked 0] ne $w} {
2452                 array unset selected_paths
2453                 [lindex $last_clicked 0] tag remove in_sel 0.0 end
2454         }
2456         set last_clicked [list $w $lno]
2457         if {[catch {set in_sel $selected_paths($path)}]} {
2458                 set in_sel 0
2459         }
2460         if {$in_sel} {
2461                 unset selected_paths($path)
2462                 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
2463         } else {
2464                 set selected_paths($path) 1
2465                 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
2466         }
2469 proc add_range_to_selection {w x y} {
2470         global file_lists last_clicked selected_paths
2472         if {[lindex $last_clicked 0] ne $w} {
2473                 toggle_or_diff $w $x $y
2474                 return
2475         }
2477         set lno [lindex [split [$w index @$x,$y] .] 0]
2478         set lc [lindex $last_clicked 1]
2479         if {$lc < $lno} {
2480                 set begin $lc
2481                 set end $lno
2482         } else {
2483                 set begin $lno
2484                 set end $lc
2485         }
2487         foreach path [lrange $file_lists($w) \
2488                 [expr {$begin - 1}] \
2489                 [expr {$end - 1}]] {
2490                 set selected_paths($path) 1
2491         }
2492         $w tag add in_sel $begin.0 [expr {$end + 1}].0
2495 proc show_more_context {} {
2496         global repo_config
2497         if {$repo_config(gui.diffcontext) < 99} {
2498                 incr repo_config(gui.diffcontext)
2499                 reshow_diff
2500         }
2503 proc show_less_context {} {
2504         global repo_config
2505         if {$repo_config(gui.diffcontext) > 1} {
2506                 incr repo_config(gui.diffcontext) -1
2507                 reshow_diff
2508         }
2511 ######################################################################
2512 ##
2513 ## ui construction
2515 set ui_comm {}
2517 # -- Menu Bar
2519 menu .mbar -tearoff 0
2520 if {[is_MacOSX]} {
2521         # -- Apple Menu (Mac OS X only)
2522         #
2523         .mbar add cascade -label Apple -menu .mbar.apple
2524         menu .mbar.apple
2526 .mbar add cascade -label [mc Repository] -menu .mbar.repository
2527 .mbar add cascade -label [mc Edit] -menu .mbar.edit
2528 if {[is_enabled branch]} {
2529         .mbar add cascade -label [mc Branch] -menu .mbar.branch
2531 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2532         .mbar add cascade -label [mc Commit@@noun] -menu .mbar.commit
2534 if {[is_enabled transport]} {
2535         .mbar add cascade -label [mc Merge] -menu .mbar.merge
2536         .mbar add cascade -label [mc Remote] -menu .mbar.remote
2538 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2539         .mbar add cascade -label [mc Tools] -menu .mbar.tools
2542 # -- Repository Menu
2544 menu .mbar.repository
2546 if {![is_bare]} {
2547         .mbar.repository add command \
2548                 -label [mc "Explore Working Copy"] \
2549                 -command {do_explore}
2550         .mbar.repository add separator
2553 .mbar.repository add command \
2554         -label [mc "Browse Current Branch's Files"] \
2555         -command {browser::new $current_branch}
2556 set ui_browse_current [.mbar.repository index last]
2557 .mbar.repository add command \
2558         -label [mc "Browse Branch Files..."] \
2559         -command browser_open::dialog
2560 .mbar.repository add separator
2562 .mbar.repository add command \
2563         -label [mc "Visualize Current Branch's History"] \
2564         -command {do_gitk $current_branch}
2565 set ui_visualize_current [.mbar.repository index last]
2566 .mbar.repository add command \
2567         -label [mc "Visualize All Branch History"] \
2568         -command {do_gitk --all}
2569 .mbar.repository add separator
2571 proc current_branch_write {args} {
2572         global current_branch
2573         .mbar.repository entryconf $::ui_browse_current \
2574                 -label [mc "Browse %s's Files" $current_branch]
2575         .mbar.repository entryconf $::ui_visualize_current \
2576                 -label [mc "Visualize %s's History" $current_branch]
2578 trace add variable current_branch write current_branch_write
2580 if {[is_enabled multicommit]} {
2581         .mbar.repository add command -label [mc "Database Statistics"] \
2582                 -command do_stats
2584         .mbar.repository add command -label [mc "Compress Database"] \
2585                 -command do_gc
2587         .mbar.repository add command -label [mc "Verify Database"] \
2588                 -command do_fsck_objects
2590         .mbar.repository add separator
2592         if {[is_Cygwin]} {
2593                 .mbar.repository add command \
2594                         -label [mc "Create Desktop Icon"] \
2595                         -command do_cygwin_shortcut
2596         } elseif {[is_Windows]} {
2597                 .mbar.repository add command \
2598                         -label [mc "Create Desktop Icon"] \
2599                         -command do_windows_shortcut
2600         } elseif {[is_MacOSX]} {
2601                 .mbar.repository add command \
2602                         -label [mc "Create Desktop Icon"] \
2603                         -command do_macosx_app
2604         }
2607 if {[is_MacOSX]} {
2608         proc ::tk::mac::Quit {args} { do_quit }
2609 } else {
2610         .mbar.repository add command -label [mc Quit] \
2611                 -command do_quit \
2612                 -accelerator $M1T-Q
2615 # -- Edit Menu
2617 menu .mbar.edit
2618 .mbar.edit add command -label [mc Undo] \
2619         -command {catch {[focus] edit undo}} \
2620         -accelerator $M1T-Z
2621 .mbar.edit add command -label [mc Redo] \
2622         -command {catch {[focus] edit redo}} \
2623         -accelerator $M1T-Y
2624 .mbar.edit add separator
2625 .mbar.edit add command -label [mc Cut] \
2626         -command {catch {tk_textCut [focus]}} \
2627         -accelerator $M1T-X
2628 .mbar.edit add command -label [mc Copy] \
2629         -command {catch {tk_textCopy [focus]}} \
2630         -accelerator $M1T-C
2631 .mbar.edit add command -label [mc Paste] \
2632         -command {catch {tk_textPaste [focus]; [focus] see insert}} \
2633         -accelerator $M1T-V
2634 .mbar.edit add command -label [mc Delete] \
2635         -command {catch {[focus] delete sel.first sel.last}} \
2636         -accelerator Del
2637 .mbar.edit add separator
2638 .mbar.edit add command -label [mc "Select All"] \
2639         -command {catch {[focus] tag add sel 0.0 end}} \
2640         -accelerator $M1T-A
2642 # -- Branch Menu
2644 if {[is_enabled branch]} {
2645         menu .mbar.branch
2647         .mbar.branch add command -label [mc "Create..."] \
2648                 -command branch_create::dialog \
2649                 -accelerator $M1T-N
2650         lappend disable_on_lock [list .mbar.branch entryconf \
2651                 [.mbar.branch index last] -state]
2653         .mbar.branch add command -label [mc "Checkout..."] \
2654                 -command branch_checkout::dialog \
2655                 -accelerator $M1T-O
2656         lappend disable_on_lock [list .mbar.branch entryconf \
2657                 [.mbar.branch index last] -state]
2659         .mbar.branch add command -label [mc "Rename..."] \
2660                 -command branch_rename::dialog
2661         lappend disable_on_lock [list .mbar.branch entryconf \
2662                 [.mbar.branch index last] -state]
2664         .mbar.branch add command -label [mc "Delete..."] \
2665                 -command branch_delete::dialog
2666         lappend disable_on_lock [list .mbar.branch entryconf \
2667                 [.mbar.branch index last] -state]
2669         .mbar.branch add command -label [mc "Reset..."] \
2670                 -command merge::reset_hard
2671         lappend disable_on_lock [list .mbar.branch entryconf \
2672                 [.mbar.branch index last] -state]
2675 # -- Commit Menu
2677 proc commit_btn_caption {} {
2678         if {[is_enabled nocommit]} {
2679                 return [mc "Done"]
2680         } else {
2681                 return [mc Commit@@verb]
2682         }
2685 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2686         menu .mbar.commit
2688         if {![is_enabled nocommit]} {
2689                 .mbar.commit add radiobutton \
2690                         -label [mc "New Commit"] \
2691                         -command do_select_commit_type \
2692                         -variable selected_commit_type \
2693                         -value new
2694                 lappend disable_on_lock \
2695                         [list .mbar.commit entryconf [.mbar.commit index last] -state]
2697                 .mbar.commit add radiobutton \
2698                         -label [mc "Amend Last Commit"] \
2699                         -command do_select_commit_type \
2700                         -variable selected_commit_type \
2701                         -value amend
2702                 lappend disable_on_lock \
2703                         [list .mbar.commit entryconf [.mbar.commit index last] -state]
2705                 .mbar.commit add separator
2706         }
2708         .mbar.commit add command -label [mc Rescan] \
2709                 -command ui_do_rescan \
2710                 -accelerator F5
2711         lappend disable_on_lock \
2712                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2714         .mbar.commit add command -label [mc "Stage To Commit"] \
2715                 -command do_add_selection \
2716                 -accelerator $M1T-T
2717         lappend disable_on_lock \
2718                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2720         .mbar.commit add command -label [mc "Stage Changed Files To Commit"] \
2721                 -command do_add_all \
2722                 -accelerator $M1T-I
2723         lappend disable_on_lock \
2724                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2726         .mbar.commit add command -label [mc "Unstage From Commit"] \
2727                 -command do_unstage_selection \
2728                 -accelerator $M1T-U
2729         lappend disable_on_lock \
2730                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2732         .mbar.commit add command -label [mc "Revert Changes"] \
2733                 -command do_revert_selection \
2734                 -accelerator $M1T-J
2735         lappend disable_on_lock \
2736                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2738         .mbar.commit add separator
2740         .mbar.commit add command -label [mc "Show Less Context"] \
2741                 -command show_less_context \
2742                 -accelerator $M1T-\-
2744         .mbar.commit add command -label [mc "Show More Context"] \
2745                 -command show_more_context \
2746                 -accelerator $M1T-=
2748         .mbar.commit add separator
2750         if {![is_enabled nocommitmsg]} {
2751                 .mbar.commit add command -label [mc "Sign Off"] \
2752                         -command do_signoff \
2753                         -accelerator $M1T-S
2754         }
2756         .mbar.commit add command -label [commit_btn_caption] \
2757                 -command do_commit \
2758                 -accelerator $M1T-Return
2759         lappend disable_on_lock \
2760                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2763 # -- Merge Menu
2765 if {[is_enabled branch]} {
2766         menu .mbar.merge
2767         .mbar.merge add command -label [mc "Local Merge..."] \
2768                 -command merge::dialog \
2769                 -accelerator $M1T-M
2770         lappend disable_on_lock \
2771                 [list .mbar.merge entryconf [.mbar.merge index last] -state]
2772         .mbar.merge add command -label [mc "Abort Merge..."] \
2773                 -command merge::reset_hard
2774         lappend disable_on_lock \
2775                 [list .mbar.merge entryconf [.mbar.merge index last] -state]
2778 # -- Transport Menu
2780 if {[is_enabled transport]} {
2781         menu .mbar.remote
2783         .mbar.remote add command \
2784                 -label [mc "Add..."] \
2785                 -command remote_add::dialog \
2786                 -accelerator $M1T-A
2787         .mbar.remote add command \
2788                 -label [mc "Push..."] \
2789                 -command do_push_anywhere \
2790                 -accelerator $M1T-P
2791         .mbar.remote add command \
2792                 -label [mc "Delete Branch..."] \
2793                 -command remote_branch_delete::dialog
2796 if {[is_MacOSX]} {
2797         proc ::tk::mac::ShowPreferences {} {do_options}
2798 } else {
2799         # -- Edit Menu
2800         #
2801         .mbar.edit add separator
2802         .mbar.edit add command -label [mc "Options..."] \
2803                 -command do_options
2806 # -- Tools Menu
2808 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2809         set tools_menubar .mbar.tools
2810         menu $tools_menubar
2811         $tools_menubar add separator
2812         $tools_menubar add command -label [mc "Add..."] -command tools_add::dialog
2813         $tools_menubar add command -label [mc "Remove..."] -command tools_remove::dialog
2814         set tools_tailcnt 3
2815         if {[array names repo_config guitool.*.cmd] ne {}} {
2816                 tools_populate_all
2817         }
2820 # -- Help Menu
2822 .mbar add cascade -label [mc Help] -menu .mbar.help
2823 menu .mbar.help
2825 if {[is_MacOSX]} {
2826         .mbar.apple add command -label [mc "About %s" [appname]] \
2827                 -command do_about
2828         .mbar.apple add separator
2829 } else {
2830         .mbar.help add command -label [mc "About %s" [appname]] \
2831                 -command do_about
2833 . configure -menu .mbar
2835 set doc_path [githtmldir]
2836 if {$doc_path ne {}} {
2837         set doc_path [file join $doc_path index.html]
2839         if {[is_Cygwin]} {
2840                 set doc_path [exec cygpath --mixed $doc_path]
2841         }
2844 if {[file isfile $doc_path]} {
2845         set doc_url "file:$doc_path"
2846 } else {
2847         set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
2850 proc start_browser {url} {
2851         git "web--browse" $url
2854 .mbar.help add command -label [mc "Online Documentation"] \
2855         -command [list start_browser $doc_url]
2857 .mbar.help add command -label [mc "Show SSH Key"] \
2858         -command do_ssh_key
2860 unset doc_path doc_url
2862 # -- Standard bindings
2864 wm protocol . WM_DELETE_WINDOW do_quit
2865 bind all <$M1B-Key-q> do_quit
2866 bind all <$M1B-Key-Q> do_quit
2867 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
2868 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
2870 set subcommand_args {}
2871 proc usage {} {
2872         set s "usage: $::argv0 $::subcommand $::subcommand_args"
2873         if {[tk windowingsystem] eq "win32"} {
2874                 wm withdraw .
2875                 tk_messageBox -icon info -message $s \
2876                         -title [mc "Usage"]
2877         } else {
2878                 puts stderr $s
2879         }
2880         exit 1
2883 proc normalize_relpath {path} {
2884         set elements {}
2885         foreach item [file split $path] {
2886                 if {$item eq {.}} continue
2887                 if {$item eq {..} && [llength $elements] > 0
2888                     && [lindex $elements end] ne {..}} {
2889                         set elements [lrange $elements 0 end-1]
2890                         continue
2891                 }
2892                 lappend elements $item
2893         }
2894         return [eval file join $elements]
2897 # -- Not a normal commit type invocation?  Do that instead!
2899 switch -- $subcommand {
2900 browser -
2901 blame {
2902         if {$subcommand eq "blame"} {
2903                 set subcommand_args {[--line=<num>] rev? path}
2904         } else {
2905                 set subcommand_args {rev? path}
2906         }
2907         if {$argv eq {}} usage
2908         set head {}
2909         set path {}
2910         set jump_spec {}
2911         set is_path 0
2912         foreach a $argv {
2913                 if {$is_path || [file exists $_prefix$a]} {
2914                         if {$path ne {}} usage
2915                         set path [normalize_relpath $_prefix$a]
2916                         break
2917                 } elseif {$a eq {--}} {
2918                         if {$path ne {}} {
2919                                 if {$head ne {}} usage
2920                                 set head $path
2921                                 set path {}
2922                         }
2923                         set is_path 1
2924                 } elseif {[regexp {^--line=(\d+)$} $a a lnum]} {
2925                         if {$jump_spec ne {} || $head ne {}} usage
2926                         set jump_spec [list $lnum]
2927                 } elseif {$head eq {}} {
2928                         if {$head ne {}} usage
2929                         set head $a
2930                         set is_path 1
2931                 } else {
2932                         usage
2933                 }
2934         }
2935         unset is_path
2937         if {$head ne {} && $path eq {}} {
2938                 set path [normalize_relpath $_prefix$head]
2939                 set head {}
2940         }
2942         if {$head eq {}} {
2943                 load_current_branch
2944         } else {
2945                 if {[regexp {^[0-9a-f]{1,39}$} $head]} {
2946                         if {[catch {
2947                                         set head [git rev-parse --verify $head]
2948                                 } err]} {
2949                                 if {[tk windowingsystem] eq "win32"} {
2950                                         tk_messageBox -icon error -title [mc Error] -message $err
2951                                 } else {
2952                                         puts stderr $err
2953                                 }
2954                                 exit 1
2955                         }
2956                 }
2957                 set current_branch $head
2958         }
2960         wm deiconify .
2961         switch -- $subcommand {
2962         browser {
2963                 if {$jump_spec ne {}} usage
2964                 if {$head eq {}} {
2965                         if {$path ne {} && [file isdirectory $path]} {
2966                                 set head $current_branch
2967                         } else {
2968                                 set head $path
2969                                 set path {}
2970                         }
2971                 }
2972                 browser::new $head $path
2973         }
2974         blame   {
2975                 if {$head eq {} && ![file exists $path]} {
2976                         catch {wm withdraw .}
2977                         tk_messageBox \
2978                                 -icon error \
2979                                 -type ok \
2980                                 -title [mc "git-gui: fatal error"] \
2981                                 -message [mc "fatal: cannot stat path %s: No such file or directory" $path]
2982                         exit 1
2983                 }
2984                 blame::new $head $path $jump_spec
2985         }
2986         }
2987         return
2989 citool -
2990 gui {
2991         if {[llength $argv] != 0} {
2992                 usage
2993         }
2994         # fall through to setup UI for commits
2996 default {
2997         set err "usage: $argv0 \[{blame|browser|citool}\]"
2998         if {[tk windowingsystem] eq "win32"} {
2999                 wm withdraw .
3000                 tk_messageBox -icon error -message $err \
3001                         -title [mc "Usage"]
3002         } else {
3003                 puts stderr $err
3004         }
3005         exit 1
3009 # -- Branch Control
3011 ${NS}::frame .branch
3012 if {!$use_ttk} {.branch configure -borderwidth 1 -relief sunken}
3013 ${NS}::label .branch.l1 \
3014         -text [mc "Current Branch:"] \
3015         -anchor w \
3016         -justify left
3017 ${NS}::label .branch.cb \
3018         -textvariable current_branch \
3019         -anchor w \
3020         -justify left
3021 pack .branch.l1 -side left
3022 pack .branch.cb -side left -fill x
3023 pack .branch -side top -fill x
3025 # -- Main Window Layout
3027 ${NS}::panedwindow .vpane -orient horizontal
3028 ${NS}::panedwindow .vpane.files -orient vertical
3029 if {$use_ttk} {
3030         .vpane add .vpane.files
3031 } else {
3032         .vpane add .vpane.files -sticky nsew -height 100 -width 200
3034 pack .vpane -anchor n -side top -fill both -expand 1
3036 # -- Index File List
3038 ${NS}::frame .vpane.files.index -height 100 -width 200
3039 tlabel .vpane.files.index.title \
3040         -text [mc "Staged Changes (Will Commit)"] \
3041         -background lightgreen -foreground black
3042 text $ui_index -background white -foreground black \
3043         -borderwidth 0 \
3044         -width 20 -height 10 \
3045         -wrap none \
3046         -cursor $cursor_ptr \
3047         -xscrollcommand {.vpane.files.index.sx set} \
3048         -yscrollcommand {.vpane.files.index.sy set} \
3049         -state disabled
3050 ${NS}::scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
3051 ${NS}::scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
3052 pack .vpane.files.index.title -side top -fill x
3053 pack .vpane.files.index.sx -side bottom -fill x
3054 pack .vpane.files.index.sy -side right -fill y
3055 pack $ui_index -side left -fill both -expand 1
3057 # -- Working Directory File List
3059 ${NS}::frame .vpane.files.workdir -height 100 -width 200
3060 tlabel .vpane.files.workdir.title -text [mc "Unstaged Changes"] \
3061         -background lightsalmon -foreground black
3062 text $ui_workdir -background white -foreground black \
3063         -borderwidth 0 \
3064         -width 20 -height 10 \
3065         -wrap none \
3066         -cursor $cursor_ptr \
3067         -xscrollcommand {.vpane.files.workdir.sx set} \
3068         -yscrollcommand {.vpane.files.workdir.sy set} \
3069         -state disabled
3070 ${NS}::scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
3071 ${NS}::scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
3072 pack .vpane.files.workdir.title -side top -fill x
3073 pack .vpane.files.workdir.sx -side bottom -fill x
3074 pack .vpane.files.workdir.sy -side right -fill y
3075 pack $ui_workdir -side left -fill both -expand 1
3077 .vpane.files add .vpane.files.workdir
3078 .vpane.files add .vpane.files.index
3079 if {!$use_ttk} {
3080         .vpane.files paneconfigure .vpane.files.workdir -sticky news
3081         .vpane.files paneconfigure .vpane.files.index -sticky news
3084 foreach i [list $ui_index $ui_workdir] {
3085         rmsel_tag $i
3086         $i tag conf in_diff -background [$i tag cget in_sel -background]
3088 unset i
3090 # -- Diff and Commit Area
3092 ${NS}::frame .vpane.lower -height 300 -width 400
3093 ${NS}::frame .vpane.lower.commarea
3094 ${NS}::frame .vpane.lower.diff -relief sunken -borderwidth 1
3095 pack .vpane.lower.diff -fill both -expand 1
3096 pack .vpane.lower.commarea -side bottom -fill x
3097 .vpane add .vpane.lower
3098 if {!$use_ttk} {.vpane paneconfigure .vpane.lower -sticky nsew}
3100 # -- Commit Area Buttons
3102 ${NS}::frame .vpane.lower.commarea.buttons
3103 ${NS}::label .vpane.lower.commarea.buttons.l -text {} \
3104         -anchor w \
3105         -justify left
3106 pack .vpane.lower.commarea.buttons.l -side top -fill x
3107 pack .vpane.lower.commarea.buttons -side left -fill y
3109 ${NS}::button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \
3110         -command ui_do_rescan
3111 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
3112 lappend disable_on_lock \
3113         {.vpane.lower.commarea.buttons.rescan conf -state}
3115 ${NS}::button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \
3116         -command do_add_all
3117 pack .vpane.lower.commarea.buttons.incall -side top -fill x
3118 lappend disable_on_lock \
3119         {.vpane.lower.commarea.buttons.incall conf -state}
3121 if {![is_enabled nocommitmsg]} {
3122         ${NS}::button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \
3123                 -command do_signoff
3124         pack .vpane.lower.commarea.buttons.signoff -side top -fill x
3127 ${NS}::button .vpane.lower.commarea.buttons.commit -text [commit_btn_caption] \
3128         -command do_commit
3129 pack .vpane.lower.commarea.buttons.commit -side top -fill x
3130 lappend disable_on_lock \
3131         {.vpane.lower.commarea.buttons.commit conf -state}
3133 if {![is_enabled nocommit]} {
3134         ${NS}::button .vpane.lower.commarea.buttons.push -text [mc Push] \
3135                 -command do_push_anywhere
3136         pack .vpane.lower.commarea.buttons.push -side top -fill x
3139 # -- Commit Message Buffer
3141 ${NS}::frame .vpane.lower.commarea.buffer
3142 ${NS}::frame .vpane.lower.commarea.buffer.header
3143 set ui_comm .vpane.lower.commarea.buffer.t
3144 set ui_coml .vpane.lower.commarea.buffer.header.l
3146 if {![is_enabled nocommit]} {
3147         ${NS}::radiobutton .vpane.lower.commarea.buffer.header.new \
3148                 -text [mc "New Commit"] \
3149                 -command do_select_commit_type \
3150                 -variable selected_commit_type \
3151                 -value new
3152         lappend disable_on_lock \
3153                 [list .vpane.lower.commarea.buffer.header.new conf -state]
3154         ${NS}::radiobutton .vpane.lower.commarea.buffer.header.amend \
3155                 -text [mc "Amend Last Commit"] \
3156                 -command do_select_commit_type \
3157                 -variable selected_commit_type \
3158                 -value amend
3159         lappend disable_on_lock \
3160                 [list .vpane.lower.commarea.buffer.header.amend conf -state]
3163 ${NS}::label $ui_coml \
3164         -anchor w \
3165         -justify left
3166 proc trace_commit_type {varname args} {
3167         global ui_coml commit_type
3168         switch -glob -- $commit_type {
3169         initial       {set txt [mc "Initial Commit Message:"]}
3170         amend         {set txt [mc "Amended Commit Message:"]}
3171         amend-initial {set txt [mc "Amended Initial Commit Message:"]}
3172         amend-merge   {set txt [mc "Amended Merge Commit Message:"]}
3173         merge         {set txt [mc "Merge Commit Message:"]}
3174         *             {set txt [mc "Commit Message:"]}
3175         }
3176         $ui_coml conf -text $txt
3178 trace add variable commit_type write trace_commit_type
3179 pack $ui_coml -side left -fill x
3181 if {![is_enabled nocommit]} {
3182         pack .vpane.lower.commarea.buffer.header.amend -side right
3183         pack .vpane.lower.commarea.buffer.header.new -side right
3186 text $ui_comm -background white -foreground black \
3187         -borderwidth 1 \
3188         -undo true \
3189         -maxundo 20 \
3190         -autoseparators true \
3191         -relief sunken \
3192         -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \
3193         -font font_diff \
3194         -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
3195 ${NS}::scrollbar .vpane.lower.commarea.buffer.sby \
3196         -command [list $ui_comm yview]
3197 pack .vpane.lower.commarea.buffer.header -side top -fill x
3198 pack .vpane.lower.commarea.buffer.sby -side right -fill y
3199 pack $ui_comm -side left -fill y
3200 pack .vpane.lower.commarea.buffer -side left -fill y
3202 # -- Commit Message Buffer Context Menu
3204 set ctxm .vpane.lower.commarea.buffer.ctxm
3205 menu $ctxm -tearoff 0
3206 $ctxm add command \
3207         -label [mc Cut] \
3208         -command {tk_textCut $ui_comm}
3209 $ctxm add command \
3210         -label [mc Copy] \
3211         -command {tk_textCopy $ui_comm}
3212 $ctxm add command \
3213         -label [mc Paste] \
3214         -command {tk_textPaste $ui_comm}
3215 $ctxm add command \
3216         -label [mc Delete] \
3217         -command {catch {$ui_comm delete sel.first sel.last}}
3218 $ctxm add separator
3219 $ctxm add command \
3220         -label [mc "Select All"] \
3221         -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
3222 $ctxm add command \
3223         -label [mc "Copy All"] \
3224         -command {
3225                 $ui_comm tag add sel 0.0 end
3226                 tk_textCopy $ui_comm
3227                 $ui_comm tag remove sel 0.0 end
3228         }
3229 $ctxm add separator
3230 $ctxm add command \
3231         -label [mc "Sign Off"] \
3232         -command do_signoff
3233 set ui_comm_ctxm $ctxm
3235 # -- Diff Header
3237 proc trace_current_diff_path {varname args} {
3238         global current_diff_path diff_actions file_states
3239         if {$current_diff_path eq {}} {
3240                 set s {}
3241                 set f {}
3242                 set p {}
3243                 set o disabled
3244         } else {
3245                 set p $current_diff_path
3246                 set s [mapdesc [lindex $file_states($p) 0] $p]
3247                 set f [mc "File:"]
3248                 set p [escape_path $p]
3249                 set o normal
3250         }
3252         .vpane.lower.diff.header.status configure -text $s
3253         .vpane.lower.diff.header.file configure -text $f
3254         .vpane.lower.diff.header.path configure -text $p
3255         foreach w $diff_actions {
3256                 uplevel #0 $w $o
3257         }
3259 trace add variable current_diff_path write trace_current_diff_path
3261 gold_frame .vpane.lower.diff.header
3262 tlabel .vpane.lower.diff.header.status \
3263         -background gold \
3264         -foreground black \
3265         -width $max_status_desc \
3266         -anchor w \
3267         -justify left
3268 tlabel .vpane.lower.diff.header.file \
3269         -background gold \
3270         -foreground black \
3271         -anchor w \
3272         -justify left
3273 tlabel .vpane.lower.diff.header.path \
3274         -background gold \
3275         -foreground black \
3276         -anchor w \
3277         -justify left
3278 pack .vpane.lower.diff.header.status -side left
3279 pack .vpane.lower.diff.header.file -side left
3280 pack .vpane.lower.diff.header.path -fill x
3281 set ctxm .vpane.lower.diff.header.ctxm
3282 menu $ctxm -tearoff 0
3283 $ctxm add command \
3284         -label [mc Copy] \
3285         -command {
3286                 clipboard clear
3287                 clipboard append \
3288                         -format STRING \
3289                         -type STRING \
3290                         -- $current_diff_path
3291         }
3292 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3293 bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
3295 # -- Diff Body
3297 ${NS}::frame .vpane.lower.diff.body
3298 set ui_diff .vpane.lower.diff.body.t
3299 text $ui_diff -background white -foreground black \
3300         -borderwidth 0 \
3301         -width 80 -height 5 -wrap none \
3302         -font font_diff \
3303         -xscrollcommand {.vpane.lower.diff.body.sbx set} \
3304         -yscrollcommand {.vpane.lower.diff.body.sby set} \
3305         -state disabled
3306 ${NS}::scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
3307         -command [list $ui_diff xview]
3308 ${NS}::scrollbar .vpane.lower.diff.body.sby -orient vertical \
3309         -command [list $ui_diff yview]
3310 pack .vpane.lower.diff.body.sbx -side bottom -fill x
3311 pack .vpane.lower.diff.body.sby -side right -fill y
3312 pack $ui_diff -side left -fill both -expand 1
3313 pack .vpane.lower.diff.header -side top -fill x
3314 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
3316 $ui_diff tag conf d_cr -elide true
3317 $ui_diff tag conf d_@ -foreground blue -font font_diffbold
3318 $ui_diff tag conf d_+ -foreground {#00a000}
3319 $ui_diff tag conf d_- -foreground red
3321 $ui_diff tag conf d_++ -foreground {#00a000}
3322 $ui_diff tag conf d_-- -foreground red
3323 $ui_diff tag conf d_+s \
3324         -foreground {#00a000} \
3325         -background {#e2effa}
3326 $ui_diff tag conf d_-s \
3327         -foreground red \
3328         -background {#e2effa}
3329 $ui_diff tag conf d_s+ \
3330         -foreground {#00a000} \
3331         -background ivory1
3332 $ui_diff tag conf d_s- \
3333         -foreground red \
3334         -background ivory1
3336 $ui_diff tag conf d<<<<<<< \
3337         -foreground orange \
3338         -font font_diffbold
3339 $ui_diff tag conf d======= \
3340         -foreground orange \
3341         -font font_diffbold
3342 $ui_diff tag conf d>>>>>>> \
3343         -foreground orange \
3344         -font font_diffbold
3346 $ui_diff tag raise sel
3348 # -- Diff Body Context Menu
3351 proc create_common_diff_popup {ctxm} {
3352         $ctxm add command \
3353                 -label [mc Refresh] \
3354                 -command reshow_diff
3355         lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3356         $ctxm add command \
3357                 -label [mc Copy] \
3358                 -command {tk_textCopy $ui_diff}
3359         lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3360         $ctxm add command \
3361                 -label [mc "Select All"] \
3362                 -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
3363         lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3364         $ctxm add command \
3365                 -label [mc "Copy All"] \
3366                 -command {
3367                         $ui_diff tag add sel 0.0 end
3368                         tk_textCopy $ui_diff
3369                         $ui_diff tag remove sel 0.0 end
3370                 }
3371         lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3372         $ctxm add separator
3373         $ctxm add command \
3374                 -label [mc "Decrease Font Size"] \
3375                 -command {incr_font_size font_diff -1}
3376         lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3377         $ctxm add command \
3378                 -label [mc "Increase Font Size"] \
3379                 -command {incr_font_size font_diff 1}
3380         lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3381         $ctxm add separator
3382         set emenu $ctxm.enc
3383         menu $emenu
3384         build_encoding_menu $emenu [list force_diff_encoding]
3385         $ctxm add cascade \
3386                 -label [mc "Encoding"] \
3387                 -menu $emenu
3388         lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3389         $ctxm add separator
3390         $ctxm add command -label [mc "Options..."] \
3391                 -command do_options
3394 set ctxm .vpane.lower.diff.body.ctxm
3395 menu $ctxm -tearoff 0
3396 $ctxm add command \
3397         -label [mc "Apply/Reverse Hunk"] \
3398         -command {apply_hunk $cursorX $cursorY}
3399 set ui_diff_applyhunk [$ctxm index last]
3400 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
3401 $ctxm add command \
3402         -label [mc "Apply/Reverse Line"] \
3403         -command {apply_range_or_line $cursorX $cursorY; do_rescan}
3404 set ui_diff_applyline [$ctxm index last]
3405 lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state]
3406 $ctxm add separator
3407 $ctxm add command \
3408         -label [mc "Show Less Context"] \
3409         -command show_less_context
3410 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3411 $ctxm add command \
3412         -label [mc "Show More Context"] \
3413         -command show_more_context
3414 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3415 $ctxm add separator
3416 create_common_diff_popup $ctxm
3418 set ctxmmg .vpane.lower.diff.body.ctxmmg
3419 menu $ctxmmg -tearoff 0
3420 $ctxmmg add command \
3421         -label [mc "Run Merge Tool"] \
3422         -command {merge_resolve_tool}
3423 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3424 $ctxmmg add separator
3425 $ctxmmg add command \
3426         -label [mc "Use Remote Version"] \
3427         -command {merge_resolve_one 3}
3428 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3429 $ctxmmg add command \
3430         -label [mc "Use Local Version"] \
3431         -command {merge_resolve_one 2}
3432 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3433 $ctxmmg add command \
3434         -label [mc "Revert To Base"] \
3435         -command {merge_resolve_one 1}
3436 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3437 $ctxmmg add separator
3438 $ctxmmg add command \
3439         -label [mc "Show Less Context"] \
3440         -command show_less_context
3441 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3442 $ctxmmg add command \
3443         -label [mc "Show More Context"] \
3444         -command show_more_context
3445 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3446 $ctxmmg add separator
3447 create_common_diff_popup $ctxmmg
3449 set ctxmsm .vpane.lower.diff.body.ctxmsm
3450 menu $ctxmsm -tearoff 0
3451 $ctxmsm add command \
3452         -label [mc "Visualize These Changes In The Submodule"] \
3453         -command {do_gitk -- true}
3454 lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
3455 $ctxmsm add command \
3456         -label [mc "Visualize Current Branch History In The Submodule"] \
3457         -command {do_gitk {} true}
3458 lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
3459 $ctxmsm add command \
3460         -label [mc "Visualize All Branch History In The Submodule"] \
3461         -command {do_gitk --all true}
3462 lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
3463 $ctxmsm add separator
3464 $ctxmsm add command \
3465         -label [mc "Start git gui In The Submodule"] \
3466         -command {do_git_gui}
3467 lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
3468 $ctxmsm add separator
3469 create_common_diff_popup $ctxmsm
3471 proc has_textconv {path} {
3472         if {[is_config_false gui.textconv]} {
3473                 return 0
3474         }
3475         set filter [gitattr $path diff set]
3476         set textconv [get_config [join [list diff $filter textconv] .]]
3477         if {$filter ne {set} && $textconv ne {}} {
3478                 return 1
3479         } else {
3480                 return 0
3481         }
3484 proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} {
3485         global current_diff_path file_states
3486         set ::cursorX $x
3487         set ::cursorY $y
3488         if {[info exists file_states($current_diff_path)]} {
3489                 set state [lindex $file_states($current_diff_path) 0]
3490         } else {
3491                 set state {__}
3492         }
3493         if {[string first {U} $state] >= 0} {
3494                 tk_popup $ctxmmg $X $Y
3495         } elseif {$::is_submodule_diff} {
3496                 tk_popup $ctxmsm $X $Y
3497         } else {
3498                 set has_range [expr {[$::ui_diff tag nextrange sel 0.0] != {}}]
3499                 if {$::ui_index eq $::current_diff_side} {
3500                         set l [mc "Unstage Hunk From Commit"]
3501                         if {$has_range} {
3502                                 set t [mc "Unstage Lines From Commit"]
3503                         } else {
3504                                 set t [mc "Unstage Line From Commit"]
3505                         }
3506                 } else {
3507                         set l [mc "Stage Hunk For Commit"]
3508                         if {$has_range} {
3509                                 set t [mc "Stage Lines For Commit"]
3510                         } else {
3511                                 set t [mc "Stage Line For Commit"]
3512                         }
3513                 }
3514                 if {$::is_3way_diff
3515                         || $current_diff_path eq {}
3516                         || {__} eq $state
3517                         || {_O} eq $state
3518                         || {_T} eq $state
3519                         || {T_} eq $state
3520                         || [has_textconv $current_diff_path]} {
3521                         set s disabled
3522                 } else {
3523                         set s normal
3524                 }
3525                 $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
3526                 $ctxm entryconf $::ui_diff_applyline -state $s -label $t
3527                 tk_popup $ctxm $X $Y
3528         }
3530 bind_button3 $ui_diff [list popup_diff_menu $ctxm $ctxmmg $ctxmsm %x %y %X %Y]
3532 # -- Status Bar
3534 set main_status [::status_bar::new .status]
3535 pack .status -anchor w -side bottom -fill x
3536 $main_status show [mc "Initializing..."]
3538 # -- Load geometry
3540 proc on_ttk_pane_mapped {w pane pos} {
3541         bind $w <Map> {}
3542         after 0 [list after idle [list $w sashpos $pane $pos]]
3544 proc on_tk_pane_mapped {w pane x y} {
3545         bind $w <Map> {}
3546         after 0 [list after idle [list $w sash place $pane $x $y]]
3548 proc on_application_mapped {} {
3549         global repo_config use_ttk
3550         bind . <Map> {}
3551         set gm $repo_config(gui.geometry)
3552         if {$use_ttk} {
3553                 bind .vpane <Map> \
3554                     [list on_ttk_pane_mapped %W 0 [lindex $gm 1]]
3555                 bind .vpane.files <Map> \
3556                     [list on_ttk_pane_mapped %W 0 [lindex $gm 2]]
3557         } else {
3558                 bind .vpane <Map> \
3559                     [list on_tk_pane_mapped %W 0 \
3560                          [lindex $gm 1] \
3561                          [lindex [.vpane sash coord 0] 1]]
3562                 bind .vpane.files <Map> \
3563                     [list on_tk_pane_mapped %W 0 \
3564                          [lindex [.vpane.files sash coord 0] 0] \
3565                          [lindex $gm 2]]
3566         }
3567         wm geometry . [lindex $gm 0]
3569 if {[info exists repo_config(gui.geometry)]} {
3570         bind . <Map> [list on_application_mapped]
3571         wm geometry . [lindex $repo_config(gui.geometry) 0]
3574 # -- Load window state
3576 if {[info exists repo_config(gui.wmstate)]} {
3577         catch {wm state . $repo_config(gui.wmstate)}
3580 # -- Key Bindings
3582 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
3583 bind $ui_comm <$M1B-Key-t> {do_add_selection;break}
3584 bind $ui_comm <$M1B-Key-T> {do_add_selection;break}
3585 bind $ui_comm <$M1B-Key-u> {do_unstage_selection;break}
3586 bind $ui_comm <$M1B-Key-U> {do_unstage_selection;break}
3587 bind $ui_comm <$M1B-Key-j> {do_revert_selection;break}
3588 bind $ui_comm <$M1B-Key-J> {do_revert_selection;break}
3589 bind $ui_comm <$M1B-Key-i> {do_add_all;break}
3590 bind $ui_comm <$M1B-Key-I> {do_add_all;break}
3591 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
3592 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
3593 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
3594 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
3595 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
3596 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
3597 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3598 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3599 bind $ui_comm <$M1B-Key-minus> {show_less_context;break}
3600 bind $ui_comm <$M1B-Key-KP_Subtract> {show_less_context;break}
3601 bind $ui_comm <$M1B-Key-equal> {show_more_context;break}
3602 bind $ui_comm <$M1B-Key-plus> {show_more_context;break}
3603 bind $ui_comm <$M1B-Key-KP_Add> {show_more_context;break}
3605 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
3606 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
3607 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
3608 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
3609 bind $ui_diff <$M1B-Key-v> {break}
3610 bind $ui_diff <$M1B-Key-V> {break}
3611 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3612 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3613 bind $ui_diff <Key-Up>     {catch {%W yview scroll -1 units};break}
3614 bind $ui_diff <Key-Down>   {catch {%W yview scroll  1 units};break}
3615 bind $ui_diff <Key-Left>   {catch {%W xview scroll -1 units};break}
3616 bind $ui_diff <Key-Right>  {catch {%W xview scroll  1 units};break}
3617 bind $ui_diff <Key-k>         {catch {%W yview scroll -1 units};break}
3618 bind $ui_diff <Key-j>         {catch {%W yview scroll  1 units};break}
3619 bind $ui_diff <Key-h>         {catch {%W xview scroll -1 units};break}
3620 bind $ui_diff <Key-l>         {catch {%W xview scroll  1 units};break}
3621 bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
3622 bind $ui_diff <Control-Key-f> {catch {%W yview scroll  1 pages};break}
3623 bind $ui_diff <Button-1>   {focus %W}
3625 if {[is_enabled branch]} {
3626         bind . <$M1B-Key-n> branch_create::dialog
3627         bind . <$M1B-Key-N> branch_create::dialog
3628         bind . <$M1B-Key-o> branch_checkout::dialog
3629         bind . <$M1B-Key-O> branch_checkout::dialog
3630         bind . <$M1B-Key-m> merge::dialog
3631         bind . <$M1B-Key-M> merge::dialog
3633 if {[is_enabled transport]} {
3634         bind . <$M1B-Key-p> do_push_anywhere
3635         bind . <$M1B-Key-P> do_push_anywhere
3638 bind .   <Key-F5>     ui_do_rescan
3639 bind .   <$M1B-Key-r> ui_do_rescan
3640 bind .   <$M1B-Key-R> ui_do_rescan
3641 bind .   <$M1B-Key-s> do_signoff
3642 bind .   <$M1B-Key-S> do_signoff
3643 bind .   <$M1B-Key-t> do_add_selection
3644 bind .   <$M1B-Key-T> do_add_selection
3645 bind .   <$M1B-Key-j> do_revert_selection
3646 bind .   <$M1B-Key-J> do_revert_selection
3647 bind .   <$M1B-Key-i> do_add_all
3648 bind .   <$M1B-Key-I> do_add_all
3649 bind .   <$M1B-Key-minus> {show_less_context;break}
3650 bind .   <$M1B-Key-KP_Subtract> {show_less_context;break}
3651 bind .   <$M1B-Key-equal> {show_more_context;break}
3652 bind .   <$M1B-Key-plus> {show_more_context;break}
3653 bind .   <$M1B-Key-KP_Add> {show_more_context;break}
3654 bind .   <$M1B-Key-Return> do_commit
3655 foreach i [list $ui_index $ui_workdir] {
3656         bind $i <Button-1>       "toggle_or_diff         $i %x %y; break"
3657         bind $i <$M1B-Button-1>  "add_one_to_selection   $i %x %y; break"
3658         bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
3660 unset i
3662 set file_lists($ui_index) [list]
3663 set file_lists($ui_workdir) [list]
3665 wm title . "[appname] ([reponame]) [file normalize $_gitworktree]"
3666 focus -force $ui_comm
3668 # -- Warn the user about environmental problems.  Cygwin's Tcl
3669 #    does *not* pass its env array onto any processes it spawns.
3670 #    This means that git processes get none of our environment.
3672 if {[is_Cygwin]} {
3673         set ignored_env 0
3674         set suggest_user {}
3675         set msg [mc "Possible environment issues exist.
3677 The following environment variables are probably
3678 going to be ignored by any Git subprocess run
3679 by %s:
3681 " [appname]]
3682         foreach name [array names env] {
3683                 switch -regexp -- $name {
3684                 {^GIT_INDEX_FILE$} -
3685                 {^GIT_OBJECT_DIRECTORY$} -
3686                 {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
3687                 {^GIT_DIFF_OPTS$} -
3688                 {^GIT_EXTERNAL_DIFF$} -
3689                 {^GIT_PAGER$} -
3690                 {^GIT_TRACE$} -
3691                 {^GIT_CONFIG$} -
3692                 {^GIT_(AUTHOR|COMMITTER)_DATE$} {
3693                         append msg " - $name\n"
3694                         incr ignored_env
3695                 }
3696                 {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
3697                         append msg " - $name\n"
3698                         incr ignored_env
3699                         set suggest_user $name
3700                 }
3701                 }
3702         }
3703         if {$ignored_env > 0} {
3704                 append msg [mc "
3705 This is due to a known issue with the
3706 Tcl binary distributed by Cygwin."]
3708                 if {$suggest_user ne {}} {
3709                         append msg [mc "
3711 A good replacement for %s
3712 is placing values for the user.name and
3713 user.email settings into your personal
3714 ~/.gitconfig file.
3715 " $suggest_user]
3716                 }
3717                 warn_popup $msg
3718         }
3719         unset ignored_env msg suggest_user name
3722 # -- Only initialize complex UI if we are going to stay running.
3724 if {[is_enabled transport]} {
3725         load_all_remotes
3727         set n [.mbar.remote index end]
3728         populate_remotes_menu
3729         set n [expr {[.mbar.remote index end] - $n}]
3730         if {$n > 0} {
3731                 if {[.mbar.remote type 0] eq "tearoff"} { incr n }
3732                 .mbar.remote insert $n separator
3733         }
3734         unset n
3737 if {[winfo exists $ui_comm]} {
3738         set GITGUI_BCK_exists [load_message GITGUI_BCK]
3740         # -- If both our backup and message files exist use the
3741         #    newer of the two files to initialize the buffer.
3742         #
3743         if {$GITGUI_BCK_exists} {
3744                 set m [gitdir GITGUI_MSG]
3745                 if {[file isfile $m]} {
3746                         if {[file mtime [gitdir GITGUI_BCK]] > [file mtime $m]} {
3747                                 catch {file delete [gitdir GITGUI_MSG]}
3748                         } else {
3749                                 $ui_comm delete 0.0 end
3750                                 $ui_comm edit reset
3751                                 $ui_comm edit modified false
3752                                 catch {file delete [gitdir GITGUI_BCK]}
3753                                 set GITGUI_BCK_exists 0
3754                         }
3755                 }
3756                 unset m
3757         }
3759         proc backup_commit_buffer {} {
3760                 global ui_comm GITGUI_BCK_exists
3762                 set m [$ui_comm edit modified]
3763                 if {$m || $GITGUI_BCK_exists} {
3764                         set msg [string trim [$ui_comm get 0.0 end]]
3765                         regsub -all -line {[ \r\t]+$} $msg {} msg
3767                         if {$msg eq {}} {
3768                                 if {$GITGUI_BCK_exists} {
3769                                         catch {file delete [gitdir GITGUI_BCK]}
3770                                         set GITGUI_BCK_exists 0
3771                                 }
3772                         } elseif {$m} {
3773                                 catch {
3774                                         set fd [open [gitdir GITGUI_BCK] w]
3775                                         puts -nonewline $fd $msg
3776                                         close $fd
3777                                         set GITGUI_BCK_exists 1
3778                                 }
3779                         }
3781                         $ui_comm edit modified false
3782                 }
3784                 set ::GITGUI_BCK_i [after 2000 backup_commit_buffer]
3785         }
3787         backup_commit_buffer
3789         # -- If the user has aspell available we can drive it
3790         #    in pipe mode to spellcheck the commit message.
3791         #
3792         set spell_cmd [list |]
3793         set spell_dict [get_config gui.spellingdictionary]
3794         lappend spell_cmd aspell
3795         if {$spell_dict ne {}} {
3796                 lappend spell_cmd --master=$spell_dict
3797         }
3798         lappend spell_cmd --mode=none
3799         lappend spell_cmd --encoding=utf-8
3800         lappend spell_cmd pipe
3801         if {$spell_dict eq {none}
3802          || [catch {set spell_fd [open $spell_cmd r+]} spell_err]} {
3803                 bind_button3 $ui_comm [list tk_popup $ui_comm_ctxm %X %Y]
3804         } else {
3805                 set ui_comm_spell [spellcheck::init \
3806                         $spell_fd \
3807                         $ui_comm \
3808                         $ui_comm_ctxm \
3809                 ]
3810         }
3811         unset -nocomplain spell_cmd spell_fd spell_err spell_dict
3814 lock_index begin-read
3815 if {![winfo ismapped .]} {
3816         wm deiconify .
3818 after 1 {
3819         if {[is_enabled initialamend]} {
3820                 force_amend
3821         } else {
3822                 do_rescan
3823         }
3825         if {[is_enabled nocommitmsg]} {
3826                 $ui_comm configure -state disabled -background gray
3827         }
3829 if {[is_enabled multicommit]} {
3830         after 1000 hint_gc
3832 if {[is_enabled retcode]} {
3833         bind . <Destroy> {+terminate_me %W}
3835 if {$picked && [is_config_true gui.autoexplore]} {
3836         do_explore
3839 # Local variables:
3840 # mode: tcl
3841 # indent-tabs-mode: t
3842 # tab-width: 4
3843 # End: