244

I've been using Git for about a year now and think it's fantastic, but I've just started on a second version of the project and started a new branch for it. I'm struggling a little with the best way to handle things going forward.

I have two branches called say master10 (for v1) and master20 (for v2). I've been making bug fixes in v1 on branch master10, and developing new stuff of master20. Whenever I make a bug fix I merge it into v2 by checking out master20 and doing git merge master10. So far so good.

Now however, I've made a change in v1 that I don't want in v2, but I want to continue merging other bug fixes. How do I tell Git to skip that particular commit (or a range of commits), but that going forward I still want to merge other bug fixes.

I thought git rebase might be what I need but read the doc and my head nearly exploded.

I think what I want is something like a "git sync" command that tells git that two branches are now in-sync and in future only merge the commits from this sync-point on.

Any help appreciated.

Lernkurve
  • 20,203
  • 28
  • 86
  • 118
Brad Robinson
  • 44,114
  • 19
  • 59
  • 88

8 Answers8

342

If you want to merge most but not all of the commits on branch "maint" to "master", for instance, you can do this. It requires some work---- as mentioned above, the usual use case is to merge everything from a branch--- but sometimes it happens that you made a change to a release version that shouldn't be integrated back (maybe that code's been superceded in master already), so how do you represent that? Here goes...

So let's suppose maint has had 5 changes applied, and one of those (maint~3) is not to be merged back into master, although all the others should be. You do this in three stages: actually merge everything before that one, tell git to mark maint~3 as merged even when it isn't, and then merge the rest. The magic is:

bash <master>$ git merge maint~4
bash <master>$ git merge -s ours maint~3
bash <master>$ git merge maint

The first command merges everything before your troublesome maint commit onto master. The default merge log message will explain you're merging "branch 'maint' (early part)".

The second command merges the troublesome maint~3 commit, but the "-s ours" option tells git to use a special "merge strategy" which, in fact, works by simply keeping the tree you are merging into and ignoring the commit(s) you are merging completely. But it does still make a new merge commit with HEAD and maint~3 as the parents, so the revision graph now says that maint~3 is merged. So in fact you probably want to use the -m option to git merge as well, to explain that that maint~3 commit is actually being ignored!

The final command simply merges the rest of maint (maint~2..maint) into master so that you're all synced up again.

Masih IT
  • 52
  • 10
araqnid
  • 127,052
  • 24
  • 157
  • 134
  • 1
    I wonder however if that is not a little bit dangerous: what is mast~3 must not be ignored but simply postponed ? With your solution, any subsequent git merge maint would not merge back mast~3. – VonC Apr 08 '09 at 17:09
  • 1
    For postponed commit, I would created a "sub-branch", do exactly what you describe (including git merge -s ours maint~3), then merge everything from the sub-branch to master. I believe a future "git merge maint" would this time merge "mast~3" – VonC Apr 08 '09 at 17:11
  • 6
    I think that if you need to postpone merging that commit, you have little choice but to skip it (merge -s ours) and later apply it using the cherry-pick command. Once the commit is reachable from master, it can't be merged again- avoiding merging the same change twice is one of the main git goals. – araqnid Apr 08 '09 at 18:04
  • 3
    Regarding your sub-branch example: I suspect that once you have merged the sub-branch you are in exactly the same situation as you are after the second step of my post. Creating additional branches makes no difference to the commit relationships. – araqnid Apr 08 '09 at 18:07
  • "Creating additional branches makes no difference to the commit relationships": fair enough, I will have to test it out. – VonC Apr 08 '09 at 21:23
  • 2
    Excellent. That's exactly what I was looking for. All I need to do now is when I make a change in v1 that I want in v2 is git merge master10 and when it's a change I want to ignore is git merge -s ours master10. Perfect. "git merge -s ours" is the "sync" command I suggested in OP. Thanks! – Brad Robinson Apr 09 '09 at 01:25
  • 5
    Just out of interest, if you just want to omit one change, why wouldn’t you just do a single merge and then revert that one changeset, rather than perform three merges? It would result in fewer changesets being committed to the repository and a more explicit history. – Mark Booth Mar 28 '12 at 16:57
  • 1
    "A more explicit history" doesn't seem so clear to me. If you do merging this way, you're being explicit about "took these changes, skipped that one, took these others". And fewer changesets isn't obviously a big deal. – araqnid Jul 16 '12 at 21:20
  • 4
    Often it's easier to explicitly name the commits. So you just need to view the git log of the to-be-merged branch and note the hashes of the commit that shouldn't be merged and the one before - instead of counting commits … – zwirbeltier Mar 06 '13 at 15:35
  • 20
    @MarkBooth: The commit you want to skip might be in a huge conflict with the branch you want to merge in. It is easier to just skip it instead of fixing the conflicts then reverting that change and fixing the conflicts again – SztupY Apr 10 '13 at 15:55
  • 1
    Looks like this should help me with similar problem, however, I can't figure a way to make 'git checkout maint && git merge master' keep the change made by that excluded commit (running the maint->master merge with additional arguments is not an option since anyone could be doing that in my case). In other words I need to ensure that the change introduced by the commit will remain only in the maint branch after multiple bi-directional merges in future. – Andrei LED Nov 04 '15 at 15:52
  • Having just migrated from Subversion (and merged between DEV and RELEASE branches just before) I was disappointed to find that Git did not understand the recent common ancestry and presented me with dozens of bizarre conflicts. I was on the verge of resorting to a cherry-pick but the explanation presented in this answer coupled with the excellent visualisations presented by 'GitUp' has saved my life. – Ed Randall Oct 09 '16 at 11:55
  • @araqnid what about ignoring 2 successive commits ? – TooCool Feb 13 '18 at 17:33
  • This absolutely saved my bacon today. We have multiple release branches, where each is merged forward to the next. Due to a Gitlab merge conflict oddity, a merge occurred in reverse, then was reverted. Unfortunately this reversion commit poisoned that branch so it actively tried to undo work when the branch was merged forward. By using "merge -s ours", I was able to build a shield of cascaded commits past this bad merge so that subsequent merges were performed cleanly. THANK YOU! – chrispitude Dec 25 '20 at 00:18
  • The only beef I have with this is this skipping cannot be done in one step or interactively and leaves no obvious trace. I have to know beforehand what to skip and stumble on each such commit. – ivan_pozdeev Jul 14 '22 at 07:20
  • How to specify the commit instead of counting the commit position ? – Puck Nov 22 '22 at 08:36
  • @MarkBooth: What if you merge back into the other branch? Wouldn't your _revert commit_ revert the changes in the original branch then? – mselmany May 27 '23 at 04:55
  • No @mselmany the revert is after the original commit in the DAG, so it wouldn't be re-applied. Any other behaviour would make subsequent merges a nightmare. – Mark Booth Jun 09 '23 at 10:54
  • @MarkBooth, I wasn't clear enough maybe. Let's say I had a commit (call it _cb_) in branch _B_. I merged _B_ into _A_, so _cb_ showed up in _A_ too. I don't want _cb_ in _A_, so I reverted it. Let's call revert commit as _rcb_. Later on, for some reason, I merged _A_ into _B_. This caused _rcb_ to be merged into _B_ as well. Eventually, _rcb_ reverts the changes done in _cb_ in the branch _B_ too. I've tried and confirmed that this scenario happens as I proposed. The solution proposed by __araqnid__ solves such scenarios as well. – mselmany Jun 17 '23 at 13:17
52

IMHO, the most logical thing to do, is to merge everything, and then use git revert (commit_you_dont_want) to remove it.

Example:

git merge master
git revert 12345678

If you have multiple "to-ignore" commits, or would like to edit revert message:

git merge master
git revert -n 123456
git revert -n abcdef
git commit -m "... Except commits 123456 and abcdef"

Then your history may look like:

| ... Except 123456 and abcdef
|\ Merge branch 'master' into 'your_branch'

If you have conflicts involving ONLY these "to-ignore" commits, you may use:

git merge master -X ours

So your version will persist over the other one. Even without error messages, you may still "revert" those unwanted commits, because they may have other changes that did not conflict, and you still don't want them.

If you have conflicts envolving NOT ONLY the "to-ignore" commits, you should resolve them manually, and you'll probably have to resolve them again during reverting.

Derek Mahar
  • 27,608
  • 43
  • 124
  • 174
Alexandre T.
  • 3,581
  • 1
  • 18
  • 11
  • 3
    If you then want to later merge those reverted commits into the branch into question, will Git still ignore them? – Anriëtte Myburgh Nov 03 '15 at 19:43
  • 1
    @AnriëtteMyburgh You can revert the reversion commits to later merge them in- something you actually can't do as easily with the "merge -s ours" strategy. – Lily Chung Jun 24 '17 at 19:49
  • Thanks, this is exactly what I needed had a few older commits in a feature branch that I did not want in master and a bunch that I did want in master. Nice and clean. – Vale Trujillo Mar 12 '20 at 18:27
  • Thanks,this way is better IMHO after doing the revert you can see a clear log of all the commits you did not merge easier to follow and unrevert in the future – galsi Aug 04 '20 at 06:21
  • This worked better for me than the previous answer – Meeting Attender Jun 01 '21 at 19:27
  • This for me was THE answer. Thanks for reminding me revert exists. I'm in Unity and I'm keeping 2 branches with different project settings (which must be tracked); this approach is a good alternative to keeping 2 separate repos, at least in early stage. The only downside is that one has to remember to revert (and which commits) every time they merge. – Steak Overflow Sep 13 '21 at 08:15
  • If you later merge the merged branch back into the source one, those reverted commits would be merged, too -- which is undesirable. So you'd need to skip those reverts at that point accoring to the [accepted answer](https://stackoverflow.com/a/729723) anyway. This way may be easier than just using the accepted answer though since reverts are easily distinguishable. – ivan_pozdeev Jul 14 '22 at 06:45
  • It should be noted that this doesn't really "skip" the original commit - it still adds it to the history, then adds an extra commit at the end – Jordan Mitchell Barrett Aug 12 '22 at 02:21
20

Commits include ancestry. You can't merge a commit without merging prior commits.

You can cherry-pick them, of course. That's a fine flow when you have a branch that's in maintenance mode.

Dustin
  • 89,080
  • 21
  • 111
  • 133
8

Sounds like a classic case for 'git cherry-pick' https://git-scm.com/docs/git-cherry-pick it does exactly what it sounds like

GDwag
  • 97
  • 1
  • 3
3

A kind of an advertisement to my project which basically wraps the process described by @araqnid.

It's kind of helper that introduces following GIT flow:

  • there's a daily/weekly notification on pending merges from maintenance branches into dev/master branch
  • branch maintainer checks for the status and decides him/herself whether all commits are required and either block some them or asks developers to block themselves. In the end maintenance branch is merged into the upsteam.

A quote from the project page:

Depending on the workflow it's possible to have maintenance or customer-specific branches along with the master branch. These branches are also called LTS branches.

Oftentimes the hot fixes go into the branches where the bug was reported and then the commit is merged back into the master branch.

General practice is to have all branches perfectly synchronized with the master, i.e. you want to see a clear delta between a particular branch and master to understand whether the master contains all features and bugfixes.

However sometimes you don't want particular commits because they are customer-specific and shall not be visible by other users. Or your master branch diverged that much that it requires completely different approach to fix the issue, or even better, the problem is not anymore present there.

Also in case of cherry-pick from master into the maintenance branch the resulting commit shall be blocked in master.

Dmytro
  • 402
  • 3
  • 4
3

You can use git-cherry pick for this:

git cherry-pick $(git merge-base HEAD origin/branch)..$(git rev-parse <offending-commit-hash>^)
git cherry-pick <offending-commit-hash>..$(git rev-parse origin/branch)

This cherry-picks all commits from last common commit of current HEAD and origin/branch until commit to be ignored (note ^ telling cherry-pick to stop at parent of that commit).

Second command cherry-picks everything from <offending-commit-hash> with rest of origin/branch.

Note that some tinkering might be still needed if skipped over commit is needed to merge other patches (git will stop cherry-pick and tell you to resolve conflicts).

Tomas Pruzina
  • 8,397
  • 6
  • 26
  • 39
1

Rather than revert or cherry-pick for this case, you need to get git to consider the changes you're skipping to be older than the ones you made.

So:

  1. merge the last commit before the commits you want to skip. This will, of course, merge all commits prior. git merge ccc
  2. merge the commits you want to skip. git merge fff --no-commit
  3. stage any merges, unstage all changes, undo all the changes. (Perhaps there are fool-proof commands for this, but I'd just do this part in a UI - however you know how)
  4. complete the empty merge git merge --continue
  5. merge the commits AFTER the one you wanted to skip. git merge source-branch-head

After Step 4, git will consider your branch more recent than that commit, since you dealt with it already (by choosing to keep YOUR versions of things).

Jason Kleban
  • 20,024
  • 18
  • 75
  • 125
1

Create a third branch for the changes you want in master10 but not in master20. Always consider master10 as your "master", the most stable branch of all. The branch all other branches want to keep in sync with at all times.

wilhelmtell
  • 57,473
  • 20
  • 96
  • 131
  • I guess that could work, but I've already got myself into this state, and a third branch would probably confuse me even more. :) – Brad Robinson Apr 08 '09 at 06:08