Course Tonight

Course Tonight

Did You Know?

Docy turns out that context is a key part of learning.

Chapter 5: Ignoring Files and Folders

Estimated reading: 16 minutes 7 views

This chapter discusses how to avoid adding unwanted files or file changes to a Git repository. There are several methods to achieve this, including using a .gitignore file, .git/exclude file, git update-index –assume-unchanged command, and git update-index –skip-tree command. It’s important to remember that when you ignore files, it means ignoring the content of a folder, including its files. An empty folder is ignored by default since it cannot be added anyway.

Section 5.1: Ignoring Files and Directories with a .gitignore File

To make Git ignore certain files and directories, you can create one or more .gitignore files in your repository. The .gitignore file typically contains a list of files and/or directories that are generated during the build process or at runtime. Entries in the .gitignore file can include names or paths pointing to temporary resources, local configuration files, or files containing secret information.

When a file or directory is ignored, it will not be tracked by Git, reported by commands like git status or git diff, or staged with commands like git add -A. If you need to ignore already tracked files, special care should be taken.

The .gitignore rules apply recursively when the file is created in the top-level directory, and they apply to that specific directory and its subdirectories when created in a sub-directory.

The examples below demonstrate the usage of .gitignore rules using glob file patterns:

  • Ignore files called ‘file.ext’:
file.ext
  • Ignore files with full path:
dir/otherdir/file.ext
otherfile.ext
  • Ignore directories and their contents:
bin/
gen/
  • Ignore files by extension:
*.apk
*.class
  • Ignore files with certain extensions in specific directories:
java/*.apk
gen/*.class
  • Ignore files only at the top-level directory, but not in its subdirectories:
/*.apk
/*.class
  • Ignore directories named DirectoryA:
**/DirectoryA/
  • Ignore directories named DirectoryB within a directory named DirectoryA:
DirectoryA/**/DirectoryB/
  • Exclude specific files when using wildcards:
!.gitignore
  • Ignore files with a hash (#):
\#*#

You can find comprehensive .gitignore files listed by language online to get started. Alternatively, you can use an online tool to generate a starter .gitignore file for a fresh project.

Section 5.2: Checking if a File is Ignored

The git check-ignore command allows you to check which files are ignored by Git. By passing filenames as command-line arguments, git check-ignore will list the files that are ignored based on the .gitignore rules. Adding the -v option to the command will display the line responsible for ignoring each file.

For example:

$ cat .gitignore
*.o
$ git check-ignore example.o Readme.md
example.o

Section 5.3: Exceptions in a .gitignore File

To specify exceptions for files ignored using patterns in a .gitignore file, prefix an exclamation mark (!) to the exception. For example:

*.txt
!important.txt

In the above example, Git will ignore all files with the .txt extension except for the file named important.txt.

However, if the file you want to include is in an ignored folder, you cannot easily re-include it. Simply using an exception like !folder/*.txt will not work, and the .txt files in the folder will remain ignored.

To properly re-include the folder and ignore its files, you need to follow these steps:

  1. Re-include the folder itself on a separate line: !folder/
  2. Ignore all files in the folder using folder/*
  3. Re-include the *.txt files in the folder: !folder/*.txt

So the correct .gitignore configuration would be:

!folder/
folder/*
!folder/*.txt

Note: If a file name begins with an exclamation mark, you need to add two exclamation marks or escape it with the \ character. For example:

!!includethis
\!excludethis

Section 5.4: A global .gitignore file To have Git ignore certain files across all repositories, you can create a global .gitignore file. You can set it up using the following command in your terminal or command prompt:

$ git config --global core.excludesfile <Path_To_Global_gitignore_file>

Git will now use this global .gitignore file in addition to each repository’s own .gitignore file. The rules for the global .gitignore file are as follows:

  • If the local .gitignore file explicitly includes a file while the global .gitignore file ignores it, the local .gitignore takes priority, and the file will be included.
  • If the repository is cloned on multiple machines, the global .gitignore file must be loaded on all machines or at least included. This is because the ignored files will be pushed up to the repository while the machine with the global .gitignore file wouldn’t update it. In such cases, a repository-specific .gitignore file is a better idea than a global one if the project is worked on by a team.

The global .gitignore file is a good place to keep platform, machine, or user-specific ignores, such as .DS_Store on macOS, Thumbs.db on Windows, or Vim *.ext~ and *.ext.swp files. Each team member can customize their own global .gitignore file according to their needs.

Section 5.5: Ignore files that have already been committed to a Git repository If you have already added a file to your Git repository and want to stop tracking it (so that it won’t be present in future commits), you can remove it from the index using the following command:

git rm --cached <file>

This command will remove the file from the repository and prevent further changes from being tracked by Git. The --cached option ensures that the file is not physically deleted.

Note that the previously added contents of the file will still be visible via the Git history.

Keep in mind that if anyone else pulls from the repository after you remove the file from the index, their copy will be physically deleted.

Alternatively, you can make Git pretend that the working directory version of the file is up to date and read the index version instead, ignoring changes in it. This can be done using the “skip worktree” bit:

To skip worktree changes:

git update-index --skip-worktree <file>

Writing to the file is not affected by this bit, as content safety remains a priority. Your ignored changes are preserved. However, please note that this bit conflicts with stashing. To remove the “skip worktree” bit, use the following command:

git update-index --no-skip-worktree <file>

Another approach is to use the “assume unchanged” bit, which makes Git assume that the file is unchanged without examining its contents. To mark a file as “assume unchanged,” use the following command:

git update-index --assume-unchanged <file>

Please keep in mind that if you pull any changes to this file or stash it, your ignored changes will be lost. To make Git “care” about the file again and track its changes, run the following command:

git update-index --no-assume-unchanged <file>

Section 5.6: Ignore files locally without committing ignore rules

The .gitignore file is used to ignore files locally, but it is intended to be committed to the repository and shared with other contributors and users. If you want to ignore certain files in a repository locally without making the file part of any repository, you can edit the .git/info/exclude file inside your repository.

Here’s an example of how to use the .git/info/exclude file:

  • Open the .git/info/exclude file in your repository.
  • Add the files or patterns you want to ignore, one per line.
  • These rules are specific to your local repository and are not shared with anyone else.
  • For example, you can add the following lines to ignore specific files:
# These files are only ignored in this repo
# These rules are not shared with anyone as they are personal
gtk_tests.py
gui/gtk/tests/*
localhost
pushReports.py
server/

Section 5.7: Ignoring subsequent changes to a file (without removing it)

In Git, there is a way to have a file held in the repository but ignore any subsequent changes made to it. You can achieve this by using the git update-index command with the --assume-unchanged option. Here’s how it works:

git update-index --assume-unchanged my-file.txt

The above command tells Git to assume that my-file.txt hasn’t been changed, and it won’t check or report any modifications to this file. However, please note that the file will still be present in the repository.

This feature can be useful when you want to provide default values or configurations in a file but allow local environment overrides. For example:

Create a file with some initial values:

cat <<EOF
MYSQL_USER=app
MYSQL_PASSWORD=FIXME_SECRET_PASSWORD
EOF > .env

Add the file to Git and commit it:

git add .env
git commit -m "Adding .env template"

Ignore future changes to the file using --assume-unchanged:

git update-index --assume-unchanged .env

Update the file with local changes (e.g., changing the password):

vi .env

When you run git status, you will see that there are no changes reported for .env:

git status

This way, Git will ignore any further changes to my-file.txt while keeping it in the repository.

Section 5.8: Ignoring a file in any directory

If you want to ignore a specific file, such as foo.txt, in any directory within your repository, you can simply write its name in the .gitignore file:

foo.txt

This rule will match all files named foo.txt in any directory.

If you want to ignore the file only in a specific part of the directory tree, you can use the ** pattern to specify subdirectories of a specific directory. For example:

bar/**/foo.txt

This rule matches all files named foo.txt in the directory bar and all its subdirectories.

Alternatively, you can create a .gitignore file in the bar/ directory. In this case, you would create a file named .gitignore in the bar/ directory and add the following line to it:

foo.txt

This rule would match all files named foo.txt in any directory under bar/.

Section 5.9: Prefilled .gitignore Templates

If you’re unsure about which rules to include in your .gitignore file or if you want to add commonly accepted exceptions to your project, you can use pre-filled .gitignore templates. There are online resources and repositories available that provide these templates:

These platforms allow you to generate .gitignore files based on the programming languages and IDEs you are using. They provide a collection of common rules for excluding files and directories from version control.

Section 5.10: Ignoring files in subfolders (Multiple .gitignore files)

In a repository with a specific structure, you may have the need to ignore a file that exists in multiple subfolders. For example:

examples/
output.log
src/
<files not shown>
output.log
README.md

In this case, the output.log file in the examples directory is valid and necessary for the project, while the one in the src/ directory is generated during debugging and should not be part of the repository’s history.

There are two approaches to ignoring this file. You can either place an absolute path to the file in the root .gitignore file:

# /.gitignore
src/output.log

Alternatively, you can create a .gitignore file in the src/ directory and ignore the file relative to that .gitignore:

# /src/.gitignore
output.log

Both approaches will effectively ignore the output.log file in the src/ directory.

Section 5.11: Create an Empty Folder

In Git, it’s not possible to add and commit an empty folder directly. Git only manages files and associates directories with them, which helps optimize commits and improve performance. However, there are two common methods to work around this limitation:

Method one: Using .gitkeep

One workaround is to create a file named .gitkeep inside the empty folder. The .gitkeep file is typically empty and serves the purpose of registering the folder with Git. To create the .gitkeep file, you can use the following command in Git Bash or any command-line interface:

$ touch .gitkeep

This command creates an empty .gitkeep file in the current directory, serving as a marker to keep the folder tracked by Git.

Method two: Using dummy.txt

Another approach is similar to the previous method, but instead of using .gitkeep, you can create a file named dummy.txt or any other filename. The steps are the same as with .gitkeep, and you can easily create the file in Windows using the context menu. The advantage of using dummy.txt is that you can add custom messages or content to the file. Similar to .gitkeep, this file can also be used to track the empty directory.

Remember that these files (.gitkeep or dummy.txt) don’t serve any functional purpose in the project but act as placeholders to include an otherwise empty folder in Git’s tracking.

Section 5.12: Finding files ignored by .gitignore

To list all files ignored by Git in the current directory, you can use the following command:

git status --ignored

For example, if your repository structure is as follows:

.git
.gitignore
./example_1
./dir/example_2
./example_2

And your .gitignore file contains the entry example_2, running the command git status --ignored will produce the following output:

On branch master

Initial commit

Untracked files:
(use “git add <file>…” to include in what will be committed)

.gitignore
.example_1

Ignored files:
(use “git add -f <file>…” to include in what will be committed)

dir/
example_2

If you want to recursively list ignored files in directories, you can use the additional --untracked-files=all parameter:

git status --ignored --untracked-files=all

The output will include the recursively ignored files:

On branch master

Initial commit

Untracked files:
(use “git add <file>…” to include in what will be committed)

.gitignore
example_1

Ignored files:
(use “git add -f <file>…” to include in what will be committed)

dir/example_2
example_2

Section 5.13: Ignoring only part of a file

Sometimes, you may want to have local changes in a file that you don’t want to commit or publish. While it’s recommended to concentrate local settings in a separate file added to .gitignore, there are cases where temporarily having local changes in a checked-in file can be helpful.

You can make Git “unsee” specific lines using a clean filter, which ensures that those lines won’t even show up in diffs.

For example, let’s say you have a file named file1.c with the following snippet:

struct settings s;
s.host = "localhost";
s.port = 5653;
s.auth = 1;
s.port = 15653; // NOCOMMIT
s.debug = 1; // NOCOMMIT
s.auth = 0; // NOCOMMIT

Suppose you don’t want to publish the lines with NOCOMMIT anywhere.

To achieve this, you can create a “nocommit” filter by adding the following to your Git config file (.git/config):

[filter "nocommit"]
clean = grep -v NOCOMMIT

Next, add or create the following in the .git/info/attributes or .gitmodules file:

file1.c filter=nocommit

With this setup, the NOCOMMIT lines in the file1.c file will be hidden from Git. Please note that using a clean filter may slow down file processing, especially on Windows, and there can be some caveats with line disappearance when Git updates the file. These caveats can be counteracted with a smudge filter, but that is more complex. Keep in mind that this approach has not been tested on Windows.

Section 5.14: Ignoring changes in tracked files

The .gitignore and .git/info/exclude files only work for untracked files. If you want to set the ignore flag on a tracked file, you can use the update-index command.

To skip changes in a tracked file, you can use the following command:

git update-index --skip-worktree myfile.c

This command tells Git to ignore future changes to the myfile.c file.

If you want to revert this and start tracking changes again, you can use the following command:

git update-index --no-skip-worktree myfile.c

This command removes the ignore flag on the myfile.c file.

To make these commands more convenient, you can add aliases to your global Git config file (~/.gitconfig). Add the following snippet to have git hide, git unhide, and git hidden commands:

[alias]
hide = update-index --skip-worktree
unhide = update-index --no-skip-worktree
hidden = "!git ls-files -v | grep ^[hsS] | cut -c 3-"

With these aliases, you can use git hide myfile.c to skip changes in a file, git unhide myfile.c to start tracking changes again, and git hidden to list all currently hidden files.

Additionally, you can use the --assume-unchanged option with the git update-index command. When the --assume-unchanged flag is specified, Git assumes that the working tree file matches what is recorded in the index. This means Git won’t check for changes in the file unless explicitly told to do so. Use the following commands:

git update-index --assume-unchanged <file>

To watch the file for changes again, use:

git update-index --no-assume-unchanged <file>

When using the --assume-unchanged flag, it’s important to note that Git will fail if it needs to modify the file in the index, such as during a merge. If the assumed-unchanged file is changed upstream, you will need to handle the situation manually. This approach focuses on performance.

The --skip-worktree flag is useful when you want to instruct Git not to touch a specific file because it will be changed locally, and you don’t want to accidentally commit the changes. This is commonly used for configuration or properties files that are configured for a particular environment. When both --skip-worktree and --assume-unchanged flags are set, the --skip-worktree flag takes precedence.

Section 5.15: Clear already committed files, but included in .gitignore

Sometimes it happens that a file was being tracked by Git, but at a later point in time, it was added to .gitignore to stop tracking it. It’s a very common scenario to forget to clean up such files before adding them to .gitignore. In this case, the old file will still be present in the repository.

To fix this problem, you can perform a “dry-run” removal of everything in the repository, followed by re-adding all the files back. As long as you don’t have any pending changes and the --cached parameter is passed, this command is fairly safe to run:

# Remove everything from the index (the files will stay in the file system)

$ git rm -r --cached .

# Re-add everything (they’ll be added in the current state, changes included)

$ git add .

# Commit, if anything changed. You should see only deletions

$ git commit -m 'Remove all files that are in the .gitignore'

# Update the remote

$ git push origin master

By following these steps, you can effectively remove the files that are listed in the .gitignore file from the repository, ensuring they are no longer tracked.

Leave a Comment

Share this Doc

Chapter 5: Ignoring Files and Folders

Or copy link

CONTENTS