Break a previous commit into multiple commits

前端 未结 14 2057
渐次进展
渐次进展 2020-12-20 13:09

Without creating a branch and doing a bunch of funky work on a new branch, is it possible to break a single commit into a few different commits after it\'s been committed to

相关标签:
14条回答
  • 2020-12-20 13:24

    From git-rebase manual (SPLITTING COMMITS section)

    In interactive mode, you can mark commits with the action "edit". However, this does not necessarily mean that git rebase expects the result of this edit to be exactly one commit. Indeed, you can undo the commit, or you can add other commits. This can be used to split a commit into two:

    • Start an interactive rebase with git rebase -i <commit>^, where <commit> is the commit you want to split. In fact, any commit range will do, as long as it contains that commit.

    • Mark the commit you want to split with the action "edit".

    • When it comes to editing that commit, execute git reset HEAD^. The effect is that the HEAD is rewound by one, and the index follows suit. However, the working tree stays the same.

    • Now add the changes to the index that you want to have in the first commit. You can use git add (possibly interactively) or git gui (or both) to do that.

    • Commit the now-current index with whatever commit message is appropriate now.

    • Repeat the last two steps until your working tree is clean.

    • Continue the rebase with git rebase --continue.

    0 讨论(0)
  • 2020-12-20 13:27

    Easiest thing to do without an interactive rebase is (probably) to make a new branch starting at the commit before the one you want to split, cherry-pick -n the commit, reset, stash, commit the file move, reapply the stash and commit the changes, and then either merge with the former branch or cherry-pick the commits that followed. (Then switch the former branch name to the current head.) (It's probably better to follow MBOs advice and do an interactive rebase.)

    0 讨论(0)
  • 2020-12-20 13:29

    Previous answers have covered the use of git rebase -i to edit the commit that you want to split, and committing it in parts.

    This works well when splitting the files into different commits, but if you want to break apart changes to the individual files, there's more you need to know.

    Having got to the commit you want to split, using rebase -i and marking it for edit, you have two options.

    1. After using git reset HEAD~, go through the patches individually using git add -p to select the ones you want in each commit

    2. Edit the working copy to remove the changes you do not want; commit that interim state; and then pull back the full commit for the next round.

    Option 2 is useful if you're splitting a large commit, as it lets you check that the interim versions build and run properly as part of the merge. This proceeds as follows.

    After using rebase -i and editing the commit, use

    git reset --soft HEAD~
    

    to undo the commit, but leave the committed files in the index. You can also do a mixed reset by omitting --soft, depending on how close to the final result your initial commit is going to be. The only difference is whether you start with all the changes staged or with them all unstaged.

    Now go in and edit the code. You can remove changes, delete added files, and do whatever you want to construct the first commit of the series you're looking for. You can also build it, run it, and confirm that you have a consistent set of source.

    Once you're happy, stage/unstage the files as needed (I like to use git gui for this), and commit the changes through the UI or the command line

    git commit
    

    That's the first commit done. Now you want to restore your working copy to the state it had after the commit you are splitting, so that you can take more of the changes for your next commit. To find the sha1 of the commit you're editing, use git status. In the first few lines of the status you'll see the rebase command that is currently executing, in which you can find the sha1 of your original commit:

    $ git status
    interactive rebase in progress; onto be83b41
    Last commands done (3 commands done):
       pick 4847406 US135756: add debugging to the file download code
       e 65dfb6a US135756: write data and download from remote
      (see more in file .git/rebase-merge/done)
    ...
    

    In this case, the commit I'm editing has sha1 65dfb6a. Knowing that, I can check out the content of that commit over my working directory using the form of git checkout which takes both a commit and a file location. Here I use . as the file location to replace the whole working copy:

    git checkout 65dfb6a .
    

    Don't miss the dot on the end!

    This will check out, and stage, the files as they were after the commit you're editing, but relative to the previous commit you made, so any changes you already committed won't be part of the commit.

    You can either go ahead now and commit it as-is to finish the split, or go around again, deleting some parts of the commit before making another interim commit.

    If you want to reuse the original commit message for one or more commits, you can use it straight from the rebase's working files:

    git commit --file .git/rebase-merge/message
    

    Finally, once you've committed all the changes,

    git rebase --continue
    

    will carry on and complete the rebase operation.

    0 讨论(0)
  • 2020-12-20 13:29

    If you have this:

    A - B <- mybranch
    

    Where you have committed some content in commit B:

    /modules/a/file1
    /modules/a/file2
    /modules/b/file3
    /modules/b/file4
    

    But you want to split B into C - D, and get this result:

    A - C - D <-mybranch
    

    You can divide the content like this for example (content from different directories in different commits)...

    Reset the branch back to the commit before the one to split:

    git checkout mybranch
    git reset --hard A
    

    Create first commit (C):

    git checkout B /modules/a
    git add -u
    git commit -m "content of /modules/a"
    

    Create second commit (D):

    git checkout B /modules/b
    git add -u
    git commit -m "content of /modules/b"
    
    0 讨论(0)
  • 2020-12-20 13:30

    git rebase --interactive can be used to split a commit into smaller commits. The Git docs on rebase have a concise walkthrough of the process - Splitting Commits:

    In interactive mode, you can mark commits with the action "edit". However, this does not necessarily mean that git rebase expects the result of this edit to be exactly one commit. Indeed, you can undo the commit, or you can add other commits. This can be used to split a commit into two:

    • Start an interactive rebase with git rebase -i <commit>^, where <commit> is the commit you want to split. In fact, any commit range will do, as long as it contains that commit.

    • Mark the commit you want to split with the action "edit".

    • When it comes to editing that commit, execute git reset HEAD^. The effect is that the HEAD is rewound by one, and the index follows suit. However, the working tree stays the same.

    • Now add the changes to the index that you want to have in the first commit. You can use git add (possibly interactively) or git gui (or both) to do that.

    • Commit the now-current index with whatever commit message is appropriate now.

    • Repeat the last two steps until your working tree is clean.

    • Continue the rebase with git rebase --continue.

    If you are not absolutely sure that the intermediate revisions are consistent (they compile, pass the testsuite, etc.) you should use git stash to stash away the not-yet-committed changes after each commit, test, and amend the commit if fixes are necessary.

    0 讨论(0)
  • 2020-12-20 13:30

    It's been more than 8 years, but maybe someone will find it helpful anyway. I was able to do the trick without rebase -i. The idea is to lead git to the same state it was before you did git commit:

    # first rewind back (mind the dot,
    # though it can be any valid path,
    # for instance if you want to apply only a subset of the commit)
    git reset --hard <previous-commit> .
    
    # apply the changes
    git checkout <commit-you-want-to-split>
    
    # we're almost there, but the changes are in the index at the moment,
    # hence one more step (exactly as git gently suggests):
    # (use "git reset HEAD <file>..." to unstage)
    git reset
    

    After this you'll see this shiny Unstaged changes after reset: and your repo is in a state like you're about to commit all these files. From now on you can easily commit it again like you usually do. Hope it helps.

    0 讨论(0)
提交回复
热议问题