Reformatting your code base using prettier or eslint without destroying git history

First off I need to thank Elliot Chance for his blog post that helped me get this mission off the ground.

Also, skip to the How it is done section if you don’t want to read my story.

Background

I work for a company that uses node.js (and other frameworks like angular) for the overwhelming majority of our projects. Over the past two years the code has changed lead maintainers a few times from founder to architect 1 to architect 2. With the founder code ranging from “Hello World” level to advanced compiler level code. You can imagine that the format and style of this codebase is far form consistent.

Enter Prettier. After discovering Prettier while it was still in its unstable period I was itching to add it to our standard workflow. So every new project was set up using the formatter from the start. The only thing stopping me from running Prettier on our existing code was the ever so valuable git history.

How it is done

git filter-branch

A tool used for rewriting git history to pretend that something never happened. This is most commonly used for removing large files from git history or if someone accidentally commits a node_modules directory, you can remove it forever. Heres an example of that use case:

git filter-branch --tree-filter “rm -rf node_modules” -- --all

So what is this doing?

Here we are walking every commit from every tree in the git history, deleting the node_modules directory and re-committing. This makes it appear that the author of that commit never added the node_modules directory to begin with. So the silly commit titled Added node_modules will still be there but just be empty in the history.

So with that understanding we can translate it over to formatting the code. We want to walk every commit, format the code, and then recommit. This will give the illusion in the git history that the author of the commit wrote perfectly formatted code from the start! No more ugly “founder code” (in appearance at least). The command I used was:

git filter-branch --tree-filter '\
prettier --no-config --single-quote --tab-width=4\
--print-width=110 --write "**/**.js" || \
echo “Error formatting, possibly invalid JS“' -- --all

Lets break it down:

git filter-branch --tree-filter we have already gone over this.

prettier run the formatted

--no-config I used this because someone committed an invalid package.json in the past so needed to be ignored.

--single-quote --tab-width=4 --print-width=110 my opinionated options

--write Format the file in place, will change the files!

"**/**.js" Glob indicated run this on all JS files. More on this later.

echo "Error formatting, possibly invalid JS" incase someone committed invalid JS in the history this will swallow the error and continue

--all Do the rewrite on all refs in the git history

So the above command will work for all of you out there but it is pretty slow for large projects, talking several hours.

An optimization is:

git filter-branch --tree-filter 'git show $GIT_COMMIT --name-status\
| grep "^[AM]" | grep ".js$" | cut -f2 | tr "\n" " "\
xargs prettier --no-config --single-quote --tab-width=4\
--print-width=110 --write {} || \
echo “Error formatting, possibly invalid JS“' -- --all

Note: This DID NOT work for me but others may have success. Please comment if you see the issue.

What this does is pipe only the added and modified files for that commit into prettier with xargs. This reduced my run from several hours to about 20 minutes.

The same can be done with eslint but it is even slower:

git filter-branch --tree-filter '\
eslint . --fix || \
echo “Error formatting, possibly invalid JS“' -- --all

Conclusion

After you confirmed your code is looking good and your tests are passing, just force push it up to your repo and tell your coworkers to delete the repo locally and re-clone. We did this on production code and so far we have not had any issues (knock on wood). Our code is now pretty with a clean history. Thanks Prettier.