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
74 global cfg_trust_mtime
76 if {$status_active ||
![lock_index
read]} return
78 repository_state new_HEAD new_type
79 if {$commit_type == {amend
}
80 && $new_type == {normal
}
81 && $new_HEAD == $HEAD} {
85 set commit_type
$new_type
88 array
unset file_states
89 foreach w
[list
$ui_index $ui_other] {
92 $w conf
-state disabled
95 if {![$ui_comm edit modified
]
96 ||
[string trim
[$ui_comm get
0.0 end
]] == {}} {
97 if {[load_message GITGUI_MSG
]} {
98 } elseif
{[load_message MERGE_MSG
]} {
99 } elseif
{[load_message SQUASH_MSG
]} {
101 $ui_comm edit modified false
104 if {$cfg_trust_mtime == {true
}} {
105 update_status_stage2
{} $final
108 set ui_status_value
{Refreshing
file status...
}
109 set fd_rf
[open
"| git update-index -q --unmerged --refresh" r
]
110 fconfigure
$fd_rf -blocking 0 -translation binary
111 fileevent
$fd_rf readable
[list update_status_stage2
$fd_rf $final]
115 proc update_status_stage2
{fd final
} {
116 global gitdir PARENT commit_type
117 global ui_index ui_other ui_status_value ui_comm
118 global status_active file_states
119 global buf_rdi buf_rdf buf_rlo
123 if {![eof
$fd]} return
127 set ls_others
[list | git ls-files
--others -z \
128 --exclude-per-directory=.gitignore
]
129 set info_exclude
[file join $gitdir info exclude
]
130 if {[file readable
$info_exclude]} {
131 lappend ls_others
"--exclude-from=$info_exclude"
139 set ui_status_value
{Scanning
for modified files ...
}
140 set fd_di
[open
"| git diff-index --cached -z $PARENT" r
]
141 set fd_df
[open
"| git diff-files -z" r
]
142 set fd_lo
[open
$ls_others r
]
144 fconfigure
$fd_di -blocking 0 -translation binary
145 fconfigure
$fd_df -blocking 0 -translation binary
146 fconfigure
$fd_lo -blocking 0 -translation binary
147 fileevent
$fd_di readable
[list read_diff_index
$fd_di $final]
148 fileevent
$fd_df readable
[list read_diff_files
$fd_df $final]
149 fileevent
$fd_lo readable
[list read_ls_others
$fd_lo $final]
152 proc load_message
{file} {
153 global gitdir ui_comm
155 set f
[file join $gitdir $file]
156 if {[file isfile
$f]} {
157 if {[catch
{set fd
[open
$f r
]}]} {
160 set content
[string trim
[read $fd]]
162 $ui_comm delete
0.0 end
163 $ui_comm insert end
$content
169 proc read_diff_index
{fd final
} {
172 append buf_rdi
[read $fd]
174 set n
[string length
$buf_rdi]
176 set z1
[string first
"\0" $buf_rdi $c]
179 set z2
[string first
"\0" $buf_rdi $z1]
185 [string range
$buf_rdi $z1 $z2] \
186 [string index
$buf_rdi [expr $z1 - 2]]_
190 set buf_rdi
[string range
$buf_rdi $c end
]
195 status_eof
$fd buf_rdi
$final
198 proc read_diff_files
{fd final
} {
201 append buf_rdf
[read $fd]
203 set n
[string length
$buf_rdf]
205 set z1
[string first
"\0" $buf_rdf $c]
208 set z2
[string first
"\0" $buf_rdf $z1]
214 [string range
$buf_rdf $z1 $z2] \
215 _
[string index
$buf_rdf [expr $z1 - 2]]
219 set buf_rdf
[string range
$buf_rdf $c end
]
224 status_eof
$fd buf_rdf
$final
227 proc read_ls_others
{fd final
} {
230 append buf_rlo
[read $fd]
231 set pck
[split $buf_rlo "\0"]
232 set buf_rlo
[lindex
$pck end
]
233 foreach p
[lrange
$pck 0 end-1
] {
236 status_eof
$fd buf_rlo
$final
239 proc status_eof
{fd buf final
} {
240 global status_active
$buf
241 global ui_fname_value ui_status_value file_states
247 if {[incr status_active
-1] == 0} {
251 set ui_status_value
$final
253 if {$ui_fname_value != {} && [array names file_states \
254 -exact $ui_fname_value] != {}} {
255 show_diff
$ui_fname_value
263 ######################################################################
268 global ui_diff ui_fname_value ui_fstatus_value
270 $ui_diff conf
-state normal
271 $ui_diff delete
0.0 end
272 $ui_diff conf
-state disabled
273 set ui_fname_value
{}
274 set ui_fstatus_value
{}
277 proc show_diff
{path
} {
278 global file_states PARENT diff_3way diff_active
279 global ui_diff ui_fname_value ui_fstatus_value ui_status_value
281 if {$diff_active ||
![lock_index
read]} return
284 set s
$file_states($path)
288 set ui_fname_value
$path
289 set ui_fstatus_value
[mapdesc
$m $path]
290 set ui_status_value
"Loading diff of $path..."
292 set cmd
[list | git diff-index
-p $PARENT -- $path]
297 set cmd
[list | git diff-index
-p -c $PARENT $path]
301 set fd
[open
$path r
]
302 set content
[read $fd]
307 set ui_status_value
"Unable to display $path"
308 error_popup
"Error loading file:\n$err"
311 $ui_diff conf
-state normal
312 $ui_diff insert end
$content
313 $ui_diff conf
-state disabled
316 set ui_status_value
{Ready.
}
321 if {[catch
{set fd
[open
$cmd r
]} err
]} {
324 set ui_status_value
"Unable to display $path"
325 error_popup
"Error loading diff:\n$err"
329 fconfigure
$fd -blocking 0 -translation auto
330 fileevent
$fd readable
[list read_diff
$fd]
333 proc read_diff
{fd
} {
334 global ui_diff ui_status_value diff_3way diff_active
336 while {[gets
$fd line
] >= 0} {
337 if {[string match
{diff --git *} $line]} continue
338 if {[string match
{diff --combined *} $line]} continue
339 if {[string match
{--- *} $line]} continue
340 if {[string match
{+++ *} $line]} continue
341 if {[string match index
* $line]} {
342 if {[string first
, $line] >= 0} {
347 $ui_diff conf
-state normal
349 set x
[string index
$line 0]
354 default
{set tags
{}}
357 set x
[string range
$line 0 1]
359 default
{set tags
{}}
361 "++" {set tags dp
; set x
" +"}
362 " +" {set tags
{di bold
}; set x
"++"}
363 "+ " {set tags dni
; set x
"-+"}
364 "--" {set tags dm
; set x
" -"}
365 " -" {set tags
{dm bold
}; set x
"--"}
366 "- " {set tags di
; set x
"+-"}
367 default
{set tags
{}}
369 set line
[string replace
$line 0 1 $x]
371 $ui_diff insert end
$line $tags
372 $ui_diff insert end
"\n"
373 $ui_diff conf
-state disabled
380 set ui_status_value
{Ready.
}
384 ######################################################################
388 proc load_last_commit
{} {
389 global HEAD PARENT commit_type ui_comm
391 if {$commit_type == {amend
}} return
392 if {$commit_type != {normal
}} {
393 error_popup
"Can't amend a $commit_type commit."
401 set fd
[open
"| git cat-file commit $HEAD" r
]
402 while {[gets
$fd line
] > 0} {
403 if {[string match
{parent
*} $line]} {
404 set parent
[string range
$line 7 end
]
408 set msg
[string trim
[read $fd]]
411 error_popup
"Error loading commit data for amend:\n$err"
415 if {$parent_count == 0} {
416 set commit_type amend
420 } elseif
{$parent_count == 1} {
421 set commit_type amend
423 $ui_comm delete
0.0 end
424 $ui_comm insert end
$msg
425 $ui_comm edit modified false
428 error_popup
{You can
't amend a merge commit.}
433 proc commit_tree {} {
434 global tcl_platform HEAD gitdir commit_type file_states
435 global commit_active ui_status_value
438 if {$commit_active || ![lock_index update]} return
440 # -- Our in memory state should match the repository.
442 repository_state curHEAD cur_type
443 if {$commit_type == {amend}
444 && $cur_type == {normal}
445 && $curHEAD == $HEAD} {
446 } elseif {$commit_type != $cur_type || $HEAD != $curHEAD} {
447 error_popup {Last scanned state does not match repository state.
449 Its highly likely that another Git program modified the
450 repository since our last scan. A rescan is required
458 # -- At least one file should differ in the index.
461 foreach path [array names file_states] {
462 set s $file_states($path)
463 switch -glob -- [lindex $s 0] {
467 M* {set files_ready 1; break}
469 error_popup "Unmerged files cannot be committed.
471 File $path has merge conflicts.
472 You must resolve them and check the file in before committing.
478 error_popup "Unknown file state [lindex $s 0] detected.
480 File $path cannot be committed by this program.
486 error_popup {No checked-in files to commit.
488 You must check-in at least 1 file before you can commit.
494 # -- A message is required.
496 set msg [string trim [$ui_comm get 1.0 end]]
498 error_popup {Please supply a commit message.
500 A good commit message has the following format:
502 - First line: Describe in one sentance what you did.
504 - Remaining lines: Describe why this change is good.
510 # -- Ask the pre-commit hook for the go-ahead.
512 set pchook [file join $gitdir hooks pre-commit]
513 if {$tcl_platform(platform) == {windows} && [file isfile $pchook]} {
514 set pchook [list sh -c \
515 "if test -x \"$pchook\"; then exec \"$pchook\"; fi"]
516 } elseif {[file executable $pchook]} {
517 set pchook [list $pchook]
521 if {$pchook != {} && [catch {eval exec $pchook} err]} {
522 hook_failed_popup pre-commit $err
527 # -- Write the tree in the background.
530 set ui_status_value {Committing changes...}
532 set fd_wt [open "| git write-tree" r]
533 fileevent $fd_wt readable [list commit_stage2 $fd_wt $curHEAD $msg]
536 proc commit_stage2 {fd_wt curHEAD msg} {
537 global single_commit gitdir PARENT commit_type
538 global commit_active ui_status_value ui_comm
543 if {$tree_id == {}} {
544 error_popup "write-tree failed"
546 set ui_status_value {Commit failed.}
551 # -- Create the commit.
553 set cmd [list git commit-tree $tree_id]
555 lappend cmd -p $PARENT
557 if {$commit_type == {merge}} {
559 set fd_mh [open [file join $gitdir MERGE_HEAD] r]
560 while {[gets $fd_mh merge_head] >= 0} {
561 lappend cmd -p $merge_head
565 error_popup "Loading MERGE_HEADs failed:\n$err"
567 set ui_status_value {Commit failed.}
573 # git commit-tree writes to stderr during initial commit.
574 lappend cmd 2>/dev/null
577 if {[catch {set cmt_id [eval exec $cmd]} err]} {
578 error_popup "commit-tree failed:\n$err"
580 set ui_status_value {Commit failed.}
585 # -- Update the HEAD ref.
588 if {$commit_type != {normal}} {
589 append reflogm " ($commit_type)"
591 set i [string first "\n" $msg]
593 append reflogm {: } [string range $msg 0 [expr $i - 1]]
595 append reflogm {: } $msg
597 set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD]
598 if {[catch {eval exec $cmd} err]} {
599 error_popup "update-ref failed:\n$err"
601 set ui_status_value {Commit failed.}
606 # -- Cleanup after ourselves.
608 catch {file delete [file join $gitdir MERGE_HEAD]}
609 catch {file delete [file join $gitdir MERGE_MSG]}
610 catch {file delete [file join $gitdir SQUASH_MSG]}
611 catch {file delete [file join $gitdir GITGUI_MSG]}
613 # -- Let rerere do its thing.
615 if {[file isdirectory [file join $gitdir rr-cache]]} {
616 catch {exec git rerere}
619 $ui_comm delete 0.0 end
620 $ui_comm edit modified false
622 if {$single_commit} do_quit
629 update_status "Changes committed as $cmt_id."
632 ######################################################################
636 proc fetch_from {remote} {
637 set w [new_console "fetch $remote" \
638 "Fetching new changes from $remote"]
639 set cmd [list git fetch]
644 proc pull_remote {remote branch} {
645 set w [new_console "pull $remote $branch" \
646 "Pulling new changes from branch $branch in $remote"]
647 set cmd [list git pull]
650 console_exec $w $cmd [list post_pull_remote $remote $branch]
653 proc post_pull_remote {remote branch success} {
655 update_status "Successfully pulled $branch from $remote."
657 update_status "Conflicts detected while pulling $branch from $remote."
661 proc push_to {remote} {
662 set w [new_console "push $remote" \
663 "Pushing changes to $remote"]
664 set cmd [list git push]
669 ######################################################################
673 proc mapcol {state path} {
674 global all_cols ui_other
676 if {[catch {set r $all_cols($state)}]} {
677 puts "error: no column for state={$state} $path"
683 proc mapicon {state path} {
686 if {[catch {set r $all_icons($state)}]} {
687 puts "error: no icon for state={$state} $path"
693 proc mapdesc {state path} {
696 if {[catch {set r $all_descs($state)}]} {
697 puts "error: no desc for state={$state} $path"
703 proc bsearch {w path} {
704 set hi [expr [lindex [split [$w index end] .] 0] - 2]
710 set mi [expr [expr $lo + $hi] / 2]
711 set ti [expr $mi + 1]
712 set cmp [string compare [$w get $ti.1 $ti.end] $path]
715 } elseif {$cmp == 0} {
721 return -[expr $lo + 1]
726 proc merge_state {path new_state} {
727 global file_states next_icon_id
729 set s0 [string index $new_state 0]
730 set s1 [string index $new_state 1]
732 if {[catch {set info $file_states($path)}]} {
734 set icon n[incr next_icon_id]
736 set state [lindex $info 0]
737 set icon [lindex $info 1]
741 set s0 [string index $state 0]
742 } elseif {$s0 == {*}} {
747 set s1 [string index $state 1]
748 } elseif {$s1 == {*}} {
752 set file_states($path) [list $s0$s1 $icon]
756 proc display_file {path state} {
757 global ui_index ui_other file_states status_active
759 set old_m [merge_state $path $state]
760 if {$status_active} return
762 set s $file_states($path)
763 set new_m [lindex $s 0]
764 set new_w [mapcol $new_m $path]
765 set old_w [mapcol $old_m $path]
766 set new_icon [mapicon $new_m $path]
768 if {$new_w != $old_w} {
769 set lno [bsearch $old_w $path]
772 $old_w conf -state normal
773 $old_w delete $lno.0 [expr $lno + 1].0
774 $old_w conf -state disabled
777 set lno [expr abs([bsearch $new_w $path] + 1) + 1]
778 $new_w conf -state normal
779 $new_w image create $lno.0 \
780 -align center -padx 5 -pady 1 \
781 -name [lindex $s 1] \
782 -image [mapicon $m $path]
783 $new_w insert $lno.1 "$path\n"
784 $new_w conf -state disabled
785 } elseif {$new_icon != [mapicon $old_m $path]} {
786 $new_w conf -state normal
787 $new_w image conf [lindex $s 1] -image $new_icon
788 $new_w conf -state disabled
792 proc display_all_files {} {
793 global ui_index ui_other file_states
795 $ui_index conf -state normal
796 $ui_other conf -state normal
798 foreach path [lsort [array names file_states]] {
799 set s $file_states($path)
801 set w [mapcol $m $path]
802 $w image create end \
803 -align center -padx 5 -pady 1 \
804 -name [lindex $s 1] \
805 -image [mapicon $m $path]
806 $w insert end "$path\n"
809 $ui_index conf -state disabled
810 $ui_other conf -state disabled
813 proc with_update_index {body} {
814 global update_index_fd
816 if {$update_index_fd == {}} {
817 if {![lock_index update]} return
818 set update_index_fd [open \
819 "| git update-index --add --remove -z --stdin" \
821 fconfigure $update_index_fd -translation binary
823 close $update_index_fd
824 set update_index_fd {}
831 proc update_index {path} {
832 global update_index_fd
834 if {$update_index_fd == {}} {
835 error {not in with_update_index}
837 puts -nonewline $update_index_fd "$path\0"
841 proc toggle_mode {path} {
842 global file_states ui_fname_value
844 set s $file_states($path)
857 with_update_index {update_index $path}
858 display_file $path $new
859 if {$ui_fname_value == $path} {
864 ######################################################################
866 ## config (fetch push pull)
868 proc load_repo_config {} {
870 global cfg_trust_mtime
872 array unset repo_config
874 set fd_rc [open "| git repo-config --list" r]
875 while {[gets $fd_rc line] >= 0} {
876 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
877 lappend repo_config($name) $value
883 if {[catch {set cfg_trust_mtime $repo_config(gui.trustmtime)}]} {
884 set cfg_trust_mtime false
888 proc save_my_config {} {
890 global cfg_trust_mtime
892 if {[catch {set rc_trustMTime $repo_config(gui.trustmtime)}]} {
893 set rc_trustMTime false
895 if {$cfg_trust_mtime != $rc_trustMTime} {
896 exec git repo-config gui.trustMTime $cfg_trust_mtime
900 proc load_all_remotes {} {
901 global gitdir all_remotes repo_config
903 set all_remotes [list]
904 set rm_dir [file join $gitdir remotes]
905 if {[file isdirectory $rm_dir]} {
906 set all_remotes [concat $all_remotes [glob \
910 -directory $rm_dir *]]
913 foreach line [array names repo_config remote.*.url] {
914 if {[regexp ^remote\.(.*)\.url\$ $line line name]} {
915 lappend all_remotes $name
919 set all_remotes [lsort -unique $all_remotes]
922 proc populate_remote_menu {m pfx op} {
923 global all_remotes mainfont
925 foreach remote $all_remotes {
926 $m add command -label "$pfx $remote..." \
927 -command [list $op $remote] \
932 proc populate_pull_menu {m} {
933 global gitdir repo_config all_remotes mainfont
935 foreach remote $all_remotes {
937 if {[array get repo_config remote.$remote.url] != {}} {
938 if {[array get repo_config remote.$remote.fetch] != {}} {
940 [lindex $repo_config(remote.$remote.fetch) 0] \
945 set fd [open [file join $gitdir remotes $remote] r]
946 while {[gets $fd line] >= 0} {
947 if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} {
956 regsub ^refs/heads/ $rb {} rb_short
957 if {$rb_short != {}} {
959 -label "Branch $rb_short from $remote..." \
960 -command [list pull_remote $remote $rb] \
966 ######################################################################
971 #define mask_width 14
972 #define mask_height 15
973 static unsigned char mask_bits[] = {
974 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
975 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
976 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
979 image create bitmap file_plain -background white -foreground black -data {
980 #define plain_width 14
981 #define plain_height 15
982 static unsigned char plain_bits[] = {
983 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
984 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
985 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
986 } -maskdata $filemask
988 image create bitmap file_mod -background white -foreground blue -data {
990 #define mod_height 15
991 static unsigned char mod_bits[] = {
992 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
993 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
994 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
995 } -maskdata $filemask
997 image create bitmap file_fulltick -background white -foreground "#007000" -data {
998 #define file_fulltick_width 14
999 #define file_fulltick_height 15
1000 static unsigned char file_fulltick_bits[] = {
1001 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1002 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1003 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1004 } -maskdata $filemask
1006 image create bitmap file_parttick -background white -foreground "#005050" -data {
1007 #define parttick_width 14
1008 #define parttick_height 15
1009 static unsigned char parttick_bits[] = {
1010 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1011 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1012 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1013 } -maskdata $filemask
1015 image create bitmap file_question -background white -foreground black -data {
1016 #define file_question_width 14
1017 #define file_question_height 15
1018 static unsigned char file_question_bits[] = {
1019 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1020 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1021 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1022 } -maskdata $filemask
1024 image create bitmap file_removed -background white -foreground red -data {
1025 #define file_removed_width 14
1026 #define file_removed_height 15
1027 static unsigned char file_removed_bits[] = {
1028 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1029 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1030 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1031 } -maskdata $filemask
1033 image create bitmap file_merge -background white -foreground blue -data {
1034 #define file_merge_width 14
1035 #define file_merge_height 15
1036 static unsigned char file_merge_bits[] = {
1037 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1038 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1039 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1040 } -maskdata $filemask
1042 set ui_index .vpane.files.index.list
1043 set ui_other .vpane.files.other.list
1044 set max_status_desc 0
1046 {__ i plain "Unmodified"}
1047 {_M i mod "Modified"}
1048 {M_ i fulltick "Checked in"}
1049 {MM i parttick "Partially checked in"}
1051 {_O o plain "Untracked"}
1052 {A_ o fulltick "Added"}
1053 {AM o parttick "Partially added"}
1054 {AD o question "Added (but now gone)"}
1056 {_D i question "Missing"}
1057 {D_ i removed "Removed"}
1058 {DD i removed "Removed"}
1059 {DO i removed "Removed (still exists)"}
1061 {UM i merge "Merge conflicts"}
1062 {U_ i merge "Merge conflicts"}
1064 if {$max_status_desc < [string length [lindex $i 3]]} {
1065 set max_status_desc [string length [lindex $i 3]]
1067 if {[lindex $i 1] == {i}} {
1068 set all_cols([lindex $i 0]) $ui_index
1070 set all_cols([lindex $i 0]) $ui_other
1072 set all_icons([lindex $i 0]) file_[lindex $i 2]
1073 set all_descs([lindex $i 0]) [lindex $i 3]
1077 ######################################################################
1081 proc error_popup {msg} {
1088 proc show_msg {w top msg} {
1089 global gitdir appname mainfont
1091 message $w.m -text $msg -justify left -aspect 400
1092 pack $w.m -side top -fill x -padx 5 -pady 10
1093 button $w.ok -text OK \
1096 -command "destroy $top"
1097 pack $w.ok -side bottom
1098 bind $top <Visibility> "grab $top; focus $top"
1099 bind $top <Key-Return> "destroy $top"
1100 wm title $w "$appname ([lindex [file split \
1101 [file normalize [file dirname $gitdir]]] \
1106 proc hook_failed_popup {hook msg} {
1107 global gitdir mainfont difffont appname
1114 label $w.m.l1 -text "$hook hook failed:" \
1117 -font [concat $mainfont bold]
1119 -background white -borderwidth 1 \
1121 -width 80 -height 10 \
1123 -yscrollcommand [list $w.m.sby set]
1125 -text {You must correct the above errors before committing.} \
1128 -font [concat $mainfont bold]
1129 scrollbar $w.m.sby -command [list $w.m.t yview]
1130 pack $w.m.l1 -side top -fill x
1131 pack $w.m.l2 -side bottom -fill x
1132 pack $w.m.sby -side right -fill y
1133 pack $w.m.t -side left -fill both -expand 1
1134 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1136 $w.m.t insert 1.0 $msg
1137 $w.m.t conf -state disabled
1139 button $w.ok -text OK \
1142 -command "destroy $w"
1143 pack $w.ok -side bottom
1145 bind $w <Visibility> "grab $w; focus $w"
1146 bind $w <Key-Return> "destroy $w"
1147 wm title $w "$appname ([lindex [file split \
1148 [file normalize [file dirname $gitdir]]] \
1153 set next_console_id 0
1155 proc new_console {short_title long_title} {
1156 global next_console_id console_data
1157 set w .console[incr next_console_id]
1158 set console_data($w) [list $short_title $long_title]
1159 return [console_init $w]
1162 proc console_init {w} {
1163 global console_cr console_data
1164 global gitdir appname mainfont difffont
1166 set console_cr($w) 1.0
1169 label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
1172 -font [concat $mainfont bold]
1174 -background white -borderwidth 1 \
1176 -width 80 -height 10 \
1179 -yscrollcommand [list $w.m.sby set]
1180 label $w.m.s -anchor w \
1182 -font [concat $mainfont bold]
1183 scrollbar $w.m.sby -command [list $w.m.t yview]
1184 pack $w.m.l1 -side top -fill x
1185 pack $w.m.s -side bottom -fill x
1186 pack $w.m.sby -side right -fill y
1187 pack $w.m.t -side left -fill both -expand 1
1188 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1190 button $w.ok -text {Running...} \
1194 -command "destroy $w"
1195 pack $w.ok -side bottom
1197 bind $w <Visibility> "focus $w"
1198 wm title $w "$appname ([lindex [file split \
1199 [file normalize [file dirname $gitdir]]] \
1200 end]): [lindex $console_data($w) 0]"
1204 proc console_exec {w cmd {after {}}} {
1207 # -- Windows tosses the enviroment when we exec our child.
1208 # But most users need that so we have to relogin. :-(
1210 if {$tcl_platform(platform) == {windows}} {
1211 set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
1214 # -- Tcl won't
let us redirect both stdout and stderr to
1215 # the same pipe. So pass it through cat...
1217 set cmd
[concat |
$cmd |
& cat]
1219 set fd_f
[open
$cmd r
]
1220 fconfigure
$fd_f -blocking 0 -translation binary
1221 fileevent
$fd_f readable
[list console_read
$w $fd_f $after]
1224 proc console_read
{w fd after
} {
1225 global console_cr console_data
1229 if {![winfo exists
$w]} {console_init
$w}
1230 $w.m.t conf
-state normal
1232 set n
[string length
$buf]
1234 set cr
[string first
"\r" $buf $c]
1235 set lf
[string first
"\n" $buf $c]
1236 if {$cr < 0} {set cr
[expr $n + 1]}
1237 if {$lf < 0} {set lf
[expr $n + 1]}
1240 $w.m.t insert end
[string range
$buf $c $lf]
1241 set console_cr
($w) [$w.m.t index
{end
-1c}]
1245 $w.m.t delete
$console_cr($w) end
1246 $w.m.t insert end
"\n"
1247 $w.m.t insert end
[string range
$buf $c $cr]
1252 $w.m.t conf
-state disabled
1256 fconfigure
$fd -blocking 1
1258 if {[catch
{close
$fd}]} {
1259 if {![winfo exists
$w]} {console_init
$w}
1260 $w.m.s conf
-background red
-text {Error
: Command Failed
}
1261 $w.ok conf
-text Close
1262 $w.ok conf
-state normal
1264 } elseif
{[winfo exists
$w]} {
1265 $w.m.s conf
-background green
-text {Success
}
1266 $w.ok conf
-text Close
1267 $w.ok conf
-state normal
1270 array
unset console_cr
$w
1271 array
unset console_data
$w
1273 uplevel
#0 $after $ok
1277 fconfigure
$fd -blocking 0
1280 ######################################################################
1284 set starting_gitk_msg
{Please
wait... Starting gitk...
}
1287 global tcl_platform ui_status_value starting_gitk_msg
1289 set ui_status_value
$starting_gitk_msg
1291 if {$ui_status_value == $starting_gitk_msg} {
1292 set ui_status_value
{Ready.
}
1296 if {$tcl_platform(platform
) == {windows
}} {
1304 set w
[new_console
"repack" "Repacking the object database"]
1305 set cmd
[list git repack
]
1308 console_exec
$w $cmd
1312 global gitdir ui_comm
1314 set save
[file join $gitdir GITGUI_MSG
]
1315 set msg
[string trim
[$ui_comm get
0.0 end
]]
1316 if {[$ui_comm edit modified
] && $msg != {}} {
1318 set fd
[open
$save w
]
1319 puts
$fd [string trim
[$ui_comm get
0.0 end
]]
1322 } elseif
{$msg == {} && [file exists
$save]} {
1334 proc do_checkin_all
{} {
1335 global checkin_active ui_status_value
1337 if {$checkin_active ||
![lock_index begin-update
]} return
1339 set checkin_active
1
1340 set ui_status_value
{Checking
in all files...
}
1343 foreach path
[array names file_states
] {
1344 set s
$file_states($path)
1350 _D
{toggle_mode
$path}
1354 set checkin_active
0
1355 set ui_status_value
{Ready.
}
1359 proc do_signoff
{} {
1363 set me
[exec git var GIT_COMMITTER_IDENT
]
1364 if {[regexp
{(.
*) [0-9]+ [-+0-9]+$
} $me me name
]} {
1365 set str
"Signed-off-by: $name"
1366 if {[$ui_comm get
{end
-1c linestart
} {end
-1c}] != $str} {
1367 $ui_comm insert end
"\n"
1368 $ui_comm insert end
$str
1375 proc do_amend_last
{} {
1383 # shift == 1: left click
1385 proc click
{w x y
shift wx wy
} {
1386 global ui_index ui_other
1388 set pos
[split [$w index @
$x,$y] .
]
1389 set lno
[lindex
$pos 0]
1390 set col [lindex
$pos 1]
1391 set path
[$w get
$lno.1 $lno.end
]
1392 if {$path == {}} return
1394 if {$col > 0 && $shift == 1} {
1395 $ui_index tag remove in_diff
0.0 end
1396 $ui_other tag remove in_diff
0.0 end
1397 $w tag add in_diff
$lno.0 [expr $lno + 1].0
1402 proc unclick
{w x y
} {
1403 set pos
[split [$w index @
$x,$y] .
]
1404 set lno
[lindex
$pos 0]
1405 set col [lindex
$pos 1]
1406 set path
[$w get
$lno.1 $lno.end
]
1407 if {$path == {}} return
1414 ######################################################################
1418 set mainfont
{Helvetica
10}
1419 set difffont
{Courier
10}
1420 set maincursor
[. cget
-cursor]
1422 switch
-glob -- "$tcl_platform(platform),$tcl_platform(os)" {
1423 windows
,* {set M1B Control
; set M1T Ctrl
}
1424 unix
,Darwin
{set M1B M1
; set M1T Cmd
}
1425 default
{set M1B M1
; set M1T M1
}
1429 menu .mbar
-tearoff 0
1430 .mbar add cascade
-label Project
-menu .mbar.project
1431 .mbar add cascade
-label Commit
-menu .mbar.commit
1432 .mbar add cascade
-label Fetch
-menu .mbar.fetch
1433 .mbar add cascade
-label Pull
-menu .mbar.pull
1434 .mbar add cascade
-label Push
-menu .mbar.push
1435 .mbar add cascade
-label Options
-menu .mbar.options
1436 . configure
-menu .mbar
1440 .mbar.project add
command -label Visualize \
1443 .mbar.project add
command -label {Repack Database
} \
1444 -command do_repack \
1446 .mbar.project add
command -label Quit \
1448 -accelerator $M1T-Q \
1453 .mbar.commit add
command -label Rescan \
1454 -command do_rescan \
1457 lappend disable_on_lock \
1458 [list .mbar.commit entryconf
[.mbar.commit index last
] -state]
1459 .mbar.commit add
command -label {Amend Last Commit
} \
1460 -command do_amend_last \
1462 lappend disable_on_lock \
1463 [list .mbar.commit entryconf
[.mbar.commit index last
] -state]
1464 .mbar.commit add
command -label {Check-in All Files
} \
1465 -command do_checkin_all \
1466 -accelerator $M1T-U \
1468 lappend disable_on_lock \
1469 [list .mbar.commit entryconf
[.mbar.commit index last
] -state]
1470 .mbar.commit add
command -label {Sign Off
} \
1471 -command do_signoff \
1472 -accelerator $M1T-S \
1474 .mbar.commit add
command -label Commit \
1475 -command do_commit \
1476 -accelerator $M1T-Return \
1478 lappend disable_on_lock \
1479 [list .mbar.commit entryconf
[.mbar.commit index last
] -state]
1492 .mbar.options add checkbutton
-label {Trust File Modification Timestamp
} \
1495 -variable cfg_trust_mtime
1497 # -- Main Window Layout
1498 panedwindow .vpane
-orient vertical
1499 panedwindow .vpane.files
-orient horizontal
1500 .vpane add .vpane.files
-sticky nsew
-height 100 -width 400
1501 pack .vpane
-anchor n
-side top
-fill both
-expand 1
1503 # -- Index File List
1504 frame .vpane.files.index
-height 100 -width 400
1505 label .vpane.files.index.title
-text {Modified Files
} \
1508 text
$ui_index -background white
-borderwidth 0 \
1509 -width 40 -height 10 \
1511 -yscrollcommand {.vpane.files.index.sb
set} \
1512 -cursor $maincursor \
1514 scrollbar .vpane.files.index.sb
-command [list
$ui_index yview
]
1515 pack .vpane.files.index.title
-side top
-fill x
1516 pack .vpane.files.index.sb
-side right
-fill y
1517 pack
$ui_index -side left
-fill both
-expand 1
1518 .vpane.files add .vpane.files.index
-sticky nsew
1520 # -- Other (Add) File List
1521 frame .vpane.files.other
-height 100 -width 100
1522 label .vpane.files.other.title
-text {Untracked Files
} \
1525 text
$ui_other -background white
-borderwidth 0 \
1526 -width 40 -height 10 \
1528 -yscrollcommand {.vpane.files.other.sb
set} \
1529 -cursor $maincursor \
1531 scrollbar .vpane.files.other.sb
-command [list
$ui_other yview
]
1532 pack .vpane.files.other.title
-side top
-fill x
1533 pack .vpane.files.other.sb
-side right
-fill y
1534 pack
$ui_other -side left
-fill both
-expand 1
1535 .vpane.files add .vpane.files.other
-sticky nsew
1537 $ui_index tag conf in_diff
-font [concat
$mainfont bold
]
1538 $ui_other tag conf in_diff
-font [concat
$mainfont bold
]
1540 # -- Diff and Commit Area
1541 frame .vpane.lower
-height 400 -width 400
1542 frame .vpane.lower.commarea
1543 frame .vpane.lower.
diff -relief sunken
-borderwidth 1
1544 pack .vpane.lower.commarea
-side top
-fill x
1545 pack .vpane.lower.
diff -side bottom
-fill both
-expand 1
1546 .vpane add .vpane.lower
-stick nsew
1548 # -- Commit Area Buttons
1549 frame .vpane.lower.commarea.buttons
1550 label .vpane.lower.commarea.buttons.l
-text {} \
1554 pack .vpane.lower.commarea.buttons.l
-side top
-fill x
1555 pack .vpane.lower.commarea.buttons
-side left
-fill y
1557 button .vpane.lower.commarea.buttons.rescan
-text {Rescan
} \
1558 -command do_rescan \
1560 pack .vpane.lower.commarea.buttons.rescan
-side top
-fill x
1561 lappend disable_on_lock
{.vpane.lower.commarea.buttons.rescan conf
-state}
1563 button .vpane.lower.commarea.buttons.amend
-text {Amend Last
} \
1564 -command do_amend_last \
1566 pack .vpane.lower.commarea.buttons.amend
-side top
-fill x
1567 lappend disable_on_lock
{.vpane.lower.commarea.buttons.amend conf
-state}
1569 button .vpane.lower.commarea.buttons.ciall
-text {Check-in All
} \
1570 -command do_checkin_all \
1572 pack .vpane.lower.commarea.buttons.ciall
-side top
-fill x
1573 lappend disable_on_lock
{.vpane.lower.commarea.buttons.ciall conf
-state}
1575 button .vpane.lower.commarea.buttons.signoff
-text {Sign Off
} \
1576 -command do_signoff \
1578 pack .vpane.lower.commarea.buttons.signoff
-side top
-fill x
1580 button .vpane.lower.commarea.buttons.commit
-text {Commit
} \
1581 -command do_commit \
1583 pack .vpane.lower.commarea.buttons.commit
-side top
-fill x
1584 lappend disable_on_lock
{.vpane.lower.commarea.buttons.commit conf
-state}
1586 # -- Commit Message Buffer
1587 frame .vpane.lower.commarea.buffer
1588 set ui_comm .vpane.lower.commarea.buffer.t
1589 set ui_coml .vpane.lower.commarea.buffer.l
1590 label
$ui_coml -text {Commit Message
:} \
1594 trace add variable commit_type
write {uplevel
#0 {
1595 switch
-glob $commit_type \
1596 initial
{$ui_coml conf
-text {Initial Commit Message
:}} \
1597 amend
{$ui_coml conf
-text {Amended Commit Message
:}} \
1598 merge
{$ui_coml conf
-text {Merge Commit Message
:}} \
1599 * {$ui_coml conf
-text {Commit Message
:}}
1601 text
$ui_comm -background white
-borderwidth 1 \
1603 -width 75 -height 9 -wrap none \
1605 -yscrollcommand {.vpane.lower.commarea.buffer.sby
set} \
1607 scrollbar .vpane.lower.commarea.buffer.sby
-command [list
$ui_comm yview
]
1608 pack
$ui_coml -side top
-fill x
1609 pack .vpane.lower.commarea.buffer.sby
-side right
-fill y
1610 pack
$ui_comm -side left
-fill y
1611 pack .vpane.lower.commarea.buffer
-side left
-fill y
1614 set ui_fname_value
{}
1615 set ui_fstatus_value
{}
1616 frame .vpane.lower.
diff.header
-background orange
1617 label .vpane.lower.
diff.header.l1
-text {File
:} \
1618 -background orange \
1620 label .vpane.lower.
diff.header.l2
-textvariable ui_fname_value \
1621 -background orange \
1625 label .vpane.lower.
diff.header.l3
-text {Status
:} \
1626 -background orange \
1628 label .vpane.lower.
diff.header.l4
-textvariable ui_fstatus_value \
1629 -background orange \
1630 -width $max_status_desc \
1634 pack .vpane.lower.
diff.header.l1
-side left
1635 pack .vpane.lower.
diff.header.l2
-side left
-fill x
1636 pack .vpane.lower.
diff.header.l4
-side right
1637 pack .vpane.lower.
diff.header.l3
-side right
1640 frame .vpane.lower.
diff.body
1641 set ui_diff .vpane.lower.
diff.body.t
1642 text
$ui_diff -background white
-borderwidth 0 \
1643 -width 80 -height 15 -wrap none \
1645 -xscrollcommand {.vpane.lower.
diff.body.sbx
set} \
1646 -yscrollcommand {.vpane.lower.
diff.body.sby
set} \
1647 -cursor $maincursor \
1649 scrollbar .vpane.lower.
diff.body.sbx
-orient horizontal \
1650 -command [list
$ui_diff xview
]
1651 scrollbar .vpane.lower.
diff.body.sby
-orient vertical \
1652 -command [list
$ui_diff yview
]
1653 pack .vpane.lower.
diff.body.sbx
-side bottom
-fill x
1654 pack .vpane.lower.
diff.body.sby
-side right
-fill y
1655 pack
$ui_diff -side left
-fill both
-expand 1
1656 pack .vpane.lower.
diff.header
-side top
-fill x
1657 pack .vpane.lower.
diff.body
-side bottom
-fill both
-expand 1
1659 $ui_diff tag conf dm
-foreground red
1660 $ui_diff tag conf dp
-foreground blue
1661 $ui_diff tag conf da
-font [concat
$difffont bold
]
1662 $ui_diff tag conf di
-foreground "#00a000"
1663 $ui_diff tag conf dni
-foreground "#a000a0"
1664 $ui_diff tag conf bold
-font [concat
$difffont bold
]
1667 set ui_status_value
{Initializing...
}
1668 label .status
-textvariable ui_status_value \
1674 pack .status
-anchor w
-side bottom
-fill x
1677 bind $ui_comm <$M1B-Key-Return> {do_commit
;break}
1678 bind .
<Destroy
> do_quit
1679 bind all
<Key-F5
> do_rescan
1680 bind all
<$M1B-Key-r> do_rescan
1681 bind all
<$M1B-Key-R> do_rescan
1682 bind .
<$M1B-Key-s> do_signoff
1683 bind .
<$M1B-Key-S> do_signoff
1684 bind .
<$M1B-Key-u> do_checkin_all
1685 bind .
<$M1B-Key-U> do_checkin_all
1686 bind .
<$M1B-Key-Return> do_commit
1687 bind all
<$M1B-Key-q> do_quit
1688 bind all
<$M1B-Key-Q> do_quit
1689 bind all
<$M1B-Key-w> {destroy
[winfo toplevel
%W
]}
1690 bind all
<$M1B-Key-W> {destroy
[winfo toplevel
%W
]}
1691 foreach i
[list
$ui_index $ui_other] {
1692 bind $i <Button-1
> {click
%W
%x
%y
1 %X
%Y
; break}
1693 bind $i <Button-3
> {click
%W
%x
%y
3 %X
%Y
; break}
1694 bind $i <ButtonRelease-1
> {unclick
%W
%x
%y
; break}
1698 ######################################################################
1702 set appname
[lindex
[file split $argv0] end
]
1705 if {[catch
{set cdup
[exec git rev-parse
--show-cdup]} err
]} {
1706 show_msg
{} .
"Cannot find the git directory: $err"
1714 if {[catch
{set gitdir
[exec git rev-parse
--git-dir]} err
]} {
1715 show_msg
{} .
"Cannot find the git directory: $err"
1719 if {$appname == {git-citool
}} {
1723 wm title .
"$appname ([file normalize [file dirname $gitdir]])"
1724 focus
-force $ui_comm
1727 populate_remote_menu .mbar.fetch From fetch_from
1728 populate_remote_menu .mbar.push To push_to
1729 populate_pull_menu .mbar.pull