开发者

Isolating one change from another with git

I've been working on my local master branch on a new feature that is not yet ready to be pushed live for production. 开发者_如何转开发However, I just discovered a separate bug in my live app, and so I quickly fixed it locally. However, I want to push this bug fix to my remote master branch without pushing this new feature I've been working on. How might I do this?


Less serious answer:

By going back in time and using a proper branch model for your development. Stop working on your production branch and start using feature branches. The situation you describe is exactly why you should be using branches: The ability to set your development work aside, check out master, perform bug fixes and return to your development branch. By ignoring branching, you're ignoring a lot of what makes Git awesome.

More serious answer:

More practically, you can reorder the commits, point your local master to the one you want to push, push, and then check out your development commit as a new branch.

If your commit history looks like this:

A <-- (master) bug fix, you want to push this
B <-- you don't want to push this
C <-- (origin/master)

You can use git rebase -i HEAD~2 to re-order the last two commits to look like this (just switch the order of the lines in the editor that comes up):

B <-- (master) you don't want to push this
A <-- bug fix
C <-- (origin/master)

Make a note of the SHA1 of B, you're about to temporarily cut it out of your master branch. Once you've recorded the SHA1, you can use git update-ref refs/heads/master [SHA1 of A], which results in

A <-- (master) bug fix
C <-- (origin/master)

You can now git push to merge A into origin/master and send the results to origin.

Lastly, to get your development work (commit B) back, create a new development branch (which you should have done in the first place) pointing at your B commit: git branch development [SHA1 of B]

Your repository will now look like this:

B <-- (development) you don't want to push this
A <-- (master,origin/master) bug fix
C <-- where origin/master *was* before pushing

When you're ready to have that development work merged into master, you can:

git checkout master
git merge development


You got some answers that are good, but a little complex. I'm hoping to simplify it a little. The following assumes you committed your fix in only one commit, the last one on your local master branch.

git branch new-feature master~1
git checkout -b bugfix
git branch -f master origin/master
git rebase --onto master new-feature

This puts the work on your new feature on its own branch called new-feature, puts your bug fix on its own separate branch called bugfix, resets your master branch to match what you last pushed, then rebases to remove the new feature work from your bug fix branch.

This is how you normally would want your branches to look at this point. How I got it there isn't terribly important to understand yet, because in the future you just create a new branch with git checkout -b another-new-feature master before you start work on another new feature or bug fix.

Now, to push your bugfix only, just do:

git checkout master
git merge bugfix
git push

Then to continue working on your new feature, do:

git checkout new-feature

If you want to include your bug fix in your new-feature branch, do:

git rebase master

Otherwise, it will be merged in before you do your next push. When you're ready to push your new feature, just do the same merge you did for the bugfix branch, but with the new-feature branch.


Well you can add only the files you want to the commit to the staging area and commit them on your branch. Then create an other branch and commit the files you dont want to commit to it. Then you just push the branch with the fix.

When you commit, it only adds the files in your staging area so you can commit a part of your work on a branch and an other part of your work on an other branch.

If you want to switch branch while having non-commited files, you can add them to your staging area, checkout an other branch and then commit your staging area to that other branch.

If you have made some changes you want to push and some change you dont want to push on the same file then it get more complicated.


Here is an answer that walks through the commands. I wrote it to a similar question.

The basics of it are, moving your bug fix to a separate branch and merging that branch in with your master and topic. Instead of fixing the changes in step 4, use git cherry-pick bugFixSHA than remove the commit from your feature branch.


  1. Interactive rebase and move the bug fix commit to the be the first commit of the changeset
  2. Create a new branch pointing at the bug fix commit
  3. Push the new bug fix branch to remote master

I don't have my normal test machine with me today, so syntax is from memory. Someone can call me out if so, please.

Not sure if the ascii art will work:

  [origin/master]  hash_0 blah
  [new_feature  ]  hash_1 feature progress: more widgets!
                   hash_2 feature progress: less widgets, what was I thinking?
                   hash_3 bug fix: fixed memory management

The commands might look something like this:

I'm going to assume we are on branch new feature.

git rebase -i origin/master

At this point you'll get a list of commits:

hash_1 feature progress: more widgets!
hash_2 feature progress: less widgets, what was I thinking?
hash_3 bug fix: fixed memory management

You'll reorder your commits:

hash_3 bug fix: fixed memory management
hash_1 feature progress: more widgets!
hash_2 feature progress: less widgets, what was I thinking?

If you get errors here, then your bug fix depends on your new feature changes. Hard to give advice on that. Usually I just recreate the bug fix in origin/master in that case.

  [origin/master]  hash_0 blah
  [new_feature  ]  hash_3 bug fix: fixed memory management
                   hash_1 feature progress: more widgets!
                   hash_2 feature progress: less widgets, what was I thinking?

We should be in the correct order, I usually open up gitk and visually verify it looks how I expect. You expect to see your bug fix be the next commit after origin/master.

git checkout hash_3
git branch some_bug_fix
git push origin some_bug_fix:master
git checkout new_feature
git rebase origin/master

The last rebase is superfluous probably.

  [origin/master]  hash_0 blah
                   hash_3 bug fix: fixed memory management
  [new_feature  ]  hash_1 feature progress: more widgets!
                   hash_2 feature progress: less widgets, what was I thinking?

Alternatively,

  1. Checkout the remote
  2. Cherry pick the bug fix commit.

This will duplicate the code in your local branch though, so you'd need to drop it from your local branch and rebase on master.

I always use gitk to cherry pick, can't help you with commands.


Use Git's "interactive add" feature to add only the bugfix to the "staging area". You can then stash away the rest of your changes, test the bugfix to make sure it works, and then commit it by itself.

Below is an example walkthrough. At first it may seem long and complicated, but it's actually quite easy. Once you've done this a couple of times it will become second-nature and you're likely to find yourself using this technique quite often.


In this example there are changes to both bar.c and foo.c, but only foo.c has changes related to the bugfix. First, I use git add -i to interactively add just the bugfix:

test(master *)$ git add -i
           staged     unstaged path
  1:    unchanged        +1/-0 bar.c
  2:    unchanged        +1/-1 foo.c

*** Commands ***
  1: status       2: update       3: revert       4: add untracked
  5: patch        6: diff         7: quit         8: help

I select the "p" option to tell Git that I wish to select individual "patches" to be added to the "staging area":

What now> p
           staged     unstaged path
  1:    unchanged        +1/-0 bar.c
  2:    unchanged        +1/-1 foo.c

Next I type 2 to tell it that I want to select individual patches from foo.c:

Patch update>> 2
           staged     unstaged path
  1:    unchanged        +1/-0 bar.c
* 2:    unchanged        +1/-1 foo.c

Since foo.c is the only file that has patches that I want to add to the "staging area", now I'm finished selecting files so I just press enter at the prompt:

Patch update>>

Next Git shows me the individual patches from "foo.c" and asks if I'd like to add them to the index. In this example, there is only one change so I stage it:

diff --git a/foo.c b/foo.c
index 7bc741e..ec7ddfc 100644
--- a/foo.c
+++ b/foo.c
@@ -1 +1 @@
-Here is foo.c; it has a bug.
+Here is foo.c.
Stage this hunk [y,n,q,a,d,/,e,?]? y

I've now staged everything that is part of the bugfix. So I quit the "interactive add" mode:

*** Commands ***
  1: status       2: update       3: revert       4: add untracked
  5: patch        6: diff         7: quit         8: help
What now> q
Bye.

Note the status. foo.c has changes added to the "staging area", but "bar.c" does not:

test(master *+)$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   foo.c
#
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   bar.c
#

Next I tell Git to stash my work, but to keep what I've added to the "staging area" (a.k.a. the "index"):

test(master *+)$ git stash --keep-index
Saved working directory and index state WIP on master: ba84dec Adding bar.c with new "bar feature"
HEAD is now at ba84dec Adding bar.c with new "bar feature"
test(master +$)$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   foo.c
#

Now notice that the only thing in my work tree is the bugfix, and it's ready to be committed in the next commit. Now I can do whatever testing I'd like to do on the bugfix before I commit it.

When I'm satisfied with my changes, I can now commit the bugfix:

test(master +$)$ git commit
[master 79acd00] Commit bugfix for foo.c
 1 files changed, 1 insertions(+), 1 deletions(-)

Now I pop the stash to get the other work that I was doing in bar.c back into my working tree:

test(master $)$ git stash pop
# On branch master
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   bar.c
#
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (d306d098a272335ed31c14f07cf57e62ffc13151)

And I'm done. I can now push to the remote repository and it will get the bugfix, while the rest of my work is still in my work tree and does not get pushed.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜