Chapter 4. Managing Your Worktree

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:

  • Ignoring files: marking files as intentionally not under version control
  • File attributes: path-specific configuration
  • Handling text and binary files
  • End of line conversion of text files, for repository portability
  • Using various modes of the git reset command
  • Stashing away your changes to handle interruptions
  • Searching and examining files in any place
  • Resetting files and reverting file changes interactively
  • Cleaning the working area by removing untracked files

Ignoring files

Your 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.

Note

The index, or the staging area, is used not only for Git to know which files to track, but also as a kind of a scratchpad to create new commits, as described in Chapter 3, Developing with Git, and to help resolve merge conflicts, as shown in Chapter 7, Merging Changes Together.

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.

Tip

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.

Marking files as intentionally untracked

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:

  • The per-user file that can be specified by the configuration variable 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.
  • The per-local repository $GIT_DIR/info/exclude file in the administrative area of the local clone of the repository.
  • The .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:

  • Patterns not containing a slash match everywhere in the repository; one can say that the pattern is recursive.

    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.

  • Patterns ending with a slash match only directories, but are otherwise recursive (unless they contain other slashes).

    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).

  • To anchor a pattern and make it non-recursive, add a leading slash.

    For example the /TODO file will ignore the current-level TODO file, but not files in subdirectories, for example src/TODO.

  • Patterns containing a slash are anchored and non-recursive, and wildcard characters (*, ?, 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.

Which types of file should be ignored?

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.

Note

The per-user ignore file cannot be ~/.gitignore, as this would be the in-repository .gitignore file for the versioned user's home directory, if the user wants to keep the ~/ directory ($HOME) under version control.

This is the place where you can put patterns matching the backup or temporary files generated by your editor or IDE of choice.

Tip

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.

Listing ignored files

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

Tip

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.

Ignoring changes in tracked files

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

Note

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset