1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "$@"
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.
11 ######################################################################
12 ##
13 ## status
15 set status_active 0
16 set diff_active 0
17 set checkin_active 0
18 set update_index_fd {}
20 proc is_busy {} {
21 global status_active diff_active checkin_active update_index_fd
23 if {$status_active > 0
24 || $diff_active
25 || $checkin_active
26 || $update_index_fd != {}} {
27 return 1
28 }
29 return 0
30 }
32 proc 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
37 if {[is_busy]} return
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 }
46 if {[catch {set HEAD [exec git rev-parse --verify HEAD]}]} {
47 set commit_type initial
48 } else {
49 set commit_type normal
50 }
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 }
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 }
68 proc 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
73 read $fd
74 if {![eof $fd]} return
75 close $fd
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 }
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]
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 }
98 proc load_message {file} {
99 global gitdir ui_comm
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 }
115 proc read_diff_index {fd} {
116 global buf_rdi
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 }
129 proc read_diff_files {fd} {
130 global buf_rdf
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 }
143 proc read_ls_others {fd} {
144 global buf_rlo
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 }
155 proc status_eof {fd buf} {
156 global status_active $buf
157 global ui_fname_value ui_status_value
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 }
171 ######################################################################
172 ##
173 ## diff
175 proc clear_diff {} {
176 global ui_diff ui_fname_value ui_fstatus_value
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 }
185 proc 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
189 if {[is_busy]} return
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..."
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 }
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 }
232 fconfigure $fd -blocking 0 -translation binary
233 fileevent $fd readable [list read_diff $fd]
234 }
236 proc read_diff {fd} {
237 global ui_diff ui_status_value diff_3way diff_active
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 }
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 }
275 if {[eof $fd]} {
276 close $fd
277 set diff_active 0
278 set ui_status_value {Ready.}
279 }
280 }
282 ######################################################################
283 ##
284 ## ui helpers
286 proc mapcol {state path} {
287 global all_cols
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 }
296 proc mapicon {state path} {
297 global all_icons
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 }
306 proc mapdesc {state path} {
307 global all_descs
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 }
316 proc 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 }
337 proc merge_state {path state} {
338 global file_states
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 }
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 }
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 }
361 set file_states($path) [lreplace $s 0 0 $state]
362 return $o
363 }
365 proc display_file {path state} {
366 global ui_index ui_other file_states
368 set old_m [merge_state $path $state]
369 set s $file_states($path)
370 set m [lindex $s 0]
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 }
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 }
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 }
412 proc with_update_index {body} {
413 global update_index_fd
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 }
428 proc update_index {path} {
429 global update_index_fd
431 if {$update_index_fd == {}} {
432 error {not in with_update_index}
433 } else {
434 puts -nonewline $update_index_fd "$path\0"
435 }
436 }
438 proc toggle_mode {path} {
439 global file_states
441 set s $file_states($path)
442 set m [lindex $s 0]
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 }
453 with_update_index {update_index $path}
454 display_file $path $new
455 }
457 ######################################################################
458 ##
459 ## icons
461 set filemask {
462 #define mask_width 14
463 #define mask_height 15
464 static 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 }
470 image create bitmap file_plain -background white -foreground black -data {
471 #define plain_width 14
472 #define plain_height 15
473 static 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
479 image create bitmap file_mod -background white -foreground blue -data {
480 #define mod_width 14
481 #define mod_height 15
482 static 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
488 image create bitmap file_fulltick -background white -foreground "#007000" -data {
489 #define file_fulltick_width 14
490 #define file_fulltick_height 15
491 static 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
497 image create bitmap file_parttick -background white -foreground "#005050" -data {
498 #define parttick_width 14
499 #define parttick_height 15
500 static 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
506 image create bitmap file_question -background white -foreground black -data {
507 #define file_question_width 14
508 #define file_question_height 15
509 static 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
515 image create bitmap file_removed -background white -foreground red -data {
516 #define file_removed_width 14
517 #define file_removed_height 15
518 static 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
524 image create bitmap file_merge -background white -foreground blue -data {
525 #define file_merge_width 14
526 #define file_merge_height 15
527 static 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
533 set max_status_desc 0
534 foreach i {
535 {__ i plain "Unmodified"}
536 {_M i mod "Modified"}
537 {M_ i fulltick "Checked in"}
538 {MM i parttick "Partially checked in"}
540 {_O o plain "Untracked"}
541 {A_ o fulltick "Added"}
542 {AM o parttick "Partially added"}
544 {_D i question "Missing"}
545 {D_ i removed "Removed"}
546 {DD i removed "Removed"}
547 {DO i removed "Removed (still exists)"}
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 }
559 unset filemask i
561 ######################################################################
562 ##
563 ## util
565 proc error_popup {msg} {
566 set w .error
567 toplevel $w
568 wm transient $w .
569 show_msg $w $w $msg
570 }
572 proc 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 }
582 ######################################################################
583 ##
584 ## ui commands
586 proc do_gitk {} {
587 global tcl_platform
589 if {$tcl_platform(platform) == "windows"} {
590 exec sh -c gitk &
591 } else {
592 exec gitk &
593 }
594 }
596 proc do_quit {} {
597 global gitdir ui_comm
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 }
611 destroy .
612 }
614 proc do_rescan {} {
615 update_status
616 }
618 proc do_checkin_all {} {
619 global checkin_active ui_status_value
621 if {[is_busy]} return
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 }
643 proc do_signoff {} {
644 global ui_comm
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 }
659 # shift == 1: left click
660 # 3: right click
661 proc click {w x y shift wx wy} {
662 global ui_index ui_other
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
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 }
678 proc 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
685 if {$col == 0 && ![is_busy]} {
686 toggle_mode $path
687 }
688 }
690 ######################################################################
691 ##
692 ## ui init
694 set mainfont {Helvetica 10}
695 set difffont {Courier 10}
696 set maincursor [. cget -cursor]
698 # -- Menu Bar
699 menu .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
706 # -- Project Menu
707 menu .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
715 # -- Commit Menu
716 menu .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
730 # -- Fetch Menu
731 menu .mbar.fetch
733 # -- Pull Menu
734 menu .mbar.pull
736 # -- Main Window Layout
737 panedwindow .vpane -orient vertical
738 panedwindow .vpane.files -orient horizontal
739 .vpane add .vpane.files -sticky nsew
740 pack .vpane -anchor n -side top -fill both -expand 1
742 # -- Index File List
743 set ui_index .vpane.files.index.list
744 frame .vpane.files.index -height 100 -width 400
745 label .vpane.files.index.title -text {Modified Files} \
746 -background green \
747 -font $mainfont
748 text $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
754 scrollbar .vpane.files.index.sb -command [list $ui_index yview]
755 pack .vpane.files.index.title -side top -fill x
756 pack .vpane.files.index.sb -side right -fill y
757 pack $ui_index -side left -fill both -expand 1
758 .vpane.files add .vpane.files.index -sticky nsew
760 # -- Other (Add) File List
761 set ui_other .vpane.files.other.list
762 frame .vpane.files.other -height 100 -width 100
763 label .vpane.files.other.title -text {Untracked Files} \
764 -background red \
765 -font $mainfont
766 text $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
772 scrollbar .vpane.files.other.sb -command [list $ui_other yview]
773 pack .vpane.files.other.title -side top -fill x
774 pack .vpane.files.other.sb -side right -fill y
775 pack $ui_other -side left -fill both -expand 1
776 .vpane.files add .vpane.files.other -sticky nsew
778 $ui_index tag conf in_diff -font [concat $mainfont bold]
779 $ui_other tag conf in_diff -font [concat $mainfont bold]
781 # -- Diff Header
782 set ui_fname_value {}
783 set ui_fstatus_value {}
784 frame .vpane.diff -height 50 -width 400
785 frame .vpane.diff.header
786 label .vpane.diff.header.l1 -text {File:} -font $mainfont
787 label .vpane.diff.header.l2 -textvariable ui_fname_value \
788 -anchor w \
789 -justify left \
790 -font $mainfont
791 label .vpane.diff.header.l3 -text {Status:} -font $mainfont
792 label .vpane.diff.header.l4 -textvariable ui_fstatus_value \
793 -width $max_status_desc \
794 -anchor w \
795 -justify left \
796 -font $mainfont
797 pack .vpane.diff.header.l1 -side left
798 pack .vpane.diff.header.l2 -side left -fill x
799 pack .vpane.diff.header.l4 -side right
800 pack .vpane.diff.header.l3 -side right
802 # -- Diff Body
803 frame .vpane.diff.body
804 set ui_diff .vpane.diff.body.t
805 text $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
812 scrollbar .vpane.diff.body.sbx -orient horizontal \
813 -command [list $ui_diff xview]
814 scrollbar .vpane.diff.body.sby -orient vertical \
815 -command [list $ui_diff yview]
816 pack .vpane.diff.body.sbx -side bottom -fill x
817 pack .vpane.diff.body.sby -side right -fill y
818 pack $ui_diff -side left -fill both -expand 1
819 pack .vpane.diff.header -side top -fill x
820 pack .vpane.diff.body -side bottom -fill both -expand 1
821 .vpane add .vpane.diff -stick nsew
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]
830 # -- Commit Area
831 frame .vpane.commarea -height 50
832 .vpane add .vpane.commarea -stick nsew
834 # -- Commit Area Buttons
835 frame .vpane.commarea.buttons
836 label .vpane.commarea.buttons.l -text {} \
837 -anchor w \
838 -justify left \
839 -font $mainfont
840 pack .vpane.commarea.buttons.l -side top -fill x
841 pack .vpane.commarea.buttons -side left -fill y
843 button .vpane.commarea.buttons.rescan -text {Rescan} \
844 -command do_rescan \
845 -font $mainfont
846 pack .vpane.commarea.buttons.rescan -side top -fill x
848 button .vpane.commarea.buttons.ciall -text {Check-in All} \
849 -command do_checkin_all \
850 -font $mainfont
851 pack .vpane.commarea.buttons.ciall -side top -fill x
853 button .vpane.commarea.buttons.signoff -text {Sign Off} \
854 -command do_signoff \
855 -font $mainfont
856 pack .vpane.commarea.buttons.signoff -side top -fill x
858 button .vpane.commarea.buttons.commit -text {Commit} \
859 -command do_commit \
860 -font $mainfont
861 pack .vpane.commarea.buttons.commit -side top -fill x
863 # -- Commit Message Buffer
864 frame .vpane.commarea.buffer
865 set ui_comm .vpane.commarea.buffer.t
866 label .vpane.commarea.buffer.l -text {Commit Message:} \
867 -anchor w \
868 -justify left \
869 -font $mainfont
870 text $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
876 scrollbar .vpane.commarea.buffer.sby -command [list $ui_comm yview]
877 pack .vpane.commarea.buffer.l -side top -fill x
878 pack .vpane.commarea.buffer.sby -side right -fill y
879 pack $ui_comm -side left -fill y
880 pack .vpane.commarea.buffer -side left -fill y
882 # -- Status Bar
883 set ui_status_value {Initializing...}
884 label .status -textvariable ui_status_value \
885 -anchor w \
886 -justify left \
887 -borderwidth 1 \
888 -relief sunken \
889 -font $mainfont
890 pack .status -anchor w -side bottom -fill x
892 # -- Key Bindings
893 bind . <Destroy> do_quit
894 bind . <Key-F5> do_rescan
895 bind . <M1-Key-r> do_rescan
896 bind . <M1-Key-R> do_rescan
897 bind . <M1-Key-s> do_signoff
898 bind . <M1-Key-S> do_signoff
899 bind . <M1-Key-u> do_checkin_all
900 bind . <M1-Key-U> do_checkin_all
901 bind . <M1-Key-Return> do_commit
902 bind . <M1-Key-q> do_quit
903 bind . <M1-Key-Q> do_quit
904 foreach 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 }
909 unset i
911 ######################################################################
912 ##
913 ## main
915 if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} {
916 show_msg {} . "Cannot find the git directory: $err"
917 exit 1
918 }
920 wm title . "git-ui ([file normalize [file dirname $gitdir]])"
921 focus -force $ui_comm
922 update_status