git-gui: More performance improvements to rescan logic.
[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
cb07fc2a
SP
10######################################################################
11##
e210e674 12## task management
cb07fc2a 13
ec6b424a 14set single_commit 0
cb07fc2a 15set status_active 0
131f503b
SP
16set diff_active 0
17set checkin_active 0
ec6b424a 18set commit_active 0
131f503b
SP
19set update_index_fd {}
20
e210e674
SP
21set disable_on_lock [list]
22set index_lock_type none
23
e57ca85e
SP
24set HEAD {}
25set PARENT {}
26set commit_type {}
27
e210e674
SP
28proc lock_index {type} {
29 global index_lock_type disable_on_lock
131f503b 30
e210e674
SP
31 if {$index_lock_type == {none}} {
32 set index_lock_type $type
33 foreach w $disable_on_lock {
34 uplevel #0 $w disabled
35 }
36 return 1
37 } elseif {$index_lock_type == {begin-update} && $type == {update}} {
38 set index_lock_type $type
131f503b
SP
39 return 1
40 }
41 return 0
42}
cb07fc2a 43
e210e674
SP
44proc unlock_index {} {
45 global index_lock_type disable_on_lock
46
47 set index_lock_type none
48 foreach w $disable_on_lock {
49 uplevel #0 $w normal
50 }
51}
52
53######################################################################
54##
55## status
56
ec6b424a
SP
57proc repository_state {hdvar ctvar} {
58 global gitdir
59 upvar $hdvar hd $ctvar ct
60
61 if {[catch {set hd [exec git rev-parse --verify HEAD]}]} {
62 set ct initial
63 } elseif {[file exists [file join $gitdir MERGE_HEAD]]} {
64 set ct merge
65 } else {
66 set ct normal
67 }
68}
69
e57ca85e
SP
70proc update_status {{final Ready.}} {
71 global HEAD PARENT commit_type
131f503b 72 global ui_index ui_other ui_status_value ui_comm
6b292675 73 global status_active file_states status_start
cb07fc2a 74
e210e674 75 if {$status_active || ![lock_index read]} return
cb07fc2a 76
e57ca85e
SP
77 repository_state new_HEAD new_type
78 if {$commit_type == {amend}
79 && $new_type == {normal}
80 && $new_HEAD == $HEAD} {
81 } else {
82 set HEAD $new_HEAD
83 set PARENT $new_HEAD
84 set commit_type $new_type
85 }
86
6b292675 87 set status_start [clock seconds]
cb07fc2a 88 array unset file_states
cb07fc2a
SP
89 foreach w [list $ui_index $ui_other] {
90 $w conf -state normal
91 $w delete 0.0 end
92 $w conf -state disabled
93 }
94
131f503b 95 if {![$ui_comm edit modified]
cc4b1c02 96 || [string trim [$ui_comm get 0.0 end]] == {}} {
131f503b
SP
97 if {[load_message GITGUI_MSG]} {
98 } elseif {[load_message MERGE_MSG]} {
99 } elseif {[load_message SQUASH_MSG]} {
100 }
101 $ui_comm edit modified false
102 }
103
104 set status_active 1
105 set ui_status_value {Refreshing file status...}
106 set fd_rf [open "| git update-index -q --unmerged --refresh" r]
107 fconfigure $fd_rf -blocking 0 -translation binary
e57ca85e 108 fileevent $fd_rf readable [list read_refresh $fd_rf $final]
131f503b
SP
109}
110
e57ca85e
SP
111proc read_refresh {fd final} {
112 global gitdir PARENT commit_type
131f503b
SP
113 global ui_index ui_other ui_status_value ui_comm
114 global status_active file_states
868c8752 115 global buf_rdi buf_rdf buf_rlo
131f503b
SP
116
117 read $fd
118 if {![eof $fd]} return
119 close $fd
120
cb07fc2a
SP
121 set ls_others [list | git ls-files --others -z \
122 --exclude-per-directory=.gitignore]
123 set info_exclude [file join $gitdir info exclude]
124 if {[file readable $info_exclude]} {
125 lappend ls_others "--exclude-from=$info_exclude"
126 }
127
868c8752
SP
128 set buf_rdi {}
129 set buf_rdf {}
130 set buf_rlo {}
131
131f503b
SP
132 set status_active 3
133 set ui_status_value {Scanning for modified files ...}
e57ca85e 134 set fd_di [open "| git diff-index --cached -z $PARENT" r]
cb07fc2a
SP
135 set fd_df [open "| git diff-files -z" r]
136 set fd_lo [open $ls_others r]
cb07fc2a
SP
137
138 fconfigure $fd_di -blocking 0 -translation binary
139 fconfigure $fd_df -blocking 0 -translation binary
140 fconfigure $fd_lo -blocking 0 -translation binary
e57ca85e
SP
141 fileevent $fd_di readable [list read_diff_index $fd_di $final]
142 fileevent $fd_df readable [list read_diff_files $fd_df $final]
143 fileevent $fd_lo readable [list read_ls_others $fd_lo $final]
cb07fc2a
SP
144}
145
131f503b
SP
146proc load_message {file} {
147 global gitdir ui_comm
148
149 set f [file join $gitdir $file]
e57ca85e 150 if {[file isfile $f]} {
131f503b
SP
151 if {[catch {set fd [open $f r]}]} {
152 return 0
153 }
e57ca85e 154 set content [string trim [read $fd]]
131f503b
SP
155 close $fd
156 $ui_comm delete 0.0 end
157 $ui_comm insert end $content
158 return 1
159 }
160 return 0
161}
162
e57ca85e 163proc read_diff_index {fd final} {
cb07fc2a
SP
164 global buf_rdi
165
166 append buf_rdi [read $fd]
868c8752
SP
167 set c 0
168 set n [string length $buf_rdi]
169 while {$c < $n} {
170 set z1 [string first "\0" $buf_rdi $c]
171 if {$z1 == -1} break
172 incr z1
173 set z2 [string first "\0" $buf_rdi $z1]
174 if {$z2 == -1} break
175
176 set c $z2
177 incr z2 -1
178 display_file \
179 [string range $buf_rdi $z1 $z2] \
180 [string index $buf_rdi [expr $z1 - 2]]_
181 incr c
cb07fc2a 182 }
868c8752
SP
183 if {$c < $n} {
184 set buf_rdi [string range $buf_rdi $c end]
185 } else {
186 set buf_rdi {}
187 }
188
e57ca85e 189 status_eof $fd buf_rdi $final
cb07fc2a
SP
190}
191
e57ca85e 192proc read_diff_files {fd final} {
cb07fc2a
SP
193 global buf_rdf
194
195 append buf_rdf [read $fd]
868c8752
SP
196 set c 0
197 set n [string length $buf_rdf]
198 while {$c < $n} {
199 set z1 [string first "\0" $buf_rdf $c]
200 if {$z1 == -1} break
201 incr z1
202 set z2 [string first "\0" $buf_rdf $z1]
203 if {$z2 == -1} break
204
205 set c $z2
206 incr z2 -1
207 display_file \
208 [string range $buf_rdf $z1 $z2] \
209 _[string index $buf_rdf [expr $z1 - 2]]
210 incr c
211 }
212 if {$c < $n} {
213 set buf_rdf [string range $buf_rdf $c end]
214 } else {
215 set buf_rdf {}
cb07fc2a 216 }
868c8752 217
e57ca85e 218 status_eof $fd buf_rdf $final
cb07fc2a
SP
219}
220
e57ca85e 221proc read_ls_others {fd final} {
cb07fc2a
SP
222 global buf_rlo
223
224 append buf_rlo [read $fd]
225 set pck [split $buf_rlo "\0"]
226 set buf_rlo [lindex $pck end]
227 foreach p [lrange $pck 0 end-1] {
228 display_file $p _O
229 }
e57ca85e 230 status_eof $fd buf_rlo $final
cb07fc2a
SP
231}
232
e57ca85e 233proc status_eof {fd buf final} {
6b292675 234 global status_active status_start $buf
e57ca85e 235 global ui_fname_value ui_status_value file_states
cb07fc2a
SP
236
237 if {[eof $fd]} {
238 set $buf {}
239 close $fd
93f654df 240
cb07fc2a 241 if {[incr status_active -1] == 0} {
e210e674 242 unlock_index
e57ca85e 243
6b292675 244 set e1 [clock seconds]
93f654df 245 display_all_files
6b292675
SP
246 set e2 [clock seconds]
247puts "TIME [expr $e1 - $status_start] + [expr $e2 - $e1] = [expr $e2 - $status_start]"
248
249 set ui_status_value $final
93f654df 250
e57ca85e
SP
251 if {$ui_fname_value != {} && [array names file_states \
252 -exact $ui_fname_value] != {}} {
cb07fc2a 253 show_diff $ui_fname_value
e57ca85e
SP
254 } else {
255 clear_diff
cb07fc2a
SP
256 }
257 }
258 }
259}
260
261######################################################################
262##
263## diff
264
cb07fc2a
SP
265proc clear_diff {} {
266 global ui_diff ui_fname_value ui_fstatus_value
267
268 $ui_diff conf -state normal
269 $ui_diff delete 0.0 end
270 $ui_diff conf -state disabled
271 set ui_fname_value {}
272 set ui_fstatus_value {}
273}
274
275proc show_diff {path} {
e57ca85e 276 global file_states PARENT diff_3way diff_active
cb07fc2a
SP
277 global ui_diff ui_fname_value ui_fstatus_value ui_status_value
278
e210e674 279 if {$diff_active || ![lock_index read]} return
cb07fc2a
SP
280
281 clear_diff
282 set s $file_states($path)
283 set m [lindex $s 0]
284 set diff_3way 0
285 set diff_active 1
286 set ui_fname_value $path
287 set ui_fstatus_value [mapdesc $m $path]
288 set ui_status_value "Loading diff of $path..."
289
e57ca85e 290 set cmd [list | git diff-index -p $PARENT -- $path]
cb07fc2a
SP
291 switch $m {
292 AM {
293 }
294 MM {
e57ca85e 295 set cmd [list | git diff-index -p -c $PARENT $path]
cb07fc2a
SP
296 }
297 _O {
298 if {[catch {
299 set fd [open $path r]
300 set content [read $fd]
301 close $fd
302 } err ]} {
131f503b 303 set diff_active 0
e210e674 304 unlock_index
cb07fc2a
SP
305 set ui_status_value "Unable to display $path"
306 error_popup "Error loading file:\n$err"
307 return
308 }
309 $ui_diff conf -state normal
310 $ui_diff insert end $content
311 $ui_diff conf -state disabled
bd1e2b40
SP
312 set diff_active 0
313 unlock_index
314 set ui_status_value {Ready.}
cb07fc2a
SP
315 return
316 }
317 }
318
319 if {[catch {set fd [open $cmd r]} err]} {
131f503b 320 set diff_active 0
e210e674 321 unlock_index
cb07fc2a
SP
322 set ui_status_value "Unable to display $path"
323 error_popup "Error loading diff:\n$err"
324 return
325 }
326
6f6eed28 327 fconfigure $fd -blocking 0 -translation auto
cb07fc2a
SP
328 fileevent $fd readable [list read_diff $fd]
329}
330
331proc read_diff {fd} {
332 global ui_diff ui_status_value diff_3way diff_active
333
334 while {[gets $fd line] >= 0} {
6f6eed28
SP
335 if {[string match {diff --git *} $line]} continue
336 if {[string match {diff --combined *} $line]} continue
337 if {[string match {--- *} $line]} continue
338 if {[string match {+++ *} $line]} continue
cb07fc2a
SP
339 if {[string match index* $line]} {
340 if {[string first , $line] >= 0} {
341 set diff_3way 1
342 }
343 }
344
345 $ui_diff conf -state normal
346 if {!$diff_3way} {
347 set x [string index $line 0]
348 switch -- $x {
349 "@" {set tags da}
350 "+" {set tags dp}
351 "-" {set tags dm}
352 default {set tags {}}
353 }
354 } else {
355 set x [string range $line 0 1]
356 switch -- $x {
357 default {set tags {}}
358 "@@" {set tags da}
359 "++" {set tags dp; set x " +"}
360 " +" {set tags {di bold}; set x "++"}
361 "+ " {set tags dni; set x "-+"}
362 "--" {set tags dm; set x " -"}
363 " -" {set tags {dm bold}; set x "--"}
364 "- " {set tags di; set x "+-"}
365 default {set tags {}}
366 }
367 set line [string replace $line 0 1 $x]
368 }
369 $ui_diff insert end $line $tags
370 $ui_diff insert end "\n"
371 $ui_diff conf -state disabled
372 }
373
374 if {[eof $fd]} {
375 close $fd
376 set diff_active 0
e210e674 377 unlock_index
cb07fc2a
SP
378 set ui_status_value {Ready.}
379 }
380}
381
ec6b424a
SP
382######################################################################
383##
384## commit
385
e57ca85e
SP
386proc load_last_commit {} {
387 global HEAD PARENT commit_type ui_comm
388
389 if {$commit_type == {amend}} return
390 if {$commit_type != {normal}} {
391 error_popup "Can't amend a $commit_type commit."
392 return
393 }
394
395 set msg {}
396 set parent {}
397 set parent_count 0
398 if {[catch {
399 set fd [open "| git cat-file commit $HEAD" r]
400 while {[gets $fd line] > 0} {
401 if {[string match {parent *} $line]} {
402 set parent [string range $line 7 end]
403 incr parent_count
404 }
405 }
406 set msg [string trim [read $fd]]
407 close $fd
408 } err]} {
409 error_popup "Error loading commit data for amend:\n$err"
410 return
411 }
412
413 if {$parent_count == 0} {
414 set commit_type amend
415 set HEAD {}
416 set PARENT {}
417 update_status
418 } elseif {$parent_count == 1} {
419 set commit_type amend
420 set PARENT $parent
421 $ui_comm delete 0.0 end
422 $ui_comm insert end $msg
423 $ui_comm edit modified false
424 update_status
425 } else {
426 error_popup {You can't amend a merge commit.}
427 return
428 }
429}
430
ec6b424a
SP
431proc commit_tree {} {
432 global tcl_platform HEAD gitdir commit_type file_states
433 global commit_active ui_status_value
434 global ui_comm
435
436 if {$commit_active || ![lock_index update]} return
437
438 # -- Our in memory state should match the repository.
439 #
440 repository_state curHEAD cur_type
e57ca85e
SP
441 if {$commit_type == {amend}
442 && $cur_type == {normal}
443 && $curHEAD == $HEAD} {
444 } elseif {$commit_type != $cur_type || $HEAD != $curHEAD} {
ec6b424a
SP
445 error_popup {Last scanned state does not match repository state.
446
447Its highly likely that another Git program modified the
448repository since our last scan. A rescan is required
449before committing.
450}
451 unlock_index
452 update_status
453 return
454 }
455
456 # -- At least one file should differ in the index.
457 #
458 set files_ready 0
459 foreach path [array names file_states] {
460 set s $file_states($path)
461 switch -glob -- [lindex $s 0] {
462 _* {continue}
463 A* -
464 D* -
465 M* {set files_ready 1; break}
466 U* {
467 error_popup "Unmerged files cannot be committed.
468
469File $path has merge conflicts.
470You must resolve them and check the file in before committing.
471"
472 unlock_index
473 return
474 }
475 default {
476 error_popup "Unknown file state [lindex $s 0] detected.
477
478File $path cannot be committed by this program.
479"
480 }
481 }
482 }
483 if {!$files_ready} {
484 error_popup {No checked-in files to commit.
485
486You must check-in at least 1 file before you can commit.
487}
488 unlock_index
489 return
490 }
491
492 # -- A message is required.
493 #
494 set msg [string trim [$ui_comm get 1.0 end]]
495 if {$msg == {}} {
496 error_popup {Please supply a commit message.
497
498A good commit message has the following format:
499
500- First line: Describe in one sentance what you did.
501- Second line: Blank
502- Remaining lines: Describe why this change is good.
503}
504 unlock_index
505 return
506 }
507
508 # -- Ask the pre-commit hook for the go-ahead.
509 #
510 set pchook [file join $gitdir hooks pre-commit]
e57ca85e 511 if {$tcl_platform(platform) == {windows} && [file isfile $pchook]} {
ec6b424a
SP
512 set pchook [list sh -c \
513 "if test -x \"$pchook\"; then exec \"$pchook\"; fi"]
514 } elseif {[file executable $pchook]} {
515 set pchook [list $pchook]
516 } else {
517 set pchook {}
518 }
519 if {$pchook != {} && [catch {eval exec $pchook} err]} {
520 hook_failed_popup pre-commit $err
521 unlock_index
522 return
523 }
524
525 # -- Write the tree in the background.
526 #
527 set commit_active 1
528 set ui_status_value {Committing changes...}
529
530 set fd_wt [open "| git write-tree" r]
bd1e2b40 531 fileevent $fd_wt readable [list commit_stage2 $fd_wt $curHEAD $msg]
ec6b424a
SP
532}
533
534proc commit_stage2 {fd_wt curHEAD msg} {
e57ca85e
SP
535 global single_commit gitdir PARENT commit_type
536 global commit_active ui_status_value ui_comm
ec6b424a
SP
537
538 gets $fd_wt tree_id
539 close $fd_wt
540
541 if {$tree_id == {}} {
542 error_popup "write-tree failed"
543 set commit_active 0
544 set ui_status_value {Commit failed.}
545 unlock_index
546 return
547 }
548
549 # -- Create the commit.
550 #
551 set cmd [list git commit-tree $tree_id]
e57ca85e
SP
552 if {$PARENT != {}} {
553 lappend cmd -p $PARENT
ec6b424a
SP
554 }
555 if {$commit_type == {merge}} {
556 if {[catch {
557 set fd_mh [open [file join $gitdir MERGE_HEAD] r]
bd1e2b40
SP
558 while {[gets $fd_mh merge_head] >= 0} {
559 lappend cmd -p $merge_head
ec6b424a
SP
560 }
561 close $fd_mh
562 } err]} {
563 error_popup "Loading MERGE_HEADs failed:\n$err"
564 set commit_active 0
565 set ui_status_value {Commit failed.}
566 unlock_index
567 return
568 }
569 }
e57ca85e 570 if {$PARENT == {}} {
ec6b424a
SP
571 # git commit-tree writes to stderr during initial commit.
572 lappend cmd 2>/dev/null
573 }
574 lappend cmd << $msg
575 if {[catch {set cmt_id [eval exec $cmd]} err]} {
576 error_popup "commit-tree failed:\n$err"
577 set commit_active 0
578 set ui_status_value {Commit failed.}
579 unlock_index
580 return
581 }
582
583 # -- Update the HEAD ref.
584 #
585 set reflogm commit
586 if {$commit_type != {normal}} {
587 append reflogm " ($commit_type)"
588 }
589 set i [string first "\n" $msg]
590 if {$i >= 0} {
591 append reflogm {: } [string range $msg 0 [expr $i - 1]]
592 } else {
593 append reflogm {: } $msg
594 }
e57ca85e 595 set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD]
ec6b424a
SP
596 if {[catch {eval exec $cmd} err]} {
597 error_popup "update-ref failed:\n$err"
598 set commit_active 0
599 set ui_status_value {Commit failed.}
600 unlock_index
601 return
602 }
603
604 # -- Cleanup after ourselves.
605 #
606 catch {file delete [file join $gitdir MERGE_HEAD]}
607 catch {file delete [file join $gitdir MERGE_MSG]}
608 catch {file delete [file join $gitdir SQUASH_MSG]}
609 catch {file delete [file join $gitdir GITGUI_MSG]}
610
611 # -- Let rerere do its thing.
612 #
613 if {[file isdirectory [file join $gitdir rr-cache]]} {
614 catch {exec git rerere}
615 }
616
e57ca85e
SP
617 $ui_comm delete 0.0 end
618 $ui_comm edit modified false
ec6b424a
SP
619
620 if {$single_commit} do_quit
621
e57ca85e 622 set commit_type {}
ec6b424a 623 set commit_active 0
bd1e2b40
SP
624 set HEAD $cmt_id
625 set PARENT $cmt_id
ec6b424a 626 unlock_index
e57ca85e 627 update_status "Changes committed as $cmt_id."
ec6b424a
SP
628}
629
8c0ce436
SP
630######################################################################
631##
632## fetch pull push
633
634proc fetch_from {remote} {
635 set w [new_console "fetch $remote" \
636 "Fetching new changes from $remote"]
cc4b1c02 637 set cmd [list git fetch]
8c0ce436 638 lappend cmd $remote
cc4b1c02 639 console_exec $w $cmd
8c0ce436
SP
640}
641
d33ba5fa
SP
642proc pull_remote {remote branch} {
643 set w [new_console "pull $remote $branch" \
644 "Pulling new changes from branch $branch in $remote"]
645 set cmd [list git pull]
646 lappend cmd $remote
647 lappend cmd $branch
648 console_exec $w $cmd [list post_pull_remote $remote $branch]
649}
650
651proc post_pull_remote {remote branch success} {
652 if {$success} {
653 update_status "Successfully pulled $branch from $remote."
654 } else {
655 update_status "Conflicts detected while pulling $branch from $remote."
656 }
657}
658
8c0ce436
SP
659proc push_to {remote} {
660 set w [new_console "push $remote" \
661 "Pushing changes to $remote"]
cc4b1c02 662 set cmd [list git push]
8c0ce436 663 lappend cmd $remote
cc4b1c02 664 console_exec $w $cmd
8c0ce436
SP
665}
666
cb07fc2a
SP
667######################################################################
668##
669## ui helpers
670
671proc mapcol {state path} {
6b292675 672 global all_cols ui_other
cb07fc2a
SP
673
674 if {[catch {set r $all_cols($state)}]} {
675 puts "error: no column for state={$state} $path"
6b292675 676 return $ui_other
cb07fc2a
SP
677 }
678 return $r
679}
680
681proc mapicon {state path} {
682 global all_icons
683
684 if {[catch {set r $all_icons($state)}]} {
685 puts "error: no icon for state={$state} $path"
686 return file_plain
687 }
688 return $r
689}
690
691proc mapdesc {state path} {
692 global all_descs
693
694 if {[catch {set r $all_descs($state)}]} {
695 puts "error: no desc for state={$state} $path"
696 return $state
697 }
698 return $r
699}
700
701proc bsearch {w path} {
702 set hi [expr [lindex [split [$w index end] .] 0] - 2]
703 if {$hi == 0} {
704 return -1
705 }
706 set lo 0
707 while {$lo < $hi} {
708 set mi [expr [expr $lo + $hi] / 2]
709 set ti [expr $mi + 1]
710 set cmp [string compare [$w get $ti.1 $ti.end] $path]
711 if {$cmp < 0} {
712 set lo $ti
713 } elseif {$cmp == 0} {
714 return $mi
715 } else {
716 set hi $mi
717 }
718 }
719 return -[expr $lo + 1]
720}
721
93f654df
SP
722set next_icon_id 0
723
6b292675 724proc merge_state {path new_state} {
93f654df 725 global file_states next_icon_id
cb07fc2a 726
6b292675
SP
727 set s0 [string index $new_state 0]
728 set s1 [string index $new_state 1]
729
730 if {[catch {set info $file_states($path)}]} {
731 set state __
732 set icon n[incr next_icon_id]
cb07fc2a 733 } else {
6b292675
SP
734 set state [lindex $info 0]
735 set icon [lindex $info 1]
cb07fc2a
SP
736 }
737
6b292675
SP
738 if {$s0 == {_}} {
739 set s0 [string index $state 0]
740 } elseif {$s0 == {*}} {
741 set s0 _
cb07fc2a
SP
742 }
743
6b292675
SP
744 if {$s1 == {_}} {
745 set s1 [string index $state 1]
746 } elseif {$s1 == {*}} {
747 set s1 _
cb07fc2a
SP
748 }
749
6b292675
SP
750 set file_states($path) [list $s0$s1 $icon]
751 return $state
cb07fc2a
SP
752}
753
754proc display_file {path state} {
93f654df 755 global ui_index ui_other file_states status_active
cb07fc2a
SP
756
757 set old_m [merge_state $path $state]
93f654df
SP
758 if {$status_active} return
759
cb07fc2a 760 set s $file_states($path)
6b292675
SP
761 set old_w [mapcol $old_m $path]
762 set new_w [mapcol $new_m $path]
93f654df 763 set new_m [lindex $s 0]
93f654df 764 set new_ico [mapicon $new_m $path]
cb07fc2a 765
6b292675 766 if {$new_w != $old_w} {
93f654df 767 set lno [bsearch $old_w $path]
cb07fc2a
SP
768 if {$lno >= 0} {
769 incr lno
93f654df
SP
770 $old_w conf -state normal
771 $old_w delete $lno.0 [expr $lno + 1].0
772 $old_w conf -state disabled
cb07fc2a 773 }
93f654df
SP
774
775 set lno [expr abs([bsearch $new_w $path] + 1) + 1]
776 $new_w conf -state normal
777 $new_w image create $lno.0 \
778 -align center -padx 5 -pady 1 \
779 -name [lindex $s 1] \
780 -image [mapicon $m $path]
781 $new_w insert $lno.1 "$path\n"
782 $new_w conf -state disabled
783 } elseif {$new_icon != [mapicon $old_m $path]} {
784 $new_w conf -state normal
785 $new_w image conf [lindex $s 1] -image $new_icon
786 $new_w conf -state disabled
cb07fc2a 787 }
93f654df 788}
cb07fc2a 789
93f654df
SP
790proc display_all_files {} {
791 global ui_index ui_other file_states
792
793 $ui_index conf -state normal
794 $ui_other conf -state normal
795
796 foreach path [lsort [array names file_states]] {
797 set s $file_states($path)
798 set m [lindex $s 0]
6b292675
SP
799 set w [mapcol $m $path]
800 $w image create end \
cb07fc2a 801 -align center -padx 5 -pady 1 \
93f654df
SP
802 -name [lindex $s 1] \
803 -image [mapicon $m $path]
6b292675 804 $w insert end "$path\n"
cb07fc2a 805 }
93f654df
SP
806
807 $ui_index conf -state disabled
808 $ui_other conf -state disabled
cb07fc2a
SP
809}
810
131f503b
SP
811proc with_update_index {body} {
812 global update_index_fd
813
814 if {$update_index_fd == {}} {
e210e674 815 if {![lock_index update]} return
131f503b
SP
816 set update_index_fd [open \
817 "| git update-index --add --remove -z --stdin" \
818 w]
819 fconfigure $update_index_fd -translation binary
820 uplevel 1 $body
821 close $update_index_fd
822 set update_index_fd {}
e210e674 823 unlock_index
131f503b
SP
824 } else {
825 uplevel 1 $body
826 }
827}
828
829proc update_index {path} {
830 global update_index_fd
831
832 if {$update_index_fd == {}} {
833 error {not in with_update_index}
834 } else {
835 puts -nonewline $update_index_fd "$path\0"
836 }
837}
838
cb07fc2a 839proc toggle_mode {path} {
bd1e2b40 840 global file_states ui_fname_value
cb07fc2a
SP
841
842 set s $file_states($path)
843 set m [lindex $s 0]
844
845 switch -- $m {
846 AM -
131f503b
SP
847 _O {set new A*}
848 _M -
849 MM {set new M*}
bd1e2b40 850 AD -
131f503b
SP
851 _D {set new D*}
852 default {return}
cb07fc2a
SP
853 }
854
131f503b 855 with_update_index {update_index $path}
cb07fc2a 856 display_file $path $new
bd1e2b40
SP
857 if {$ui_fname_value == $path} {
858 show_diff $path
859 }
cb07fc2a
SP
860}
861
8c0ce436
SP
862######################################################################
863##
864## config (fetch push pull)
865
0d4f3eb5
SP
866proc load_repo_config {} {
867 global repo_config
868
869 array unset repo_config
870 catch {
871 set fd_rc [open "| git repo-config --list" r]
872 while {[gets $fd_rc line] >= 0} {
873 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
874 lappend repo_config($name) $value
875 }
876 }
877 close $fd_rc
878 }
879}
880
8c0ce436 881proc load_all_remotes {} {
0d4f3eb5 882 global gitdir all_remotes repo_config
8c0ce436
SP
883
884 set all_remotes [list]
885 set rm_dir [file join $gitdir remotes]
886 if {[file isdirectory $rm_dir]} {
d47ae541
SP
887 set all_remotes [concat $all_remotes [glob \
888 -types f \
889 -tails \
890 -nocomplain \
891 -directory $rm_dir *]]
8c0ce436
SP
892 }
893
0d4f3eb5
SP
894 foreach line [array names repo_config remote.*.url] {
895 if {[regexp ^remote\.(.*)\.url\$ $line line name]} {
8c0ce436
SP
896 lappend all_remotes $name
897 }
898 }
8c0ce436
SP
899
900 set all_remotes [lsort -unique $all_remotes]
901}
902
903proc populate_remote_menu {m pfx op} {
d33ba5fa 904 global all_remotes mainfont
8c0ce436
SP
905
906 foreach remote $all_remotes {
907 $m add command -label "$pfx $remote..." \
908 -command [list $op $remote] \
909 -font $mainfont
910 }
911}
912
d33ba5fa
SP
913proc populate_pull_menu {m} {
914 global gitdir repo_config all_remotes mainfont
915
916 foreach remote $all_remotes {
917 set rb {}
918 if {[array get repo_config remote.$remote.url] != {}} {
919 if {[array get repo_config remote.$remote.fetch] != {}} {
920 regexp {^([^:]+):} \
921 [lindex $repo_config(remote.$remote.fetch) 0] \
922 line rb
923 }
924 } else {
925 catch {
926 set fd [open [file join $gitdir remotes $remote] r]
927 while {[gets $fd line] >= 0} {
928 if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} {
929 break
930 }
931 }
932 close $fd
933 }
934 }
935
936 set rb_short $rb
937 regsub ^refs/heads/ $rb {} rb_short
938 if {$rb_short != {}} {
939 $m add command \
940 -label "Branch $rb_short from $remote..." \
941 -command [list pull_remote $remote $rb] \
942 -font $mainfont
943 }
944 }
945}
946
cb07fc2a
SP
947######################################################################
948##
949## icons
950
951set filemask {
952#define mask_width 14
953#define mask_height 15
954static unsigned char mask_bits[] = {
955 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
956 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
957 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
958}
959
960image create bitmap file_plain -background white -foreground black -data {
961#define plain_width 14
962#define plain_height 15
963static unsigned char plain_bits[] = {
964 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
965 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
966 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
967} -maskdata $filemask
968
969image create bitmap file_mod -background white -foreground blue -data {
970#define mod_width 14
971#define mod_height 15
972static unsigned char mod_bits[] = {
973 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
974 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
975 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
976} -maskdata $filemask
977
131f503b
SP
978image create bitmap file_fulltick -background white -foreground "#007000" -data {
979#define file_fulltick_width 14
980#define file_fulltick_height 15
981static unsigned char file_fulltick_bits[] = {
cb07fc2a
SP
982 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
983 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
984 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
985} -maskdata $filemask
986
987image create bitmap file_parttick -background white -foreground "#005050" -data {
988#define parttick_width 14
989#define parttick_height 15
990static unsigned char parttick_bits[] = {
991 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
992 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
993 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
994} -maskdata $filemask
995
996image create bitmap file_question -background white -foreground black -data {
997#define file_question_width 14
998#define file_question_height 15
999static unsigned char file_question_bits[] = {
1000 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1001 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1002 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1003} -maskdata $filemask
1004
1005image create bitmap file_removed -background white -foreground red -data {
1006#define file_removed_width 14
1007#define file_removed_height 15
1008static unsigned char file_removed_bits[] = {
1009 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1010 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1011 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1012} -maskdata $filemask
1013
1014image create bitmap file_merge -background white -foreground blue -data {
1015#define file_merge_width 14
1016#define file_merge_height 15
1017static unsigned char file_merge_bits[] = {
1018 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1019 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1020 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1021} -maskdata $filemask
1022
6b292675
SP
1023set ui_index .vpane.files.index.list
1024set ui_other .vpane.files.other.list
131f503b 1025set max_status_desc 0
cb07fc2a 1026foreach i {
131f503b
SP
1027 {__ i plain "Unmodified"}
1028 {_M i mod "Modified"}
1029 {M_ i fulltick "Checked in"}
1030 {MM i parttick "Partially checked in"}
1031
1032 {_O o plain "Untracked"}
1033 {A_ o fulltick "Added"}
1034 {AM o parttick "Partially added"}
6b292675 1035 {AD o question "Added (but now gone)"}
131f503b
SP
1036
1037 {_D i question "Missing"}
1038 {D_ i removed "Removed"}
1039 {DD i removed "Removed"}
1040 {DO i removed "Removed (still exists)"}
1041
1042 {UM i merge "Merge conflicts"}
1043 {U_ i merge "Merge conflicts"}
cb07fc2a 1044 } {
131f503b
SP
1045 if {$max_status_desc < [string length [lindex $i 3]]} {
1046 set max_status_desc [string length [lindex $i 3]]
1047 }
6b292675
SP
1048 if {[lindex $i 1] == {i}} {
1049 set all_cols([lindex $i 0]) $ui_index
1050 } else {
1051 set all_cols([lindex $i 0]) $ui_other
1052 }
131f503b
SP
1053 set all_icons([lindex $i 0]) file_[lindex $i 2]
1054 set all_descs([lindex $i 0]) [lindex $i 3]
cb07fc2a
SP
1055}
1056unset filemask i
1057
1058######################################################################
1059##
1060## util
1061
1062proc error_popup {msg} {
1063 set w .error
1064 toplevel $w
1065 wm transient $w .
1066 show_msg $w $w $msg
1067}
1068
1069proc show_msg {w top msg} {
b8ce6f0e 1070 global gitdir appname mainfont
6e27d826
SP
1071
1072 message $w.m -text $msg -justify left -aspect 400
ec6b424a
SP
1073 pack $w.m -side top -fill x -padx 5 -pady 10
1074 button $w.ok -text OK \
1075 -width 15 \
8c0ce436 1076 -font $mainfont \
ec6b424a 1077 -command "destroy $top"
6e27d826 1078 pack $w.ok -side bottom
cb07fc2a
SP
1079 bind $top <Visibility> "grab $top; focus $top"
1080 bind $top <Key-Return> "destroy $top"
d33ba5fa
SP
1081 wm title $w "$appname ([lindex [file split \
1082 [file normalize [file dirname $gitdir]]] \
1083 end]): error"
cb07fc2a
SP
1084 tkwait window $top
1085}
1086
6e27d826 1087proc hook_failed_popup {hook msg} {
ec6b424a 1088 global gitdir mainfont difffont appname
6e27d826
SP
1089
1090 set w .hookfail
1091 toplevel $w
1092 wm transient $w .
1093
1094 frame $w.m
1095 label $w.m.l1 -text "$hook hook failed:" \
1096 -anchor w \
1097 -justify left \
1098 -font [concat $mainfont bold]
1099 text $w.m.t \
1100 -background white -borderwidth 1 \
1101 -relief sunken \
1102 -width 80 -height 10 \
1103 -font $difffont \
1104 -yscrollcommand [list $w.m.sby set]
1105 label $w.m.l2 \
1106 -text {You must correct the above errors before committing.} \
1107 -anchor w \
1108 -justify left \
1109 -font [concat $mainfont bold]
1110 scrollbar $w.m.sby -command [list $w.m.t yview]
1111 pack $w.m.l1 -side top -fill x
1112 pack $w.m.l2 -side bottom -fill x
1113 pack $w.m.sby -side right -fill y
1114 pack $w.m.t -side left -fill both -expand 1
1115 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1116
1117 $w.m.t insert 1.0 $msg
1118 $w.m.t conf -state disabled
1119
1120 button $w.ok -text OK \
1121 -width 15 \
8c0ce436 1122 -font $mainfont \
6e27d826
SP
1123 -command "destroy $w"
1124 pack $w.ok -side bottom
1125
1126 bind $w <Visibility> "grab $w; focus $w"
1127 bind $w <Key-Return> "destroy $w"
d33ba5fa
SP
1128 wm title $w "$appname ([lindex [file split \
1129 [file normalize [file dirname $gitdir]]] \
1130 end]): error"
6e27d826
SP
1131 tkwait window $w
1132}
1133
8c0ce436
SP
1134set next_console_id 0
1135
1136proc new_console {short_title long_title} {
37af79d1
SP
1137 global next_console_id console_data
1138 set w .console[incr next_console_id]
1139 set console_data($w) [list $short_title $long_title]
1140 return [console_init $w]
1141}
1142
1143proc console_init {w} {
1144 global console_cr console_data
ee3dc935 1145 global gitdir appname mainfont difffont
8c0ce436 1146
ee3dc935 1147 set console_cr($w) 1.0
8c0ce436
SP
1148 toplevel $w
1149 frame $w.m
37af79d1 1150 label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
8c0ce436
SP
1151 -anchor w \
1152 -justify left \
1153 -font [concat $mainfont bold]
1154 text $w.m.t \
1155 -background white -borderwidth 1 \
1156 -relief sunken \
1157 -width 80 -height 10 \
1158 -font $difffont \
1159 -state disabled \
1160 -yscrollcommand [list $w.m.sby set]
07123f40
SP
1161 label $w.m.s -anchor w \
1162 -justify left \
1163 -font [concat $mainfont bold]
8c0ce436
SP
1164 scrollbar $w.m.sby -command [list $w.m.t yview]
1165 pack $w.m.l1 -side top -fill x
07123f40 1166 pack $w.m.s -side bottom -fill x
8c0ce436
SP
1167 pack $w.m.sby -side right -fill y
1168 pack $w.m.t -side left -fill both -expand 1
1169 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1170
ee3dc935 1171 button $w.ok -text {Running...} \
8c0ce436
SP
1172 -width 15 \
1173 -font $mainfont \
1174 -state disabled \
1175 -command "destroy $w"
1176 pack $w.ok -side bottom
1177
1178 bind $w <Visibility> "focus $w"
d33ba5fa
SP
1179 wm title $w "$appname ([lindex [file split \
1180 [file normalize [file dirname $gitdir]]] \
1181 end]): [lindex $console_data($w) 0]"
8c0ce436
SP
1182 return $w
1183}
1184
d33ba5fa 1185proc console_exec {w cmd {after {}}} {
cc4b1c02
SP
1186 global tcl_platform
1187
1188 # -- Windows tosses the enviroment when we exec our child.
1189 # But most users need that so we have to relogin. :-(
1190 #
1191 if {$tcl_platform(platform) == {windows}} {
1192 set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
1193 }
1194
1195 # -- Tcl won't let us redirect both stdout and stderr to
1196 # the same pipe. So pass it through cat...
1197 #
1198 set cmd [concat | $cmd |& cat]
1199
1200 set fd_f [open $cmd r]
ee3dc935 1201 fconfigure $fd_f -blocking 0 -translation binary
d33ba5fa 1202 fileevent $fd_f readable [list console_read $w $fd_f $after]
cc4b1c02
SP
1203}
1204
d33ba5fa 1205proc console_read {w fd after} {
37af79d1 1206 global console_cr console_data
ee3dc935 1207
ee3dc935 1208 set buf [read $fd]
37af79d1
SP
1209 if {$buf != {}} {
1210 if {![winfo exists $w]} {console_init $w}
1211 $w.m.t conf -state normal
1212 set c 0
1213 set n [string length $buf]
1214 while {$c < $n} {
1215 set cr [string first "\r" $buf $c]
1216 set lf [string first "\n" $buf $c]
1217 if {$cr < 0} {set cr [expr $n + 1]}
1218 if {$lf < 0} {set lf [expr $n + 1]}
1219
1220 if {$lf < $cr} {
1221 $w.m.t insert end [string range $buf $c $lf]
1222 set console_cr($w) [$w.m.t index {end -1c}]
1223 set c $lf
1224 incr c
1225 } else {
1226 $w.m.t delete $console_cr($w) end
1227 $w.m.t insert end "\n"
1228 $w.m.t insert end [string range $buf $c $cr]
1229 set c $cr
1230 incr c
1231 }
ee3dc935 1232 }
37af79d1
SP
1233 $w.m.t conf -state disabled
1234 $w.m.t see end
8c0ce436 1235 }
8c0ce436 1236
07123f40 1237 fconfigure $fd -blocking 1
8c0ce436 1238 if {[eof $fd]} {
07123f40 1239 if {[catch {close $fd}]} {
37af79d1 1240 if {![winfo exists $w]} {console_init $w}
07123f40 1241 $w.m.s conf -background red -text {Error: Command Failed}
37af79d1
SP
1242 $w.ok conf -text Close
1243 $w.ok conf -state normal
d33ba5fa 1244 set ok 0
37af79d1 1245 } elseif {[winfo exists $w]} {
07123f40 1246 $w.m.s conf -background green -text {Success}
37af79d1
SP
1247 $w.ok conf -text Close
1248 $w.ok conf -state normal
d33ba5fa 1249 set ok 1
07123f40 1250 }
ee3dc935 1251 array unset console_cr $w
37af79d1 1252 array unset console_data $w
d33ba5fa
SP
1253 if {$after != {}} {
1254 uplevel #0 $after $ok
1255 }
07123f40 1256 return
8c0ce436 1257 }
07123f40 1258 fconfigure $fd -blocking 0
8c0ce436
SP
1259}
1260
cb07fc2a
SP
1261######################################################################
1262##
1263## ui commands
1264
e210e674 1265set starting_gitk_msg {Please wait... Starting gitk...}
cc4b1c02 1266
cb07fc2a 1267proc do_gitk {} {
e210e674
SP
1268 global tcl_platform ui_status_value starting_gitk_msg
1269
1270 set ui_status_value $starting_gitk_msg
e57ca85e 1271 after 10000 {
e210e674
SP
1272 if {$ui_status_value == $starting_gitk_msg} {
1273 set ui_status_value {Ready.}
1274 }
1275 }
cb07fc2a 1276
cc4b1c02 1277 if {$tcl_platform(platform) == {windows}} {
cb07fc2a
SP
1278 exec sh -c gitk &
1279 } else {
1280 exec gitk &
1281 }
1282}
1283
1284proc do_quit {} {
131f503b
SP
1285 global gitdir ui_comm
1286
1287 set save [file join $gitdir GITGUI_MSG]
ec6b424a
SP
1288 set msg [string trim [$ui_comm get 0.0 end]]
1289 if {[$ui_comm edit modified] && $msg != {}} {
131f503b
SP
1290 catch {
1291 set fd [open $save w]
1292 puts $fd [string trim [$ui_comm get 0.0 end]]
1293 close $fd
1294 }
ec6b424a 1295 } elseif {$msg == {} && [file exists $save]} {
131f503b
SP
1296 file delete $save
1297 }
1298
cb07fc2a
SP
1299 destroy .
1300}
1301
1302proc do_rescan {} {
1303 update_status
1304}
1305
131f503b
SP
1306proc do_checkin_all {} {
1307 global checkin_active ui_status_value
1308
e210e674 1309 if {$checkin_active || ![lock_index begin-update]} return
131f503b
SP
1310
1311 set checkin_active 1
1312 set ui_status_value {Checking in all files...}
1313 after 1 {
1314 with_update_index {
1315 foreach path [array names file_states] {
1316 set s $file_states($path)
1317 set m [lindex $s 0]
1318 switch -- $m {
1319 AM -
1320 MM -
1321 _M -
1322 _D {toggle_mode $path}
1323 }
1324 }
1325 }
1326 set checkin_active 0
1327 set ui_status_value {Ready.}
1328 }
1329}
1330
1331proc do_signoff {} {
1332 global ui_comm
1333
1334 catch {
1335 set me [exec git var GIT_COMMITTER_IDENT]
1336 if {[regexp {(.*) [0-9]+ [-+0-9]+$} $me me name]} {
1337 set str "Signed-off-by: $name"
1338 if {[$ui_comm get {end -1c linestart} {end -1c}] != $str} {
1339 $ui_comm insert end "\n"
1340 $ui_comm insert end $str
1341 $ui_comm see end
1342 }
1343 }
1344 }
1345}
1346
e57ca85e
SP
1347proc do_amend_last {} {
1348 load_last_commit
1349}
1350
6e27d826 1351proc do_commit {} {
ec6b424a 1352 commit_tree
6e27d826
SP
1353}
1354
cb07fc2a
SP
1355# shift == 1: left click
1356# 3: right click
1357proc click {w x y shift wx wy} {
131f503b
SP
1358 global ui_index ui_other
1359
cb07fc2a
SP
1360 set pos [split [$w index @$x,$y] .]
1361 set lno [lindex $pos 0]
1362 set col [lindex $pos 1]
1363 set path [$w get $lno.1 $lno.end]
1364 if {$path == {}} return
1365
1366 if {$col > 0 && $shift == 1} {
131f503b
SP
1367 $ui_index tag remove in_diff 0.0 end
1368 $ui_other tag remove in_diff 0.0 end
1369 $w tag add in_diff $lno.0 [expr $lno + 1].0
cb07fc2a
SP
1370 show_diff $path
1371 }
1372}
1373
1374proc unclick {w x y} {
1375 set pos [split [$w index @$x,$y] .]
1376 set lno [lindex $pos 0]
1377 set col [lindex $pos 1]
1378 set path [$w get $lno.1 $lno.end]
1379 if {$path == {}} return
1380
e210e674 1381 if {$col == 0} {
cb07fc2a
SP
1382 toggle_mode $path
1383 }
1384}
1385
1386######################################################################
1387##
1388## ui init
1389
1390set mainfont {Helvetica 10}
1391set difffont {Courier 10}
1392set maincursor [. cget -cursor]
1393
66144892
SP
1394switch -glob -- "$tcl_platform(platform),$tcl_platform(os)" {
1395windows,* {set M1B Control; set M1T Ctrl}
1396unix,Darwin {set M1B M1; set M1T Cmd}
1397default {set M1B M1; set M1T M1}
e210e674
SP
1398}
1399
cb07fc2a
SP
1400# -- Menu Bar
1401menu .mbar -tearoff 0
1402.mbar add cascade -label Project -menu .mbar.project
1403.mbar add cascade -label Commit -menu .mbar.commit
1404.mbar add cascade -label Fetch -menu .mbar.fetch
1405.mbar add cascade -label Pull -menu .mbar.pull
8c0ce436 1406.mbar add cascade -label Push -menu .mbar.push
cb07fc2a
SP
1407. configure -menu .mbar
1408
1409# -- Project Menu
1410menu .mbar.project
6f6eed28 1411.mbar.project add command -label Visualize \
cb07fc2a
SP
1412 -command do_gitk \
1413 -font $mainfont
1414.mbar.project add command -label Quit \
1415 -command do_quit \
e210e674 1416 -accelerator $M1T-Q \
cb07fc2a
SP
1417 -font $mainfont
1418
1419# -- Commit Menu
1420menu .mbar.commit
1421.mbar.commit add command -label Rescan \
1422 -command do_rescan \
e210e674 1423 -accelerator F5 \
cb07fc2a 1424 -font $mainfont
e210e674
SP
1425lappend disable_on_lock \
1426 [list .mbar.commit entryconf [.mbar.commit index last] -state]
e57ca85e
SP
1427.mbar.commit add command -label {Amend Last Commit} \
1428 -command do_amend_last \
1429 -font $mainfont
1430lappend disable_on_lock \
1431 [list .mbar.commit entryconf [.mbar.commit index last] -state]
131f503b
SP
1432.mbar.commit add command -label {Check-in All Files} \
1433 -command do_checkin_all \
e210e674 1434 -accelerator $M1T-U \
131f503b 1435 -font $mainfont
e210e674
SP
1436lappend disable_on_lock \
1437 [list .mbar.commit entryconf [.mbar.commit index last] -state]
131f503b
SP
1438.mbar.commit add command -label {Sign Off} \
1439 -command do_signoff \
e210e674 1440 -accelerator $M1T-S \
131f503b
SP
1441 -font $mainfont
1442.mbar.commit add command -label Commit \
1443 -command do_commit \
e210e674 1444 -accelerator $M1T-Return \
131f503b 1445 -font $mainfont
e210e674
SP
1446lappend disable_on_lock \
1447 [list .mbar.commit entryconf [.mbar.commit index last] -state]
cb07fc2a
SP
1448
1449# -- Fetch Menu
1450menu .mbar.fetch
1451
1452# -- Pull Menu
1453menu .mbar.pull
1454
8c0ce436
SP
1455# -- Push Menu
1456menu .mbar.push
1457
cb07fc2a
SP
1458# -- Main Window Layout
1459panedwindow .vpane -orient vertical
1460panedwindow .vpane.files -orient horizontal
6f6eed28 1461.vpane add .vpane.files -sticky nsew -height 100 -width 400
cb07fc2a
SP
1462pack .vpane -anchor n -side top -fill both -expand 1
1463
1464# -- Index File List
cb07fc2a
SP
1465frame .vpane.files.index -height 100 -width 400
1466label .vpane.files.index.title -text {Modified Files} \
1467 -background green \
1468 -font $mainfont
1469text $ui_index -background white -borderwidth 0 \
1470 -width 40 -height 10 \
1471 -font $mainfont \
1472 -yscrollcommand {.vpane.files.index.sb set} \
1473 -cursor $maincursor \
1474 -state disabled
1475scrollbar .vpane.files.index.sb -command [list $ui_index yview]
1476pack .vpane.files.index.title -side top -fill x
1477pack .vpane.files.index.sb -side right -fill y
1478pack $ui_index -side left -fill both -expand 1
1479.vpane.files add .vpane.files.index -sticky nsew
1480
1481# -- Other (Add) File List
cb07fc2a
SP
1482frame .vpane.files.other -height 100 -width 100
1483label .vpane.files.other.title -text {Untracked Files} \
1484 -background red \
1485 -font $mainfont
1486text $ui_other -background white -borderwidth 0 \
1487 -width 40 -height 10 \
1488 -font $mainfont \
1489 -yscrollcommand {.vpane.files.other.sb set} \
1490 -cursor $maincursor \
1491 -state disabled
1492scrollbar .vpane.files.other.sb -command [list $ui_other yview]
1493pack .vpane.files.other.title -side top -fill x
1494pack .vpane.files.other.sb -side right -fill y
1495pack $ui_other -side left -fill both -expand 1
1496.vpane.files add .vpane.files.other -sticky nsew
1497
131f503b
SP
1498$ui_index tag conf in_diff -font [concat $mainfont bold]
1499$ui_other tag conf in_diff -font [concat $mainfont bold]
1500
cb07fc2a
SP
1501# -- Diff Header
1502set ui_fname_value {}
1503set ui_fstatus_value {}
6f6eed28 1504frame .vpane.diff -height 200 -width 400
cb07fc2a
SP
1505frame .vpane.diff.header
1506label .vpane.diff.header.l1 -text {File:} -font $mainfont
1507label .vpane.diff.header.l2 -textvariable ui_fname_value \
1508 -anchor w \
1509 -justify left \
1510 -font $mainfont
1511label .vpane.diff.header.l3 -text {Status:} -font $mainfont
1512label .vpane.diff.header.l4 -textvariable ui_fstatus_value \
131f503b 1513 -width $max_status_desc \
cb07fc2a
SP
1514 -anchor w \
1515 -justify left \
1516 -font $mainfont
1517pack .vpane.diff.header.l1 -side left
1518pack .vpane.diff.header.l2 -side left -fill x
1519pack .vpane.diff.header.l4 -side right
1520pack .vpane.diff.header.l3 -side right
1521
1522# -- Diff Body
1523frame .vpane.diff.body
1524set ui_diff .vpane.diff.body.t
1525text $ui_diff -background white -borderwidth 0 \
6f6eed28 1526 -width 80 -height 15 -wrap none \
cb07fc2a
SP
1527 -font $difffont \
1528 -xscrollcommand {.vpane.diff.body.sbx set} \
1529 -yscrollcommand {.vpane.diff.body.sby set} \
1530 -cursor $maincursor \
1531 -state disabled
1532scrollbar .vpane.diff.body.sbx -orient horizontal \
1533 -command [list $ui_diff xview]
1534scrollbar .vpane.diff.body.sby -orient vertical \
1535 -command [list $ui_diff yview]
1536pack .vpane.diff.body.sbx -side bottom -fill x
1537pack .vpane.diff.body.sby -side right -fill y
1538pack $ui_diff -side left -fill both -expand 1
1539pack .vpane.diff.header -side top -fill x
1540pack .vpane.diff.body -side bottom -fill both -expand 1
1541.vpane add .vpane.diff -stick nsew
1542
1543$ui_diff tag conf dm -foreground red
1544$ui_diff tag conf dp -foreground blue
1545$ui_diff tag conf da -font [concat $difffont bold]
1546$ui_diff tag conf di -foreground "#00a000"
1547$ui_diff tag conf dni -foreground "#a000a0"
1548$ui_diff tag conf bold -font [concat $difffont bold]
1549
1550# -- Commit Area
e57ca85e 1551frame .vpane.commarea -height 170
cb07fc2a
SP
1552.vpane add .vpane.commarea -stick nsew
1553
1554# -- Commit Area Buttons
1555frame .vpane.commarea.buttons
1556label .vpane.commarea.buttons.l -text {} \
1557 -anchor w \
1558 -justify left \
1559 -font $mainfont
1560pack .vpane.commarea.buttons.l -side top -fill x
131f503b
SP
1561pack .vpane.commarea.buttons -side left -fill y
1562
cb07fc2a
SP
1563button .vpane.commarea.buttons.rescan -text {Rescan} \
1564 -command do_rescan \
1565 -font $mainfont
1566pack .vpane.commarea.buttons.rescan -side top -fill x
e210e674 1567lappend disable_on_lock {.vpane.commarea.buttons.rescan conf -state}
131f503b 1568
e57ca85e
SP
1569button .vpane.commarea.buttons.amend -text {Amend Last} \
1570 -command do_amend_last \
1571 -font $mainfont
1572pack .vpane.commarea.buttons.amend -side top -fill x
1573lappend disable_on_lock {.vpane.commarea.buttons.amend conf -state}
1574
cb07fc2a
SP
1575button .vpane.commarea.buttons.ciall -text {Check-in All} \
1576 -command do_checkin_all \
1577 -font $mainfont
1578pack .vpane.commarea.buttons.ciall -side top -fill x
e210e674 1579lappend disable_on_lock {.vpane.commarea.buttons.ciall conf -state}
131f503b
SP
1580
1581button .vpane.commarea.buttons.signoff -text {Sign Off} \
1582 -command do_signoff \
1583 -font $mainfont
1584pack .vpane.commarea.buttons.signoff -side top -fill x
1585
cb07fc2a
SP
1586button .vpane.commarea.buttons.commit -text {Commit} \
1587 -command do_commit \
1588 -font $mainfont
1589pack .vpane.commarea.buttons.commit -side top -fill x
e210e674 1590lappend disable_on_lock {.vpane.commarea.buttons.commit conf -state}
cb07fc2a
SP
1591
1592# -- Commit Message Buffer
1593frame .vpane.commarea.buffer
1594set ui_comm .vpane.commarea.buffer.t
bd1e2b40
SP
1595set ui_coml .vpane.commarea.buffer.l
1596label $ui_coml -text {Commit Message:} \
cb07fc2a
SP
1597 -anchor w \
1598 -justify left \
1599 -font $mainfont
bd1e2b40
SP
1600trace add variable commit_type write {uplevel #0 {
1601 switch -glob $commit_type \
1602 initial {$ui_coml conf -text {Initial Commit Message:}} \
1603 amend {$ui_coml conf -text {Amended Commit Message:}} \
1604 merge {$ui_coml conf -text {Merge Commit Message:}} \
1605 * {$ui_coml conf -text {Commit Message:}}
1606}}
cb07fc2a
SP
1607text $ui_comm -background white -borderwidth 1 \
1608 -relief sunken \
1609 -width 75 -height 10 -wrap none \
1610 -font $difffont \
1611 -yscrollcommand {.vpane.commarea.buffer.sby set} \
1612 -cursor $maincursor
1613scrollbar .vpane.commarea.buffer.sby -command [list $ui_comm yview]
bd1e2b40 1614pack $ui_coml -side top -fill x
cb07fc2a
SP
1615pack .vpane.commarea.buffer.sby -side right -fill y
1616pack $ui_comm -side left -fill y
1617pack .vpane.commarea.buffer -side left -fill y
1618
1619# -- Status Bar
1620set ui_status_value {Initializing...}
1621label .status -textvariable ui_status_value \
1622 -anchor w \
1623 -justify left \
1624 -borderwidth 1 \
1625 -relief sunken \
1626 -font $mainfont
1627pack .status -anchor w -side bottom -fill x
1628
1629# -- Key Bindings
ec6b424a 1630bind $ui_comm <$M1B-Key-Return> {do_commit;break}
07123f40
SP
1631bind . <Destroy> do_quit
1632bind all <Key-F5> do_rescan
1633bind all <$M1B-Key-r> do_rescan
1634bind all <$M1B-Key-R> do_rescan
1635bind . <$M1B-Key-s> do_signoff
1636bind . <$M1B-Key-S> do_signoff
1637bind . <$M1B-Key-u> do_checkin_all
1638bind . <$M1B-Key-U> do_checkin_all
1639bind . <$M1B-Key-Return> do_commit
1640bind all <$M1B-Key-q> do_quit
1641bind all <$M1B-Key-Q> do_quit
1642bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
1643bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
cb07fc2a
SP
1644foreach i [list $ui_index $ui_other] {
1645 bind $i <Button-1> {click %W %x %y 1 %X %Y; break}
1646 bind $i <Button-3> {click %W %x %y 3 %X %Y; break}
1647 bind $i <ButtonRelease-1> {unclick %W %x %y; break}
1648}
e210e674 1649unset i M1B M1T
cb07fc2a
SP
1650
1651######################################################################
1652##
1653## main
1654
b8ce6f0e
SP
1655set appname [lindex [file split $argv0] end]
1656set gitdir {}
1657
1658if {[catch {set cdup [exec git rev-parse --show-cdup]} err]} {
cb07fc2a
SP
1659 show_msg {} . "Cannot find the git directory: $err"
1660 exit 1
1661}
bd1e2b40
SP
1662if {$cdup != ""} {
1663 cd $cdup
1664}
1665unset cdup
cb07fc2a 1666
b8ce6f0e
SP
1667if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} {
1668 show_msg {} . "Cannot find the git directory: $err"
1669 exit 1
1670}
1671
ec6b424a
SP
1672if {$appname == {git-citool}} {
1673 set single_commit 1
1674}
1675
1676wm title . "$appname ([file normalize [file dirname $gitdir]])"
cb07fc2a 1677focus -force $ui_comm
0d4f3eb5 1678load_repo_config
8c0ce436
SP
1679load_all_remotes
1680populate_remote_menu .mbar.fetch From fetch_from
1681populate_remote_menu .mbar.push To push_to
d33ba5fa 1682populate_pull_menu .mbar.pull
cb07fc2a 1683update_status