2 # Tcl ignores the next line -*- tcl -*- \
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 ######################################################################
17 proc update_status {} {
18 global gitdir HEAD commit_type
19 global ui_index ui_other ui_status_value
20 global status_active file_states
22 if {$status_active > 0} return
24 array unset file_states
25 set ui_status_value {Refreshing file status...}
26 foreach w [list $ui_index $ui_other] {
29 $w conf -state disabled
32 if {[catch {set HEAD [exec git rev-parse --verify HEAD]}]} {
33 set commit_type initial
35 set commit_type normal
38 set ls_others [list | git ls-files --others -z \
39 --exclude-per-directory=.gitignore]
40 set info_exclude [file join $gitdir info exclude]
41 if {[file readable $info_exclude]} {
42 lappend ls_others "--exclude-from=$info_exclude"
45 set fd_di [open "| git diff-index --cached -z $HEAD" r]
46 set fd_df [open "| git diff-files -z" r]
47 set fd_lo [open $ls_others r]
50 fconfigure $fd_di -blocking 0 -translation binary
51 fconfigure $fd_df -blocking 0 -translation binary
52 fconfigure $fd_lo -blocking 0 -translation binary
53 fileevent $fd_di readable [list read_diff_index $fd_di]
54 fileevent $fd_df readable [list read_diff_files $fd_df]
55 fileevent $fd_lo readable [list read_ls_others $fd_lo]
58 proc read_diff_index {fd} {
61 append buf_rdi [read $fd]
62 set pck [split $buf_rdi "\0"]
63 set buf_rdi [lindex $pck end]
64 foreach {m p} [lrange $pck 0 end-1] {
65 if {$m != {} && $p != {}} {
66 display_file $p [string index $m end]_
69 status_eof $fd buf_rdi
72 proc read_diff_files {fd} {
75 append buf_rdf [read $fd]
76 set pck [split $buf_rdf "\0"]
77 set buf_rdf [lindex $pck end]
78 foreach {m p} [lrange $pck 0 end-1] {
79 if {$m != {} && $p != {}} {
80 display_file $p _[string index $m end]
83 status_eof $fd buf_rdf
86 proc read_ls_others {fd} {
89 append buf_rlo [read $fd]
90 set pck [split $buf_rlo "\0"]
91 set buf_rlo [lindex $pck end]
92 foreach p [lrange $pck 0 end-1] {
95 status_eof $fd buf_rlo
98 proc status_eof {fd buf} {
99 global status_active $buf
100 global ui_fname_value ui_status_value
105 if {[incr status_active -1] == 0} {
106 set ui_status_value {Ready.}
107 if {$ui_fname_value != {}} {
108 show_diff $ui_fname_value
114 ######################################################################
121 global ui_diff ui_fname_value ui_fstatus_value
123 $ui_diff conf -state normal
124 $ui_diff delete 0.0 end
125 $ui_diff conf -state disabled
126 set ui_fname_value {}
127 set ui_fstatus_value {}
130 proc show_diff {path} {
131 global file_states HEAD status_active diff_3way diff_active
132 global ui_diff ui_fname_value ui_fstatus_value ui_status_value
134 if {$status_active > 0} return
135 if {$diff_active} return
138 set s $file_states($path)
142 set ui_fname_value $path
143 set ui_fstatus_value [mapdesc $m $path]
144 set ui_status_value "Loading diff of $path..."
146 set cmd [list | git diff-index -p $HEAD -- $path]
151 set cmd [list | git diff-index -p -c $HEAD $path]
155 set fd [open $path r]
156 set content [read $fd]
159 set ui_status_value "Unable to display $path"
160 error_popup "Error loading file:\n$err"
163 $ui_diff conf -state normal
164 $ui_diff insert end $content
165 $ui_diff conf -state disabled
170 if {[catch {set fd [open $cmd r]} err]} {
171 set ui_status_value "Unable to display $path"
172 error_popup "Error loading diff:\n$err"
176 fconfigure $fd -blocking 0
177 fileevent $fd readable [list read_diff $fd]
180 proc read_diff {fd} {
181 global ui_diff ui_status_value diff_3way diff_active
183 while {[gets $fd line] >= 0} {
184 if {[string match index* $line]} {
185 if {[string first , $line] >= 0} {
190 $ui_diff conf -state normal
192 set x [string index $line 0]
197 default {set tags {}}
200 set x [string range $line 0 1]
202 default {set tags {}}
204 "++" {set tags dp; set x " +"}
205 " +" {set tags {di bold}; set x "++"}
206 "+ " {set tags dni; set x "-+"}
207 "--" {set tags dm; set x " -"}
208 " -" {set tags {dm bold}; set x "--"}
209 "- " {set tags di; set x "+-"}
210 default {set tags {}}
212 set line [string replace $line 0 1 $x]
214 $ui_diff insert end $line $tags
215 $ui_diff insert end "\n"
216 $ui_diff conf -state disabled
222 set ui_status_value {Ready.}
226 ######################################################################
230 proc mapcol {state path} {
233 if {[catch {set r $all_cols($state)}]} {
234 puts "error: no column for state={$state} $path"
240 proc mapicon {state path} {
243 if {[catch {set r $all_icons($state)}]} {
244 puts "error: no icon for state={$state} $path"
250 proc mapdesc {state path} {
253 if {[catch {set r $all_descs($state)}]} {
254 puts "error: no desc for state={$state} $path"
260 proc bsearch {w path} {
261 set hi [expr [lindex [split [$w index end] .] 0] - 2]
267 set mi [expr [expr $lo + $hi] / 2]
268 set ti [expr $mi + 1]
269 set cmp [string compare [$w get $ti.1 $ti.end] $path]
272 } elseif {$cmp == 0} {
278 return -[expr $lo + 1]
281 proc merge_state {path state} {
284 if {[array names file_states -exact $path] == {}} {
286 set s [list $o none none]
288 set s $file_states($path)
293 if {[string index $state 0] == "_"} {
294 set state [string index $m 0][string index $state 1]
295 } elseif {[string index $state 0] == "*"} {
296 set state _[string index $state 1]
299 if {[string index $state 1] == "_"} {
300 set state [string index $state 0][string index $m 1]
301 } elseif {[string index $state 1] == "*"} {
302 set state [string index $state 0]_
305 set file_states($path) [lreplace $s 0 0 $state]
309 proc display_file {path state} {
310 global ui_index ui_other file_states
312 set old_m [merge_state $path $state]
313 set s $file_states($path)
316 if {[mapcol $m $path] == "o"} {
328 set d [lindex $s $ii]
330 set lno [bsearch $iw $path]
333 $iw conf -state normal
334 $iw delete $lno.0 [expr $lno + 1].0
335 $iw conf -state disabled
336 set s [lreplace $s $ii $ii none]
340 set d [lindex $s $ai]
342 set lno [expr abs([bsearch $aw $path] + 1) + 1]
343 $aw conf -state normal
344 set ico [$aw image create $lno.0 \
345 -align center -padx 5 -pady 1 \
346 -image [mapicon $m $path]]
347 $aw insert $lno.1 "$path\n"
348 $aw conf -state disabled
349 set file_states($path) [lreplace $s $ai $ai [list $ico]]
350 } elseif {[mapicon $m $path] != [mapicon $old_m $path]} {
351 set ico [lindex $d 0]
352 $aw image conf $ico -image [mapicon $m $path]
356 proc toggle_mode {path} {
359 set s $file_states($path)
366 set cmd [list exec git update-index --add $path]
370 set cmd [list exec git update-index $path]
374 set cmd [list exec git update-index --remove $path]
381 if {[catch {eval $cmd} err]} {
382 error_popup "Error processing file:\n$err"
385 display_file $path $new
388 ######################################################################
393 #define mask_width 14
394 #define mask_height 15
395 static unsigned char mask_bits[] = {
396 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
397 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
398 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
401 image create bitmap file_plain -background white -foreground black -data {
402 #define plain_width 14
403 #define plain_height 15
404 static unsigned char plain_bits[] = {
405 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
406 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
407 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
408 } -maskdata $filemask
410 image create bitmap file_mod -background white -foreground blue -data {
412 #define mod_height 15
413 static unsigned char mod_bits[] = {
414 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
415 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
416 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
417 } -maskdata $filemask
419 image create bitmap file_tick -background white -foreground "#007000" -data {
420 #define file_tick_width 14
421 #define file_tick_height 15
422 static unsigned char file_tick_bits[] = {
423 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
424 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
425 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
426 } -maskdata $filemask
428 image create bitmap file_parttick -background white -foreground "#005050" -data {
429 #define parttick_width 14
430 #define parttick_height 15
431 static unsigned char parttick_bits[] = {
432 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
433 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
434 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
435 } -maskdata $filemask
437 image create bitmap file_question -background white -foreground black -data {
438 #define file_question_width 14
439 #define file_question_height 15
440 static unsigned char file_question_bits[] = {
441 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
442 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
443 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
444 } -maskdata $filemask
446 image create bitmap file_removed -background white -foreground red -data {
447 #define file_removed_width 14
448 #define file_removed_height 15
449 static unsigned char file_removed_bits[] = {
450 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
451 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
452 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
453 } -maskdata $filemask
455 image create bitmap file_merge -background white -foreground blue -data {
456 #define file_merge_width 14
457 #define file_merge_height 15
458 static unsigned char file_merge_bits[] = {
459 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
460 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
461 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
462 } -maskdata $filemask
465 {__ i "Unmodified" plain}
466 {_M i "Modified" mod}
467 {M_ i "Checked in" tick}
468 {MM i "Partially checked in" parttick}
470 {_O o "Untracked" plain}
472 {AM o "Partially added" parttick}
474 {_D i "Missing" question}
475 {D_ i "Removed" removed}
476 {DD i "Removed" removed}
477 {DO i "Partially removed" removed}
479 {UM i "Merge conflicts" merge}
480 {U_ i "Merge conflicts" merge}
482 set all_cols([lindex $i 0]) [lindex $i 1]
483 set all_descs([lindex $i 0]) [lindex $i 2]
484 set all_icons([lindex $i 0]) file_[lindex $i 3]
488 ######################################################################
492 proc error_popup {msg} {
499 proc show_msg {w top msg} {
500 message $w.m -text $msg -justify center -aspect 400
501 pack $w.m -side top -fill x -padx 20 -pady 20
502 button $w.ok -text OK -command "destroy $top"
503 pack $w.ok -side bottom -fill x
504 bind $top <Visibility> "grab $top; focus $top"
505 bind $top <Key-Return> "destroy $top"
509 ######################################################################
516 if {$tcl_platform(platform) == "windows"} {
531 # shift == 1: left click
533 proc click {w x y shift wx wy} {
534 set pos [split [$w index @$x,$y] .]
535 set lno [lindex $pos 0]
536 set col [lindex $pos 1]
537 set path [$w get $lno.1 $lno.end]
538 if {$path == {}} return
540 if {$col > 0 && $shift == 1} {
545 proc unclick {w x y} {
546 set pos [split [$w index @$x,$y] .]
547 set lno [lindex $pos 0]
548 set col [lindex $pos 1]
549 set path [$w get $lno.1 $lno.end]
550 if {$path == {}} return
557 ######################################################################
561 set mainfont {Helvetica 10}
562 set difffont {Courier 10}
563 set maincursor [. cget -cursor]
566 menu .mbar -tearoff 0
567 .mbar add cascade -label Project -menu .mbar.project
568 .mbar add cascade -label Commit -menu .mbar.commit
569 .mbar add cascade -label Fetch -menu .mbar.fetch
570 .mbar add cascade -label Pull -menu .mbar.pull
571 . configure -menu .mbar
575 .mbar.project add command -label Visulize \
578 .mbar.project add command -label Quit \
584 .mbar.commit add command -label Rescan \
594 # -- Main Window Layout
595 panedwindow .vpane -orient vertical
596 panedwindow .vpane.files -orient horizontal
597 .vpane add .vpane.files -sticky nsew
598 pack .vpane -anchor n -side top -fill both -expand 1
601 set ui_index .vpane.files.index.list
602 frame .vpane.files.index -height 100 -width 400
603 label .vpane.files.index.title -text {Modified Files} \
606 text $ui_index -background white -borderwidth 0 \
607 -width 40 -height 10 \
609 -yscrollcommand {.vpane.files.index.sb set} \
610 -cursor $maincursor \
612 scrollbar .vpane.files.index.sb -command [list $ui_index yview]
613 pack .vpane.files.index.title -side top -fill x
614 pack .vpane.files.index.sb -side right -fill y
615 pack $ui_index -side left -fill both -expand 1
616 .vpane.files add .vpane.files.index -sticky nsew
618 # -- Other (Add) File List
619 set ui_other .vpane.files.other.list
620 frame .vpane.files.other -height 100 -width 100
621 label .vpane.files.other.title -text {Untracked Files} \
624 text $ui_other -background white -borderwidth 0 \
625 -width 40 -height 10 \
627 -yscrollcommand {.vpane.files.other.sb set} \
628 -cursor $maincursor \
630 scrollbar .vpane.files.other.sb -command [list $ui_other yview]
631 pack .vpane.files.other.title -side top -fill x
632 pack .vpane.files.other.sb -side right -fill y
633 pack $ui_other -side left -fill both -expand 1
634 .vpane.files add .vpane.files.other -sticky nsew
637 set ui_fname_value {}
638 set ui_fstatus_value {}
639 frame .vpane.diff -height 100 -width 100
640 frame .vpane.diff.header
641 label .vpane.diff.header.l1 -text {File:} -font $mainfont
642 label .vpane.diff.header.l2 -textvariable ui_fname_value \
646 label .vpane.diff.header.l3 -text {Status:} -font $mainfont
647 label .vpane.diff.header.l4 -textvariable ui_fstatus_value \
652 pack .vpane.diff.header.l1 -side left
653 pack .vpane.diff.header.l2 -side left -fill x
654 pack .vpane.diff.header.l4 -side right
655 pack .vpane.diff.header.l3 -side right
658 frame .vpane.diff.body
659 set ui_diff .vpane.diff.body.t
660 text $ui_diff -background white -borderwidth 0 \
661 -width 40 -height 20 \
663 -xscrollcommand {.vpane.diff.body.sbx set} \
664 -yscrollcommand {.vpane.diff.body.sby set} \
665 -cursor $maincursor \
667 scrollbar .vpane.diff.body.sbx -orient horizontal \
668 -command [list $ui_diff xview]
669 scrollbar .vpane.diff.body.sby -orient vertical \
670 -command [list $ui_diff yview]
671 pack .vpane.diff.body.sbx -side bottom -fill x
672 pack .vpane.diff.body.sby -side right -fill y
673 pack $ui_diff -side left -fill both -expand 1
674 pack .vpane.diff.header -side top -fill x
675 pack .vpane.diff.body -side bottom -fill both -expand 1
676 .vpane add .vpane.diff -stick nsew
678 $ui_diff tag conf dm -foreground red
679 $ui_diff tag conf dp -foreground blue
680 $ui_diff tag conf da -font [concat $difffont bold]
681 $ui_diff tag conf di -foreground "#00a000"
682 $ui_diff tag conf dni -foreground "#a000a0"
683 $ui_diff tag conf bold -font [concat $difffont bold]
686 frame .vpane.commarea -height 50
687 .vpane add .vpane.commarea -stick nsew
689 # -- Commit Area Buttons
690 frame .vpane.commarea.buttons
691 label .vpane.commarea.buttons.l -text {} \
695 pack .vpane.commarea.buttons.l -side top -fill x
696 button .vpane.commarea.buttons.rescan -text {Rescan} \
699 pack .vpane.commarea.buttons.rescan -side top -fill x
700 button .vpane.commarea.buttons.ciall -text {Check-in All} \
701 -command do_checkin_all \
703 pack .vpane.commarea.buttons.ciall -side top -fill x
704 button .vpane.commarea.buttons.commit -text {Commit} \
707 pack .vpane.commarea.buttons.commit -side top -fill x
708 pack .vpane.commarea.buttons -side left -fill y
710 # -- Commit Message Buffer
711 frame .vpane.commarea.buffer
712 set ui_comm .vpane.commarea.buffer.t
713 label .vpane.commarea.buffer.l -text {Commit Message:} \
717 text $ui_comm -background white -borderwidth 1 \
719 -width 75 -height 10 -wrap none \
721 -yscrollcommand {.vpane.commarea.buffer.sby set} \
723 scrollbar .vpane.commarea.buffer.sby -command [list $ui_comm yview]
724 pack .vpane.commarea.buffer.l -side top -fill x
725 pack .vpane.commarea.buffer.sby -side right -fill y
726 pack $ui_comm -side left -fill y
727 pack .vpane.commarea.buffer -side left -fill y
730 set ui_status_value {Initializing...}
731 label .status -textvariable ui_status_value \
737 pack .status -anchor w -side bottom -fill x
740 bind . <Destroy> do_quit
741 bind . <Key-F5> do_rescan
742 bind . <M1-Key-r> do_rescan
743 bind . <M1-Key-R> do_rescan
744 bind . <M1-Key-q> do_quit
745 bind . <M1-Key-Q> do_quit
746 foreach i [list $ui_index $ui_other] {
747 bind $i <Button-1> {click %W %x %y 1 %X %Y; break}
748 bind $i <Button-3> {click %W %x %y 3 %X %Y; break}
749 bind $i <ButtonRelease-1> {unclick %W %x %y; break}
753 ######################################################################
757 if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} {
758 show_msg {} . "Cannot find the git directory: $err"
762 wm title . "git-ui ([file normalize [file dirname $gitdir]])"
763 focus -force $ui_comm