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 commit_author
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]]
37 } elseif {[regexp "author (.*)\\s<(.*)>\\s(\\d.*$)" $line all name email time]} {
38 set commit_author [list name $name email $email date $time]
44 set enc [tcl_encoding $enc]
46 set msg [encoding convertfrom $enc $msg]
48 set msg [string trim $msg]
50 error_popup [strcat [mc "Error loading commit data for amend:"] "\n\n$err"]
57 switch -- [llength $parents] {
58 0 {set commit_type amend-initial}
59 1 {set commit_type amend}
60 default {set commit_type amend-merge}
63 $ui_comm delete 0.0 end
64 $ui_comm insert end $msg
66 $ui_comm edit modified false
70 set GIT_COMMITTER_IDENT {}
72 proc committer_ident {} {
73 global GIT_COMMITTER_IDENT
75 if {$GIT_COMMITTER_IDENT eq {}} {
76 if {[catch {set me [git var GIT_COMMITTER_IDENT]} err]} {
77 error_popup [strcat [mc "Unable to obtain your identity:"] "\n\n$err"]
80 if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
81 $me me GIT_COMMITTER_IDENT]} {
82 error_popup [strcat [mc "Invalid GIT_COMMITTER_IDENT:"] "\n\n$me"]
87 return $GIT_COMMITTER_IDENT
93 set me [committer_ident]
96 set sob "Signed-off-by: $me"
97 set last [$ui_comm get {end -1c linestart} {end -1c}]
99 $ui_comm edit separator
101 && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
102 $ui_comm insert end "\n"
104 $ui_comm insert end "\n$sob"
105 $ui_comm edit separator
110 proc create_new_commit {} {
111 global commit_type ui_comm commit_author
113 set commit_type normal
114 unset -nocomplain commit_author
115 $ui_comm delete 0.0 end
117 $ui_comm edit modified false
121 proc setup_commit_encoding {msg_wt {quiet 0}} {
124 if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
127 set use_enc [tcl_encoding $enc]
128 if {$use_enc ne {}} {
129 fconfigure $msg_wt -encoding $use_enc
132 error_popup [mc "warning: Tcl does not support encoding '%s'." $enc]
134 fconfigure $msg_wt -encoding utf-8
138 proc commit_tree {} {
139 global HEAD commit_type file_states ui_comm repo_config
142 if {[committer_ident] eq {}} return
143 if {![lock_index update]} return
145 # -- Our in memory state should match the repository.
147 repository_state curType curHEAD curMERGE_HEAD
148 if {[string match amend* $commit_type]
149 && $curType eq {normal}
150 && $curHEAD eq $HEAD} {
151 } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
152 info_popup [mc "Last scanned state does not match repository state.
154 Another Git program has modified this repository since the last scan. A rescan must be performed before another commit can be created.
156 The rescan will be automatically started now.
163 # -- At least one file should differ in the index.
166 foreach path [array names file_states] {
167 set s $file_states($path)
168 switch -glob -- [lindex $s 0] {
173 M? {set files_ready 1}
176 error_popup [mc "Unmerged files cannot be committed.
178 File %s has merge conflicts. You must resolve them and stage the file before committing.
179 " [short_path $path]]
184 error_popup [mc "Unknown file state %s detected.
186 File %s cannot be committed by this program.
187 " [lindex $s 0] [short_path $path]]
191 if {!$files_ready && ![string match *merge $curType] && ![is_enabled nocommit]} {
192 info_popup [mc "No changes to commit.
194 You must stage at least 1 file before you can commit.
200 if {[is_enabled nocommitmsg]} { do_quit 0 }
202 # -- A message is required.
204 set msg [string trim [$ui_comm get 1.0 end]]
205 regsub -all -line {[ \t\r]+$} $msg {} msg
207 error_popup [mc "Please supply a commit message.
209 A good commit message has the following format:
211 - First line: Describe in one sentence what you did.
213 - Remaining lines: Describe why this change is good.
219 # -- Build the message file.
221 set msg_p [gitdir GITGUI_EDITMSG]
222 set msg_wt [open $msg_p w]
223 fconfigure $msg_wt -translation lf
224 setup_commit_encoding $msg_wt
228 if {[is_enabled nocommit]} { do_quit 0 }
230 # -- Run the pre-commit hook.
232 set fd_ph [githook_read pre-commit]
234 commit_commitmsg $curHEAD $msg_p
238 ui_status [mc "Calling pre-commit hook..."]
240 fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
241 fileevent $fd_ph readable \
242 [list commit_prehook_wait $fd_ph $curHEAD $msg_p]
245 proc commit_prehook_wait {fd_ph curHEAD msg_p} {
248 append pch_error [read $fd_ph]
249 fconfigure $fd_ph -blocking 1
251 if {[catch {close $fd_ph}]} {
252 catch {file delete $msg_p}
253 ui_status [mc "Commit declined by pre-commit hook."]
254 hook_failed_popup pre-commit $pch_error
257 commit_commitmsg $curHEAD $msg_p
262 fconfigure $fd_ph -blocking 0
265 proc commit_commitmsg {curHEAD msg_p} {
266 global is_detached repo_config
270 && ![file exists [gitdir rebase-merge head-name]]
271 && [is_config_true gui.warndetachedcommit]} {
272 set msg [mc "You are about to commit on a detached head.\
273 This is a potentially dangerous thing to do because if you switch\
274 to another branch you will lose your changes and it can be difficult\
275 to retrieve them later from the reflog. You should probably cancel this\
276 commit and create a new branch to continue.\n\
278 Do you really want to proceed with your Commit?"]
279 if {[ask_popup $msg] ne yes} {
285 # -- Run the commit-msg hook.
287 set fd_ph [githook_read commit-msg $msg_p]
289 commit_writetree $curHEAD $msg_p
293 ui_status [mc "Calling commit-msg hook..."]
295 fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
296 fileevent $fd_ph readable \
297 [list commit_commitmsg_wait $fd_ph $curHEAD $msg_p]
300 proc commit_commitmsg_wait {fd_ph curHEAD msg_p} {
303 append pch_error [read $fd_ph]
304 fconfigure $fd_ph -blocking 1
306 if {[catch {close $fd_ph}]} {
307 catch {file delete $msg_p}
308 ui_status [mc "Commit declined by commit-msg hook."]
309 hook_failed_popup commit-msg $pch_error
312 commit_writetree $curHEAD $msg_p
317 fconfigure $fd_ph -blocking 0
320 proc commit_writetree {curHEAD msg_p} {
321 ui_status [mc "Committing changes..."]
322 set fd_wt [git_read write-tree]
323 fileevent $fd_wt readable \
324 [list commit_committree $fd_wt $curHEAD $msg_p]
327 proc commit_committree {fd_wt curHEAD msg_p} {
328 global HEAD PARENT MERGE_HEAD commit_type commit_author
329 global current_branch
330 global ui_comm selected_commit_type
331 global file_states selected_paths rescan_active
336 if {[catch {close $fd_wt} err]} {
337 catch {file delete $msg_p}
338 error_popup [strcat [mc "write-tree failed:"] "\n\n$err"]
339 ui_status [mc "Commit failed."]
344 # -- Verify this wasn't an empty change.
346 if {$commit_type eq {normal}} {
347 set fd_ot [git_read cat-file commit $PARENT]
348 fconfigure $fd_ot -encoding binary -translation lf
349 set old_tree [gets $fd_ot]
352 if {[string equal -length 5 {tree } $old_tree]
353 && [string length $old_tree] == 45} {
354 set old_tree [string range $old_tree 5 end]
356 error [mc "Commit %s appears to be corrupt" $PARENT]
359 if {$tree_id eq $old_tree} {
360 catch {file delete $msg_p}
361 info_popup [mc "No changes to commit.
363 No files were modified by this commit and it was not a merge commit.
365 A rescan will be automatically started now.
368 rescan {ui_status [mc "No changes to commit."]}
373 if {[info exists commit_author]} {
374 set old_author [commit_author_ident $commit_author]
376 # -- Create the commit.
378 set cmd [list commit-tree $tree_id]
379 if {[is_config_true commit.gpgsign]} {
382 foreach p [concat $PARENT $MERGE_HEAD] {
386 if {[catch {set cmt_id [eval git $cmd]} err]} {
387 catch {file delete $msg_p}
388 error_popup [strcat [mc "commit-tree failed:"] "\n\n$err"]
389 ui_status [mc "Commit failed."]
391 unset -nocomplain commit_author
392 commit_author_reset $old_author
395 if {[info exists commit_author]} {
396 unset -nocomplain commit_author
397 commit_author_reset $old_author
400 # -- Update the HEAD ref.
403 if {$commit_type ne {normal}} {
404 append reflogm " ($commit_type)"
406 set msg_fd [open $msg_p r]
407 setup_commit_encoding $msg_fd 1
410 append reflogm {: } $subject
412 git update-ref -m $reflogm HEAD $cmt_id $curHEAD
414 catch {file delete $msg_p}
415 error_popup [strcat [mc "update-ref failed:"] "\n\n$err"]
416 ui_status [mc "Commit failed."]
421 # -- Cleanup after ourselves.
423 catch {file delete $msg_p}
424 catch {file delete [gitdir MERGE_HEAD]}
425 catch {file delete [gitdir MERGE_MSG]}
426 catch {file delete [gitdir SQUASH_MSG]}
427 catch {file delete [gitdir GITGUI_MSG]}
428 catch {file delete [gitdir CHERRY_PICK_HEAD]}
430 # -- Let rerere do its thing.
432 if {[get_config rerere.enabled] eq {}} {
433 set rerere [file isdirectory [gitdir rr-cache]]
435 set rerere [is_config_true rerere.enabled]
441 # -- Run the post-commit hook.
443 set fd_ph [githook_read post-commit]
447 fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
448 fileevent $fd_ph readable \
449 [list commit_postcommit_wait $fd_ph $cmt_id]
452 $ui_comm delete 0.0 end
454 $ui_comm edit modified false
455 if {$::GITGUI_BCK_exists} {
456 catch {file delete [gitdir GITGUI_BCK]}
457 set ::GITGUI_BCK_exists 0
460 if {[is_enabled singlecommit]} { do_quit 0 }
462 # -- Update in memory status
464 set selected_commit_type new
465 set commit_type normal
468 set MERGE_HEAD [list]
470 foreach path [array names file_states] {
471 set s $file_states($path)
482 unset file_states($path)
483 catch {unset selected_paths($path)}
486 set file_states($path) [list _O [lindex $s 1] {} {}]
496 set file_states($path) [list \
497 _[string index $m 1] \
508 ui_status [mc "Created commit %s: %s" [string range $cmt_id 0 7] $subject]
511 proc commit_postcommit_wait {fd_ph cmt_id} {
514 append pch_error [read $fd_ph]
515 fconfigure $fd_ph -blocking 1
517 if {[catch {close $fd_ph}]} {
518 hook_failed_popup post-commit $pch_error 0
523 fconfigure $fd_ph -blocking 0
526 proc commit_author_ident {details} {
528 array set author $details
529 set old [array get env GIT_AUTHOR_*]
530 set env(GIT_AUTHOR_NAME) $author(name)
531 set env(GIT_AUTHOR_EMAIL) $author(email)
532 set env(GIT_AUTHOR_DATE) $author(date)
535 proc commit_author_reset {details} {
537 unset env(GIT_AUTHOR_NAME) env(GIT_AUTHOR_EMAIL) env(GIT_AUTHOR_DATE)
538 if {$details ne {}} {
539 array set env $details