Code

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