The previous chapter, Developing with Git, described how to use Git for development, including how to create new revisions. Now we will focus on learning how to manage your working directory (worktree) to prepare contents for a new commit. This chapter will teach you how to manage your files, in detail. It will show how to care for files that require special handling, introducing the concepts of ignored files and file attributes.
You will also learn how to fix mistakes in handling files, both in the working directory and in the staging area; and how to fix the latest commit. You will find out how to safely handle interruptions in the workflow with stash and multiple working directories.
The previous chapter taught you how to examine changes. Here you will learn how to undo and redo those changes selectively, and how to view different versions of a file.
This chapter will cover the following topics:
git reset
commandYour files inside your working area (also known as the worktree) can be either tracked or untracked by Git. Tracked files, as the name suggests, are whose changes Git will follow. For Git, if a file is present in the staging area (also known as the index), it will be tracked and—unless specified otherwise—it will be a part of the next revision. You add files to be tracked so as to have them as a part of the project history.
Often you will have some individual files or a class of files that you never want to be a part of the project history, and never want to track. These can be your editor backup files, or automatically generated files produced by the project's build system.
You don't want Git to automatically add such files, for example, when doing bulk add with git add :/
(adding the entire working tree), git add .
(adding the current directory), or when updating the index to the worktree state with git add --all
. Quite the opposite: you want Git to actively prevent from accidentally adding them. You also want such files to be absent from the git status
output, as there can be quite a number of them. They could drown out legitimate new unknown files there otherwise. You want such files to be intentionally untracked: ignored.
Un-tracking and re-tracking files
If you want to start ignoring a file that was formerly tracked, for example when moving from hand-generated HTML file to using a lightweight markup language such as Markdown instead, you usually need to un-track the file without removing it from the working directory, while adding it to the list of ignored files. You can do this with git rm --cached <file>
.
To add (start tracking) an intentionally untracked (that is, ignored) file, you need to use git add -f
.
In such case, you can add a shell glob pattern to match files that you want to have ignored by Git to one of the gitignore files, one pattern per line:
core.excludesFile
, which by default is $XDG_CONFIG_HOME/git/ignore
. This in turn defaults to the $HOME/.config/git/ignore
if $XDG_CONFIG_HOME
environment variable is not set or empty.$GIT_DIR/info/exclude
file in the administrative area of the local clone of the repository..gitignore
files in the working directories of a project; these are usually tracked and thus shared among developers.Some commands, such as git clean
, also allow us to specify ignore patterns from a command line.
When deciding whether to ignore a path, Git checks all those sources in the order specified on preceding list, with the last matching pattern deciding the outcome. The .gitignore
files are checked in order, starting from the top directory of the project down to the directory of files to be examined.
To make gitignore files more readable you can use blank lines to separate groups of files (a blank line matches no files). You can also describe patterns or groups of patterns with comments; a line starting with #
serves as one (to ignore a pattern beginning with the hash character, #
, escape the first hash character with a backslash , for example,
#*#
). Trailing spaces (at the end of the line) are ignored unless escaped with a backslash .
Each line in the gitignore
file specifies a Unix glob pattern, a shell wildcard. The *
wildcard matches zero or more characters (any string), while the ?
wildcard matches any single character. You can also use character classes with brackets [...]
. Take for example the following list of patterns:
*.[oa] *~
Here the first line tells Git to ignore all files with the .a
or .o
extension—archive (for example, a static library) and object files that may be the products of compiling your code. The second line tells Git to ignore all files ending with a tilde, ~
; this is used by many Unix text editors to mark temporary backup files.
If the pattern does not contain a slash /
, which is a directory (path component) separator, Git treats it as a shell glob and checks file name or directory name for a match, starting at appropriate depth, for example the .gitignore
file location, or the top level of the repository. The exception is patterns ending with slash /
—which is used to have the pattern matched against directories only—but otherwise the trailing slash is removed. A leading slash matches the beginning of the path name. This means the following:
For example, the *.o
pattern matches object files anywhere, both in the gitignore
file level and in subdirectories: file.o
, obj/file.o
, and so on.
For example, the auto/
pattern will match the top-level auto
directory and for example src/auto
, but will not match the auto
file (or a symbolic link either).
For example the /TODO
file will ignore the current-level TODO
file, but not files in subdirectories, for example src/TODO
.
*
, ?
, a character class such as [ao]
) do not match the directory separator that is slash. If you want to match any number of directories, use two consecutive asterisks **
in place of the path component (which means **/foo
, foo/**,
and foo/**/bar
).For example, doc/*.html
matches doc/index.html
file but not doc/api/index.html
; to match HTML files anywhere inside the doc
directory you can use the doc/**/*.html
pattern (or put the *.html
pattern in the doc/.gitignore
file).
You can also negate a pattern by prefixing it with an exclamation mark !
; any matching file excluded by the earlier rule is then included (non-ignored) again. For example to ignore all generated HTML files, but include one generated by hand, you can put the following in the gitignore file:
# ignore html files, generated from AsciiDoc sources *.html # except for the files below which are generated by hand !welcome.html
Note however that for performance reasons Git doesn't go into excluded directories, and (up till Git 2.7) this meant that you cannot re-include a file if a parent directory is excluded. This means that to ignore everything except for the subdirectory, you need to write the following:
# exclude everything except directory t0001/bin /* !/t0001 /t0001/* !/t0001/bin
To match a pattern beginning with !
, escape it with a backslash—for example, !important!.md
to match !important!.md
.
Now that we know how to mark files as intentionally untracked (ignored), there is the question of which files (or classes of files) should be marked as such. Another issue is where, in which of the three gitignore files, should we add a pattern for ignoring specific types of file?
First, you should never track automatically generated files (usually generated by the build system of a project). If you add them to the repository, there is a high chance that they will get out of sync with their source. Besides, they are not necessary, as you can always re-generate them. The only possible exception is generated files where the source changes rarely, and generating them requires extra tools that developers might not have (if the source changes more often, you can use an orphan branch to store these generated files, and refresh this branch only at release time).
Those are the files that all developers will want to ignore. Therefore they should go into a tracked .gitignore
file. The list of patterns will be version-controlled and distributed to other developers via a clone. You can find a collection of useful .gitignore
templates for different programming languages at https://github.com/github/gitignore.
Second, there are temporary files and by-products specific to one user's toolchain; those should usually not be shared with other developers. If the pattern is specific to both the repository and the user, for example, auxiliary files that live inside the repository but are specific to the workflow of a user (for example, to the IDE used for the project), it should go into the per-clone $GIT_DIR/info/exclude
file.
Patterns which the user wants to ignore in all situations, not specific to the repository (or to the project), should generally go into a file specified by the core.excludesFile
config variable, set in the per-user (global) config file ~/.gitconfig
(or ~/.config/git/config
). This is usually by default ~/.config/git/ignore
.
This is the place where you can put patterns matching the backup or temporary files generated by your editor or IDE of choice.
Ignored files are considered expendable
Warning: Do not add precious files, that is those which you do not want to track in a given repository but whose contents are important, to the list of ignored files! The types of file that are ignored (excluded) by Git are either easy to re-generate (build products and other generated files), or not important to the user (temporary files, backup files).
Therefore Git considers ignored files expendable and will remove them without warning when required to do a requested command, for example, if the ignored file conflicts with the contents of the revision being checked out.
You can list untracked ignored files with the --ignored
option to the status command:
$ git status --ignored On branch master Ignored files: (use "git add -f <file>..." to include in what will be committed) rand.c~ no changes added to commit (use "git add" and/or "git commit -a") $ git status --short --branch --ignored ## master !! rand.c~
You could instead use the dry-run option of cleaning ignored files: git clean -Xnd
, or the low-level (plumbing) command git ls-files
:
$ git ls-files --others --ignored --exclude-standard rand.c~
The latter command can also be used to list tracked files that match ignore patterns. Having such files might mean that some files need to be un-tracked (perhaps because what was once a source file is now generated), or that ignore patterns are too broad. As Git uses the existence of a file in the staging area (cache) to know which files to track, this can be done with the following command:
$ git ls-files --cached --ignored --exclude-standard
Plumbing versus porcelain commands
Git commands are divided into two sets: high-level porcelain commands intended for interactive usage by the user, and low-level plumbing commands intended mainly for shell scripting. The major difference is that high-level commands have output that can change and that is constantly improving, for example, going from (no branch)
to (detached from HEAD)
in the git branch
output in the detached from HEAD
case; though some porcelain commands have the option (usually --porcelain
) to switch to unchanging output. Their output and behavior are subject to configuration.
Another important difference is that plumbing commands try to guess what you meant, have default parameters, use the default configuration, and so on. Not so with plumbing commands. In particular you need to pass the --exclude-standard
option to the git ls-files
command to make it respect the default set of ignore files.
You can find more on this topic in Chapter 10, Customizing and Extending Git.
You might have files in the repository that are changed, but rarely committed. These can be various local configuration files that are edited to match the local setup, but should never be committed upstream. This can be a file containing the proposed name for a new release, to be committed when tagging the next released version.
You would want to keep such files in the dirty state most of the time, but you would like Git not to tell you about their changes all the time, in case you miss other changes because you are used to ignore such messages.
Git can be configured to skip checking the worktree (to assume that it is always up to date), and to use instead the staged version of the file, by setting the aptly named skip-worktree
flag for a file. For this you would need to use the low-level git update-index
command, the plumbing equivalent of the user-facing git add
porcelain (you can check file status and flags with `git ls-files`):
$ git update-index --skip-worktree GIT-VERSION-NAME $ git ls-files -v S GIT-VERSION-NAME H Makefile
Note however that this elision of worktree also includes the git stash
command; to stash away your changes and make the working directory clean, you need to disable this flag (at least temporarily). To make Git again look at the working directory version, and start tracking changes to the file, use the following command:
$ git update-index --no-assume-unchanged GIT-VERSION-NAME
There is a similar assume-unchanged
flag that can be used to make Git completely ignore any changes to the file, or rather assume that it is unchanged. Files marked with this flag never show as changed in the output of the git status
or git diff
command. The changes to it will not be staged nor committed.
This is sometimes useful when working with a big project on a filesystem with very slow checking for changes. Do not use assume-unchanged for ignoring changes to tracked files. You are promising that the file didn't change; lying to Git with, for example, git stash save
believing what you stated, would lose your local changes.