Dealing With Compiled Files in Git
Why might you need this?
If you have file A
, and every time you change A
, file B
gets rebuilt, and you have to commit file B
, these steps will help you. These examples are written with Sass in mind.
You should avoid committing built or compiled files to Git. Always try to fix that first. These steps are only a last resort.
1. Don't Show Minified CSS In Your Diffs
Why: We don't want to see huge blobs of minified CSS when we run git diff
. We only care about diffs of the source (Sass) files.
How: List built files in a .gitattributes
file, in your repository's root directory:
path/to/your/built/file1.css -diff
path/to/your/built/file2.css -diff
Explanation: This tells Git not to use a diff program when looking at your built files, essentially marking them as binary. You can learn more at the gitattributes documentation.
Now when we run git diff
:
# On branch dingus
#
# modified: path/to/your/built/file.css
Binary files a/path/to/your/built/file.css and b/path/to/your/built/file.css differ
Ah, so nice.
To get the diff back, just pass the -a
or --text
flag. They do the same thing, which is treat all files as text.
git diff -a -- path/to/your/built/file.css
index 57eb03f..278a4a2 100644
--- a/path/to/your/built/file.css
+++ b/path/to/your/built/file.css
@@ -17,7 +17,7 @@
- */.clearfix{*zoom:1}.clearfix:before...
+ */.clearfix{*zoom:1}.clearfix:before...
Caveats:
Github does not respect binary flags, so you will still see full diffs on the "files changed" tab.
2. Don't Let Compiled Files Conflict In A Rebase
Why: When rebasing branches containing minified CSS, the blobs can conflict. We don't want these files to merge, we just want their source files to merge cleanly.
How: Set up a merge driver in your .git/config
file:
[merge "ours"]
name = "Keep ours merge"
driver = true
Then tell your built files to use that merge driver in your .gitattributes
file:
path/to/your/built/file1.css merge=ours
path/to/your/built/file2.css merge=ours
Explanation: When merging built CSS files, use --ours
, as in git checkout --ours file
, which will use their branch's version of the file. That's not a typo, Git is just weird. Instead of trying to merge the files, it just picks one version over the other. The next section tells us how to automatically rebuild the files so they aren't out of sync after a rebase.
Caveats
You can't commit your .git/config
. You must add the merge driver for every repo setup.
3. Rebuild Files Automatically After A Rebase
Why: Remember, the above step will just pick one version of the file, not attempt to merge them. After you have merged the source files (Sass) cleanly in a rebase, let's automatically rebuild the output (CSS) files and sneak them into the commit.
How: Create a Git hook in a file called exactly: .git/hooks/post-rewrite
if [[ $1 = "rebase" ]]; then
echo "\nRebuiling compiled files post $1..."
YOUR SASS BUILD SCRIPT HERE
echo "Adding built files to the last commit"
git add -u
git commit --amend --no-edit
fi
Then chmod +x .git/hooks/post-rewrite
Explanation: Every time you rebase code, ever, it will run your Sass build script and add the changes to the previous commit. In this script $1
will be one of rebase
or amend
. We don't want to run it on amend because it will loop infinitely, since the last step is amending the commit.
For non-Sass examples, just run your build script.
Caveats
- You can't commit Git hooks. Damn you, Linus.
- If you can't run your build script command line (let's say your IDE generates these files automagically), then…uh…go home.
4. Prevent Yourself From Committing Unbuilt Files
Why: This is more trivial, but I find it useful. For example, our Jenkins build server will fail a build if you didn't compile your Sass before committing, to let you know you were working without of date styles locally. I would rather know this at commit time than at build time.
How: Create a pre-commit Git hook in .git/hooks/pre-commit
#!/bin/sh
# Check for built files, and yell at user if not
# Find source files that are marked as modified
sources=`git status -s | grep 'M.*PATTERN\/.*'`
# Find built files that have changed (we don't use M because they could be deleted, etc)
built=`git status -s | grep 'BUILT_FILE'`
# If source files have changed, but not built files, block the commit
if [ -n "$sources" ] && [ -z "$built" ]; then
echo "Build the files, you clod!"
exit 1
fi
Then chmod +x .git/hooks/pre-commit
For Sass, Replace PATTERN
with the name of your Sass directory, and replace BUILT_FILE
with path/to/your/built/file1.css
.
Explanation: Before Git accepts your commit, it will run this hook. If you have changed source file A
, and compiled file B
that depends on A
is not changed, it will prevent you from committing. We use exit 1
(quit script and return status code of 1
) which means there was an error, and Git will not let you commit.
You can learn more about bash's horrifying conditional syntax at Introduction to if.
That's it!
If this article helped you improve your Git setup, consider following me on Twitter or buying me a coffee :).