Git Attributes, if you’ve never worked with them before, provide the following benefits:
-
File Handling Customization: Allow you to specify how different file types should be treated, enabling precise control over your repository.
-
Line Ending Normalization: Ensure consistent line endings across different operating systems, preventing unnecessary diffs.
-
Diff and Merge Strategies: Allow customization of specific file types, improving the handling of non-text files when managing diffs and/or merges.
-
Binaries: Provide handling of binary files (or large files in general) more efficiently. An example would be with Git LFS (Large File Storage).
-
Language Specific Settings: Allow customization of specific programming languages, aiding in correct syntax highlighting and formatting.
-
Encryption: Marking of sensitive files for encryption, enhancing security in your repository.
-
Export Control: Specify which files should be included or excluded when creating archives of your repository.
I won’t detail all features — because there is a lot you can do — but will provide with you with a few helpful customizations that I use (or sometimes use) in my Dotfiles.
Configuration
There are multiple ways to configure your Git Attributes. I use a global configuration so my settings are applied to all projects via a XDG configuration (highly recommend):
$HOME/.config/git/attributes
Here’s my source code, if it helps.
You can also apply a project-specific configuration. For example, if your project is called demo, then you could use demo/.gitattributes. Keep in mind that these attributes are applied to anyone using your project. I find this too be too heavy handed in practice, though. Having good global configuration hygene is all you need with rare situations where you need a local configuration.
That said, should you want a local configuration that doesn’t effect the rest of the team, my favorite is to use a .git/info/attributes file in the root of the project since it only applies to you and is invisible to the rest of the team. This also a nice way to override any project settings in which a project is poorly managed and you don’t want to cause any strife.
Binaries
⚠️ First off, avoid committing binary files to your repository. This is a bad practice because Git can’t diff binary files due to making an entire file copy for each change. This grows repository size quickly and you want to keep your repository footprint small. Diffing large binary files quickly adds up to megabytes, even gigabytes, which makes repository maintenance and cloning pure misery.
Warning aside, there is a way to configure Git look at file metadata for diffing purposes. For example purposes, let’s say you have a 100x100 pixel demo.png image committed to your repository. Now let’s make a change to that file using GraphicsMagick and perform a diff:
brew install graphicsmagick
gm convert -resize "10x10" demo.png demo.png
git diff
Running the above will convert our original image to a 10x10 resolution and produce the following diff:
demo.png (binary file)
Sadly, the above is only stating the obvious. The way to make the diff more useful is via these steps:
-
Install ExifTool via
brew install exiftoolso you can view file metadata. -
Next create to your local Git Attributes for your project via
printf "%s\n" "*.png diff=exif" > .git/info/attributes.
With the above changes, you can run git diff again. This time you’ll see the following output (or similar output):
Notice, amongst other metadata, we can more clearly see image width, height, and size changes. With Git Attributes in place, the git show command is enhanced as well:
git show icon.png
# ExifTool Version Number : 12.76
# File Name : demo.png
# Directory : /var/folders/dg/2rgbfbr11m3bn5w9hmxsg5080000gn/T//git-blob-ZaTqSZ
# File Size : 4.6 kB
# File Modification Date/Time : 2024:09:07 16:38:48-06:00
# File Access Date/Time : 2024:09:07 16:38:48-06:00
# File Inode Change Date/Time : 2024:09:07 16:38:48-06:00
# File Permissions : -rw-------
# File Type : PNG
# File Type Extension : png
# MIME Type : image/png
# Image Width : 100
# Image Height : 100
# Bit Depth : 8
# Color Type : Grayscale
# Compression : Deflate/Inflate
# Filter : Adaptive
# Interlace : Noninterlaced
# Image Size : 100x100
# Megapixels : 0.010
Definitely an improvement over the one line output from earlier but, again, avoid committing binaries to your repository.
Filters
Filters provide powerful automation by modifying file contents when files are checked in or out. To use filters, you’ll need to modify both your Git Configuration and Git Attributes.
For example, let’s say you’d like to filter sensitive information from being checked into your repository (clean) but remove the filter upon checkout (smudge). You could do this by first adding filter instructions to your Git Configuration:
[filter "encrypt_password"] clean = sed -e 's/^password=.*/password=ENCRYPTED/' smudge = sed -e 's/^password=ENCRYPTED/password=my_secret/'
Then you can add the following to your Git Attributes:
*.secrets.txt filter=encrypt_password
With our Git Configuration, we use sed to clean our secrets.txt file by replacing our password with ENCRYPTED as the value. This filtering happens automatically each time we commit changes to the file. When you push changes to the remote repository, ENCRYPTED is all you’ll see for the value. On the flip side, when checking out changes our filter will smudge our secrets.txt file by replacing ENCRYPTED with our private password: my_secret. If it helps to visually this further, this is what your secrets.txt file will look like when viewing it locally or remotely:
# Local password=my_secret # Remote password=ENCRYPTED
There is a quite a bit you can do with filters so read the Git Attributes documentation to learn more.
GitHub
For folks that use GitHub, you can override the automatic language detection that GitHub applies to your project. This is useful for situations where you might use multiple languages for your project but want to ensure the language you desire is marked as the primary language.
For instance, to make your project a Ruby project, you’d only need to add:
*.rb linguist-language=Ruby
If you need to callout specific files, use:
path/to/implementation.rb linguist-language=Ruby
You can also do the following, for example:
# Override language detection for JavaScript files. *.js linguist-language=TypeScript # Mark all Markdown files as documentation. *.md linguist-documentation=true # Exclude generated files. build/*.js linguist-generated=true # Exclude vendored code. vendor/* linguist-vendored=true
💡 Keep in mind, all of the above changes only apply to GitHub. Git won’t be affected.
Conclusion
As mentioned at the start of this article, this is my no means exhaustive in what you can do with Git Attributes so I recommend diving into the documentation to learn more. Hopefully, this is enough to wet your appetite to explore/experiment more. Enjoy!