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 inmaster
because of the differences betweenproduction
anddevelopment
environmentsdevelopment
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:
- Makes sure we have the latest code
- Builds a list of commit hashes to cherry pick based on changes to a path in the source branch
- 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.
Nice!
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!