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
10
11######################################################################
12##
13## status
14
15set status_active 0
16set diff_active 0
17set checkin_active 0
18set update_index_fd {}
19
20proc is_busy {} {
21 global status_active diff_active checkin_active update_index_fd
22
23 if {$status_active > 0
24 || $diff_active
25 || $checkin_active
26 || $update_index_fd != {}} {
27 return 1
28 }
29 return 0
30}
31
32proc update_status {} {
33 global gitdir HEAD commit_type
34 global ui_index ui_other ui_status_value ui_comm
35 global status_active file_states
36
37 if {[is_busy]} return
38
39 array unset file_states
40 foreach w [list $ui_index $ui_other] {
41 $w conf -state normal
42 $w delete 0.0 end
43 $w conf -state disabled
44 }
45
46 if {[catch {set HEAD [exec git rev-parse --verify HEAD]}]} {
47 set commit_type initial
48 } else {
49 set commit_type normal
50 }
51
52 if {![$ui_comm edit modified]
53 || [string trim [$ui_comm get 0.0 end]] == {}} {
54 if {[load_message GITGUI_MSG]} {
55 } elseif {[load_message MERGE_MSG]} {
56 } elseif {[load_message SQUASH_MSG]} {
57 }
58 $ui_comm edit modified false
59 }
60
61 set status_active 1
62 set ui_status_value {Refreshing file status...}
63 set fd_rf [open "| git update-index -q --unmerged --refresh" r]
64 fconfigure $fd_rf -blocking 0 -translation binary
65 fileevent $fd_rf readable [list read_refresh $fd_rf]
66}
67
68proc read_refresh {fd} {
69 global gitdir HEAD commit_type
70 global ui_index ui_other ui_status_value ui_comm
71 global status_active file_states
72
73 read $fd
74 if {![eof $fd]} return
75 close $fd
76
77 set ls_others [list | git ls-files --others -z \
78 --exclude-per-directory=.gitignore]
79 set info_exclude [file join $gitdir info exclude]
80 if {[file readable $info_exclude]} {
81 lappend ls_others "--exclude-from=$info_exclude"
82 }
83
84 set status_active 3
85 set ui_status_value {Scanning for modified files ...}
86 set fd_di [open "| git diff-index --cached -z $HEAD" r]
87 set fd_df [open "| git diff-files -z" r]
88 set fd_lo [open $ls_others r]
89
90 fconfigure $fd_di -blocking 0 -translation binary
91 fconfigure $fd_df -blocking 0 -translation binary
92 fconfigure $fd_lo -blocking 0 -translation binary
93 fileevent $fd_di readable [list read_diff_index $fd_di]
94 fileevent $fd_df readable [list read_diff_files $fd_df]
95 fileevent $fd_lo readable [list read_ls_others $fd_lo]
96}
97
98proc load_message {file} {
99 global gitdir ui_comm
100
101 set f [file join $gitdir $file]
102 if {[file exists $f]} {
103 if {[catch {set fd [open $f r]}]} {
104 return 0
105 }
106 set content [read $fd]
107 close $fd
108 $ui_comm delete 0.0 end
109 $ui_comm insert end $content
110 return 1
111 }
112 return 0
113}
114
115proc read_diff_index {fd} {
116 global buf_rdi
117
118 append buf_rdi [read $fd]
119 set pck [split $buf_rdi "\0"]
120 set buf_rdi [lindex $pck end]
121 foreach {m p} [lrange $pck 0 end-1] {
122 if {$m != {} && $p != {}} {
123 display_file $p [string index $m end]_
124 }
125 }
126 status_eof $fd buf_rdi
127}
128
129proc read_diff_files {fd} {
130 global buf_rdf
131
132 append buf_rdf [read $fd]
133 set pck [split $buf_rdf "\0"]
134 set buf_rdf [lindex $pck end]
135 foreach {m p} [lrange $pck 0 end-1] {
136 if {$m != {} && $p != {}} {
137 display_file $p _[string index $m end]
138 }
139 }
140 status_eof $fd buf_rdf
141}
142
143proc read_ls_others {fd} {
144 global buf_rlo
145
146 append buf_rlo [read $fd]
147 set pck [split $buf_rlo "\0"]
148 set buf_rlo [lindex $pck end]
149 foreach p [lrange $pck 0 end-1] {
150 display_file $p _O
151 }
152 status_eof $fd buf_rlo
153}
154
155proc status_eof {fd buf} {
156 global status_active $buf
157 global ui_fname_value ui_status_value
158
159 if {[eof $fd]} {
160 set $buf {}
161 close $fd
162 if {[incr status_active -1] == 0} {
163 set ui_status_value {Ready.}
164 if {$ui_fname_value != {}} {
165 show_diff $ui_fname_value
166 }
167 }
168 }
169}
170
171######################################################################
172##
173## diff
174
175proc clear_diff {} {
176 global ui_diff ui_fname_value ui_fstatus_value
177
178 $ui_diff conf -state normal
179 $ui_diff delete 0.0 end
180 $ui_diff conf -state disabled
181 set ui_fname_value {}
182 set ui_fstatus_value {}
183}
184
185proc show_diff {path} {
186 global file_states HEAD diff_3way diff_active
187 global ui_diff ui_fname_value ui_fstatus_value ui_status_value
188
189 if {[is_busy]} return
190
191 clear_diff
192 set s $file_states($path)
193 set m [lindex $s 0]
194 set diff_3way 0
195 set diff_active 1
196 set ui_fname_value $path
197 set ui_fstatus_value [mapdesc $m $path]
198 set ui_status_value "Loading diff of $path..."
199
200 set cmd [list | git diff-index -p $HEAD -- $path]
201 switch $m {
202 AM {
203 }
204 MM {
205 set cmd [list | git diff-index -p -c $HEAD $path]
206 }
207 _O {
208 if {[catch {
209 set fd [open $path r]
210 set content [read $fd]
211 close $fd
212 } err ]} {
213 set diff_active 0
214 set ui_status_value "Unable to display $path"
215 error_popup "Error loading file:\n$err"
216 return
217 }
218 $ui_diff conf -state normal
219 $ui_diff insert end $content
220 $ui_diff conf -state disabled
221 return
222 }
223 }
224
225 if {[catch {set fd [open $cmd r]} err]} {
226 set diff_active 0
227 set ui_status_value "Unable to display $path"
228 error_popup "Error loading diff:\n$err"
229 return
230 }
231
232 fconfigure $fd -blocking 0 -translation binary
233 fileevent $fd readable [list read_diff $fd]
234}
235
236proc read_diff {fd} {
237 global ui_diff ui_status_value diff_3way diff_active
238
239 while {[gets $fd line] >= 0} {
240 if {[string match index* $line]} {
241 if {[string first , $line] >= 0} {
242 set diff_3way 1
243 }
244 }
245
246 $ui_diff conf -state normal
247 if {!$diff_3way} {
248 set x [string index $line 0]
249 switch -- $x {
250 "@" {set tags da}
251 "+" {set tags dp}
252 "-" {set tags dm}
253 default {set tags {}}
254 }
255 } else {
256 set x [string range $line 0 1]
257 switch -- $x {
258 default {set tags {}}
259 "@@" {set tags da}
260 "++" {set tags dp; set x " +"}
261 " +" {set tags {di bold}; set x "++"}
262 "+ " {set tags dni; set x "-+"}
263 "--" {set tags dm; set x " -"}
264 " -" {set tags {dm bold}; set x "--"}
265 "- " {set tags di; set x "+-"}
266 default {set tags {}}
267 }
268 set line [string replace $line 0 1 $x]
269 }
270 $ui_diff insert end $line $tags
271 $ui_diff insert end "\n"
272 $ui_diff conf -state disabled
273 }
274
275 if {[eof $fd]} {
276 close $fd
277 set diff_active 0
278 set ui_status_value {Ready.}
279 }
280}
281
282######################################################################
283##
284## ui helpers
285
286proc mapcol {state path} {
287 global all_cols
288
289 if {[catch {set r $all_cols($state)}]} {
290 puts "error: no column for state={$state} $path"
291 return o
292 }
293 return $r
294}
295
296proc mapicon {state path} {
297 global all_icons
298
299 if {[catch {set r $all_icons($state)}]} {
300 puts "error: no icon for state={$state} $path"
301 return file_plain
302 }
303 return $r
304}
305
306proc mapdesc {state path} {
307 global all_descs
308
309 if {[catch {set r $all_descs($state)}]} {
310 puts "error: no desc for state={$state} $path"
311 return $state
312 }
313 return $r
314}
315
316proc bsearch {w path} {
317 set hi [expr [lindex [split [$w index end] .] 0] - 2]
318 if {$hi == 0} {
319 return -1
320 }
321 set lo 0
322 while {$lo < $hi} {
323 set mi [expr [expr $lo + $hi] / 2]
324 set ti [expr $mi + 1]
325 set cmp [string compare [$w get $ti.1 $ti.end] $path]
326 if {$cmp < 0} {
327 set lo $ti
328 } elseif {$cmp == 0} {
329 return $mi
330 } else {
331 set hi $mi
332 }
333 }
334 return -[expr $lo + 1]
335}
336
337proc merge_state {path state} {
338 global file_states
339
340 if {[array names file_states -exact $path] == {}} {
341 set o __
342 set s [list $o none none]
343 } else {
344 set s $file_states($path)
345 set o [lindex $s 0]
346 }
347
348 set m [lindex $s 0]
349 if {[string index $state 0] == "_"} {
350 set state [string index $m 0][string index $state 1]
351 } elseif {[string index $state 0] == "*"} {
352 set state _[string index $state 1]
353 }
354
355 if {[string index $state 1] == "_"} {
356 set state [string index $state 0][string index $m 1]
357 } elseif {[string index $state 1] == "*"} {
358 set state [string index $state 0]_
359 }
360
361 set file_states($path) [lreplace $s 0 0 $state]
362 return $o
363}
364
365proc display_file {path state} {
366 global ui_index ui_other file_states
367
368 set old_m [merge_state $path $state]
369 set s $file_states($path)
370 set m [lindex $s 0]
371
372 if {[mapcol $m $path] == "o"} {
373 set ii 1
374 set ai 2
375 set iw $ui_index
376 set aw $ui_other
377 } else {
378 set ii 2
379 set ai 1
380 set iw $ui_other
381 set aw $ui_index
382 }
383
384 set d [lindex $s $ii]
385 if {$d != "none"} {
386 set lno [bsearch $iw $path]
387 if {$lno >= 0} {
388 incr lno
389 $iw conf -state normal
390 $iw delete $lno.0 [expr $lno + 1].0
391 $iw conf -state disabled
392 set s [lreplace $s $ii $ii none]
393 }
394 }
395
396 set d [lindex $s $ai]
397 if {$d == "none"} {
398 set lno [expr abs([bsearch $aw $path] + 1) + 1]
399 $aw conf -state normal
400 set ico [$aw image create $lno.0 \
401 -align center -padx 5 -pady 1 \
402 -image [mapicon $m $path]]
403 $aw insert $lno.1 "$path\n"
404 $aw conf -state disabled
405 set file_states($path) [lreplace $s $ai $ai [list $ico]]
406 } elseif {[mapicon $m $path] != [mapicon $old_m $path]} {
407 set ico [lindex $d 0]
408 $aw image conf $ico -image [mapicon $m $path]
409 }
410}
411
412proc with_update_index {body} {
413 global update_index_fd
414
415 if {$update_index_fd == {}} {
416 set update_index_fd [open \
417 "| git update-index --add --remove -z --stdin" \
418 w]
419 fconfigure $update_index_fd -translation binary
420 uplevel 1 $body
421 close $update_index_fd
422 set update_index_fd {}
423 } else {
424 uplevel 1 $body
425 }
426}
427
428proc update_index {path} {
429 global update_index_fd
430
431 if {$update_index_fd == {}} {
432 error {not in with_update_index}
433 } else {
434 puts -nonewline $update_index_fd "$path\0"
435 }
436}
437
438proc toggle_mode {path} {
439 global file_states
440
441 set s $file_states($path)
442 set m [lindex $s 0]
443
444 switch -- $m {
445 AM -
446 _O {set new A*}
447 _M -
448 MM {set new M*}
449 _D {set new D*}
450 default {return}
451 }
452
453 with_update_index {update_index $path}
454 display_file $path $new
455}
456
457######################################################################
458##
459## icons
460
461set filemask {
462#define mask_width 14
463#define mask_height 15
464static unsigned char mask_bits[] = {
465 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
466 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
467 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
468}
469
470image create bitmap file_plain -background white -foreground black -data {
471#define plain_width 14
472#define plain_height 15
473static unsigned char plain_bits[] = {
474 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
475 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
476 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
477} -maskdata $filemask
478
479image create bitmap file_mod -background white -foreground blue -data {
480#define mod_width 14
481#define mod_height 15
482static unsigned char mod_bits[] = {
483 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
484 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
485 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
486} -maskdata $filemask
487
488image create bitmap file_fulltick -background white -foreground "#007000" -data {
489#define file_fulltick_width 14
490#define file_fulltick_height 15
491static unsigned char file_fulltick_bits[] = {
492 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
493 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
494 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
495} -maskdata $filemask
496
497image create bitmap file_parttick -background white -foreground "#005050" -data {
498#define parttick_width 14
499#define parttick_height 15
500static unsigned char parttick_bits[] = {
501 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
502 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
503 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
504} -maskdata $filemask
505
506image create bitmap file_question -background white -foreground black -data {
507#define file_question_width 14
508#define file_question_height 15
509static unsigned char file_question_bits[] = {
510 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
511 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
512 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
513} -maskdata $filemask
514
515image create bitmap file_removed -background white -foreground red -data {
516#define file_removed_width 14
517#define file_removed_height 15
518static unsigned char file_removed_bits[] = {
519 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
520 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
521 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
522} -maskdata $filemask
523
524image create bitmap file_merge -background white -foreground blue -data {
525#define file_merge_width 14
526#define file_merge_height 15
527static unsigned char file_merge_bits[] = {
528 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
529 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
530 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
531} -maskdata $filemask
532
533set max_status_desc 0
534foreach i {
535 {__ i plain "Unmodified"}
536 {_M i mod "Modified"}
537 {M_ i fulltick "Checked in"}
538 {MM i parttick "Partially checked in"}
539
540 {_O o plain "Untracked"}
541 {A_ o fulltick "Added"}
542 {AM o parttick "Partially added"}
543
544 {_D i question "Missing"}
545 {D_ i removed "Removed"}
546 {DD i removed "Removed"}
547 {DO i removed "Removed (still exists)"}
548
549 {UM i merge "Merge conflicts"}
550 {U_ i merge "Merge conflicts"}
551 } {
552 if {$max_status_desc < [string length [lindex $i 3]]} {
553 set max_status_desc [string length [lindex $i 3]]
554 }
555 set all_cols([lindex $i 0]) [lindex $i 1]
556 set all_icons([lindex $i 0]) file_[lindex $i 2]
557 set all_descs([lindex $i 0]) [lindex $i 3]
558}
559unset filemask i
560
561######################################################################
562##
563## util
564
565proc error_popup {msg} {
566 set w .error
567 toplevel $w
568 wm transient $w .
569 show_msg $w $w $msg
570}
571
572proc show_msg {w top msg} {
573 message $w.m -text $msg -justify center -aspect 400
574 pack $w.m -side top -fill x -padx 20 -pady 20
575 button $w.ok -text OK -command "destroy $top"
576 pack $w.ok -side bottom -fill x
577 bind $top <Visibility> "grab $top; focus $top"
578 bind $top <Key-Return> "destroy $top"
579 tkwait window $top
580}
581
582######################################################################
583##
584## ui commands
585
586proc do_gitk {} {
587 global tcl_platform
588
589 if {$tcl_platform(platform) == "windows"} {
590 exec sh -c gitk &
591 } else {
592 exec gitk &
593 }
594}
595
596proc do_quit {} {
597 global gitdir ui_comm
598
599 set save [file join $gitdir GITGUI_MSG]
600 if {[$ui_comm edit modified]
601 && [string trim [$ui_comm get 0.0 end]] != {}} {
602 catch {
603 set fd [open $save w]
604 puts $fd [string trim [$ui_comm get 0.0 end]]
605 close $fd
606 }
607 } elseif {[file exists $save]} {
608 file delete $save
609 }
610
611 destroy .
612}
613
614proc do_rescan {} {
615 update_status
616}
617
618proc do_checkin_all {} {
619 global checkin_active ui_status_value
620
621 if {[is_busy]} return
622
623 set checkin_active 1
624 set ui_status_value {Checking in all files...}
625 after 1 {
626 with_update_index {
627 foreach path [array names file_states] {
628 set s $file_states($path)
629 set m [lindex $s 0]
630 switch -- $m {
631 AM -
632 MM -
633 _M -
634 _D {toggle_mode $path}
635 }
636 }
637 }
638 set checkin_active 0
639 set ui_status_value {Ready.}
640 }
641}
642
643proc do_signoff {} {
644 global ui_comm
645
646 catch {
647 set me [exec git var GIT_COMMITTER_IDENT]
648 if {[regexp {(.*) [0-9]+ [-+0-9]+$} $me me name]} {
649 set str "Signed-off-by: $name"
650 if {[$ui_comm get {end -1c linestart} {end -1c}] != $str} {
651 $ui_comm insert end "\n"
652 $ui_comm insert end $str
653 $ui_comm see end
654 }
655 }
656 }
657}
658
659# shift == 1: left click
660# 3: right click
661proc click {w x y shift wx wy} {
662 global ui_index ui_other
663
664 set pos [split [$w index @$x,$y] .]
665 set lno [lindex $pos 0]
666 set col [lindex $pos 1]
667 set path [$w get $lno.1 $lno.end]
668 if {$path == {}} return
669
670 if {$col > 0 && $shift == 1} {
671 $ui_index tag remove in_diff 0.0 end
672 $ui_other tag remove in_diff 0.0 end
673 $w tag add in_diff $lno.0 [expr $lno + 1].0
674 show_diff $path
675 }
676}
677
678proc unclick {w x y} {
679 set pos [split [$w index @$x,$y] .]
680 set lno [lindex $pos 0]
681 set col [lindex $pos 1]
682 set path [$w get $lno.1 $lno.end]
683 if {$path == {}} return
684
685 if {$col == 0 && ![is_busy]} {
686 toggle_mode $path
687 }
688}
689
690######################################################################
691##
692## ui init
693
694set mainfont {Helvetica 10}
695set difffont {Courier 10}
696set maincursor [. cget -cursor]
697
698# -- Menu Bar
699menu .mbar -tearoff 0
700.mbar add cascade -label Project -menu .mbar.project
701.mbar add cascade -label Commit -menu .mbar.commit
702.mbar add cascade -label Fetch -menu .mbar.fetch
703.mbar add cascade -label Pull -menu .mbar.pull
704. configure -menu .mbar
705
706# -- Project Menu
707menu .mbar.project
708.mbar.project add command -label Visulize \
709 -command do_gitk \
710 -font $mainfont
711.mbar.project add command -label Quit \
712 -command do_quit \
713 -font $mainfont
714
715# -- Commit Menu
716menu .mbar.commit
717.mbar.commit add command -label Rescan \
718 -command do_rescan \
719 -font $mainfont
720.mbar.commit add command -label {Check-in All Files} \
721 -command do_checkin_all \
722 -font $mainfont
723.mbar.commit add command -label {Sign Off} \
724 -command do_signoff \
725 -font $mainfont
726.mbar.commit add command -label Commit \
727 -command do_commit \
728 -font $mainfont
729
730# -- Fetch Menu
731menu .mbar.fetch
732
733# -- Pull Menu
734menu .mbar.pull
735
736# -- Main Window Layout
737panedwindow .vpane -orient vertical
738panedwindow .vpane.files -orient horizontal
739.vpane add .vpane.files -sticky nsew
740pack .vpane -anchor n -side top -fill both -expand 1
741
742# -- Index File List
743set ui_index .vpane.files.index.list
744frame .vpane.files.index -height 100 -width 400
745label .vpane.files.index.title -text {Modified Files} \
746 -background green \
747 -font $mainfont
748text $ui_index -background white -borderwidth 0 \
749 -width 40 -height 10 \
750 -font $mainfont \
751 -yscrollcommand {.vpane.files.index.sb set} \
752 -cursor $maincursor \
753 -state disabled
754scrollbar .vpane.files.index.sb -command [list $ui_index yview]
755pack .vpane.files.index.title -side top -fill x
756pack .vpane.files.index.sb -side right -fill y
757pack $ui_index -side left -fill both -expand 1
758.vpane.files add .vpane.files.index -sticky nsew
759
760# -- Other (Add) File List
761set ui_other .vpane.files.other.list
762frame .vpane.files.other -height 100 -width 100
763label .vpane.files.other.title -text {Untracked Files} \
764 -background red \
765 -font $mainfont
766text $ui_other -background white -borderwidth 0 \
767 -width 40 -height 10 \
768 -font $mainfont \
769 -yscrollcommand {.vpane.files.other.sb set} \
770 -cursor $maincursor \
771 -state disabled
772scrollbar .vpane.files.other.sb -command [list $ui_other yview]
773pack .vpane.files.other.title -side top -fill x
774pack .vpane.files.other.sb -side right -fill y
775pack $ui_other -side left -fill both -expand 1
776.vpane.files add .vpane.files.other -sticky nsew
777
778$ui_index tag conf in_diff -font [concat $mainfont bold]
779$ui_other tag conf in_diff -font [concat $mainfont bold]
780
781# -- Diff Header
782set ui_fname_value {}
783set ui_fstatus_value {}
784frame .vpane.diff -height 50 -width 400
785frame .vpane.diff.header
786label .vpane.diff.header.l1 -text {File:} -font $mainfont
787label .vpane.diff.header.l2 -textvariable ui_fname_value \
788 -anchor w \
789 -justify left \
790 -font $mainfont
791label .vpane.diff.header.l3 -text {Status:} -font $mainfont
792label .vpane.diff.header.l4 -textvariable ui_fstatus_value \
793 -width $max_status_desc \
794 -anchor w \
795 -justify left \
796 -font $mainfont
797pack .vpane.diff.header.l1 -side left
798pack .vpane.diff.header.l2 -side left -fill x
799pack .vpane.diff.header.l4 -side right
800pack .vpane.diff.header.l3 -side right
801
802# -- Diff Body
803frame .vpane.diff.body
804set ui_diff .vpane.diff.body.t
805text $ui_diff -background white -borderwidth 0 \
806 -width 80 -height 15 \
807 -font $difffont \
808 -xscrollcommand {.vpane.diff.body.sbx set} \
809 -yscrollcommand {.vpane.diff.body.sby set} \
810 -cursor $maincursor \
811 -state disabled
812scrollbar .vpane.diff.body.sbx -orient horizontal \
813 -command [list $ui_diff xview]
814scrollbar .vpane.diff.body.sby -orient vertical \
815 -command [list $ui_diff yview]
816pack .vpane.diff.body.sbx -side bottom -fill x
817pack .vpane.diff.body.sby -side right -fill y
818pack $ui_diff -side left -fill both -expand 1
819pack .vpane.diff.header -side top -fill x
820pack .vpane.diff.body -side bottom -fill both -expand 1
821.vpane add .vpane.diff -stick nsew
822
823$ui_diff tag conf dm -foreground red
824$ui_diff tag conf dp -foreground blue
825$ui_diff tag conf da -font [concat $difffont bold]
826$ui_diff tag conf di -foreground "#00a000"
827$ui_diff tag conf dni -foreground "#a000a0"
828$ui_diff tag conf bold -font [concat $difffont bold]
829
830# -- Commit Area
831frame .vpane.commarea -height 50
832.vpane add .vpane.commarea -stick nsew
833
834# -- Commit Area Buttons
835frame .vpane.commarea.buttons
836label .vpane.commarea.buttons.l -text {} \
837 -anchor w \
838 -justify left \
839 -font $mainfont
840pack .vpane.commarea.buttons.l -side top -fill x
841pack .vpane.commarea.buttons -side left -fill y
842
843button .vpane.commarea.buttons.rescan -text {Rescan} \
844 -command do_rescan \
845 -font $mainfont
846pack .vpane.commarea.buttons.rescan -side top -fill x
847
848button .vpane.commarea.buttons.ciall -text {Check-in All} \
849 -command do_checkin_all \
850 -font $mainfont
851pack .vpane.commarea.buttons.ciall -side top -fill x
852
853button .vpane.commarea.buttons.signoff -text {Sign Off} \
854 -command do_signoff \
855 -font $mainfont
856pack .vpane.commarea.buttons.signoff -side top -fill x
857
858button .vpane.commarea.buttons.commit -text {Commit} \
859 -command do_commit \
860 -font $mainfont
861pack .vpane.commarea.buttons.commit -side top -fill x
862
863# -- Commit Message Buffer
864frame .vpane.commarea.buffer
865set ui_comm .vpane.commarea.buffer.t
866label .vpane.commarea.buffer.l -text {Commit Message:} \
867 -anchor w \
868 -justify left \
869 -font $mainfont
870text $ui_comm -background white -borderwidth 1 \
871 -relief sunken \
872 -width 75 -height 10 -wrap none \
873 -font $difffont \
874 -yscrollcommand {.vpane.commarea.buffer.sby set} \
875 -cursor $maincursor
876scrollbar .vpane.commarea.buffer.sby -command [list $ui_comm yview]
877pack .vpane.commarea.buffer.l -side top -fill x
878pack .vpane.commarea.buffer.sby -side right -fill y
879pack $ui_comm -side left -fill y
880pack .vpane.commarea.buffer -side left -fill y
881
882# -- Status Bar
883set ui_status_value {Initializing...}
884label .status -textvariable ui_status_value \
885 -anchor w \
886 -justify left \
887 -borderwidth 1 \
888 -relief sunken \
889 -font $mainfont
890pack .status -anchor w -side bottom -fill x
891
892# -- Key Bindings
893bind . <Destroy> do_quit
894bind . <Key-F5> do_rescan
895bind . <M1-Key-r> do_rescan
896bind . <M1-Key-R> do_rescan
897bind . <M1-Key-s> do_signoff
898bind . <M1-Key-S> do_signoff
899bind . <M1-Key-u> do_checkin_all
900bind . <M1-Key-U> do_checkin_all
901bind . <M1-Key-Return> do_commit
902bind . <M1-Key-q> do_quit
903bind . <M1-Key-Q> do_quit
904foreach i [list $ui_index $ui_other] {
905 bind $i <Button-1> {click %W %x %y 1 %X %Y; break}
906 bind $i <Button-3> {click %W %x %y 3 %X %Y; break}
907 bind $i <ButtonRelease-1> {unclick %W %x %y; break}
908}
909unset i
910
911######################################################################
912##
913## main
914
915if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} {
916 show_msg {} . "Cannot find the git directory: $err"
917 exit 1
918}
919
920wm title . "git-ui ([file normalize [file dirname $gitdir]])"
921focus -force $ui_comm
922update_status