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