Easy partial branch merging in git

Recently I have been trying to diversify my usage of source code control so as to be more familiar with the different tools that are currently popular so I have been using git more for personal projects and recently I came across a process hurdle that didn’t seem to have a simple solution:

  • We have a project in git hosted as a private repo at GitHub
  • We have multiple contributors
  • Development is happening in a development branch
  • development contains many commits that we don’t want in master because of the differences between production and development environments
  • development also contains code we don’t want to release yet
  • We want to maintain attribution for code changes in what is merged to master

All in all, it felt like this should be simple, right, surely I can just git checkout master && git merge development {path_to_folder_containing_code} right … well not really.

After a lot of searching around it seemed that the recommendation in general in our situation was to just “copy the changes files into a checkout of master and commit them”.  I really didn’t want to do this because loosing direct attribution was a no go – yes we could review the branch history to work out who did what but I believe that everyone who participated should get attribution via their GitHub profile activity graph.

More searching led to a second option, which also sounded very painful, we could use git cherry-pick and cherry pick every single individual commit over into master.  This ticked all of the boxes as far as what I wanted to achieve and the only downside was going to be the manual work of cherry picking.. it didn’t feel like it was going to be fun and certainly wasn’t going to be sustainable as we iterated further on the code.

So I set out to see if I could come up with a “simple” was to automate this process and so the following script was born:

#!/usr/bin/env bash -l
#args - from-branch to-branch path
FROMBRANCH=$1
TOBRANCH=$2
REPOPATH=$3
INIT=$4

git fetch
git checkout $FROMBRANCH
git pull
git checkout $TOBRANCH
git pull
git checkout $FROMBRANCH
if [[ 'init' = $INIT ]]; then
	COMMAND="git log --format="%H" --reverse --cherry-pick --no-merges $TOBRANCH..$FROMBRANCH $REPOPATH"
else
	COMMAND="git log --after=`git log $TOBRANCH -n 1 --format="%aI"` --format="%H" --reverse --cherry-pick --no-merges $TOBRANCH..$FROMBRANCH $REPOPATH"
fi
CHERRIES=$(eval $COMMAND)
git checkout $TOBRANCH
for CHERRY in $CHERRIES
do
	git cherry-pick --ff $CHERRY
done

Basically this:

  1. Makes sure we have the latest code
  2. Builds a list of commit hashes to cherry pick based on changes to a path in the source branch
  3. Cherry picks each individual commit hash into the target branch

The script has two modes based on whether or not you have already existing code in the target location (if you don’t you pass the extra init argument to tell it to replay all the history).

Example usage: ~/bin/git-merge-path-from-branch-to-branch.sh development staging {path/to/folder}

This will merge everything over to a staging branch which can then be used to generate a pull request against master.

So far this has been working really well for us, the only time it has had issues are when someone makes changes to master outside of working in development first because then we end up with the wrong “last merge date” detection in the script.

One thought on “Easy partial branch merging in git

  1. Nice!

    the only time it has had issues are when someone makes changes to master outside of working in development first

    If you don’t want folks to commit to master, you could lock that branch in your GitHub settings, so only a few selected folks can commit to it. Another alternative could be to stop all direct commits to master and force everyone to create PRs instead, but then you would have to change your script 🙂

    I guess you’ve thought of all that already though 🙂

    That sure looks like an interesting challenge!

Leave a Reply

Your email address will not be published. Required fields are marked *