The answer is "no", or "maybe", or even "yes, but probably not the way you're thinking". More specifically, .gitignore
is only going to help indirectly, if at all. The reason is that you can only ignore untracked files. There is an alternative, which is to tell Git that tracked files should be treated as unchanged. This alternative does not use .gitignore
at all. (I also don't particularly recommend it.
Many of these descriptions about .gitignore
are vague and difficult to interpret because they don't start with the correct foundation. The proper foundation for understanding .gitignore
is Git's index, which is also called the staging area and the cache. But before we get to this, we need one even-more-basic item.
Basics: commits, the index, and the work-tree
Remember first that what Git stores—the unit of operation, as it were—is the commit. A commit holds a single snapshot of a file tree, along with metadata (who made the commit and when; their description of the commit; and some crucial parent commit information). People like to think of Git as storing files, and that's not completely wrong, but in fact, Git is storing commits, and it's the commits that have the files. Doing something with a Git repository generally means doing something with a commit. Note that all existing commits are read-only: you cannot change anything about any commit. (You can instead add new commits.)
The files in a commit, or in Git in general—this includes files stored in the index—are stored in a special, compressed, Git-only form. These files are useless to almost everything else on your computer (including yourself) so Git has to extract them into a work-tree. It's these work-tree copies that you can see, edit, use as credentials, and so on.
When someone clones a repository, they first get the commits—usually all of them. They don't yet have an index and they don't yet have a work-tree. So, as the last step of git clone
, Git runs git checkout
to create the index and the work-tree.1 This git checkout
step selects one particular commit, typically the commit named by a branch name like master
. That commit stores a snapshot—a set of files—and Git now copies those files from the commit into the index, and then from the index into the work-tree.
What you have, then, when you first clone a repository, or use git checkout
to switch cleanly to a new commit, is an index full of all the same files as are in the current commit, and a work-tree full of all the same files as are in the index. The index exists because the commit is read-only, and the work-tree exists, separately from the index, because the index stores the files in their special, internal-only, Gitty format.
To create a new commit, you first work on the files in the work-tree, then run git add
on some or all of these files. What git add
does is copy the files back into the index (converting them to the special Gitty format). Eventually you will run git commit
, and that takes whatever is in the index right then and uses that to make the snapshot for the new commit.
1Note that the index and the work-tree come as a pair. If you use git worktree add
to create a second work-tree for a repository, that creates a new index to go with the new work-tree. The underlying implementation is a bit odd for historical reasons, but these should always be treated as a sort of mated pair.
Defining the index
The index, then, can be described as what will go into the next commit you make. Initially, it has every file that's also in the current commit, and it gets used to populate the work-tree as well. Over time, you change the stored data, and maybe even the set of files, and make new commits from the modified index. So, at any given time, the index has a bunch of files in it. The index copy of some file with path P will usually match at least one of two other copies of the files: the copy of P in the current commit, or the copy of P in the work-tree.
The initial state, as we saw above, is that every file in the work-tree has a copy in the index and in the current commit. All three copies match. You can read them all you want, but if you change them in the work-tree, you get into an interesting situation: Git now "wants you" to git add
them, to copy the updated versions back into the index so that they will be updated in the next commit you make.
Work-tree files are either tracked or untracked
You can, on your own, create new files in the work-tree, that have no index entry. Such a file is called an untracked file. The definition of an untracked file is any file that is not in the index. And, if it's not in the index, it won't be in the next commit either.
Note that what's in the index can change over time. You could have a file that is in the index right now, and then use git rm
or git rm --cached
to remove the file from the index. Now it's no longer in the index, and won't be in the next commit. But in general these also remove the work-tree copies.
You probably want untracked files. If you create a file in the work-tree that has no corresponding index and commit entry, this is an untracked file. (Hence a file that is in the index and work-tree is a tracked file.) But there's an annoyance with untracked files: Git keeps whining about them.
This is where .gitignore
files come in. A .gitignore
file lists particular path names or name patterns. Before Git whines about a file being untracked, Git checks the .gitignore
files that apply to that path. If the path is listed, Git simply shuts up. This affects only untracked files. If a file is tracked—by which we mean if the file's name is in the index—then .gitignore
does not apply at all.
The alternative is git update-index --skip-worktree
Note that the pre-condition for a file to be ignored is that it must also be untracked. If it is untracked, it is, by definition, not in any new commits you make. For it to start out as untracked, it must not be in the index made from the commit you check out with git checkout
. So it's probably not in any interesting commits. (It may be in dull, boring commits deep in the commit history, for instance, but as soon as you check one of those out, the file is tracked, and when you switch back to a recent commit, Git removes the file. So it's best to avoid this entirely.)
You can, instead, have a file in the index (hence tracked) but tell Git that no matter what happens to the work-tree version, Git should pretend that nothing happened, and keep using the index version. You do this with git update-index
, using the --skip-worktree
option. (See also Git - Difference Between 'assume-unchanged' and 'skip-worktree'.)
Review, or, there's no good answer
If a file isn't in a commit, it won't be in the index when you check out that commit. If you checked out that commit as the last step of git clone
, you just made a new index-and-work-tree pair, so you don't have any untracked files at all. The credentials file you want is simply not there. You must run at least one command to create it. (It can be pre-listed in the .gitignore
file.)
If the file is in a commit, it will be in the index and work-tree, ready to use. But it won't be marked "skip" in the index, so that if someone touches it, Git will tell them to commit it. Adding it to .gitignore
won't help because the file is tracked. You must run at least one command to make it skipped.
Either way, you need at least one command beyond git clone
. I believe the better approach is to have template files that are committed, and to have the one command copy those templates to untracked, pre-ignored work-tree files, but the --skip-worktree
variant also works.