Git fork
at reftables-rust 581 lines 14 kB view raw
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 commit_author 6 global repo_config 7 8 if {[llength $PARENT] == 0} { 9 error_popup [mc "There is nothing to amend. 10 11You are about to create the initial commit. There is no commit before this to amend. 12"] 13 return 14 } 15 16 repository_state curType curHEAD curMERGE_HEAD 17 if {$curType eq {merge}} { 18 error_popup [mc "Cannot amend while merging. 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. 21"] 22 return 23 } 24 25 set msg {} 26 set parents [list] 27 if {[catch { 28 set name "" 29 set email "" 30 set fd [git_read [list cat-file commit $curHEAD]] 31 fconfigure $fd -encoding iso8859-1 32 # By default commits are assumed to be in utf-8 33 set enc 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]} { } 40 } 41 set msg [read $fd] 42 close $fd 43 44 set enc [tcl_encoding $enc] 45 if {$enc ne {}} { 46 set msg [convertfrom $enc $msg] 47 set name [convertfrom $enc $name] 48 set email [convertfrom $enc $email] 49 } 50 if {$name ne {} && $email ne {}} { 51 set commit_author [list name $name email $email date $time] 52 } 53 54 set msg [string trim $msg] 55 } err]} { 56 error_popup [strcat [mc "Error loading commit data for amend:"] "\n\n$err"] 57 return 58 } 59 60 set HEAD $curHEAD 61 set PARENT $parents 62 set MERGE_HEAD [list] 63 switch -- [llength $parents] { 64 0 {set commit_type amend-initial} 65 1 {set commit_type amend} 66 default {set commit_type amend-merge} 67 } 68 69 $ui_comm delete 0.0 end 70 $ui_comm insert end $msg 71 $ui_comm edit reset 72 $ui_comm edit modified false 73 rescan ui_ready 74} 75 76set GIT_COMMITTER_IDENT {} 77 78proc committer_ident {} { 79 global GIT_COMMITTER_IDENT 80 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"] 84 return {} 85 } 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"] 89 return {} 90 } 91 } 92 93 return $GIT_COMMITTER_IDENT 94} 95 96proc do_signoff {} { 97 global ui_comm 98 99 set me [committer_ident] 100 if {$me eq {}} return 101 102 set sob "Signed-off-by: $me" 103 set last [$ui_comm get {end -1c linestart} {end -1c}] 104 if {$last ne $sob} { 105 $ui_comm edit separator 106 if {$last ne {} 107 && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} { 108 $ui_comm insert end "\n" 109 } 110 $ui_comm insert end "\n$sob" 111 $ui_comm edit separator 112 $ui_comm see end 113 } 114} 115 116proc create_new_commit {} { 117 global commit_type ui_comm commit_author 118 119 set commit_type normal 120 unset -nocomplain commit_author 121 $ui_comm delete 0.0 end 122 $ui_comm edit reset 123 $ui_comm edit modified false 124 rescan ui_ready 125} 126 127proc setup_commit_encoding {msg_wt {quiet 0}} { 128 global repo_config 129 130 if {[catch {set enc $repo_config(i18n.commitencoding)}]} { 131 set enc utf-8 132 } 133 set use_enc [tcl_encoding $enc] 134 if {$use_enc ne {}} { 135 fconfigure $msg_wt -encoding $use_enc 136 } else { 137 if {!$quiet} { 138 error_popup [mc "warning: Tcl does not support encoding '%s'." $enc] 139 } 140 fconfigure $msg_wt -encoding utf-8 141 } 142} 143 144proc commit_tree {} { 145 global HEAD commit_type file_states ui_comm repo_config 146 global pch_error 147 148 if {[committer_ident] eq {}} return 149 if {![lock_index update]} return 150 151 # -- Our in memory state should match the repository. 152 # 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. 159 160Another Git program has modified this repository since the last scan. A rescan must be performed before another commit can be created. 161 162The rescan will be automatically started now. 163"] 164 unlock_index 165 rescan ui_ready 166 return 167 } 168 169 # -- At least one file should differ in the index. 170 # 171 set files_ready 0 172 foreach path [array names file_states] { 173 set s $file_states($path) 174 switch -glob -- [lindex $s 0] { 175 _? {continue} 176 A? - 177 D? - 178 T? - 179 M? {set files_ready 1} 180 _U - 181 U? { 182 error_popup [mc "Unmerged files cannot be committed. 183 184File %s has merge conflicts. You must resolve them and stage the file before committing. 185" [short_path $path]] 186 unlock_index 187 return 188 } 189 default { 190 error_popup [mc "Unknown file state %s detected. 191 192File %s cannot be committed by this program. 193" [lindex $s 0] [short_path $path]] 194 } 195 } 196 } 197 if {!$files_ready && ![string match *merge $curType] && ![is_enabled nocommit]} { 198 info_popup [mc "No changes to commit. 199 200You must stage at least 1 file before you can commit. 201"] 202 unlock_index 203 return 204 } 205 206 if {[is_enabled nocommitmsg]} { do_quit 0 } 207 208 # -- A message is required. 209 # 210 set msg [$ui_comm get 1.0 end] 211 212 # -- Build the message file. 213 # 214 set msg_p [gitdir GITGUI_EDITMSG] 215 set msg_wt [safe_open_file $msg_p w] 216 fconfigure $msg_wt -translation lf 217 setup_commit_encoding $msg_wt 218 puts $msg_wt $msg 219 close $msg_wt 220 221 if {[is_enabled nocommit]} { do_quit 0 } 222 223 # -- Run the pre-commit hook. 224 # 225 set fd_ph [githook_read pre-commit] 226 if {$fd_ph eq {}} { 227 commit_commitmsg $curHEAD $msg_p 228 return 229 } 230 231 ui_status [mc "Calling pre-commit hook..."] 232 set pch_error {} 233 fconfigure $fd_ph -blocking 0 -translation binary 234 fileevent $fd_ph readable \ 235 [list commit_prehook_wait $fd_ph $curHEAD $msg_p] 236} 237 238proc commit_prehook_wait {fd_ph curHEAD msg_p} { 239 global pch_error 240 241 append pch_error [read $fd_ph] 242 fconfigure $fd_ph -blocking 1 243 if {[eof $fd_ph]} { 244 if {[catch {close $fd_ph}]} { 245 catch {file delete $msg_p} 246 ui_status [mc "Commit declined by pre-commit hook."] 247 hook_failed_popup pre-commit $pch_error 248 unlock_index 249 } else { 250 commit_commitmsg $curHEAD $msg_p 251 } 252 set pch_error {} 253 return 254 } 255 fconfigure $fd_ph -blocking 0 256} 257 258proc commit_commitmsg {curHEAD msg_p} { 259 global is_detached repo_config 260 global pch_error 261 262 if {$is_detached 263 && ![file exists [gitdir rebase-merge head-name]] 264 && [is_config_true gui.warndetachedcommit]} { 265 set msg [mc "You are about to commit on a detached head.\ 266This is a potentially dangerous thing to do because if you switch\ 267to another branch you will lose your changes and it can be difficult\ 268to retrieve them later from the reflog. You should probably cancel this\ 269commit and create a new branch to continue.\n\ 270\n\ 271Do you really want to proceed with your Commit?"] 272 if {[ask_popup $msg] ne yes} { 273 unlock_index 274 return 275 } 276 } 277 278 # -- Run the commit-msg hook. 279 # 280 set fd_ph [githook_read commit-msg $msg_p] 281 if {$fd_ph eq {}} { 282 commit_writetree $curHEAD $msg_p 283 return 284 } 285 286 ui_status [mc "Calling commit-msg hook..."] 287 set pch_error {} 288 fconfigure $fd_ph -blocking 0 -translation binary 289 fileevent $fd_ph readable \ 290 [list commit_commitmsg_wait $fd_ph $curHEAD $msg_p] 291} 292 293proc commit_commitmsg_wait {fd_ph curHEAD msg_p} { 294 global pch_error 295 296 append pch_error [read $fd_ph] 297 fconfigure $fd_ph -blocking 1 298 if {[eof $fd_ph]} { 299 if {[catch {close $fd_ph}]} { 300 catch {file delete $msg_p} 301 ui_status [mc "Commit declined by commit-msg hook."] 302 hook_failed_popup commit-msg $pch_error 303 unlock_index 304 } else { 305 commit_writetree $curHEAD $msg_p 306 } 307 set pch_error {} 308 return 309 } 310 fconfigure $fd_ph -blocking 0 311} 312 313proc wash_commit_message {msg} { 314 # Strip trailing whitespace 315 regsub -all -line {[ \t\r]+$} $msg {} msg 316 # Strip comment lines 317 global comment_string 318 set cmt_rx [strcat {(^|\n)} [regsub -all {\W} $comment_string {\\&}] {[^\n]*}] 319 regsub -all $cmt_rx $msg {\1} msg 320 # Strip leading and trailing empty lines (puts adds one \n) 321 set msg [string trim $msg \n] 322 # Compress consecutive empty lines 323 regsub -all {\n{3,}} $msg \n\n msg 324 325 return $msg 326} 327 328proc commit_writetree {curHEAD msg_p} { 329 # -- Process the commit message after hooks have run. 330 # 331 set msg_fd [safe_open_file $msg_p r] 332 setup_commit_encoding $msg_fd 1 333 set msg [read $msg_fd] 334 close $msg_fd 335 336 # Process the message (strip whitespace, comments, etc.) 337 set msg [wash_commit_message $msg] 338 339 if {$msg eq {}} { 340 error_popup [mc "Please supply a commit message. 341 342A good commit message has the following format: 343 344- First line: Describe in one sentence what you did. 345- Second line: Blank 346- Remaining lines: Describe why this change is good. 347"] 348 unlock_index 349 return 350 } 351 352 # Write the processed message back to the file 353 set msg_wt [safe_open_file $msg_p w] 354 fconfigure $msg_wt -translation lf 355 setup_commit_encoding $msg_wt 356 puts $msg_wt $msg 357 close $msg_wt 358 359 ui_status [mc "Committing changes..."] 360 set fd_wt [git_read [list write-tree]] 361 fileevent $fd_wt readable \ 362 [list commit_committree $fd_wt $curHEAD $msg_p] 363} 364 365proc commit_committree {fd_wt curHEAD msg_p} { 366 global HEAD PARENT MERGE_HEAD commit_type commit_author 367 global current_branch 368 global ui_comm commit_type_is_amend 369 global file_states selected_paths rescan_active 370 global repo_config 371 global env 372 global hashlength 373 374 gets $fd_wt tree_id 375 if {[catch {close $fd_wt} err]} { 376 catch {file delete $msg_p} 377 error_popup [strcat [mc "write-tree failed:"] "\n\n$err"] 378 ui_status [mc "Commit failed."] 379 unlock_index 380 return 381 } 382 383 # -- Verify this wasn't an empty change. 384 # 385 if {$commit_type eq {normal}} { 386 set fd_ot [git_read [list cat-file commit $PARENT]] 387 fconfigure $fd_ot -encoding iso8859-1 388 set old_tree [gets $fd_ot] 389 close $fd_ot 390 391 if {[string equal -length 5 {tree } $old_tree] 392 && [string length $old_tree] == [expr {$hashlength + 5}]} { 393 set old_tree [string range $old_tree 5 end] 394 } else { 395 error [mc "Commit %s appears to be corrupt" $PARENT] 396 } 397 398 if {$tree_id eq $old_tree} { 399 catch {file delete $msg_p} 400 info_popup [mc "No changes to commit. 401 402No files were modified by this commit and it was not a merge commit. 403 404A rescan will be automatically started now. 405"] 406 unlock_index 407 rescan {ui_status [mc "No changes to commit."]} 408 return 409 } 410 } 411 412 if {[info exists commit_author]} { 413 set old_author [commit_author_ident $commit_author] 414 } 415 # -- Create the commit. 416 # 417 set cmd [list commit-tree $tree_id] 418 if {[is_config_true commit.gpgsign]} { 419 lappend cmd -S 420 } 421 foreach p [concat $PARENT $MERGE_HEAD] { 422 lappend cmd -p $p 423 } 424 set msgtxt [list <$msg_p] 425 if {[catch {set cmt_id [git_redir $cmd $msgtxt]} err]} { 426 catch {file delete $msg_p} 427 error_popup [strcat [mc "commit-tree failed:"] "\n\n$err"] 428 ui_status [mc "Commit failed."] 429 unlock_index 430 unset -nocomplain commit_author 431 commit_author_reset $old_author 432 return 433 } 434 if {[info exists commit_author]} { 435 unset -nocomplain commit_author 436 commit_author_reset $old_author 437 } 438 439 # -- Update the HEAD ref. 440 # 441 set reflogm commit 442 if {$commit_type ne {normal}} { 443 append reflogm " ($commit_type)" 444 } 445 set msg_fd [safe_open_file $msg_p r] 446 setup_commit_encoding $msg_fd 1 447 gets $msg_fd subject 448 close $msg_fd 449 append reflogm {: } $subject 450 if {[catch { 451 git update-ref -m $reflogm HEAD $cmt_id $curHEAD 452 } err]} { 453 catch {file delete $msg_p} 454 error_popup [strcat [mc "update-ref failed:"] "\n\n$err"] 455 ui_status [mc "Commit failed."] 456 unlock_index 457 return 458 } 459 460 # -- Cleanup after ourselves. 461 # 462 catch {file delete $msg_p} 463 catch {file delete [gitdir MERGE_HEAD]} 464 catch {file delete [gitdir MERGE_MSG]} 465 catch {file delete [gitdir SQUASH_MSG]} 466 catch {file delete [gitdir GITGUI_MSG]} 467 catch {file delete [gitdir CHERRY_PICK_HEAD]} 468 469 # -- Let rerere do its thing. 470 # 471 if {[get_config rerere.enabled] eq {}} { 472 set rerere [file isdirectory [gitdir rr-cache]] 473 } else { 474 set rerere [is_config_true rerere.enabled] 475 } 476 if {$rerere} { 477 catch {git rerere} 478 } 479 480 # -- Run the post-commit hook. 481 # 482 set fd_ph [githook_read post-commit] 483 if {$fd_ph ne {}} { 484 global pch_error 485 set pch_error {} 486 fconfigure $fd_ph -blocking 0 -translation binary 487 fileevent $fd_ph readable \ 488 [list commit_postcommit_wait $fd_ph $cmt_id] 489 } 490 491 $ui_comm delete 0.0 end 492 load_message [get_config commit.template] 493 $ui_comm edit reset 494 $ui_comm edit modified false 495 if {$::GITGUI_BCK_exists} { 496 catch {file delete [gitdir GITGUI_BCK]} 497 set ::GITGUI_BCK_exists 0 498 } 499 500 if {[is_enabled singlecommit]} { do_quit 0 } 501 502 # -- Update in memory status 503 # 504 set commit_type normal 505 set commit_type_is_amend 0 506 set HEAD $cmt_id 507 set PARENT $cmt_id 508 set MERGE_HEAD [list] 509 510 foreach path [array names file_states] { 511 set s $file_states($path) 512 set m [lindex $s 0] 513 switch -glob -- $m { 514 _O - 515 _M - 516 _D {continue} 517 __ - 518 A_ - 519 M_ - 520 T_ - 521 D_ { 522 unset file_states($path) 523 catch {unset selected_paths($path)} 524 } 525 DO { 526 set file_states($path) [list _O [lindex $s 1] {} {}] 527 } 528 AM - 529 AD - 530 AT - 531 TM - 532 TD - 533 MM - 534 MT - 535 MD { 536 set file_states($path) [list \ 537 _[string index $m 1] \ 538 [lindex $s 1] \ 539 [lindex $s 3] \ 540 {}] 541 } 542 } 543 } 544 545 display_all_files 546 unlock_index 547 reshow_diff 548 ui_status [mc "Created commit %s: %s" [string range $cmt_id 0 7] $subject] 549} 550 551proc commit_postcommit_wait {fd_ph cmt_id} { 552 global pch_error 553 554 append pch_error [read $fd_ph] 555 fconfigure $fd_ph -blocking 1 556 if {[eof $fd_ph]} { 557 if {[catch {close $fd_ph}]} { 558 hook_failed_popup post-commit $pch_error 0 559 } 560 unset pch_error 561 return 562 } 563 fconfigure $fd_ph -blocking 0 564} 565 566proc commit_author_ident {details} { 567 global env 568 array set author $details 569 set old [array get env GIT_AUTHOR_*] 570 set env(GIT_AUTHOR_NAME) $author(name) 571 set env(GIT_AUTHOR_EMAIL) $author(email) 572 set env(GIT_AUTHOR_DATE) $author(date) 573 return $old 574} 575proc commit_author_reset {details} { 576 global env 577 unset env(GIT_AUTHOR_NAME) env(GIT_AUTHOR_EMAIL) env(GIT_AUTHOR_DATE) 578 if {$details ne {}} { 579 array set env $details 580 } 581}