How to split last commit into two in Git

前端 未结 10 501
囚心锁ツ
囚心锁ツ 2020-12-07 06:23

I have two working branches, master and forum and I\'ve just made some modifications in forum branch, that I\'d like to ch

相关标签:
10条回答
  • 2020-12-07 07:03

    You should use the index. After doing a mixed reset ("git reset HEAD^"), add the first set of changes into the index, then commit them. Then commit the rest.

    You can use "git add" to put all changes made in a file to the index. If you don't want to stage every modification made in a file, only some of them, you can use "git add -p".

    Let's see an example. Let's suppose I had a file called myfile, which contains the following text:

    something
    something else
    something again
    

    I modified it in my last commit so that now it looks like this:

    1
    something
    something else
    something again
    2
    

    Now I decide that I want to split it into two, and I want the insertion of the first line to be in the first commit, and the insertion of the last line to be in the second commit.

    First I go back to HEAD's parent, but I want to keep the modifications in file system, so I use "git reset" without argument (which will do a so-called "mixed" reset):

    $ git reset HEAD^
    myfile: locally modified
    $ cat myfile
    1
    something
    something else
    something again
    2
    

    Now I use "git add -p" to add the changes I want to commit to the index (=I stage them). "git add -p" is an interactive tool that asks you about what changes to the file should it add to the index.

    $ git add -p myfile
    diff --git a/myfile b/myfile
    index 93db4cb..2f113ce 100644
    --- a/myfile
    +++ b/myfile
    @@ -1,3 +1,5 @@
    +1
     something
     something else
     something again
    +2
    Stage this hunk [y,n,a,d,/,s,e,?]? s    # split this section into two!
    Split into 2 hunks.
    @@ -1,3 +1,4 @@
    +1
     something
     something else
     something again
    Stage this hunk [y,n,a,d,/,j,J,g,e,?]? y  # yes, I want to stage this
    @@ -1,3 +2,4 @@
     something
     something else
     something again
    +2
    Stage this hunk [y,n,a,d,/,K,g,e,?]? n   # no, I don't want to stage this
    

    Then I commit this first change:

    $ git commit -m "Added first line"
    [master cef3d4e] Added first line
     1 files changed, 1 insertions(+), 0 deletions(-)
    

    Now I can commit all the other changes (namely the numeral "2" put in the last line):

    $ git commit -am "Added last line"
    [master 5e284e6] Added last line
     1 files changed, 1 insertions(+), 0 deletions(-)
    

    Let's check the log to see what commits we have:

    $ git log -p -n2 | cat
    Commit 5e284e652f5e05a47ad8883d9f59ed9817be59d8
    Author: ...
    Date: ...
    
        Added last line
    
    Diff --git a/myfile b/myfile
    Index f9e1a67..2f113ce 100644
    --- a/myfile
    +++ b/myfile
    @@ -2,3 +2,4 @@
     something
     something else
     something again
    +2
    
    Commit cef3d4e0298dd5d279a911440bb72d39410e7898
    Author: ...
    Date: ...
    
        Added first line
    
    Diff --git a/myfile b/myfile
    Index 93db4cb..f9e1a67 100644
    --- a/myfile
    +++ b/myfile
    @@ -1,3 +1,4 @@
    +1
     something
     something else
     something again
    
    0 讨论(0)
  • 2020-12-07 07:03

    To change the current commit into two commits, you can do something like the following.

    Either:

    git reset --soft HEAD^
    

    This undoes the last commit but leaves everything staged. You can then unstage certain files:

    git reset -- file.file
    

    Optionally restage parts of those files:

    git add -p file.file
    

    Make a new first commit:

    git commit
    

    The stage and commit the rest of the changes in a second commit:

    git commit -a
    

    Or:

    Undo and unstage all of the changes from the last commit:

    git reset HEAD^
    

    Selectively stage the first round of changes:

    git add -p
    

    Commit:

    git commit
    

    Commit the rest of the changes:

    git commit -a
    

    (In either step, if you undid a commit that added a brand new file and want to add this to the second commit you'll have to manually add it as commit -a only stages changes to already tracked files.)

    0 讨论(0)
  • 2020-12-07 07:04

    You can use git rebase -i <commit>, where <commit> is the latest commit you want to keep as-is. Add a break at each point where you would like to insert a new split-out commit. Then at each break, use git checkout -p <commit containing parts you want> to pull in the parts you want to split out, and commit them. Then git rebase --continue to remove those parts from later commits.

    For the simple case of splitting the most recent commit, this looks like:

    $ git rebase -i HEAD^
    # add 'break' at the start
    
    $ git checkout -p master # or whatever your branch is called
    # choose the parts you want to split out
    
    $ git commit
    # commit the newly-split-out parts
    
    $ git rebase --continue
    # rebase the remaining parts of the change onto the split-out parts
    

    This assumes you want the later commit to retain the original commit message; that's the situation I usually find myself in (factoring out some preparatory change).

    0 讨论(0)
  • 2020-12-07 07:06

    I'm surprised nobody suggested git cherry-pick -n forum. This will stage the changes from the latest forum commit but not commit them - you can then reset away the changes you don't need and commit what you want to keep.

    0 讨论(0)
  • 2020-12-07 07:07

    Goals:

    • I want to split a past commit (splitme) into two.
    • I want to maintain the commit message.

    Plan:

    1. rebase interactive from one before splitme.
    2. edit splitme.
    3. Reset the files to split into a second commit.
    4. Amend commit, maintaining message, modify as necessary.
    5. Add back the files split out from the first commit.
    6. Commit with a new message.
    7. Continue rebase.

    The rebase steps (1 & 7) can be skipped if the splitme is the most recent commit.

    git rebase -i splitme^
    # mark splitme commit with 'e'
    git reset HEAD^ -- $files
    git commit --amend
    git add $files
    git commit -m "commit with just some files"
    git rebase --continue
    

    If I wanted the split files to be committed first, I'd then rebase -i again and switch the order

    git rebase -i splitme^
    # swap order of splitme and 'just some files'
    
    0 讨论(0)
  • 2020-12-07 07:14

    The double-revert-squash method

    1. Make another commit that removes the unwanted changes. (If it's per file, this is really easy: git checkout HEAD~1 -- files with unwanted changes and git commit. If not, files with mixed changes can be partially staged git reset file and git add -p file as an intermediate step.) Call this the revert.
    2. git revert HEAD – Make yet another commit, that adds back the unwanted changes. This is the double-revert
    3. Of the 2 commits you now made, squash the first onto the commit to split (git rebase -i HEAD~3). This commit now becomes free of the unwanted changes, for those are in the second commit.

    Benefits

    • Preserves the commit message
    • Works even if the commit to split is not the last one. It only requires that the unwanted changes do not conflict with later commits
    0 讨论(0)
提交回复
热议问题