How can I do git merges in such a way that they are easy to roll back?
There is lots of talk about how it's not easy to "undo" a merge in git. Short version: if you undo a merge commit, it also tells git to never merge those changes back in in the future.
Is there something I can do when doing the merge in order to abate this problem? There are plenty of situations where undoing a merge would be really, really useful, just in the normal course of software dev, and more crucially, in controlling the state of a release branch, when changes need to be rolled back.
edit
I have seen the solution in this article and don't really consider it a solution, more of an explanation of the problem. It requires
- always use --no-ff
- remember all your undone-merges when you want to bring back in code that depends on them (this could be a few hours, days, week, or months in the future…)
what I want
Here is how it works in Subversion. Let's say I have a branch call开发者_JS百科ed "release-candidate", which is what we run on the staging server and where we try out features. Let's say I merge in feature A branch. In Subversion, it's all one changeset, and all history for all files is merged. Let's say we don't like it, so we want to take it out. We just undo that single changeset, and don't have to think about anything else. We can merge feature branch A back in at any time in the future without having to remember that we at one point merged it in and took it out.
I'd like to be able to get as close to that flow as possible. I'd like to optimize for "not having to remember stuff in the future", even if it makes things take more steps along the way somehow. (this might be impossible...)
UPDATE:
A workflow that makes it easier to work with branch-per-feature is here: http://dymitruk.com/blog/2012/02/05/branch-per-feature/
(the SVN part of the question has been answered at the end)
Yes, here is how you reintroduce a feature that you unmerged. Consider the following history (assumes you "undid" a merge already):
x---x----x--x---x--M--U--L
\ /
x--x--x--x-F
F is the feature branch, M is the merge, U is the opposite when you unmerge that feature, L is the latest commit.
Here are your choices:
revert U (no
--force
necessary to push):x---x----x--x---x--M--U--L--^U \ / x--x--x--x-F
rebase F onto L (and then merge
--ff-only F'
) (no--force
necessary to push):x---x----x--x---x--M--U--L \ / \ x--x--x--x-x x'--x'--x'--x'--F'
rebase F onto L (and then merge
--no-ff F'
- preserves your new branch point) (no --force necessary to push):x---x----x--x---x--M--U--L-------------------M2 \ / \ / x--x--x--x-x x'--x'--x'--x'--F'
rebase -i head^^
and eliminate U from the list (--force
is necessary to push):x---x----x--x---x--M--L \ / x--x--x--x-F
rebase --onto M^1 L^ L
to get rid of the merge and unmerge. Now you can remerge F later.L' / x---x----x--x---x--M--U--L \ / x--x--x--x-F
To squash all the feature commits, use the --squash
modifier on the initial merge. I'll let your imagination do the work on how that would look in history. There is a reason I don't recommend doing this. There is value in knowing how you got a feature working and what steps it took. Subsequent merges will be easier since Git can examine the history of why a certain file looks like it does. Squashing the commits together loses that information.
There are additional drawbacks that may or may not affect your ability to take advantage of rerere history.
What I recommend is always marking what is released with a blank merge in master. This is done via a merge with the --no-ff
option. You never work on master and the only commits that are done there are those merges - no code change commits. In the QA branch, you tag the commit that marks the point at which you released. So when you do git merge --no-ff rc-12.2
, you will autogenerate a commit comment "merged rc-12.2".
Check out git-flow.
Hope that provides you more detail.
精彩评论