1 # git-gui misc. commit reading/writing support
2 # Copyright (C) 2006, 2007 Shawn Pearce
4 proc load_last_commit {} {
5 global HEAD PARENT MERGE_HEAD commit_type ui_comm
8 if {[llength $PARENT] == 0} {
9 error_popup [mc "There is nothing to amend.
11 You are about to create the initial commit. There is no commit before this to amend.
16 repository_state curType curHEAD curMERGE_HEAD
17 if {$curType eq {merge}} {
18 error_popup [mc "Cannot amend while merging.
20 You are currently in the middle of a merge that has not been fully completed. You cannot amend the prior commit unless you first abort the current merge activity.
28 set fd [git_read cat-file commit $curHEAD]
29 fconfigure $fd -encoding binary -translation lf
30 # By default commits are assumed to be in utf-8
32 while {[gets $fd line] > 0} {
33 if {[string match {parent *} $line]} {
34 lappend parents [string range $line 7 end]
35 } elseif {[string match {encoding *} $line]} {
36 set enc [string tolower [string range $line 9 end]]
42 set enc [tcl_encoding $enc]
44 set msg [encoding convertfrom $enc $msg]
46 set msg [string trim $msg]
48 error_popup [strcat [mc "Error loading commit data for amend:"] "\n\n$err"]
55 switch -- [llength $parents] {
56 0 {set commit_type amend-initial}
57 1 {set commit_type amend}
58 default {set commit_type amend-merge}
61 $ui_comm delete 0.0 end
62 $ui_comm insert end $msg
64 $ui_comm edit modified false
68 set GIT_COMMITTER_IDENT {}
70 proc committer_ident {} {
71 global GIT_COMMITTER_IDENT
73 if {$GIT_COMMITTER_IDENT eq {}} {
74 if {[catch {set me [git var GIT_COMMITTER_IDENT]} err]} {
75 error_popup [strcat [mc "Unable to obtain your identity:"] "\n\n$err"]
78 if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
79 $me me GIT_COMMITTER_IDENT]} {
80 error_popup [strcat [mc "Invalid GIT_COMMITTER_IDENT:"] "\n\n$me"]
85 return $GIT_COMMITTER_IDENT
91 set me [committer_ident]
94 set sob "Signed-off-by: $me"
95 set last [$ui_comm get {end -1c linestart} {end -1c}]
97 $ui_comm edit separator
99 && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
100 $ui_comm insert end "\n"
102 $ui_comm insert end "\n$sob"
103 $ui_comm edit separator
108 proc create_new_commit {} {
109 global commit_type ui_comm
111 set commit_type normal
112 $ui_comm delete 0.0 end
114 $ui_comm edit modified false
118 proc setup_commit_encoding {msg_wt {quiet 0}} {
121 if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
124 set use_enc [tcl_encoding $enc]
125 if {$use_enc ne {}} {
126 fconfigure $msg_wt -encoding $use_enc
129 error_popup [mc "warning: Tcl does not support encoding '%s'." $enc]
131 fconfigure $msg_wt -encoding utf-8
135 proc commit_tree {} {
136 global HEAD commit_type file_states ui_comm repo_config
139 if {[committer_ident] eq {}} return
140 if {![lock_index update]} return
142 # -- Our in memory state should match the repository.
144 repository_state curType curHEAD curMERGE_HEAD
145 if {[string match amend* $commit_type]
146 && $curType eq {normal}
147 && $curHEAD eq $HEAD} {
148 } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
149 info_popup [mc "Last scanned state does not match repository state.
151 Another Git program has modified this repository since the last scan. A rescan must be performed before another commit can be created.
153 The rescan will be automatically started now.
160 # -- At least one file should differ in the index.
163 foreach path [array names file_states] {
164 set s $file_states($path)
165 switch -glob -- [lindex $s 0] {
170 M? {set files_ready 1}
173 error_popup [mc "Unmerged files cannot be committed.
175 File %s has merge conflicts. You must resolve them and stage the file before committing.
176 " [short_path $path]]
181 error_popup [mc "Unknown file state %s detected.
183 File %s cannot be committed by this program.
184 " [lindex $s 0] [short_path $path]]
188 if {!$files_ready && ![string match *merge $curType] && ![is_enabled nocommit]} {
189 info_popup [mc "No changes to commit.
191 You must stage at least 1 file before you can commit.
197 if {[is_enabled nocommitmsg]} { do_quit 0 }
199 # -- A message is required.
201 set msg [string trim [$ui_comm get 1.0 end]]
202 regsub -all -line {[ \t\r]+$} $msg {} msg
204 error_popup [mc "Please supply a commit message.
206 A good commit message has the following format:
208 - First line: Describe in one sentence what you did.
210 - Remaining lines: Describe why this change is good.
216 # -- Build the message file.
218 set msg_p [gitdir GITGUI_EDITMSG]
219 set msg_wt [open $msg_p w]
220 fconfigure $msg_wt -translation lf
221 setup_commit_encoding $msg_wt
225 if {[is_enabled nocommit]} { do_quit 0 }
227 # -- Run the pre-commit hook.
229 set fd_ph [githook_read pre-commit]
231 commit_commitmsg $curHEAD $msg_p
235 ui_status [mc "Calling pre-commit hook..."]
237 fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
238 fileevent $fd_ph readable \
239 [list commit_prehook_wait $fd_ph $curHEAD $msg_p]
242 proc commit_prehook_wait {fd_ph curHEAD msg_p} {
245 append pch_error [read $fd_ph]
246 fconfigure $fd_ph -blocking 1
248 if {[catch {close $fd_ph}]} {
249 catch {file delete $msg_p}
250 ui_status [mc "Commit declined by pre-commit hook."]
251 hook_failed_popup pre-commit $pch_error
254 commit_commitmsg $curHEAD $msg_p
259 fconfigure $fd_ph -blocking 0
262 proc commit_commitmsg {curHEAD msg_p} {
263 global is_detached repo_config
267 && ![file exists [gitdir rebase-merge head-name]]
268 && [is_config_true gui.warndetachedcommit]} {
269 set msg [mc "You are about to commit on a detached head.\
270 This is a potentially dangerous thing to do because if you switch\
271 to another branch you will lose your changes and it can be difficult\
272 to retrieve them later from the reflog. You should probably cancel this\
273 commit and create a new branch to continue.\n\
275 Do you really want to proceed with your Commit?"]
276 if {[ask_popup $msg] ne yes} {
282 # -- Run the commit-msg hook.
284 set fd_ph [githook_read commit-msg $msg_p]
286 commit_writetree $curHEAD $msg_p
290 ui_status [mc "Calling commit-msg hook..."]
292 fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
293 fileevent $fd_ph readable \
294 [list commit_commitmsg_wait $fd_ph $curHEAD $msg_p]
297 proc commit_commitmsg_wait {fd_ph curHEAD msg_p} {
300 append pch_error [read $fd_ph]
301 fconfigure $fd_ph -blocking 1
303 if {[catch {close $fd_ph}]} {
304 catch {file delete $msg_p}
305 ui_status [mc "Commit declined by commit-msg hook."]
306 hook_failed_popup commit-msg $pch_error
309 commit_writetree $curHEAD $msg_p
314 fconfigure $fd_ph -blocking 0
317 proc commit_writetree {curHEAD msg_p} {
318 ui_status [mc "Committing changes..."]
319 set fd_wt [git_read write-tree]
320 fileevent $fd_wt readable \
321 [list commit_committree $fd_wt $curHEAD $msg_p]
324 proc commit_committree {fd_wt curHEAD msg_p} {
325 global HEAD PARENT MERGE_HEAD commit_type
326 global current_branch
327 global ui_comm selected_commit_type
328 global file_states selected_paths rescan_active
332 if {[catch {close $fd_wt} err]} {
333 catch {file delete $msg_p}
334 error_popup [strcat [mc "write-tree failed:"] "\n\n$err"]
335 ui_status [mc "Commit failed."]
340 # -- Verify this wasn't an empty change.
342 if {$commit_type eq {normal}} {
343 set fd_ot [git_read cat-file commit $PARENT]
344 fconfigure $fd_ot -encoding binary -translation lf
345 set old_tree [gets $fd_ot]
348 if {[string equal -length 5 {tree } $old_tree]
349 && [string length $old_tree] == 45} {
350 set old_tree [string range $old_tree 5 end]
352 error [mc "Commit %s appears to be corrupt" $PARENT]
355 if {$tree_id eq $old_tree} {
356 catch {file delete $msg_p}
357 info_popup [mc "No changes to commit.
359 No files were modified by this commit and it was not a merge commit.
361 A rescan will be automatically started now.
364 rescan {ui_status [mc "No changes to commit."]}
369 # -- Create the commit.
371 set cmd [list commit-tree $tree_id]
372 foreach p [concat $PARENT $MERGE_HEAD] {
376 if {[catch {set cmt_id [eval git $cmd]} err]} {
377 catch {file delete $msg_p}
378 error_popup [strcat [mc "commit-tree failed:"] "\n\n$err"]
379 ui_status [mc "Commit failed."]
384 # -- Update the HEAD ref.
387 if {$commit_type ne {normal}} {
388 append reflogm " ($commit_type)"
390 set msg_fd [open $msg_p r]
391 setup_commit_encoding $msg_fd 1
394 append reflogm {: } $subject
396 git update-ref -m $reflogm HEAD $cmt_id $curHEAD
398 catch {file delete $msg_p}
399 error_popup [strcat [mc "update-ref failed:"] "\n\n$err"]
400 ui_status [mc "Commit failed."]
405 # -- Cleanup after ourselves.
407 catch {file delete $msg_p}
408 catch {file delete [gitdir MERGE_HEAD]}
409 catch {file delete [gitdir MERGE_MSG]}
410 catch {file delete [gitdir SQUASH_MSG]}
411 catch {file delete [gitdir GITGUI_MSG]}
412 catch {file delete [gitdir CHERRY_PICK_HEAD]}
414 # -- Let rerere do its thing.
416 if {[get_config rerere.enabled] eq {}} {
417 set rerere [file isdirectory [gitdir rr-cache]]
419 set rerere [is_config_true rerere.enabled]
425 # -- Run the post-commit hook.
427 set fd_ph [githook_read post-commit]
431 fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
432 fileevent $fd_ph readable \
433 [list commit_postcommit_wait $fd_ph $cmt_id]
436 $ui_comm delete 0.0 end
438 $ui_comm edit modified false
439 if {$::GITGUI_BCK_exists} {
440 catch {file delete [gitdir GITGUI_BCK]}
441 set ::GITGUI_BCK_exists 0
444 if {[is_enabled singlecommit]} { do_quit 0 }
446 # -- Update in memory status
448 set selected_commit_type new
449 set commit_type normal
452 set MERGE_HEAD [list]
454 foreach path [array names file_states] {
455 set s $file_states($path)
466 unset file_states($path)
467 catch {unset selected_paths($path)}
470 set file_states($path) [list _O [lindex $s 1] {} {}]
480 set file_states($path) [list \
481 _[string index $m 1] \
492 ui_status [mc "Created commit %s: %s" [string range $cmt_id 0 7] $subject]
495 proc commit_postcommit_wait {fd_ph cmt_id} {
498 append pch_error [read $fd_ph]
499 fconfigure $fd_ph -blocking 1
501 if {[catch {close $fd_ph}]} {
502 hook_failed_popup post-commit $pch_error 0
507 fconfigure $fd_ph -blocking 0