This is a fairly common question, and there isn't a One True Answer.
These are the most common techniques:
- Modify your app such that, before loading its config from e.g.
config.ini
, it first looks for e.g.config.mine.ini
, and loads it instead if it exists. Track a standardconfig.ini
in your repo, but if you need to override the values in it, copy it toconfig.mine.ini
and modify. Then configure git to ignoreconfig.mine.ini
. - Modify your app such that it first loads e.g.
config.ini
, but then attempts to load e.g.config.mine.ini
, and if it exists, uses keys fromconfig.mine.ini
to override those obtained fromconfig.ini
. This is similar to the first approach, but means thatconfig.mine.ini
needs to specify only options that you want to override. Thanks to shrugger for this one. - Have your app look at environmental variables for its config, before using the values from its config file (or hardcoded defaults). Then set those variables as appropriate (e.g. in your
.bashrc
for shell scripts, or somewhere in your webserver's config for web apps). Only works if your app doesn't need much config, but can be a useful behaviour for simple apps, and is particularly useful for things like API keys.
- Have your app just look for e.g.
config.ini
, but don't track this file. Instead create and track e.g.config.sample.ini
. Tell git to ignoreconfig.ini
. Everyone who clones the repo has to copyconfig.sample.ini
toconfig.ini
, but they can modifyconfig.ini
as much as they want. - Use some gitattributes clean/smudge magic as suggested by SethRobertson, see the other file in this gist.
- Use a local configuration branch. This is a more complex setup, but allows the config files to be stored within, and tracked by, git.
- Use git update-index --skip-worktree. If git asks to stash your work, you'll need to disable the flag temporally, since git stash is not able to stash changes to files with the skip-worktree bit set.
Many resources online recommend using
git update-index --assume-unchanged
to ignore tracked changes. This is poor advice since this bit exists to
address performance issues and should not be used as an ignore mechanism.
If git
determines the file was changed (e.g. file was changed upstream), it
can disable the flag (which can result in accidental push of sensitive
information if this change goes unnoticed) and under some circumstances will
discard your local changes without possibility of recovery.
Excerpt from the git mailing list:
Assume-unchanged should not be abused for an ignore mechanism. It is "I know my filesystem operations are slow. I'll promise Git that I won't change these paths by making them with that bit---that way, Git does not have to check if I changed things in there every time I ask for 'git status' output". It does not mean anything other than that. Especially, it is *not* a promise by Git that Git will always consider these paths are unmodified---if Git can determine a path that is marked as assume-unchanged has changed without incurring extra lstat(2) cost, it reserves the right to report that the path has been modified (as a result, "git commit -a" is free to commit that change).
If you follow one of the above suggestions -- where tracked and untracked config are stored in different files, with the latter ignored -- but need to commit the untracked config for deploying to somewhere, consider an extra deployment step that does something like the following:
# Assume the remote we're deploying to is called 'heroku', although this isn't heroku-specific
# We'll create a commit on master containing the normally-untracked config, merge that into heroku/master, push, then remove the commit from master
# Require a clean working directory, possibly using `git diff-index --quiet HEAD --` to check for uncommitted stuff
git fetch heroku
git checkout <branch_to_deploy_from> # e.g. master in git-flow
# Add our normally-untracked confing
git add -f <normally_untracked_config_file>
# Create a deploy commit on master
git commit -m "Deploy: Some useful commit message"
# Checkout the branch we're going to push to, and merge that deploy commit in
# We avoid having to have a local version of the heroku/master branch by detaching the HEAD
git checkout -q heroku/master
git merge --no-ff -m "Merge deploy commit" <branch_to_deploy_from>
# Push our deploy commit+merge to heroku
git push heroku HEAD:master
# Checkout <branch_to_deploy_from>, and remove the deploy commit
git checkout <branch_to_deploy_from>
git reset --mixed HEAD^
This creates a nice visual history, with a set of merges from <branch_to_deploy_from>
to heroku/master
(if you need to roll back), but master branch doesn't have a record of the deployments.