Break a previous commit into multiple commits
Asked Answered
M

23

1798

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 the local repository?

Mobley answered 2/6, 2011 at 16:11 Comment(6)
A good source for learning how to do this is Pro Git §6.4 Git Tools - Rewriting History, in the "Splitting a Commit" section.Her
The docs linked at the above comment are excellent, better explained than the answers below.Hako
I suggest use of this alias https://mcmap.net/q/12728/-how-do-i-run-git-rebase-interactive-in-non-interactive-manner. It allows to split a commit using git autorebase split COMMIT_IDOverboard
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.) (Copied from 2010 answer below)Smattering
I ran into this problem after I accidentally squashed two commits during a rebase in an earlier commit. My way to fix it was to checkout the squashed commit, git reset HEAD~, git stash, then git cherry-pick the first commit within the squash, then git stash pop. My cherry-pick case is quite specific here, but git stash and git stash pop is quite handy for others.Hayrack
All of these answers which mention git rebase -i are completely impractical for large changes that span a lot of commits due to the reason that conflicts have to be rehandled. So it's only feasible if there is only a few prior resolved conflicts.Quillet
C
2487

git rebase -i will do it.

First, start with a clean working directory: git status should show no pending modifications, deletions, or additions.

Now, you have to decide which commit(s) you want to split.

A) Splitting the most recent commit

To split apart your most recent commit, first:

$ git reset HEAD~

Now commit the pieces individually in the usual way, producing as many commits as you need.

B) Splitting a commit farther back

This requires rebasing, that is, rewriting history. To specify the correct commit, you have several choices:

  • If it is three commits back, then

      $ git rebase -i HEAD~3
    

    where 3 is how many commits back it is.

  • If it is farther back in the tree than you want to count, then

      $ git rebase -i 123abcd~
    

    where 123abcd is the SHA1 of the commit you want to split up.

  • If you are on a different branch (e.g., a feature branch) that you want to merge into master:

      $ git rebase -i master
    

When you get the rebase edit screen, find the commit you want to break apart. At the beginning of that line, replace pick with edit (e for short). Save the buffer and exit. Rebase will now stop just after the commit you want to edit. Then:

$ git reset HEAD~

Commit the pieces individually in the usual way, producing as many commits as you need.

Finally

$ git rebase --continue
Calida answered 2/6, 2011 at 16:26 Comment(35)
Thank you for this answer. I wanted to have some previously committed files in the staging area, so the instructions for me were a little different. Before I could git rebase --continue, I actually had to git add (files to be added), git commit, then git stash (for the remaining files). After git rebase --continue, I used git checkout stash . to get the remaining filesAnoxia
manojlds's answer actually has this link to the documentation on git-scm, which also explains the process of splitting commits very clearly.Her
You will also want to take advantage of git add -p to add only partial sections of files, possibly with the e option to edit diffs to only commit some of a hunk. git stash is also useful if you want to carry some work forward but remove it from the current commit.Pantalets
I've done this and wanted to split the previous commits of branch massive_branch not only into smaller commits, but split into separate branches. I manually record the SHA of certain commits, then create a new branch and cherry-pick them over when I'm done with the rebase of the massive_branch. Any better way to do this? Like, as you are rebasing and creating new commits, marking commits to move to a separate branch?Exegetics
If you want to split and reorder commits, what I like to do is split first and then reorder separately using another git rebase -i HEAD^3 command. This way if the split goes bad you don't have to undo quite as much work.Whatever
@DavidM.Lloyd I take the same approach when there's a lot of work involved.Calida
@CraigRinger Even better than git add -p is git add -i but it seems to be lesser known.Gait
What mean the ^ in git rebase -i 123abcd^ ?Garibay
@Garibay ^ without a number means "first parent". I think it is equivalent to ~ without a number, which means "first ancestor." If given numbers, e.g. ^2 or ~2, then they are not necessarily equivalent. I'll edit the answer to make it more consistent.Calida
This is not a good answer. Unless I'm wrong git reset HEAD~ will lose newly added files.Cement
@Cement git reset HEAD~ does not lose newly added files, nor will it lose modifications made to tracked files. It takes the "--hard" switch to make git reset do unsafe things. That said, I recommend starting this sequence with a clean tree.Calida
@WayneConrad My experience is that if a commit in question contains newly added files that were not present in the previous commit, doing git reset HEAD~ during rebase will, as the name suggest, reset the tree to the previous commit, ie. without the files introduced by the commmit.Cement
@Cement The files that were newly committed in HEAD will be left on disk after git reset HEAD~. They are not lost.Calida
It is giving the error : error: failed to push some refs to 'https://github.com/MyRepo/repo.git' hint: Updates were rejected because the tip of your current branch is behind on pushing the changes after the above modifications. pull before push resolved the issue.Phia
@FarrukhChishti That means that someone pushed commit(s) between when you pulled and when you pushed. That's not an issue with the rebase. I'm glad you were able to resolve it.Calida
I often want to reuse the log message in one of the commits. Just do this for the commit where you want to reuse the message: git status shows the hash of the original commit, use git commit --reuse-message=<that hash>.Dissimilitude
Why is git rebase --continue needed for A? As far as I can see, there's no rebase being started there 🤔Interrelation
@Interrelation I think you are correct. An edit was introduced that caused this error; I'll revert that edit.Calida
if you toss your changes mistakenly with a git restore, you can 1) ctrl + z or undo or 2) do this rebase on another branch. You cannot lose your changes this wayAerial
consider doing this work on a new branch to give yourself a way to restartAerial
what does ~ mean?Aerial
@GeoffLangenderfer ~ on the end of a version reference means to go back one one generation. For example, HEAD is the most recent commit in the current branch. HEAD~ (which is short for HEAD~1) is the commit before the most recent commit.Calida
@WayneConrad so ~ and ^ are equivalent?Aerial
@GeoffLangenderfer ttpshttps://mcmap.net/q/12625/-what-39-s-the-difference-between-head-and-head-in-gitCalida
Great answer! I would suggest to use git reset HEAD~1 -p to split commit into several by going through all the changed code hunks one by one.Eraser
In case u want to break the initial commit, use git rebase -i --root.Establishment
I think I come back to this answer bi-monthlyJacobus
@Jacobus I am very glad this answer has been so helpful! Thank you for the kind comment.Calida
123abcd should be the one before the one you want to split up; otherwise edit won't workMatron
@Matron That's what the tilde does in 123abcd~. That selects the parent commit of 123abcd. If you are not including the tilde, then you are correct that you must specify the commit before 123abcd.Calida
@WayneConrad tilde can be seen as abbreviation for the full SHA1 for novice git user. IMO It's better to say it explicitly to avoid overlooking it.Matron
@Matron "tilde can be seen as abbreviation for the full SHA1" -- That's not what tilde does though. Maybe we are having a communication difficulty.Calida
you need to do git push --force-with-lease in order to rewrite the history on remote (github)Curt
@Curt It's best of course to avoid rewriting history of commits that have been pushed. That said, you are correct. And TIL that git push --force-with-lease is a newer, gentler alternative to the more violent git push --force that I knew about. Thank you for that comment.Calida
@CraigRinger Also, if you only use e of git add -p, you can use the shortcut git add -e.Remy
K
418

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.

Kuomintang answered 22/1, 2010 at 15:5 Comment(8)
On Windows you have you use ~ instead of ^.Parthenia
Word of caution: with this approach I lost the commit message.Peterkin
@Peterkin Yes, of course. We are resetting the commit, after all - message included. The prudent thing to do, if you know you're going to be splitting a commit but want to keep some/all of its message, is to take a copy of that message. So, git show the commit before rebaseing, or if you forget or prefer this: get back to it later via the reflog. None of it will actually be "lost" until it's garbage-collected away in 2 weeks or whatever.Reveal
~ and ^ are different things, even on Windows. You still want the caret ^, so you'll just need to escape it as appropriate for your shell. In PowerShell, it's HEAD`^. With cmd.exe, you can double it to escape like HEAD^^. In most (all?) shells, you can surround with quotes like "HEAD^".Locomotion
@Peterkin the commit message can be recovered in the reflog (git reflog); git log <sha>Courtship
You can also do git commit --reuse-message=abcd123. The short option for it is -C.Photothermic
Or use --reedit-message=<commit> if you want to change it. Short option is-cNonpros
@j0057, jarno: This is the solution I was looking for. Most of the time splitting a commit is just a "reverse fixup" where one realizes that the commit is okay but certain changes don't belong.Trinidad
C
81

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.

Crimson answered 31/3, 2017 at 10:23 Comment(3)
Thank you!!! This should be the accepted answer. Would have saved me a lot of time and pain today. It's the only answer where the result of the final commit brings you to the same state as the commit under edit.Rheotaxis
I like the way you use the original commit message.Rafaellle
Using option 2, when I do git checkout *Sha I'm Editing* . it always says Updated 0 paths from *Some Sha That's Not In Git Log* and gives no changes.Morrie
H
50

Use git rebase --interactive to edit that earlier commit, run git reset HEAD~, and then git add -p to add some, then make a commit, then add some more and make another commit, as many times as you like. When you're done, run git rebase --continue, and you'll have all the split commits earlier in your stack.

Important: Note that you can play around and make all the changes you want, and not have to worry about losing old changes, because you can always run git reflog to find the point in your project that contains the changes you want, (let's call it a8c4ab), and then git reset a8c4ab.

Here's a series of commands to show how it works:

mkdir git-test; cd git-test; git init

now add a file A

vi A

add this line:

one

git commit -am one

then add this line to A:

two

git commit -am two

then add this line to A:

three

git commit -am three

now the file A looks like this:

one
two
three

and our git log looks like the following (well, I use git log --pretty=oneline --pretty="%h %cn %cr ---- %s"

bfb8e46 Rose Perrone 4 seconds ago ---- three
2b613bc Rose Perrone 14 seconds ago ---- two
9aac58f Rose Perrone 24 seconds ago ---- one

Let's say we want to split the second commit, two.

git rebase --interactive HEAD~2

This brings up a message that looks like this:

pick 2b613bc two
pick bfb8e46 three

Change the first pick to an e to edit that commit.

git reset HEAD~

git diff shows us that we just unstaged the commit we made for the second commit:

diff --git a/A b/A
index 5626abf..814f4a4 100644
--- a/A
+++ b/A
@@ -1 +1,2 @@
 one
+two

Let's stage that change, and add "and a third" to that line in file A.

git add .

This is usually the point during an interactive rebase where we would run git rebase --continue, because we usually just want to go back in our stack of commits to edit an earlier commit. But this time, we want to create a new commit. So we'll run git commit -am 'two and a third'. Now we edit file A and add the line two and two thirds.

git add . git commit -am 'two and two thirds' git rebase --continue

We have a conflict with our commit, three, so let's resolve it:

We'll change

one
<<<<<<< HEAD
two and a third
two and two thirds
=======
two
three
>>>>>>> bfb8e46... three

to

one
two and a third
two and two thirds
three

git add .; git rebase --continue

Now our git log -p looks like this:

commit e59ca35bae8360439823d66d459238779e5b4892
Author: Rose Perrone <[email protected]>
Date:   Sun Jul 7 13:57:00 2013 -0700

    three

diff --git a/A b/A
index 5aef867..dd8fb63 100644
--- a/A
+++ b/A
@@ -1,3 +1,4 @@
 one
 two and a third
 two and two thirds
+three

commit 4a283ba9bf83ef664541b467acdd0bb4d770ab8e
Author: Rose Perrone <[email protected]>
Date:   Sun Jul 7 14:07:07 2013 -0700

    two and two thirds

diff --git a/A b/A
index 575010a..5aef867 100644
--- a/A
+++ b/A
@@ -1,2 +1,3 @@
 one
 two and a third
+two and two thirds

commit 704d323ca1bc7c45ed8b1714d924adcdc83dfa44
Author: Rose Perrone <[email protected]>
Date:   Sun Jul 7 14:06:40 2013 -0700

    two and a third

diff --git a/A b/A
index 5626abf..575010a 100644
--- a/A
+++ b/A
@@ -1 +1,2 @@
 one
+two and a third

commit 9aac58f3893488ec643fecab3c85f5a2f481586f
Author: Rose Perrone <[email protected]>
Date:   Sun Jul 7 13:56:40 2013 -0700

    one

diff --git a/A b/A
new file mode 100644
index 0000000..5626abf
--- /dev/null
+++ b/A
@@ -0,0 +1 @@
+one
Histaminase answered 7/7, 2013 at 21:10 Comment(2)
About playing around: one doesn't even need reflog to roll it all back, if you are in a rebase. git rebase --abort does it.Glutenous
It's usually easier to rebase on a separate branch, compared to relying on the reflog or rebase --abort. That way you can easily switch back to the non-rebased version if you need to switch tasks.Hekker
H
20

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.

Her answered 14/7, 2013 at 22:25 Comment(5)
Under Windows, remember ^ is an escape character for command line: it should be doubled. By example, issue git reset HEAD^^ instead of git reset HEAD^.Caravaggio
@Frédéric :s I've never run into this. At least in PowerShell this is not the case. Then using ^ twice resets two commits above the current HEAD.Wicklund
@Farway, try it in a classic command line. PowerShell is quite another beast, its escape character is the backtilt.Caravaggio
To summarize: "HEAD^" in cmd.exe or PowerShell, HEAD^^ in cmd.exe, HEAD`^ in PowerShell. It's useful to learn about how shells — and your particular shell — work (i.e., how a command turns into individual parts that get passed to the program) so that you can adapt commands online into the right characters for your particular shell. (Not specific to Windows.)Locomotion
you need to do git push --force-with-lease in order to rewrite the history on remote (github)Curt
V
20

Another approach to split a commit, is to invoke

git rebase -i <branch or commit>

mark the commit you'd like to split as edit and then duplicate that line. This will present you the commit twice during rebase. When editing the first one, you remove everything you would like to have in the second one. When done, (run tests, if applicable) git add -A, git commit --amend (adjust the commit message here) and git rebase --continue. You are now on the second commit, which just brings in the changes you just removed from the first one.

I think this is much easier than having to carefully use git add -p.

Vellum answered 8/3, 2023 at 17:34 Comment(0)
V
16

Now in the latest TortoiseGit on Windows you can do it very easily.

Open the rebase dialog, configure it, and do the following steps.

  • Right-click the commit you want to split and select "Edit" (among pick, squash, delete...).
  • Click "Start" to start rebasing.
  • Once it arrives to the commit to split, check the "Edit/Split" button and click on "Amend" directly. The commit dialog opens.
    Edit/Split commit
  • Unselect the files you want to put on a separate commit.
  • Edit the commit message, and then click "commit".
  • Until there are files to commit, the commit dialog will open again and again. When there is no more file to commit, it will still ask you if you want to add one more commit.

Very helpful, thanks TortoiseGit !

Vezza answered 22/4, 2016 at 10:32 Comment(0)
T
14

A quick reference of the necessary commands, because I basically know what to do but always forget the right syntax:

git rebase -i <sha1_before_split>
# mark the targeted commit with 'edit'
git reset HEAD^
git add ...
git commit -m "First part"
git add ...
git commit -m "Second part"
git rebase --continue

Credits to Emmanuel Bernard's blog post.

Touchdown answered 13/2, 2020 at 12:14 Comment(0)
T
13

Working with a latest commit

If you just want to extract something from existing commit and keep the original one, you can use

git reset --patch HEAD^

instead of git reset HEAD^. This command allows you to reset just chunks you need.

After you chose the chunks you want to reset, you'll have staged chunks that will reset changes in previous commit. Now you alter the last commit removing those changes from it

git commit --amend --no-edit

and you have unstaged chunks that you can add to the separate commit by

git add .
git commit -m "new commit"

Working not with a latest commit

And of course use git rebase --interactive as suggested above to go to some of previous commits.

Off topic fact:

In mercurial they have hg split - the second feature after hg absorb I'd like to see in git.

Thoria answered 2/2, 2021 at 22:15 Comment(5)
I learn something new everyday. Thanks for this! This is EXACTLY what I was looking for! Also, this works for any commit via interactive rebasing. In my case, I wanted to edit the 2nd commit back: git rebase -i HEAD~2. In the todo list, I changed the first commit to e, and then applied the above. Worked like a charm!Huffish
Somehow it didn't work for me... Firstly, I didn't know what to pick in the git reset - should I select (yes) or reject (no) the pieces I want to extract? Secondly, after doing the git commit --amend I ended with the same commit I started with and no unstaged changes. Thirdly, if I'm not mistaken it will extract into a dependent change, while my need at hand is to extract into a pre-commit.Cookie
Published too soon. I'll give you some answers in a minute.... Sorry, it's a bit hard to answer so many questions at once. In such cases I personally prefer creating a simple sandbox with mkdir git_sandbox && cd git_sandbox && git init && touch a.file. This gives me confidence that I won't break anything and freedom to experiment and make mistakes.Thoria
1. Yes, select 'y' to changes you need to extract. 2. after commit --amend you end up with the commit you're amending not containing the changes you wanted to extract and extracted changes being 'unstashed'. 3. I didn't get this part, sorry.Thoria
Also it looks to me like you have a single hunk of changes there and you need to press "e" to edit it during git reset --patch. And to understand what to leave there and what to remove you definitely need an experiment :)Thoria
B
10

You can do interactive rebase git rebase -i. Man page has exactly what you want:

http://git-scm.com/docs/git-rebase#_splitting_commits

Barlow answered 2/6, 2011 at 16:15 Comment(1)
Giving a bit more context on how to approach the issues vs. just giving an RTFM would be a bit more helpful.Marchpane
L
10

Please note there's also git reset --soft HEAD^. It's similar to git reset (which defaults to --mixed) but it retains the index contents. So that if you've added/removed files, you have them in the index already.

Turns out to be very useful in case of giant commits.

Lovieloving answered 27/11, 2014 at 15:23 Comment(0)
S
9

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.)

Smattering answered 22/1, 2010 at 17:4 Comment(6)
according to SO standarts these days this should be qualified as not-an-answer; but this can still be helpful for others, so if you don't mind please move this to comments of the original postAttributive
@Attributive Seems reasonable. On the principal of minimal action, I'll not delete the answer, but I would not object if someone else does.Smattering
this would be much easier than all the rebase -i suggestions. I think this didn't get much attention due to lack of any formatting, though. Maybe you might review it, now that you have 126k points and probably know how to SO. ;)Almanac
git rebase -i is functionally equivalent to sequence of git cherry-pick operations.Hekker
Technically, this may not be the most accurate answer, but it feels way safer in practice. I feel so much less likely to blow my foot off using this method. Especially when splitting a commit deeper in the history.Adage
@Adage I agree. I've found over the years that I very seldom use the higher level tooling and just stick with manipulating the objects more directly. Except for rebase. I use that all the time, but generally only for simple cases. I can never understand the messages about current state when I'm in the middle of an interactive rebase that failed or in the middle of a cherry-pick with merge conflicts and wind up doing reset --hard and fixing things manually anyway.Smattering
I
6

Here is how to split one commit in IntelliJ IDEA, PyCharm, PhpStorm etc

  1. In Version Control log window, select the commit you would like to split, right click and select the Interactively Rebase from Here

  2. mark the one you want to split as edit, click Start Rebasing

  3. You should see a yellow tag is placed meaning that the HEAD is set to that commit. Right click on that commit, select Undo Commit

  4. Now those commits are back to staging area, you can then commit them separately. After all change has been committed, the old commit becomes inactive.

Intermit answered 3/12, 2019 at 15:48 Comment(0)
M
4

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.

Muriel answered 5/7, 2019 at 11:43 Comment(1)
Be very very careful with reset --hard. It will destroy untracked changes without warning or confirmation prompt.Hekker
I
3

Most existing answers suggest using interactive rebasing — git rebase -i or similar. For those like me who have a phobia of “interactive” approaches and like to hold onto the handrail when they go down stairs, here’s an alternative.

Say your history looks like … —> P –> Q –> R –> … –> Z = mybranch, and you want to split P –> Q into two commits, to end up with P –> Q1 –> Q' –> R' –> … Z' = mybranch, where the code state at Q', R', etc is identical to Q, R, etc.

Before starting, if you’re paranoid, make a backup of mybranch, so you don’t risk losing history:

git checkout mybranch
git checkout -b mybranch-backup

First, check out P (the commit before where you want to split), and create a new branch to work with

git checkout P
git checkout -b mybranch-splitting

Now, checkout any files you want from Q, and edit as desired to create the new intermediate commit:

git checkout Q file1.txt file2.txt
[…edit, stage commit with “git add”, etc…]
git commit -m "Refactored the widgets"

Note the hash of this commit, as Q1. Now check out the full state of Q, over a detached HEAD at Q1, commit this (creating Q'), and pull the working branch up to it:

git checkout Q
git reset --soft Q1
git commit -m "Added unit tests for widgets"
git branch -f mybranch-splitting

You’re now on mybranch-splitting at Q', and it should have precisely the same code state as Q did. Now rebase the original branch (from Q to Z) onto this:

git rebase --onto HEAD Q mybranch

Now mybranch should look like … P -> Q1 –> Q' –> R' –> … Z', as you wanted. So after checking that everything has worked correctly, you can delete your working and backup branches, and (if appropriate) push the rewritten mybranch upstream. If it had already been pushed, you’ll need to force-push, and all the usual caveats about force-pushing apply.

git push --force mybranch
git branch -d mybranch-splitting mybranch-backup
Infirm answered 6/4, 2021 at 17:39 Comment(2)
The backup branch is useful after the rebasing. Since you are just splitting commits you want to be sure that your tree remains the same. So you do git diff mybranch-backup to ensure that you didn't accidentally forget something. And if it shows a diff - you can just git reset --hard mybranch-backup to start over again. Also git checkout Q file1.txt file2.txt is IMO a much more fragile approach than reset HEAD^ and commit -p.Inaptitude
This looks a lot like what git cherry-pick --no-commit does.Hekker
S
2

I think that the best way i use git rebase -i. I created a video to show the steps to split a commit: https://www.youtube.com/watch?v=3EzOz7e1ADI

Stringhalt answered 25/7, 2016 at 11:47 Comment(0)
V
2

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"
Veriee answered 8/2, 2017 at 17:44 Comment(1)
What if there are commits above B?Supplication
L
2

I did this with rebase. Editing the commit does not work for me as that already picks the commit files and lets you amend to it, but I wanted to add all the files as untracked files so I could just pick some of them. The steps were:

  1. git rebase -i HEAD~5 (I wanted to split the 5th last commit in my history)
  2. Copy the target commit ID (you will need it later)
  3. Mark the commit with d to drop it; add a b line right after the commit to stop the rebasing process and continue it later. Even if this is the last commit, this gives you some room to just git rebase --abort and reset everything in case something goes wrong.
  4. When rebasing reaches the break point, use git cherry-pick -n <COMMIT ID>. This will pick the commit changes without picking the commit itself, leaving them as untracked.
  5. Add the files you want in the first commit (or use git add -i and patch so you can add specific chunks)
  6. Commit your changes.
  7. Decide what to do with the leftover changes. In my case, I wanted them at the end of the history and there were no conflicts, so I did git stash, but you can also just commit them.
  8. git rebase --continue to pick the additional changes

As a huge fan of interactive rebases, this was the easiest and most direct set of steps that I could come with. I hope this helps anyone facing this issue!

Levileviable answered 3/6, 2021 at 20:44 Comment(0)
P
2

I wrote a script to make this easier. Copy the contents to a file named git-split into a folder that is in your $PATH and make it executable. Then you will be able to use git split to run this script. See comments in the script for how to use:

#!/usr/bin/env zsh

# Use `git split` to split a commit into two commits.
# 
# First, use `git checkout --patch HEAD~` to stage changes that you
# want to split from the HEAD commit into a separate commit. Then run
# `git split` (this script) and enter a commit message for the new commit.

set -e

git commit --fixup=HEAD --quiet
git revert --no-commit HEAD
git revert --quit
if ! git commit $@; then
    git cherry-pick --no-commit HEAD
    git reset --soft HEAD~
    exit 0
fi
git -c sequence.editor=: rebase --autosquash --autostash --interactive HEAD~3
Pleuron answered 18/4, 2023 at 18:24 Comment(0)
D
1

If you're using GitKraken, there is an easy way, but it's not very straight forward. Trust me, it looks more complicated than it is. (Note: same as reordering or squashing, don't do it when you already pushed to origin).

  1. Checkout the commit before the one you want to split, this creates a HEAD tag
  2. Cherry pick your commit, answer No to the question if you want to commit immediately
  3. Create as many commits you need from the changes
  4. Cherry pick all the later commits, answer Yes to the question, it will keep your commit message
  5. Now create a new branch from your HEAD using right click
  6. Checkout your original branch
  7. Hard Reset the branch to the last common commit using hard, the original commits will disappear
  8. Rebase the new onto the original branch (while still on the original branch, right click on new, rebase)
  9. The new branch can now be deleted

Done. Told you, it looks more complicated than it is.

Dozer answered 9/10, 2023 at 15:2 Comment(2)
6. Checkout to your original branch 7. Hard Reset original branch to last commont coomitChromosphere
Thx @BeatleRefractor for the precisions, I integrated it in the answer!Dozer
L
0

This method is most useful if your changes were mostly adding new content.

Sometimes you do not want to lose commit message associated with commit that is being split. If you have commited some changes that you want to split, you can:

  1. Edit the changes you want removed out of the file (ie delete the lines or change the files approprietely to fit into first commit). You can use combination of your chosen editor and git checkout -p HEAD^ -- path/to/file to revert some changes into current tree.
  2. Commit this edit as a new commit, with something like git add . ; git commit -m 'removal of things that should be changed later', so you will have original commit in history and you will also have another commit with changes that you made, so the files on current HEAD look like you would want them in first commit after splitting.
000aaa Original commit
000bbb removal of things that should be changed later
  1. Revert the edit with git revert HEAD, this will create revert commit. Files will look like they do on original commit and your history will now look like
000aaa Original commit
000bbb removal of things that should be changed later
000ccc Revert "removal of things that should be changed later" (assuming you didn't edit commit message immediately)
  1. Now, you can squash/fixup first two commits into one with git rebase -i, optionally amend revert commit if you didn't give meaningful commit message to it earlier. You should be left with
000ddd Original commit, but without some content that is changed later
000eee Things that should be changed later
Lummox answered 27/1, 2021 at 13:44 Comment(0)
G
0

The recipes given in answers to this question are far too complicated to be executed regularly by mere mortals. That's what computers are for! I believe that splitting of git commits should be safe, convenient and accessible to everyone! That's why I wrote git-split, a simple shell script that delegates implementation of the recipe to a computer.

https://github.com/tomjaguarpaw/git-split

If you want to split a commit with hash abc123 into two parts simply run

sh split.sh bash abc123

commit the first part and then type exit 0. git-split does the rest. (You can replace bash with your favourite shell of course.)

Gadroon answered 8/12, 2023 at 16:29 Comment(0)
N
-1

This is straightforward in Sublime Merge.

  1. Choose the commit you want to modify in the commit list. Right-click and select Edit Commit > Edit Commit Contents.
  2. You are now in HEAD, with the commit contents in the staging area.
  3. Unstage the files/hunks/lines you want to separate, leaving only the contents of the first commit.
  4. Provide the commit message, then press Commit.
  5. Stage/commit as needed until you have the commit broken up how you want.
  6. Once there are no more uncommitted changes, press "Continue Rebase"

More info on related Edit Commit commands in the Sublime Merge docs.

Nebuchadnezzar answered 10/3 at 22:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.