Code

3ee32e105c2f5be814042ff1aea910fea06a76b4
[git.git] / git-gui
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
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
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
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
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         }
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 {}
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 auto
233         fileevent $fd readable [list read_diff $fd]
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 {diff --git *} $line]} continue
241                 if {[string match {diff --combined *} $line]} continue
242                 if {[string match {--- *} $line]} continue
243                 if {[string match {+++ *} $line]} continue
244                 if {[string match index* $line]} {
245                         if {[string first , $line] >= 0} {
246                                 set diff_3way 1
247                         }
248                 }
250                 $ui_diff conf -state normal
251                 if {!$diff_3way} {
252                         set x [string index $line 0]
253                         switch -- $x {
254                         "@" {set tags da}
255                         "+" {set tags dp}
256                         "-" {set tags dm}
257                         default {set tags {}}
258                         }
259                 } else {
260                         set x [string range $line 0 1]
261                         switch -- $x {
262                         default {set tags {}}
263                         "@@" {set tags da}
264                         "++" {set tags dp; set x " +"}
265                         " +" {set tags {di bold}; set x "++"}
266                         "+ " {set tags dni; set x "-+"}
267                         "--" {set tags dm; set x " -"}
268                         " -" {set tags {dm bold}; set x "--"}
269                         "- " {set tags di; set x "+-"}
270                         default {set tags {}}
271                         }
272                         set line [string replace $line 0 1 $x]
273                 }
274                 $ui_diff insert end $line $tags
275                 $ui_diff insert end "\n"
276                 $ui_diff conf -state disabled
277         }
279         if {[eof $fd]} {
280                 close $fd
281                 set diff_active 0
282                 set ui_status_value {Ready.}
283         }
286 ######################################################################
287 ##
288 ## ui helpers
290 proc mapcol {state path} {
291         global all_cols
293         if {[catch {set r $all_cols($state)}]} {
294                 puts "error: no column for state={$state} $path"
295                 return o
296         }
297         return $r
300 proc mapicon {state path} {
301         global all_icons
303         if {[catch {set r $all_icons($state)}]} {
304                 puts "error: no icon for state={$state} $path"
305                 return file_plain
306         }
307         return $r
310 proc mapdesc {state path} {
311         global all_descs
313         if {[catch {set r $all_descs($state)}]} {
314                 puts "error: no desc for state={$state} $path"
315                 return $state
316         }
317         return $r
320 proc bsearch {w path} {
321         set hi [expr [lindex [split [$w index end] .] 0] - 2]
322         if {$hi == 0} {
323                 return -1
324         }
325         set lo 0
326         while {$lo < $hi} {
327                 set mi [expr [expr $lo + $hi] / 2]
328                 set ti [expr $mi + 1]
329                 set cmp [string compare [$w get $ti.1 $ti.end] $path]
330                 if {$cmp < 0} {
331                         set lo $ti
332                 } elseif {$cmp == 0} {
333                         return $mi
334                 } else {
335                         set hi $mi
336                 }
337         }
338         return -[expr $lo + 1]
341 proc merge_state {path state} {
342         global file_states
344         if {[array names file_states -exact $path] == {}}  {
345                 set o __
346                 set s [list $o none none]
347         } else {
348                 set s $file_states($path)
349                 set o [lindex $s 0]
350         }
352         set m [lindex $s 0]
353         if {[string index $state 0] == "_"} {
354                 set state [string index $m 0][string index $state 1]
355         } elseif {[string index $state 0] == "*"} {
356                 set state _[string index $state 1]
357         }
359         if {[string index $state 1] == "_"} {
360                 set state [string index $state 0][string index $m 1]
361         } elseif {[string index $state 1] == "*"} {
362                 set state [string index $state 0]_
363         }
365         set file_states($path) [lreplace $s 0 0 $state]
366         return $o
369 proc display_file {path state} {
370         global ui_index ui_other file_states
372         set old_m [merge_state $path $state]
373         set s $file_states($path)
374         set m [lindex $s 0]
376         if {[mapcol $m $path] == "o"} {
377                 set ii 1
378                 set ai 2
379                 set iw $ui_index
380                 set aw $ui_other
381         } else {
382                 set ii 2
383                 set ai 1
384                 set iw $ui_other
385                 set aw $ui_index
386         }
388         set d [lindex $s $ii]
389         if {$d != "none"} {
390                 set lno [bsearch $iw $path]
391                 if {$lno >= 0} {
392                         incr lno
393                         $iw conf -state normal
394                         $iw delete $lno.0 [expr $lno + 1].0
395                         $iw conf -state disabled
396                         set s [lreplace $s $ii $ii none]
397                 }
398         }
400         set d [lindex $s $ai]
401         if {$d == "none"} {
402                 set lno [expr abs([bsearch $aw $path] + 1) + 1]
403                 $aw conf -state normal
404                 set ico [$aw image create $lno.0 \
405                         -align center -padx 5 -pady 1 \
406                         -image [mapicon $m $path]]
407                 $aw insert $lno.1 "$path\n"
408                 $aw conf -state disabled
409                 set file_states($path) [lreplace $s $ai $ai [list $ico]]
410         } elseif {[mapicon $m $path] != [mapicon $old_m $path]} {
411                 set ico [lindex $d 0]
412                 $aw image conf $ico -image [mapicon $m $path]
413         }
416 proc with_update_index {body} {
417         global update_index_fd
419         if {$update_index_fd == {}} {
420                 set update_index_fd [open \
421                         "| git update-index --add --remove -z --stdin" \
422                         w]
423                 fconfigure $update_index_fd -translation binary
424                 uplevel 1 $body
425                 close $update_index_fd
426                 set update_index_fd {}
427         } else {
428                 uplevel 1 $body
429         }
432 proc update_index {path} {
433         global update_index_fd
435         if {$update_index_fd == {}} {
436                 error {not in with_update_index}
437         } else {
438                 puts -nonewline $update_index_fd "$path\0"
439         }
442 proc toggle_mode {path} {
443         global file_states
445         set s $file_states($path)
446         set m [lindex $s 0]
448         switch -- $m {
449         AM -
450         _O {set new A*}
451         _M -
452         MM {set new M*}
453         _D {set new D*}
454         default {return}
455         }
457         with_update_index {update_index $path}
458         display_file $path $new
461 ######################################################################
462 ##
463 ## icons
465 set filemask {
466 #define mask_width 14
467 #define mask_height 15
468 static unsigned char mask_bits[] = {
469    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
470    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
471    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
474 image create bitmap file_plain -background white -foreground black -data {
475 #define plain_width 14
476 #define plain_height 15
477 static unsigned char plain_bits[] = {
478    0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
479    0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
480    0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
481 } -maskdata $filemask
483 image create bitmap file_mod -background white -foreground blue -data {
484 #define mod_width 14
485 #define mod_height 15
486 static unsigned char mod_bits[] = {
487    0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
488    0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
489    0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
490 } -maskdata $filemask
492 image create bitmap file_fulltick -background white -foreground "#007000" -data {
493 #define file_fulltick_width 14
494 #define file_fulltick_height 15
495 static unsigned char file_fulltick_bits[] = {
496    0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
497    0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
498    0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
499 } -maskdata $filemask
501 image create bitmap file_parttick -background white -foreground "#005050" -data {
502 #define parttick_width 14
503 #define parttick_height 15
504 static unsigned char parttick_bits[] = {
505    0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
506    0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
507    0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
508 } -maskdata $filemask
510 image create bitmap file_question -background white -foreground black -data {
511 #define file_question_width 14
512 #define file_question_height 15
513 static unsigned char file_question_bits[] = {
514    0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
515    0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
516    0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
517 } -maskdata $filemask
519 image create bitmap file_removed -background white -foreground red -data {
520 #define file_removed_width 14
521 #define file_removed_height 15
522 static unsigned char file_removed_bits[] = {
523    0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
524    0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
525    0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
526 } -maskdata $filemask
528 image create bitmap file_merge -background white -foreground blue -data {
529 #define file_merge_width 14
530 #define file_merge_height 15
531 static unsigned char file_merge_bits[] = {
532    0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
533    0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
534    0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
535 } -maskdata $filemask
537 set max_status_desc 0
538 foreach i {
539                 {__ i plain    "Unmodified"}
540                 {_M i mod      "Modified"}
541                 {M_ i fulltick "Checked in"}
542                 {MM i parttick "Partially checked in"}
544                 {_O o plain    "Untracked"}
545                 {A_ o fulltick "Added"}
546                 {AM o parttick "Partially added"}
548                 {_D i question "Missing"}
549                 {D_ i removed  "Removed"}
550                 {DD i removed  "Removed"}
551                 {DO i removed  "Removed (still exists)"}
553                 {UM i merge    "Merge conflicts"}
554                 {U_ i merge    "Merge conflicts"}
555         } {
556         if {$max_status_desc < [string length [lindex $i 3]]} {
557                 set max_status_desc [string length [lindex $i 3]]
558         }
559         set all_cols([lindex $i 0]) [lindex $i 1]
560         set all_icons([lindex $i 0]) file_[lindex $i 2]
561         set all_descs([lindex $i 0]) [lindex $i 3]
563 unset filemask i
565 ######################################################################
566 ##
567 ## util
569 proc error_popup {msg} {
570         set w .error
571         toplevel $w
572         wm transient $w .
573         show_msg $w $w $msg
576 proc show_msg {w top msg} {
577         message $w.m -text $msg -justify center -aspect 400
578         pack $w.m -side top -fill x -padx 20 -pady 20
579         button $w.ok -text OK -command "destroy $top"
580         pack $w.ok -side bottom -fill x
581         bind $top <Visibility> "grab $top; focus $top"
582         bind $top <Key-Return> "destroy $top"
583         tkwait window $top
586 ######################################################################
587 ##
588 ## ui commands
590 proc do_gitk {} {
591         global tcl_platform ui_status_value
593         set ui_status_value "Please wait... Starting gitk..."
594     if {$tcl_platform(platform) == "windows"} {
595                 exec sh -c gitk &
596         } else {
597                 exec gitk &
598         }
601 proc do_quit {} {
602         global gitdir ui_comm
604         set save [file join $gitdir GITGUI_MSG]
605         if {[$ui_comm edit modified]
606             && [string trim [$ui_comm get 0.0 end]] != {}} {
607                 catch {
608                         set fd [open $save w]
609                         puts $fd [string trim [$ui_comm get 0.0 end]]
610                         close $fd
611                 }
612         } elseif {[file exists $save]} {
613                 file delete $save
614         }
616         destroy .
619 proc do_rescan {} {
620         update_status
623 proc do_checkin_all {} {
624         global checkin_active ui_status_value
626         if {[is_busy]} return
628         set checkin_active 1
629         set ui_status_value {Checking in all files...}
630         after 1 {
631                 with_update_index {
632                         foreach path [array names file_states] {
633                                 set s $file_states($path)
634                                 set m [lindex $s 0]
635                                 switch -- $m {
636                                 AM -
637                                 MM -
638                                 _M -
639                                 _D {toggle_mode $path}
640                                 }
641                         }
642                 }
643                 set checkin_active 0
644                 set ui_status_value {Ready.}
645         }
648 proc do_signoff {} {
649         global ui_comm
651         catch {
652                 set me [exec git var GIT_COMMITTER_IDENT]
653                 if {[regexp {(.*) [0-9]+ [-+0-9]+$} $me me name]} {
654                         set str "Signed-off-by: $name"
655                         if {[$ui_comm get {end -1c linestart} {end -1c}] != $str} {
656                                 $ui_comm insert end "\n"
657                                 $ui_comm insert end $str
658                                 $ui_comm see end
659                         }
660                 }
661         }
664 # shift == 1: left click
665 #          3: right click  
666 proc click {w x y shift wx wy} {
667         global ui_index ui_other
669         set pos [split [$w index @$x,$y] .]
670         set lno [lindex $pos 0]
671         set col [lindex $pos 1]
672         set path [$w get $lno.1 $lno.end]
673         if {$path == {}} return
675         if {$col > 0 && $shift == 1} {
676                 $ui_index tag remove in_diff 0.0 end
677                 $ui_other tag remove in_diff 0.0 end
678                 $w tag add in_diff $lno.0 [expr $lno + 1].0
679                 show_diff $path
680         }
683 proc unclick {w x y} {
684         set pos [split [$w index @$x,$y] .]
685         set lno [lindex $pos 0]
686         set col [lindex $pos 1]
687         set path [$w get $lno.1 $lno.end]
688         if {$path == {}} return
690         if {$col == 0 && ![is_busy]} {
691                 toggle_mode $path
692         }
695 ######################################################################
696 ##
697 ## ui init
699 set mainfont {Helvetica 10}
700 set difffont {Courier 10}
701 set maincursor [. cget -cursor]
703 # -- Menu Bar
704 menu .mbar -tearoff 0
705 .mbar add cascade -label Project -menu .mbar.project
706 .mbar add cascade -label Commit -menu .mbar.commit
707 .mbar add cascade -label Fetch -menu .mbar.fetch
708 .mbar add cascade -label Pull -menu .mbar.pull
709 . configure -menu .mbar
711 # -- Project Menu
712 menu .mbar.project
713 .mbar.project add command -label Visualize \
714         -command do_gitk \
715         -font $mainfont
716 .mbar.project add command -label Quit \
717         -command do_quit \
718         -font $mainfont
720 # -- Commit Menu
721 menu .mbar.commit
722 .mbar.commit add command -label Rescan \
723         -command do_rescan \
724         -font $mainfont
725 .mbar.commit add command -label {Check-in All Files} \
726         -command do_checkin_all \
727         -font $mainfont
728 .mbar.commit add command -label {Sign Off} \
729         -command do_signoff \
730         -font $mainfont
731 .mbar.commit add command -label Commit \
732         -command do_commit \
733         -font $mainfont
735 # -- Fetch Menu
736 menu .mbar.fetch
738 # -- Pull Menu
739 menu .mbar.pull
741 # -- Main Window Layout
742 panedwindow .vpane -orient vertical
743 panedwindow .vpane.files -orient horizontal
744 .vpane add .vpane.files -sticky nsew -height 100 -width 400
745 pack .vpane -anchor n -side top -fill both -expand 1
747 # -- Index File List
748 set ui_index .vpane.files.index.list
749 frame .vpane.files.index -height 100 -width 400
750 label .vpane.files.index.title -text {Modified Files} \
751         -background green \
752         -font $mainfont
753 text $ui_index -background white -borderwidth 0 \
754         -width 40 -height 10 \
755         -font $mainfont \
756         -yscrollcommand {.vpane.files.index.sb set} \
757         -cursor $maincursor \
758         -state disabled
759 scrollbar .vpane.files.index.sb -command [list $ui_index yview]
760 pack .vpane.files.index.title -side top -fill x
761 pack .vpane.files.index.sb -side right -fill y
762 pack $ui_index -side left -fill both -expand 1
763 .vpane.files add .vpane.files.index -sticky nsew
765 # -- Other (Add) File List
766 set ui_other .vpane.files.other.list
767 frame .vpane.files.other -height 100 -width 100
768 label .vpane.files.other.title -text {Untracked Files} \
769         -background red \
770         -font $mainfont
771 text $ui_other -background white -borderwidth 0 \
772         -width 40 -height 10 \
773         -font $mainfont \
774         -yscrollcommand {.vpane.files.other.sb set} \
775         -cursor $maincursor \
776         -state disabled
777 scrollbar .vpane.files.other.sb -command [list $ui_other yview]
778 pack .vpane.files.other.title -side top -fill x
779 pack .vpane.files.other.sb -side right -fill y
780 pack $ui_other -side left -fill both -expand 1
781 .vpane.files add .vpane.files.other -sticky nsew
783 $ui_index tag conf in_diff -font [concat $mainfont bold]
784 $ui_other tag conf in_diff -font [concat $mainfont bold]
786 # -- Diff Header
787 set ui_fname_value {}
788 set ui_fstatus_value {}
789 frame .vpane.diff -height 200 -width 400
790 frame .vpane.diff.header
791 label .vpane.diff.header.l1 -text {File:} -font $mainfont
792 label .vpane.diff.header.l2 -textvariable ui_fname_value \
793         -anchor w \
794         -justify left \
795         -font $mainfont
796 label .vpane.diff.header.l3 -text {Status:} -font $mainfont
797 label .vpane.diff.header.l4 -textvariable ui_fstatus_value \
798         -width $max_status_desc \
799         -anchor w \
800         -justify left \
801         -font $mainfont
802 pack .vpane.diff.header.l1 -side left
803 pack .vpane.diff.header.l2 -side left -fill x
804 pack .vpane.diff.header.l4 -side right
805 pack .vpane.diff.header.l3 -side right
807 # -- Diff Body
808 frame .vpane.diff.body
809 set ui_diff .vpane.diff.body.t
810 text $ui_diff -background white -borderwidth 0 \
811         -width 80 -height 15 -wrap none \
812         -font $difffont \
813         -xscrollcommand {.vpane.diff.body.sbx set} \
814         -yscrollcommand {.vpane.diff.body.sby set} \
815         -cursor $maincursor \
816         -state disabled
817 scrollbar .vpane.diff.body.sbx -orient horizontal \
818         -command [list $ui_diff xview]
819 scrollbar .vpane.diff.body.sby -orient vertical \
820         -command [list $ui_diff yview]
821 pack .vpane.diff.body.sbx -side bottom -fill x
822 pack .vpane.diff.body.sby -side right -fill y
823 pack $ui_diff -side left -fill both -expand 1
824 pack .vpane.diff.header -side top -fill x
825 pack .vpane.diff.body -side bottom -fill both -expand 1
826 .vpane add .vpane.diff -stick nsew
828 $ui_diff tag conf dm -foreground red
829 $ui_diff tag conf dp -foreground blue
830 $ui_diff tag conf da -font [concat $difffont bold]
831 $ui_diff tag conf di -foreground "#00a000"
832 $ui_diff tag conf dni -foreground "#a000a0"
833 $ui_diff tag conf bold -font [concat $difffont bold]
835 # -- Commit Area
836 frame .vpane.commarea -height 150
837 .vpane add .vpane.commarea -stick nsew
839 # -- Commit Area Buttons
840 frame .vpane.commarea.buttons
841 label .vpane.commarea.buttons.l -text {} \
842         -anchor w \
843         -justify left \
844         -font $mainfont
845 pack .vpane.commarea.buttons.l -side top -fill x
846 pack .vpane.commarea.buttons -side left -fill y
848 button .vpane.commarea.buttons.rescan -text {Rescan} \
849         -command do_rescan \
850         -font $mainfont
851 pack .vpane.commarea.buttons.rescan -side top -fill x
853 button .vpane.commarea.buttons.ciall -text {Check-in All} \
854         -command do_checkin_all \
855         -font $mainfont
856 pack .vpane.commarea.buttons.ciall -side top -fill x
858 button .vpane.commarea.buttons.signoff -text {Sign Off} \
859         -command do_signoff \
860         -font $mainfont
861 pack .vpane.commarea.buttons.signoff -side top -fill x
863 button .vpane.commarea.buttons.commit -text {Commit} \
864         -command do_commit \
865         -font $mainfont
866 pack .vpane.commarea.buttons.commit -side top -fill x
868 # -- Commit Message Buffer
869 frame .vpane.commarea.buffer
870 set ui_comm .vpane.commarea.buffer.t
871 label .vpane.commarea.buffer.l -text {Commit Message:} \
872         -anchor w \
873         -justify left \
874         -font $mainfont
875 text $ui_comm -background white -borderwidth 1 \
876         -relief sunken \
877         -width 75 -height 10 -wrap none \
878         -font $difffont \
879         -yscrollcommand {.vpane.commarea.buffer.sby set} \
880         -cursor $maincursor
881 scrollbar .vpane.commarea.buffer.sby -command [list $ui_comm yview]
882 pack .vpane.commarea.buffer.l -side top -fill x
883 pack .vpane.commarea.buffer.sby -side right -fill y
884 pack $ui_comm -side left -fill y
885 pack .vpane.commarea.buffer -side left -fill y
887 # -- Status Bar
888 set ui_status_value {Initializing...}
889 label .status -textvariable ui_status_value \
890         -anchor w \
891         -justify left \
892         -borderwidth 1 \
893         -relief sunken \
894         -font $mainfont
895 pack .status -anchor w -side bottom -fill x
897 # -- Key Bindings
898 bind . <Destroy> do_quit
899 bind . <Key-F5> do_rescan
900 bind . <M1-Key-r> do_rescan
901 bind . <M1-Key-R> do_rescan
902 bind . <M1-Key-s> do_signoff
903 bind . <M1-Key-S> do_signoff
904 bind . <M1-Key-u> do_checkin_all
905 bind . <M1-Key-U> do_checkin_all
906 bind . <M1-Key-Return> do_commit
907 bind . <M1-Key-q> do_quit
908 bind . <M1-Key-Q> do_quit
909 foreach i [list $ui_index $ui_other] {
910         bind $i <Button-1> {click %W %x %y 1 %X %Y; break}
911         bind $i <Button-3> {click %W %x %y 3 %X %Y; break}
912         bind $i <ButtonRelease-1> {unclick %W %x %y; break}
914 unset i
916 ######################################################################
917 ##
918 ## main
920 if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} {
921         show_msg {} . "Cannot find the git directory: $err"
922         exit 1
925 wm title . "git-ui ([file normalize [file dirname $gitdir]])"
926 focus -force $ui_comm
927 update_status