]> asedeno.scripts.mit.edu Git - git.git/blob - git-gui.sh
git-gui: Ensure consistent usage of mergetool.keepBackup
[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" -- "$@"
11
12 set appvers {@@GITGUI_VERSION@@}
13 set copyright [encoding convertfrom utf-8 {
14 Copyright © 2006, 2007 Shawn Pearce, et. al.
15
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.
20
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.
25
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}]
29
30 ######################################################################
31 ##
32 ## Tcl/Tk sanity check
33
34 if {[catch {package require Tcl 8.4} err]
35  || [catch {package require Tk  8.4} err]
36 } {
37         catch {wm withdraw .}
38         tk_messageBox \
39                 -icon error \
40                 -type ok \
41                 -title [mc "git-gui: fatal error"] \
42                 -message $err
43         exit 1
44 }
45
46 catch {rename send {}} ; # What an evil concept...
47
48 ######################################################################
49 ##
50 ## locate our library
51
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
69
70 ######################################################################
71 ##
72 ## enable verbose loading?
73
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 }
87
88 ######################################################################
89 ##
90 ## Internationalization (i18n) through msgcat and gettext. See
91 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
92
93 package require msgcat
94
95 proc _mc_trim {fmt} {
96         set cmk [string first @@ $fmt]
97         if {$cmk > 0} {
98                 return [string range $fmt 0 [expr {$cmk - 1}]]
99         }
100         return $fmt
101 }
102
103 proc mc {en_fmt args} {
104         set fmt [_mc_trim [::msgcat::mc $en_fmt]]
105         if {[catch {set msg [eval [list format $fmt] $args]} err]} {
106                 set msg [eval [list format [_mc_trim $en_fmt]] $args]
107         }
108         return $msg
109 }
110
111 proc strcat {args} {
112         return [join $args {}]
113 }
114
115 ::msgcat::mcload $oguimsg
116 unset oguimsg
117
118 ######################################################################
119 ##
120 ## read only globals
121
122 set _appname {Git Gui}
123 set _gitdir {}
124 set _gitexec {}
125 set _reponame {}
126 set _iscygwin {}
127 set _search_path {}
128
129 set _trace [lsearch -exact $argv --trace]
130 if {$_trace >= 0} {
131         set argv [lreplace $argv $_trace $_trace]
132         set _trace 1
133 } else {
134         set _trace 0
135 }
136
137 proc appname {} {
138         global _appname
139         return $_appname
140 }
141
142 proc gitdir {args} {
143         global _gitdir
144         if {$args eq {}} {
145                 return $_gitdir
146         }
147         return [eval [list file join $_gitdir] $args]
148 }
149
150 proc gitexec {args} {
151         global _gitexec
152         if {$_gitexec eq {}} {
153                 if {[catch {set _gitexec [git --exec-path]} err]} {
154                         error "Git not installed?\n\n$err"
155                 }
156                 if {[is_Cygwin]} {
157                         set _gitexec [exec cygpath \
158                                 --windows \
159                                 --absolute \
160                                 $_gitexec]
161                 } else {
162                         set _gitexec [file normalize $_gitexec]
163                 }
164         }
165         if {$args eq {}} {
166                 return $_gitexec
167         }
168         return [eval [list file join $_gitexec] $args]
169 }
170
171 proc reponame {} {
172         return $::_reponame
173 }
174
175 proc is_MacOSX {} {
176         if {[tk windowingsystem] eq {aqua}} {
177                 return 1
178         }
179         return 0
180 }
181
182 proc is_Windows {} {
183         if {$::tcl_platform(platform) eq {windows}} {
184                 return 1
185         }
186         return 0
187 }
188
189 proc is_Cygwin {} {
190         global _iscygwin
191         if {$_iscygwin eq {}} {
192                 if {$::tcl_platform(platform) eq {windows}} {
193                         if {[catch {set p [exec cygpath --windir]} err]} {
194                                 set _iscygwin 0
195                         } else {
196                                 set _iscygwin 1
197                         }
198                 } else {
199                         set _iscygwin 0
200                 }
201         }
202         return $_iscygwin
203 }
204
205 proc is_enabled {option} {
206         global enabled_options
207         if {[catch {set on $enabled_options($option)}]} {return 0}
208         return $on
209 }
210
211 proc enable_option {option} {
212         global enabled_options
213         set enabled_options($option) 1
214 }
215
216 proc disable_option {option} {
217         global enabled_options
218         set enabled_options($option) 0
219 }
220
221 ######################################################################
222 ##
223 ## config
224
225 proc is_many_config {name} {
226         switch -glob -- $name {
227         gui.recentrepo -
228         remote.*.fetch -
229         remote.*.push
230                 {return 1}
231         *
232                 {return 0}
233         }
234 }
235
236 proc is_config_true {name} {
237         global repo_config
238         if {[catch {set v $repo_config($name)}]} {
239                 return 0
240         } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
241                 return 1
242         } else {
243                 return 0
244         }
245 }
246
247 proc get_config {name} {
248         global repo_config
249         if {[catch {set v $repo_config($name)}]} {
250                 return {}
251         } else {
252                 return $v
253         }
254 }
255
256 ######################################################################
257 ##
258 ## handy utils
259
260 proc _trace_exec {cmd} {
261         if {!$::_trace} return
262         set d {}
263         foreach v $cmd {
264                 if {$d ne {}} {
265                         append d { }
266                 }
267                 if {[regexp {[ \t\r\n'"$?*]} $v]} {
268                         set v [sq $v]
269                 }
270                 append d $v
271         }
272         puts stderr $d
273 }
274
275 proc _git_cmd {name} {
276         global _git_cmd_path
277
278         if {[catch {set v $_git_cmd_path($name)}]} {
279                 switch -- $name {
280                   version   -
281                 --version   -
282                 --exec-path { return [list $::_git $name] }
283                 }
284
285                 set p [gitexec git-$name$::_search_exe]
286                 if {[file exists $p]} {
287                         set v [list $p]
288                 } elseif {[is_Windows] && [file exists [gitexec git-$name]]} {
289                         # Try to determine what sort of magic will make
290                         # git-$name go and do its thing, because native
291                         # Tcl on Windows doesn't know it.
292                         #
293                         set p [gitexec git-$name]
294                         set f [open $p r]
295                         set s [gets $f]
296                         close $f
297
298                         switch -glob -- [lindex $s 0] {
299                         #!*sh     { set i sh     }
300                         #!*perl   { set i perl   }
301                         #!*python { set i python }
302                         default   { error "git-$name is not supported: $s" }
303                         }
304
305                         upvar #0 _$i interp
306                         if {![info exists interp]} {
307                                 set interp [_which $i]
308                         }
309                         if {$interp eq {}} {
310                                 error "git-$name requires $i (not in PATH)"
311                         }
312                         set v [concat [list $interp] [lrange $s 1 end] [list $p]]
313                 } else {
314                         # Assume it is builtin to git somehow and we
315                         # aren't actually able to see a file for it.
316                         #
317                         set v [list $::_git $name]
318                 }
319                 set _git_cmd_path($name) $v
320         }
321         return $v
322 }
323
324 proc _which {what args} {
325         global env _search_exe _search_path
326
327         if {$_search_path eq {}} {
328                 if {[is_Cygwin] && [regexp {^(/|\.:)} $env(PATH)]} {
329                         set _search_path [split [exec cygpath \
330                                 --windows \
331                                 --path \
332                                 --absolute \
333                                 $env(PATH)] {;}]
334                         set _search_exe .exe
335                 } elseif {[is_Windows]} {
336                         set gitguidir [file dirname [info script]]
337                         regsub -all ";" $gitguidir "\\;" gitguidir
338                         set env(PATH) "$gitguidir;$env(PATH)"
339                         set _search_path [split $env(PATH) {;}]
340                         set _search_exe .exe
341                 } else {
342                         set _search_path [split $env(PATH) :]
343                         set _search_exe {}
344                 }
345         }
346
347         if {[is_Windows] && [lsearch -exact $args -script] >= 0} {
348                 set suffix {}
349         } else {
350                 set suffix $_search_exe
351         }
352
353         foreach p $_search_path {
354                 set p [file join $p $what$suffix]
355                 if {[file exists $p]} {
356                         return [file normalize $p]
357                 }
358         }
359         return {}
360 }
361
362 proc _lappend_nice {cmd_var} {
363         global _nice
364         upvar $cmd_var cmd
365
366         if {![info exists _nice]} {
367                 set _nice [_which nice]
368         }
369         if {$_nice ne {}} {
370                 lappend cmd $_nice
371         }
372 }
373
374 proc git {args} {
375         set opt [list]
376
377         while {1} {
378                 switch -- [lindex $args 0] {
379                 --nice {
380                         _lappend_nice opt
381                 }
382
383                 default {
384                         break
385                 }
386
387                 }
388
389                 set args [lrange $args 1 end]
390         }
391
392         set cmdp [_git_cmd [lindex $args 0]]
393         set args [lrange $args 1 end]
394
395         _trace_exec [concat $opt $cmdp $args]
396         set result [eval exec $opt $cmdp $args]
397         if {$::_trace} {
398                 puts stderr "< $result"
399         }
400         return $result
401 }
402
403 proc _open_stdout_stderr {cmd} {
404         _trace_exec $cmd
405         if {[catch {
406                         set fd [open [concat [list | ] $cmd] r]
407                 } err]} {
408                 if {   [lindex $cmd end] eq {2>@1}
409                     && $err eq {can not find channel named "1"}
410                         } {
411                         # Older versions of Tcl 8.4 don't have this 2>@1 IO
412                         # redirect operator.  Fallback to |& cat for those.
413                         # The command was not actually started, so its safe
414                         # to try to start it a second time.
415                         #
416                         set fd [open [concat \
417                                 [list | ] \
418                                 [lrange $cmd 0 end-1] \
419                                 [list |& cat] \
420                                 ] r]
421                 } else {
422                         error $err
423                 }
424         }
425         fconfigure $fd -eofchar {}
426         return $fd
427 }
428
429 proc git_read {args} {
430         set opt [list]
431
432         while {1} {
433                 switch -- [lindex $args 0] {
434                 --nice {
435                         _lappend_nice opt
436                 }
437
438                 --stderr {
439                         lappend args 2>@1
440                 }
441
442                 default {
443                         break
444                 }
445
446                 }
447
448                 set args [lrange $args 1 end]
449         }
450
451         set cmdp [_git_cmd [lindex $args 0]]
452         set args [lrange $args 1 end]
453
454         return [_open_stdout_stderr [concat $opt $cmdp $args]]
455 }
456
457 proc git_write {args} {
458         set opt [list]
459
460         while {1} {
461                 switch -- [lindex $args 0] {
462                 --nice {
463                         _lappend_nice opt
464                 }
465
466                 default {
467                         break
468                 }
469
470                 }
471
472                 set args [lrange $args 1 end]
473         }
474
475         set cmdp [_git_cmd [lindex $args 0]]
476         set args [lrange $args 1 end]
477
478         _trace_exec [concat $opt $cmdp $args]
479         return [open [concat [list | ] $opt $cmdp $args] w]
480 }
481
482 proc githook_read {hook_name args} {
483         set pchook [gitdir hooks $hook_name]
484         lappend args 2>@1
485
486         # On Windows [file executable] might lie so we need to ask
487         # the shell if the hook is executable.  Yes that's annoying.
488         #
489         if {[is_Windows]} {
490                 upvar #0 _sh interp
491                 if {![info exists interp]} {
492                         set interp [_which sh]
493                 }
494                 if {$interp eq {}} {
495                         error "hook execution requires sh (not in PATH)"
496                 }
497
498                 set scr {if test -x "$1";then exec "$@";fi}
499                 set sh_c [list $interp -c $scr $interp $pchook]
500                 return [_open_stdout_stderr [concat $sh_c $args]]
501         }
502
503         if {[file executable $pchook]} {
504                 return [_open_stdout_stderr [concat [list $pchook] $args]]
505         }
506
507         return {}
508 }
509
510 proc kill_file_process {fd} {
511         set process [pid $fd]
512
513         catch {
514                 if {[is_Windows]} {
515                         # Use a Cygwin-specific flag to allow killing
516                         # native Windows processes
517                         exec kill -f $process
518                 } else {
519                         exec kill $process
520                 }
521         }
522 }
523
524 proc gitattr {path attr default} {
525         if {[catch {set r [git check-attr $attr -- $path]}]} {
526                 set r unspecified
527         } else {
528                 set r [join [lrange [split $r :] 2 end] :]
529                 regsub {^ } $r {} r
530         }
531         if {$r eq {unspecified}} {
532                 return $default
533         }
534         return $r
535 }
536
537 proc sq {value} {
538         regsub -all ' $value "'\\''" value
539         return "'$value'"
540 }
541
542 proc load_current_branch {} {
543         global current_branch is_detached
544
545         set fd [open [gitdir HEAD] r]
546         if {[gets $fd ref] < 1} {
547                 set ref {}
548         }
549         close $fd
550
551         set pfx {ref: refs/heads/}
552         set len [string length $pfx]
553         if {[string equal -length $len $pfx $ref]} {
554                 # We're on a branch.  It might not exist.  But
555                 # HEAD looks good enough to be a branch.
556                 #
557                 set current_branch [string range $ref $len end]
558                 set is_detached 0
559         } else {
560                 # Assume this is a detached head.
561                 #
562                 set current_branch HEAD
563                 set is_detached 1
564         }
565 }
566
567 auto_load tk_optionMenu
568 rename tk_optionMenu real__tkOptionMenu
569 proc tk_optionMenu {w varName args} {
570         set m [eval real__tkOptionMenu $w $varName $args]
571         $m configure -font font_ui
572         $w configure -font font_ui
573         return $m
574 }
575
576 proc rmsel_tag {text} {
577         $text tag conf sel \
578                 -background [$text cget -background] \
579                 -foreground [$text cget -foreground] \
580                 -borderwidth 0
581         $text tag conf in_sel -background lightgray
582         bind $text <Motion> break
583         return $text
584 }
585
586 set root_exists 0
587 bind . <Visibility> {
588         bind . <Visibility> {}
589         set root_exists 1
590 }
591
592 if {[is_Windows]} {
593         wm iconbitmap . -default $oguilib/git-gui.ico
594         set ::tk::AlwaysShowSelection 1
595
596         # Spoof an X11 display for SSH
597         if {![info exists env(DISPLAY)]} {
598                 set env(DISPLAY) :9999
599         }
600 } else {
601         catch {
602                 image create photo gitlogo -width 16 -height 16
603
604                 gitlogo put #33CC33 -to  7  0  9  2
605                 gitlogo put #33CC33 -to  4  2 12  4
606                 gitlogo put #33CC33 -to  7  4  9  6
607                 gitlogo put #CC3333 -to  4  6 12  8
608                 gitlogo put gray26  -to  4  9  6 10
609                 gitlogo put gray26  -to  3 10  6 12
610                 gitlogo put gray26  -to  8  9 13 11
611                 gitlogo put gray26  -to  8 11 10 12
612                 gitlogo put gray26  -to 11 11 13 14
613                 gitlogo put gray26  -to  3 12  5 14
614                 gitlogo put gray26  -to  5 13
615                 gitlogo put gray26  -to 10 13
616                 gitlogo put gray26  -to  4 14 12 15
617                 gitlogo put gray26  -to  5 15 11 16
618                 gitlogo redither
619
620                 wm iconphoto . -default gitlogo
621         }
622 }
623
624 ######################################################################
625 ##
626 ## config defaults
627
628 set cursor_ptr arrow
629 font create font_diff -family Courier -size 10
630 font create font_ui
631 catch {
632         label .dummy
633         eval font configure font_ui [font actual [.dummy cget -font]]
634         destroy .dummy
635 }
636
637 font create font_uiitalic
638 font create font_uibold
639 font create font_diffbold
640 font create font_diffitalic
641
642 foreach class {Button Checkbutton Entry Label
643                 Labelframe Listbox Menu Message
644                 Radiobutton Spinbox Text} {
645         option add *$class.font font_ui
646 }
647 unset class
648
649 if {[is_Windows] || [is_MacOSX]} {
650         option add *Menu.tearOff 0
651 }
652
653 if {[is_MacOSX]} {
654         set M1B M1
655         set M1T Cmd
656 } else {
657         set M1B Control
658         set M1T Ctrl
659 }
660
661 proc bind_button3 {w cmd} {
662         bind $w <Any-Button-3> $cmd
663         if {[is_MacOSX]} {
664                 # Mac OS X sends Button-2 on right click through three-button mouse,
665                 # or through trackpad right-clicking (two-finger touch + click).
666                 bind $w <Any-Button-2> $cmd
667                 bind $w <Control-Button-1> $cmd
668         }
669 }
670
671 proc apply_config {} {
672         global repo_config font_descs
673
674         foreach option $font_descs {
675                 set name [lindex $option 0]
676                 set font [lindex $option 1]
677                 if {[catch {
678                         set need_weight 1
679                         foreach {cn cv} $repo_config(gui.$name) {
680                                 if {$cn eq {-weight}} {
681                                         set need_weight 0
682                                 }
683                                 font configure $font $cn $cv
684                         }
685                         if {$need_weight} {
686                                 font configure $font -weight normal
687                         }
688                         } err]} {
689                         error_popup [strcat [mc "Invalid font specified in %s:" "gui.$name"] "\n\n$err"]
690                 }
691                 foreach {cn cv} [font configure $font] {
692                         font configure ${font}bold $cn $cv
693                         font configure ${font}italic $cn $cv
694                 }
695                 font configure ${font}bold -weight bold
696                 font configure ${font}italic -slant italic
697         }
698 }
699
700 set default_config(branch.autosetupmerge) true
701 set default_config(merge.tool) {}
702 set default_config(mergetool.keepbackup) true
703 set default_config(merge.diffstat) true
704 set default_config(merge.summary) false
705 set default_config(merge.verbosity) 2
706 set default_config(user.name) {}
707 set default_config(user.email) {}
708
709 set default_config(gui.encoding) [encoding system]
710 set default_config(gui.matchtrackingbranch) false
711 set default_config(gui.pruneduringfetch) false
712 set default_config(gui.trustmtime) false
713 set default_config(gui.fastcopyblame) false
714 set default_config(gui.copyblamethreshold) 40
715 set default_config(gui.blamehistoryctx) 7
716 set default_config(gui.diffcontext) 5
717 set default_config(gui.commitmsgwidth) 75
718 set default_config(gui.newbranchtemplate) {}
719 set default_config(gui.spellingdictionary) {}
720 set default_config(gui.fontui) [font configure font_ui]
721 set default_config(gui.fontdiff) [font configure font_diff]
722 set font_descs {
723         {fontui   font_ui   {mc "Main Font"}}
724         {fontdiff font_diff {mc "Diff/Console Font"}}
725 }
726
727 ######################################################################
728 ##
729 ## find git
730
731 set _git  [_which git]
732 if {$_git eq {}} {
733         catch {wm withdraw .}
734         tk_messageBox \
735                 -icon error \
736                 -type ok \
737                 -title [mc "git-gui: fatal error"] \
738                 -message [mc "Cannot find git in PATH."]
739         exit 1
740 }
741
742 ######################################################################
743 ##
744 ## version check
745
746 if {[catch {set _git_version [git --version]} err]} {
747         catch {wm withdraw .}
748         tk_messageBox \
749                 -icon error \
750                 -type ok \
751                 -title [mc "git-gui: fatal error"] \
752                 -message "Cannot determine Git version:
753
754 $err
755
756 [appname] requires Git 1.5.0 or later."
757         exit 1
758 }
759 if {![regsub {^git version } $_git_version {} _git_version]} {
760         catch {wm withdraw .}
761         tk_messageBox \
762                 -icon error \
763                 -type ok \
764                 -title [mc "git-gui: fatal error"] \
765                 -message [strcat [mc "Cannot parse Git version string:"] "\n\n$_git_version"]
766         exit 1
767 }
768
769 set _real_git_version $_git_version
770 regsub -- {[\-\.]dirty$} $_git_version {} _git_version
771 regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version
772 regsub {\.[a-zA-Z]+\.?[0-9]+$} $_git_version {} _git_version
773 regsub {\.GIT$} $_git_version {} _git_version
774 regsub {\.[a-zA-Z]+\.?[0-9]+$} $_git_version {} _git_version
775
776 if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} {
777         catch {wm withdraw .}
778         if {[tk_messageBox \
779                 -icon warning \
780                 -type yesno \
781                 -default no \
782                 -title "[appname]: warning" \
783                  -message [mc "Git version cannot be determined.
784
785 %s claims it is version '%s'.
786
787 %s requires at least Git 1.5.0 or later.
788
789 Assume '%s' is version 1.5.0?
790 " $_git $_real_git_version [appname] $_real_git_version]] eq {yes}} {
791                 set _git_version 1.5.0
792         } else {
793                 exit 1
794         }
795 }
796 unset _real_git_version
797
798 proc git-version {args} {
799         global _git_version
800
801         switch [llength $args] {
802         0 {
803                 return $_git_version
804         }
805
806         2 {
807                 set op [lindex $args 0]
808                 set vr [lindex $args 1]
809                 set cm [package vcompare $_git_version $vr]
810                 return [expr $cm $op 0]
811         }
812
813         4 {
814                 set type [lindex $args 0]
815                 set name [lindex $args 1]
816                 set parm [lindex $args 2]
817                 set body [lindex $args 3]
818
819                 if {($type ne {proc} && $type ne {method})} {
820                         error "Invalid arguments to git-version"
821                 }
822                 if {[llength $body] < 2 || [lindex $body end-1] ne {default}} {
823                         error "Last arm of $type $name must be default"
824                 }
825
826                 foreach {op vr cb} [lrange $body 0 end-2] {
827                         if {[git-version $op $vr]} {
828                                 return [uplevel [list $type $name $parm $cb]]
829                         }
830                 }
831
832                 return [uplevel [list $type $name $parm [lindex $body end]]]
833         }
834
835         default {
836                 error "git-version >= x"
837         }
838
839         }
840 }
841
842 if {[git-version < 1.5]} {
843         catch {wm withdraw .}
844         tk_messageBox \
845                 -icon error \
846                 -type ok \
847                 -title [mc "git-gui: fatal error"] \
848                 -message "[appname] requires Git 1.5.0 or later.
849
850 You are using [git-version]:
851
852 [git --version]"
853         exit 1
854 }
855
856 ######################################################################
857 ##
858 ## configure our library
859
860 set idx [file join $oguilib tclIndex]
861 if {[catch {set fd [open $idx r]} err]} {
862         catch {wm withdraw .}
863         tk_messageBox \
864                 -icon error \
865                 -type ok \
866                 -title [mc "git-gui: fatal error"] \
867                 -message $err
868         exit 1
869 }
870 if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
871         set idx [list]
872         while {[gets $fd n] >= 0} {
873                 if {$n ne {} && ![string match #* $n]} {
874                         lappend idx $n
875                 }
876         }
877 } else {
878         set idx {}
879 }
880 close $fd
881
882 if {$idx ne {}} {
883         set loaded [list]
884         foreach p $idx {
885                 if {[lsearch -exact $loaded $p] >= 0} continue
886                 source [file join $oguilib $p]
887                 lappend loaded $p
888         }
889         unset loaded p
890 } else {
891         set auto_path [concat [list $oguilib] $auto_path]
892 }
893 unset -nocomplain idx fd
894
895 ######################################################################
896 ##
897 ## config file parsing
898
899 git-version proc _parse_config {arr_name args} {
900         >= 1.5.3 {
901                 upvar $arr_name arr
902                 array unset arr
903                 set buf {}
904                 catch {
905                         set fd_rc [eval \
906                                 [list git_read config] \
907                                 $args \
908                                 [list --null --list]]
909                         fconfigure $fd_rc -translation binary
910                         set buf [read $fd_rc]
911                         close $fd_rc
912                 }
913                 foreach line [split $buf "\0"] {
914                         if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} {
915                                 if {[is_many_config $name]} {
916                                         lappend arr($name) $value
917                                 } else {
918                                         set arr($name) $value
919                                 }
920                         }
921                 }
922         }
923         default {
924                 upvar $arr_name arr
925                 array unset arr
926                 catch {
927                         set fd_rc [eval [list git_read config --list] $args]
928                         while {[gets $fd_rc line] >= 0} {
929                                 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
930                                         if {[is_many_config $name]} {
931                                                 lappend arr($name) $value
932                                         } else {
933                                                 set arr($name) $value
934                                         }
935                                 }
936                         }
937                         close $fd_rc
938                 }
939         }
940 }
941
942 proc load_config {include_global} {
943         global repo_config global_config system_config default_config
944
945         if {$include_global} {
946                 _parse_config system_config --system
947                 _parse_config global_config --global
948         }
949         _parse_config repo_config
950
951         foreach name [array names default_config] {
952                 if {[catch {set v $system_config($name)}]} {
953                         set system_config($name) $default_config($name)
954                 }
955         }
956         foreach name [array names system_config] {
957                 if {[catch {set v $global_config($name)}]} {
958                         set global_config($name) $system_config($name)
959                 }
960                 if {[catch {set v $repo_config($name)}]} {
961                         set repo_config($name) $system_config($name)
962                 }
963         }
964 }
965
966 ######################################################################
967 ##
968 ## feature option selection
969
970 if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} {
971         unset _junk
972 } else {
973         set subcommand gui
974 }
975 if {$subcommand eq {gui.sh}} {
976         set subcommand gui
977 }
978 if {$subcommand eq {gui} && [llength $argv] > 0} {
979         set subcommand [lindex $argv 0]
980         set argv [lrange $argv 1 end]
981 }
982
983 enable_option multicommit
984 enable_option branch
985 enable_option transport
986 disable_option bare
987
988 switch -- $subcommand {
989 browser -
990 blame {
991         enable_option bare
992
993         disable_option multicommit
994         disable_option branch
995         disable_option transport
996 }
997 citool {
998         enable_option singlecommit
999         enable_option retcode
1000
1001         disable_option multicommit
1002         disable_option branch
1003         disable_option transport
1004
1005         while {[llength $argv] > 0} {
1006                 set a [lindex $argv 0]
1007                 switch -- $a {
1008                 --amend {
1009                         enable_option initialamend
1010                 }
1011                 --nocommit {
1012                         enable_option nocommit
1013                         enable_option nocommitmsg
1014                 }
1015                 --commitmsg {
1016                         disable_option nocommitmsg
1017                 }
1018                 default {
1019                         break
1020                 }
1021                 }
1022
1023                 set argv [lrange $argv 1 end]
1024         }
1025 }
1026 }
1027
1028 ######################################################################
1029 ##
1030 ## execution environment
1031
1032 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
1033
1034 # Suggest our implementation of askpass, if none is set
1035 if {![info exists env(SSH_ASKPASS)]} {
1036         set env(SSH_ASKPASS) [gitexec git-gui--askpass]
1037 }
1038
1039 ######################################################################
1040 ##
1041 ## repository setup
1042
1043 set picked 0
1044 if {[catch {
1045                 set _gitdir $env(GIT_DIR)
1046                 set _prefix {}
1047                 }]
1048         && [catch {
1049                 set _gitdir [git rev-parse --git-dir]
1050                 set _prefix [git rev-parse --show-prefix]
1051         } err]} {
1052         load_config 1
1053         apply_config
1054         choose_repository::pick
1055         set picked 1
1056 }
1057 if {![file isdirectory $_gitdir] && [is_Cygwin]} {
1058         catch {set _gitdir [exec cygpath --windows $_gitdir]}
1059 }
1060 if {![file isdirectory $_gitdir]} {
1061         catch {wm withdraw .}
1062         error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"]
1063         exit 1
1064 }
1065 if {$_prefix ne {}} {
1066         regsub -all {[^/]+/} $_prefix ../ cdup
1067         if {[catch {cd $cdup} err]} {
1068                 catch {wm withdraw .}
1069                 error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"]
1070                 exit 1
1071         }
1072         unset cdup
1073 } elseif {![is_enabled bare]} {
1074         if {[lindex [file split $_gitdir] end] ne {.git}} {
1075                 catch {wm withdraw .}
1076                 error_popup [strcat [mc "Cannot use funny .git directory:"] "\n\n$_gitdir"]
1077                 exit 1
1078         }
1079         if {[catch {cd [file dirname $_gitdir]} err]} {
1080                 catch {wm withdraw .}
1081                 error_popup [strcat [mc "No working directory"] " [file dirname $_gitdir]:\n\n$err"]
1082                 exit 1
1083         }
1084 }
1085 set _reponame [file split [file normalize $_gitdir]]
1086 if {[lindex $_reponame end] eq {.git}} {
1087         set _reponame [lindex $_reponame end-1]
1088 } else {
1089         set _reponame [lindex $_reponame end]
1090 }
1091
1092 ######################################################################
1093 ##
1094 ## global init
1095
1096 set current_diff_path {}
1097 set current_diff_side {}
1098 set diff_actions [list]
1099
1100 set HEAD {}
1101 set PARENT {}
1102 set MERGE_HEAD [list]
1103 set commit_type {}
1104 set empty_tree {}
1105 set current_branch {}
1106 set is_detached 0
1107 set current_diff_path {}
1108 set is_3way_diff 0
1109 set is_conflict_diff 0
1110 set selected_commit_type new
1111 set diff_empty_count 0
1112
1113 set nullid "0000000000000000000000000000000000000000"
1114 set nullid2 "0000000000000000000000000000000000000001"
1115
1116 ######################################################################
1117 ##
1118 ## task management
1119
1120 set rescan_active 0
1121 set diff_active 0
1122 set last_clicked {}
1123
1124 set disable_on_lock [list]
1125 set index_lock_type none
1126
1127 proc lock_index {type} {
1128         global index_lock_type disable_on_lock
1129
1130         if {$index_lock_type eq {none}} {
1131                 set index_lock_type $type
1132                 foreach w $disable_on_lock {
1133                         uplevel #0 $w disabled
1134                 }
1135                 return 1
1136         } elseif {$index_lock_type eq "begin-$type"} {
1137                 set index_lock_type $type
1138                 return 1
1139         }
1140         return 0
1141 }
1142
1143 proc unlock_index {} {
1144         global index_lock_type disable_on_lock
1145
1146         set index_lock_type none
1147         foreach w $disable_on_lock {
1148                 uplevel #0 $w normal
1149         }
1150 }
1151
1152 ######################################################################
1153 ##
1154 ## status
1155
1156 proc repository_state {ctvar hdvar mhvar} {
1157         global current_branch
1158         upvar $ctvar ct $hdvar hd $mhvar mh
1159
1160         set mh [list]
1161
1162         load_current_branch
1163         if {[catch {set hd [git rev-parse --verify HEAD]}]} {
1164                 set hd {}
1165                 set ct initial
1166                 return
1167         }
1168
1169         set merge_head [gitdir MERGE_HEAD]
1170         if {[file exists $merge_head]} {
1171                 set ct merge
1172                 set fd_mh [open $merge_head r]
1173                 while {[gets $fd_mh line] >= 0} {
1174                         lappend mh $line
1175                 }
1176                 close $fd_mh
1177                 return
1178         }
1179
1180         set ct normal
1181 }
1182
1183 proc PARENT {} {
1184         global PARENT empty_tree
1185
1186         set p [lindex $PARENT 0]
1187         if {$p ne {}} {
1188                 return $p
1189         }
1190         if {$empty_tree eq {}} {
1191                 set empty_tree [git mktree << {}]
1192         }
1193         return $empty_tree
1194 }
1195
1196 proc force_amend {} {
1197         global selected_commit_type
1198         global HEAD PARENT MERGE_HEAD commit_type
1199
1200         repository_state newType newHEAD newMERGE_HEAD
1201         set HEAD $newHEAD
1202         set PARENT $newHEAD
1203         set MERGE_HEAD $newMERGE_HEAD
1204         set commit_type $newType
1205
1206         set selected_commit_type amend
1207         do_select_commit_type
1208 }
1209
1210 proc rescan {after {honor_trustmtime 1}} {
1211         global HEAD PARENT MERGE_HEAD commit_type
1212         global ui_index ui_workdir ui_comm
1213         global rescan_active file_states
1214         global repo_config
1215
1216         if {$rescan_active > 0 || ![lock_index read]} return
1217
1218         repository_state newType newHEAD newMERGE_HEAD
1219         if {[string match amend* $commit_type]
1220                 && $newType eq {normal}
1221                 && $newHEAD eq $HEAD} {
1222         } else {
1223                 set HEAD $newHEAD
1224                 set PARENT $newHEAD
1225                 set MERGE_HEAD $newMERGE_HEAD
1226                 set commit_type $newType
1227         }
1228
1229         array unset file_states
1230
1231         if {!$::GITGUI_BCK_exists &&
1232                 (![$ui_comm edit modified]
1233                 || [string trim [$ui_comm get 0.0 end]] eq {})} {
1234                 if {[string match amend* $commit_type]} {
1235                 } elseif {[load_message GITGUI_MSG]} {
1236                 } elseif {[run_prepare_commit_msg_hook]} {
1237                 } elseif {[load_message MERGE_MSG]} {
1238                 } elseif {[load_message SQUASH_MSG]} {
1239                 }
1240                 $ui_comm edit reset
1241                 $ui_comm edit modified false
1242         }
1243
1244         if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
1245                 rescan_stage2 {} $after
1246         } else {
1247                 set rescan_active 1
1248                 ui_status [mc "Refreshing file status..."]
1249                 set fd_rf [git_read update-index \
1250                         -q \
1251                         --unmerged \
1252                         --ignore-missing \
1253                         --refresh \
1254                         ]
1255                 fconfigure $fd_rf -blocking 0 -translation binary
1256                 fileevent $fd_rf readable \
1257                         [list rescan_stage2 $fd_rf $after]
1258         }
1259 }
1260
1261 if {[is_Cygwin]} {
1262         set is_git_info_exclude {}
1263         proc have_info_exclude {} {
1264                 global is_git_info_exclude
1265
1266                 if {$is_git_info_exclude eq {}} {
1267                         if {[catch {exec test -f [gitdir info exclude]}]} {
1268                                 set is_git_info_exclude 0
1269                         } else {
1270                                 set is_git_info_exclude 1
1271                         }
1272                 }
1273                 return $is_git_info_exclude
1274         }
1275 } else {
1276         proc have_info_exclude {} {
1277                 return [file readable [gitdir info exclude]]
1278         }
1279 }
1280
1281 proc rescan_stage2 {fd after} {
1282         global rescan_active buf_rdi buf_rdf buf_rlo
1283
1284         if {$fd ne {}} {
1285                 read $fd
1286                 if {![eof $fd]} return
1287                 close $fd
1288         }
1289
1290         set ls_others [list --exclude-per-directory=.gitignore]
1291         if {[have_info_exclude]} {
1292                 lappend ls_others "--exclude-from=[gitdir info exclude]"
1293         }
1294         set user_exclude [get_config core.excludesfile]
1295         if {$user_exclude ne {} && [file readable $user_exclude]} {
1296                 lappend ls_others "--exclude-from=$user_exclude"
1297         }
1298
1299         set buf_rdi {}
1300         set buf_rdf {}
1301         set buf_rlo {}
1302
1303         set rescan_active 3
1304         ui_status [mc "Scanning for modified files ..."]
1305         set fd_di [git_read diff-index --cached -z [PARENT]]
1306         set fd_df [git_read diff-files -z]
1307         set fd_lo [eval git_read ls-files --others -z $ls_others]
1308
1309         fconfigure $fd_di -blocking 0 -translation binary -encoding binary
1310         fconfigure $fd_df -blocking 0 -translation binary -encoding binary
1311         fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
1312         fileevent $fd_di readable [list read_diff_index $fd_di $after]
1313         fileevent $fd_df readable [list read_diff_files $fd_df $after]
1314         fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
1315 }
1316
1317 proc load_message {file} {
1318         global ui_comm
1319
1320         set f [gitdir $file]
1321         if {[file isfile $f]} {
1322                 if {[catch {set fd [open $f r]}]} {
1323                         return 0
1324                 }
1325                 fconfigure $fd -eofchar {}
1326                 set content [string trim [read $fd]]
1327                 close $fd
1328                 regsub -all -line {[ \r\t]+$} $content {} content
1329                 $ui_comm delete 0.0 end
1330                 $ui_comm insert end $content
1331                 return 1
1332         }
1333         return 0
1334 }
1335
1336 proc run_prepare_commit_msg_hook {} {
1337         global pch_error
1338
1339         # prepare-commit-msg requires PREPARE_COMMIT_MSG exist.  From git-gui
1340         # it will be .git/MERGE_MSG (merge), .git/SQUASH_MSG (squash), or an
1341         # empty file but existant file.
1342
1343         set fd_pcm [open [gitdir PREPARE_COMMIT_MSG] a]
1344
1345         if {[file isfile [gitdir MERGE_MSG]]} {
1346                 set pcm_source "merge"
1347                 set fd_mm [open [gitdir MERGE_MSG] r]
1348                 puts -nonewline $fd_pcm [read $fd_mm]
1349                 close $fd_mm
1350         } elseif {[file isfile [gitdir SQUASH_MSG]]} {
1351                 set pcm_source "squash"
1352                 set fd_sm [open [gitdir SQUASH_MSG] r]
1353                 puts -nonewline $fd_pcm [read $fd_sm]
1354                 close $fd_sm
1355         } else {
1356                 set pcm_source ""
1357         }
1358
1359         close $fd_pcm
1360
1361         set fd_ph [githook_read prepare-commit-msg \
1362                         [gitdir PREPARE_COMMIT_MSG] $pcm_source]
1363         if {$fd_ph eq {}} {
1364                 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1365                 return 0;
1366         }
1367
1368         ui_status [mc "Calling prepare-commit-msg hook..."]
1369         set pch_error {}
1370
1371         fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
1372         fileevent $fd_ph readable \
1373                 [list prepare_commit_msg_hook_wait $fd_ph]
1374
1375         return 1;
1376 }
1377
1378 proc prepare_commit_msg_hook_wait {fd_ph} {
1379         global pch_error
1380
1381         append pch_error [read $fd_ph]
1382         fconfigure $fd_ph -blocking 1
1383         if {[eof $fd_ph]} {
1384                 if {[catch {close $fd_ph}]} {
1385                         ui_status [mc "Commit declined by prepare-commit-msg hook."]
1386                         hook_failed_popup prepare-commit-msg $pch_error
1387                         catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1388                         exit 1
1389                 } else {
1390                         load_message PREPARE_COMMIT_MSG
1391                 }
1392                 set pch_error {}
1393                 catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1394                 return
1395         }
1396         fconfigure $fd_ph -blocking 0
1397         catch {file delete [gitdir PREPARE_COMMIT_MSG]}
1398 }
1399
1400 proc read_diff_index {fd after} {
1401         global buf_rdi
1402
1403         append buf_rdi [read $fd]
1404         set c 0
1405         set n [string length $buf_rdi]
1406         while {$c < $n} {
1407                 set z1 [string first "\0" $buf_rdi $c]
1408                 if {$z1 == -1} break
1409                 incr z1
1410                 set z2 [string first "\0" $buf_rdi $z1]
1411                 if {$z2 == -1} break
1412
1413                 incr c
1414                 set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
1415                 set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
1416                 merge_state \
1417                         [encoding convertfrom $p] \
1418                         [lindex $i 4]? \
1419                         [list [lindex $i 0] [lindex $i 2]] \
1420                         [list]
1421                 set c $z2
1422                 incr c
1423         }
1424         if {$c < $n} {
1425                 set buf_rdi [string range $buf_rdi $c end]
1426         } else {
1427                 set buf_rdi {}
1428         }
1429
1430         rescan_done $fd buf_rdi $after
1431 }
1432
1433 proc read_diff_files {fd after} {
1434         global buf_rdf
1435
1436         append buf_rdf [read $fd]
1437         set c 0
1438         set n [string length $buf_rdf]
1439         while {$c < $n} {
1440                 set z1 [string first "\0" $buf_rdf $c]
1441                 if {$z1 == -1} break
1442                 incr z1
1443                 set z2 [string first "\0" $buf_rdf $z1]
1444                 if {$z2 == -1} break
1445
1446                 incr c
1447                 set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
1448                 set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
1449                 merge_state \
1450                         [encoding convertfrom $p] \
1451                         ?[lindex $i 4] \
1452                         [list] \
1453                         [list [lindex $i 0] [lindex $i 2]]
1454                 set c $z2
1455                 incr c
1456         }
1457         if {$c < $n} {
1458                 set buf_rdf [string range $buf_rdf $c end]
1459         } else {
1460                 set buf_rdf {}
1461         }
1462
1463         rescan_done $fd buf_rdf $after
1464 }
1465
1466 proc read_ls_others {fd after} {
1467         global buf_rlo
1468
1469         append buf_rlo [read $fd]
1470         set pck [split $buf_rlo "\0"]
1471         set buf_rlo [lindex $pck end]
1472         foreach p [lrange $pck 0 end-1] {
1473                 set p [encoding convertfrom $p]
1474                 if {[string index $p end] eq {/}} {
1475                         set p [string range $p 0 end-1]
1476                 }
1477                 merge_state $p ?O
1478         }
1479         rescan_done $fd buf_rlo $after
1480 }
1481
1482 proc rescan_done {fd buf after} {
1483         global rescan_active current_diff_path
1484         global file_states repo_config
1485         upvar $buf to_clear
1486
1487         if {![eof $fd]} return
1488         set to_clear {}
1489         close $fd
1490         if {[incr rescan_active -1] > 0} return
1491
1492         prune_selection
1493         unlock_index
1494         display_all_files
1495         if {$current_diff_path ne {}} { reshow_diff $after }
1496         if {$current_diff_path eq {}} { select_first_diff $after }
1497 }
1498
1499 proc prune_selection {} {
1500         global file_states selected_paths
1501
1502         foreach path [array names selected_paths] {
1503                 if {[catch {set still_here $file_states($path)}]} {
1504                         unset selected_paths($path)
1505                 }
1506         }
1507 }
1508
1509 ######################################################################
1510 ##
1511 ## ui helpers
1512
1513 proc mapicon {w state path} {
1514         global all_icons
1515
1516         if {[catch {set r $all_icons($state$w)}]} {
1517                 puts "error: no icon for $w state={$state} $path"
1518                 return file_plain
1519         }
1520         return $r
1521 }
1522
1523 proc mapdesc {state path} {
1524         global all_descs
1525
1526         if {[catch {set r $all_descs($state)}]} {
1527                 puts "error: no desc for state={$state} $path"
1528                 return $state
1529         }
1530         return $r
1531 }
1532
1533 proc ui_status {msg} {
1534         global main_status
1535         if {[info exists main_status]} {
1536                 $main_status show $msg
1537         }
1538 }
1539
1540 proc ui_ready {{test {}}} {
1541         global main_status
1542         if {[info exists main_status]} {
1543                 $main_status show [mc "Ready."] $test
1544         }
1545 }
1546
1547 proc escape_path {path} {
1548         regsub -all {\\} $path "\\\\" path
1549         regsub -all "\n" $path "\\n" path
1550         return $path
1551 }
1552
1553 proc short_path {path} {
1554         return [escape_path [lindex [file split $path] end]]
1555 }
1556
1557 set next_icon_id 0
1558 set null_sha1 [string repeat 0 40]
1559
1560 proc merge_state {path new_state {head_info {}} {index_info {}}} {
1561         global file_states next_icon_id null_sha1
1562
1563         set s0 [string index $new_state 0]
1564         set s1 [string index $new_state 1]
1565
1566         if {[catch {set info $file_states($path)}]} {
1567                 set state __
1568                 set icon n[incr next_icon_id]
1569         } else {
1570                 set state [lindex $info 0]
1571                 set icon [lindex $info 1]
1572                 if {$head_info eq {}}  {set head_info  [lindex $info 2]}
1573                 if {$index_info eq {}} {set index_info [lindex $info 3]}
1574         }
1575
1576         if     {$s0 eq {?}} {set s0 [string index $state 0]} \
1577         elseif {$s0 eq {_}} {set s0 _}
1578
1579         if     {$s1 eq {?}} {set s1 [string index $state 1]} \
1580         elseif {$s1 eq {_}} {set s1 _}
1581
1582         if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
1583                 set head_info [list 0 $null_sha1]
1584         } elseif {$s0 ne {_} && [string index $state 0] eq {_}
1585                 && $head_info eq {}} {
1586                 set head_info $index_info
1587         }
1588
1589         set file_states($path) [list $s0$s1 $icon \
1590                 $head_info $index_info \
1591                 ]
1592         return $state
1593 }
1594
1595 proc display_file_helper {w path icon_name old_m new_m} {
1596         global file_lists
1597
1598         if {$new_m eq {_}} {
1599                 set lno [lsearch -sorted -exact $file_lists($w) $path]
1600                 if {$lno >= 0} {
1601                         set file_lists($w) [lreplace $file_lists($w) $lno $lno]
1602                         incr lno
1603                         $w conf -state normal
1604                         $w delete $lno.0 [expr {$lno + 1}].0
1605                         $w conf -state disabled
1606                 }
1607         } elseif {$old_m eq {_} && $new_m ne {_}} {
1608                 lappend file_lists($w) $path
1609                 set file_lists($w) [lsort -unique $file_lists($w)]
1610                 set lno [lsearch -sorted -exact $file_lists($w) $path]
1611                 incr lno
1612                 $w conf -state normal
1613                 $w image create $lno.0 \
1614                         -align center -padx 5 -pady 1 \
1615                         -name $icon_name \
1616                         -image [mapicon $w $new_m $path]
1617                 $w insert $lno.1 "[escape_path $path]\n"
1618                 $w conf -state disabled
1619         } elseif {$old_m ne $new_m} {
1620                 $w conf -state normal
1621                 $w image conf $icon_name -image [mapicon $w $new_m $path]
1622                 $w conf -state disabled
1623         }
1624 }
1625
1626 proc display_file {path state} {
1627         global file_states selected_paths
1628         global ui_index ui_workdir
1629
1630         set old_m [merge_state $path $state]
1631         set s $file_states($path)
1632         set new_m [lindex $s 0]
1633         set icon_name [lindex $s 1]
1634
1635         set o [string index $old_m 0]
1636         set n [string index $new_m 0]
1637         if {$o eq {U}} {
1638                 set o _
1639         }
1640         if {$n eq {U}} {
1641                 set n _
1642         }
1643         display_file_helper     $ui_index $path $icon_name $o $n
1644
1645         if {[string index $old_m 0] eq {U}} {
1646                 set o U
1647         } else {
1648                 set o [string index $old_m 1]
1649         }
1650         if {[string index $new_m 0] eq {U}} {
1651                 set n U
1652         } else {
1653                 set n [string index $new_m 1]
1654         }
1655         display_file_helper     $ui_workdir $path $icon_name $o $n
1656
1657         if {$new_m eq {__}} {
1658                 unset file_states($path)
1659                 catch {unset selected_paths($path)}
1660         }
1661 }
1662
1663 proc display_all_files_helper {w path icon_name m} {
1664         global file_lists
1665
1666         lappend file_lists($w) $path
1667         set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
1668         $w image create end \
1669                 -align center -padx 5 -pady 1 \
1670                 -name $icon_name \
1671                 -image [mapicon $w $m $path]
1672         $w insert end "[escape_path $path]\n"
1673 }
1674
1675 proc display_all_files {} {
1676         global ui_index ui_workdir
1677         global file_states file_lists
1678         global last_clicked
1679
1680         $ui_index conf -state normal
1681         $ui_workdir conf -state normal
1682
1683         $ui_index delete 0.0 end
1684         $ui_workdir delete 0.0 end
1685         set last_clicked {}
1686
1687         set file_lists($ui_index) [list]
1688         set file_lists($ui_workdir) [list]
1689
1690         foreach path [lsort [array names file_states]] {
1691                 set s $file_states($path)
1692                 set m [lindex $s 0]
1693                 set icon_name [lindex $s 1]
1694
1695                 set s [string index $m 0]
1696                 if {$s ne {U} && $s ne {_}} {
1697                         display_all_files_helper $ui_index $path \
1698                                 $icon_name $s
1699                 }
1700
1701                 if {[string index $m 0] eq {U}} {
1702                         set s U
1703                 } else {
1704                         set s [string index $m 1]
1705                 }
1706                 if {$s ne {_}} {
1707                         display_all_files_helper $ui_workdir $path \
1708                                 $icon_name $s
1709                 }
1710         }
1711
1712         $ui_index conf -state disabled
1713         $ui_workdir conf -state disabled
1714 }
1715
1716 ######################################################################
1717 ##
1718 ## icons
1719
1720 set filemask {
1721 #define mask_width 14
1722 #define mask_height 15
1723 static unsigned char mask_bits[] = {
1724    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1725    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1726    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1727 }
1728
1729 image create bitmap file_plain -background white -foreground black -data {
1730 #define plain_width 14
1731 #define plain_height 15
1732 static unsigned char plain_bits[] = {
1733    0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1734    0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1735    0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1736 } -maskdata $filemask
1737
1738 image create bitmap file_mod -background white -foreground blue -data {
1739 #define mod_width 14
1740 #define mod_height 15
1741 static unsigned char mod_bits[] = {
1742    0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1743    0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1744    0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1745 } -maskdata $filemask
1746
1747 image create bitmap file_fulltick -background white -foreground "#007000" -data {
1748 #define file_fulltick_width 14
1749 #define file_fulltick_height 15
1750 static unsigned char file_fulltick_bits[] = {
1751    0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1752    0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1753    0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1754 } -maskdata $filemask
1755
1756 image create bitmap file_parttick -background white -foreground "#005050" -data {
1757 #define parttick_width 14
1758 #define parttick_height 15
1759 static unsigned char parttick_bits[] = {
1760    0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1761    0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1762    0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1763 } -maskdata $filemask
1764
1765 image create bitmap file_question -background white -foreground black -data {
1766 #define file_question_width 14
1767 #define file_question_height 15
1768 static unsigned char file_question_bits[] = {
1769    0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1770    0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1771    0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1772 } -maskdata $filemask
1773
1774 image create bitmap file_removed -background white -foreground red -data {
1775 #define file_removed_width 14
1776 #define file_removed_height 15
1777 static unsigned char file_removed_bits[] = {
1778    0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1779    0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1780    0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1781 } -maskdata $filemask
1782
1783 image create bitmap file_merge -background white -foreground blue -data {
1784 #define file_merge_width 14
1785 #define file_merge_height 15
1786 static unsigned char file_merge_bits[] = {
1787    0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1788    0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1789    0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1790 } -maskdata $filemask
1791
1792 image create bitmap file_statechange -background white -foreground green -data {
1793 #define file_merge_width 14
1794 #define file_merge_height 15
1795 static unsigned char file_statechange_bits[] = {
1796    0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x62, 0x10,
1797    0x62, 0x10, 0xba, 0x11, 0xba, 0x11, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10,
1798    0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1799 } -maskdata $filemask
1800
1801 set ui_index .vpane.files.index.list
1802 set ui_workdir .vpane.files.workdir.list
1803
1804 set all_icons(_$ui_index)   file_plain
1805 set all_icons(A$ui_index)   file_fulltick
1806 set all_icons(M$ui_index)   file_fulltick
1807 set all_icons(D$ui_index)   file_removed
1808 set all_icons(U$ui_index)   file_merge
1809 set all_icons(T$ui_index)   file_statechange
1810
1811 set all_icons(_$ui_workdir) file_plain
1812 set all_icons(M$ui_workdir) file_mod
1813 set all_icons(D$ui_workdir) file_question
1814 set all_icons(U$ui_workdir) file_merge
1815 set all_icons(O$ui_workdir) file_plain
1816 set all_icons(T$ui_workdir) file_statechange
1817
1818 set max_status_desc 0
1819 foreach i {
1820                 {__ {mc "Unmodified"}}
1821
1822                 {_M {mc "Modified, not staged"}}
1823                 {M_ {mc "Staged for commit"}}
1824                 {MM {mc "Portions staged for commit"}}
1825                 {MD {mc "Staged for commit, missing"}}
1826
1827                 {_T {mc "File type changed, not staged"}}
1828                 {T_ {mc "File type changed, staged"}}
1829
1830                 {_O {mc "Untracked, not staged"}}
1831                 {A_ {mc "Staged for commit"}}
1832                 {AM {mc "Portions staged for commit"}}
1833                 {AD {mc "Staged for commit, missing"}}
1834
1835                 {_D {mc "Missing"}}
1836                 {D_ {mc "Staged for removal"}}
1837                 {DO {mc "Staged for removal, still present"}}
1838
1839                 {_U {mc "Requires merge resolution"}}
1840                 {U_ {mc "Requires merge resolution"}}
1841                 {UU {mc "Requires merge resolution"}}
1842                 {UM {mc "Requires merge resolution"}}
1843                 {UD {mc "Requires merge resolution"}}
1844                 {UT {mc "Requires merge resolution"}}
1845         } {
1846         set text [eval [lindex $i 1]]
1847         if {$max_status_desc < [string length $text]} {
1848                 set max_status_desc [string length $text]
1849         }
1850         set all_descs([lindex $i 0]) $text
1851 }
1852 unset i
1853
1854 ######################################################################
1855 ##
1856 ## util
1857
1858 proc scrollbar2many {list mode args} {
1859         foreach w $list {eval $w $mode $args}
1860 }
1861
1862 proc many2scrollbar {list mode sb top bottom} {
1863         $sb set $top $bottom
1864         foreach w $list {$w $mode moveto $top}
1865 }
1866
1867 proc incr_font_size {font {amt 1}} {
1868         set sz [font configure $font -size]
1869         incr sz $amt
1870         font configure $font -size $sz
1871         font configure ${font}bold -size $sz
1872         font configure ${font}italic -size $sz
1873 }
1874
1875 ######################################################################
1876 ##
1877 ## ui commands
1878
1879 set starting_gitk_msg [mc "Starting gitk... please wait..."]
1880
1881 proc do_gitk {revs} {
1882         # -- Always start gitk through whatever we were loaded with.  This
1883         #    lets us bypass using shell process on Windows systems.
1884         #
1885         set exe [_which gitk -script]
1886         set cmd [list [info nameofexecutable] $exe]
1887         if {$exe eq {}} {
1888                 error_popup [mc "Couldn't find gitk in PATH"]
1889         } else {
1890                 global env
1891
1892                 if {[info exists env(GIT_DIR)]} {
1893                         set old_GIT_DIR $env(GIT_DIR)
1894                 } else {
1895                         set old_GIT_DIR {}
1896                 }
1897
1898                 set pwd [pwd]
1899                 cd [file dirname [gitdir]]
1900                 set env(GIT_DIR) [file tail [gitdir]]
1901
1902                 eval exec $cmd $revs &
1903
1904                 if {$old_GIT_DIR eq {}} {
1905                         unset env(GIT_DIR)
1906                 } else {
1907                         set env(GIT_DIR) $old_GIT_DIR
1908                 }
1909                 cd $pwd
1910
1911                 ui_status $::starting_gitk_msg
1912                 after 10000 {
1913                         ui_ready $starting_gitk_msg
1914                 }
1915         }
1916 }
1917
1918 proc do_explore {} {
1919         set explorer {}
1920         if {[is_Cygwin] || [is_Windows]} {
1921                 set explorer "explorer.exe"
1922         } elseif {[is_MacOSX]} {
1923                 set explorer "open"
1924         } else {
1925                 # freedesktop.org-conforming system is our best shot
1926                 set explorer "xdg-open"
1927         }
1928         eval exec $explorer [file dirname [gitdir]] &
1929 }
1930
1931 set is_quitting 0
1932 set ret_code    1
1933
1934 proc terminate_me {win} {
1935         global ret_code
1936         if {$win ne {.}} return
1937         exit $ret_code
1938 }
1939
1940 proc do_quit {{rc {1}}} {
1941         global ui_comm is_quitting repo_config commit_type
1942         global GITGUI_BCK_exists GITGUI_BCK_i
1943         global ui_comm_spell
1944         global ret_code
1945
1946         if {$is_quitting} return
1947         set is_quitting 1
1948
1949         if {[winfo exists $ui_comm]} {
1950                 # -- Stash our current commit buffer.
1951                 #
1952                 set save [gitdir GITGUI_MSG]
1953                 if {$GITGUI_BCK_exists && ![$ui_comm edit modified]} {
1954                         file rename -force [gitdir GITGUI_BCK] $save
1955                         set GITGUI_BCK_exists 0
1956                 } else {
1957                         set msg [string trim [$ui_comm get 0.0 end]]
1958                         regsub -all -line {[ \r\t]+$} $msg {} msg
1959                         if {(![string match amend* $commit_type]
1960                                 || [$ui_comm edit modified])
1961                                 && $msg ne {}} {
1962                                 catch {
1963                                         set fd [open $save w]
1964                                         puts -nonewline $fd $msg
1965                                         close $fd
1966                                 }
1967                         } else {
1968                                 catch {file delete $save}
1969                         }
1970                 }
1971
1972                 # -- Cancel our spellchecker if its running.
1973                 #
1974                 if {[info exists ui_comm_spell]} {
1975                         $ui_comm_spell stop
1976                 }
1977
1978                 # -- Remove our editor backup, its not needed.
1979                 #
1980                 after cancel $GITGUI_BCK_i
1981                 if {$GITGUI_BCK_exists} {
1982                         catch {file delete [gitdir GITGUI_BCK]}
1983                 }
1984
1985                 # -- Stash our current window geometry into this repository.
1986                 #
1987                 set cfg_geometry [list]
1988                 lappend cfg_geometry [wm geometry .]
1989                 lappend cfg_geometry [lindex [.vpane sash coord 0] 0]
1990                 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 1]
1991                 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1992                         set rc_geometry {}
1993                 }
1994                 if {$cfg_geometry ne $rc_geometry} {
1995                         catch {git config gui.geometry $cfg_geometry}
1996                 }
1997         }
1998
1999         set ret_code $rc
2000         destroy .
2001 }
2002
2003 proc do_rescan {} {
2004         rescan ui_ready
2005 }
2006
2007 proc ui_do_rescan {} {
2008         rescan {force_first_diff ui_ready}
2009 }
2010
2011 proc do_commit {} {
2012         commit_tree
2013 }
2014
2015 proc next_diff {{after {}}} {
2016         global next_diff_p next_diff_w next_diff_i
2017         show_diff $next_diff_p $next_diff_w {} {} $after
2018 }
2019
2020 proc find_anchor_pos {lst name} {
2021         set lid [lsearch -sorted -exact $lst $name]
2022
2023         if {$lid == -1} {
2024                 set lid 0
2025                 foreach lname $lst {
2026                         if {$lname >= $name} break
2027                         incr lid
2028                 }
2029         }
2030
2031         return $lid
2032 }
2033
2034 proc find_file_from {flist idx delta path mmask} {
2035         global file_states
2036
2037         set len [llength $flist]
2038         while {$idx >= 0 && $idx < $len} {
2039                 set name [lindex $flist $idx]
2040
2041                 if {$name ne $path && [info exists file_states($name)]} {
2042                         set state [lindex $file_states($name) 0]
2043
2044                         if {$mmask eq {} || [regexp $mmask $state]} {
2045                                 return $idx
2046                         }
2047                 }
2048
2049                 incr idx $delta
2050         }
2051
2052         return {}
2053 }
2054
2055 proc find_next_diff {w path {lno {}} {mmask {}}} {
2056         global next_diff_p next_diff_w next_diff_i
2057         global file_lists ui_index ui_workdir
2058
2059         set flist $file_lists($w)
2060         if {$lno eq {}} {
2061                 set lno [find_anchor_pos $flist $path]
2062         } else {
2063                 incr lno -1
2064         }
2065
2066         if {$mmask ne {} && ![regexp {(^\^)|(\$$)} $mmask]} {
2067                 if {$w eq $ui_index} {
2068                         set mmask "^$mmask"
2069                 } else {
2070                         set mmask "$mmask\$"
2071                 }
2072         }
2073
2074         set idx [find_file_from $flist $lno 1 $path $mmask]
2075         if {$idx eq {}} {
2076                 incr lno -1
2077                 set idx [find_file_from $flist $lno -1 $path $mmask]
2078         }
2079
2080         if {$idx ne {}} {
2081                 set next_diff_w $w
2082                 set next_diff_p [lindex $flist $idx]
2083                 set next_diff_i [expr {$idx+1}]
2084                 return 1
2085         } else {
2086                 return 0
2087         }
2088 }
2089
2090 proc next_diff_after_action {w path {lno {}} {mmask {}}} {
2091         global current_diff_path
2092
2093         if {$path ne $current_diff_path} {
2094                 return {}
2095         } elseif {[find_next_diff $w $path $lno $mmask]} {
2096                 return {next_diff;}
2097         } else {
2098                 return {reshow_diff;}
2099         }
2100 }
2101
2102 proc select_first_diff {after} {
2103         global ui_workdir
2104
2105         if {[find_next_diff $ui_workdir {} 1 {^_?U}] ||
2106             [find_next_diff $ui_workdir {} 1 {[^O]$}]} {
2107                 next_diff $after
2108         } else {
2109                 uplevel #0 $after
2110         }
2111 }
2112
2113 proc force_first_diff {after} {
2114         global ui_workdir current_diff_path file_states
2115
2116         if {[info exists file_states($current_diff_path)]} {
2117                 set state [lindex $file_states($current_diff_path) 0]
2118         } else {
2119                 set state {OO}
2120         }
2121
2122         set reselect 0
2123         if {[string first {U} $state] >= 0} {
2124                 # Already a conflict, do nothing
2125         } elseif {[find_next_diff $ui_workdir $current_diff_path {} {^_?U}]} {
2126                 set reselect 1
2127         } elseif {[string index $state 1] ne {O}} {
2128                 # Already a diff & no conflicts, do nothing
2129         } elseif {[find_next_diff $ui_workdir $current_diff_path {} {[^O]$}]} {
2130                 set reselect 1
2131         }
2132
2133         if {$reselect} {
2134                 next_diff $after
2135         } else {
2136                 uplevel #0 $after
2137         }
2138 }
2139
2140 proc toggle_or_diff {w x y} {
2141         global file_states file_lists current_diff_path ui_index ui_workdir
2142         global last_clicked selected_paths
2143
2144         set pos [split [$w index @$x,$y] .]
2145         set lno [lindex $pos 0]
2146         set col [lindex $pos 1]
2147         set path [lindex $file_lists($w) [expr {$lno - 1}]]
2148         if {$path eq {}} {
2149                 set last_clicked {}
2150                 return
2151         }
2152
2153         set last_clicked [list $w $lno]
2154         array unset selected_paths
2155         $ui_index tag remove in_sel 0.0 end
2156         $ui_workdir tag remove in_sel 0.0 end
2157
2158         # Determine the state of the file
2159         if {[info exists file_states($path)]} {
2160                 set state [lindex $file_states($path) 0]
2161         } else {
2162                 set state {__}
2163         }
2164
2165         # Restage the file, or simply show the diff
2166         if {$col == 0 && $y > 1} {
2167                 # Conflicts need special handling
2168                 if {[string first {U} $state] >= 0} {
2169                         # $w must always be $ui_workdir, but...
2170                         if {$w ne $ui_workdir} { set lno {} }
2171                         merge_stage_workdir $path $lno
2172                         return
2173                 }
2174
2175                 if {[string index $state 1] eq {O}} {
2176                         set mmask {}
2177                 } else {
2178                         set mmask {[^O]}
2179                 }
2180
2181                 set after [next_diff_after_action $w $path $lno $mmask]
2182
2183                 if {$w eq $ui_index} {
2184                         update_indexinfo \
2185                                 "Unstaging [short_path $path] from commit" \
2186                                 [list $path] \
2187                                 [concat $after [list ui_ready]]
2188                 } elseif {$w eq $ui_workdir} {
2189                         update_index \
2190                                 "Adding [short_path $path]" \
2191                                 [list $path] \
2192                                 [concat $after [list ui_ready]]
2193                 }
2194         } else {
2195                 show_diff $path $w $lno
2196         }
2197 }
2198
2199 proc add_one_to_selection {w x y} {
2200         global file_lists last_clicked selected_paths
2201
2202         set lno [lindex [split [$w index @$x,$y] .] 0]
2203         set path [lindex $file_lists($w) [expr {$lno - 1}]]
2204         if {$path eq {}} {
2205                 set last_clicked {}
2206                 return
2207         }
2208
2209         if {$last_clicked ne {}
2210                 && [lindex $last_clicked 0] ne $w} {
2211                 array unset selected_paths
2212                 [lindex $last_clicked 0] tag remove in_sel 0.0 end
2213         }
2214
2215         set last_clicked [list $w $lno]
2216         if {[catch {set in_sel $selected_paths($path)}]} {
2217                 set in_sel 0
2218         }
2219         if {$in_sel} {
2220                 unset selected_paths($path)
2221                 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
2222         } else {
2223                 set selected_paths($path) 1
2224                 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
2225         }
2226 }
2227
2228 proc add_range_to_selection {w x y} {
2229         global file_lists last_clicked selected_paths
2230
2231         if {[lindex $last_clicked 0] ne $w} {
2232                 toggle_or_diff $w $x $y
2233                 return
2234         }
2235
2236         set lno [lindex [split [$w index @$x,$y] .] 0]
2237         set lc [lindex $last_clicked 1]
2238         if {$lc < $lno} {
2239                 set begin $lc
2240                 set end $lno
2241         } else {
2242                 set begin $lno
2243                 set end $lc
2244         }
2245
2246         foreach path [lrange $file_lists($w) \
2247                 [expr {$begin - 1}] \
2248                 [expr {$end - 1}]] {
2249                 set selected_paths($path) 1
2250         }
2251         $w tag add in_sel $begin.0 [expr {$end + 1}].0
2252 }
2253
2254 proc show_more_context {} {
2255         global repo_config
2256         if {$repo_config(gui.diffcontext) < 99} {
2257                 incr repo_config(gui.diffcontext)
2258                 reshow_diff
2259         }
2260 }
2261
2262 proc show_less_context {} {
2263         global repo_config
2264         if {$repo_config(gui.diffcontext) > 1} {
2265                 incr repo_config(gui.diffcontext) -1
2266                 reshow_diff
2267         }
2268 }
2269
2270 ######################################################################
2271 ##
2272 ## ui construction
2273
2274 load_config 0
2275 apply_config
2276 set ui_comm {}
2277
2278 # -- Menu Bar
2279 #
2280 menu .mbar -tearoff 0
2281 .mbar add cascade -label [mc Repository] -menu .mbar.repository
2282 .mbar add cascade -label [mc Edit] -menu .mbar.edit
2283 if {[is_enabled branch]} {
2284         .mbar add cascade -label [mc Branch] -menu .mbar.branch
2285 }
2286 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2287         .mbar add cascade -label [mc Commit@@noun] -menu .mbar.commit
2288 }
2289 if {[is_enabled transport]} {
2290         .mbar add cascade -label [mc Merge] -menu .mbar.merge
2291         .mbar add cascade -label [mc Remote] -menu .mbar.remote
2292 }
2293 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2294         .mbar add cascade -label [mc Tools] -menu .mbar.tools
2295 }
2296 . configure -menu .mbar
2297
2298 # -- Repository Menu
2299 #
2300 menu .mbar.repository
2301
2302 .mbar.repository add command \
2303         -label [mc "Explore Working Copy"] \
2304         -command {do_explore}
2305 .mbar.repository add separator
2306
2307 .mbar.repository add command \
2308         -label [mc "Browse Current Branch's Files"] \
2309         -command {browser::new $current_branch}
2310 set ui_browse_current [.mbar.repository index last]
2311 .mbar.repository add command \
2312         -label [mc "Browse Branch Files..."] \
2313         -command browser_open::dialog
2314 .mbar.repository add separator
2315
2316 .mbar.repository add command \
2317         -label [mc "Visualize Current Branch's History"] \
2318         -command {do_gitk $current_branch}
2319 set ui_visualize_current [.mbar.repository index last]
2320 .mbar.repository add command \
2321         -label [mc "Visualize All Branch History"] \
2322         -command {do_gitk --all}
2323 .mbar.repository add separator
2324
2325 proc current_branch_write {args} {
2326         global current_branch
2327         .mbar.repository entryconf $::ui_browse_current \
2328                 -label [mc "Browse %s's Files" $current_branch]
2329         .mbar.repository entryconf $::ui_visualize_current \
2330                 -label [mc "Visualize %s's History" $current_branch]
2331 }
2332 trace add variable current_branch write current_branch_write
2333
2334 if {[is_enabled multicommit]} {
2335         .mbar.repository add command -label [mc "Database Statistics"] \
2336                 -command do_stats
2337
2338         .mbar.repository add command -label [mc "Compress Database"] \
2339                 -command do_gc
2340
2341         .mbar.repository add command -label [mc "Verify Database"] \
2342                 -command do_fsck_objects
2343
2344         .mbar.repository add separator
2345
2346         if {[is_Cygwin]} {
2347                 .mbar.repository add command \
2348                         -label [mc "Create Desktop Icon"] \
2349                         -command do_cygwin_shortcut
2350         } elseif {[is_Windows]} {
2351                 .mbar.repository add command \
2352                         -label [mc "Create Desktop Icon"] \
2353                         -command do_windows_shortcut
2354         } elseif {[is_MacOSX]} {
2355                 .mbar.repository add command \
2356                         -label [mc "Create Desktop Icon"] \
2357                         -command do_macosx_app
2358         }
2359 }
2360
2361 if {[is_MacOSX]} {
2362         proc ::tk::mac::Quit {args} { do_quit }
2363 } else {
2364         .mbar.repository add command -label [mc Quit] \
2365                 -command do_quit \
2366                 -accelerator $M1T-Q
2367 }
2368
2369 # -- Edit Menu
2370 #
2371 menu .mbar.edit
2372 .mbar.edit add command -label [mc Undo] \
2373         -command {catch {[focus] edit undo}} \
2374         -accelerator $M1T-Z
2375 .mbar.edit add command -label [mc Redo] \
2376         -command {catch {[focus] edit redo}} \
2377         -accelerator $M1T-Y
2378 .mbar.edit add separator
2379 .mbar.edit add command -label [mc Cut] \
2380         -command {catch {tk_textCut [focus]}} \
2381         -accelerator $M1T-X
2382 .mbar.edit add command -label [mc Copy] \
2383         -command {catch {tk_textCopy [focus]}} \
2384         -accelerator $M1T-C
2385 .mbar.edit add command -label [mc Paste] \
2386         -command {catch {tk_textPaste [focus]; [focus] see insert}} \
2387         -accelerator $M1T-V
2388 .mbar.edit add command -label [mc Delete] \
2389         -command {catch {[focus] delete sel.first sel.last}} \
2390         -accelerator Del
2391 .mbar.edit add separator
2392 .mbar.edit add command -label [mc "Select All"] \
2393         -command {catch {[focus] tag add sel 0.0 end}} \
2394         -accelerator $M1T-A
2395
2396 # -- Branch Menu
2397 #
2398 if {[is_enabled branch]} {
2399         menu .mbar.branch
2400
2401         .mbar.branch add command -label [mc "Create..."] \
2402                 -command branch_create::dialog \
2403                 -accelerator $M1T-N
2404         lappend disable_on_lock [list .mbar.branch entryconf \
2405                 [.mbar.branch index last] -state]
2406
2407         .mbar.branch add command -label [mc "Checkout..."] \
2408                 -command branch_checkout::dialog \
2409                 -accelerator $M1T-O
2410         lappend disable_on_lock [list .mbar.branch entryconf \
2411                 [.mbar.branch index last] -state]
2412
2413         .mbar.branch add command -label [mc "Rename..."] \
2414                 -command branch_rename::dialog
2415         lappend disable_on_lock [list .mbar.branch entryconf \
2416                 [.mbar.branch index last] -state]
2417
2418         .mbar.branch add command -label [mc "Delete..."] \
2419                 -command branch_delete::dialog
2420         lappend disable_on_lock [list .mbar.branch entryconf \
2421                 [.mbar.branch index last] -state]
2422
2423         .mbar.branch add command -label [mc "Reset..."] \
2424                 -command merge::reset_hard
2425         lappend disable_on_lock [list .mbar.branch entryconf \
2426                 [.mbar.branch index last] -state]
2427 }
2428
2429 # -- Commit Menu
2430 #
2431 proc commit_btn_caption {} {
2432         if {[is_enabled nocommit]} {
2433                 return [mc "Done"]
2434         } else {
2435                 return [mc Commit@@verb]
2436         }
2437 }
2438
2439 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2440         menu .mbar.commit
2441
2442         if {![is_enabled nocommit]} {
2443                 .mbar.commit add radiobutton \
2444                         -label [mc "New Commit"] \
2445                         -command do_select_commit_type \
2446                         -variable selected_commit_type \
2447                         -value new
2448                 lappend disable_on_lock \
2449                         [list .mbar.commit entryconf [.mbar.commit index last] -state]
2450
2451                 .mbar.commit add radiobutton \
2452                         -label [mc "Amend Last Commit"] \
2453                         -command do_select_commit_type \
2454                         -variable selected_commit_type \
2455                         -value amend
2456                 lappend disable_on_lock \
2457                         [list .mbar.commit entryconf [.mbar.commit index last] -state]
2458
2459                 .mbar.commit add separator
2460         }
2461
2462         .mbar.commit add command -label [mc Rescan] \
2463                 -command ui_do_rescan \
2464                 -accelerator F5
2465         lappend disable_on_lock \
2466                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2467
2468         .mbar.commit add command -label [mc "Stage To Commit"] \
2469                 -command do_add_selection \
2470                 -accelerator $M1T-T
2471         lappend disable_on_lock \
2472                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2473
2474         .mbar.commit add command -label [mc "Stage Changed Files To Commit"] \
2475                 -command do_add_all \
2476                 -accelerator $M1T-I
2477         lappend disable_on_lock \
2478                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2479
2480         .mbar.commit add command -label [mc "Unstage From Commit"] \
2481                 -command do_unstage_selection
2482         lappend disable_on_lock \
2483                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2484
2485         .mbar.commit add command -label [mc "Revert Changes"] \
2486                 -command do_revert_selection
2487         lappend disable_on_lock \
2488                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2489
2490         .mbar.commit add separator
2491
2492         .mbar.commit add command -label [mc "Show Less Context"] \
2493                 -command show_less_context \
2494                 -accelerator $M1T-\-
2495
2496         .mbar.commit add command -label [mc "Show More Context"] \
2497                 -command show_more_context \
2498                 -accelerator $M1T-=
2499
2500         .mbar.commit add separator
2501
2502         if {![is_enabled nocommitmsg]} {
2503                 .mbar.commit add command -label [mc "Sign Off"] \
2504                         -command do_signoff \
2505                         -accelerator $M1T-S
2506         }
2507
2508         .mbar.commit add command -label [commit_btn_caption] \
2509                 -command do_commit \
2510                 -accelerator $M1T-Return
2511         lappend disable_on_lock \
2512                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
2513 }
2514
2515 # -- Merge Menu
2516 #
2517 if {[is_enabled branch]} {
2518         menu .mbar.merge
2519         .mbar.merge add command -label [mc "Local Merge..."] \
2520                 -command merge::dialog \
2521                 -accelerator $M1T-M
2522         lappend disable_on_lock \
2523                 [list .mbar.merge entryconf [.mbar.merge index last] -state]
2524         .mbar.merge add command -label [mc "Abort Merge..."] \
2525                 -command merge::reset_hard
2526         lappend disable_on_lock \
2527                 [list .mbar.merge entryconf [.mbar.merge index last] -state]
2528 }
2529
2530 # -- Transport Menu
2531 #
2532 if {[is_enabled transport]} {
2533         menu .mbar.remote
2534
2535         .mbar.remote add command \
2536                 -label [mc "Add..."] \
2537                 -command remote_add::dialog \
2538                 -accelerator $M1T-A
2539         .mbar.remote add command \
2540                 -label [mc "Push..."] \
2541                 -command do_push_anywhere \
2542                 -accelerator $M1T-P
2543         .mbar.remote add command \
2544                 -label [mc "Delete Branch..."] \
2545                 -command remote_branch_delete::dialog
2546 }
2547
2548 if {[is_MacOSX]} {
2549         # -- Apple Menu (Mac OS X only)
2550         #
2551         .mbar add cascade -label Apple -menu .mbar.apple
2552         menu .mbar.apple
2553
2554         .mbar.apple add command -label [mc "About %s" [appname]] \
2555                 -command do_about
2556         .mbar.apple add separator
2557         .mbar.apple add command \
2558                 -label [mc "Preferences..."] \
2559                 -command do_options \
2560                 -accelerator $M1T-,
2561         bind . <$M1B-,> do_options
2562 } else {
2563         # -- Edit Menu
2564         #
2565         .mbar.edit add separator
2566         .mbar.edit add command -label [mc "Options..."] \
2567                 -command do_options
2568 }
2569
2570 # -- Tools Menu
2571 #
2572 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
2573         set tools_menubar .mbar.tools
2574         menu $tools_menubar
2575         $tools_menubar add separator
2576         $tools_menubar add command -label [mc "Add..."] -command tools_add::dialog
2577         $tools_menubar add command -label [mc "Remove..."] -command tools_remove::dialog
2578         set tools_tailcnt 3
2579         if {[array names repo_config guitool.*.cmd] ne {}} {
2580                 tools_populate_all
2581         }
2582 }
2583
2584 # -- Help Menu
2585 #
2586 .mbar add cascade -label [mc Help] -menu .mbar.help
2587 menu .mbar.help
2588
2589 if {![is_MacOSX]} {
2590         .mbar.help add command -label [mc "About %s" [appname]] \
2591                 -command do_about
2592 }
2593
2594
2595 set doc_path [file dirname [gitexec]]
2596 set doc_path [file join $doc_path Documentation index.html]
2597
2598 if {[is_Cygwin]} {
2599         set doc_path [exec cygpath --mixed $doc_path]
2600 }
2601
2602 if {[file isfile $doc_path]} {
2603         set doc_url "file:$doc_path"
2604 } else {
2605         set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
2606 }
2607
2608 proc start_browser {url} {
2609         git "web--browse" $url
2610 }
2611
2612 .mbar.help add command -label [mc "Online Documentation"] \
2613         -command [list start_browser $doc_url]
2614
2615 .mbar.help add command -label [mc "Show SSH Key"] \
2616         -command do_ssh_key
2617
2618 unset doc_path doc_url
2619
2620 # -- Standard bindings
2621 #
2622 wm protocol . WM_DELETE_WINDOW do_quit
2623 bind all <$M1B-Key-q> do_quit
2624 bind all <$M1B-Key-Q> do_quit
2625 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
2626 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
2627
2628 set subcommand_args {}
2629 proc usage {} {
2630         puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
2631         exit 1
2632 }
2633
2634 proc normalize_relpath {path} {
2635         set elements {}
2636         foreach item [file split $path] {
2637                 if {$item eq {.}} continue
2638                 if {$item eq {..} && [llength $elements] > 0
2639                     && [lindex $elements end] ne {..}} {
2640                         set elements [lrange $elements 0 end-1]
2641                         continue
2642                 }
2643                 lappend elements $item
2644         }
2645         return [eval file join $elements]
2646 }
2647
2648 # -- Not a normal commit type invocation?  Do that instead!
2649 #
2650 switch -- $subcommand {
2651 browser -
2652 blame {
2653         if {$subcommand eq "blame"} {
2654                 set subcommand_args {[--line=<num>] rev? path}
2655         } else {
2656                 set subcommand_args {rev? path}
2657         }
2658         if {$argv eq {}} usage
2659         set head {}
2660         set path {}
2661         set jump_spec {}
2662         set is_path 0
2663         foreach a $argv {
2664                 if {$is_path || [file exists $_prefix$a]} {
2665                         if {$path ne {}} usage
2666                         set path [normalize_relpath $_prefix$a]
2667                         break
2668                 } elseif {$a eq {--}} {
2669                         if {$path ne {}} {
2670                                 if {$head ne {}} usage
2671                                 set head $path
2672                                 set path {}
2673                         }
2674                         set is_path 1
2675                 } elseif {[regexp {^--line=(\d+)$} $a a lnum]} {
2676                         if {$jump_spec ne {} || $head ne {}} usage
2677                         set jump_spec [list $lnum]
2678                 } elseif {$head eq {}} {
2679                         if {$head ne {}} usage
2680                         set head $a
2681                         set is_path 1
2682                 } else {
2683                         usage
2684                 }
2685         }
2686         unset is_path
2687
2688         if {$head ne {} && $path eq {}} {
2689                 set path [normalize_relpath $_prefix$head]
2690                 set head {}
2691         }
2692
2693         if {$head eq {}} {
2694                 load_current_branch
2695         } else {
2696                 if {[regexp {^[0-9a-f]{1,39}$} $head]} {
2697                         if {[catch {
2698                                         set head [git rev-parse --verify $head]
2699                                 } err]} {
2700                                 puts stderr $err
2701                                 exit 1
2702                         }
2703                 }
2704                 set current_branch $head
2705         }
2706
2707         switch -- $subcommand {
2708         browser {
2709                 if {$jump_spec ne {}} usage
2710                 if {$head eq {}} {
2711                         if {$path ne {} && [file isdirectory $path]} {
2712                                 set head $current_branch
2713                         } else {
2714                                 set head $path
2715                                 set path {}
2716                         }
2717                 }
2718                 browser::new $head $path
2719         }
2720         blame   {
2721                 if {$head eq {} && ![file exists $path]} {
2722                         puts stderr [mc "fatal: cannot stat path %s: No such file or directory" $path]
2723                         exit 1
2724                 }
2725                 blame::new $head $path $jump_spec
2726         }
2727         }
2728         return
2729 }
2730 citool -
2731 gui {
2732         if {[llength $argv] != 0} {
2733                 puts -nonewline stderr "usage: $argv0"
2734                 if {$subcommand ne {gui}
2735                         && [file tail $argv0] ne "git-$subcommand"} {
2736                         puts -nonewline stderr " $subcommand"
2737                 }
2738                 puts stderr {}
2739                 exit 1
2740         }
2741         # fall through to setup UI for commits
2742 }
2743 default {
2744         puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
2745         exit 1
2746 }
2747 }
2748
2749 # -- Branch Control
2750 #
2751 frame .branch \
2752         -borderwidth 1 \
2753         -relief sunken
2754 label .branch.l1 \
2755         -text [mc "Current Branch:"] \
2756         -anchor w \
2757         -justify left
2758 label .branch.cb \
2759         -textvariable current_branch \
2760         -anchor w \
2761         -justify left
2762 pack .branch.l1 -side left
2763 pack .branch.cb -side left -fill x
2764 pack .branch -side top -fill x
2765
2766 # -- Main Window Layout
2767 #
2768 panedwindow .vpane -orient horizontal
2769 panedwindow .vpane.files -orient vertical
2770 .vpane add .vpane.files -sticky nsew -height 100 -width 200
2771 pack .vpane -anchor n -side top -fill both -expand 1
2772
2773 # -- Index File List
2774 #
2775 frame .vpane.files.index -height 100 -width 200
2776 label .vpane.files.index.title -text [mc "Staged Changes (Will Commit)"] \
2777         -background lightgreen -foreground black
2778 text $ui_index -background white -foreground black \
2779         -borderwidth 0 \
2780         -width 20 -height 10 \
2781         -wrap none \
2782         -cursor $cursor_ptr \
2783         -xscrollcommand {.vpane.files.index.sx set} \
2784         -yscrollcommand {.vpane.files.index.sy set} \
2785         -state disabled
2786 scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
2787 scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
2788 pack .vpane.files.index.title -side top -fill x
2789 pack .vpane.files.index.sx -side bottom -fill x
2790 pack .vpane.files.index.sy -side right -fill y
2791 pack $ui_index -side left -fill both -expand 1
2792
2793 # -- Working Directory File List
2794 #
2795 frame .vpane.files.workdir -height 100 -width 200
2796 label .vpane.files.workdir.title -text [mc "Unstaged Changes"] \
2797         -background lightsalmon -foreground black
2798 text $ui_workdir -background white -foreground black \
2799         -borderwidth 0 \
2800         -width 20 -height 10 \
2801         -wrap none \
2802         -cursor $cursor_ptr \
2803         -xscrollcommand {.vpane.files.workdir.sx set} \
2804         -yscrollcommand {.vpane.files.workdir.sy set} \
2805         -state disabled
2806 scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
2807 scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
2808 pack .vpane.files.workdir.title -side top -fill x
2809 pack .vpane.files.workdir.sx -side bottom -fill x
2810 pack .vpane.files.workdir.sy -side right -fill y
2811 pack $ui_workdir -side left -fill both -expand 1
2812
2813 .vpane.files add .vpane.files.workdir -sticky nsew
2814 .vpane.files add .vpane.files.index -sticky nsew
2815
2816 foreach i [list $ui_index $ui_workdir] {
2817         rmsel_tag $i
2818         $i tag conf in_diff -background [$i tag cget in_sel -background]
2819 }
2820 unset i
2821
2822 # -- Diff and Commit Area
2823 #
2824 frame .vpane.lower -height 300 -width 400
2825 frame .vpane.lower.commarea
2826 frame .vpane.lower.diff -relief sunken -borderwidth 1
2827 pack .vpane.lower.diff -fill both -expand 1
2828 pack .vpane.lower.commarea -side bottom -fill x
2829 .vpane add .vpane.lower -sticky nsew
2830
2831 # -- Commit Area Buttons
2832 #
2833 frame .vpane.lower.commarea.buttons
2834 label .vpane.lower.commarea.buttons.l -text {} \
2835         -anchor w \
2836         -justify left
2837 pack .vpane.lower.commarea.buttons.l -side top -fill x
2838 pack .vpane.lower.commarea.buttons -side left -fill y
2839
2840 button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \
2841         -command ui_do_rescan
2842 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
2843 lappend disable_on_lock \
2844         {.vpane.lower.commarea.buttons.rescan conf -state}
2845
2846 button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \
2847         -command do_add_all
2848 pack .vpane.lower.commarea.buttons.incall -side top -fill x
2849 lappend disable_on_lock \
2850         {.vpane.lower.commarea.buttons.incall conf -state}
2851
2852 if {![is_enabled nocommitmsg]} {
2853         button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \
2854                 -command do_signoff
2855         pack .vpane.lower.commarea.buttons.signoff -side top -fill x
2856 }
2857
2858 button .vpane.lower.commarea.buttons.commit -text [commit_btn_caption] \
2859         -command do_commit
2860 pack .vpane.lower.commarea.buttons.commit -side top -fill x
2861 lappend disable_on_lock \
2862         {.vpane.lower.commarea.buttons.commit conf -state}
2863
2864 if {![is_enabled nocommit]} {
2865         button .vpane.lower.commarea.buttons.push -text [mc Push] \
2866                 -command do_push_anywhere
2867         pack .vpane.lower.commarea.buttons.push -side top -fill x
2868 }
2869
2870 # -- Commit Message Buffer
2871 #
2872 frame .vpane.lower.commarea.buffer
2873 frame .vpane.lower.commarea.buffer.header
2874 set ui_comm .vpane.lower.commarea.buffer.t
2875 set ui_coml .vpane.lower.commarea.buffer.header.l
2876
2877 if {![is_enabled nocommit]} {
2878         radiobutton .vpane.lower.commarea.buffer.header.new \
2879                 -text [mc "New Commit"] \
2880                 -command do_select_commit_type \
2881                 -variable selected_commit_type \
2882                 -value new
2883         lappend disable_on_lock \
2884                 [list .vpane.lower.commarea.buffer.header.new conf -state]
2885         radiobutton .vpane.lower.commarea.buffer.header.amend \
2886                 -text [mc "Amend Last Commit"] \
2887                 -command do_select_commit_type \
2888                 -variable selected_commit_type \
2889                 -value amend
2890         lappend disable_on_lock \
2891                 [list .vpane.lower.commarea.buffer.header.amend conf -state]
2892 }
2893
2894 label $ui_coml \
2895         -anchor w \
2896         -justify left
2897 proc trace_commit_type {varname args} {
2898         global ui_coml commit_type
2899         switch -glob -- $commit_type {
2900         initial       {set txt [mc "Initial Commit Message:"]}
2901         amend         {set txt [mc "Amended Commit Message:"]}
2902         amend-initial {set txt [mc "Amended Initial Commit Message:"]}
2903         amend-merge   {set txt [mc "Amended Merge Commit Message:"]}
2904         merge         {set txt [mc "Merge Commit Message:"]}
2905         *             {set txt [mc "Commit Message:"]}
2906         }
2907         $ui_coml conf -text $txt
2908 }
2909 trace add variable commit_type write trace_commit_type
2910 pack $ui_coml -side left -fill x
2911
2912 if {![is_enabled nocommit]} {
2913         pack .vpane.lower.commarea.buffer.header.amend -side right
2914         pack .vpane.lower.commarea.buffer.header.new -side right
2915 }
2916
2917 text $ui_comm -background white -foreground black \
2918         -borderwidth 1 \
2919         -undo true \
2920         -maxundo 20 \
2921         -autoseparators true \
2922         -relief sunken \
2923         -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \
2924         -font font_diff \
2925         -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
2926 scrollbar .vpane.lower.commarea.buffer.sby \
2927         -command [list $ui_comm yview]
2928 pack .vpane.lower.commarea.buffer.header -side top -fill x
2929 pack .vpane.lower.commarea.buffer.sby -side right -fill y
2930 pack $ui_comm -side left -fill y
2931 pack .vpane.lower.commarea.buffer -side left -fill y
2932
2933 # -- Commit Message Buffer Context Menu
2934 #
2935 set ctxm .vpane.lower.commarea.buffer.ctxm
2936 menu $ctxm -tearoff 0
2937 $ctxm add command \
2938         -label [mc Cut] \
2939         -command {tk_textCut $ui_comm}
2940 $ctxm add command \
2941         -label [mc Copy] \
2942         -command {tk_textCopy $ui_comm}
2943 $ctxm add command \
2944         -label [mc Paste] \
2945         -command {tk_textPaste $ui_comm}
2946 $ctxm add command \
2947         -label [mc Delete] \
2948         -command {catch {$ui_comm delete sel.first sel.last}}
2949 $ctxm add separator
2950 $ctxm add command \
2951         -label [mc "Select All"] \
2952         -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
2953 $ctxm add command \
2954         -label [mc "Copy All"] \
2955         -command {
2956                 $ui_comm tag add sel 0.0 end
2957                 tk_textCopy $ui_comm
2958                 $ui_comm tag remove sel 0.0 end
2959         }
2960 $ctxm add separator
2961 $ctxm add command \
2962         -label [mc "Sign Off"] \
2963         -command do_signoff
2964 set ui_comm_ctxm $ctxm
2965
2966 # -- Diff Header
2967 #
2968 proc trace_current_diff_path {varname args} {
2969         global current_diff_path diff_actions file_states
2970         if {$current_diff_path eq {}} {
2971                 set s {}
2972                 set f {}
2973                 set p {}
2974                 set o disabled
2975         } else {
2976                 set p $current_diff_path
2977                 set s [mapdesc [lindex $file_states($p) 0] $p]
2978                 set f [mc "File:"]
2979                 set p [escape_path $p]
2980                 set o normal
2981         }
2982
2983         .vpane.lower.diff.header.status configure -text $s
2984         .vpane.lower.diff.header.file configure -text $f
2985         .vpane.lower.diff.header.path configure -text $p
2986         foreach w $diff_actions {
2987                 uplevel #0 $w $o
2988         }
2989 }
2990 trace add variable current_diff_path write trace_current_diff_path
2991
2992 frame .vpane.lower.diff.header -background gold
2993 label .vpane.lower.diff.header.status \
2994         -background gold \
2995         -foreground black \
2996         -width $max_status_desc \
2997         -anchor w \
2998         -justify left
2999 label .vpane.lower.diff.header.file \
3000         -background gold \
3001         -foreground black \
3002         -anchor w \
3003         -justify left
3004 label .vpane.lower.diff.header.path \
3005         -background gold \
3006         -foreground black \
3007         -anchor w \
3008         -justify left
3009 pack .vpane.lower.diff.header.status -side left
3010 pack .vpane.lower.diff.header.file -side left
3011 pack .vpane.lower.diff.header.path -fill x
3012 set ctxm .vpane.lower.diff.header.ctxm
3013 menu $ctxm -tearoff 0
3014 $ctxm add command \
3015         -label [mc Copy] \
3016         -command {
3017                 clipboard clear
3018                 clipboard append \
3019                         -format STRING \
3020                         -type STRING \
3021                         -- $current_diff_path
3022         }
3023 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3024 bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
3025
3026 # -- Diff Body
3027 #
3028 frame .vpane.lower.diff.body
3029 set ui_diff .vpane.lower.diff.body.t
3030 text $ui_diff -background white -foreground black \
3031         -borderwidth 0 \
3032         -width 80 -height 15 -wrap none \
3033         -font font_diff \
3034         -xscrollcommand {.vpane.lower.diff.body.sbx set} \
3035         -yscrollcommand {.vpane.lower.diff.body.sby set} \
3036         -state disabled
3037 scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
3038         -command [list $ui_diff xview]
3039 scrollbar .vpane.lower.diff.body.sby -orient vertical \
3040         -command [list $ui_diff yview]
3041 pack .vpane.lower.diff.body.sbx -side bottom -fill x
3042 pack .vpane.lower.diff.body.sby -side right -fill y
3043 pack $ui_diff -side left -fill both -expand 1
3044 pack .vpane.lower.diff.header -side top -fill x
3045 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
3046
3047 $ui_diff tag conf d_cr -elide true
3048 $ui_diff tag conf d_@ -foreground blue -font font_diffbold
3049 $ui_diff tag conf d_+ -foreground {#00a000}
3050 $ui_diff tag conf d_- -foreground red
3051
3052 $ui_diff tag conf d_++ -foreground {#00a000}
3053 $ui_diff tag conf d_-- -foreground red
3054 $ui_diff tag conf d_+s \
3055         -foreground {#00a000} \
3056         -background {#e2effa}
3057 $ui_diff tag conf d_-s \
3058         -foreground red \
3059         -background {#e2effa}
3060 $ui_diff tag conf d_s+ \
3061         -foreground {#00a000} \
3062         -background ivory1
3063 $ui_diff tag conf d_s- \
3064         -foreground red \
3065         -background ivory1
3066
3067 $ui_diff tag conf d<<<<<<< \
3068         -foreground orange \
3069         -font font_diffbold
3070 $ui_diff tag conf d======= \
3071         -foreground orange \
3072         -font font_diffbold
3073 $ui_diff tag conf d>>>>>>> \
3074         -foreground orange \
3075         -font font_diffbold
3076
3077 $ui_diff tag raise sel
3078
3079 # -- Diff Body Context Menu
3080 #
3081
3082 proc create_common_diff_popup {ctxm} {
3083         $ctxm add command \
3084                 -label [mc "Show Less Context"] \
3085                 -command show_less_context
3086         lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3087         $ctxm add command \
3088                 -label [mc "Show More Context"] \
3089                 -command show_more_context
3090         lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3091         $ctxm add separator
3092         $ctxm add command \
3093                 -label [mc Refresh] \
3094                 -command reshow_diff
3095         lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3096         $ctxm add command \
3097                 -label [mc Copy] \
3098                 -command {tk_textCopy $ui_diff}
3099         lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3100         $ctxm add command \
3101                 -label [mc "Select All"] \
3102                 -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
3103         lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3104         $ctxm add command \
3105                 -label [mc "Copy All"] \
3106                 -command {
3107                         $ui_diff tag add sel 0.0 end
3108                         tk_textCopy $ui_diff
3109                         $ui_diff tag remove sel 0.0 end
3110                 }
3111         lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3112         $ctxm add separator
3113         $ctxm add command \
3114                 -label [mc "Decrease Font Size"] \
3115                 -command {incr_font_size font_diff -1}
3116         lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3117         $ctxm add command \
3118                 -label [mc "Increase Font Size"] \
3119                 -command {incr_font_size font_diff 1}
3120         lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3121         $ctxm add separator
3122         set emenu $ctxm.enc
3123         menu $emenu
3124         build_encoding_menu $emenu [list force_diff_encoding]
3125         $ctxm add cascade \
3126                 -label [mc "Encoding"] \
3127                 -menu $emenu
3128         lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3129         $ctxm add separator
3130         $ctxm add command -label [mc "Options..."] \
3131                 -command do_options
3132 }
3133
3134 set ctxm .vpane.lower.diff.body.ctxm
3135 menu $ctxm -tearoff 0
3136 $ctxm add command \
3137         -label [mc "Apply/Reverse Hunk"] \
3138         -command {apply_hunk $cursorX $cursorY}
3139 set ui_diff_applyhunk [$ctxm index last]
3140 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
3141 $ctxm add command \
3142         -label [mc "Apply/Reverse Line"] \
3143         -command {apply_line $cursorX $cursorY; do_rescan}
3144 set ui_diff_applyline [$ctxm index last]
3145 lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state]
3146 $ctxm add separator
3147 create_common_diff_popup $ctxm
3148
3149 set ctxmmg .vpane.lower.diff.body.ctxmmg
3150 menu $ctxmmg -tearoff 0
3151 $ctxmmg add command \
3152         -label [mc "Run Merge Tool"] \
3153         -command {merge_resolve_tool}
3154 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3155 $ctxmmg add separator
3156 $ctxmmg add command \
3157         -label [mc "Use Remote Version"] \
3158         -command {merge_resolve_one 3}
3159 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3160 $ctxmmg add command \
3161         -label [mc "Use Local Version"] \
3162         -command {merge_resolve_one 2}
3163 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3164 $ctxmmg add command \
3165         -label [mc "Revert To Base"] \
3166         -command {merge_resolve_one 1}
3167 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
3168 $ctxmmg add separator
3169 create_common_diff_popup $ctxmmg
3170
3171 proc popup_diff_menu {ctxm ctxmmg x y X Y} {
3172         global current_diff_path file_states
3173         set ::cursorX $x
3174         set ::cursorY $y
3175         if {[info exists file_states($current_diff_path)]} {
3176                 set state [lindex $file_states($current_diff_path) 0]
3177         } else {
3178                 set state {__}
3179         }
3180         if {[string first {U} $state] >= 0} {
3181                 tk_popup $ctxmmg $X $Y
3182         } else {
3183                 if {$::ui_index eq $::current_diff_side} {
3184                         set l [mc "Unstage Hunk From Commit"]
3185                         set t [mc "Unstage Line From Commit"]
3186                 } else {
3187                         set l [mc "Stage Hunk For Commit"]
3188                         set t [mc "Stage Line For Commit"]
3189                 }
3190                 if {$::is_3way_diff
3191                         || $current_diff_path eq {}
3192                         || {__} eq $state
3193                         || {_O} eq $state
3194                         || {_T} eq $state
3195                         || {T_} eq $state} {
3196                         set s disabled
3197                 } else {
3198                         set s normal
3199                 }
3200                 $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
3201                 $ctxm entryconf $::ui_diff_applyline -state $s -label $t
3202                 tk_popup $ctxm $X $Y
3203         }
3204 }
3205 bind_button3 $ui_diff [list popup_diff_menu $ctxm $ctxmmg %x %y %X %Y]
3206
3207 # -- Status Bar
3208 #
3209 set main_status [::status_bar::new .status]
3210 pack .status -anchor w -side bottom -fill x
3211 $main_status show [mc "Initializing..."]
3212
3213 # -- Load geometry
3214 #
3215 catch {
3216 set gm $repo_config(gui.geometry)
3217 wm geometry . [lindex $gm 0]
3218 .vpane sash place 0 \
3219         [lindex $gm 1] \
3220         [lindex [.vpane sash coord 0] 1]
3221 .vpane.files sash place 0 \
3222         [lindex [.vpane.files sash coord 0] 0] \
3223         [lindex $gm 2]
3224 unset gm
3225 }
3226
3227 # -- Key Bindings
3228 #
3229 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
3230 bind $ui_comm <$M1B-Key-t> {do_add_selection;break}
3231 bind $ui_comm <$M1B-Key-T> {do_add_selection;break}
3232 bind $ui_comm <$M1B-Key-i> {do_add_all;break}
3233 bind $ui_comm <$M1B-Key-I> {do_add_all;break}
3234 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
3235 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
3236 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
3237 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
3238 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
3239 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
3240 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3241 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3242 bind $ui_comm <$M1B-Key-minus> {show_less_context;break}
3243 bind $ui_comm <$M1B-Key-KP_Subtract> {show_less_context;break}
3244 bind $ui_comm <$M1B-Key-equal> {show_more_context;break}
3245 bind $ui_comm <$M1B-Key-plus> {show_more_context;break}
3246 bind $ui_comm <$M1B-Key-KP_Add> {show_more_context;break}
3247
3248 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
3249 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
3250 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
3251 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
3252 bind $ui_diff <$M1B-Key-v> {break}
3253 bind $ui_diff <$M1B-Key-V> {break}
3254 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3255 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3256 bind $ui_diff <Key-Up>     {catch {%W yview scroll -1 units};break}
3257 bind $ui_diff <Key-Down>   {catch {%W yview scroll  1 units};break}
3258 bind $ui_diff <Key-Left>   {catch {%W xview scroll -1 units};break}
3259 bind $ui_diff <Key-Right>  {catch {%W xview scroll  1 units};break}
3260 bind $ui_diff <Key-k>         {catch {%W yview scroll -1 units};break}
3261 bind $ui_diff <Key-j>         {catch {%W yview scroll  1 units};break}
3262 bind $ui_diff <Key-h>         {catch {%W xview scroll -1 units};break}
3263 bind $ui_diff <Key-l>         {catch {%W xview scroll  1 units};break}
3264 bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
3265 bind $ui_diff <Control-Key-f> {catch {%W yview scroll  1 pages};break}
3266 bind $ui_diff <Button-1>   {focus %W}
3267
3268 if {[is_enabled branch]} {
3269         bind . <$M1B-Key-n> branch_create::dialog
3270         bind . <$M1B-Key-N> branch_create::dialog
3271         bind . <$M1B-Key-o> branch_checkout::dialog
3272         bind . <$M1B-Key-O> branch_checkout::dialog
3273         bind . <$M1B-Key-m> merge::dialog
3274         bind . <$M1B-Key-M> merge::dialog
3275 }
3276 if {[is_enabled transport]} {
3277         bind . <$M1B-Key-p> do_push_anywhere
3278         bind . <$M1B-Key-P> do_push_anywhere
3279 }
3280
3281 bind .   <Key-F5>     ui_do_rescan
3282 bind .   <$M1B-Key-r> ui_do_rescan
3283 bind .   <$M1B-Key-R> ui_do_rescan
3284 bind .   <$M1B-Key-s> do_signoff
3285 bind .   <$M1B-Key-S> do_signoff
3286 bind .   <$M1B-Key-t> do_add_selection
3287 bind .   <$M1B-Key-T> do_add_selection
3288 bind .   <$M1B-Key-i> do_add_all
3289 bind .   <$M1B-Key-I> do_add_all
3290 bind .   <$M1B-Key-minus> {show_less_context;break}
3291 bind .   <$M1B-Key-KP_Subtract> {show_less_context;break}
3292 bind .   <$M1B-Key-equal> {show_more_context;break}
3293 bind .   <$M1B-Key-plus> {show_more_context;break}
3294 bind .   <$M1B-Key-KP_Add> {show_more_context;break}
3295 bind .   <$M1B-Key-Return> do_commit
3296 foreach i [list $ui_index $ui_workdir] {
3297         bind $i <Button-1>       "toggle_or_diff         $i %x %y; break"
3298         bind $i <$M1B-Button-1>  "add_one_to_selection   $i %x %y; break"
3299         bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
3300 }
3301 unset i
3302
3303 set file_lists($ui_index) [list]
3304 set file_lists($ui_workdir) [list]
3305
3306 wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
3307 focus -force $ui_comm
3308
3309 # -- Warn the user about environmental problems.  Cygwin's Tcl
3310 #    does *not* pass its env array onto any processes it spawns.
3311 #    This means that git processes get none of our environment.
3312 #
3313 if {[is_Cygwin]} {
3314         set ignored_env 0
3315         set suggest_user {}
3316         set msg [mc "Possible environment issues exist.
3317
3318 The following environment variables are probably
3319 going to be ignored by any Git subprocess run
3320 by %s:
3321
3322 " [appname]]
3323         foreach name [array names env] {
3324                 switch -regexp -- $name {
3325                 {^GIT_INDEX_FILE$} -
3326                 {^GIT_OBJECT_DIRECTORY$} -
3327                 {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
3328                 {^GIT_DIFF_OPTS$} -
3329                 {^GIT_EXTERNAL_DIFF$} -
3330                 {^GIT_PAGER$} -
3331                 {^GIT_TRACE$} -
3332                 {^GIT_CONFIG$} -
3333                 {^GIT_(AUTHOR|COMMITTER)_DATE$} {
3334                         append msg " - $name\n"
3335                         incr ignored_env
3336                 }
3337                 {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
3338                         append msg " - $name\n"
3339                         incr ignored_env
3340                         set suggest_user $name
3341                 }
3342                 }
3343         }
3344         if {$ignored_env > 0} {
3345                 append msg [mc "
3346 This is due to a known issue with the
3347 Tcl binary distributed by Cygwin."]
3348
3349                 if {$suggest_user ne {}} {
3350                         append msg [mc "
3351
3352 A good replacement for %s
3353 is placing values for the user.name and
3354 user.email settings into your personal
3355 ~/.gitconfig file.
3356 " $suggest_user]
3357                 }
3358                 warn_popup $msg
3359         }
3360         unset ignored_env msg suggest_user name
3361 }
3362
3363 # -- Only initialize complex UI if we are going to stay running.
3364 #
3365 if {[is_enabled transport]} {
3366         load_all_remotes
3367
3368         set n [.mbar.remote index end]
3369         populate_remotes_menu
3370         set n [expr {[.mbar.remote index end] - $n}]
3371         if {$n > 0} {
3372                 if {[.mbar.remote type 0] eq "tearoff"} { incr n }
3373                 .mbar.remote insert $n separator
3374         }
3375         unset n
3376 }
3377
3378 if {[winfo exists $ui_comm]} {
3379         set GITGUI_BCK_exists [load_message GITGUI_BCK]
3380
3381         # -- If both our backup and message files exist use the
3382         #    newer of the two files to initialize the buffer.
3383         #
3384         if {$GITGUI_BCK_exists} {
3385                 set m [gitdir GITGUI_MSG]
3386                 if {[file isfile $m]} {
3387                         if {[file mtime [gitdir GITGUI_BCK]] > [file mtime $m]} {
3388                                 catch {file delete [gitdir GITGUI_MSG]}
3389                         } else {
3390                                 $ui_comm delete 0.0 end
3391                                 $ui_comm edit reset
3392                                 $ui_comm edit modified false
3393                                 catch {file delete [gitdir GITGUI_BCK]}
3394                                 set GITGUI_BCK_exists 0
3395                         }
3396                 }
3397                 unset m
3398         }
3399
3400         proc backup_commit_buffer {} {
3401                 global ui_comm GITGUI_BCK_exists
3402
3403                 set m [$ui_comm edit modified]
3404                 if {$m || $GITGUI_BCK_exists} {
3405                         set msg [string trim [$ui_comm get 0.0 end]]
3406                         regsub -all -line {[ \r\t]+$} $msg {} msg
3407
3408                         if {$msg eq {}} {
3409                                 if {$GITGUI_BCK_exists} {
3410                                         catch {file delete [gitdir GITGUI_BCK]}
3411                                         set GITGUI_BCK_exists 0
3412                                 }
3413                         } elseif {$m} {
3414                                 catch {
3415                                         set fd [open [gitdir GITGUI_BCK] w]
3416                                         puts -nonewline $fd $msg
3417                                         close $fd
3418                                         set GITGUI_BCK_exists 1
3419                                 }
3420                         }
3421
3422                         $ui_comm edit modified false
3423                 }
3424
3425                 set ::GITGUI_BCK_i [after 2000 backup_commit_buffer]
3426         }
3427
3428         backup_commit_buffer
3429
3430         # -- If the user has aspell available we can drive it
3431         #    in pipe mode to spellcheck the commit message.
3432         #
3433         set spell_cmd [list |]
3434         set spell_dict [get_config gui.spellingdictionary]
3435         lappend spell_cmd aspell
3436         if {$spell_dict ne {}} {
3437                 lappend spell_cmd --master=$spell_dict
3438         }
3439         lappend spell_cmd --mode=none
3440         lappend spell_cmd --encoding=utf-8
3441         lappend spell_cmd pipe
3442         if {$spell_dict eq {none}
3443          || [catch {set spell_fd [open $spell_cmd r+]} spell_err]} {
3444                 bind_button3 $ui_comm [list tk_popup $ui_comm_ctxm %X %Y]
3445         } else {
3446                 set ui_comm_spell [spellcheck::init \
3447                         $spell_fd \
3448                         $ui_comm \
3449                         $ui_comm_ctxm \
3450                 ]
3451         }
3452         unset -nocomplain spell_cmd spell_fd spell_err spell_dict
3453 }
3454
3455 lock_index begin-read
3456 if {![winfo ismapped .]} {
3457         wm deiconify .
3458 }
3459 after 1 {
3460         if {[is_enabled initialamend]} {
3461                 force_amend
3462         } else {
3463                 do_rescan
3464         }
3465
3466         if {[is_enabled nocommitmsg]} {
3467                 $ui_comm configure -state disabled -background gray
3468         }
3469 }
3470 if {[is_enabled multicommit]} {
3471         after 1000 hint_gc
3472 }
3473 if {[is_enabled retcode]} {
3474         bind . <Destroy> {+terminate_me %W}
3475 }
3476 if {$picked && [is_config_true gui.autoexplore]} {
3477         do_explore
3478 }