0

We have a challenge with how things have been merged into two different branches in our (public) Git repository.

We have two branches:

master (commits: a, b, c, d, e) maintenance-8.x.x (commits: a, x, y, z) 

Different feature branches have been merged into each of these branches (via GitHub PRs)

Goal: Create version-8.1 containing:

  • All commits from maintenance-8.x.x (x, y, z)
  • Only some commits from master (c, e) — excluding b, d

Later merge maintenance-8.x.x changes back into master.

My first thought was to branch version-8.1 from maintenance-8.x.x, then cherry-pick or rebase all of the many commits from master that we want in 8.1. However, I've run into two problems, and I need help figuring out how to avoid these two problems or deal with them.

First, if the commits are rebased or cherry-picked, we end up with the same changes being applied with different commit IDs. That causes the issues discussed here: Git commits are duplicated in the same branch after doing a rebase

I already got a few such duplicate commits in master before I stopped to ask for help.

The other problem is I found myself having to resolve the same conflicts over and over as described here by @Whymarrh: Git workflow and rebase vs merge questions

How can we achieve this selective merge without:

  • Creating duplicate commits that mess up future merges?
  • Resolving the same conflicts repeatedly?

The actual repo involved is https://github.com/iNavFlight/inav/

5
  • As I continued to research this, I found I might be able to leverage the fact that there is a merge commit for each set of commits- they are Github PRs. And I can either use or skip the entire PR/merge, not needing to choose individual commits within each merge. Therefore maybe I can do something like this?: git cherry-pick -m 1 --keep-redundant-commits $pr_merge_commit Would that be a mistake? Commented Jul 13 at 6:25
  • "Without making a mess"? Too late, you already have a mess of features in the master branch. To not have this mess, put each feature on a branch of its own. Fork this branch from a commit which you know with absolute certainty will be in the next release that could have a slight chance to have this feature. Then merge all feature branches to master. Later, merge only the features selected for release into the release branch. You get a very branchy history with many merges. Some might say that that's a mess, but I say it isn't. Commented Jul 13 at 7:31
  • "Only some commits from master (c, e) — excluding b, d" — Why do you need to exclude them? Commented Jul 13 at 9:02
  • @RuslanOsmanov "Why do you need to exclude them?" -- They break backward compatibility, and therefore cannot be in a point release. Rather, they'll be in the next major version release (9.0.0). Commented Jul 13 at 17:10
  • It's impossible to pick c and e, excluding d, without changing their hashes because each hash is calculated using the parent commits. So, I'm afraid, the answer is: you can't. It's possible to resolve the problem without the constraint of keeping the hashes unchanged (using cherry-pick, rebase, and even revert + merge). Still, these solutions don't save you from the "mess" you want to avoid. Commented Jul 14 at 8:26

2 Answers 2

1

Get the list of commit hash(es) for the commits you wish to merge using the git log.

git log <branch-name>

Then add run the below command for all the commit hashes to pick all the commits you wish to pick.

git cherry-pick <commit-hash>

then use

git push origin <target-branch>

You can also use this refrence link:

https://betterstack.com/community/questions/how-to-merge-specific-commit/

Sign up to request clarification or add additional context in comments.

Comments

1

The basic scheme: make a prereqs branch of just the specifics you want to import from your master branch to your new branch, and record the resulting history structure, tell Git where things go:

git checkout -b 8.1-prereqs $(git merge-base master maintenance-8.x.x) # select prereqs... git cherry-pick c e # ... c and e git checkout -b maintewnance-8.1 mainteance-8.x.x # for new 8.1 git merge 8.1-prereqs # with that selection git checkout master # let Git know git merge -s ours 8.1-prereqs # prereqs are in master 

As a smoketest, I picked a commit set at whim from your history (and thank you for providing something concrete to work with), the ones affecting barometer.[ch], so the cherry-pick above, after the merge-base checkout, was

git rev-list --no-merges --reverse ..master -- \*/barometer.[ch] \ | git cherry-pick --stdin -Xtheirs 

and actually picked five commits.

After that merging master and the new 8.1 branch had no conflicts, which was the reason for recording where the cherry-picks had been merged to.

Basically, if you're going to be maintaining multiple deployment targets, make deployable changes on separate branches and merge those into the deployment targets. This will make your deployment targets (like your everything-that-works master branch) a collection of merged, discrete changesets. If what you're doing here is unusual for you you can do the pick-out-the-changesets-a-la-carte as shown here, cherry-picking a changeset branch out of the main line, merging it where it needs to go and then recording the slice back to the master so Git sees it.

2 Comments

Thank you for this. I think the last two commands may be highlighting what I've been missing this whole time, so let me ask about that specifically. 8.1-prereqs would contain c and e, changes we cherry-picked from master, right? But at the end, you check out master and merge 8.1-prereqs into master. From my (wrong?) understanding, that would bring c and e into -- which is where we got them from to start with? What is that merge, because in my (mis)understanding it seems like it wouldn't do anything?
Git history is snapshots, that -s ours merge sets up just its first-parent snapshot as-is but recorded as a merge of what's new in the second parent -- which is exactly the interesting part here, this way wherever you merge the 8.1-prereqs branch Git can explicitly see that those changes have been merged and reason about them, … without having to check through every possible permutation of commit sequences. Situations like this are rare enough that in Git you record them only as needed, and explicitly.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.