Is it possible to exclude specific commits when doing a git merge?
Asked Answered
K

8

86

Let's say that I want to merge from a release branch to the master branch and there are some commits in the release branch that I don't want to include in the master branch. Is there a way to do the merge so that one or more of those commits will not be merged?

My strategy so far is to do the following (in master):

git merge --no-commit release-branch
# Resolve conflicts and apply reverse patch of the commits that I don't want included
git commit # Edit commit message so that it lists the commits that have been reverse-patched

Is there a better way to do this?

Kelwin answered 1/12, 2008 at 22:55 Comment(1)
Possible duplicate of git - skipping specific commits when merging - I know that is younger, but its accepted answer is IMHO betterLaundress
P
52

Create a new branch, rebase the branch interactively and drop commits you don't want, and then merge that.

You can't take changes out of the middle of a branch without rehashing, but the right thing will happen when it sees the same changes in a later merge (e.g. from cherry-picking and what-not).

Parable answered 1/12, 2008 at 23:6 Comment(3)
But what happens when you try to merge that branch again?Empress
With which exact commands is this to be done? I see no way how I can "drop" the unwanted commitsPhenice
@Phenice when you git rebase -i other-branch it gives you a text editor with a bunch of commits in it. Delete the lines you don't want.Parable
S
123

I've found a solution that works for me in the Pro Git book.

Let's say you want to exclude the file config.php.

On branch A:

  1. Create a file named .gitattributes in the same dir, with this line: config.php merge=ours. This tells git what strategy to use when merging the file. In this case it always keep your version, ie. the version on the branch you are merging into.

  2. Add the .gitattributes file and commit

On branch B: repeat steps 1-2

Try merging now. Your file should be left untouched.

Shinshina answered 19/10, 2010 at 16:21 Comment(7)
For future readers, this works great for explicitly not including specific files. (In my case, I'm deploying different branches to different servers and want to keep my Capistrano deploy script unique on each branch.)Quicklime
This doesn't work for me for some reason. I created a new branch with a commit changing the file, and added .gitattributes file in both branches. When I merge back into the original branch, it seems to completely ignore the line in .gitattributes, and pulls in the changed file regardless. Is there some setting I'm missing?Nightfall
Doesn't work for me. I followed the instructions in the question listed by VonC too. I'm on Windows. The file I'm trying to avoid merging is a dot file (.core.config). Do I need to surround it's name with quotes or something?Malaspina
This only works if the two files are modified, else merge ours is ignoredWalt
@Nightfall Caumons is right. Basically, this attribute is only triggered if there is would be merge conflict. In your case, you have a FF merge. Git just does it without checking merge strategySadomasochism
this shouldn't be the top voted answer, it doesn't actually answer the question. The question is about excluding specific COMMITS not specific files (yes there is a difference, a big difference)Indwell
I did this and got an error on next pull: CONFLICT (add/add): Merge conflict in .gitattributesKingkingbird
P
52

Create a new branch, rebase the branch interactively and drop commits you don't want, and then merge that.

You can't take changes out of the middle of a branch without rehashing, but the right thing will happen when it sees the same changes in a later merge (e.g. from cherry-picking and what-not).

Parable answered 1/12, 2008 at 23:6 Comment(3)
But what happens when you try to merge that branch again?Empress
With which exact commands is this to be done? I see no way how I can "drop" the unwanted commitsPhenice
@Phenice when you git rebase -i other-branch it gives you a text editor with a bunch of commits in it. Delete the lines you don't want.Parable
S
27

If you have a support branch where you fix bugs and build new versions. On master you have the next version where you also build new versions frequently.

Every time you build a new version you change the version in some file, commit that new file, create a tag and push. Now merges from support to master will always have conflicts in the file containing the version info.

If the file containing the version information only contains the version information, you can go with the answer of fcurella. But if it does indeed may also contain mergeable information (pom.xml, gradle.properties, MANIFEST.MF, ...), you must perform some extra action.

Lets use the following example

      C---D*---E---F* support
     /
A---B---G---H*---I master

where commits with stars contain only changes due to version changes that should be ignored during merge.

To merge support into master without merge-conflicts due to the version builds, you can do either of the following:

Multiple merge commits

git checkout master
git merge C
git merge D -s ours
git merge E
git merge F -s ours

With the -s ours argument we are telling git to only record a merge without altering the workspace. This is comparable to the --record-only option of svn.

The above will result in the following layout

      -------------C---D*---E---F* support
     /              \   \    \   \
A---B---G---H*---I---J---K----L---M master

One merge commit using cherry-pick

git checkout master
git merge support -s ours --no-commit
git cherry-pick C E --no-commit
git commit -m 'merged support into master'

first we are starting a merge but only record that we are merging, without altering the workspace and without doing the merge commit. Then we are cherry-picking the commits to merge, again without commit. Finally we are committing the merge.

The above will result in the following layout

      C---D*---E---F* support
     /              \
A---B---G---H*---I---J master

One could even automate the cherry-picking.

git checkout master
git merge support -s ours --no-commit
for id in `git log support --reverse --not HEAD --format="%H [%an] %s" |
  grep -v "bump version" |
  sed "s/\(\w*\)\s.*/\1/g"`
do
  git cherry-pick --no-commit $id
done
git commit -m 'merged support into master'
Siloum answered 30/4, 2014 at 15:22 Comment(4)
Any example to automate multiple merge commits?Abarca
The solution that gives a single merge commit using cherry pick is fantastic! Thanks! This is exactly what I needed. We have two projects that branched from the same source. We need to maintain a few differences between them, but in general we need the bulk of the changes merged back and forth. Great post!Columbus
It often happens that, when creating a new branch, some configuration needs to be updated for that branch. We want to commit those changes, but we don't want to merge them back. With SVN, we could record-merge them and then forget about them. The non-cherry-pick solution seems to do something similar - but could I merge D and F in a single move, maybe before merging C or E?Teacake
In a similar scenario to that of my previous comment, can we "cherry-UNpick" certain commits? What I really need is the ability to forget about those commits in future work on that branch. In other words, I don't really want to select the meaningful changes, I'd much rather mark the non-meaningful ones as "don't merge". Can we do that in Git?Teacake
E
4

The reason why this can't be done directly is that every commit contains links to the parent commits (typically just one but several for merges). That way if you have one commit (by its SHA1 sum) the whole history is also fixed as the parents also contain links to their parents and so on. So the only way to leave out patches in the history is to write a new one. git rebase -i on a newly created branch is probably the easiest way of achieving that.

Eloign answered 9/12, 2008 at 13:37 Comment(0)
P
3

It's also possible to modify .git/info/attributes file and keep it inside .git folder instead of adding .gitattribute files all over that will require adding them to source control eventually.

Perish answered 18/1, 2012 at 7:24 Comment(0)
L
3

The main question is: how do you want to represent the commits you want to skip?

  1. sneakily hide them (not my favorite)
  2. explicitly skip them
  3. explicitly undo them

Unfortunately no. 2 is impossible to express in the history graph.

No. 1 is possible, but I would never do that: a merge commit can contain changes. – Normally a merge commit points to the result of the merge of two more more branches: all things developed in those branches should be in the code after the merge (i.e. in the code pointed to by the merge commit). And nothing else should be in this commit.

But surprise, surprise, you can change the entire code base and represent it as a merge. The effect of a merge is two-fold: it merges the history tree and it should merge two code bases. The former it does for sure (otherwise no-one calls it a merge), for the latter it might fail, e.g. when a merge conflict arose and it was resolved wrongly (then the code bases were not merged properly).

Some of the other answers suggest this hiding. I recommend the explicit way: merge plus revert commits.

Ledge answered 26/6, 2015 at 14:20 Comment(5)
This is a great strategy for short-lived branches. The problem with this strategy is that when you merge your correctly merged branch (the one with the revert) back into the working branch, the working branch will also receive the revert, because it is a regular, genuine commit. If you have the case that the working branch should really not have the commit you will have to revert the revert. This will continue as long as that particular working branch is in use.Nonmetallic
@RonWertlen You are right with the statement that the master can not be merged again into that “working branch”/release branch without introducing the effect of the reverts. – But this is not just true for my suggested strategy 3 of my answer, but also for strategy 1. – This is also one reason why I personally wouldn’t pursue the workflow from the OP’s question.Ledge
I agree with you too. 1 and 2 are anti-workflows. If you have long-lived branches, and want to permanently keep commits out of one and in the other, it is time to start thinking about restructuring your project, and using sub-modules (with node.js, you also have node_modules as a parallel mechanism for achieving the same thing).Nonmetallic
Further, no 2. is not expressible in history graph, this statement is not 100% clear. Practically it is possible to "lie" yourself a history by resetting or rebasing and rewriting the index of a remote repo with "--force". This is of course a very bad idea.Nonmetallic
@RonWertlen You just reminded me of a way to merge something but skipping some commits. – But this has nothing to do with remotes.Ledge
O
1

If you only want to exclude some commits that are at the end, you can just commit to a specific commit number:

git checkout partlyMergedFrom
git whatchanged
--> find the commit hash up to where you want to merge
git checkout partlyMergedInto
git merge e40a0e384f58409fe3c864c655a8d252b6422bfc
git whatchanged
--> check that you really got all the changes you want to have
Owing answered 26/8, 2011 at 10:9 Comment(0)
M
1

To avoid overwriting problem encountered with @fcurella's answer, start by defining a merge driver that would always favor our current version of the file, by making use of the existing true command. We’ll call this driver ours, to keep in line with similar merge strategies:

git config --global merge.ours.driver true

After that we can implement the @fcurella solution:

Let's say you want to exclude the file .env

On branch A (e.g. Master):

  1. Create a file named .gitattributes in the same dir, with this line:

    .env merge=ours 
    

    This tells git what strategy to use when merging the file. In this case it always keep your version, ie. the version on the branch you are merging into.

  2. Add the .gitattributes file and commit

On branch B (e.g. Develop): repeat steps 1-2

Try merging now. Your file should be left untouched.

Read this for further explanations

Mariahmariam answered 11/11, 2022 at 1:15 Comment(2)
I like this, it solves the problem including the no merge conflict overwrite problem. My use-case is CI/CD management between prod/stage environments. However i hate to depend on a global git configuration, easy to forget and not configure this on other machines.Citronellal
This only works when there is a merge conflict. If the incoming change does not create a conflict, the incoming change will still be used.Entopic

© 2022 - 2024 — McMap. All rights reserved.