At Ableton, we use git extensively, aiming for a very good git history that tends to look like literate programming. Therefore, I have accumulated a few git config and commands to help with these workflows. To get utmost confidence when writing this page, I referenced the git rebase help page, it is a very good documentation piece that I recommend you to read top to bottom one day.
Here are useful git config flags and commands that solves common situations.
useful git config flags
Avoid adding flags that are almost always used.
git config --global rebase.updateRefs truegit config --global rebase.rescheduleFailedExec truegit config --global rebase.rebaseMerges no-rebase-cousinsgit config --global rebase.autoStash truegit config --global rebase.autoSquash true- bonus:
git config --global commit.verbose true
They can still be disabled with --no-flag, for example --no-update-refs
The default flags of rebase are conservative, but I found that the optional rebase algorithms/heuristics to be predictable enough that I was always using them.
So to avoid having to specify them over and over, I put them in options:
- updateRefs: rebase branches that are on the way
- rescheduleFailedExec: if an exec step fails, reschedule that step in the todo so that the exec step will run again when continuing the rebase
- rebaseMerges: if the commits to rebase contain merge commits, don’t drop merge commits. I use no-rebase-cousins for simplicity but more advances use cases may require rebase-cousins instead.
- autoStash: stash before the rebase, stash pop after the rebase
- autoSquash: automatically resolve fixup/squash/amend/reword commits
- commit verbose: adds to the commit message the commit diff to make it easy to read the commit content when editing the commit log. especially useful when rewording multiple commits in the same git rebase run
Intro: git-rebase-todo file format
git rebase --interactive will automatically call git rebase --edit-todo before starting the rebase.
pick deadbee The oneline of this commitpick fa1afe1 The oneline of the nextcommit. . . |
It is just an ordered list of $command $commit-sha $some-description-that-will be-ignored :
pickthe commitdropthe commit (deleting the line also drops the commit)editthe commit: rebase will stop here and can be continued withgit rebase --continuebreak: rebase will stop herereword: interactively open the editor as if you did agit commit, and automatically continues once the editor is closedsquash: fold two or more commits into one, and open the editor to edit the commit message, and automatically continues once the editor is closedfixup: fold two or more commits into one, only keeping the commit log message of the first commit
git rebase will stop if a commit fails to apply. When you are done editing and/or resolving conflicts you can continue with git rebase --continue.
FAQ: I created a branch at some point but it needs to be on origin/master
if you have a history like this:
X \ A---M---B /---o---O---P---origin/master |
Suppose you want to rebase the side branch starting at A onto origin/master . Make sure that the current HEAD is B , and call
git rebase -i -r --onto origin/master O |
FAQ: I want to reorder and edit commits. How can I check that it still compiles?
pick deadbee Implement feature XXXfixup f1a5c00 Fix to feature XXXexec makepick c0ffeee The oneline of the nextcommitedit deadbab The oneline of the commit afterexec cd subdir; make test |
If it stops at some point, you can continue with git rebase --continue .
You may want to check that your history editing did not break anything by running a test, or at least recompiling at intermediate points in history by using the “exec” command (shortcut “x”). You may do so by creating a todo list like this one.
The interactive rebase will stop when a command fails (i.e. exits with non-0 status) to give you an opportunity to fix the problem. You can continue with git rebase --continue.
FAQ: I want that each commit of my branch configures and builds. How can I check that every commit builds?
$ git rebase -ix "make" origin/master |
This command lets you check that intermediate commits compile correctly. The todo list becomes like that:
pick 5928aea oneexec "make"pick 04d0fda twoexec "make" |
FAQ: I created commits on a branch, but then realize an earlier commit should be fixed
git add fileA.txtgit commit -m "A"git add fileB.txtgit commit -m "B"echo "lorem ipsum" >> fileA.txtgit add fileA.txtgit commit --fixup COMMIT-SHA
You can continue working as long as needed. Eventually then, you can rebase all the changes and the TODO will automatically contain fixup todos.
git rebase -i origin/master
Also, if you want to modify the commit log message you can:
git commit --fixup=amend:COMMIT-SHAthat will modify the content, and replace the commit log messagegit commit --fixup=reword:COMMIT-SHAthat will only replace the commit log message
FAQ: One old commit I did is too big. How can I split into two commits?
git rebase -i COMMIT-TO-SPLIT-SHA^(^meansparent)- mark the commit to split with the action “edit”
git reset HEAD^git add(possibly interactively) orgit guigit commitwith the appropriate message- repeat last two steps until working tree is clean
git rebase --continue
FAQ: One old commit do not belong to this branch. How can I split into two branches?
This commit is largely inspired from the git rebase documentation. Let us imagine a branch called matthieutalbot-cmake , that contains a commit modifying some tls support (unrelated to cmake). First, we create the needed branch
$ git checkout -b |
then rebase todo will look like
label ontoreset ontopick 192837Switch fromGNU Makefiles to CMakepick 5a6c7eDocument the switch to CMakepick 918273Fix detection of OpenSSL inCMakepick afbecd http: add support forTLS v1.3pick fdbaec Fix detection of cURL inCMake on Windowsupdate-ref refs/heads/ |
and you can make it look like that
label ontoreset ontopick afbecd http: add support forTLS v1.3update-ref refs/heads/mtt-tlsv1.3pick 192837Switch fromGNU Makefiles to CMakepick 5a6c7eDocument the switch to CMakepick 918273Fix detection of OpenSSL inCMakepick fdbaec Fix detection of cURL inCMake on Windows |
The one commit in this list that is not related to CMake may very well have been motivated by working on fixing all those bugs introduced by switching to CMake, but it addresses a different concern. To split this branch into two topic branches, the todo list could be edited like this.
label/reset/merge instructions are explained in the --rebase-merges documentation: in contrast to a regular interactive rebase, there are label, reset and merge commands in addition to pick ones.
The label command associates a label with the current HEAD when that command is executed. (These labels are created as worktree-local refs (refs/rewritten/<label>) that will be deleted when the rebase finishes. That way, rebase operations in multiple worktrees linked to the same repository do not interfere with one another. )
The reset command resets the HEAD, index and worktree to the specified revision. It is similar to an exec git reset --hard <label>, but refuses to overwrite untracked files.
For completeness, there is also the merge command which merges the specified revision(s) into whatever is HEAD at that time. -c or -C With -C <original-commit>, the commit message of the specified merge commit will be used. When the -C is changed to a lower-case -c, the message will be opened in an editor after a successful merge so that the user can edit the message.
Failed label , reset and merge commands are always rescheduled.
FAQ: I have updated multiple branches. How can I push them all at once?
By using some convention for branch naming, you can then use this snipped (if I prepend all my branches with matthieutalbot-)
$ cat >> $HOME/.bashrc <<'HERE_DOC'function gpushforce (){ git branch --list' xargs -t git push --force-with-lease origin}HERE_DOC |
FAQ: I have pushed multiple branches on one computer. How can I pull them all at once?
By using the same convention for branch naming as gpushforce (see above)
$ cat >> $HOME/.bashrc <<'HERE_DOC'function gfetchforce (){ echo "Careful not to loose any local commits!" git branch --list ${BRANCH_PREFIX}* --format='+%(refname:lstrip=2):%(refname:lstrip=2)' | git fetch origin --verbose --update-head-ok --stdinHERE_DOC |