The letter A styled as Alchemists logo. lchemists
Published September 15, 2024 Updated September 15, 2024
Cover
Git Attributes

Git Attributes, if you’ve never worked with them before, provide the following benefits:

  1. File Handling Customization: Allow you to specify how different file types should be treated, enabling precise control over your repository.

  2. Line Ending Normalization: Ensure consistent line endings across different operating systems, preventing unnecessary diffs.

  3. Diff and Merge Strategies: Allow customization of specific file types, improving the handling of non-text files when managing diffs and/or merges.

  4. Binaries: Provide handling of binary files (or large files in general) more efficiently. An example would be with Git LFS (Large File Storage).

  5. Language Specific Settings: Allow customization of specific programming languages, aiding in correct syntax highlighting and formatting.

  6. Encryption: Marking of sensitive files for encryption, enhancing security in your repository.

  7. 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:

  1. Install ExifTool via brew install exiftool so you can view file metadata.

  2. 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):

Diff

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!