git-gui: Finished commit implementation.

We can now commit any type of commit (initial, normal or merge) using
the same techniques as git-commit.sh does for these types of things.

If invoked as git-citool we run exit immediately after the commit was
finished.  If invoked as git-gui then we stay running.

Also fixed a bug which caused the commit message buffer to be lost
when the application shutdown and restarted.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
This commit is contained in:
Shawn O. Pearce 2006-11-06 20:50:59 -05:00
Родитель 6e27d826c8
Коммит ec6b424abb
2 изменённых файлов: 238 добавлений и 102 удалений

1
git-citool Symbolic link
Просмотреть файл

@ -0,0 +1 @@
git-gui

339
git-gui
Просмотреть файл

@ -11,9 +11,11 @@ exec wish "$0" -- "$@"
##
## task management
set single_commit 0
set status_active 0
set diff_active 0
set checkin_active 0
set commit_active 0
set update_index_fd {}
set disable_on_lock [list]
@ -48,13 +50,27 @@ proc unlock_index {} {
##
## status
proc repository_state {hdvar ctvar} {
global gitdir
upvar $hdvar hd $ctvar ct
if {[catch {set hd [exec git rev-parse --verify HEAD]}]} {
set ct initial
} elseif {[file exists [file join $gitdir MERGE_HEAD]]} {
set ct merge
} else {
set ct normal
}
}
proc update_status {} {
global gitdir HEAD commit_type
global HEAD commit_type
global ui_index ui_other ui_status_value ui_comm
global status_active file_states
if {$status_active || ![lock_index read]} return
repository_state HEAD commit_type
array unset file_states
foreach w [list $ui_index $ui_other] {
$w conf -state normal
@ -62,12 +78,6 @@ proc update_status {} {
$w conf -state disabled
}
if {[catch {set HEAD [exec git rev-parse --verify HEAD]}]} {
set commit_type initial
} else {
set commit_type normal
}
if {![$ui_comm edit modified]
|| [string trim [$ui_comm get 0.0 end]] == {}} {
if {[load_message GITGUI_MSG]} {
@ -306,6 +316,207 @@ proc read_diff {fd} {
}
}
######################################################################
##
## commit
proc commit_tree {} {
global tcl_platform HEAD gitdir commit_type file_states
global commit_active ui_status_value
global ui_comm
if {$commit_active || ![lock_index update]} return
# -- Our in memory state should match the repository.
#
repository_state curHEAD cur_type
if {$commit_type != $cur_type || $HEAD != $curHEAD} {
error_popup {Last scanned state does not match repository state.
Its highly likely that another Git program modified the
repository since our last scan. A rescan is required
before committing.
}
unlock_index
update_status
return
}
# -- At least one file should differ in the index.
#
set files_ready 0
foreach path [array names file_states] {
set s $file_states($path)
switch -glob -- [lindex $s 0] {
_* {continue}
A* -
D* -
M* {set files_ready 1; break}
U* {
error_popup "Unmerged files cannot be committed.
File $path has merge conflicts.
You must resolve them and check the file in before committing.
"
unlock_index
return
}
default {
error_popup "Unknown file state [lindex $s 0] detected.
File $path cannot be committed by this program.
"
}
}
}
if {!$files_ready} {
error_popup {No checked-in files to commit.
You must check-in at least 1 file before you can commit.
}
unlock_index
return
}
# -- A message is required.
#
set msg [string trim [$ui_comm get 1.0 end]]
if {$msg == {}} {
error_popup {Please supply a commit message.
A good commit message has the following format:
- First line: Describe in one sentance what you did.
- Second line: Blank
- Remaining lines: Describe why this change is good.
}
unlock_index
return
}
# -- Ask the pre-commit hook for the go-ahead.
#
set pchook [file join $gitdir hooks pre-commit]
if {$tcl_platform(platform) == {windows} && [file exists $pchook]} {
set pchook [list sh -c \
"if test -x \"$pchook\"; then exec \"$pchook\"; fi"]
} elseif {[file executable $pchook]} {
set pchook [list $pchook]
} else {
set pchook {}
}
if {$pchook != {} && [catch {eval exec $pchook} err]} {
hook_failed_popup pre-commit $err
unlock_index
return
}
# -- Write the tree in the background.
#
set commit_active 1
set ui_status_value {Committing changes...}
set fd_wt [open "| git write-tree" r]
fileevent $fd_wt readable \
[list commit_stage2 $fd_wt $curHEAD $msg]
}
proc commit_stage2 {fd_wt curHEAD msg} {
global single_commit gitdir HEAD commit_type
global commit_active ui_status_value comm_ui
gets $fd_wt tree_id
close $fd_wt
if {$tree_id == {}} {
error_popup "write-tree failed"
set commit_active 0
set ui_status_value {Commit failed.}
unlock_index
return
}
# -- Create the commit.
#
set cmd [list git commit-tree $tree_id]
if {$commit_type != {initial}} {
lappend cmd -p $HEAD
}
if {$commit_type == {merge}} {
if {[catch {
set fd_mh [open [file join $gitdir MERGE_HEAD] r]
while {[gets $fd_mh merge_head] > 0} {
lappend -p $merge_head
}
close $fd_mh
} err]} {
error_popup "Loading MERGE_HEADs failed:\n$err"
set commit_active 0
set ui_status_value {Commit failed.}
unlock_index
return
}
}
if {$commit_type == {initial}} {
# git commit-tree writes to stderr during initial commit.
lappend cmd 2>/dev/null
}
lappend cmd << $msg
if {[catch {set cmt_id [eval exec $cmd]} err]} {
error_popup "commit-tree failed:\n$err"
set commit_active 0
set ui_status_value {Commit failed.}
unlock_index
return
}
# -- Update the HEAD ref.
#
set reflogm commit
if {$commit_type != {normal}} {
append reflogm " ($commit_type)"
}
set i [string first "\n" $msg]
if {$i >= 0} {
append reflogm {: } [string range $msg 0 [expr $i - 1]]
} else {
append reflogm {: } $msg
}
set cmd [list git update-ref \
-m $reflogm \
HEAD $cmt_id $curHEAD]
if {[catch {eval exec $cmd} err]} {
error_popup "update-ref failed:\n$err"
set commit_active 0
set ui_status_value {Commit failed.}
unlock_index
return
}
# -- Cleanup after ourselves.
#
catch {file delete [file join $gitdir MERGE_HEAD]}
catch {file delete [file join $gitdir MERGE_MSG]}
catch {file delete [file join $gitdir SQUASH_MSG]}
catch {file delete [file join $gitdir GITGUI_MSG]}
# -- Let rerere do its thing.
#
if {[file isdirectory [file join $gitdir rr-cache]]} {
catch {exec git rerere}
}
$comm_ui delete 0.0 end
$comm_ui edit modified false
if {$single_commit} do_quit
set commit_active 0
set ui_status_value "Changes committed as $cmt_id."
unlock_index
update_status
}
######################################################################
##
## ui helpers
@ -599,20 +810,22 @@ proc error_popup {msg} {
}
proc show_msg {w top msg} {
global gitdir
global gitdir appname
message $w.m -text $msg -justify left -aspect 400
pack $w.m -side top -fill x -padx 20 -pady 20
button $w.ok -text OK -command "destroy $top"
pack $w.m -side top -fill x -padx 5 -pady 10
button $w.ok -text OK \
-width 15 \
-command "destroy $top"
pack $w.ok -side bottom
bind $top <Visibility> "grab $top; focus $top"
bind $top <Key-Return> "destroy $top"
wm title $top "error: git-ui ([file normalize [file dirname $gitdir]])"
wm title $top "error: $appname ([file normalize [file dirname $gitdir]])"
tkwait window $top
}
proc hook_failed_popup {hook msg} {
global gitdir mainfont difffont
global gitdir mainfont difffont appname
set w .hookfail
toplevel $w
@ -651,7 +864,7 @@ proc hook_failed_popup {hook msg} {
bind $w <Visibility> "grab $w; focus $w"
bind $w <Key-Return> "destroy $w"
wm title $w "error: git-ui ([file normalize [file dirname $gitdir]])"
wm title $w "error: $appname ([file normalize [file dirname $gitdir]])"
tkwait window $w
}
@ -681,14 +894,14 @@ proc do_quit {} {
global gitdir ui_comm
set save [file join $gitdir GITGUI_MSG]
if {[$ui_comm edit modified]
&& [string trim [$ui_comm get 0.0 end]] != {}} {
set msg [string trim [$ui_comm get 0.0 end]]
if {[$ui_comm edit modified] && $msg != {}} {
catch {
set fd [open $save w]
puts $fd [string trim [$ui_comm get 0.0 end]]
close $fd
}
} elseif {[file exists $save]} {
} elseif {$msg == {} && [file exists $save]} {
file delete $save
}
@ -741,91 +954,7 @@ proc do_signoff {} {
}
proc do_commit {} {
global tcl_platform HEAD gitdir commit_type file_states
global ui_comm
# -- Our in memory state should match the repository.
#
if {[catch {set curHEAD [exec git rev-parse --verify HEAD]}]} {
set cur_type initial
} else {
set cur_type normal
}
if {$commit_type != $commit_type || $HEAD != $curHEAD} {
error_popup {Last scanned state does not match repository state.
Its highly likely that another Git program modified the
repository since our last scan. A rescan is required
before committing.
}
update_status
return
}
# -- At least one file should differ in the index.
#
set files_ready 0
foreach path [array names file_states] {
set s $file_states($path)
switch -glob -- [lindex $s 0] {
_* {continue}
A* -
D* -
M* {set files_ready 1; break}
U* {
error_popup "Unmerged files cannot be committed.
File $path has merge conflicts.
You must resolve them and check the file in before committing.
"
return
}
default {
error_popup "Unknown file state [lindex $s 0] detected.
File $path cannot be committed by this program.
"
}
}
}
if {!$files_ready} {
error_popup {No checked-in files to commit.
You must check-in at least 1 file before you can commit.
}
return
}
# -- A message is required.
#
set msg [string trim [$ui_comm get 1.0 end]]
if {$msg == {}} {
error_popup {Please supply a commit message.
A good commit message has the following format:
- First line: Describe in one sentance what you did.
- Second line: Blank
- Remaining lines: Describe why this change is good.
}
return
}
# -- Ask the pre-commit hook for the go-ahead.
#
set pchook [file join $gitdir hooks pre-commit]
if {$tcl_platform(platform) == {windows} && [file exists $pchook]} {
set pchook [list sh -c \
"if test -x \"$pchook\"; then exec \"$pchook\"; fi"]
} elseif {[file executable $pchook]} {
set pchook [list $pchook]
} else {
set pchook {}
}
if {$pchook != {} && [catch {eval exec $pchook} err]} {
hook_failed_popup pre-commit $err
return
}
commit_tree
}
# shift == 1: left click
@ -1081,6 +1210,7 @@ label .status -textvariable ui_status_value \
pack .status -anchor w -side bottom -fill x
# -- Key Bindings
bind $ui_comm <$M1B-Key-Return> {do_commit;break}
bind . <Destroy> do_quit
bind . <Key-F5> do_rescan
bind . <$M1B-Key-r> do_rescan
@ -1108,6 +1238,11 @@ if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} {
exit 1
}
wm title . "git-ui ([file normalize [file dirname $gitdir]])"
set appname [lindex [file split $argv0] end]
if {$appname == {git-citool}} {
set single_commit 1
}
wm title . "$appname ([file normalize [file dirname $gitdir]])"
focus -force $ui_comm
update_status