Make It Work: Global .gitattributes

Git is the vanguard of distributed VCSs, and as a matter of course, it provide a way to perform diff or merge on certain files while intermediately treating them as documents of a certain other format. By reflecting appropriate changes to configuration and attributes of git as described in "Git - Git Attributes," you can set up this intermediate conversion for files of your choice while in diff or merge process.

This is best described with an example. Say, there exists a file format called "property list" (hereinafter plist) in Mac, and it can come in 3 forms: XML, binary or JSON (it's not exactly JSON, but is similar to it). To perform diff or merge on files of this type in a consistent way, use command plutil provided by Mac OS X (or an open-source, GPL-licensed alternative libplist). Then take the following 2 steps.

1. Changes to Git Configuration

git config --global diff.plist.textconv "plutil -convert xml1 -o -"
This command sets up a diff driver named "plist" that intermediately converts input plist files of any format into ones of XML format. Replacing diff.plist into merge.plist, you can set up a merge driver instead (be forewarned though, for conversion in merge is no longer intermediate, remember it changes file contents according to the conversion). --global is optional; Per-user or per-repository basis, it's your call.

2. Changes to Git Attributes

Git attributes can be defined like ".gitignore" file (BTW if interested, check my post: You need a .gitignore template? Try, It Rocks!), but I think that fact is not widely known because it's not mentioned in the official documents "Git - Git Configuration" or "Git - Git Attributes." But apparently, there were once a discussion about this global git attributes in the official git mailing list ([PATCH] Add global and system-wide gitattributes) and seemingly the final patch was merged into the source code trunk. Okay, here are 3 ways to set up git attributes;

For per-user basis:
echo "*.plist diff=plist" > ~/.gitattributes
git config --global core.attributesfile "~/.gitattributes"

For per-repository basis without attributes file being placed under your project source tree:
echo "*.plist diff=plist" > ${your_repo}/.git/info/attributes

For per-path basis:
echo "*.plist diff=plist" > ${your_repo}/path/to/.gitattributes
 *.plist is a target file name pattern, and diff=plist is a diff attribute that instructs git to use a diff driver named "plist" when it generates diff text of matching files. Like in Step 1, replacing diff with merge does the same for merge.


If a plist file in your repository is binary and no diff/merge driver was specified, then diff text will look like below. Not informative.
contender-x% git diff
diff --git a/Info.plist b/Info.plist
index f38dced..9ea7d8e 100644
Binary files a/Info.plist and b/Info.plist differ

But if you follows the steps above, then diff text will look like below.
contender-x% git diff
diff --git a/Info.plist b/Info.plist
index f38dced..9ea7d8e 100644
--- a/Info.plist
+++ b/Info.plist
@@ -15,7 +15,7 @@
-       <string>1.0.0</string>
+       <string>1.0.1</string>

Depending on a diff/merge driver you set up, generated diff text changes. My example dealt with textual, sequential data like plist files, so diff text generation was simply done. But use of diff/merge driver for intermediate conversion can do more, such as generating diff text for audio, graphics, or arbitrarily structured data. Creating your own conversion tool for a diff/merge driver can be fun.


Popular posts from this blog

Hardcoding Data Like Images and Sounds into HTML or CSS

DDS for GIMP (Mountain Lion, Native, no X11 GUI)