I work on a few projects right now, some using the Android code-style and some their own. This is very confusing, and always causes problems when pushing a commit for merge.

I’m trying to consolidate all the projects into Android’s code-style. Specifically, no-tabs and 4 spaces indents.

Game Plan

Going over all the files and changing the tabs to spaces is, of course, not an options. More over, we do not want our name on every single git blame code line! So, we’ll need to convert all the tabs to spaces, ensure all indentation is 4 spaces, and re-write history to hide it.

Prerequisites

  • Linux or Mac OSX machine. If you know how to do it under Windows, please leave a comment.
  • You’ll need git push -f permission to the remote repo. That’s the only way to re-write history.
  • expand should be installed on you machine

Switching Tabs to 4 Spaces

find . -name '*.java' ! -type d -exec bash -c 'expand -t 4 "$0" > /tmp/e && mv /tmp/e "$0"' {} \;

Here’s what we are doing with that command:

  • find all the Java files in the repo.
  • On each file, execute expand (using bash) which will convert tab characters to 4 spaces, and save into a temporary file.
  • The move the temporary file to overwrite the original file.
  • If you also want to convert your XML files to spaces, then run the same command with *.xml filter.

Commit the Changes

git add .
git commit -m "converting tabs to spaces"
Now, we have a commit in our local git repo that holds all the changes. Let’s get the hash for the commit using git rev-parse HEAD. For our example, let’s say it is 0867e54002195d16dbfb69076d1df2d8b1f83e6f.

Re-write git History

git filter-branch --tree-filter 'git diff-tree --name-only --diff-filter=AM -r --no-commit-id 0867e54002195d16dbfb69076d1df2d8b1f83e6f' HEAD
This is the cool part: hiding all the evidence that you made the switch. This will ensure that when developers check git blame on a file, the tabs->spaces changes will not show.
This takes a very long time to run. The bigger the repo the longer it will take.

Force push to master

git push -f origin master
Pushing to the remote repo (be it origin or upstream).

Possible Problems

  • filter-branch creates a new, revised, branch in your git repo. This means a few things:
  • the size of your repo may be doubled!
  • hash of the commits in your history will be changed! This means that if you have a link to a specific commit, this link might be dead after this process. The reason for that is that if git sees that nothing is pointing to a specific commit, it will garbage collect it.
    A simple way to fix that, is to use tag, and annotate the commit prior to starting this process.
  • Applying the filter-branch may fail (“Cannot create a new backup.”) because of an existing backup in your refs folder. Delete those with rm -rf .git/refs/original/refs/heads/master from the root of your repo.
  • If you get ! [rejected] master -> master (non-fast-forward) when pushing the changes to your git repo, it means that you do not have force push permissions in the git repo. Talk to the owner.
  • Previous tags are kept, and are pointing to the original commit, not the revised one. So we are not completely rewriting history.

blog comments powered by Disqus