Have you ever wanted to make local changes to a repository for your own benefit, but don’t want to send them remotely? Maybe you’ve once wanted to:
- Work around scripts insisting that you have a tool like
yarninstalled globally, but you correctly recognize that as being stupid and need to wrap it?
- Add some Dockerfiles for more productive development on your end, but only want to keep them to your machine?
- Append some extra helpful commands to a
package.jsonto quickly rebuild or test things?
- Change some invocation flags (e.g. configs, heap size, local web server ports) to make stuff work better on your own system?
- Patch out the execution of long-running tests locally, and leave them to the CI instead?
- Nix-ify the development environment so that installing the build tools won’t conflict with anything else on your system?
- Develop on OSX but the rest of the team is on Linux so your build scripts need adjusting?
- Make literally any kind of suit-tailored change for your own setup?
You’ve probably had to do such things before and ended up with a small mess of untracked files, changes that you need to be ‘careful not to accidentally stage’, and loose-leaf changes scattered in places.
We can do much better. You can productively accomplish all this and more with a pattern I like to use and abuse: the
The idea is simple. We’ll make a new branch called
dev, configure git to not push commits from it anywhere, put all our custom tailorings in it, and then learn how to use it as our new base of operations when writing pull requests.
Oh, yeah, if you’re not using git then I don’t know what to tell you. Stop reading here, I guess.
Make a new branch off of
main, and call it
$ git checkout main $ git checkout -b dev
This branch will be what we use all the time now. We’re almost never going to spend time on
dev branch is where we hang out now. Especially on the weekends, or if it’s raining.
Set up pre-commit githooks
We’re going to be adding our bespoke, tailor-made commits to this branch and we don’t want to share them with anybody else. We’ll use git hooks to prevent accidental sharing.
.git/hooks/ folder, there should be a
pre-push.sample example of a pre-push hook. If you don’t have it, you can find it on github here.
Copy it without the
$ cp .git/hooks/pre-push.sample .git/hooks/pre-push
The example shows how to prevent pushing of commits that start with “WIP”. We’re going to edit it to prevent pushing commits that contain “nocommit” anywhere in the message. Apply this patch:
@@ -19,6 +19,9 @@ # This sample shows how to prevent push of commits where the log message starts # with "WIP" (work in progress). + +echo "Checking for 'nocommit' commits" + remote="$1" url="$2" @@ -40,11 +43,11 @@ range="$remote_oid..$local_oid" fi - # Check for WIP commit - commit=$(git rev-list -n 1 --grep '^WIP' "$range") + # Check for NOCOMMIT commits + commit=$(git rev-list -n 1 --grep '.*nocommit.*' "$range") if test -n "$commit" then - echo >&2 "Found WIP commit in $local_ref, not pushing" + echo >&2 "Found NOCOMMIT commit in $local_ref, not pushing" exit 1 fi fi
By putting it into a file and using it like this:
$ patch .git/hooks/pre-push < patchfile
Test the push hook
We’re now going to make an ‘empty’ commit, and try to push it. It should fail. If it succeeds – no harm done, nobody is going to really notice anything.
$ git commit -m "sentinel - nocommit - sentinel" --allow-empty $ git push origin dev
Git should refuse to push this. If so, then it worked. If it did push just fine, delete the remote branch, debug it and try again.
Be very careful about making sure the hook is present. It is not stored in git itself, but only on your local filesystem. Take care if you move your dev branch to another machine and push.
Make all the tailorings you want
This “sentinel commit” will refuse to be pushed. That also means that any commits you make on this branch afterwards will also be refused. You can now make whatever tailorings you’d like, and commit them safely onto
dev. They don’t need to contain the magic
nocommit keyword from our pre-push hook. Only the sentinel commit does, and we’ve taken care of that.
Go ahead and change those port numbers or build commands. Add whatever scripts or Dockerfiles you like. Nix-ify the entire build. Go wild. Commit it all.
How to get work done
dev branch is now your base of operations. You will no longer create topic branches off of
main, but will do so off of
$ git checkout dev $ git checkout -b my-new-feature # do some work $ ./run-my-secret-tests.sh $ git commit -avm "fix thingy" $ git push # uh oh, how do i push this up? fuck
The workflow is almost the same, but you’ll find git won’t let you push your work up at the end. It contains all your tailorings, plus the feature!
To get stuff done, we’ll have to become a little bit familiar with
git rebase. Here’s the magic trick:
$ git rebase dev --onto main
This will take
my-new-feature, slice off all the commits since the last tailoring you made on
dev, and slide them over onto
main. Think of it like lifting it off the dev stuff and plopping it down on main.
This branch is now ready to push, with nobody the wiser.
Pulling down other people’s branches
My colleague just pushed up their own
fixes-huge-bug. I want to get all my tailorings back while I look at the branch. How do I do it?
Another dash of
$ git checkout fixes-huge-bug $ git rebase dev # browse through the code however you like now
This will put all our tailorings underneath the commits of
When I said the
dev branch will be our base of operations, I wasn’t kidding.
Updating the dev branch
Your dev branch will have been made against a “snapshot” of main at the time it was put together. Work on main carries on, and we wish to have those changes incorporated. How do we do it?
rebase to the rescue:
$ git checkout main $ git pull # get latest stuff $ git checkout dev $ git rebase main # dev is now sitting atop the latest main
This will take our tailorings and slide the newest
main underneath. This could have merge conflicts, for instance if you’ve modified a local server port for yourself and then somebody did it for real in
You’ll need to just resolve them if they happen.
Once this is done, you may wish to update topic branches that you’ve made off
$ git checkout my-great-fix $ git rebase dev
Again, this slides in the newest tailorings made on top of the newest
main happenings, underneath the commits for
Adding more tailorings later
You’re working on a feature, and you realize you want to add another change for yourself which would be really handy while writing this feature. How do you do it?
$ git checkout dev # add some more custom stuff for ourselves $ git commit -vm "added another great local thing" $ git checkout - # dash goes back to the branch we were just on $ git rebase dev
With this we can go back to our
dev branch, add the additional tailoring, and then come back to our feature branch and slide in the latest
dev work underneath. You’ll need to rebase each feature branch you have locally, in order to get the freshest tailorings incorporated into it.
This is the
dev branch pattern. I use it / abuse it literally all the time. Very few repositories go by without me wanting to make some sorts of adjustments for myself.
I hope this trick helps you be a more productive engineer. Please use and abuse it.