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