]> git.ipfire.org Git - thirdparty/git.git/blame - git-gui/lib/commit.tcl
Merge tag 'gitgui-0.17.0' of git://repo.or.cz/git-gui
[thirdparty/git.git] / git-gui / lib / commit.tcl
CommitLineData
f522c9b5
SP
1# git-gui misc. commit reading/writing support
2# Copyright (C) 2006, 2007 Shawn Pearce
3
4proc load_last_commit {} {
5 global HEAD PARENT MERGE_HEAD commit_type ui_comm
6 global repo_config
7
8 if {[llength $PARENT] == 0} {
1ac17950 9 error_popup [mc "There is nothing to amend.
f522c9b5
SP
10
11You are about to create the initial commit. There is no commit before this to amend.
1ac17950 12"]
f522c9b5
SP
13 return
14 }
15
16 repository_state curType curHEAD curMERGE_HEAD
17 if {$curType eq {merge}} {
1ac17950 18 error_popup [mc "Cannot amend while merging.
f522c9b5
SP
19
20You 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.
1ac17950 21"]
f522c9b5
SP
22 return
23 }
24
25 set msg {}
26 set parents [list]
27 if {[catch {
0b812616 28 set fd [git_read cat-file commit $curHEAD]
f522c9b5 29 fconfigure $fd -encoding binary -translation lf
3ac31e44
AG
30 # By default commits are assumed to be in utf-8
31 set enc utf-8
f522c9b5
SP
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 }
38 }
c4638f66 39 set msg [read $fd]
f522c9b5 40 close $fd
c4638f66
SP
41
42 set enc [tcl_encoding $enc]
43 if {$enc ne {}} {
44 set msg [encoding convertfrom $enc $msg]
45 }
46 set msg [string trim $msg]
f522c9b5 47 } err]} {
31bb1d1b 48 error_popup [strcat [mc "Error loading commit data for amend:"] "\n\n$err"]
f522c9b5
SP
49 return
50 }
51
52 set HEAD $curHEAD
53 set PARENT $parents
54 set MERGE_HEAD [list]
55 switch -- [llength $parents] {
56 0 {set commit_type amend-initial}
57 1 {set commit_type amend}
58 default {set commit_type amend-merge}
59 }
60
61 $ui_comm delete 0.0 end
62 $ui_comm insert end $msg
63 $ui_comm edit reset
64 $ui_comm edit modified false
699d5601 65 rescan ui_ready
f522c9b5
SP
66}
67
68set GIT_COMMITTER_IDENT {}
69
70proc committer_ident {} {
71 global GIT_COMMITTER_IDENT
72
73 if {$GIT_COMMITTER_IDENT eq {}} {
74 if {[catch {set me [git var GIT_COMMITTER_IDENT]} err]} {
31bb1d1b 75 error_popup [strcat [mc "Unable to obtain your identity:"] "\n\n$err"]
f522c9b5
SP
76 return {}
77 }
78 if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
79 $me me GIT_COMMITTER_IDENT]} {
31bb1d1b 80 error_popup [strcat [mc "Invalid GIT_COMMITTER_IDENT:"] "\n\n$me"]
f522c9b5
SP
81 return {}
82 }
83 }
84
85 return $GIT_COMMITTER_IDENT
86}
87
88proc do_signoff {} {
89 global ui_comm
90
91 set me [committer_ident]
92 if {$me eq {}} return
93
94 set sob "Signed-off-by: $me"
95 set last [$ui_comm get {end -1c linestart} {end -1c}]
96 if {$last ne $sob} {
97 $ui_comm edit separator
98 if {$last ne {}
99 && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
100 $ui_comm insert end "\n"
101 }
102 $ui_comm insert end "\n$sob"
103 $ui_comm edit separator
104 $ui_comm see end
105 }
106}
107
108proc create_new_commit {} {
109 global commit_type ui_comm
110
111 set commit_type normal
112 $ui_comm delete 0.0 end
113 $ui_comm edit reset
114 $ui_comm edit modified false
699d5601 115 rescan ui_ready
f522c9b5
SP
116}
117
06569cd5
AG
118proc setup_commit_encoding {msg_wt {quiet 0}} {
119 global repo_config
120
121 if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
122 set enc utf-8
123 }
124 set use_enc [tcl_encoding $enc]
125 if {$use_enc ne {}} {
126 fconfigure $msg_wt -encoding $use_enc
127 } else {
128 if {!$quiet} {
129 error_popup [mc "warning: Tcl does not support encoding '%s'." $enc]
130 }
131 fconfigure $msg_wt -encoding utf-8
132 }
133}
134
f522c9b5
SP
135proc commit_tree {} {
136 global HEAD commit_type file_states ui_comm repo_config
699d5601 137 global pch_error
f522c9b5
SP
138
139 if {[committer_ident] eq {}} return
140 if {![lock_index update]} return
141
142 # -- Our in memory state should match the repository.
143 #
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} {
1ac17950 149 info_popup [mc "Last scanned state does not match repository state.
f522c9b5
SP
150
151Another Git program has modified this repository since the last scan. A rescan must be performed before another commit can be created.
152
153The rescan will be automatically started now.
1ac17950 154"]
f522c9b5 155 unlock_index
699d5601 156 rescan ui_ready
f522c9b5
SP
157 return
158 }
159
160 # -- At least one file should differ in the index.
161 #
162 set files_ready 0
163 foreach path [array names file_states] {
4a065c8a
BW
164 set s $file_states($path)
165 switch -glob -- [lindex $s 0] {
f522c9b5
SP
166 _? {continue}
167 A? -
168 D? -
7587f4d3 169 T? -
f522c9b5 170 M? {set files_ready 1}
ff515d81 171 _U -
f522c9b5 172 U? {
1ac17950 173 error_popup [mc "Unmerged files cannot be committed.
f522c9b5 174
1ac17950
CS
175File %s has merge conflicts. You must resolve them and stage the file before committing.
176" [short_path $path]]
f522c9b5
SP
177 unlock_index
178 return
179 }
180 default {
1ac17950 181 error_popup [mc "Unknown file state %s detected.
f522c9b5 182
1ac17950
CS
183File %s cannot be committed by this program.
184" [lindex $s 0] [short_path $path]]
f522c9b5
SP
185 }
186 }
187 }
1e65c622 188 if {!$files_ready && ![string match *merge $curType] && ![is_enabled nocommit]} {
1ac17950 189 info_popup [mc "No changes to commit.
f522c9b5 190
360cc106 191You must stage at least 1 file before you can commit.
1ac17950 192"]
f522c9b5
SP
193 unlock_index
194 return
195 }
196
1e65c622
AG
197 if {[is_enabled nocommitmsg]} { do_quit 0 }
198
f522c9b5
SP
199 # -- A message is required.
200 #
201 set msg [string trim [$ui_comm get 1.0 end]]
202 regsub -all -line {[ \t\r]+$} $msg {} msg
203 if {$msg eq {}} {
1ac17950 204 error_popup [mc "Please supply a commit message.
f522c9b5
SP
205
206A good commit message has the following format:
207
208320de 208- First line: Describe in one sentence what you did.
f522c9b5
SP
209- Second line: Blank
210- Remaining lines: Describe why this change is good.
1ac17950 211"]
f522c9b5
SP
212 unlock_index
213 return
214 }
215
fb0ca475
SP
216 # -- Build the message file.
217 #
218 set msg_p [gitdir GITGUI_EDITMSG]
219 set msg_wt [open $msg_p w]
220 fconfigure $msg_wt -translation lf
06569cd5 221 setup_commit_encoding $msg_wt
fb0ca475
SP
222 puts $msg_wt $msg
223 close $msg_wt
224
1e65c622
AG
225 if {[is_enabled nocommit]} { do_quit 0 }
226
f522c9b5
SP
227 # -- Run the pre-commit hook.
228 #
ed76cb70
SP
229 set fd_ph [githook_read pre-commit]
230 if {$fd_ph eq {}} {
fb0ca475 231 commit_commitmsg $curHEAD $msg_p
f522c9b5
SP
232 return
233 }
234
5e6d7768 235 ui_status [mc "Calling pre-commit hook..."]
f522c9b5 236 set pch_error {}
6eb420ef 237 fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
f522c9b5 238 fileevent $fd_ph readable \
fb0ca475 239 [list commit_prehook_wait $fd_ph $curHEAD $msg_p]
f522c9b5
SP
240}
241
fb0ca475 242proc commit_prehook_wait {fd_ph curHEAD msg_p} {
699d5601 243 global pch_error
f522c9b5
SP
244
245 append pch_error [read $fd_ph]
246 fconfigure $fd_ph -blocking 1
247 if {[eof $fd_ph]} {
248 if {[catch {close $fd_ph}]} {
fb0ca475 249 catch {file delete $msg_p}
5e6d7768 250 ui_status [mc "Commit declined by pre-commit hook."]
f522c9b5
SP
251 hook_failed_popup pre-commit $pch_error
252 unlock_index
253 } else {
fb0ca475 254 commit_commitmsg $curHEAD $msg_p
f522c9b5
SP
255 }
256 set pch_error {}
257 return
258 }
259 fconfigure $fd_ph -blocking 0
260}
261
fb0ca475 262proc commit_commitmsg {curHEAD msg_p} {
e34789cc 263 global is_detached repo_config
fb0ca475
SP
264 global pch_error
265
d8d166bf
BW
266 if {$is_detached
267 && ![file exists [gitdir rebase-merge head-name]]
268 && [is_config_true gui.warndetachedcommit]} {
e34789cc
HV
269 set msg [mc "You are about to commit on a detached head.\
270This is a potentially dangerous thing to do because if you switch\
9ef75087 271to another branch you will lose your changes and it can be difficult\
e34789cc
HV
272to retrieve them later from the reflog. You should probably cancel this\
273commit and create a new branch to continue.\n\
274\n\
275Do you really want to proceed with your Commit?"]
276 if {[ask_popup $msg] ne yes} {
277 unlock_index
278 return
279 }
280 }
281
fb0ca475
SP
282 # -- Run the commit-msg hook.
283 #
ed76cb70
SP
284 set fd_ph [githook_read commit-msg $msg_p]
285 if {$fd_ph eq {}} {
fb0ca475
SP
286 commit_writetree $curHEAD $msg_p
287 return
288 }
289
5e6d7768 290 ui_status [mc "Calling commit-msg hook..."]
fb0ca475 291 set pch_error {}
fb0ca475
SP
292 fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
293 fileevent $fd_ph readable \
294 [list commit_commitmsg_wait $fd_ph $curHEAD $msg_p]
295}
296
297proc commit_commitmsg_wait {fd_ph curHEAD msg_p} {
298 global pch_error
299
300 append pch_error [read $fd_ph]
301 fconfigure $fd_ph -blocking 1
302 if {[eof $fd_ph]} {
303 if {[catch {close $fd_ph}]} {
304 catch {file delete $msg_p}
5e6d7768 305 ui_status [mc "Commit declined by commit-msg hook."]
fb0ca475
SP
306 hook_failed_popup commit-msg $pch_error
307 unlock_index
308 } else {
309 commit_writetree $curHEAD $msg_p
310 }
311 set pch_error {}
312 return
313 }
314 fconfigure $fd_ph -blocking 0
315}
316
317proc commit_writetree {curHEAD msg_p} {
5e6d7768 318 ui_status [mc "Committing changes..."]
0b812616 319 set fd_wt [git_read write-tree]
f522c9b5 320 fileevent $fd_wt readable \
fb0ca475 321 [list commit_committree $fd_wt $curHEAD $msg_p]
f522c9b5
SP
322}
323
fb0ca475 324proc commit_committree {fd_wt curHEAD msg_p} {
f522c9b5 325 global HEAD PARENT MERGE_HEAD commit_type
699d5601
SP
326 global current_branch
327 global ui_comm selected_commit_type
f522c9b5
SP
328 global file_states selected_paths rescan_active
329 global repo_config
330
331 gets $fd_wt tree_id
8af52d7a 332 if {[catch {close $fd_wt} err]} {
fb0ca475 333 catch {file delete $msg_p}
31bb1d1b 334 error_popup [strcat [mc "write-tree failed:"] "\n\n$err"]
5e6d7768 335 ui_status [mc "Commit failed."]
f522c9b5
SP
336 unlock_index
337 return
338 }
339
340 # -- Verify this wasn't an empty change.
341 #
342 if {$commit_type eq {normal}} {
b215883d 343 set fd_ot [git_read cat-file commit $PARENT]
20f1a10b
SP
344 fconfigure $fd_ot -encoding binary -translation lf
345 set old_tree [gets $fd_ot]
346 close $fd_ot
347
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]
351 } else {
c8c4854b 352 error [mc "Commit %s appears to be corrupt" $PARENT]
20f1a10b
SP
353 }
354
f522c9b5 355 if {$tree_id eq $old_tree} {
fb0ca475 356 catch {file delete $msg_p}
1ac17950 357 info_popup [mc "No changes to commit.
f522c9b5
SP
358
359No files were modified by this commit and it was not a merge commit.
360
361A rescan will be automatically started now.
1ac17950 362"]
f522c9b5 363 unlock_index
1ac17950 364 rescan {ui_status [mc "No changes to commit."]}
f522c9b5
SP
365 return
366 }
367 }
368
f522c9b5
SP
369 # -- Create the commit.
370 #
371 set cmd [list commit-tree $tree_id]
372 foreach p [concat $PARENT $MERGE_HEAD] {
373 lappend cmd -p $p
374 }
375 lappend cmd <$msg_p
376 if {[catch {set cmt_id [eval git $cmd]} err]} {
fb0ca475 377 catch {file delete $msg_p}
31bb1d1b 378 error_popup [strcat [mc "commit-tree failed:"] "\n\n$err"]
5e6d7768 379 ui_status [mc "Commit failed."]
f522c9b5
SP
380 unlock_index
381 return
382 }
383
384 # -- Update the HEAD ref.
385 #
386 set reflogm commit
387 if {$commit_type ne {normal}} {
388 append reflogm " ($commit_type)"
389 }
fb0ca475 390 set msg_fd [open $msg_p r]
06569cd5 391 setup_commit_encoding $msg_fd 1
fb0ca475
SP
392 gets $msg_fd subject
393 close $msg_fd
f522c9b5
SP
394 append reflogm {: } $subject
395 if {[catch {
396 git update-ref -m $reflogm HEAD $cmt_id $curHEAD
397 } err]} {
fb0ca475 398 catch {file delete $msg_p}
31bb1d1b 399 error_popup [strcat [mc "update-ref failed:"] "\n\n$err"]
5e6d7768 400 ui_status [mc "Commit failed."]
f522c9b5
SP
401 unlock_index
402 return
403 }
404
405 # -- Cleanup after ourselves.
406 #
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]}
5a5e4d25 412 catch {file delete [gitdir CHERRY_PICK_HEAD]}
f522c9b5
SP
413
414 # -- Let rerere do its thing.
415 #
d4c53077
SP
416 if {[get_config rerere.enabled] eq {}} {
417 set rerere [file isdirectory [gitdir rr-cache]]
418 } else {
419 set rerere [is_config_true rerere.enabled]
420 }
421 if {$rerere} {
f522c9b5
SP
422 catch {git rerere}
423 }
424
425 # -- Run the post-commit hook.
426 #
ed76cb70
SP
427 set fd_ph [githook_read post-commit]
428 if {$fd_ph ne {}} {
f0d4eec9
JL
429 global pch_error
430 set pch_error {}
ed76cb70
SP
431 fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
432 fileevent $fd_ph readable \
433 [list commit_postcommit_wait $fd_ph $cmt_id]
f522c9b5
SP
434 }
435
436 $ui_comm delete 0.0 end
437 $ui_comm edit reset
438 $ui_comm edit modified false
4578c5cb
SP
439 if {$::GITGUI_BCK_exists} {
440 catch {file delete [gitdir GITGUI_BCK]}
9c5a3c77 441 set ::GITGUI_BCK_exists 0
4578c5cb 442 }
f522c9b5 443
1e65c622 444 if {[is_enabled singlecommit]} { do_quit 0 }
f522c9b5 445
f522c9b5
SP
446 # -- Update in memory status
447 #
448 set selected_commit_type new
449 set commit_type normal
450 set HEAD $cmt_id
451 set PARENT $cmt_id
452 set MERGE_HEAD [list]
453
454 foreach path [array names file_states] {
455 set s $file_states($path)
456 set m [lindex $s 0]
457 switch -glob -- $m {
458 _O -
459 _M -
460 _D {continue}
461 __ -
462 A_ -
463 M_ -
e681cb7d 464 T_ -
f522c9b5
SP
465 D_ {
466 unset file_states($path)
467 catch {unset selected_paths($path)}
468 }
469 DO {
470 set file_states($path) [list _O [lindex $s 1] {} {}]
471 }
472 AM -
473 AD -
7587f4d3
BW
474 AT -
475 TM -
476 TD -
f522c9b5 477 MM -
7587f4d3 478 MT -
f522c9b5
SP
479 MD {
480 set file_states($path) [list \
481 _[string index $m 1] \
482 [lindex $s 1] \
483 [lindex $s 3] \
484 {}]
485 }
486 }
487 }
488
489 display_all_files
490 unlock_index
491 reshow_diff
1ac17950 492 ui_status [mc "Created commit %s: %s" [string range $cmt_id 0 7] $subject]
f522c9b5 493}
ed76cb70
SP
494
495proc commit_postcommit_wait {fd_ph cmt_id} {
f0d4eec9 496 global pch_error
ed76cb70
SP
497
498 append pch_error [read $fd_ph]
499 fconfigure $fd_ph -blocking 1
500 if {[eof $fd_ph]} {
501 if {[catch {close $fd_ph}]} {
502 hook_failed_popup post-commit $pch_error 0
503 }
504 unset pch_error
505 return
506 }
507 fconfigure $fd_ph -blocking 0
508}