Chapters

Hide chapters

Advanced Git

First Edition · Git 2.28 · Console

Section I: Advanced Git

Section 1: 7 chapters
Show chapters Hide chapters

6. Gitignore After the Fact
Written by Chris Belanger

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

When you start a new software project, you might think that the prefab .gitignore you started with will cover every possible situation. But more often than not, you’ll realize that you’ve committed files to the repository that you shouldn’t have. While it seems that all you have to do to correct this is to reference that file in .gitignore, you’ll find that this doesn’t solve the problem as you thought it would.

In this chapter, you’ll cover a few common scenarios where you need to go back and tell Git to ignore those types of mistakes. You’re going to look at two scenarios to fix this locally: Forcing Git to assume a file is unchanged and removing a file from Git’s internal index.

Getting started

To start, extract the repository contained in the starter .zip file inside the starter directory from this chapter’s materials. Or, if you completed all of the challenges from the previous chapter, feel free to continue with that instead.

.gitignore across branches

Git’s easy and cheap branching strategy is amazing, isn’t it? But there are times when flipping between branches without a little forethought can get you into a mess.

Please ignore this file. It's unimportant.
git checkout yDoublyEven
ls -la
IGNORE_ME*
Please don't look in here
On branch yDoublyEven
nothing to commit, working tree clean
git checkout master
Please ignore this file. It's unimportant.
.
├── .DS_Store
├── .git
├── .tools-version
├── IGNORE_ME
├── LICENSE
├── README.md
├── SECRETS
├── css
├── img
├── index.html
└── js
IGNORE_ME*
Please don't look in here
git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   IGNORE_ME

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	.gitignore

no changes added to commit (use "git add" and/or "git commit -a")

How Git tracking works

When you stage a change to your repository, you’re adding the information about that file to Git’s index, or cache. This is a binary structure on disk that tracks everything you’ve added to your repository.

git status --ignored
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   IGNORE_ME

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	.gitignore

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

	.DS_Store
	js/.DS_Store

no changes added to commit (use "git add" and/or "git commit -a")

Updating the index manually

If all you want is for Git to ignore this file, you can update the index yourself to tell Git to assume that this file will never, ever change again. That’s a cheap and easy workaround.

git update-index --assume-unchanged IGNORE_ME
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)

	.gitignore

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

	.DS_Store
	js/.DS_Store

nothing added to commit but untracked files present (use "git add" to track)
Please don't look in here. I mean it.

Removing files from the index

When you implicitly or explicitly ask Git to start tracking a file, Git dutifully places that file in your index and starts watching for changes. If you’re quite certain that you don’t want Git to track this file anymore, you can remove this file from the index yourself.

git rm --cached IGNORE_ME
rm 'IGNORE_ME'
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	deleted:    IGNORE_ME

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	.gitignore

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

	.DS_Store
	IGNORE_ME
	js/.DS_Store
git add .gitignore
git commit -m "Added .gitignore and removed unnecessary file"
git log -- IGNORE_ME
commit 7ba2a1012e69c83c4642c56ec630cf383cc9c62b
Author: Yasmin <yasmin@example.com>
Date:   Mon Jul 3 17:34:22 2017 +0700
an
    Adding the IGNORE_ME file

Rebasing isn’t always the solution

Assume you don’t want anyone to know about the existence of IGNORE_ME. You’ve already learned one way to rewrite the history of your repository: Rebasing. But will this solve your current issue?

git log -p -1 7ba2a10
commit 7ba2a1012e69c83c4642c56ec630cf383cc9c62b
Author: Yasmin <yasmin@example.com>
Date:   Mon Jul 3 17:34:22 2017 +0700

    Adding the IGNORE_ME file

diff --git a/IGNORE_ME b/IGNORE_ME
new file mode 100644
index 0000000..28c0f4f
--- /dev/null
+++ b/IGNORE_ME
@@ -0,0 +1 @@
+Please ignore this file. It's unimportant.
git rebase -i 7ba2a10^
pick 7ba2a10 Adding the IGNORE_ME file
pick 883eb6f Adding methods to allow editing of the magic square
pick e632550 Adding ID to <pre> tag
pick f28af7a Adding ability to validate the inline square
pick c2cf184 Wiring up the square editing and validation
.
.
.
pick 5d026f0 Added .gitignore and removed unnecessary file
drop 7ba2a10 Adding the IGNORE_ME file
pick 883eb6f Adding methods to allow editing of the magic square
pick e632550 Adding ID to <pre> tag
pick f28af7a Adding ability to validate the inline square
pick c2cf184 Wiring up the square editing and validation
.
.
.
pick 5d026f0 Added .gitignore and removed unnecessary file
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
error: could not apply f985ed1... Centre align everything
git log --all --decorate --oneline --graph
.
.
.
| * | | e632550 Adding ID to <pre> tag
| * | | 883eb6f Adding methods to allow editing of the magic square
| |/ /
* | | 7ba2a10 Adding the IGNORE_ME file
* | | 32067b8 Adding the structure to the generator
|/ /
* | 69670e7 Adding a new secret
.
.
.
git rebase --abort

Using filter-branch to rewrite history

Let’s put the issue with IGNORE_ME aside for the moment; you’ll come back to it at the end of the chapter. Right now, you’ll work through an issue with a similar file, SECRETS, that plays out the dreaded scenario above where you’ve committed files or other information that you never wanted to be public.

cat SECRETS
DEPLOY_KEY=THIS_IS_REALLY_SECRET
RAYS_HOTTUB_NUMBER=012-555-6789
git rm --cached -- NoFileHere
fatal: pathspec 'NoFileHere' did not match any files
git rm --cached -- NoFileHere && echo 'success!'
git rm --cached --ignore-unmatch NoFileHere && echo 'success!'
git filter-branch -f --index-filter 'git rm --cached --ignore-unmatch -- SECRETS' HEAD
Rewrite f28af7aad4f77da8deb28f1e0eb93b85ee755b43 (20/38) (1 seconds passed, remaining 0 predicted)    rm 'SECRETS'
git log -- SECRETS
dcbdf0c Adding a new secret
git log -p -1 dcbdf0c
commit dcbdf0c2b3b5cf06eafd5dc6e441c8ab3a1d2ed5
Author: Will <will@example.com>
Date:   Mon Jul 3 14:10:59 2017 +0700
    Adding a new secret
git filter-branch --prune-empty -f HEAD

Challenge

Challenge: Remove IGNORE_ME from the repository

Now that you’ve learned how to eradicate any trace of a file from a repository, you can go back and remove all traces of IGNORE_ME from your repository. You previously removed all traces of SECRETS from your repository, but that took you two steps. The challenge here is to do the same in one single command:

Key points

  • .gitignore works by comparing files in the staging area, or index, to what’s in your working tree.
  • .gitignore won’t filter out any files already present in the index.
  • git status --ignored shows you the files that Git is currently ignoring.
  • git update-index --assume-unchanged <filename> tells Git to always assume that the file contained in the index will never change. This is a quick way to work around a file that isn’t being ignored.
  • git rm --cached <filename> removes a file from the index but leaves the original file in your working tree.
  • git rm --cached --ignore-unmatch <filename> will succeed, returning an exit code of 0, if git rm doesn’t match on a file in the index. This is important when you use this command in conjunction with filter-branch.
  • git filter-branch -f --index-filter 'git rm --cached --ignore-unmatch -- <filename>' HEAD will modify any matching commits in the repository to remove <filename> from their contents.
  • The --prune-empty option will remove any commits from the repository that are empty after your filter-branch.

Where to go from here?

What you’ve learned in this chapter will usually serve you well when you’ve committed something to your repository that you didn’t intend to be there.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now