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