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