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 foreach p
[concat $PARENT $MERGE_HEAD] {
383 if {[catch {set cmt_id
[eval git
$cmd]} err
]} {
384 catch {file delete
$msg_p}
385 error_popup
[strcat
[mc
"commit-tree failed:"] "\n\n$err"]
386 ui_status
[mc
"Commit failed."]
388 unset -nocomplain commit_author
389 commit_author_reset
$old_author
392 if {[info exists commit_author
]} {
393 unset -nocomplain commit_author
394 commit_author_reset
$old_author
397 # -- Update the HEAD ref.
400 if {$commit_type ne
{normal
}} {
401 append reflogm
" ($commit_type)"
403 set msg_fd
[open $msg_p r
]
404 setup_commit_encoding
$msg_fd 1
407 append reflogm
{: } $subject
409 git update-ref
-m $reflogm HEAD
$cmt_id $curHEAD
411 catch {file delete
$msg_p}
412 error_popup
[strcat
[mc
"update-ref failed:"] "\n\n$err"]
413 ui_status
[mc
"Commit failed."]
418 # -- Cleanup after ourselves.
420 catch {file delete
$msg_p}
421 catch {file delete
[gitdir MERGE_HEAD
]}
422 catch {file delete
[gitdir MERGE_MSG
]}
423 catch {file delete
[gitdir SQUASH_MSG
]}
424 catch {file delete
[gitdir GITGUI_MSG
]}
425 catch {file delete
[gitdir CHERRY_PICK_HEAD
]}
427 # -- Let rerere do its thing.
429 if {[get_config rerere.enabled
] eq
{}} {
430 set rerere
[file isdirectory
[gitdir rr-cache
]]
432 set rerere
[is_config_true rerere.enabled
]
438 # -- Run the post-commit hook.
440 set fd_ph
[githook_read post-commit
]
444 fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
445 fileevent $fd_ph readable
\
446 [list commit_postcommit_wait
$fd_ph $cmt_id]
449 $ui_comm delete
0.0 end
451 $ui_comm edit modified false
452 if {$::GITGUI_BCK_exists} {
453 catch {file delete
[gitdir GITGUI_BCK
]}
454 set ::GITGUI_BCK_exists 0
457 if {[is_enabled singlecommit
]} { do_quit
0 }
459 # -- Update in memory status
461 set selected_commit_type new
462 set commit_type normal
465 set MERGE_HEAD
[list]
467 foreach path
[array names file_states
] {
468 set s
$file_states($path)
479 unset file_states
($path)
480 catch {unset selected_paths
($path)}
483 set file_states
($path) [list _O
[lindex $s 1] {} {}]
493 set file_states
($path) [list \
494 _
[string index
$m 1] \
505 ui_status
[mc
"Created commit %s: %s" [string range
$cmt_id 0 7] $subject]
508 proc commit_postcommit_wait
{fd_ph cmt_id
} {
511 append pch_error
[read $fd_ph]
512 fconfigure $fd_ph -blocking 1
514 if {[catch {close $fd_ph}]} {
515 hook_failed_popup post-commit
$pch_error 0
520 fconfigure $fd_ph -blocking 0
523 proc commit_author_ident
{details
} {
525 array set author
$details
526 set old
[array get env GIT_AUTHOR_
*]
527 set env
(GIT_AUTHOR_NAME
) $author(name
)
528 set env
(GIT_AUTHOR_EMAIL
) $author(email
)
529 set env
(GIT_AUTHOR_DATE
) $author(date
)
532 proc commit_author_reset
{details
} {
534 unset env
(GIT_AUTHOR_NAME
) env
(GIT_AUTHOR_EMAIL
) env
(GIT_AUTHOR_DATE
)
535 if {$details ne
{}} {
536 array set env
$details