git-gui: Finished commit implementation.
authorShawn O. Pearce <spearce@spearce.org>
Tue, 7 Nov 2006 01:50:59 +0000 (20:50 -0500)
committerShawn O. Pearce <spearce@spearce.org>
Tue, 7 Nov 2006 08:05:17 +0000 (03:05 -0500)
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>
git-citool [new symlink]
git-gui

diff --git a/git-citool b/git-citool
new file mode 120000 (symlink)
index 0000000..b5f620f
--- /dev/null
@@ -0,0 +1 @@
+git-gui
\ No newline at end of file
diff --git a/git-gui b/git-gui
index 0b941c3ffb8a320e0e8c86413d7a3463d71c51ba..0e9e519636530e4ae522333274e2ca5219880ef0 100755 (executable)
--- a/git-gui
+++ b/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