Git’s rebase command takes an --exec
option to run a shell command on every revision in the rebase.
For example, to run a formatter like Prettier1 over each file in your repository for every past revision2:
git rebase -i --exec 'prettier --write {**/*,*}.js' ffcfe45
Being an interactive rebase, Git opens up your $EDITOR
to show the actions that are about to be executed.
Although this rebase spans nine commits, there are eighteen actions as the commands are run as separate steps:
pick 22b042f npm install --save-dev mocha exec prettier --write {**/*}.js pick 76995a2 echo node_modules >> .gitignore exec prettier --write {**/*}.js pick 85d9d77 Use mocha as test script exec prettier --write {**/*}.js pick cfa165c Add failing test for crush.crush() exec prettier --write {**/*}.js pick 93cfd2a Minify with cssnano exec prettier --write {**/*}.js pick c9dcfb4 Add failing test for purging exec prettier --write {**/*}.js pick 1703abd npm install --save-dev @fullhuman/postcss-purgecss postcss exec prettier --write {**/*}.js pick 5faa328 Purge css with purgecss exec prettier --write {**/*}.js pick bc787ae Add bin/crush.js exec prettier --write {**/*}.js # Rebase ffcfe45..bc787ae onto 5faa328 (18 commands) # # Commands: # p, pick <commit> = use commit # r, reword <commit> = use commit, but edit the commit message # e, edit <commit> = use commit, but stop for amending # s, squash <commit> = use commit, but meld into previous commit # f, fixup <commit> = like "squash", but discard this commit's log message # x, exec <command> = run command (the rest of the line) using shell # b, break = stop here (continue rebase later with 'git rebase --continue') # d, drop <commit> = remove commit # l, label <label> = label current HEAD with a name # t, reset <label> = reset HEAD to a label # m, merge [-C <commit> | -c <commit>] <label> [# <oneline>] # . create a merge commit using the original merge commit's # . message (or the oneline, if no original merge commit was # . specified). Use -c <commit> to reword the commit message. # # These lines can be re-ordered; they are executed from top to bottom. # # If you remove a line here THAT COMMIT WILL BE LOST. # # However, if you remove everything, the rebase will be aborted. # # Note that empty commits are commented out
When running the rebase, Git halts whenever the command returns a non-zero exit code to allow you to check what happened.
In this example, the first couple of commits don’t have any JavaScript files to format, which are skipped with git rebase --continue
:
Executing: prettier --write {**/*,*}.js [error] No files matching the pattern were found: "**/*.js". [error] No files matching the pattern were found: "*.js". warning: execution failed: prettier --write {**/*,*}.js You can fix the problem, and then run git rebase --continue
Each commit gets picked before the execution happens, which can cause conflicts between formatted and still-unformatted code.
In that case, remember that the next exec
step reformats the code and amends it to the commit, so you can pick the unformatted version and have the formatter do the formatting.
: Examples with different formatters:
mix format
(Elixir)git rebase -i --exec 'mix format' main
- Rubocop (Ruby)
git rebase -i --exec 'rubocop --auto-correct' main
- Prettier (Node.js)
git rebase -i --exec 'prettier --write {**/*,*}.js' main
:
↩︎ffcfe45
is the commit that adds prettier to the project. I injected that commit after the others were already written to be able to the formatter retroactively to clean up history.