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