ericP
ericP

Reputation: 1915

How can I avoid merging branch-specific changes back to master?

I essentially want to mark a commit in branch B as merged in master so when I (eventually) merge from B back to master, I don't have to reverse those edits. I've tried both cherry-pick and merge --strategy=ours. (How to avoid pushing 'branch-specific' files to master branch? doesn't ask this question.)

Take a simple code repo which has a corresponding .../tests repo for tests. travis.yml clones the .../tests repo for running tests. It has some random commits on master and B:

git init
echo '... git clone --branch=master .../tests' > travis.yml
echo a > a.txt
git add . && git commit -m "master has a.txt and a travis.yml"
git checkout -b B
echo b > b.txt && git add . && git commit -m "B has b.txt"

Now I want travis.yml to grab tests from a parallel branch in a tests repo. In a separate commit, I update travis.yml to checkout B in the test repo:

echo '... git clone --branch=B .../tests' > travis.yml
git add . && git commit -m "B tests on .../tests branch B"

Now I want to mark this commit as merged in master. cherry-pick doesn't work 'cause it only has one parent:

git checkout master
git cherry-pick --no-commit --strategy=ours B
git commit --allow-empty -m "cherry-pick B's tests but keep ours"

This did not have the desired effect of adding the last commit on B to master's history as cherry-picks have only one parent:

git log --abbrev-commit --parents --oneline -1
ee71621 46bf9bb (HEAD -> master) cherry-pick B's tests but keep ours

This problem manifests later when we are ready to merge B, which will tromp our travis.yml with its own:

git merge B -m "absorb B's wisdom"
cat travis.yml 
... git clone --branch=B .../tests

So much for cherry-pick, let's try using a temporary branch. Rewind master and B to before I monkeyed with B's travis.yml:

git reset --hard master~2
git branch -f B B~1

Create a temporary branch with the desired edit:

git checkout -b B-tmp
echo '... git clone --branch=B .../tests' > travis.yml
git add . && git commit -m "B tests on .../tests branch B"

Merge B-tmp into B and merge it into master with --strategy=ours:

git checkout B
git merge B-tmp -m "merge B's branch-specific changes"
git checkout master
git merge --strategy=ours B-tmp -m "keep master's changes"

HEAD now has two parents:

git log --abbrev-commit --parents --oneline -1
a8c0375 166af70 3f661f8 (HEAD -> master) keep --branch=master on master

but the next time I merge master back to B, it tromps travis.yml:

echo aa > a.txt && git add . && git commit -m "aa"
git checkout B
git merge master -m "fetch changes to a.txt"
cat travis.yml
... git clone --branch=master .../tests

I've effectively moved the problem from remembering to update travis.yml once (when B's merged back to master) to having to update it every time I have to keep B up-to-date with master.

What's an elegant way to accomplish this (besides sticking a post-it on my monitor)?

[[ Edit: In light of the "git doesn't do that" answer, I added .git/hooks/pre-commit to tell me when I'd merged something inappropriate into master:

if [ $(git rev-parse --abbrev-ref HEAD) = "master" ] && test $(grep git\ clone travis.yml | grep branch=master | wc -l) != 1
then echo "should be testing on master" ; exit -1
fi

]]

Upvotes: 0

Views: 61

Answers (1)

torek
torek

Reputation: 487765

What's an elegant way to accomplish this (besides sticking a post-it on my monitor)?

There isn't one, in general. There might be one for your specific problem.

The "Git way" to deal with files that should be similar, but not identical, in different commits at different branch-tips is to use multiple files. This works really well and avoids all these headaches ... except when the file name has to be one specific name, common to all branch-tips, to satisfy some external software.

You can often, but not always, work around that by having a Git hook run in post-checkout and/or post-merge to create, as an untracked file, the specific name that the other software demands. This clearly does not work for travis.yml, since that file must be present in the commit that Travis-CI is using.

However, the build step in a travis.yml can include commands that create and then use untracked files, and can inspect the repository in various ways. So you can write up a build script that looks at "what is to be built" and from there, chooses files that depend on what you are building.

In short, don't even attempt to put the full set of rules into travis.yml itself directly. Instead, have travis.yml grab additional rules from branch-dependent files. This may mean giving up some handy Travis feature in favor of re-coding it yourself.

Upvotes: 1

Related Questions