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.
10 ######################################################################
19 set update_index_fd
{}
21 set disable_on_lock
[list
]
22 set index_lock_type none
28 proc lock_index
{type} {
29 global index_lock_type disable_on_lock
31 if {$index_lock_type == {none
}} {
32 set index_lock_type
$type
33 foreach w
$disable_on_lock {
34 uplevel
#0 $w disabled
37 } elseif
{$index_lock_type == {begin-update
} && $type == {update
}} {
38 set index_lock_type
$type
44 proc unlock_index
{} {
45 global index_lock_type disable_on_lock
47 set index_lock_type none
48 foreach w
$disable_on_lock {
53 ######################################################################
57 proc repository_state
{hdvar ctvar
} {
59 upvar
$hdvar hd
$ctvar ct
61 if {[catch
{set hd
[exec git rev-parse
--verify HEAD
]}]} {
63 } elseif
{[file exists
[file join $gitdir MERGE_HEAD
]]} {
70 proc update_status
{{final Ready.
}} {
71 global HEAD PARENT commit_type
72 global ui_index ui_other ui_status_value ui_comm
73 global status_active file_states
75 if {$status_active ||
![lock_index
read]} return
77 repository_state new_HEAD new_type
78 if {$commit_type == {amend
}
79 && $new_type == {normal
}
80 && $new_HEAD == $HEAD} {
84 set commit_type
$new_type
87 array
unset file_states
88 foreach w
[list
$ui_index $ui_other] {
91 $w conf
-state disabled
94 if {![$ui_comm edit modified
]
95 ||
[string trim
[$ui_comm get
0.0 end
]] == {}} {
96 if {[load_message GITGUI_MSG
]} {
97 } elseif
{[load_message MERGE_MSG
]} {
98 } elseif
{[load_message SQUASH_MSG
]} {
100 $ui_comm edit modified false
104 set ui_status_value
{Refreshing
file status...
}
105 set fd_rf
[open
"| git update-index -q --unmerged --refresh" r
]
106 fconfigure
$fd_rf -blocking 0 -translation binary
107 fileevent
$fd_rf readable
[list read_refresh
$fd_rf $final]
110 proc read_refresh
{fd final
} {
111 global gitdir PARENT commit_type
112 global ui_index ui_other ui_status_value ui_comm
113 global status_active file_states
116 if {![eof
$fd]} return
119 set ls_others
[list | git ls-files
--others -z \
120 --exclude-per-directory=.gitignore
]
121 set info_exclude
[file join $gitdir info exclude
]
122 if {[file readable
$info_exclude]} {
123 lappend ls_others
"--exclude-from=$info_exclude"
127 set ui_status_value
{Scanning
for modified files ...
}
128 set fd_di
[open
"| git diff-index --cached -z $PARENT" r
]
129 set fd_df
[open
"| git diff-files -z" r
]
130 set fd_lo
[open
$ls_others r
]
132 fconfigure
$fd_di -blocking 0 -translation binary
133 fconfigure
$fd_df -blocking 0 -translation binary
134 fconfigure
$fd_lo -blocking 0 -translation binary
135 fileevent
$fd_di readable
[list read_diff_index
$fd_di $final]
136 fileevent
$fd_df readable
[list read_diff_files
$fd_df $final]
137 fileevent
$fd_lo readable
[list read_ls_others
$fd_lo $final]
140 proc load_message
{file} {
141 global gitdir ui_comm
143 set f
[file join $gitdir $file]
144 if {[file isfile
$f]} {
145 if {[catch
{set fd
[open
$f r
]}]} {
148 set content
[string trim
[read $fd]]
150 $ui_comm delete
0.0 end
151 $ui_comm insert end
$content
157 proc read_diff_index
{fd final
} {
160 append buf_rdi
[read $fd]
161 set pck
[split $buf_rdi "\0"]
162 set buf_rdi
[lindex
$pck end
]
163 foreach
{m p
} [lrange
$pck 0 end-1
] {
164 if {$m != {} && $p != {}} {
165 display_file
$p [string index
$m end
]_
168 status_eof
$fd buf_rdi
$final
171 proc read_diff_files
{fd final
} {
174 append buf_rdf
[read $fd]
175 set pck
[split $buf_rdf "\0"]
176 set buf_rdf
[lindex
$pck end
]
177 foreach
{m p
} [lrange
$pck 0 end-1
] {
178 if {$m != {} && $p != {}} {
179 display_file
$p _
[string index
$m end
]
182 status_eof
$fd buf_rdf
$final
185 proc read_ls_others
{fd final
} {
188 append buf_rlo
[read $fd]
189 set pck
[split $buf_rlo "\0"]
190 set buf_rlo
[lindex
$pck end
]
191 foreach p
[lrange
$pck 0 end-1
] {
194 status_eof
$fd buf_rlo
$final
197 proc status_eof
{fd buf final
} {
198 global status_active
$buf
199 global ui_fname_value ui_status_value file_states
204 if {[incr status_active
-1] == 0} {
207 set ui_status_value
$final
208 if {$ui_fname_value != {} && [array names file_states \
209 -exact $ui_fname_value] != {}} {
210 show_diff
$ui_fname_value
218 ######################################################################
223 global ui_diff ui_fname_value ui_fstatus_value
225 $ui_diff conf
-state normal
226 $ui_diff delete
0.0 end
227 $ui_diff conf
-state disabled
228 set ui_fname_value
{}
229 set ui_fstatus_value
{}
232 proc show_diff
{path
} {
233 global file_states PARENT diff_3way diff_active
234 global ui_diff ui_fname_value ui_fstatus_value ui_status_value
236 if {$diff_active ||
![lock_index
read]} return
239 set s
$file_states($path)
243 set ui_fname_value
$path
244 set ui_fstatus_value
[mapdesc
$m $path]
245 set ui_status_value
"Loading diff of $path..."
247 set cmd
[list | git diff-index
-p $PARENT -- $path]
252 set cmd
[list | git diff-index
-p -c $PARENT $path]
256 set fd
[open
$path r
]
257 set content
[read $fd]
262 set ui_status_value
"Unable to display $path"
263 error_popup
"Error loading file:\n$err"
266 $ui_diff conf
-state normal
267 $ui_diff insert end
$content
268 $ui_diff conf
-state disabled
271 set ui_status_value
{Ready.
}
276 if {[catch
{set fd
[open
$cmd r
]} err
]} {
279 set ui_status_value
"Unable to display $path"
280 error_popup
"Error loading diff:\n$err"
284 fconfigure
$fd -blocking 0 -translation auto
285 fileevent
$fd readable
[list read_diff
$fd]
288 proc read_diff
{fd
} {
289 global ui_diff ui_status_value diff_3way diff_active
291 while {[gets
$fd line
] >= 0} {
292 if {[string match
{diff --git *} $line]} continue
293 if {[string match
{diff --combined *} $line]} continue
294 if {[string match
{--- *} $line]} continue
295 if {[string match
{+++ *} $line]} continue
296 if {[string match index
* $line]} {
297 if {[string first
, $line] >= 0} {
302 $ui_diff conf
-state normal
304 set x
[string index
$line 0]
309 default
{set tags
{}}
312 set x
[string range
$line 0 1]
314 default
{set tags
{}}
316 "++" {set tags dp
; set x
" +"}
317 " +" {set tags
{di bold
}; set x
"++"}
318 "+ " {set tags dni
; set x
"-+"}
319 "--" {set tags dm
; set x
" -"}
320 " -" {set tags
{dm bold
}; set x
"--"}
321 "- " {set tags di
; set x
"+-"}
322 default
{set tags
{}}
324 set line
[string replace
$line 0 1 $x]
326 $ui_diff insert end
$line $tags
327 $ui_diff insert end
"\n"
328 $ui_diff conf
-state disabled
335 set ui_status_value
{Ready.
}
339 ######################################################################
343 proc load_last_commit
{} {
344 global HEAD PARENT commit_type ui_comm
346 if {$commit_type == {amend
}} return
347 if {$commit_type != {normal
}} {
348 error_popup
"Can't amend a $commit_type commit."
356 set fd
[open
"| git cat-file commit $HEAD" r
]
357 while {[gets
$fd line
] > 0} {
358 if {[string match
{parent
*} $line]} {
359 set parent
[string range
$line 7 end
]
363 set msg
[string trim
[read $fd]]
366 error_popup
"Error loading commit data for amend:\n$err"
370 if {$parent_count == 0} {
371 set commit_type amend
375 } elseif
{$parent_count == 1} {
376 set commit_type amend
378 $ui_comm delete
0.0 end
379 $ui_comm insert end
$msg
380 $ui_comm edit modified false
383 error_popup
{You can
't amend a merge commit.}
388 proc commit_tree {} {
389 global tcl_platform HEAD gitdir commit_type file_states
390 global commit_active ui_status_value
393 if {$commit_active || ![lock_index update]} return
395 # -- Our in memory state should match the repository.
397 repository_state curHEAD cur_type
398 if {$commit_type == {amend}
399 && $cur_type == {normal}
400 && $curHEAD == $HEAD} {
401 } elseif {$commit_type != $cur_type || $HEAD != $curHEAD} {
402 error_popup {Last scanned state does not match repository state.
404 Its highly likely that another Git program modified the
405 repository since our last scan. A rescan is required
413 # -- At least one file should differ in the index.
416 foreach path [array names file_states] {
417 set s $file_states($path)
418 switch -glob -- [lindex $s 0] {
422 M* {set files_ready 1; break}
424 error_popup "Unmerged files cannot be committed.
426 File $path has merge conflicts.
427 You must resolve them and check the file in before committing.
433 error_popup "Unknown file state [lindex $s 0] detected.
435 File $path cannot be committed by this program.
441 error_popup {No checked-in files to commit.
443 You must check-in at least 1 file before you can commit.
449 # -- A message is required.
451 set msg [string trim [$ui_comm get 1.0 end]]
453 error_popup {Please supply a commit message.
455 A good commit message has the following format:
457 - First line: Describe in one sentance what you did.
459 - Remaining lines: Describe why this change is good.
465 # -- Ask the pre-commit hook for the go-ahead.
467 set pchook [file join $gitdir hooks pre-commit]
468 if {$tcl_platform(platform) == {windows} && [file isfile $pchook]} {
469 set pchook [list sh -c \
470 "if test -x \"$pchook\"; then exec \"$pchook\"; fi"]
471 } elseif {[file executable $pchook]} {
472 set pchook [list $pchook]
476 if {$pchook != {} && [catch {eval exec $pchook} err]} {
477 hook_failed_popup pre-commit $err
482 # -- Write the tree in the background.
485 set ui_status_value {Committing changes...}
487 set fd_wt [open "| git write-tree" r]
488 fileevent $fd_wt readable [list commit_stage2 $fd_wt $curHEAD $msg]
491 proc commit_stage2 {fd_wt curHEAD msg} {
492 global single_commit gitdir PARENT commit_type
493 global commit_active ui_status_value ui_comm
498 if {$tree_id == {}} {
499 error_popup "write-tree failed"
501 set ui_status_value {Commit failed.}
506 # -- Create the commit.
508 set cmd [list git commit-tree $tree_id]
510 lappend cmd -p $PARENT
512 if {$commit_type == {merge}} {
514 set fd_mh [open [file join $gitdir MERGE_HEAD] r]
515 while {[gets $fd_mh merge_head] >= 0} {
516 lappend cmd -p $merge_head
520 error_popup "Loading MERGE_HEADs failed:\n$err"
522 set ui_status_value {Commit failed.}
528 # git commit-tree writes to stderr during initial commit.
529 lappend cmd 2>/dev/null
532 if {[catch {set cmt_id [eval exec $cmd]} err]} {
533 error_popup "commit-tree failed:\n$err"
535 set ui_status_value {Commit failed.}
540 # -- Update the HEAD ref.
543 if {$commit_type != {normal}} {
544 append reflogm " ($commit_type)"
546 set i [string first "\n" $msg]
548 append reflogm {: } [string range $msg 0 [expr $i - 1]]
550 append reflogm {: } $msg
552 set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD]
553 if {[catch {eval exec $cmd} err]} {
554 error_popup "update-ref failed:\n$err"
556 set ui_status_value {Commit failed.}
561 # -- Cleanup after ourselves.
563 catch {file delete [file join $gitdir MERGE_HEAD]}
564 catch {file delete [file join $gitdir MERGE_MSG]}
565 catch {file delete [file join $gitdir SQUASH_MSG]}
566 catch {file delete [file join $gitdir GITGUI_MSG]}
568 # -- Let rerere do its thing.
570 if {[file isdirectory [file join $gitdir rr-cache]]} {
571 catch {exec git rerere}
574 $ui_comm delete 0.0 end
575 $ui_comm edit modified false
577 if {$single_commit} do_quit
584 update_status "Changes committed as $cmt_id."
587 ######################################################################
591 proc fetch_from {remote} {
592 set w [new_console "fetch $remote" \
593 "Fetching new changes from $remote"]
594 set cmd [list git fetch]
600 proc push_to {remote} {
601 set w [new_console "push $remote" \
602 "Pushing changes to $remote"]
603 set cmd [list git push]
609 ######################################################################
613 proc mapcol {state path} {
616 if {[catch {set r $all_cols($state)}]} {
617 puts "error: no column for state={$state} $path"
623 proc mapicon {state path} {
626 if {[catch {set r $all_icons($state)}]} {
627 puts "error: no icon for state={$state} $path"
633 proc mapdesc {state path} {
636 if {[catch {set r $all_descs($state)}]} {
637 puts "error: no desc for state={$state} $path"
643 proc bsearch {w path} {
644 set hi [expr [lindex [split [$w index end] .] 0] - 2]
650 set mi [expr [expr $lo + $hi] / 2]
651 set ti [expr $mi + 1]
652 set cmp [string compare [$w get $ti.1 $ti.end] $path]
655 } elseif {$cmp == 0} {
661 return -[expr $lo + 1]
664 proc merge_state {path state} {
667 if {[array names file_states -exact $path] == {}} {
669 set s [list $o none none]
671 set s $file_states($path)
676 if {[string index $state 0] == "_"} {
677 set state [string index $m 0][string index $state 1]
678 } elseif {[string index $state 0] == "*"} {
679 set state _[string index $state 1]
682 if {[string index $state 1] == "_"} {
683 set state [string index $state 0][string index $m 1]
684 } elseif {[string index $state 1] == "*"} {
685 set state [string index $state 0]_
688 set file_states($path) [lreplace $s 0 0 $state]
692 proc display_file {path state} {
693 global ui_index ui_other file_states
695 set old_m [merge_state $path $state]
696 set s $file_states($path)
699 if {[mapcol $m $path] == "o"} {
711 set d [lindex $s $ii]
713 set lno [bsearch $iw $path]
716 $iw conf -state normal
717 $iw delete $lno.0 [expr $lno + 1].0
718 $iw conf -state disabled
719 set s [lreplace $s $ii $ii none]
723 set d [lindex $s $ai]
725 set lno [expr abs([bsearch $aw $path] + 1) + 1]
726 $aw conf -state normal
727 set ico [$aw image create $lno.0 \
728 -align center -padx 5 -pady 1 \
729 -image [mapicon $m $path]]
730 $aw insert $lno.1 "$path\n"
731 $aw conf -state disabled
732 set file_states($path) [lreplace $s $ai $ai [list $ico]]
733 } elseif {[mapicon $m $path] != [mapicon $old_m $path]} {
734 set ico [lindex $d 0]
735 $aw image conf $ico -image [mapicon $m $path]
739 proc with_update_index {body} {
740 global update_index_fd
742 if {$update_index_fd == {}} {
743 if {![lock_index update]} return
744 set update_index_fd [open \
745 "| git update-index --add --remove -z --stdin" \
747 fconfigure $update_index_fd -translation binary
749 close $update_index_fd
750 set update_index_fd {}
757 proc update_index {path} {
758 global update_index_fd
760 if {$update_index_fd == {}} {
761 error {not in with_update_index}
763 puts -nonewline $update_index_fd "$path\0"
767 proc toggle_mode {path} {
768 global file_states ui_fname_value
770 set s $file_states($path)
783 with_update_index {update_index $path}
784 display_file $path $new
785 if {$ui_fname_value == $path} {
790 ######################################################################
792 ## config (fetch push pull)
794 proc load_all_remotes {} {
795 global gitdir all_remotes
797 set all_remotes [list]
798 set rm_dir [file join $gitdir remotes]
799 if {[file isdirectory $rm_dir]} {
800 set all_remotes [concat $all_remotes [glob \
804 -directory $rm_dir *]]
807 set fd_rc [open "| git repo-config --list" r]
808 while {[gets $fd_rc line] >= 0} {
809 if {[regexp ^remote\.(.*)\.url= $line line name]} {
810 lappend all_remotes $name
815 set all_remotes [lsort -unique $all_remotes]
818 proc populate_remote_menu {m pfx op} {
819 global gitdir all_remotes mainfont
821 foreach remote $all_remotes {
822 $m add command -label "$pfx $remote..." \
823 -command [list $op $remote] \
828 ######################################################################
833 #define mask_width 14
834 #define mask_height 15
835 static unsigned char mask_bits[] = {
836 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
837 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
838 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
841 image create bitmap file_plain -background white -foreground black -data {
842 #define plain_width 14
843 #define plain_height 15
844 static unsigned char plain_bits[] = {
845 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
846 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
847 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
848 } -maskdata $filemask
850 image create bitmap file_mod -background white -foreground blue -data {
852 #define mod_height 15
853 static unsigned char mod_bits[] = {
854 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
855 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
856 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
857 } -maskdata $filemask
859 image create bitmap file_fulltick -background white -foreground "#007000" -data {
860 #define file_fulltick_width 14
861 #define file_fulltick_height 15
862 static unsigned char file_fulltick_bits[] = {
863 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
864 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
865 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
866 } -maskdata $filemask
868 image create bitmap file_parttick -background white -foreground "#005050" -data {
869 #define parttick_width 14
870 #define parttick_height 15
871 static unsigned char parttick_bits[] = {
872 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
873 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
874 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
875 } -maskdata $filemask
877 image create bitmap file_question -background white -foreground black -data {
878 #define file_question_width 14
879 #define file_question_height 15
880 static unsigned char file_question_bits[] = {
881 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
882 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
883 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
884 } -maskdata $filemask
886 image create bitmap file_removed -background white -foreground red -data {
887 #define file_removed_width 14
888 #define file_removed_height 15
889 static unsigned char file_removed_bits[] = {
890 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
891 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
892 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
893 } -maskdata $filemask
895 image create bitmap file_merge -background white -foreground blue -data {
896 #define file_merge_width 14
897 #define file_merge_height 15
898 static unsigned char file_merge_bits[] = {
899 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
900 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
901 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
902 } -maskdata $filemask
904 set max_status_desc 0
906 {__ i plain "Unmodified"}
907 {_M i mod "Modified"}
908 {M_ i fulltick "Checked in"}
909 {MM i parttick "Partially checked in"}
911 {_O o plain "Untracked"}
912 {A_ o fulltick "Added"}
913 {AM o parttick "Partially added"}
914 {AD o question "Added (but now gone)"}
916 {_D i question "Missing"}
917 {D_ i removed "Removed"}
918 {DD i removed "Removed"}
919 {DO i removed "Removed (still exists)"}
921 {UM i merge "Merge conflicts"}
922 {U_ i merge "Merge conflicts"}
924 if {$max_status_desc < [string length [lindex $i 3]]} {
925 set max_status_desc [string length [lindex $i 3]]
927 set all_cols([lindex $i 0]) [lindex $i 1]
928 set all_icons([lindex $i 0]) file_[lindex $i 2]
929 set all_descs([lindex $i 0]) [lindex $i 3]
933 ######################################################################
937 proc error_popup {msg} {
944 proc show_msg {w top msg} {
945 global gitdir appname mainfont
947 message $w.m -text $msg -justify left -aspect 400
948 pack $w.m -side top -fill x -padx 5 -pady 10
949 button $w.ok -text OK \
952 -command "destroy $top"
953 pack $w.ok -side bottom
954 bind $top <Visibility> "grab $top; focus $top"
955 bind $top <Key-Return> "destroy $top"
956 wm title $top "error: $appname ([file normalize [file dirname $gitdir]])"
960 proc hook_failed_popup {hook msg} {
961 global gitdir mainfont difffont appname
968 label $w.m.l1 -text "$hook hook failed:" \
971 -font [concat $mainfont bold]
973 -background white -borderwidth 1 \
975 -width 80 -height 10 \
977 -yscrollcommand [list $w.m.sby set]
979 -text {You must correct the above errors before committing.} \
982 -font [concat $mainfont bold]
983 scrollbar $w.m.sby -command [list $w.m.t yview]
984 pack $w.m.l1 -side top -fill x
985 pack $w.m.l2 -side bottom -fill x
986 pack $w.m.sby -side right -fill y
987 pack $w.m.t -side left -fill both -expand 1
988 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
990 $w.m.t insert 1.0 $msg
991 $w.m.t conf -state disabled
993 button $w.ok -text OK \
996 -command "destroy $w"
997 pack $w.ok -side bottom
999 bind $w <Visibility> "grab $w; focus $w"
1000 bind $w <Key-Return> "destroy $w"
1001 wm title $w "error: $appname ([file normalize [file dirname $gitdir]])"
1005 set next_console_id 0
1007 proc new_console {short_title long_title} {
1008 global next_console_id console_data
1009 set w .console[incr next_console_id]
1010 set console_data($w) [list $short_title $long_title]
1011 return [console_init $w]
1014 proc console_init {w} {
1015 global console_cr console_data
1016 global gitdir appname mainfont difffont
1018 set console_cr($w) 1.0
1021 label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
1024 -font [concat $mainfont bold]
1026 -background white -borderwidth 1 \
1028 -width 80 -height 10 \
1031 -yscrollcommand [list $w.m.sby set]
1032 label $w.m.s -anchor w \
1034 -font [concat $mainfont bold]
1035 scrollbar $w.m.sby -command [list $w.m.t yview]
1036 pack $w.m.l1 -side top -fill x
1037 pack $w.m.s -side bottom -fill x
1038 pack $w.m.sby -side right -fill y
1039 pack $w.m.t -side left -fill both -expand 1
1040 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1042 button $w.ok -text {Running...} \
1046 -command "destroy $w"
1047 pack $w.ok -side bottom
1049 bind $w <Visibility> "focus $w"
1050 wm title $w "$appname ([file dirname [file normalize [file dirname $gitdir]]]): [lindex $console_data($w) 0]"
1054 proc console_exec {w cmd} {
1057 # -- Windows tosses the enviroment when we exec our child.
1058 # But most users need that so we have to relogin. :-(
1060 if {$tcl_platform(platform) == {windows}} {
1061 set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
1064 # -- Tcl won't
let us redirect both stdout and stderr to
1065 # the same pipe. So pass it through cat...
1067 set cmd
[concat |
$cmd |
& cat]
1069 set fd_f
[open
$cmd r
]
1070 fconfigure
$fd_f -blocking 0 -translation binary
1071 fileevent
$fd_f readable
[list console_read
$w $fd_f]
1074 proc console_read
{w fd
} {
1075 global console_cr console_data
1079 if {![winfo exists
$w]} {console_init
$w}
1080 $w.m.t conf
-state normal
1082 set n
[string length
$buf]
1084 set cr
[string first
"\r" $buf $c]
1085 set lf
[string first
"\n" $buf $c]
1086 if {$cr < 0} {set cr
[expr $n + 1]}
1087 if {$lf < 0} {set lf
[expr $n + 1]}
1090 $w.m.t insert end
[string range
$buf $c $lf]
1091 set console_cr
($w) [$w.m.t index
{end
-1c}]
1095 $w.m.t delete
$console_cr($w) end
1096 $w.m.t insert end
"\n"
1097 $w.m.t insert end
[string range
$buf $c $cr]
1102 $w.m.t conf
-state disabled
1106 fconfigure
$fd -blocking 1
1108 if {[catch
{close
$fd}]} {
1109 if {![winfo exists
$w]} {console_init
$w}
1110 $w.m.s conf
-background red
-text {Error
: Command Failed
}
1111 $w.ok conf
-text Close
1112 $w.ok conf
-state normal
1113 } elseif
{[winfo exists
$w]} {
1114 $w.m.s conf
-background green
-text {Success
}
1115 $w.ok conf
-text Close
1116 $w.ok conf
-state normal
1118 array
unset console_cr
$w
1119 array
unset console_data
$w
1122 fconfigure
$fd -blocking 0
1125 ######################################################################
1129 set starting_gitk_msg
{Please
wait... Starting gitk...
}
1132 global tcl_platform ui_status_value starting_gitk_msg
1134 set ui_status_value
$starting_gitk_msg
1136 if {$ui_status_value == $starting_gitk_msg} {
1137 set ui_status_value
{Ready.
}
1141 if {$tcl_platform(platform
) == {windows
}} {
1149 global gitdir ui_comm
1151 set save
[file join $gitdir GITGUI_MSG
]
1152 set msg
[string trim
[$ui_comm get
0.0 end
]]
1153 if {[$ui_comm edit modified
] && $msg != {}} {
1155 set fd
[open
$save w
]
1156 puts
$fd [string trim
[$ui_comm get
0.0 end
]]
1159 } elseif
{$msg == {} && [file exists
$save]} {
1170 proc do_checkin_all
{} {
1171 global checkin_active ui_status_value
1173 if {$checkin_active ||
![lock_index begin-update
]} return
1175 set checkin_active
1
1176 set ui_status_value
{Checking
in all files...
}
1179 foreach path
[array names file_states
] {
1180 set s
$file_states($path)
1186 _D
{toggle_mode
$path}
1190 set checkin_active
0
1191 set ui_status_value
{Ready.
}
1195 proc do_signoff
{} {
1199 set me
[exec git var GIT_COMMITTER_IDENT
]
1200 if {[regexp
{(.
*) [0-9]+ [-+0-9]+$
} $me me name
]} {
1201 set str
"Signed-off-by: $name"
1202 if {[$ui_comm get
{end
-1c linestart
} {end
-1c}] != $str} {
1203 $ui_comm insert end
"\n"
1204 $ui_comm insert end
$str
1211 proc do_amend_last
{} {
1219 # shift == 1: left click
1221 proc click
{w x y
shift wx wy
} {
1222 global ui_index ui_other
1224 set pos
[split [$w index @
$x,$y] .
]
1225 set lno
[lindex
$pos 0]
1226 set col [lindex
$pos 1]
1227 set path
[$w get
$lno.1 $lno.end
]
1228 if {$path == {}} return
1230 if {$col > 0 && $shift == 1} {
1231 $ui_index tag remove in_diff
0.0 end
1232 $ui_other tag remove in_diff
0.0 end
1233 $w tag add in_diff
$lno.0 [expr $lno + 1].0
1238 proc unclick
{w x y
} {
1239 set pos
[split [$w index @
$x,$y] .
]
1240 set lno
[lindex
$pos 0]
1241 set col [lindex
$pos 1]
1242 set path
[$w get
$lno.1 $lno.end
]
1243 if {$path == {}} return
1250 ######################################################################
1254 set mainfont
{Helvetica
10}
1255 set difffont
{Courier
10}
1256 set maincursor
[. cget
-cursor]
1258 switch
-glob -- "$tcl_platform(platform),$tcl_platform(os)" {
1259 windows
,* {set M1B Control
; set M1T Ctrl
}
1260 unix
,Darwin
{set M1B M1
; set M1T Cmd
}
1261 default
{set M1B M1
; set M1T M1
}
1265 menu .mbar
-tearoff 0
1266 .mbar add cascade
-label Project
-menu .mbar.project
1267 .mbar add cascade
-label Commit
-menu .mbar.commit
1268 .mbar add cascade
-label Fetch
-menu .mbar.fetch
1269 .mbar add cascade
-label Pull
-menu .mbar.pull
1270 .mbar add cascade
-label Push
-menu .mbar.push
1271 . configure
-menu .mbar
1275 .mbar.project add
command -label Visualize \
1278 .mbar.project add
command -label Quit \
1280 -accelerator $M1T-Q \
1285 .mbar.commit add
command -label Rescan \
1286 -command do_rescan \
1289 lappend disable_on_lock \
1290 [list .mbar.commit entryconf
[.mbar.commit index last
] -state]
1291 .mbar.commit add
command -label {Amend Last Commit
} \
1292 -command do_amend_last \
1294 lappend disable_on_lock \
1295 [list .mbar.commit entryconf
[.mbar.commit index last
] -state]
1296 .mbar.commit add
command -label {Check-in All Files
} \
1297 -command do_checkin_all \
1298 -accelerator $M1T-U \
1300 lappend disable_on_lock \
1301 [list .mbar.commit entryconf
[.mbar.commit index last
] -state]
1302 .mbar.commit add
command -label {Sign Off
} \
1303 -command do_signoff \
1304 -accelerator $M1T-S \
1306 .mbar.commit add
command -label Commit \
1307 -command do_commit \
1308 -accelerator $M1T-Return \
1310 lappend disable_on_lock \
1311 [list .mbar.commit entryconf
[.mbar.commit index last
] -state]
1322 # -- Main Window Layout
1323 panedwindow .vpane
-orient vertical
1324 panedwindow .vpane.files
-orient horizontal
1325 .vpane add .vpane.files
-sticky nsew
-height 100 -width 400
1326 pack .vpane
-anchor n
-side top
-fill both
-expand 1
1328 # -- Index File List
1329 set ui_index .vpane.files.index.list
1330 frame .vpane.files.index
-height 100 -width 400
1331 label .vpane.files.index.title
-text {Modified Files
} \
1334 text
$ui_index -background white
-borderwidth 0 \
1335 -width 40 -height 10 \
1337 -yscrollcommand {.vpane.files.index.sb
set} \
1338 -cursor $maincursor \
1340 scrollbar .vpane.files.index.sb
-command [list
$ui_index yview
]
1341 pack .vpane.files.index.title
-side top
-fill x
1342 pack .vpane.files.index.sb
-side right
-fill y
1343 pack
$ui_index -side left
-fill both
-expand 1
1344 .vpane.files add .vpane.files.index
-sticky nsew
1346 # -- Other (Add) File List
1347 set ui_other .vpane.files.other.list
1348 frame .vpane.files.other
-height 100 -width 100
1349 label .vpane.files.other.title
-text {Untracked Files
} \
1352 text
$ui_other -background white
-borderwidth 0 \
1353 -width 40 -height 10 \
1355 -yscrollcommand {.vpane.files.other.sb
set} \
1356 -cursor $maincursor \
1358 scrollbar .vpane.files.other.sb
-command [list
$ui_other yview
]
1359 pack .vpane.files.other.title
-side top
-fill x
1360 pack .vpane.files.other.sb
-side right
-fill y
1361 pack
$ui_other -side left
-fill both
-expand 1
1362 .vpane.files add .vpane.files.other
-sticky nsew
1364 $ui_index tag conf in_diff
-font [concat
$mainfont bold
]
1365 $ui_other tag conf in_diff
-font [concat
$mainfont bold
]
1368 set ui_fname_value
{}
1369 set ui_fstatus_value
{}
1370 frame .vpane.
diff -height 200 -width 400
1371 frame .vpane.
diff.header
1372 label .vpane.
diff.header.l1
-text {File
:} -font $mainfont
1373 label .vpane.
diff.header.l2
-textvariable ui_fname_value \
1377 label .vpane.
diff.header.l3
-text {Status
:} -font $mainfont
1378 label .vpane.
diff.header.l4
-textvariable ui_fstatus_value \
1379 -width $max_status_desc \
1383 pack .vpane.
diff.header.l1
-side left
1384 pack .vpane.
diff.header.l2
-side left
-fill x
1385 pack .vpane.
diff.header.l4
-side right
1386 pack .vpane.
diff.header.l3
-side right
1389 frame .vpane.
diff.body
1390 set ui_diff .vpane.
diff.body.t
1391 text
$ui_diff -background white
-borderwidth 0 \
1392 -width 80 -height 15 -wrap none \
1394 -xscrollcommand {.vpane.
diff.body.sbx
set} \
1395 -yscrollcommand {.vpane.
diff.body.sby
set} \
1396 -cursor $maincursor \
1398 scrollbar .vpane.
diff.body.sbx
-orient horizontal \
1399 -command [list
$ui_diff xview
]
1400 scrollbar .vpane.
diff.body.sby
-orient vertical \
1401 -command [list
$ui_diff yview
]
1402 pack .vpane.
diff.body.sbx
-side bottom
-fill x
1403 pack .vpane.
diff.body.sby
-side right
-fill y
1404 pack
$ui_diff -side left
-fill both
-expand 1
1405 pack .vpane.
diff.header
-side top
-fill x
1406 pack .vpane.
diff.body
-side bottom
-fill both
-expand 1
1407 .vpane add .vpane.
diff -stick nsew
1409 $ui_diff tag conf dm
-foreground red
1410 $ui_diff tag conf dp
-foreground blue
1411 $ui_diff tag conf da
-font [concat
$difffont bold
]
1412 $ui_diff tag conf di
-foreground "#00a000"
1413 $ui_diff tag conf dni
-foreground "#a000a0"
1414 $ui_diff tag conf bold
-font [concat
$difffont bold
]
1417 frame .vpane.commarea
-height 170
1418 .vpane add .vpane.commarea
-stick nsew
1420 # -- Commit Area Buttons
1421 frame .vpane.commarea.buttons
1422 label .vpane.commarea.buttons.l
-text {} \
1426 pack .vpane.commarea.buttons.l
-side top
-fill x
1427 pack .vpane.commarea.buttons
-side left
-fill y
1429 button .vpane.commarea.buttons.rescan
-text {Rescan
} \
1430 -command do_rescan \
1432 pack .vpane.commarea.buttons.rescan
-side top
-fill x
1433 lappend disable_on_lock
{.vpane.commarea.buttons.rescan conf
-state}
1435 button .vpane.commarea.buttons.amend
-text {Amend Last
} \
1436 -command do_amend_last \
1438 pack .vpane.commarea.buttons.amend
-side top
-fill x
1439 lappend disable_on_lock
{.vpane.commarea.buttons.amend conf
-state}
1441 button .vpane.commarea.buttons.ciall
-text {Check-in All
} \
1442 -command do_checkin_all \
1444 pack .vpane.commarea.buttons.ciall
-side top
-fill x
1445 lappend disable_on_lock
{.vpane.commarea.buttons.ciall conf
-state}
1447 button .vpane.commarea.buttons.signoff
-text {Sign Off
} \
1448 -command do_signoff \
1450 pack .vpane.commarea.buttons.signoff
-side top
-fill x
1452 button .vpane.commarea.buttons.commit
-text {Commit
} \
1453 -command do_commit \
1455 pack .vpane.commarea.buttons.commit
-side top
-fill x
1456 lappend disable_on_lock
{.vpane.commarea.buttons.commit conf
-state}
1458 # -- Commit Message Buffer
1459 frame .vpane.commarea.buffer
1460 set ui_comm .vpane.commarea.buffer.t
1461 set ui_coml .vpane.commarea.buffer.l
1462 label
$ui_coml -text {Commit Message
:} \
1466 trace add variable commit_type
write {uplevel
#0 {
1467 switch
-glob $commit_type \
1468 initial
{$ui_coml conf
-text {Initial Commit Message
:}} \
1469 amend
{$ui_coml conf
-text {Amended Commit Message
:}} \
1470 merge
{$ui_coml conf
-text {Merge Commit Message
:}} \
1471 * {$ui_coml conf
-text {Commit Message
:}}
1473 text
$ui_comm -background white
-borderwidth 1 \
1475 -width 75 -height 10 -wrap none \
1477 -yscrollcommand {.vpane.commarea.buffer.sby
set} \
1479 scrollbar .vpane.commarea.buffer.sby
-command [list
$ui_comm yview
]
1480 pack
$ui_coml -side top
-fill x
1481 pack .vpane.commarea.buffer.sby
-side right
-fill y
1482 pack
$ui_comm -side left
-fill y
1483 pack .vpane.commarea.buffer
-side left
-fill y
1486 set ui_status_value
{Initializing...
}
1487 label .status
-textvariable ui_status_value \
1493 pack .status
-anchor w
-side bottom
-fill x
1496 bind $ui_comm <$M1B-Key-Return> {do_commit
;break}
1497 bind .
<Destroy
> do_quit
1498 bind all
<Key-F5
> do_rescan
1499 bind all
<$M1B-Key-r> do_rescan
1500 bind all
<$M1B-Key-R> do_rescan
1501 bind .
<$M1B-Key-s> do_signoff
1502 bind .
<$M1B-Key-S> do_signoff
1503 bind .
<$M1B-Key-u> do_checkin_all
1504 bind .
<$M1B-Key-U> do_checkin_all
1505 bind .
<$M1B-Key-Return> do_commit
1506 bind all
<$M1B-Key-q> do_quit
1507 bind all
<$M1B-Key-Q> do_quit
1508 bind all
<$M1B-Key-w> {destroy
[winfo toplevel
%W
]}
1509 bind all
<$M1B-Key-W> {destroy
[winfo toplevel
%W
]}
1510 foreach i
[list
$ui_index $ui_other] {
1511 bind $i <Button-1
> {click
%W
%x
%y
1 %X
%Y
; break}
1512 bind $i <Button-3
> {click
%W
%x
%y
3 %X
%Y
; break}
1513 bind $i <ButtonRelease-1
> {unclick
%W
%x
%y
; break}
1517 ######################################################################
1521 set appname
[lindex
[file split $argv0] end
]
1524 if {[catch
{set cdup
[exec git rev-parse
--show-cdup]} err
]} {
1525 show_msg
{} .
"Cannot find the git directory: $err"
1533 if {[catch
{set gitdir
[exec git rev-parse
--git-dir]} err
]} {
1534 show_msg
{} .
"Cannot find the git directory: $err"
1538 if {$appname == {git-citool
}} {
1542 wm title .
"$appname ([file normalize [file dirname $gitdir]])"
1543 focus
-force $ui_comm
1545 populate_remote_menu .mbar.fetch From fetch_from
1546 populate_remote_menu .mbar.push To push_to