views:

278

answers:

4

A common scenario when I develop is that the codebase will have several config files which require machine specific settings. These files will be checked into Git and other developers will always accidentally check them back in and break someone else's configuration.

A simple solution to this would be to just not check them in to Git, or even to additionally add a .gitignore entry for them. However, I find that it is much more elegant to have some sensible defaults in the file which the developer can modify to suit his needs.

Is there an elegant way to make Git play nicely with such files? I would like to be able to modify a machine-specific configuration file and then be able to run "git commit -a" without checking that file in.

+5  A: 

One possibility is to have the actual files in your .gitignore, but check in default configurations with a different extension. A typical example for a Rails app would be the config/database.yml file. We would check in config/database.yml.sample, and each developer creates their own config/database.yml which is already .gitignored.

hgimenez
Yeah this is an incremental improvement, but it is still not optimal since if the checked in version is intentionally changed, it is not reflected on the developer config files. An example of when that would be useful is when a new property is added etc.
Gordon
That could be address with good commit notes and descriptive error messages that complain when a property isn't set. Also an email communicating to your team about the change helps.
spilth
+2  A: 

Another approach is to maintain local changes to common configuration files in another private branch. I do this for some projects that require several local changes. This technique may not be applicable to all situations, but it works for me in some cases.

First I create a new branch based on the master branch (in this particular case I'm using git-svn so I need to commit from master but that's not terribly important here):

git checkout -b work master

Now modify the configuration file(s) as necessary and commit. I usually put something distinctive in the commit message like "NOCOMMIT" or "PRIVATE" (this will be useful later). At this point, you can work away on your private branch using your own config file.

When you want to push your work back upstream, cherry-pick each change from your work branch to the master. I have a script to help do this, which looks something like this:

#!/bin/sh

BRANCH=`git branch | grep ^\\* | cut -d' ' -f2`
if [ $BRANCH != "master" ]; then
  echo "$0: Current branch is not master"
  exit 1
fi

git log --pretty=oneline work...master | grep -v NOCOMMIT: | cut -d' ' -f1 | tac | xargs -l git cherry-pick

This first checks to make sure I'm on the master branch (sanity check). Then, it lists each commit in work, filters out the ones that mention the NOCOMMIT keyword, reverses the order, and finally cherry-picks each commit (now from the oldest first) into master.

Finally, after pushing the changes in master upstream, I switch back to work and rebase:

git checkout work
git rebase master

Git will reapply each of the commits in the work branch, effectively skipping over the one(s) that have already been applied in master through the cherry-picking. What you should be left with is only the NOCOMMIT local commits.

This technique makes the push process a bit more time-consuming, but it solved a problem for me so I thought I'd share.

Greg Hewgill
You realize you're asking the oblivious not-question-asker to do this? The person who just runs `git commit -a` without a care in the world?
Novelocrat
+8  A: 

Have your program read a pair of configuration files for its settings. First, it should read a config.defaults file that would be included in the repository. Then, it should read a config.local file that should be listed in .gitignore

With this arrangement, new settings appear in the defaults file and take effect as soon as it's updated. They will only vary on particular systems if they're overridden.

As a variation on this, you could have just a general config file that you ship in version control, and have it do something like include config.local to bring in the machine-specific values. This introduces a more general mechanism (versus policy) in you code, and consequently enables more complicated configurations (if that's desirable for your application). The popular extension from this, seen in many large-scale open-source software, is to include conf.d, which reads configuration from all the files in a directory.

Also see my answer to a similar question.

Novelocrat
This is a good way to do it.
corydoras
I'm gonna give this the answer. This method achieves the desired effect, with the only downside that it requires extra logic on the part of the application.
Gordon
+1  A: 

Check in a default configuration with a different extension (say .default), use a symlink to symlink the default to the correct location, add the correct location to .gitignore, and add everything else related to the configuration to .gitignore (so the only thing that gets checked in is config.default).

Additionally, write a quick install script that sets up the symlinks for your application-wide.

We used a similar approach at a previous company. The install script autodetected what environment you were running in (sandbox, development, QA, production), and would automatically do the right thing. If you had a config.sandbox file, and were running from the sandbox, it would link that (otherwise it would just link the .defaults file). Common procedure was to copy .defaults and change settings as necessary.

Writing the install script is easier than you might imagine, and gives you a lot of flexibility.

Bryan Alves