211

I have created a default version of a file included in a git repository. It's important that when someone clones the repository, they get a copy of this file. However, I would like to set git so that it ignores changes to this file later. .gitignore works only on untracked files.

My motivation is that this file contains machine-specific information. I would like to provide default values, while allowing people to make local changes that won't get pushed back to the origin repository, creating merge conflicts when we pull new changes.

We are generally pretty lazy and use git add . a lot, so I'm pretty sure if I can't tell git to ignore this file, changes to it will end up getting committed and pushed.

To summarize,

  1. I would like to create a file, call it default_values.txt that is added to my git repository and is included when someone clones that repository.
  2. git add . should not add default_values.txt to the commit.
  3. This behavior should be passed on to any clones of the repository.
Andy Lester
  • 91,102
  • 13
  • 100
  • 152
Marc
  • 5,315
  • 5
  • 30
  • 36
  • 2
    Can you make use of git hooks to have a pre-commit hook that would abort a commit if the file modified is default_values.txt (say) ? – sateesh Dec 03 '10 at 18:37
  • 2
    Git purists would say don't be lazy and use the staging area correctly, that is what it is for. – Xint0 Dec 03 '10 at 18:44
  • 1
    Git purists would say use smudge/clean scripts. It's the most maintainable solution. – Adam Dymitruk Jan 26 '11 at 21:22
  • 1
    Xint0: true. but how to you prevent other people from accidentally checking in? – Alan Jan 09 '14 at 01:17
  • possible duplicate of [Committing Machine Specific Configuration Files](http://stackoverflow.com/questions/1396617/committing-machine-specific-configuration-files) – Senseful Sep 03 '14 at 19:32
  • funny. the second most voted answer has a comment from OP stating "they didn't accept because you can't pass the behavior to other clones", but then the accepted answer has the exact same command - and issue. – igorsantos07 Dec 30 '20 at 02:57

7 Answers7

230

As many others have mentioned, a good modern solution is:

git update-index --skip-worktree default_values.txt

That will ignore changes to that file, both local and upstream, until you decide to allow them again with:

git update-index --no-skip-worktree default_values.txt

You can get a list of files that are marked skipped with:

git ls-files -v . | grep ^S

Note that unlike --skip-worktree, the --assume-unchanged status will get lost once an upstream change is pulled.

Michael
  • 8,362
  • 6
  • 61
  • 88
moodboom
  • 6,225
  • 2
  • 41
  • 45
  • 8
    If someone else pulls the repo and edits the file, are changes in their directory ignored? I'm hoping they'd have to type `--no-skip-worktree` to add their changes. – neaumusic Dec 07 '17 at 23:46
  • 5
    What is done with their changes is controlled by them. In other words, they would have to set skip-worktree on the file in their repo if they did not want their changes to be pushed. If it is a file that is meant to be sent out to everyone and then have all subsequent changes ignored, everyone would have to follow these same instructions. – moodboom Dec 08 '17 at 21:41
  • 3
    Note that you may have to undo the `--skip-worktree` status of a file before you can switch branches, if that same file is tracked in the other branch. – moodboom Dec 07 '18 at 16:22
  • 5
    hmm, it works... after I did some change in file it was not shown in `git status`, but when I tried to checkout to different branch, I've got `error: Your local changes to the following files would be overwritten by checkout: `, even -f does not help `error: Entry 'wix-stores-merchant-app/demo/credentials.js' not uptodate. Cannot merge.` – ykravv Nov 28 '19 at 09:02
  • @ykravch yes, as I mentioned in the previous comment, the --skip-worktree status can get in the way when you switch branches, and you may have to undo the --skip-worktree status in that case. Kind of a pain. – moodboom Nov 30 '19 at 22:09
  • 6
    I have these aliased to `git ignore` and `git unignore`. – Michael Jan 09 '20 at 20:56
  • 1
    Can I do this but save that state on the remote so that other devs don't accidentally commit changes on an "untracked" file, since they forgot to do `--skip-worktree` – theonlygusti Jul 11 '20 at 07:22
  • 2
    @theonlygusti `--skip-worktree` is client functionality. On the server side, if your devs have write access, it's hard to stop them from doing what they want. You could have devs add a pre-commit or pre-push hook to their repo's .git/hooks/ that would check for changes to this file and if found disallow the commit/push. You are still relying on your devs to use the hook properly, but perhaps that is good enough in your case. [Here](https://superuser.com/a/991974/226988)'s an example. – moodboom Jul 18 '20 at 19:46
  • @moodboom I don't really care about disallowing, I just want to prevent accidents. I _know_ the relevant file will be updated for sure, but I also know that in 99% of cases the updates should be only on their local machine and not affect the central repository that everyone shares. – theonlygusti Jul 18 '20 at 19:48
  • @theonlygusti in that case, the hook should prevent accidents. Have them install it when they set up their local repos. – moodboom Jul 18 '20 at 19:49
54

What you are searching for is git update-index --assume-unchanged default_values.txt.

See the docs for more details: http://www.kernel.org/pub/software/scm/git/docs/git-update-index.html

tamasd
  • 5,747
  • 3
  • 25
  • 31
  • 11
    this does not work. although it makes git add . ignore the file on the local branch, a clone of the archive does not have this behavior (if you change default_values.txt in the cloned archive, it will be added to the commit with "git add .") – Marc Dec 03 '10 at 22:06
  • 7
    Yes, because you are setting it just for the local repo. You cannot push this kind of information. – tamasd Dec 03 '10 at 22:08
  • 10
    @Indradhanush - this solution does not satisfy criterion 3 -- "the behavior should be passed on to any clones of the repository" -- which is why I did not accept it. Doesn't mean it's not a good answer. – Marc Sep 08 '14 at 13:07
  • I never noticed the 3rd criterion. Because I wasn't looking for it. :) – Indradhanush Gupta Sep 08 '14 at 13:47
  • 5
    For local config files with private app settings, you likely want to use `skip-worktree` instead of `assume-unchanged` more info http://stackoverflow.com/questions/13630849/git-difference-between-assume-unchanged-and-skip-worktree – Aaron Hoffman Dec 11 '15 at 19:56
  • I prefer this answer in most cases as it means you won't ever miss upstream changes. – DrCord May 20 '21 at 22:42
24

The approach I've generally seen is to create a file with a different name, eg: default_values_template.txt and put default_values.txt in your .gitignore. Instruct people to copy default_values_template.txt to default_values.txt in their local workspaces and make changes as needed.

Laurence Gonsalves
  • 137,896
  • 35
  • 246
  • 299
  • hmmm... maybe I could write a hook to automatically copy default_values_template to default_values if default_values doesn't exist? – Marc Dec 03 '10 at 18:47
  • 2
    This is the most common way to solve this in my experience. It's pretty much the path of least resistance in that it "just works", and you can easily make your code check whether the local config file exists and provide a helpful error if it doesn't. – Jani Hartikainen Dec 03 '10 at 18:49
  • I think the solution is indeed to do something like this, preferably with a script executed whenever you pull or clone. One idea would be that anything with a particular extension (say .basefile) gets copied to a file with the extension dropped and then the file name gets added to .gitignore in that directory. So I would create a file default_values.txt.basefile and commit that. I don't have the git or perl chops to do this, but I'll ask a friend who does and let you know how it works out. – Marc Dec 03 '10 at 22:11
  • This was downvoted because it's what clean and smudge scripts were made for. – Adam Dymitruk Dec 08 '10 at 21:49
  • 1
    @AdamDymitruk: Yes, clean/smudge can be used in this case, but it's far from clear that that is the best option. For example, this will make it rather hard if people really *want* to change the file, as the clean/smudge will get in the way. I would actually prefere the approach described here. – sleske Aug 31 '13 at 08:28
  • As to "people can forget to copy the file": Typically `default_values.txt` would be generated as part of the (local) build process by whatever build tool you use. The tool could even check `default_values_template.txt` for changes and merge them into `default_values.txt`. – sleske Nov 15 '13 at 08:45
  • This is not possible for projects where the files have to be added to the solution. E.g. a Constants class in C#. If I instruct users to rename the file and add it to the project, Adding causes a change to the *.csproj file.which would then be checked in – ganeshran Feb 25 '15 at 10:19
  • 1
    I take a cue from git itself (specifically git hooks) and use the `.sample` suffix. So in your case `default_values.txt.sample` – tir38 Apr 03 '20 at 19:36
6

Take a look at smudge/clean scripting. This way you can version control the file but when it is checked out, you will "smudge" it by replacing the generic/place-holder data with machine specific data in the file.

When you commit it, you will "clean" it by replacing the machine specific information with generic or place-holder information.

Smudge/clean scripts must be deterministic in that applying them multiple times, in different orders will be the equivalent of just running the last one in the sequence.

The same can be applied with passwords if you need to expose your repository but the contents may contain sensitive information.

user229044
  • 232,980
  • 40
  • 330
  • 338
Adam Dymitruk
  • 124,556
  • 26
  • 146
  • 141
  • Are Clean and Smudge scripts local or part of the repo? – Alan Jan 09 '14 at 01:20
  • yes. :) ... that is, you can share the smudge clean via the repo but it's not a good idea when they contain sensitive data like production passwords. If that's not a concern, git requires you to explicitly enable the script. Otherwise, people could do malicious things via github and other shared repos to other users. – Adam Dymitruk Jan 10 '14 at 17:46
  • I need to read a bit more about this. Basically I want to setup a project that has a default `user.json` that needs to be overwritten with each developers creds, but I don't want the dev to accidentally check in their creds. – Alan Jan 10 '14 at 18:27
  • I would google around for clean smudge example scripts. See what comes up. Also, jump on the git irc room on freenode. You'll get help immediately. – Adam Dymitruk Jan 10 '14 at 19:44
3

I've solved this by defining the "clean" filter to simply cat the contents of the file in the index.

git show :path/to/myfile should just print the contents of the index for the specified file, so we can use that in a script to replace the working copy with the untouched copy in the index:

#! /bin/sh

git show :$1

Set that as the "clean" filter for the file concerned (assuming you've put that in "discard_changes"):

$ git config filter.ignore_myfile.clean "discard_changes path/to/myfile"
$ echo "path/to/myfile filter=ignore_myfile" >> .gitattributes

Unfortunately I can't find a way to make this generalisable to multiple files, as there's no way to tell which file we're processing from inside the clean script. Of course, there's nothing to stop you from adding a different filter rule for each file, but it's a bit cludgy.

Haddon CD.
  • 169
  • 8
  • almost understandable. Why, if you've added the filter to the specific path in .gitattributes, would you need to specify the file to act upon in the command "discard changes" ? why wouldn't this command operate on whatever file you passed in? for example, if in .gitattributes, you specified */ProjectSettings.asset as the file in question with a filter applied, the filter should be able to operate on ANY of that file in the repo! How does one do that? – eric frazer Feb 04 '21 at 05:21
1

I found a solution that works for my team. We share our githooks via symlinks and after adding a template file to git I added a pre-commit hook that checks whether the template file has been modified and if so I git reset -- templatefile.txt. If it's the only changed file I also abort the commit.

fruitcoder
  • 1,073
  • 8
  • 24
-3

I suggest looking into submodules. If you place the machine specific files in a submodule, git add should ignore it.

ivanpro
  • 13
  • 2
  • this is a good idea, but then I also have to put the single file repository on the git server too, which is less than optimal, only because we're using github and have a limited number of repositories. – Marc Dec 03 '10 at 18:48
  • @Marc have a look at Visual Studio Team Services, unlimited free private projects and git repositories. Kanban boards, work item and bug tracking, associate checkins with work items, manage sprints if your into scrum or other project types. As well as that it has excellent build tools for building against multiple platforms, too many things to mention that are all free. Some people slag it because its Microsoft but it hands down beats what github has to offer in terms of tools apart from just repository hosting. There are limits of what you can do for free but I rarely exceed them. – Aran Mulholland Jan 03 '17 at 12:15
  • @Marc one more thing that I find really handy about it is that I can set up as many accounts as I want, so if I am writing a project for a client who wants to own the source control I can create an account, use it to plan, design and execute the project and when I am finished I can pass ownership of the account to the client. – Aran Mulholland Jan 03 '17 at 12:24