1 # git-gui misc. commit reading/writing support
2 # Copyright (C) 2006, 2007 Shawn Pearce
8 proc load_last_commit {} {
9 global HEAD PARENT MERGE_HEAD commit_type ui_comm
10 global author_name author_email author_date
13 if {[llength $PARENT] == 0} {
14 error_popup [mc "There is nothing to amend.
16 You are about to create the initial commit. There is no commit before this to amend.
21 repository_state curType curHEAD curMERGE_HEAD
22 if {$curType eq {merge}} {
23 error_popup [mc "Cannot amend while merging.
25 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.
33 set fd [git_read cat-file commit $curHEAD]
34 fconfigure $fd -encoding binary -translation lf
35 # By default commits are assumed to be in utf-8
37 while {[gets $fd line] > 0} {
38 if {[string match {parent *} $line]} {
39 lappend parents [string range $line 7 end]
40 } elseif {[string match {encoding *} $line]} {
41 set enc [string tolower [string range $line 9 end]]
42 } elseif {[regexp "author (.*)\\s<(.*)>\\s(\\d.*$)" $line all name email time]} {
44 set author_email $email
51 set enc [tcl_encoding $enc]
53 set msg [encoding convertfrom $enc $msg]
55 set msg [string trim $msg]
57 error_popup [strcat [mc "Error loading commit data for amend:"] "\n\n$err"]
64 switch -- [llength $parents] {
65 0 {set commit_type amend-initial}
66 1 {set commit_type amend}
67 default {set commit_type amend-merge}
70 $ui_comm delete 0.0 end
71 $ui_comm insert end $msg
73 $ui_comm edit modified false
77 set GIT_COMMITTER_IDENT {}
79 proc committer_ident {} {
80 global GIT_COMMITTER_IDENT
82 if {$GIT_COMMITTER_IDENT eq {}} {
83 if {[catch {set me [git var GIT_COMMITTER_IDENT]} err]} {
84 error_popup [strcat [mc "Unable to obtain your identity:"] "\n\n$err"]
87 if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
88 $me me GIT_COMMITTER_IDENT]} {
89 error_popup [strcat [mc "Invalid GIT_COMMITTER_IDENT:"] "\n\n$me"]
94 return $GIT_COMMITTER_IDENT
100 set me [committer_ident]
101 if {$me eq {}} return
103 set sob "Signed-off-by: $me"
104 set last [$ui_comm get {end -1c linestart} {end -1c}]
106 $ui_comm edit separator
108 && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
109 $ui_comm insert end "\n"
111 $ui_comm insert end "\n$sob"
112 $ui_comm edit separator
117 proc create_new_commit {} {
118 global commit_type ui_comm
119 global author_name author_email author_date
121 set commit_type normal
125 $ui_comm delete 0.0 end
127 $ui_comm edit modified false
131 proc setup_commit_encoding {msg_wt {quiet 0}} {
134 if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
137 set use_enc [tcl_encoding $enc]
138 if {$use_enc ne {}} {
139 fconfigure $msg_wt -encoding $use_enc
142 error_popup [mc "warning: Tcl does not support encoding '%s'." $enc]
144 fconfigure $msg_wt -encoding utf-8
148 proc commit_tree {} {
149 global HEAD commit_type file_states ui_comm repo_config
152 if {[committer_ident] eq {}} return
153 if {![lock_index update]} return
155 # -- Our in memory state should match the repository.
157 repository_state curType curHEAD curMERGE_HEAD
158 if {[string match amend* $commit_type]
159 && $curType eq {normal}
160 && $curHEAD eq $HEAD} {
161 } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
162 info_popup [mc "Last scanned state does not match repository state.
164 Another Git program has modified this repository since the last scan. A rescan must be performed before another commit can be created.
166 The rescan will be automatically started now.
173 # -- At least one file should differ in the index.
176 foreach path [array names file_states] {
177 set s $file_states($path)
178 switch -glob -- [lindex $s 0] {
183 M? {set files_ready 1}
186 error_popup [mc "Unmerged files cannot be committed.
188 File %s has merge conflicts. You must resolve them and stage the file before committing.
189 " [short_path $path]]
194 error_popup [mc "Unknown file state %s detected.
196 File %s cannot be committed by this program.
197 " [lindex $s 0] [short_path $path]]
201 if {!$files_ready && ![string match *merge $curType] && ![is_enabled nocommit]} {
202 info_popup [mc "No changes to commit.
204 You must stage at least 1 file before you can commit.
210 if {[is_enabled nocommitmsg]} { do_quit 0 }
212 # -- A message is required.
214 set msg [string trim [$ui_comm get 1.0 end]]
215 regsub -all -line {[ \t\r]+$} $msg {} msg
217 error_popup [mc "Please supply a commit message.
219 A good commit message has the following format:
221 - First line: Describe in one sentence what you did.
223 - Remaining lines: Describe why this change is good.
229 # -- Build the message file.
231 set msg_p [gitdir GITGUI_EDITMSG]
232 set msg_wt [open $msg_p w]
233 fconfigure $msg_wt -translation lf
234 setup_commit_encoding $msg_wt
238 if {[is_enabled nocommit]} { do_quit 0 }
240 # -- Run the pre-commit hook.
242 set fd_ph [githook_read pre-commit]
244 commit_commitmsg $curHEAD $msg_p
248 ui_status [mc "Calling pre-commit hook..."]
250 fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
251 fileevent $fd_ph readable \
252 [list commit_prehook_wait $fd_ph $curHEAD $msg_p]
255 proc commit_prehook_wait {fd_ph curHEAD msg_p} {
258 append pch_error [read $fd_ph]
259 fconfigure $fd_ph -blocking 1
261 if {[catch {close $fd_ph}]} {
262 catch {file delete $msg_p}
263 ui_status [mc "Commit declined by pre-commit hook."]
264 hook_failed_popup pre-commit $pch_error
267 commit_commitmsg $curHEAD $msg_p
272 fconfigure $fd_ph -blocking 0
275 proc commit_commitmsg {curHEAD msg_p} {
276 global is_detached repo_config
280 && ![file exists [gitdir rebase-merge head-name]]
281 && [is_config_true gui.warndetachedcommit]} {
282 set msg [mc "You are about to commit on a detached head.\
283 This is a potentially dangerous thing to do because if you switch\
284 to another branch you will lose your changes and it can be difficult\
285 to retrieve them later from the reflog. You should probably cancel this\
286 commit and create a new branch to continue.\n\
288 Do you really want to proceed with your Commit?"]
289 if {[ask_popup $msg] ne yes} {
295 # -- Run the commit-msg hook.
297 set fd_ph [githook_read commit-msg $msg_p]
299 commit_writetree $curHEAD $msg_p
303 ui_status [mc "Calling commit-msg hook..."]
305 fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
306 fileevent $fd_ph readable \
307 [list commit_commitmsg_wait $fd_ph $curHEAD $msg_p]
310 proc commit_commitmsg_wait {fd_ph curHEAD msg_p} {
313 append pch_error [read $fd_ph]
314 fconfigure $fd_ph -blocking 1
316 if {[catch {close $fd_ph}]} {
317 catch {file delete $msg_p}
318 ui_status [mc "Commit declined by commit-msg hook."]
319 hook_failed_popup commit-msg $pch_error
322 commit_writetree $curHEAD $msg_p
327 fconfigure $fd_ph -blocking 0
330 proc commit_writetree {curHEAD msg_p} {
331 ui_status [mc "Committing changes..."]
332 set fd_wt [git_read write-tree]
333 fileevent $fd_wt readable \
334 [list commit_committree $fd_wt $curHEAD $msg_p]
337 proc commit_committree {fd_wt curHEAD msg_p} {
338 global HEAD PARENT MERGE_HEAD commit_type
339 global current_branch
340 global ui_comm selected_commit_type
341 global file_states selected_paths rescan_active
343 global env author_name author_email author_date
346 if {[catch {close $fd_wt} err]} {
347 catch {file delete $msg_p}
348 error_popup [strcat [mc "write-tree failed:"] "\n\n$err"]
349 ui_status [mc "Commit failed."]
354 # -- Verify this wasn't an empty change.
356 if {$commit_type eq {normal}} {
357 set fd_ot [git_read cat-file commit $PARENT]
358 fconfigure $fd_ot -encoding binary -translation lf
359 set old_tree [gets $fd_ot]
362 if {[string equal -length 5 {tree } $old_tree]
363 && [string length $old_tree] == 45} {
364 set old_tree [string range $old_tree 5 end]
366 error [mc "Commit %s appears to be corrupt" $PARENT]
369 if {$tree_id eq $old_tree} {
370 catch {file delete $msg_p}
371 info_popup [mc "No changes to commit.
373 No files were modified by this commit and it was not a merge commit.
375 A rescan will be automatically started now.
378 rescan {ui_status [mc "No changes to commit."]}
383 if {$author_name ne ""} {
384 set env(GIT_AUTHOR_NAME) $author_name
385 set env(GIT_AUTHOR_EMAIL) $author_email
386 set env(GIT_AUTHOR_DATE) $author_date
388 # -- Create the commit.
390 set cmd [list commit-tree $tree_id]
391 foreach p [concat $PARENT $MERGE_HEAD] {
395 if {[catch {set cmt_id [eval git $cmd]} err]} {
396 catch {file delete $msg_p}
397 error_popup [strcat [mc "commit-tree failed:"] "\n\n$err"]
398 ui_status [mc "Commit failed."]
403 # -- Update the HEAD ref.
406 if {$commit_type ne {normal}} {
407 append reflogm " ($commit_type)"
409 set msg_fd [open $msg_p r]
410 setup_commit_encoding $msg_fd 1
413 append reflogm {: } $subject
415 git update-ref -m $reflogm HEAD $cmt_id $curHEAD
417 catch {file delete $msg_p}
418 error_popup [strcat [mc "update-ref failed:"] "\n\n$err"]
419 ui_status [mc "Commit failed."]
424 # -- Cleanup after ourselves.
426 catch {file delete $msg_p}
427 catch {file delete [gitdir MERGE_HEAD]}
428 catch {file delete [gitdir MERGE_MSG]}
429 catch {file delete [gitdir SQUASH_MSG]}
430 catch {file delete [gitdir GITGUI_MSG]}
431 catch {file delete [gitdir CHERRY_PICK_HEAD]}
433 # -- Let rerere do its thing.
435 if {[get_config rerere.enabled] eq {}} {
436 set rerere [file isdirectory [gitdir rr-cache]]
438 set rerere [is_config_true rerere.enabled]
444 # -- Run the post-commit hook.
446 set fd_ph [githook_read post-commit]
450 fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
451 fileevent $fd_ph readable \
452 [list commit_postcommit_wait $fd_ph $cmt_id]
455 $ui_comm delete 0.0 end
457 $ui_comm edit modified false
458 if {$::GITGUI_BCK_exists} {
459 catch {file delete [gitdir GITGUI_BCK]}
460 set ::GITGUI_BCK_exists 0
463 if {[is_enabled singlecommit]} { do_quit 0 }
465 # -- Update in memory status
467 set selected_commit_type new
468 set commit_type normal
471 set MERGE_HEAD [list]
473 foreach path [array names file_states] {
474 set s $file_states($path)
485 unset file_states($path)
486 catch {unset selected_paths($path)}
489 set file_states($path) [list _O [lindex $s 1] {} {}]
499 set file_states($path) [list \
500 _[string index $m 1] \
511 ui_status [mc "Created commit %s: %s" [string range $cmt_id 0 7] $subject]
514 proc commit_postcommit_wait {fd_ph cmt_id} {
517 append pch_error [read $fd_ph]
518 fconfigure $fd_ph -blocking 1
520 if {[catch {close $fd_ph}]} {
521 hook_failed_popup post-commit $pch_error 0
526 fconfigure $fd_ph -blocking 0