Easily manage a chain of commits and their respective Pull Requests using git
This blog post was originally posted as a Q&A style answer on Stack Overflow. I recently edited the answer on Stack Overflow, with a slightly improved method, so I felt like sharing it on my personal blog would be beneficial too.
(A Q&A style question on Stack Overflow is a question that you answer at the same time as you ask the question, kind of like writing a blog post but right on Stack Overflow instead, under the guise of a question)
The original question
I have chain of commits, each commit with its own branch, but they are all part of the same feature.
When I get comments on my Pull Requests (GitHub/Bitbucket/Whatever) I want all the following Pull Requests to get my fixes with as little manual work as possible.
In this scenario no one else is using my branches so I am free to force push to them if I need to.
I used to work at a major Android device manufacturer, and I loved the way Gerrit handled this with ease. Gerrit isn't widely used outside of Android though so I want to find a good workflow that covers the 90% case, even in a large-ish team.
I usually solve this in one of two ways:
Do fixup commits, push them to their respective branch and then merge upwards however many times is needed until all the branches have all the fixes. This can be a lot of work, depending on how long the chain of commits is.
git rebase -iand then do
git push origin <sha>:remote-branchas many times as needed. This is also a lot of work, and afterwards there is a mismatch between the local and remote branches (which can be remedied by
git resetbut that adds even more work). This can also be error prone since it's easy to misspell shas or branch names.
Is there an easier way of managing a chain of commits that are destined for Pull Requests?
I found a really good method for handling this on [William Chargin's blog][https://wchargin.github.io/posts/managing-dependent-pull-requests/], for what he calls "Dependent Pull Requests" which is a good way of describing it.
Edit 2021-11-08: I have started using
git notes for managing the
remote-branch instead of adding it to the commit message. This makes the git commit message history much cleaner.
I have modified the original script, and since it uses an MIT license I have followed the guidelines and added my name as well.
The following modifications were made to the script:
- Make it compatible with macOS by using a regexp that works on the macOS version of
- Use the git notes command
git notes showto get the remote-branch message instead of
- Use a more generic label for the branch name matcher.
- Default to an empty branch prefix.
To follow along in this answer you need to be reasonably skilled using git, and you have probably used
git rebase -i enough to be comfortable with it.
If this is your first git rodeo, it might be wise to get some experience and come back another time.
The method gets you close to the convenience of Gerrit, as long as you are aware of the potential drawbacks:
- Force pushing isn't always a good strategy.
- All the updated branches will have their tests run again if you have CI (which is good, but also bad if your CI is expensive).
- Watch out for remote branch name collision.
- Some reviewers prefer commit-by-commit changes.
- Probably other stuff too!
For this method to work you must configure git to use these settings
(I like to configure it globally, but you can remove
--global if you want it only for a specific repository)
All your commits live on a single local branch which does not necessarily need to be pushed except if you want to keep a remote backup while your work is in progress (recommended).
Add as many commits you need to build your logical chain of dependent pull requests. I once used a chain of 11 commits.
Manage your commits
For each commit in the chain you must add a
git notes that tells the script which remote branch that it should be pushed to. The remote branch does not need to exist. Add the
git notes when creating
the commit or at any time before pushing your changes.
Ready for pushing and creating/updating PRs
I would recommend that you do NOT do a
git rebase -i fixup AND use the script at the same time. Perform any changes and then test your chain of commits first, then you push. That is the way I recommend doing it.
Make sure that all the commits in the chain have a unique
remote-branch: <branch name> line!
To finish it all off call
git rebase -i with the
-x "git push-to-target" parameter.
The magic, of course, is that
git rebase stops after each
pick command and runs the
exec command, which:
- Reads the
- Finds the
- Force Pushes (
--force-with-lease) the current
HEAD(which points to the
- If the remote branch does not exist, it is created. Otherwise it is updated.
If everything worked, you should see updated remote branches in git log.
) ) ) )
If you did not have any Pull Requests, now is the time to create them using whatever service you have (Github, Bitbucket, ...), and have the branches point to each other in order.
If the Pull Requests already existed, they should now be updated and tests running again (if you have CI).
More commits, handling review comments, etc.
- If you get review comments, just
git rebase -iand do any fixups.
- If you do more feature work, just keep adding commits on top and add unique
remote-branch:labels in a
git notesfor each commit.
- Finally push again with
git rebase -i <sha> -x "git push-to-target"(after testing your changes locally, should go without saying).
When everything is merged, you might want to delete your remote backup branch.
- Tip: Add the script to your path, then you can use it as
git config --global alias.ptt push-to-target
git rebase -i <SHA> -x "git ptt"
Note: I have contacted the developer by email to inform him of my changes and ask what he thinks of them, but I have not yet received an answer.
The script should work on Linux/macOS/WSL. I have only tested it on macOS.
The majority of this script was written by William Chargin, I just made it work on macOS and made some minor changes that seemed good to me.
#!/bin/sh # # git-push-to-target: Push this commit to a branch specified in its # commit description. # # Copyright (c) 2017 William Chargin. Released under the MIT license. # Copyright (c) 2020 Erik Zivkovic. # # Originally https://wchargin.github.io/posts/managing-dependent-pull-requests/ # # Usage: For a commit add a git notes line `remote-branch: branchname` then in a rebase do `exec git push-to-target` # # Tip: Add this file to your path, then you can use it as git push-to-target # Tip: git config --global alias.ptt push-to-target # Tip: git rebase -i <SHA> -x "git ptt" # DIRECTIVE='remote-branch' # any regex metacharacters should be escaped BRANCH_PREFIX='' # Add a branch prefix if needed