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.
30 set fd [git_read cat-file commit $curHEAD]
31 fconfigure $fd -encoding binary -translation lf
32 # By default commits are assumed to be in utf-8
34 while {[gets $fd line] > 0} {
35 if {[string match {parent *} $line]} {
36 lappend parents [string range $line 7 end]
37 } elseif {[string match {encoding *} $line]} {
38 set enc [string tolower [string range $line 9 end]]
39 } elseif {[regexp "author (.*)\\s<(.*)>\\s(\\d.*$)" $line all name email time]} { }
44 set enc [tcl_encoding $enc]
46 set msg [encoding convertfrom $enc $msg]
47 set name [encoding convertfrom $enc $name]
48 set email [encoding convertfrom $enc $email]
50 if {$name ne {} && $email ne {}} {
51 set commit_author [list name $name email $email date $time]
54 set msg [string trim $msg]
56 error_popup [strcat [mc "Error loading commit data for amend:"] "\n\n$err"]
63 switch -- [llength $parents] {
64 0 {set commit_type amend-initial}
65 1 {set commit_type amend}
66 default {set commit_type amend-merge}
69 $ui_comm delete 0.0 end
70 $ui_comm insert end $msg
72 $ui_comm edit modified false
76 set GIT_COMMITTER_IDENT {}
78 proc committer_ident {} {
79 global GIT_COMMITTER_IDENT
81 if {$GIT_COMMITTER_IDENT eq {}} {
82 if {[catch {set me [git var GIT_COMMITTER_IDENT]} err]} {
83 error_popup [strcat [mc "Unable to obtain your identity:"] "\n\n$err"]
86 if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
87 $me me GIT_COMMITTER_IDENT]} {
88 error_popup [strcat [mc "Invalid GIT_COMMITTER_IDENT:"] "\n\n$me"]
93 return $GIT_COMMITTER_IDENT
99 set me [committer_ident]
100 if {$me eq {}} return
102 set sob "Signed-off-by: $me"
103 set last [$ui_comm get {end -1c linestart} {end -1c}]
105 $ui_comm edit separator
107 && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
108 $ui_comm insert end "\n"
110 $ui_comm insert end "\n$sob"
111 $ui_comm edit separator
116 proc create_new_commit {} {
117 global commit_type ui_comm commit_author
119 set commit_type normal
120 unset -nocomplain commit_author
121 $ui_comm delete 0.0 end
123 $ui_comm edit modified false
127 proc setup_commit_encoding {msg_wt {quiet 0}} {
130 if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
133 set use_enc [tcl_encoding $enc]
134 if {$use_enc ne {}} {
135 fconfigure $msg_wt -encoding $use_enc
138 error_popup [mc "warning: Tcl does not support encoding '%s'." $enc]
140 fconfigure $msg_wt -encoding utf-8
144 proc commit_tree {} {
145 global HEAD commit_type file_states ui_comm repo_config
148 if {[committer_ident] eq {}} return
149 if {![lock_index update]} return
151 # -- Our in memory state should match the repository.
153 repository_state curType curHEAD curMERGE_HEAD
154 if {[string match amend* $commit_type]
155 && $curType eq {normal}
156 && $curHEAD eq $HEAD} {
157 } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
158 info_popup [mc "Last scanned state does not match repository state.
160 Another Git program has modified this repository since the last scan. A rescan must be performed before another commit can be created.
162 The rescan will be automatically started now.
169 # -- At least one file should differ in the index.
172 foreach path [array names file_states] {
173 set s $file_states($path)
174 switch -glob -- [lindex $s 0] {
179 M? {set files_ready 1}
182 error_popup [mc "Unmerged files cannot be committed.
184 File %s has merge conflicts. You must resolve them and stage the file before committing.
185 " [short_path $path]]
190 error_popup [mc "Unknown file state %s detected.
192 File %s cannot be committed by this program.
193 " [lindex $s 0] [short_path $path]]
197 if {!$files_ready && ![string match *merge $curType] && ![is_enabled nocommit]} {
198 info_popup [mc "No changes to commit.
200 You must stage at least 1 file before you can commit.
206 if {[is_enabled nocommitmsg]} { do_quit 0 }
208 # -- A message is required.
210 set msg [string trim [$ui_comm get 1.0 end]]
211 regsub -all -line {[ \t\r]+$} $msg {} msg
213 error_popup [mc "Please supply a commit message.
215 A good commit message has the following format:
217 - First line: Describe in one sentence what you did.
219 - Remaining lines: Describe why this change is good.
225 # -- Build the message file.
227 set msg_p [gitdir GITGUI_EDITMSG]
228 set msg_wt [open $msg_p w]
229 fconfigure $msg_wt -translation lf
230 setup_commit_encoding $msg_wt
234 if {[is_enabled nocommit]} { do_quit 0 }
236 # -- Run the pre-commit hook.
238 set fd_ph [githook_read pre-commit]
240 commit_commitmsg $curHEAD $msg_p
244 ui_status [mc "Calling pre-commit hook..."]
246 fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
247 fileevent $fd_ph readable \
248 [list commit_prehook_wait $fd_ph $curHEAD $msg_p]
251 proc commit_prehook_wait {fd_ph curHEAD msg_p} {
254 append pch_error [read $fd_ph]
255 fconfigure $fd_ph -blocking 1
257 if {[catch {close $fd_ph}]} {
258 catch {file delete $msg_p}
259 ui_status [mc "Commit declined by pre-commit hook."]
260 hook_failed_popup pre-commit $pch_error
263 commit_commitmsg $curHEAD $msg_p
268 fconfigure $fd_ph -blocking 0
271 proc commit_commitmsg {curHEAD msg_p} {
272 global is_detached repo_config
276 && ![file exists [gitdir rebase-merge head-name]]
277 && [is_config_true gui.warndetachedcommit]} {
278 set msg [mc "You are about to commit on a detached head.\
279 This is a potentially dangerous thing to do because if you switch\
280 to another branch you will lose your changes and it can be difficult\
281 to retrieve them later from the reflog. You should probably cancel this\
282 commit and create a new branch to continue.\n\
284 Do you really want to proceed with your Commit?"]
285 if {[ask_popup $msg] ne yes} {
291 # -- Run the commit-msg hook.
293 set fd_ph [githook_read commit-msg $msg_p]
295 commit_writetree $curHEAD $msg_p
299 ui_status [mc "Calling commit-msg hook..."]
301 fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
302 fileevent $fd_ph readable \
303 [list commit_commitmsg_wait $fd_ph $curHEAD $msg_p]
306 proc commit_commitmsg_wait {fd_ph curHEAD msg_p} {
309 append pch_error [read $fd_ph]
310 fconfigure $fd_ph -blocking 1
312 if {[catch {close $fd_ph}]} {
313 catch {file delete $msg_p}
314 ui_status [mc "Commit declined by commit-msg hook."]
315 hook_failed_popup commit-msg $pch_error
318 commit_writetree $curHEAD $msg_p
323 fconfigure $fd_ph -blocking 0
326 proc commit_writetree {curHEAD msg_p} {
327 ui_status [mc "Committing changes..."]
328 set fd_wt [git_read write-tree]
329 fileevent $fd_wt readable \
330 [list commit_committree $fd_wt $curHEAD $msg_p]
333 proc commit_committree {fd_wt curHEAD msg_p} {
334 global HEAD PARENT MERGE_HEAD commit_type commit_author
335 global current_branch
336 global ui_comm commit_type_is_amend
337 global file_states selected_paths rescan_active
342 if {[catch {close $fd_wt} err]} {
343 catch {file delete $msg_p}
344 error_popup [strcat [mc "write-tree failed:"] "\n\n$err"]
345 ui_status [mc "Commit failed."]
350 # -- Verify this wasn't an empty change.
352 if {$commit_type eq {normal}} {
353 set fd_ot [git_read cat-file commit $PARENT]
354 fconfigure $fd_ot -encoding binary -translation lf
355 set old_tree [gets $fd_ot]
358 if {[string equal -length 5 {tree } $old_tree]
359 && [string length $old_tree] == 45} {
360 set old_tree [string range $old_tree 5 end]
362 error [mc "Commit %s appears to be corrupt" $PARENT]
365 if {$tree_id eq $old_tree} {
366 catch {file delete $msg_p}
367 info_popup [mc "No changes to commit.
369 No files were modified by this commit and it was not a merge commit.
371 A rescan will be automatically started now.
374 rescan {ui_status [mc "No changes to commit."]}
379 if {[info exists commit_author]} {
380 set old_author [commit_author_ident $commit_author]
382 # -- Create the commit.
384 set cmd [list commit-tree $tree_id]
385 if {[is_config_true commit.gpgsign]} {
388 foreach p [concat $PARENT $MERGE_HEAD] {
392 if {[catch {set cmt_id [eval git $cmd]} err]} {
393 catch {file delete $msg_p}
394 error_popup [strcat [mc "commit-tree failed:"] "\n\n$err"]
395 ui_status [mc "Commit failed."]
397 unset -nocomplain commit_author
398 commit_author_reset $old_author
401 if {[info exists commit_author]} {
402 unset -nocomplain commit_author
403 commit_author_reset $old_author
406 # -- Update the HEAD ref.
409 if {$commit_type ne {normal}} {
410 append reflogm " ($commit_type)"
412 set msg_fd [open $msg_p r]
413 setup_commit_encoding $msg_fd 1
416 append reflogm {: } $subject
418 git update-ref -m $reflogm HEAD $cmt_id $curHEAD
420 catch {file delete $msg_p}
421 error_popup [strcat [mc "update-ref failed:"] "\n\n$err"]
422 ui_status [mc "Commit failed."]
427 # -- Cleanup after ourselves.
429 catch {file delete $msg_p}
430 catch {file delete [gitdir MERGE_HEAD]}
431 catch {file delete [gitdir MERGE_MSG]}
432 catch {file delete [gitdir SQUASH_MSG]}
433 catch {file delete [gitdir GITGUI_MSG]}
434 catch {file delete [gitdir CHERRY_PICK_HEAD]}
436 # -- Let rerere do its thing.
438 if {[get_config rerere.enabled] eq {}} {
439 set rerere [file isdirectory [gitdir rr-cache]]
441 set rerere [is_config_true rerere.enabled]
447 # -- Run the post-commit hook.
449 set fd_ph [githook_read post-commit]
453 fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
454 fileevent $fd_ph readable \
455 [list commit_postcommit_wait $fd_ph $cmt_id]
458 $ui_comm delete 0.0 end
459 load_message [get_config commit.template]
461 $ui_comm edit modified false
462 if {$::GITGUI_BCK_exists} {
463 catch {file delete [gitdir GITGUI_BCK]}
464 set ::GITGUI_BCK_exists 0
467 if {[is_enabled singlecommit]} { do_quit 0 }
469 # -- Update in memory status
471 set commit_type normal
472 set commit_type_is_amend 0
475 set MERGE_HEAD [list]
477 foreach path [array names file_states] {
478 set s $file_states($path)
489 unset file_states($path)
490 catch {unset selected_paths($path)}
493 set file_states($path) [list _O [lindex $s 1] {} {}]
503 set file_states($path) [list \
504 _[string index $m 1] \
515 ui_status [mc "Created commit %s: %s" [string range $cmt_id 0 7] $subject]
518 proc commit_postcommit_wait {fd_ph cmt_id} {
521 append pch_error [read $fd_ph]
522 fconfigure $fd_ph -blocking 1
524 if {[catch {close $fd_ph}]} {
525 hook_failed_popup post-commit $pch_error 0
530 fconfigure $fd_ph -blocking 0
533 proc commit_author_ident {details} {
535 array set author $details
536 set old [array get env GIT_AUTHOR_*]
537 set env(GIT_AUTHOR_NAME) $author(name)
538 set env(GIT_AUTHOR_EMAIL) $author(email)
539 set env(GIT_AUTHOR_DATE) $author(date)
542 proc commit_author_reset {details} {
544 unset env(GIT_AUTHOR_NAME) env(GIT_AUTHOR_EMAIL) env(GIT_AUTHOR_DATE)
545 if {$details ne {}} {
546 array set env $details