git-gui: Display error dialog on Mac OS X when no .git found.
[git/git.git] / git-gui
CommitLineData
cb07fc2a
SP
1#!/bin/sh
2# Tcl ignores the next line -*- tcl -*- \
3exec wish "$0" -- "$@"
4
5# Copyright (C) 2006 Shawn Pearce, Paul Mackerras. All rights reserved.
6# This program is free software; it may be used, copied, modified
7# and distributed under the terms of the GNU General Public Licence,
8# either version 2, or (at your option) any later version.
9
da5239dc
SP
10set appname [lindex [file split $argv0] end]
11set gitdir {}
12
2d19516d
SP
13######################################################################
14##
15## config
16
51f4d16b
SP
17proc is_many_config {name} {
18 switch -glob -- $name {
19 remote.*.fetch -
20 remote.*.push
21 {return 1}
22 *
23 {return 0}
24 }
25}
2d19516d 26
6bbd1cb9 27proc load_config {include_global} {
51f4d16b
SP
28 global repo_config global_config default_config
29
30 array unset global_config
6bbd1cb9
SP
31 if {$include_global} {
32 catch {
33 set fd_rc [open "| git repo-config --global --list" r]
34 while {[gets $fd_rc line] >= 0} {
35 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
36 if {[is_many_config $name]} {
37 lappend global_config($name) $value
38 } else {
39 set global_config($name) $value
40 }
51f4d16b
SP
41 }
42 }
6bbd1cb9 43 close $fd_rc
51f4d16b 44 }
51f4d16b 45 }
6bbd1cb9
SP
46
47 array unset repo_config
2d19516d
SP
48 catch {
49 set fd_rc [open "| git repo-config --list" r]
50 while {[gets $fd_rc line] >= 0} {
51 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
51f4d16b
SP
52 if {[is_many_config $name]} {
53 lappend repo_config($name) $value
54 } else {
55 set repo_config($name) $value
56 }
2d19516d
SP
57 }
58 }
59 close $fd_rc
60 }
61
51f4d16b
SP
62 foreach name [array names default_config] {
63 if {[catch {set v $global_config($name)}]} {
64 set global_config($name) $default_config($name)
65 }
66 if {[catch {set v $repo_config($name)}]} {
67 set repo_config($name) $default_config($name)
68 }
2d19516d
SP
69 }
70}
71
51f4d16b 72proc save_config {} {
92148d80
SP
73 global default_config font_descs
74 global repo_config global_config
51f4d16b 75 global repo_config_new global_config_new
2d19516d 76
92148d80
SP
77 foreach option $font_descs {
78 set name [lindex $option 0]
79 set font [lindex $option 1]
80 font configure $font \
81 -family $global_config_new(gui.$font^^family) \
82 -size $global_config_new(gui.$font^^size)
83 font configure ${font}bold \
84 -family $global_config_new(gui.$font^^family) \
85 -size $global_config_new(gui.$font^^size)
86 set global_config_new(gui.$name) [font configure $font]
87 unset global_config_new(gui.$font^^family)
88 unset global_config_new(gui.$font^^size)
89 }
90
91 foreach name [array names default_config] {
51f4d16b 92 set value $global_config_new($name)
043f7011
SP
93 if {$value ne $global_config($name)} {
94 if {$value eq $default_config($name)} {
51f4d16b
SP
95 catch {exec git repo-config --global --unset $name}
96 } else {
7b64d0b7
SP
97 regsub -all "\[{}\]" $value {"} value
98 exec git repo-config --global $name $value
51f4d16b
SP
99 }
100 set global_config($name) $value
043f7011 101 if {$value eq $repo_config($name)} {
51f4d16b
SP
102 catch {exec git repo-config --unset $name}
103 set repo_config($name) $value
104 }
105 }
2d19516d
SP
106 }
107
92148d80 108 foreach name [array names default_config] {
51f4d16b 109 set value $repo_config_new($name)
043f7011
SP
110 if {$value ne $repo_config($name)} {
111 if {$value eq $global_config($name)} {
51f4d16b
SP
112 catch {exec git repo-config --unset $name}
113 } else {
7b64d0b7
SP
114 regsub -all "\[{}\]" $value {"} value
115 exec git repo-config $name $value
51f4d16b
SP
116 }
117 set repo_config($name) $value
118 }
2d19516d
SP
119 }
120}
121
da5239dc
SP
122proc error_popup {msg} {
123 global gitdir appname
124
125 set title $appname
043f7011 126 if {$gitdir ne {}} {
da5239dc
SP
127 append title { (}
128 append title [lindex \
129 [file split [file normalize [file dirname $gitdir]]] \
130 end]
131 append title {)}
132 }
cbbaa28b 133 set cmd [list tk_messageBox \
da5239dc
SP
134 -icon error \
135 -type ok \
136 -title "$title: error" \
cbbaa28b
SP
137 -message $msg]
138 if {[winfo ismapped .]} {
139 lappend cmd -parent .
140 }
141 eval $cmd
da5239dc
SP
142}
143
16403d0b
SP
144proc info_popup {msg} {
145 global gitdir appname
146
147 set title $appname
043f7011 148 if {$gitdir ne {}} {
16403d0b
SP
149 append title { (}
150 append title [lindex \
151 [file split [file normalize [file dirname $gitdir]]] \
152 end]
153 append title {)}
154 }
155 tk_messageBox \
156 -parent . \
157 -icon error \
158 -type ok \
159 -title $title \
160 -message $msg
161}
162
2d19516d
SP
163######################################################################
164##
165## repository setup
166
fbee8500
SP
167if { [catch {set gitdir $env(GIT_DIR)}]
168 && [catch {set gitdir [exec git rev-parse --git-dir]} err]} {
44be340e
SP
169 catch {wm withdraw .}
170 error_popup "Cannot find the git directory:\n\n$err"
2d19516d
SP
171 exit 1
172}
dbccbbda
SP
173if {![file isdirectory $gitdir]} {
174 catch {wm withdraw .}
175 error_popup "Git directory not found:\n\n$gitdir"
176 exit 1
177}
178if {[lindex [file split $gitdir] end] ne {.git}} {
179 catch {wm withdraw .}
180 error_popup "Cannot use funny .git directory:\n\n$gitdir"
181 exit 1
182}
fbee8500
SP
183if {[catch {cd [file dirname $gitdir]} err]} {
184 catch {wm withdraw .}
185 error_popup "No working directory [file dirname $gitdir]:\n\n$err"
186 exit 1
2d19516d 187}
2d19516d 188
4ccdab02 189set single_commit 0
043f7011 190if {$appname eq {git-citool}} {
2d19516d
SP
191 set single_commit 1
192}
193
cb07fc2a
SP
194######################################################################
195##
e210e674 196## task management
cb07fc2a 197
8f52548a 198set rescan_active 0
131f503b 199set diff_active 0
24263b77 200set last_clicked {}
131f503b 201
e210e674
SP
202set disable_on_lock [list]
203set index_lock_type none
204
e57ca85e
SP
205set HEAD {}
206set PARENT {}
207set commit_type {}
208
e210e674
SP
209proc lock_index {type} {
210 global index_lock_type disable_on_lock
131f503b 211
043f7011 212 if {$index_lock_type eq {none}} {
e210e674
SP
213 set index_lock_type $type
214 foreach w $disable_on_lock {
215 uplevel #0 $w disabled
216 }
217 return 1
043f7011 218 } elseif {$index_lock_type eq {begin-update} && $type eq {update}} {
e210e674 219 set index_lock_type $type
131f503b
SP
220 return 1
221 }
222 return 0
223}
cb07fc2a 224
e210e674
SP
225proc unlock_index {} {
226 global index_lock_type disable_on_lock
227
228 set index_lock_type none
229 foreach w $disable_on_lock {
230 uplevel #0 $w normal
231 }
232}
233
234######################################################################
235##
236## status
237
ec6b424a
SP
238proc repository_state {hdvar ctvar} {
239 global gitdir
240 upvar $hdvar hd $ctvar ct
241
242 if {[catch {set hd [exec git rev-parse --verify HEAD]}]} {
243 set ct initial
244 } elseif {[file exists [file join $gitdir MERGE_HEAD]]} {
245 set ct merge
246 } else {
247 set ct normal
248 }
249}
250
8f52548a 251proc rescan {after} {
e57ca85e 252 global HEAD PARENT commit_type
131f503b 253 global ui_index ui_other ui_status_value ui_comm
8f52548a 254 global rescan_active file_states
51f4d16b 255 global repo_config
cb07fc2a 256
8f52548a 257 if {$rescan_active > 0 || ![lock_index read]} return
cb07fc2a 258
e57ca85e 259 repository_state new_HEAD new_type
043f7011
SP
260 if {$commit_type eq {amend}
261 && $new_type eq {normal}
262 && $new_HEAD eq $HEAD} {
e57ca85e
SP
263 } else {
264 set HEAD $new_HEAD
265 set PARENT $new_HEAD
266 set commit_type $new_type
267 }
268
cb07fc2a 269 array unset file_states
cb07fc2a 270
131f503b 271 if {![$ui_comm edit modified]
043f7011 272 || [string trim [$ui_comm get 0.0 end]] eq {}} {
131f503b
SP
273 if {[load_message GITGUI_MSG]} {
274 } elseif {[load_message MERGE_MSG]} {
275 } elseif {[load_message SQUASH_MSG]} {
276 }
277 $ui_comm edit modified false
b2c6fcf1 278 $ui_comm edit reset
131f503b
SP
279 }
280
043f7011 281 if {$repo_config(gui.trustmtime) eq {true}} {
8f52548a 282 rescan_stage2 {} $after
e534f3a8 283 } else {
8f52548a 284 set rescan_active 1
e534f3a8 285 set ui_status_value {Refreshing file status...}
16403d0b
SP
286 set cmd [list git update-index]
287 lappend cmd -q
288 lappend cmd --unmerged
289 lappend cmd --ignore-missing
290 lappend cmd --refresh
291 set fd_rf [open "| $cmd" r]
e534f3a8 292 fconfigure $fd_rf -blocking 0 -translation binary
390adaea 293 fileevent $fd_rf readable \
8f52548a 294 [list rescan_stage2 $fd_rf $after]
e534f3a8 295 }
131f503b
SP
296}
297
8f52548a 298proc rescan_stage2 {fd after} {
e57ca85e 299 global gitdir PARENT commit_type
131f503b 300 global ui_index ui_other ui_status_value ui_comm
8f52548a 301 global rescan_active
868c8752 302 global buf_rdi buf_rdf buf_rlo
131f503b 303
043f7011 304 if {$fd ne {}} {
e534f3a8
SP
305 read $fd
306 if {![eof $fd]} return
307 close $fd
308 }
131f503b 309
cb07fc2a
SP
310 set ls_others [list | git ls-files --others -z \
311 --exclude-per-directory=.gitignore]
312 set info_exclude [file join $gitdir info exclude]
313 if {[file readable $info_exclude]} {
314 lappend ls_others "--exclude-from=$info_exclude"
315 }
316
868c8752
SP
317 set buf_rdi {}
318 set buf_rdf {}
319 set buf_rlo {}
320
8f52548a 321 set rescan_active 3
131f503b 322 set ui_status_value {Scanning for modified files ...}
e57ca85e 323 set fd_di [open "| git diff-index --cached -z $PARENT" r]
cb07fc2a
SP
324 set fd_df [open "| git diff-files -z" r]
325 set fd_lo [open $ls_others r]
cb07fc2a
SP
326
327 fconfigure $fd_di -blocking 0 -translation binary
328 fconfigure $fd_df -blocking 0 -translation binary
329 fconfigure $fd_lo -blocking 0 -translation binary
8f52548a
SP
330 fileevent $fd_di readable [list read_diff_index $fd_di $after]
331 fileevent $fd_df readable [list read_diff_files $fd_df $after]
332 fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
cb07fc2a
SP
333}
334
131f503b
SP
335proc load_message {file} {
336 global gitdir ui_comm
337
338 set f [file join $gitdir $file]
e57ca85e 339 if {[file isfile $f]} {
131f503b
SP
340 if {[catch {set fd [open $f r]}]} {
341 return 0
342 }
e57ca85e 343 set content [string trim [read $fd]]
131f503b
SP
344 close $fd
345 $ui_comm delete 0.0 end
346 $ui_comm insert end $content
347 return 1
348 }
349 return 0
350}
351
8f52548a 352proc read_diff_index {fd after} {
cb07fc2a
SP
353 global buf_rdi
354
355 append buf_rdi [read $fd]
868c8752
SP
356 set c 0
357 set n [string length $buf_rdi]
358 while {$c < $n} {
359 set z1 [string first "\0" $buf_rdi $c]
360 if {$z1 == -1} break
361 incr z1
362 set z2 [string first "\0" $buf_rdi $z1]
363 if {$z2 == -1} break
364
365 set c $z2
366 incr z2 -1
367 display_file \
368 [string range $buf_rdi $z1 $z2] \
24263b77 369 [string index $buf_rdi [expr {$z1 - 2}]]_
868c8752 370 incr c
cb07fc2a 371 }
868c8752
SP
372 if {$c < $n} {
373 set buf_rdi [string range $buf_rdi $c end]
374 } else {
375 set buf_rdi {}
376 }
377
8f52548a 378 rescan_done $fd buf_rdi $after
cb07fc2a
SP
379}
380
8f52548a 381proc read_diff_files {fd after} {
cb07fc2a
SP
382 global buf_rdf
383
384 append buf_rdf [read $fd]
868c8752
SP
385 set c 0
386 set n [string length $buf_rdf]
387 while {$c < $n} {
388 set z1 [string first "\0" $buf_rdf $c]
389 if {$z1 == -1} break
390 incr z1
391 set z2 [string first "\0" $buf_rdf $z1]
392 if {$z2 == -1} break
393
394 set c $z2
395 incr z2 -1
396 display_file \
397 [string range $buf_rdf $z1 $z2] \
24263b77 398 _[string index $buf_rdf [expr {$z1 - 2}]]
868c8752
SP
399 incr c
400 }
401 if {$c < $n} {
402 set buf_rdf [string range $buf_rdf $c end]
403 } else {
404 set buf_rdf {}
cb07fc2a 405 }
868c8752 406
8f52548a 407 rescan_done $fd buf_rdf $after
cb07fc2a
SP
408}
409
8f52548a 410proc read_ls_others {fd after} {
cb07fc2a
SP
411 global buf_rlo
412
413 append buf_rlo [read $fd]
414 set pck [split $buf_rlo "\0"]
415 set buf_rlo [lindex $pck end]
416 foreach p [lrange $pck 0 end-1] {
417 display_file $p _O
418 }
8f52548a 419 rescan_done $fd buf_rlo $after
cb07fc2a
SP
420}
421
8f52548a
SP
422proc rescan_done {fd buf after} {
423 global rescan_active
f7f8d322 424 global file_states repo_config
7f1df79b 425 upvar $buf to_clear
cb07fc2a 426
f7f8d322
SP
427 if {![eof $fd]} return
428 set to_clear {}
429 close $fd
8f52548a 430 if {[incr rescan_active -1] > 0} return
93f654df 431
24263b77 432 prune_selection
f7f8d322
SP
433 unlock_index
434 display_all_files
435
436 if {$repo_config(gui.partialinclude) ne {true}} {
bbe3b3b9 437 set pathList [list]
f7f8d322
SP
438 foreach path [array names file_states] {
439 switch -- [lindex $file_states($path) 0] {
440 AM -
441 MM {lappend pathList $path}
442 }
443 }
444 if {$pathList ne {}} {
04b39382
SP
445 update_index \
446 "Updating included files" \
447 $pathList \
448 [concat {reshow_diff;} $after]
f7f8d322 449 return
cb07fc2a
SP
450 }
451 }
f7f8d322
SP
452
453 reshow_diff
8f52548a 454 uplevel #0 $after
cb07fc2a
SP
455}
456
24263b77
SP
457proc prune_selection {} {
458 global file_states selected_paths
459
460 foreach path [array names selected_paths] {
461 if {[catch {set still_here $file_states($path)}]} {
462 unset selected_paths($path)
463 }
464 }
465}
466
cb07fc2a
SP
467######################################################################
468##
469## diff
470
cb07fc2a 471proc clear_diff {} {
e8ab6446 472 global ui_diff current_diff ui_index ui_other
cb07fc2a
SP
473
474 $ui_diff conf -state normal
475 $ui_diff delete 0.0 end
476 $ui_diff conf -state disabled
03e4ec53 477
e8ab6446 478 set current_diff {}
03e4ec53
SP
479
480 $ui_index tag remove in_diff 0.0 end
481 $ui_other tag remove in_diff 0.0 end
cb07fc2a
SP
482}
483
7f1df79b 484proc reshow_diff {} {
e8ab6446 485 global current_diff ui_status_value file_states
7f1df79b 486
e8ab6446
SP
487 if {$current_diff eq {}
488 || [catch {set s $file_states($current_diff)}]} {
7f1df79b 489 clear_diff
73ad179b 490 } else {
e8ab6446 491 show_diff $current_diff
7f1df79b
SP
492 }
493}
494
16403d0b 495proc handle_empty_diff {} {
e8ab6446 496 global current_diff file_states file_lists
16403d0b 497
e8ab6446 498 set path $current_diff
16403d0b 499 set s $file_states($path)
043f7011 500 if {[lindex $s 0] ne {_M}} return
16403d0b
SP
501
502 info_popup "No differences detected.
503
504[short_path $path] has no changes.
505
a37eee44
SP
506The modification date of this file was updated
507by another application and you currently have
508the Trust File Modification Timestamps option
509enabled, so Git did not automatically detect
510that there are no content differences in this
511file.
512
513This file will now be removed from the modified
514files list, to prevent possible confusion.
16403d0b
SP
515"
516 if {[catch {exec git update-index -- $path} err]} {
517 error_popup "Failed to refresh index:\n\n$err"
518 }
519
520 clear_diff
521 set old_w [mapcol [lindex $file_states($path) 0] $path]
522 set lno [lsearch -sorted $file_lists($old_w) $path]
523 if {$lno >= 0} {
524 set file_lists($old_w) \
525 [lreplace $file_lists($old_w) $lno $lno]
526 incr lno
527 $old_w conf -state normal
24263b77 528 $old_w delete $lno.0 [expr {$lno + 1}].0
16403d0b
SP
529 $old_w conf -state disabled
530 }
531}
532
03e4ec53
SP
533proc show_diff {path {w {}} {lno {}}} {
534 global file_states file_lists
fd2656fd 535 global PARENT diff_3way diff_active repo_config
e8ab6446 536 global ui_diff current_diff ui_status_value
cb07fc2a 537
e210e674 538 if {$diff_active || ![lock_index read]} return
cb07fc2a
SP
539
540 clear_diff
043f7011 541 if {$w eq {} || $lno == {}} {
03e4ec53
SP
542 foreach w [array names file_lists] {
543 set lno [lsearch -sorted $file_lists($w) $path]
544 if {$lno >= 0} {
545 incr lno
546 break
547 }
548 }
549 }
043f7011 550 if {$w ne {} && $lno >= 1} {
24263b77 551 $w tag add in_diff $lno.0 [expr {$lno + 1}].0
03e4ec53
SP
552 }
553
cb07fc2a
SP
554 set s $file_states($path)
555 set m [lindex $s 0]
556 set diff_3way 0
557 set diff_active 1
e8ab6446 558 set current_diff $path
68e009de 559 set ui_status_value "Loading diff of [escape_path $path]..."
cb07fc2a 560
fd2656fd
SP
561 set cmd [list | git diff-index]
562 lappend cmd --no-color
358d8de8
SP
563 if {$repo_config(gui.diffcontext) > 0} {
564 lappend cmd "-U$repo_config(gui.diffcontext)"
565 }
fd2656fd
SP
566 lappend cmd -p
567
cb07fc2a 568 switch $m {
cb07fc2a 569 MM {
fd2656fd 570 lappend cmd -c
cb07fc2a
SP
571 }
572 _O {
573 if {[catch {
574 set fd [open $path r]
575 set content [read $fd]
576 close $fd
577 } err ]} {
131f503b 578 set diff_active 0
e210e674 579 unlock_index
68e009de 580 set ui_status_value "Unable to display [escape_path $path]"
44be340e 581 error_popup "Error loading file:\n\n$err"
cb07fc2a
SP
582 return
583 }
584 $ui_diff conf -state normal
585 $ui_diff insert end $content
586 $ui_diff conf -state disabled
bd1e2b40
SP
587 set diff_active 0
588 unlock_index
589 set ui_status_value {Ready.}
cb07fc2a
SP
590 return
591 }
592 }
593
fd2656fd
SP
594 lappend cmd $PARENT
595 lappend cmd --
596 lappend cmd $path
597
cb07fc2a 598 if {[catch {set fd [open $cmd r]} err]} {
131f503b 599 set diff_active 0
e210e674 600 unlock_index
68e009de 601 set ui_status_value "Unable to display [escape_path $path]"
44be340e 602 error_popup "Error loading diff:\n\n$err"
cb07fc2a
SP
603 return
604 }
605
6f6eed28 606 fconfigure $fd -blocking 0 -translation auto
cb07fc2a
SP
607 fileevent $fd readable [list read_diff $fd]
608}
609
610proc read_diff {fd} {
611 global ui_diff ui_status_value diff_3way diff_active
51f4d16b 612 global repo_config
cb07fc2a
SP
613
614 while {[gets $fd line] >= 0} {
6f6eed28
SP
615 if {[string match {diff --git *} $line]} continue
616 if {[string match {diff --combined *} $line]} continue
617 if {[string match {--- *} $line]} continue
618 if {[string match {+++ *} $line]} continue
cb07fc2a
SP
619 if {[string match index* $line]} {
620 if {[string first , $line] >= 0} {
621 set diff_3way 1
622 }
623 }
624
625 $ui_diff conf -state normal
626 if {!$diff_3way} {
627 set x [string index $line 0]
628 switch -- $x {
629 "@" {set tags da}
630 "+" {set tags dp}
631 "-" {set tags dm}
632 default {set tags {}}
633 }
634 } else {
635 set x [string range $line 0 1]
636 switch -- $x {
637 default {set tags {}}
638 "@@" {set tags da}
639 "++" {set tags dp; set x " +"}
640 " +" {set tags {di bold}; set x "++"}
641 "+ " {set tags dni; set x "-+"}
642 "--" {set tags dm; set x " -"}
643 " -" {set tags {dm bold}; set x "--"}
644 "- " {set tags di; set x "+-"}
645 default {set tags {}}
646 }
647 set line [string replace $line 0 1 $x]
648 }
649 $ui_diff insert end $line $tags
650 $ui_diff insert end "\n"
651 $ui_diff conf -state disabled
652 }
653
654 if {[eof $fd]} {
655 close $fd
656 set diff_active 0
e210e674 657 unlock_index
cb07fc2a 658 set ui_status_value {Ready.}
16403d0b 659
043f7011
SP
660 if {$repo_config(gui.trustmtime) eq {true}
661 && [$ui_diff index end] eq {2.0}} {
16403d0b
SP
662 handle_empty_diff
663 }
cb07fc2a
SP
664 }
665}
666
ec6b424a
SP
667######################################################################
668##
669## commit
670
e57ca85e
SP
671proc load_last_commit {} {
672 global HEAD PARENT commit_type ui_comm
673
043f7011
SP
674 if {$commit_type eq {amend}} return
675 if {$commit_type ne {normal}} {
e57ca85e
SP
676 error_popup "Can't amend a $commit_type commit."
677 return
678 }
679
680 set msg {}
681 set parent {}
682 set parent_count 0
683 if {[catch {
684 set fd [open "| git cat-file commit $HEAD" r]
685 while {[gets $fd line] > 0} {
686 if {[string match {parent *} $line]} {
687 set parent [string range $line 7 end]
688 incr parent_count
689 }
690 }
691 set msg [string trim [read $fd]]
692 close $fd
693 } err]} {
44be340e 694 error_popup "Error loading commit data for amend:\n\n$err"
e57ca85e
SP
695 return
696 }
697
698 if {$parent_count == 0} {
699 set commit_type amend
700 set HEAD {}
701 set PARENT {}
8f52548a 702 rescan {set ui_status_value {Ready.}}
e57ca85e
SP
703 } elseif {$parent_count == 1} {
704 set commit_type amend
705 set PARENT $parent
706 $ui_comm delete 0.0 end
707 $ui_comm insert end $msg
708 $ui_comm edit modified false
b2c6fcf1 709 $ui_comm edit reset
8f52548a 710 rescan {set ui_status_value {Ready.}}
e57ca85e
SP
711 } else {
712 error_popup {You can't amend a merge commit.}
713 return
714 }
715}
716
ec6b424a 717proc commit_tree {} {
bbe3b3b9 718 global HEAD commit_type file_states ui_comm repo_config
ec6b424a 719
333b0c74 720 if {![lock_index update]} return
ec6b424a
SP
721
722 # -- Our in memory state should match the repository.
723 #
724 repository_state curHEAD cur_type
043f7011
SP
725 if {$commit_type eq {amend}
726 && $cur_type eq {normal}
727 && $curHEAD eq $HEAD} {
728 } elseif {$commit_type ne $cur_type || $HEAD ne $curHEAD} {
ec6b424a
SP
729 error_popup {Last scanned state does not match repository state.
730
731Its highly likely that another Git program modified the
bbe3b3b9 732repository since the last scan. A rescan is required
ec6b424a 733before committing.
bbe3b3b9
SP
734
735A rescan will be automatically started now.
ec6b424a
SP
736}
737 unlock_index
8f52548a 738 rescan {set ui_status_value {Ready.}}
ec6b424a
SP
739 return
740 }
741
742 # -- At least one file should differ in the index.
743 #
744 set files_ready 0
745 foreach path [array names file_states] {
bbe3b3b9 746 switch -glob -- [lindex $file_states($path) 0] {
7f1df79b
SP
747 _? {continue}
748 A? -
749 D? -
750 M? {set files_ready 1; break}
751 U? {
ec6b424a
SP
752 error_popup "Unmerged files cannot be committed.
753
16403d0b 754File [short_path $path] has merge conflicts.
7fe7e733 755You must resolve them and include the file before committing.
ec6b424a
SP
756"
757 unlock_index
758 return
759 }
760 default {
761 error_popup "Unknown file state [lindex $s 0] detected.
762
16403d0b 763File [short_path $path] cannot be committed by this program.
ec6b424a
SP
764"
765 }
766 }
767 }
768 if {!$files_ready} {
7fe7e733 769 error_popup {No included files to commit.
ec6b424a 770
7fe7e733 771You must include at least 1 file before you can commit.
ec6b424a
SP
772}
773 unlock_index
774 return
775 }
776
777 # -- A message is required.
778 #
779 set msg [string trim [$ui_comm get 1.0 end]]
043f7011 780 if {$msg eq {}} {
ec6b424a
SP
781 error_popup {Please supply a commit message.
782
783A good commit message has the following format:
784
785- First line: Describe in one sentance what you did.
786- Second line: Blank
787- Remaining lines: Describe why this change is good.
788}
789 unlock_index
790 return
791 }
792
bbe3b3b9 793 # -- Update included files if partialincludes are off.
ec6b424a 794 #
bbe3b3b9
SP
795 if {$repo_config(gui.partialinclude) ne {true}} {
796 set pathList [list]
797 foreach path [array names file_states] {
798 switch -glob -- [lindex $file_states($path) 0] {
799 A? -
800 M? {lappend pathList $path}
801 }
802 }
803 if {$pathList ne {}} {
804 unlock_index
805 update_index \
806 "Updating included files" \
807 $pathList \
808 [concat {lock_index update;} \
809 [list commit_prehook $curHEAD $msg]]
810 return
811 }
812 }
813
814 commit_prehook $curHEAD $msg
815}
816
817proc commit_prehook {curHEAD msg} {
818 global tcl_platform gitdir ui_status_value pch_error
819
820 # On Cygwin [file executable] might lie so we need to ask
821 # the shell if the hook is executable. Yes that's annoying.
822
ec6b424a 823 set pchook [file join $gitdir hooks pre-commit]
bbe3b3b9
SP
824 if {$tcl_platform(platform) eq {windows}
825 && [file isfile $pchook]} {
4658b56f
SP
826 set pchook [list sh -c [concat \
827 "if test -x \"$pchook\";" \
828 "then exec \"$pchook\" 2>&1;" \
829 "fi"]]
ec6b424a 830 } elseif {[file executable $pchook]} {
4658b56f 831 set pchook [list $pchook |& cat]
ec6b424a 832 } else {
bbe3b3b9
SP
833 commit_writetree $curHEAD $msg
834 return
4658b56f 835 }
bbe3b3b9
SP
836
837 set ui_status_value {Calling pre-commit hook...}
838 set pch_error {}
839 set fd_ph [open "| $pchook" r]
840 fconfigure $fd_ph -blocking 0 -translation binary
841 fileevent $fd_ph readable \
842 [list commit_prehook_wait $fd_ph $curHEAD $msg]
4658b56f
SP
843}
844
bbe3b3b9 845proc commit_prehook_wait {fd_ph curHEAD msg} {
333b0c74 846 global pch_error ui_status_value
4658b56f
SP
847
848 append pch_error [read $fd_ph]
849 fconfigure $fd_ph -blocking 1
850 if {[eof $fd_ph]} {
851 if {[catch {close $fd_ph}]} {
852 set ui_status_value {Commit declined by pre-commit hook.}
853 hook_failed_popup pre-commit $pch_error
854 unlock_index
333b0c74 855 } else {
bbe3b3b9 856 commit_writetree $curHEAD $msg
4658b56f 857 }
333b0c74 858 set pch_error {}
bbe3b3b9 859 return
ec6b424a 860 }
bbe3b3b9 861 fconfigure $fd_ph -blocking 0
4658b56f
SP
862}
863
bbe3b3b9 864proc commit_writetree {curHEAD msg} {
4658b56f 865 global ui_status_value
ec6b424a 866
ec6b424a 867 set ui_status_value {Committing changes...}
ec6b424a 868 set fd_wt [open "| git write-tree" r]
bbe3b3b9
SP
869 fileevent $fd_wt readable \
870 [list commit_committree $fd_wt $curHEAD $msg]
ec6b424a
SP
871}
872
bbe3b3b9 873proc commit_committree {fd_wt curHEAD msg} {
c8ebafd8 874 global single_commit gitdir HEAD PARENT commit_type tcl_platform
333b0c74 875 global ui_status_value ui_comm
24263b77 876 global file_states selected_paths
ec6b424a
SP
877
878 gets $fd_wt tree_id
043f7011 879 if {$tree_id eq {} || [catch {close $fd_wt} err]} {
44be340e 880 error_popup "write-tree failed:\n\n$err"
ec6b424a
SP
881 set ui_status_value {Commit failed.}
882 unlock_index
883 return
884 }
885
886 # -- Create the commit.
887 #
888 set cmd [list git commit-tree $tree_id]
043f7011 889 if {$PARENT ne {}} {
e57ca85e 890 lappend cmd -p $PARENT
ec6b424a 891 }
043f7011 892 if {$commit_type eq {merge}} {
ec6b424a
SP
893 if {[catch {
894 set fd_mh [open [file join $gitdir MERGE_HEAD] r]
bd1e2b40
SP
895 while {[gets $fd_mh merge_head] >= 0} {
896 lappend cmd -p $merge_head
ec6b424a
SP
897 }
898 close $fd_mh
899 } err]} {
44be340e 900 error_popup "Loading MERGE_HEAD failed:\n\n$err"
ec6b424a
SP
901 set ui_status_value {Commit failed.}
902 unlock_index
903 return
904 }
905 }
043f7011 906 if {$PARENT eq {}} {
ec6b424a
SP
907 # git commit-tree writes to stderr during initial commit.
908 lappend cmd 2>/dev/null
909 }
910 lappend cmd << $msg
911 if {[catch {set cmt_id [eval exec $cmd]} err]} {
44be340e 912 error_popup "commit-tree failed:\n\n$err"
ec6b424a
SP
913 set ui_status_value {Commit failed.}
914 unlock_index
915 return
916 }
917
918 # -- Update the HEAD ref.
919 #
920 set reflogm commit
043f7011 921 if {$commit_type ne {normal}} {
ec6b424a
SP
922 append reflogm " ($commit_type)"
923 }
924 set i [string first "\n" $msg]
925 if {$i >= 0} {
24263b77 926 append reflogm {: } [string range $msg 0 [expr {$i - 1}]]
ec6b424a
SP
927 } else {
928 append reflogm {: } $msg
929 }
e57ca85e 930 set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD]
ec6b424a 931 if {[catch {eval exec $cmd} err]} {
44be340e 932 error_popup "update-ref failed:\n\n$err"
ec6b424a
SP
933 set ui_status_value {Commit failed.}
934 unlock_index
935 return
936 }
937
938 # -- Cleanup after ourselves.
939 #
940 catch {file delete [file join $gitdir MERGE_HEAD]}
941 catch {file delete [file join $gitdir MERGE_MSG]}
942 catch {file delete [file join $gitdir SQUASH_MSG]}
943 catch {file delete [file join $gitdir GITGUI_MSG]}
944
945 # -- Let rerere do its thing.
946 #
947 if {[file isdirectory [file join $gitdir rr-cache]]} {
948 catch {exec git rerere}
949 }
950
c8ebafd8
SP
951 # -- Run the post-commit hook.
952 #
953 set pchook [file join $gitdir hooks post-commit]
043f7011 954 if {$tcl_platform(platform) eq {windows} && [file isfile $pchook]} {
c8ebafd8
SP
955 set pchook [list sh -c [concat \
956 "if test -x \"$pchook\";" \
957 "then exec \"$pchook\";" \
958 "fi"]]
959 } elseif {![file executable $pchook]} {
960 set pchook {}
961 }
043f7011 962 if {$pchook ne {}} {
c8ebafd8
SP
963 catch {exec $pchook &}
964 }
965
e57ca85e
SP
966 $ui_comm delete 0.0 end
967 $ui_comm edit modified false
b2c6fcf1 968 $ui_comm edit reset
ec6b424a
SP
969
970 if {$single_commit} do_quit
971
7f1df79b
SP
972 # -- Update status without invoking any git commands.
973 #
7f1df79b 974 set commit_type normal
bd1e2b40
SP
975 set HEAD $cmt_id
976 set PARENT $cmt_id
7f1df79b
SP
977
978 foreach path [array names file_states] {
979 set s $file_states($path)
980 set m [lindex $s 0]
981 switch -glob -- $m {
982 A? -
983 M? -
984 D? {set m _[string index $m 1]}
985 }
986
043f7011 987 if {$m eq {__}} {
7f1df79b 988 unset file_states($path)
24263b77 989 catch {unset selected_paths($path)}
7f1df79b
SP
990 } else {
991 lset file_states($path) 0 $m
992 }
993 }
994
995 display_all_files
ec6b424a 996 unlock_index
7f1df79b
SP
997 reshow_diff
998 set ui_status_value \
999 "Changes committed as [string range $cmt_id 0 7]."
ec6b424a
SP
1000}
1001
8c0ce436
SP
1002######################################################################
1003##
1004## fetch pull push
1005
1006proc fetch_from {remote} {
1007 set w [new_console "fetch $remote" \
1008 "Fetching new changes from $remote"]
cc4b1c02 1009 set cmd [list git fetch]
8c0ce436 1010 lappend cmd $remote
cc4b1c02 1011 console_exec $w $cmd
8c0ce436
SP
1012}
1013
d33ba5fa 1014proc pull_remote {remote branch} {
ebf336b9 1015 global HEAD commit_type file_states repo_config
ec39d83a 1016
988b8a7d 1017 if {![lock_index update]} return
ec39d83a
SP
1018
1019 # -- Our in memory state should match the repository.
1020 #
1021 repository_state curHEAD cur_type
043f7011 1022 if {$commit_type ne $cur_type || $HEAD ne $curHEAD} {
ec39d83a
SP
1023 error_popup {Last scanned state does not match repository state.
1024
1025Its highly likely that another Git program modified the
1026repository since our last scan. A rescan is required
1027before a pull can be started.
1028}
1029 unlock_index
8f52548a 1030 rescan {set ui_status_value {Ready.}}
ec39d83a
SP
1031 return
1032 }
1033
1034 # -- No differences should exist before a pull.
1035 #
1036 if {[array size file_states] != 0} {
1037 error_popup {Uncommitted but modified files are present.
1038
1039You should not perform a pull with unmodified files in your working
1040directory as Git would be unable to recover from an incorrect merge.
1041
1042Commit or throw away all changes before starting a pull operation.
1043}
1044 unlock_index
1045 return
1046 }
1047
d33ba5fa
SP
1048 set w [new_console "pull $remote $branch" \
1049 "Pulling new changes from branch $branch in $remote"]
1050 set cmd [list git pull]
043f7011 1051 if {$repo_config(gui.pullsummary) eq {false}} {
ebf336b9
SP
1052 lappend cmd --no-summary
1053 }
d33ba5fa
SP
1054 lappend cmd $remote
1055 lappend cmd $branch
1056 console_exec $w $cmd [list post_pull_remote $remote $branch]
1057}
1058
1059proc post_pull_remote {remote branch success} {
ec39d83a
SP
1060 global HEAD PARENT commit_type
1061 global ui_status_value
1062
988b8a7d 1063 unlock_index
d33ba5fa 1064 if {$success} {
ec39d83a
SP
1065 repository_state HEAD commit_type
1066 set PARENT $HEAD
8f52548a 1067 set $ui_status_value "Pulling $branch from $remote complete."
d33ba5fa 1068 } else {
8f52548a
SP
1069 set m "Conflicts detected while pulling $branch from $remote."
1070 rescan "set ui_status_value {$m}"
d33ba5fa
SP
1071 }
1072}
1073
8c0ce436
SP
1074proc push_to {remote} {
1075 set w [new_console "push $remote" \
1076 "Pushing changes to $remote"]
cc4b1c02 1077 set cmd [list git push]
8c0ce436 1078 lappend cmd $remote
cc4b1c02 1079 console_exec $w $cmd
8c0ce436
SP
1080}
1081
cb07fc2a
SP
1082######################################################################
1083##
1084## ui helpers
1085
1086proc mapcol {state path} {
6b292675 1087 global all_cols ui_other
cb07fc2a
SP
1088
1089 if {[catch {set r $all_cols($state)}]} {
1090 puts "error: no column for state={$state} $path"
6b292675 1091 return $ui_other
cb07fc2a
SP
1092 }
1093 return $r
1094}
1095
1096proc mapicon {state path} {
1097 global all_icons
1098
1099 if {[catch {set r $all_icons($state)}]} {
1100 puts "error: no icon for state={$state} $path"
1101 return file_plain
1102 }
1103 return $r
1104}
1105
1106proc mapdesc {state path} {
1107 global all_descs
1108
1109 if {[catch {set r $all_descs($state)}]} {
1110 puts "error: no desc for state={$state} $path"
1111 return $state
1112 }
1113 return $r
1114}
1115
68e009de
SP
1116proc escape_path {path} {
1117 regsub -all "\n" $path "\\n" path
1118 return $path
1119}
1120
16403d0b
SP
1121proc short_path {path} {
1122 return [escape_path [lindex [file split $path] end]]
1123}
1124
93f654df
SP
1125set next_icon_id 0
1126
6b292675 1127proc merge_state {path new_state} {
93f654df 1128 global file_states next_icon_id
cb07fc2a 1129
6b292675
SP
1130 set s0 [string index $new_state 0]
1131 set s1 [string index $new_state 1]
1132
1133 if {[catch {set info $file_states($path)}]} {
1134 set state __
1135 set icon n[incr next_icon_id]
cb07fc2a 1136 } else {
6b292675
SP
1137 set state [lindex $info 0]
1138 set icon [lindex $info 1]
cb07fc2a
SP
1139 }
1140
043f7011 1141 if {$s0 eq {_}} {
6b292675 1142 set s0 [string index $state 0]
043f7011 1143 } elseif {$s0 eq {*}} {
6b292675 1144 set s0 _
cb07fc2a
SP
1145 }
1146
043f7011 1147 if {$s1 eq {_}} {
6b292675 1148 set s1 [string index $state 1]
043f7011 1149 } elseif {$s1 eq {*}} {
6b292675 1150 set s1 _
cb07fc2a
SP
1151 }
1152
6b292675
SP
1153 set file_states($path) [list $s0$s1 $icon]
1154 return $state
cb07fc2a
SP
1155}
1156
1157proc display_file {path state} {
8f52548a 1158 global file_states file_lists selected_paths rescan_active
cb07fc2a
SP
1159
1160 set old_m [merge_state $path $state]
8f52548a 1161 if {$rescan_active > 0} return
93f654df 1162
cb07fc2a 1163 set s $file_states($path)
93f654df 1164 set new_m [lindex $s 0]
0fb8f9ce
SP
1165 set new_w [mapcol $new_m $path]
1166 set old_w [mapcol $old_m $path]
1167 set new_icon [mapicon $new_m $path]
cb07fc2a 1168
043f7011 1169 if {$new_w ne $old_w} {
03e4ec53 1170 set lno [lsearch -sorted $file_lists($old_w) $path]
cb07fc2a
SP
1171 if {$lno >= 0} {
1172 incr lno
93f654df 1173 $old_w conf -state normal
24263b77 1174 $old_w delete $lno.0 [expr {$lno + 1}].0
93f654df 1175 $old_w conf -state disabled
cb07fc2a 1176 }
93f654df 1177
03e4ec53
SP
1178 lappend file_lists($new_w) $path
1179 set file_lists($new_w) [lsort $file_lists($new_w)]
1180 set lno [lsearch -sorted $file_lists($new_w) $path]
1181 incr lno
93f654df
SP
1182 $new_w conf -state normal
1183 $new_w image create $lno.0 \
1184 -align center -padx 5 -pady 1 \
1185 -name [lindex $s 1] \
e4ee9af4 1186 -image $new_icon
68e009de 1187 $new_w insert $lno.1 "[escape_path $path]\n"
24263b77
SP
1188 if {[catch {set in_sel $selected_paths($path)}]} {
1189 set in_sel 0
1190 }
1191 if {$in_sel} {
1192 $new_w tag add in_sel $lno.0 [expr {$lno + 1}].0
1193 }
93f654df 1194 $new_w conf -state disabled
043f7011 1195 } elseif {$new_icon ne [mapicon $old_m $path]} {
93f654df
SP
1196 $new_w conf -state normal
1197 $new_w image conf [lindex $s 1] -image $new_icon
1198 $new_w conf -state disabled
cb07fc2a 1199 }
93f654df 1200}
cb07fc2a 1201
93f654df 1202proc display_all_files {} {
24263b77
SP
1203 global ui_index ui_other
1204 global file_states file_lists
1205 global last_clicked selected_paths
93f654df
SP
1206
1207 $ui_index conf -state normal
1208 $ui_other conf -state normal
1209
7f1df79b
SP
1210 $ui_index delete 0.0 end
1211 $ui_other delete 0.0 end
24263b77 1212 set last_clicked {}
7f1df79b 1213
62aac80b
SP
1214 set file_lists($ui_index) [list]
1215 set file_lists($ui_other) [list]
1216
93f654df
SP
1217 foreach path [lsort [array names file_states]] {
1218 set s $file_states($path)
1219 set m [lindex $s 0]
6b292675 1220 set w [mapcol $m $path]
03e4ec53 1221 lappend file_lists($w) $path
24263b77 1222 set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
6b292675 1223 $w image create end \
cb07fc2a 1224 -align center -padx 5 -pady 1 \
93f654df
SP
1225 -name [lindex $s 1] \
1226 -image [mapicon $m $path]
68e009de 1227 $w insert end "[escape_path $path]\n"
24263b77
SP
1228 if {[catch {set in_sel $selected_paths($path)}]} {
1229 set in_sel 0
1230 }
1231 if {$in_sel} {
1232 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
1233 }
cb07fc2a 1234 }
93f654df
SP
1235
1236 $ui_index conf -state disabled
1237 $ui_other conf -state disabled
cb07fc2a
SP
1238}
1239
04b39382 1240proc update_index {msg pathList after} {
2cbe5577 1241 global update_index_cp update_index_rsd ui_status_value
131f503b 1242
74e6b12f 1243 if {![lock_index update]} return
131f503b 1244
74e6b12f 1245 set update_index_cp 0
2cbe5577 1246 set update_index_rsd 0
aaf1085a 1247 set pathList [lsort $pathList]
74e6b12f
SP
1248 set totalCnt [llength $pathList]
1249 set batch [expr {int($totalCnt * .01) + 1}]
1250 if {$batch > 25} {set batch 25}
1251
74e6b12f 1252 set ui_status_value [format \
04b39382 1253 "$msg... %i/%i files (%.2f%%)" \
74e6b12f
SP
1254 $update_index_cp \
1255 $totalCnt \
1256 0.0]
1257 set fd [open "| git update-index --add --remove -z --stdin" w]
7f09cfaf
SP
1258 fconfigure $fd \
1259 -blocking 0 \
1260 -buffering full \
1261 -buffersize 512 \
1262 -translation binary
74e6b12f
SP
1263 fileevent $fd writable [list \
1264 write_update_index \
1265 $fd \
1266 $pathList \
1267 $totalCnt \
1268 $batch \
04b39382
SP
1269 $msg \
1270 $after \
74e6b12f
SP
1271 ]
1272}
1273
04b39382 1274proc write_update_index {fd pathList totalCnt batch msg after} {
2cbe5577 1275 global update_index_cp update_index_rsd ui_status_value
e8ab6446 1276 global file_states current_diff
131f503b 1277
74e6b12f
SP
1278 if {$update_index_cp >= $totalCnt} {
1279 close $fd
1280 unlock_index
04b39382
SP
1281 if {$update_index_rsd} reshow_diff
1282 uplevel #0 $after
74e6b12f 1283 return
131f503b 1284 }
131f503b 1285
74e6b12f
SP
1286 for {set i $batch} \
1287 {$update_index_cp < $totalCnt && $i > 0} \
1288 {incr i -1} {
1289 set path [lindex $pathList $update_index_cp]
1290 incr update_index_cp
1291
bbe3b3b9 1292 switch -glob -- [lindex $file_states($path) 0] {
74e6b12f 1293 AD -
bbe3b3b9 1294 MD -
74e6b12f 1295 _D {set new D*}
bbe3b3b9
SP
1296
1297 _M -
1298 MM -
1299 M_ {set new M*}
1300
1301 _O -
1302 AM -
1303 A_ {set new A*}
1304
1305 ?? {continue}
74e6b12f 1306 }
cb07fc2a 1307
74e6b12f
SP
1308 puts -nonewline $fd $path
1309 puts -nonewline $fd "\0"
1310 display_file $path $new
e8ab6446 1311 if {$current_diff eq $path} {
2cbe5577 1312 set update_index_rsd 1
74e6b12f 1313 }
cb07fc2a
SP
1314 }
1315
74e6b12f 1316 set ui_status_value [format \
04b39382 1317 "$msg... %i/%i files (%.2f%%)" \
74e6b12f
SP
1318 $update_index_cp \
1319 $totalCnt \
1320 [expr {100.0 * $update_index_cp / $totalCnt}]]
cb07fc2a
SP
1321}
1322
8c0ce436
SP
1323######################################################################
1324##
2d19516d 1325## remote management
0d4f3eb5 1326
8c0ce436 1327proc load_all_remotes {} {
0d4f3eb5 1328 global gitdir all_remotes repo_config
8c0ce436
SP
1329
1330 set all_remotes [list]
1331 set rm_dir [file join $gitdir remotes]
1332 if {[file isdirectory $rm_dir]} {
d47ae541
SP
1333 set all_remotes [concat $all_remotes [glob \
1334 -types f \
1335 -tails \
1336 -nocomplain \
1337 -directory $rm_dir *]]
8c0ce436
SP
1338 }
1339
0d4f3eb5
SP
1340 foreach line [array names repo_config remote.*.url] {
1341 if {[regexp ^remote\.(.*)\.url\$ $line line name]} {
8c0ce436
SP
1342 lappend all_remotes $name
1343 }
1344 }
8c0ce436
SP
1345
1346 set all_remotes [lsort -unique $all_remotes]
1347}
1348
c1237ae2
SP
1349proc populate_fetch_menu {m} {
1350 global gitdir all_remotes repo_config
8c0ce436 1351
c1237ae2
SP
1352 foreach r $all_remotes {
1353 set enable 0
1354 if {![catch {set a $repo_config(remote.$r.url)}]} {
1355 if {![catch {set a $repo_config(remote.$r.fetch)}]} {
1356 set enable 1
1357 }
1358 } else {
1359 catch {
1360 set fd [open [file join $gitdir remotes $r] r]
1361 while {[gets $fd n] >= 0} {
1362 if {[regexp {^Pull:[ \t]*([^:]+):} $n]} {
1363 set enable 1
1364 break
1365 }
1366 }
1367 close $fd
1368 }
1369 }
1370
1371 if {$enable} {
1372 $m add command \
1373 -label "Fetch from $r..." \
1374 -command [list fetch_from $r] \
1375 -font font_ui
1376 }
1377 }
1378}
1379
1380proc populate_push_menu {m} {
1381 global gitdir all_remotes repo_config
1382
1383 foreach r $all_remotes {
1384 set enable 0
1385 if {![catch {set a $repo_config(remote.$r.url)}]} {
1386 if {![catch {set a $repo_config(remote.$r.push)}]} {
1387 set enable 1
1388 }
1389 } else {
1390 catch {
1391 set fd [open [file join $gitdir remotes $r] r]
1392 while {[gets $fd n] >= 0} {
1393 if {[regexp {^Push:[ \t]*([^:]+):} $n]} {
1394 set enable 1
1395 break
1396 }
1397 }
1398 close $fd
1399 }
1400 }
1401
1402 if {$enable} {
1403 $m add command \
1404 -label "Push to $r..." \
1405 -command [list push_to $r] \
1406 -font font_ui
1407 }
8c0ce436
SP
1408 }
1409}
1410
d33ba5fa 1411proc populate_pull_menu {m} {
b4946930 1412 global gitdir repo_config all_remotes disable_on_lock
d33ba5fa
SP
1413
1414 foreach remote $all_remotes {
1415 set rb {}
043f7011
SP
1416 if {[array get repo_config remote.$remote.url] ne {}} {
1417 if {[array get repo_config remote.$remote.fetch] ne {}} {
d33ba5fa
SP
1418 regexp {^([^:]+):} \
1419 [lindex $repo_config(remote.$remote.fetch) 0] \
1420 line rb
1421 }
1422 } else {
1423 catch {
1424 set fd [open [file join $gitdir remotes $remote] r]
1425 while {[gets $fd line] >= 0} {
1426 if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} {
1427 break
1428 }
1429 }
1430 close $fd
1431 }
1432 }
1433
1434 set rb_short $rb
1435 regsub ^refs/heads/ $rb {} rb_short
043f7011 1436 if {$rb_short ne {}} {
d33ba5fa
SP
1437 $m add command \
1438 -label "Branch $rb_short from $remote..." \
1439 -command [list pull_remote $remote $rb] \
b4946930 1440 -font font_ui
0a462d67
SP
1441 lappend disable_on_lock \
1442 [list $m entryconf [$m index last] -state]
d33ba5fa
SP
1443 }
1444 }
1445}
1446
cb07fc2a
SP
1447######################################################################
1448##
1449## icons
1450
1451set filemask {
1452#define mask_width 14
1453#define mask_height 15
1454static unsigned char mask_bits[] = {
1455 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1456 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1457 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1458}
1459
1460image create bitmap file_plain -background white -foreground black -data {
1461#define plain_width 14
1462#define plain_height 15
1463static unsigned char plain_bits[] = {
1464 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1465 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1466 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1467} -maskdata $filemask
1468
1469image create bitmap file_mod -background white -foreground blue -data {
1470#define mod_width 14
1471#define mod_height 15
1472static unsigned char mod_bits[] = {
1473 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1474 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1475 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1476} -maskdata $filemask
1477
131f503b
SP
1478image create bitmap file_fulltick -background white -foreground "#007000" -data {
1479#define file_fulltick_width 14
1480#define file_fulltick_height 15
1481static unsigned char file_fulltick_bits[] = {
cb07fc2a
SP
1482 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1483 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1484 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1485} -maskdata $filemask
1486
1487image create bitmap file_parttick -background white -foreground "#005050" -data {
1488#define parttick_width 14
1489#define parttick_height 15
1490static unsigned char parttick_bits[] = {
1491 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1492 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1493 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1494} -maskdata $filemask
1495
1496image create bitmap file_question -background white -foreground black -data {
1497#define file_question_width 14
1498#define file_question_height 15
1499static unsigned char file_question_bits[] = {
1500 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1501 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1502 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1503} -maskdata $filemask
1504
1505image create bitmap file_removed -background white -foreground red -data {
1506#define file_removed_width 14
1507#define file_removed_height 15
1508static unsigned char file_removed_bits[] = {
1509 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1510 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1511 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1512} -maskdata $filemask
1513
1514image create bitmap file_merge -background white -foreground blue -data {
1515#define file_merge_width 14
1516#define file_merge_height 15
1517static unsigned char file_merge_bits[] = {
1518 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1519 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1520 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1521} -maskdata $filemask
1522
6b292675
SP
1523set ui_index .vpane.files.index.list
1524set ui_other .vpane.files.other.list
131f503b 1525set max_status_desc 0
cb07fc2a 1526foreach i {
131f503b
SP
1527 {__ i plain "Unmodified"}
1528 {_M i mod "Modified"}
135f76ed 1529 {M_ i fulltick "Included in commit"}
7fe7e733 1530 {MM i parttick "Partially included"}
131f503b
SP
1531
1532 {_O o plain "Untracked"}
135f76ed 1533 {A_ o fulltick "Added by commit"}
131f503b 1534 {AM o parttick "Partially added"}
6b292675 1535 {AD o question "Added (but now gone)"}
131f503b
SP
1536
1537 {_D i question "Missing"}
135f76ed
SP
1538 {D_ i removed "Removed by commit"}
1539 {DD i removed "Removed by commit"}
131f503b
SP
1540 {DO i removed "Removed (still exists)"}
1541
1542 {UM i merge "Merge conflicts"}
1543 {U_ i merge "Merge conflicts"}
cb07fc2a 1544 } {
131f503b
SP
1545 if {$max_status_desc < [string length [lindex $i 3]]} {
1546 set max_status_desc [string length [lindex $i 3]]
1547 }
043f7011 1548 if {[lindex $i 1] eq {i}} {
6b292675
SP
1549 set all_cols([lindex $i 0]) $ui_index
1550 } else {
1551 set all_cols([lindex $i 0]) $ui_other
1552 }
131f503b
SP
1553 set all_icons([lindex $i 0]) file_[lindex $i 2]
1554 set all_descs([lindex $i 0]) [lindex $i 3]
cb07fc2a
SP
1555}
1556unset filemask i
1557
1558######################################################################
1559##
1560## util
1561
16fccd7a
SP
1562proc is_MacOSX {} {
1563 global tcl_platform tk_library
043f7011
SP
1564 if {$tcl_platform(platform) eq {unix}
1565 && $tcl_platform(os) eq {Darwin}
16fccd7a
SP
1566 && [string match /Library/Frameworks/* $tk_library]} {
1567 return 1
1568 }
1569 return 0
1570}
1571
1572proc bind_button3 {w cmd} {
1573 bind $w <Any-Button-3> $cmd
1574 if {[is_MacOSX]} {
1575 bind $w <Control-Button-1> $cmd
1576 }
1577}
1578
b4946930
SP
1579proc incr_font_size {font {amt 1}} {
1580 set sz [font configure $font -size]
1581 incr sz $amt
1582 font configure $font -size $sz
1583 font configure ${font}bold -size $sz
1584}
1585
6e27d826 1586proc hook_failed_popup {hook msg} {
b4946930 1587 global gitdir appname
6e27d826
SP
1588
1589 set w .hookfail
1590 toplevel $w
6e27d826
SP
1591
1592 frame $w.m
1593 label $w.m.l1 -text "$hook hook failed:" \
1594 -anchor w \
1595 -justify left \
b4946930 1596 -font font_uibold
6e27d826
SP
1597 text $w.m.t \
1598 -background white -borderwidth 1 \
1599 -relief sunken \
1600 -width 80 -height 10 \
b4946930 1601 -font font_diff \
6e27d826
SP
1602 -yscrollcommand [list $w.m.sby set]
1603 label $w.m.l2 \
1604 -text {You must correct the above errors before committing.} \
1605 -anchor w \
1606 -justify left \
b4946930 1607 -font font_uibold
6e27d826
SP
1608 scrollbar $w.m.sby -command [list $w.m.t yview]
1609 pack $w.m.l1 -side top -fill x
1610 pack $w.m.l2 -side bottom -fill x
1611 pack $w.m.sby -side right -fill y
1612 pack $w.m.t -side left -fill both -expand 1
1613 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1614
1615 $w.m.t insert 1.0 $msg
1616 $w.m.t conf -state disabled
1617
1618 button $w.ok -text OK \
1619 -width 15 \
b4946930 1620 -font font_ui \
6e27d826 1621 -command "destroy $w"
1e5c18fb 1622 pack $w.ok -side bottom -anchor e -pady 10 -padx 10
6e27d826
SP
1623
1624 bind $w <Visibility> "grab $w; focus $w"
1625 bind $w <Key-Return> "destroy $w"
d33ba5fa
SP
1626 wm title $w "$appname ([lindex [file split \
1627 [file normalize [file dirname $gitdir]]] \
1628 end]): error"
6e27d826
SP
1629 tkwait window $w
1630}
1631
8c0ce436
SP
1632set next_console_id 0
1633
1634proc new_console {short_title long_title} {
37af79d1
SP
1635 global next_console_id console_data
1636 set w .console[incr next_console_id]
1637 set console_data($w) [list $short_title $long_title]
1638 return [console_init $w]
1639}
1640
1641proc console_init {w} {
1642 global console_cr console_data
b4946930 1643 global gitdir appname M1B
8c0ce436 1644
ee3dc935 1645 set console_cr($w) 1.0
8c0ce436
SP
1646 toplevel $w
1647 frame $w.m
37af79d1 1648 label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
8c0ce436
SP
1649 -anchor w \
1650 -justify left \
b4946930 1651 -font font_uibold
8c0ce436
SP
1652 text $w.m.t \
1653 -background white -borderwidth 1 \
1654 -relief sunken \
1655 -width 80 -height 10 \
b4946930 1656 -font font_diff \
8c0ce436
SP
1657 -state disabled \
1658 -yscrollcommand [list $w.m.sby set]
1e5c18fb
SP
1659 label $w.m.s -text {Working... please wait...} \
1660 -anchor w \
07123f40 1661 -justify left \
b4946930 1662 -font font_uibold
8c0ce436
SP
1663 scrollbar $w.m.sby -command [list $w.m.t yview]
1664 pack $w.m.l1 -side top -fill x
07123f40 1665 pack $w.m.s -side bottom -fill x
8c0ce436
SP
1666 pack $w.m.sby -side right -fill y
1667 pack $w.m.t -side left -fill both -expand 1
1668 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1669
0e794311
SP
1670 menu $w.ctxm -tearoff 0
1671 $w.ctxm add command -label "Copy" \
b4946930 1672 -font font_ui \
0e794311
SP
1673 -command "tk_textCopy $w.m.t"
1674 $w.ctxm add command -label "Select All" \
b4946930 1675 -font font_ui \
0e794311
SP
1676 -command "$w.m.t tag add sel 0.0 end"
1677 $w.ctxm add command -label "Copy All" \
b4946930 1678 -font font_ui \
0e794311
SP
1679 -command "
1680 $w.m.t tag add sel 0.0 end
1681 tk_textCopy $w.m.t
1682 $w.m.t tag remove sel 0.0 end
1683 "
1684
1e5c18fb 1685 button $w.ok -text {Close} \
b4946930 1686 -font font_ui \
8c0ce436
SP
1687 -state disabled \
1688 -command "destroy $w"
1e5c18fb 1689 pack $w.ok -side bottom -anchor e -pady 10 -padx 10
8c0ce436 1690
16fccd7a 1691 bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y"
62aac80b
SP
1692 bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break"
1693 bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break"
8c0ce436 1694 bind $w <Visibility> "focus $w"
d33ba5fa
SP
1695 wm title $w "$appname ([lindex [file split \
1696 [file normalize [file dirname $gitdir]]] \
1697 end]): [lindex $console_data($w) 0]"
8c0ce436
SP
1698 return $w
1699}
1700
d33ba5fa 1701proc console_exec {w cmd {after {}}} {
cc4b1c02
SP
1702 global tcl_platform
1703
1704 # -- Windows tosses the enviroment when we exec our child.
1705 # But most users need that so we have to relogin. :-(
1706 #
043f7011 1707 if {$tcl_platform(platform) eq {windows}} {
cc4b1c02
SP
1708 set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
1709 }
1710
1711 # -- Tcl won't let us redirect both stdout and stderr to
1712 # the same pipe. So pass it through cat...
1713 #
1714 set cmd [concat | $cmd |& cat]
1715
1716 set fd_f [open $cmd r]
ee3dc935 1717 fconfigure $fd_f -blocking 0 -translation binary
d33ba5fa 1718 fileevent $fd_f readable [list console_read $w $fd_f $after]
cc4b1c02
SP
1719}
1720
d33ba5fa 1721proc console_read {w fd after} {
37af79d1 1722 global console_cr console_data
ee3dc935 1723
ee3dc935 1724 set buf [read $fd]
043f7011 1725 if {$buf ne {}} {
37af79d1
SP
1726 if {![winfo exists $w]} {console_init $w}
1727 $w.m.t conf -state normal
1728 set c 0
1729 set n [string length $buf]
1730 while {$c < $n} {
1731 set cr [string first "\r" $buf $c]
1732 set lf [string first "\n" $buf $c]
24263b77
SP
1733 if {$cr < 0} {set cr [expr {$n + 1}]}
1734 if {$lf < 0} {set lf [expr {$n + 1}]}
37af79d1
SP
1735
1736 if {$lf < $cr} {
1737 $w.m.t insert end [string range $buf $c $lf]
1738 set console_cr($w) [$w.m.t index {end -1c}]
1739 set c $lf
1740 incr c
1741 } else {
1742 $w.m.t delete $console_cr($w) end
1743 $w.m.t insert end "\n"
1744 $w.m.t insert end [string range $buf $c $cr]
1745 set c $cr
1746 incr c
1747 }
ee3dc935 1748 }
37af79d1
SP
1749 $w.m.t conf -state disabled
1750 $w.m.t see end
8c0ce436 1751 }
8c0ce436 1752
07123f40 1753 fconfigure $fd -blocking 1
8c0ce436 1754 if {[eof $fd]} {
07123f40 1755 if {[catch {close $fd}]} {
37af79d1 1756 if {![winfo exists $w]} {console_init $w}
07123f40 1757 $w.m.s conf -background red -text {Error: Command Failed}
37af79d1 1758 $w.ok conf -state normal
d33ba5fa 1759 set ok 0
37af79d1 1760 } elseif {[winfo exists $w]} {
07123f40 1761 $w.m.s conf -background green -text {Success}
37af79d1 1762 $w.ok conf -state normal
d33ba5fa 1763 set ok 1
07123f40 1764 }
ee3dc935 1765 array unset console_cr $w
37af79d1 1766 array unset console_data $w
043f7011 1767 if {$after ne {}} {
d33ba5fa
SP
1768 uplevel #0 $after $ok
1769 }
07123f40 1770 return
8c0ce436 1771 }
07123f40 1772 fconfigure $fd -blocking 0
8c0ce436
SP
1773}
1774
cb07fc2a
SP
1775######################################################################
1776##
1777## ui commands
1778
e210e674 1779set starting_gitk_msg {Please wait... Starting gitk...}
cc4b1c02 1780
cb07fc2a 1781proc do_gitk {} {
e210e674
SP
1782 global tcl_platform ui_status_value starting_gitk_msg
1783
1784 set ui_status_value $starting_gitk_msg
e57ca85e 1785 after 10000 {
043f7011 1786 if {$ui_status_value eq $starting_gitk_msg} {
e210e674
SP
1787 set ui_status_value {Ready.}
1788 }
1789 }
cb07fc2a 1790
043f7011 1791 if {$tcl_platform(platform) eq {windows}} {
cb07fc2a
SP
1792 exec sh -c gitk &
1793 } else {
1794 exec gitk &
1795 }
1796}
1797
d1536c48
SP
1798proc do_repack {} {
1799 set w [new_console "repack" "Repacking the object database"]
1800 set cmd [list git repack]
1801 lappend cmd -a
1802 lappend cmd -d
1803 console_exec $w $cmd
1804}
1805
b5834d70 1806set is_quitting 0
c4fe7728 1807
cb07fc2a 1808proc do_quit {} {
51f4d16b 1809 global gitdir ui_comm is_quitting repo_config
c4fe7728 1810
b5834d70
SP
1811 if {$is_quitting} return
1812 set is_quitting 1
131f503b 1813
51f4d16b
SP
1814 # -- Stash our current commit buffer.
1815 #
131f503b 1816 set save [file join $gitdir GITGUI_MSG]
ec6b424a 1817 set msg [string trim [$ui_comm get 0.0 end]]
043f7011 1818 if {[$ui_comm edit modified] && $msg ne {}} {
131f503b
SP
1819 catch {
1820 set fd [open $save w]
1821 puts $fd [string trim [$ui_comm get 0.0 end]]
1822 close $fd
1823 }
043f7011 1824 } elseif {$msg eq {} && [file exists $save]} {
131f503b
SP
1825 file delete $save
1826 }
1827
51f4d16b
SP
1828 # -- Stash our current window geometry into this repository.
1829 #
1830 set cfg_geometry [list]
1831 lappend cfg_geometry [wm geometry .]
1832 lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
1833 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
1834 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1835 set rc_geometry {}
1836 }
043f7011 1837 if {$cfg_geometry ne $rc_geometry} {
51f4d16b
SP
1838 catch {exec git repo-config gui.geometry $cfg_geometry}
1839 }
1840
cb07fc2a
SP
1841 destroy .
1842}
1843
1844proc do_rescan {} {
8f52548a 1845 rescan {set ui_status_value {Ready.}}
cb07fc2a
SP
1846}
1847
7fe7e733 1848proc do_include_all {} {
74e6b12f
SP
1849 global file_states
1850
1851 if {![lock_index begin-update]} return
1852
1853 set pathList [list]
1854 foreach path [array names file_states] {
1855 set s $file_states($path)
1856 set m [lindex $s 0]
1857 switch -- $m {
1858 AM -
1859 MM -
1860 _M -
1861 _D {lappend pathList $path}
131f503b 1862 }
74e6b12f 1863 }
043f7011 1864 if {$pathList eq {}} {
74e6b12f
SP
1865 unlock_index
1866 } else {
04b39382
SP
1867 update_index \
1868 "Including all modified files" \
1869 $pathList \
1870 {set ui_status_value {Ready to commit.}}
131f503b
SP
1871 }
1872}
1873
da5239dc
SP
1874set GIT_COMMITTER_IDENT {}
1875
131f503b 1876proc do_signoff {} {
97bf01c4 1877 global ui_comm GIT_COMMITTER_IDENT
131f503b 1878
043f7011 1879 if {$GIT_COMMITTER_IDENT eq {}} {
97bf01c4 1880 if {[catch {set me [exec git var GIT_COMMITTER_IDENT]} err]} {
44be340e 1881 error_popup "Unable to obtain your identity:\n\n$err"
97bf01c4 1882 return
131f503b 1883 }
97bf01c4
SP
1884 if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
1885 $me me GIT_COMMITTER_IDENT]} {
44be340e 1886 error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me"
97bf01c4
SP
1887 return
1888 }
1889 }
1890
1daf1d0c
SP
1891 set sob "Signed-off-by: $GIT_COMMITTER_IDENT"
1892 set last [$ui_comm get {end -1c linestart} {end -1c}]
043f7011 1893 if {$last ne $sob} {
b2c6fcf1 1894 $ui_comm edit separator
043f7011 1895 if {$last ne {}
1daf1d0c
SP
1896 && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
1897 $ui_comm insert end "\n"
1898 }
1899 $ui_comm insert end "\n$sob"
b2c6fcf1 1900 $ui_comm edit separator
97bf01c4 1901 $ui_comm see end
131f503b
SP
1902 }
1903}
1904
e57ca85e
SP
1905proc do_amend_last {} {
1906 load_last_commit
1907}
1908
6e27d826 1909proc do_commit {} {
ec6b424a 1910 commit_tree
6e27d826
SP
1911}
1912
51f4d16b 1913proc do_options {} {
92148d80 1914 global appname gitdir font_descs
51f4d16b
SP
1915 global repo_config global_config
1916 global repo_config_new global_config_new
1917
51f4d16b
SP
1918 array unset repo_config_new
1919 array unset global_config_new
1920 foreach name [array names repo_config] {
1921 set repo_config_new($name) $repo_config($name)
1922 }
358d8de8
SP
1923 load_config 1
1924 foreach name [array names repo_config] {
1925 switch -- $name {
1926 gui.diffcontext {continue}
1927 }
1928 set repo_config_new($name) $repo_config($name)
1929 }
51f4d16b
SP
1930 foreach name [array names global_config] {
1931 set global_config_new($name) $global_config($name)
1932 }
e01b4221
SP
1933 set reponame [lindex [file split \
1934 [file normalize [file dirname $gitdir]]] \
1935 end]
51f4d16b
SP
1936
1937 set w .options_editor
1938 toplevel $w
e01b4221 1939 wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
51f4d16b
SP
1940
1941 label $w.header -text "$appname Options" \
1942 -font font_uibold
1943 pack $w.header -side top -fill x
1944
1945 frame $w.buttons
92148d80
SP
1946 button $w.buttons.restore -text {Restore Defaults} \
1947 -font font_ui \
1948 -command do_restore_defaults
1949 pack $w.buttons.restore -side left
51f4d16b
SP
1950 button $w.buttons.save -text Save \
1951 -font font_ui \
92148d80 1952 -command [list do_save_config $w]
51f4d16b
SP
1953 pack $w.buttons.save -side right
1954 button $w.buttons.cancel -text {Cancel} \
1955 -font font_ui \
92148d80 1956 -command [list destroy $w]
51f4d16b 1957 pack $w.buttons.cancel -side right
92148d80 1958 pack $w.buttons -side bottom -fill x -pady 10 -padx 10
51f4d16b 1959
e01b4221 1960 labelframe $w.repo -text "$reponame Repository" \
92148d80 1961 -font font_ui \
51f4d16b
SP
1962 -relief raised -borderwidth 2
1963 labelframe $w.global -text {Global (All Repositories)} \
92148d80 1964 -font font_ui \
51f4d16b
SP
1965 -relief raised -borderwidth 2
1966 pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5
1967 pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5
1968
1969 foreach option {
f7f8d322 1970 {b partialinclude {Allow Partially Included Files}}
358d8de8
SP
1971 {b pullsummary {Show Pull Summary}}
1972 {b trustmtime {Trust File Modification Timestamps}}
1973 {i diffcontext {Number of Diff Context Lines}}
51f4d16b 1974 } {
358d8de8
SP
1975 set type [lindex $option 0]
1976 set name [lindex $option 1]
1977 set text [lindex $option 2]
51f4d16b 1978 foreach f {repo global} {
358d8de8
SP
1979 switch $type {
1980 b {
1981 checkbutton $w.$f.$name -text $text \
1982 -variable ${f}_config_new(gui.$name) \
1983 -onvalue true \
1984 -offvalue false \
1985 -font font_ui
1986 pack $w.$f.$name -side top -anchor w
1987 }
1988 i {
1989 frame $w.$f.$name
1990 label $w.$f.$name.l -text "$text:" -font font_ui
1991 pack $w.$f.$name.l -side left -anchor w -fill x
1992 spinbox $w.$f.$name.v \
1993 -textvariable ${f}_config_new(gui.$name) \
1994 -from 1 -to 99 -increment 1 \
1995 -width 3 \
1996 -font font_ui
1997 pack $w.$f.$name.v -side right -anchor e
1998 pack $w.$f.$name -side top -anchor w -fill x
1999 }
2000 }
51f4d16b
SP
2001 }
2002 }
2003
92148d80
SP
2004 set all_fonts [lsort [font families]]
2005 foreach option $font_descs {
2006 set name [lindex $option 0]
2007 set font [lindex $option 1]
2008 set text [lindex $option 2]
2009
2010 set global_config_new(gui.$font^^family) \
2011 [font configure $font -family]
2012 set global_config_new(gui.$font^^size) \
2013 [font configure $font -size]
2014
2015 frame $w.global.$name
2016 label $w.global.$name.l -text "$text:" -font font_ui
2017 pack $w.global.$name.l -side left -anchor w -fill x
2018 eval tk_optionMenu $w.global.$name.family \
2019 global_config_new(gui.$font^^family) \
2020 $all_fonts
2021 spinbox $w.global.$name.size \
2022 -textvariable global_config_new(gui.$font^^size) \
2023 -from 2 -to 80 -increment 1 \
2024 -width 3 \
2025 -font font_ui
2026 pack $w.global.$name.size -side right -anchor e
2027 pack $w.global.$name.family -side right -anchor e
2028 pack $w.global.$name -side top -anchor w -fill x
2029 }
2030
51f4d16b
SP
2031 bind $w <Visibility> "grab $w; focus $w"
2032 bind $w <Key-Escape> "destroy $w"
e01b4221 2033 wm title $w "$appname ($reponame): Options"
51f4d16b
SP
2034 tkwait window $w
2035}
2036
92148d80 2037proc do_restore_defaults {} {
7b64d0b7 2038 global font_descs default_config repo_config
92148d80
SP
2039 global repo_config_new global_config_new
2040
2041 foreach name [array names default_config] {
2042 set repo_config_new($name) $default_config($name)
2043 set global_config_new($name) $default_config($name)
2044 }
2045
2046 foreach option $font_descs {
2047 set name [lindex $option 0]
7b64d0b7 2048 set repo_config(gui.$name) $default_config(gui.$name)
92148d80
SP
2049 }
2050 apply_config
2051
2052 foreach option $font_descs {
2053 set name [lindex $option 0]
2054 set font [lindex $option 1]
2055 set global_config_new(gui.$font^^family) \
2056 [font configure $font -family]
2057 set global_config_new(gui.$font^^size) \
2058 [font configure $font -size]
2059 }
2060}
2061
2062proc do_save_config {w} {
2063 if {[catch {save_config} err]} {
2064 error_popup "Failed to completely save options:\n\n$err"
2065 }
358d8de8 2066 reshow_diff
92148d80
SP
2067 destroy $w
2068}
2069
4aca740b
SP
2070proc do_windows_shortcut {} {
2071 global gitdir appname argv0
2072
2073 set reponame [lindex [file split \
2074 [file normalize [file dirname $gitdir]]] \
2075 end]
2076
2077 if {[catch {
2078 set desktop [exec cygpath \
2079 --windows \
2080 --absolute \
2081 --long-name \
2082 --desktop]
2083 }]} {
2084 set desktop .
2085 }
2086 set fn [tk_getSaveFile \
2087 -parent . \
2088 -title "$appname ($reponame): Create Desktop Icon" \
2089 -initialdir $desktop \
2090 -initialfile "Git $reponame.bat"]
2091 if {$fn != {}} {
2092 if {[catch {
2093 set fd [open $fn w]
2094 set sh [exec cygpath \
2095 --windows \
2096 --absolute \
2097 --long-name \
2098 /bin/sh]
2099 set me [exec cygpath \
2100 --unix \
2101 --absolute \
2102 $argv0]
2103 set gd [exec cygpath \
2104 --unix \
2105 --absolute \
2106 $gitdir]
306500fc
SP
2107 regsub -all ' $me "'\\''" me
2108 regsub -all ' $gd "'\\''" gd
4aca740b 2109 puts -nonewline $fd "\"$sh\" --login -c \""
dbccbbda
SP
2110 puts -nonewline $fd "GIT_DIR='$gd'"
2111 puts -nonewline $fd " '$me'"
4aca740b
SP
2112 puts $fd "&\""
2113 close $fd
2114 } err]} {
2115 error_popup "Cannot write script:\n\n$err"
2116 }
2117 }
2118}
2119
06c31115
SP
2120proc do_macosx_app {} {
2121 global gitdir appname argv0 env
2122
2123 set reponame [lindex [file split \
2124 [file normalize [file dirname $gitdir]]] \
2125 end]
2126
2127 set fn [tk_getSaveFile \
2128 -parent . \
2129 -title "$appname ($reponame): Create Desktop Icon" \
2130 -initialdir [file join $env(HOME) Desktop] \
2131 -initialfile "Git $reponame.app"]
2132 if {$fn != {}} {
2133 if {[catch {
2134 set Contents [file join $fn Contents]
2135 set MacOS [file join $Contents MacOS]
2136 set exe [file join $MacOS git-gui]
2137
2138 file mkdir $MacOS
2139
2140 set fd [open [file join $Contents PkgInfo] w]
2141 puts -nonewline $fd {APPL????}
2142 close $fd
2143
2144 set fd [open [file join $Contents Info.plist] w]
2145 puts $fd {<?xml version="1.0" encoding="UTF-8"?>
2146<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
2147<plist version="1.0">
2148<dict>
2149 <key>CFBundleDevelopmentRegion</key>
2150 <string>English</string>
2151 <key>CFBundleExecutable</key>
2152 <string>git-gui</string>
2153 <key>CFBundleIdentifier</key>
2154 <string>org.spearce.git-gui</string>
2155 <key>CFBundleInfoDictionaryVersion</key>
2156 <string>6.0</string>
2157 <key>CFBundlePackageType</key>
2158 <string>APPL</string>
2159 <key>CFBundleSignature</key>
2160 <string>????</string>
2161 <key>CFBundleVersion</key>
2162 <string>1.0</string>
2163 <key>NSPrincipalClass</key>
2164 <string>NSApplication</string>
2165</dict>
2166</plist>}
2167 close $fd
2168
2169 set fd [open $exe w]
2170 set gd [file normalize $gitdir]
2171 set ep [file normalize [exec git --exec-path]]
2172 regsub -all ' $gd "'\\''" gd
2173 regsub -all ' $ep "'\\''" ep
2174 puts $fd "#!/bin/sh"
2175 foreach name [array names env] {
2176 if {[string match GIT_* $name]} {
2177 regsub -all ' $env($name) "'\\''" v
2178 puts $fd "export $name='$v'"
2179 }
2180 }
2181 puts $fd "export PATH='$ep':\$PATH"
2182 puts $fd "export GIT_DIR='$gd'"
2183 puts $fd "exec [file normalize $argv0]"
2184 close $fd
2185
2186 file attributes $exe -permissions u+x,g+x,o+x
2187 } err]} {
2188 error_popup "Cannot write icon:\n\n$err"
2189 }
2190 }
2191}
2192
24263b77
SP
2193proc toggle_or_diff {w x y} {
2194 global file_lists ui_index ui_other
2195 global last_clicked selected_paths
131f503b 2196
cb07fc2a
SP
2197 set pos [split [$w index @$x,$y] .]
2198 set lno [lindex $pos 0]
2199 set col [lindex $pos 1]
24263b77
SP
2200 set path [lindex $file_lists($w) [expr {$lno - 1}]]
2201 if {$path eq {}} {
2202 set last_clicked {}
2203 return
2204 }
2205
2206 set last_clicked [list $w $lno]
2207 array unset selected_paths
2208 $ui_index tag remove in_sel 0.0 end
2209 $ui_other tag remove in_sel 0.0 end
cb07fc2a 2210
24263b77 2211 if {$col == 0} {
04b39382
SP
2212 update_index \
2213 "Including [short_path $path]" \
2214 [list $path] \
2215 {set ui_status_value {Ready.}}
24263b77 2216 } else {
03e4ec53 2217 show_diff $path $w $lno
cb07fc2a
SP
2218 }
2219}
2220
24263b77 2221proc add_one_to_selection {w x y} {
7f1df79b 2222 global file_lists
24263b77 2223 global last_clicked selected_paths
7f1df79b 2224
cb07fc2a
SP
2225 set pos [split [$w index @$x,$y] .]
2226 set lno [lindex $pos 0]
2227 set col [lindex $pos 1]
24263b77
SP
2228 set path [lindex $file_lists($w) [expr {$lno - 1}]]
2229 if {$path eq {}} {
2230 set last_clicked {}
2231 return
2232 }
cb07fc2a 2233
24263b77
SP
2234 set last_clicked [list $w $lno]
2235 if {[catch {set in_sel $selected_paths($path)}]} {
2236 set in_sel 0
2237 }
2238 if {$in_sel} {
2239 unset selected_paths($path)
2240 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
2241 } else {
2242 set selected_paths($path) 1
2243 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
2244 }
2245}
2246
2247proc add_range_to_selection {w x y} {
2248 global file_lists
2249 global last_clicked selected_paths
2250
2251 if {[lindex $last_clicked 0] ne $w} {
2252 toggle_or_diff $w $x $y
2253 return
cb07fc2a 2254 }
24263b77
SP
2255
2256 set pos [split [$w index @$x,$y] .]
2257 set lno [lindex $pos 0]
2258 set lc [lindex $last_clicked 1]
2259 if {$lc < $lno} {
2260 set begin $lc
2261 set end $lno
2262 } else {
2263 set begin $lno
2264 set end $lc
2265 }
2266
2267 foreach path [lrange $file_lists($w) \
2268 [expr {$begin - 1}] \
2269 [expr {$end - 1}]] {
2270 set selected_paths($path) 1
2271 }
2272 $w tag add in_sel $begin.0 [expr {$end + 1}].0
cb07fc2a
SP
2273}
2274
2275######################################################################
2276##
92148d80 2277## config defaults
cb07fc2a 2278
00f949fb 2279set cursor_ptr arrow
b4946930
SP
2280font create font_diff -family Courier -size 10
2281font create font_ui
2282catch {
2283 label .dummy
2284 eval font configure font_ui [font actual [.dummy cget -font]]
2285 destroy .dummy
2286}
2287
92148d80
SP
2288font create font_uibold
2289font create font_diffbold
cb07fc2a 2290
16fccd7a
SP
2291set M1B M1
2292set M1T M1
043f7011 2293if {$tcl_platform(platform) eq {windows}} {
16fccd7a
SP
2294 set M1B Control
2295 set M1T Ctrl
2296} elseif {[is_MacOSX]} {
2297 set M1B M1
2298 set M1T Cmd
e210e674
SP
2299}
2300
92148d80
SP
2301proc apply_config {} {
2302 global repo_config font_descs
2303
2304 foreach option $font_descs {
2305 set name [lindex $option 0]
2306 set font [lindex $option 1]
2307 if {[catch {
2308 foreach {cn cv} $repo_config(gui.$name) {
2309 font configure $font $cn $cv
2310 }
2311 } err]} {
2312 error_popup "Invalid font specified in gui.$name:\n\n$err"
2313 }
2314 foreach {cn cv} [font configure $font] {
2315 font configure ${font}bold $cn $cv
2316 }
2317 font configure ${font}bold -weight bold
2318 }
2319}
2320
2321set default_config(gui.trustmtime) false
ebf336b9 2322set default_config(gui.pullsummary) true
f7f8d322 2323set default_config(gui.partialinclude) false
358d8de8 2324set default_config(gui.diffcontext) 5
92148d80
SP
2325set default_config(gui.fontui) [font configure font_ui]
2326set default_config(gui.fontdiff) [font configure font_diff]
2327set font_descs {
2328 {fontui font_ui {Main Font}}
2329 {fontdiff font_diff {Diff/Console Font}}
2330}
6bbd1cb9 2331load_config 0
92148d80
SP
2332apply_config
2333
2334######################################################################
2335##
2336## ui construction
2337
cb07fc2a 2338# -- Menu Bar
b4946930 2339menu .mbar -tearoff 0
cb07fc2a 2340.mbar add cascade -label Project -menu .mbar.project
9861671d 2341.mbar add cascade -label Edit -menu .mbar.edit
cb07fc2a 2342.mbar add cascade -label Commit -menu .mbar.commit
4ccdab02
SP
2343if {!$single_commit} {
2344 .mbar add cascade -label Fetch -menu .mbar.fetch
2345 .mbar add cascade -label Pull -menu .mbar.pull
2346 .mbar add cascade -label Push -menu .mbar.push
2347}
cb07fc2a
SP
2348. configure -menu .mbar
2349
2350# -- Project Menu
2351menu .mbar.project
6f6eed28 2352.mbar.project add command -label Visualize \
cb07fc2a 2353 -command do_gitk \
b4946930 2354 -font font_ui
4ccdab02
SP
2355if {!$single_commit} {
2356 .mbar.project add command -label {Repack Database} \
2357 -command do_repack \
2358 -font font_ui
4aca740b
SP
2359
2360 if {$tcl_platform(platform) eq {windows}} {
2361 .mbar.project add command \
2362 -label {Create Desktop Icon} \
2363 -command do_windows_shortcut \
2364 -font font_ui
06c31115
SP
2365 } elseif {[is_MacOSX]} {
2366 .mbar.project add command \
2367 -label {Create Desktop Icon} \
2368 -command do_macosx_app \
2369 -font font_ui
4aca740b 2370 }
4ccdab02 2371}
cb07fc2a
SP
2372.mbar.project add command -label Quit \
2373 -command do_quit \
e210e674 2374 -accelerator $M1T-Q \
b4946930 2375 -font font_ui
cb07fc2a 2376
9861671d
SP
2377# -- Edit Menu
2378#
2379menu .mbar.edit
2380.mbar.edit add command -label Undo \
2381 -command {catch {[focus] edit undo}} \
2382 -accelerator $M1T-Z \
b4946930 2383 -font font_ui
9861671d
SP
2384.mbar.edit add command -label Redo \
2385 -command {catch {[focus] edit redo}} \
2386 -accelerator $M1T-Y \
b4946930 2387 -font font_ui
9861671d
SP
2388.mbar.edit add separator
2389.mbar.edit add command -label Cut \
2390 -command {catch {tk_textCut [focus]}} \
2391 -accelerator $M1T-X \
b4946930 2392 -font font_ui
9861671d
SP
2393.mbar.edit add command -label Copy \
2394 -command {catch {tk_textCopy [focus]}} \
2395 -accelerator $M1T-C \
b4946930 2396 -font font_ui
9861671d
SP
2397.mbar.edit add command -label Paste \
2398 -command {catch {tk_textPaste [focus]; [focus] see insert}} \
2399 -accelerator $M1T-V \
b4946930 2400 -font font_ui
9861671d
SP
2401.mbar.edit add command -label Delete \
2402 -command {catch {[focus] delete sel.first sel.last}} \
2403 -accelerator Del \
b4946930 2404 -font font_ui
9861671d
SP
2405.mbar.edit add separator
2406.mbar.edit add command -label {Select All} \
2407 -command {catch {[focus] tag add sel 0.0 end}} \
2408 -accelerator $M1T-A \
b4946930 2409 -font font_ui
51f4d16b
SP
2410.mbar.edit add separator
2411.mbar.edit add command -label {Options...} \
2412 -command do_options \
2413 -font font_ui
9861671d 2414
cb07fc2a
SP
2415# -- Commit Menu
2416menu .mbar.commit
2417.mbar.commit add command -label Rescan \
2418 -command do_rescan \
e210e674 2419 -accelerator F5 \
b4946930 2420 -font font_ui
e210e674
SP
2421lappend disable_on_lock \
2422 [list .mbar.commit entryconf [.mbar.commit index last] -state]
e57ca85e
SP
2423.mbar.commit add command -label {Amend Last Commit} \
2424 -command do_amend_last \
b4946930 2425 -font font_ui
e57ca85e
SP
2426lappend disable_on_lock \
2427 [list .mbar.commit entryconf [.mbar.commit index last] -state]
7fe7e733
SP
2428.mbar.commit add command -label {Include All Files} \
2429 -command do_include_all \
49b86f01 2430 -accelerator $M1T-I \
b4946930 2431 -font font_ui
e210e674
SP
2432lappend disable_on_lock \
2433 [list .mbar.commit entryconf [.mbar.commit index last] -state]
131f503b
SP
2434.mbar.commit add command -label {Sign Off} \
2435 -command do_signoff \
e210e674 2436 -accelerator $M1T-S \
b4946930 2437 -font font_ui
131f503b
SP
2438.mbar.commit add command -label Commit \
2439 -command do_commit \
e210e674 2440 -accelerator $M1T-Return \
b4946930 2441 -font font_ui
e210e674
SP
2442lappend disable_on_lock \
2443 [list .mbar.commit entryconf [.mbar.commit index last] -state]
cb07fc2a 2444
4ccdab02
SP
2445if {!$single_commit} {
2446 # -- Fetch Menu
2447 menu .mbar.fetch
cb07fc2a 2448
4ccdab02
SP
2449 # -- Pull Menu
2450 menu .mbar.pull
cb07fc2a 2451
4ccdab02
SP
2452 # -- Push Menu
2453 menu .mbar.push
2454}
8c0ce436 2455
cb07fc2a
SP
2456# -- Main Window Layout
2457panedwindow .vpane -orient vertical
2458panedwindow .vpane.files -orient horizontal
6f6eed28 2459.vpane add .vpane.files -sticky nsew -height 100 -width 400
cb07fc2a
SP
2460pack .vpane -anchor n -side top -fill both -expand 1
2461
2462# -- Index File List
cb07fc2a
SP
2463frame .vpane.files.index -height 100 -width 400
2464label .vpane.files.index.title -text {Modified Files} \
2465 -background green \
b4946930 2466 -font font_ui
cb07fc2a
SP
2467text $ui_index -background white -borderwidth 0 \
2468 -width 40 -height 10 \
b4946930 2469 -font font_ui \
6c6dd01a 2470 -cursor $cursor_ptr \
cb07fc2a 2471 -yscrollcommand {.vpane.files.index.sb set} \
cb07fc2a
SP
2472 -state disabled
2473scrollbar .vpane.files.index.sb -command [list $ui_index yview]
2474pack .vpane.files.index.title -side top -fill x
2475pack .vpane.files.index.sb -side right -fill y
2476pack $ui_index -side left -fill both -expand 1
2477.vpane.files add .vpane.files.index -sticky nsew
2478
2479# -- Other (Add) File List
cb07fc2a
SP
2480frame .vpane.files.other -height 100 -width 100
2481label .vpane.files.other.title -text {Untracked Files} \
2482 -background red \
b4946930 2483 -font font_ui
cb07fc2a
SP
2484text $ui_other -background white -borderwidth 0 \
2485 -width 40 -height 10 \
b4946930 2486 -font font_ui \
6c6dd01a 2487 -cursor $cursor_ptr \
cb07fc2a 2488 -yscrollcommand {.vpane.files.other.sb set} \
cb07fc2a
SP
2489 -state disabled
2490scrollbar .vpane.files.other.sb -command [list $ui_other yview]
2491pack .vpane.files.other.title -side top -fill x
2492pack .vpane.files.other.sb -side right -fill y
2493pack $ui_other -side left -fill both -expand 1
2494.vpane.files add .vpane.files.other -sticky nsew
2495
24263b77
SP
2496foreach i [list $ui_index $ui_other] {
2497 $i tag conf in_diff -font font_uibold
2498 $i tag conf in_sel \
2499 -background [$i cget -foreground] \
2500 -foreground [$i cget -background]
2501}
2502unset i
131f503b 2503
0fb8f9ce 2504# -- Diff and Commit Area
8009dcdc 2505frame .vpane.lower -height 300 -width 400
0fb8f9ce
SP
2506frame .vpane.lower.commarea
2507frame .vpane.lower.diff -relief sunken -borderwidth 1
2508pack .vpane.lower.commarea -side top -fill x
2509pack .vpane.lower.diff -side bottom -fill both -expand 1
2510.vpane add .vpane.lower -stick nsew
cb07fc2a
SP
2511
2512# -- Commit Area Buttons
0fb8f9ce
SP
2513frame .vpane.lower.commarea.buttons
2514label .vpane.lower.commarea.buttons.l -text {} \
cb07fc2a
SP
2515 -anchor w \
2516 -justify left \
b4946930 2517 -font font_ui
0fb8f9ce
SP
2518pack .vpane.lower.commarea.buttons.l -side top -fill x
2519pack .vpane.lower.commarea.buttons -side left -fill y
131f503b 2520
0fb8f9ce 2521button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
cb07fc2a 2522 -command do_rescan \
b4946930 2523 -font font_ui
0fb8f9ce 2524pack .vpane.lower.commarea.buttons.rescan -side top -fill x
390adaea
SP
2525lappend disable_on_lock \
2526 {.vpane.lower.commarea.buttons.rescan conf -state}
131f503b 2527
0fb8f9ce 2528button .vpane.lower.commarea.buttons.amend -text {Amend Last} \
e57ca85e 2529 -command do_amend_last \
b4946930 2530 -font font_ui
0fb8f9ce 2531pack .vpane.lower.commarea.buttons.amend -side top -fill x
390adaea
SP
2532lappend disable_on_lock \
2533 {.vpane.lower.commarea.buttons.amend conf -state}
e57ca85e 2534
7fe7e733
SP
2535button .vpane.lower.commarea.buttons.incall -text {Include All} \
2536 -command do_include_all \
b4946930 2537 -font font_ui
7fe7e733 2538pack .vpane.lower.commarea.buttons.incall -side top -fill x
390adaea
SP
2539lappend disable_on_lock \
2540 {.vpane.lower.commarea.buttons.incall conf -state}
131f503b 2541
0fb8f9ce 2542button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
131f503b 2543 -command do_signoff \
b4946930 2544 -font font_ui
0fb8f9ce 2545pack .vpane.lower.commarea.buttons.signoff -side top -fill x
131f503b 2546
0fb8f9ce 2547button .vpane.lower.commarea.buttons.commit -text {Commit} \
cb07fc2a 2548 -command do_commit \
b4946930 2549 -font font_ui
0fb8f9ce 2550pack .vpane.lower.commarea.buttons.commit -side top -fill x
390adaea
SP
2551lappend disable_on_lock \
2552 {.vpane.lower.commarea.buttons.commit conf -state}
cb07fc2a
SP
2553
2554# -- Commit Message Buffer
0fb8f9ce
SP
2555frame .vpane.lower.commarea.buffer
2556set ui_comm .vpane.lower.commarea.buffer.t
2557set ui_coml .vpane.lower.commarea.buffer.l
bd1e2b40 2558label $ui_coml -text {Commit Message:} \
cb07fc2a
SP
2559 -anchor w \
2560 -justify left \
b4946930 2561 -font font_ui
bd1e2b40
SP
2562trace add variable commit_type write {uplevel #0 {
2563 switch -glob $commit_type \
2564 initial {$ui_coml conf -text {Initial Commit Message:}} \
2565 amend {$ui_coml conf -text {Amended Commit Message:}} \
2566 merge {$ui_coml conf -text {Merge Commit Message:}} \
2567 * {$ui_coml conf -text {Commit Message:}}
2568}}
cb07fc2a 2569text $ui_comm -background white -borderwidth 1 \
9861671d 2570 -undo true \
b2c6fcf1 2571 -maxundo 20 \
9861671d 2572 -autoseparators true \
cb07fc2a 2573 -relief sunken \
0fb8f9ce 2574 -width 75 -height 9 -wrap none \
b4946930 2575 -font font_diff \
6c6dd01a 2576 -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
390adaea
SP
2577scrollbar .vpane.lower.commarea.buffer.sby \
2578 -command [list $ui_comm yview]
bd1e2b40 2579pack $ui_coml -side top -fill x
0fb8f9ce 2580pack .vpane.lower.commarea.buffer.sby -side right -fill y
cb07fc2a 2581pack $ui_comm -side left -fill y
0fb8f9ce
SP
2582pack .vpane.lower.commarea.buffer -side left -fill y
2583
0e794311
SP
2584# -- Commit Message Buffer Context Menu
2585#
e8ab6446
SP
2586set ctxm .vpane.lower.commarea.buffer.ctxm
2587menu $ctxm -tearoff 0
2588$ctxm add command \
2589 -label {Cut} \
b4946930 2590 -font font_ui \
e8ab6446
SP
2591 -command {tk_textCut $ui_comm}
2592$ctxm add command \
2593 -label {Copy} \
b4946930 2594 -font font_ui \
e8ab6446
SP
2595 -command {tk_textCopy $ui_comm}
2596$ctxm add command \
2597 -label {Paste} \
b4946930 2598 -font font_ui \
e8ab6446
SP
2599 -command {tk_textPaste $ui_comm}
2600$ctxm add command \
2601 -label {Delete} \
b4946930 2602 -font font_ui \
e8ab6446
SP
2603 -command {$ui_comm delete sel.first sel.last}
2604$ctxm add separator
2605$ctxm add command \
2606 -label {Select All} \
b4946930 2607 -font font_ui \
e8ab6446
SP
2608 -command {$ui_comm tag add sel 0.0 end}
2609$ctxm add command \
2610 -label {Copy All} \
b4946930 2611 -font font_ui \
e8ab6446 2612 -command {
0e794311
SP
2613 $ui_comm tag add sel 0.0 end
2614 tk_textCopy $ui_comm
2615 $ui_comm tag remove sel 0.0 end
e8ab6446
SP
2616 }
2617$ctxm add separator
2618$ctxm add command \
2619 -label {Sign Off} \
b4946930 2620 -font font_ui \
0e794311 2621 -command do_signoff
e8ab6446 2622bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
0e794311 2623
0fb8f9ce 2624# -- Diff Header
e8ab6446
SP
2625set current_diff {}
2626set diff_actions [list]
2627proc current_diff_trace {varname args} {
2628 global current_diff diff_actions file_states
2629 if {$current_diff eq {}} {
2630 set s {}
2631 set f {}
2632 set p {}
2633 set o disabled
2634 } else {
2635 set p $current_diff
2636 set s [mapdesc [lindex $file_states($p) 0] $p]
2637 set f {File:}
2638 set p [escape_path $p]
2639 set o normal
2640 }
2641
2642 .vpane.lower.diff.header.status configure -text $s
2643 .vpane.lower.diff.header.file configure -text $f
2644 .vpane.lower.diff.header.path configure -text $p
2645 foreach w $diff_actions {
2646 uplevel #0 $w $o
2647 }
2648}
2649trace add variable current_diff write current_diff_trace
2650
0fb8f9ce 2651frame .vpane.lower.diff.header -background orange
e8ab6446 2652label .vpane.lower.diff.header.status \
3e7b0e1d
SP
2653 -background orange \
2654 -width $max_status_desc \
2655 -anchor w \
2656 -justify left \
2657 -font font_ui
e8ab6446 2658label .vpane.lower.diff.header.file \
0fb8f9ce 2659 -background orange \
e8ab6446
SP
2660 -anchor w \
2661 -justify left \
b4946930 2662 -font font_ui
e8ab6446 2663label .vpane.lower.diff.header.path \
0fb8f9ce 2664 -background orange \
fce89e46
SP
2665 -anchor w \
2666 -justify left \
b4946930 2667 -font font_ui
e8ab6446
SP
2668pack .vpane.lower.diff.header.status -side left
2669pack .vpane.lower.diff.header.file -side left
2670pack .vpane.lower.diff.header.path -fill x
2671set ctxm .vpane.lower.diff.header.ctxm
2672menu $ctxm -tearoff 0
2673$ctxm add command \
2674 -label {Copy} \
c11b5f20 2675 -font font_ui \
fce89e46
SP
2676 -command {
2677 clipboard clear
2678 clipboard append \
2679 -format STRING \
2680 -type STRING \
e8ab6446 2681 -- $current_diff
fce89e46 2682 }
e8ab6446
SP
2683lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2684bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
0fb8f9ce
SP
2685
2686# -- Diff Body
2687frame .vpane.lower.diff.body
2688set ui_diff .vpane.lower.diff.body.t
2689text $ui_diff -background white -borderwidth 0 \
2690 -width 80 -height 15 -wrap none \
b4946930 2691 -font font_diff \
0fb8f9ce
SP
2692 -xscrollcommand {.vpane.lower.diff.body.sbx set} \
2693 -yscrollcommand {.vpane.lower.diff.body.sby set} \
0fb8f9ce
SP
2694 -state disabled
2695scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
2696 -command [list $ui_diff xview]
2697scrollbar .vpane.lower.diff.body.sby -orient vertical \
2698 -command [list $ui_diff yview]
2699pack .vpane.lower.diff.body.sbx -side bottom -fill x
2700pack .vpane.lower.diff.body.sby -side right -fill y
2701pack $ui_diff -side left -fill both -expand 1
2702pack .vpane.lower.diff.header -side top -fill x
2703pack .vpane.lower.diff.body -side bottom -fill both -expand 1
2704
2705$ui_diff tag conf dm -foreground red
2706$ui_diff tag conf dp -foreground blue
b4946930
SP
2707$ui_diff tag conf di -foreground {#00a000}
2708$ui_diff tag conf dni -foreground {#a000a0}
2709$ui_diff tag conf da -font font_diffbold
2710$ui_diff tag conf bold -font font_diffbold
cb07fc2a 2711
0e794311
SP
2712# -- Diff Body Context Menu
2713#
e8ab6446
SP
2714set ctxm .vpane.lower.diff.body.ctxm
2715menu $ctxm -tearoff 0
2716$ctxm add command \
2717 -label {Copy} \
b4946930 2718 -font font_ui \
e8ab6446
SP
2719 -command {tk_textCopy $ui_diff}
2720lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2721$ctxm add command \
2722 -label {Select All} \
b4946930 2723 -font font_ui \
e8ab6446
SP
2724 -command {$ui_diff tag add sel 0.0 end}
2725lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2726$ctxm add command \
2727 -label {Copy All} \
b4946930 2728 -font font_ui \
e8ab6446 2729 -command {
0e794311
SP
2730 $ui_diff tag add sel 0.0 end
2731 tk_textCopy $ui_diff
2732 $ui_diff tag remove sel 0.0 end
e8ab6446
SP
2733 }
2734lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2735$ctxm add separator
2736$ctxm add command \
2737 -label {Decrease Font Size} \
b4946930
SP
2738 -font font_ui \
2739 -command {incr_font_size font_diff -1}
e8ab6446
SP
2740lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2741$ctxm add command \
2742 -label {Increase Font Size} \
b4946930
SP
2743 -font font_ui \
2744 -command {incr_font_size font_diff 1}
e8ab6446
SP
2745lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2746$ctxm add separator
2747$ctxm add command \
2748 -label {Show Less Context} \
358d8de8 2749 -font font_ui \
e8ab6446 2750 -command {if {$repo_config(gui.diffcontext) >= 2} {
358d8de8
SP
2751 incr repo_config(gui.diffcontext) -1
2752 reshow_diff
2753 }}
e8ab6446
SP
2754lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2755$ctxm add command \
2756 -label {Show More Context} \
358d8de8 2757 -font font_ui \
e8ab6446 2758 -command {
358d8de8
SP
2759 incr repo_config(gui.diffcontext)
2760 reshow_diff
e8ab6446
SP
2761 }
2762lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2763$ctxm add separator
2764$ctxm add command -label {Options...} \
8009dcdc
SP
2765 -font font_ui \
2766 -command do_options
e8ab6446 2767bind_button3 $ui_diff "tk_popup $ctxm %X %Y"
0e794311 2768
cb07fc2a 2769# -- Status Bar
e8ab6446 2770#
cb07fc2a
SP
2771set ui_status_value {Initializing...}
2772label .status -textvariable ui_status_value \
2773 -anchor w \
2774 -justify left \
2775 -borderwidth 1 \
2776 -relief sunken \
b4946930 2777 -font font_ui
cb07fc2a
SP
2778pack .status -anchor w -side bottom -fill x
2779
2d19516d 2780# -- Load geometry
e8ab6446 2781#
2d19516d 2782catch {
51f4d16b 2783set gm $repo_config(gui.geometry)
c4fe7728
SP
2784wm geometry . [lindex $gm 0]
2785.vpane sash place 0 \
2786 [lindex [.vpane sash coord 0] 0] \
2787 [lindex $gm 1]
2788.vpane.files sash place 0 \
2789 [lindex $gm 2] \
2790 [lindex [.vpane.files sash coord 0] 1]
c4fe7728 2791unset gm
390adaea 2792}
2d19516d 2793
cb07fc2a 2794# -- Key Bindings
e8ab6446 2795#
ec6b424a 2796bind $ui_comm <$M1B-Key-Return> {do_commit;break}
49b86f01
SP
2797bind $ui_comm <$M1B-Key-i> {do_include_all;break}
2798bind $ui_comm <$M1B-Key-I> {do_include_all;break}
9861671d
SP
2799bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2800bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2801bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2802bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2803bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2804bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2805bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2806bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2807
2808bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2809bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2810bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2811bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2812bind $ui_diff <$M1B-Key-v> {break}
2813bind $ui_diff <$M1B-Key-V> {break}
2814bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2815bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
b2c6fcf1
SP
2816bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break}
2817bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break}
2818bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break}
2819bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break}
49b86f01 2820
07123f40
SP
2821bind . <Destroy> do_quit
2822bind all <Key-F5> do_rescan
2823bind all <$M1B-Key-r> do_rescan
2824bind all <$M1B-Key-R> do_rescan
2825bind . <$M1B-Key-s> do_signoff
2826bind . <$M1B-Key-S> do_signoff
49b86f01
SP
2827bind . <$M1B-Key-i> do_include_all
2828bind . <$M1B-Key-I> do_include_all
07123f40
SP
2829bind . <$M1B-Key-Return> do_commit
2830bind all <$M1B-Key-q> do_quit
2831bind all <$M1B-Key-Q> do_quit
2832bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
2833bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
cb07fc2a 2834foreach i [list $ui_index $ui_other] {
24263b77
SP
2835 bind $i <Button-1> "toggle_or_diff $i %x %y; break"
2836 bind $i <$M1B-Button-1> "add_one_to_selection $i %x %y; break"
2837 bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
cb07fc2a 2838}
62aac80b
SP
2839unset i
2840
2841set file_lists($ui_index) [list]
2842set file_lists($ui_other) [list]
e8ab6446 2843set current_diff {}
cb07fc2a 2844
ec6b424a 2845wm title . "$appname ([file normalize [file dirname $gitdir]])"
cb07fc2a 2846focus -force $ui_comm
4ccdab02
SP
2847if {!$single_commit} {
2848 load_all_remotes
c1237ae2 2849 populate_fetch_menu .mbar.fetch
4ccdab02 2850 populate_pull_menu .mbar.pull
c1237ae2 2851 populate_push_menu .mbar.push
4ccdab02 2852}
8f52548a 2853after 1 do_rescan