Chapters

Hide chapters

Advanced Git

Second Edition · Git 2.32 · Console

Section I: Advanced Git

Section 1: 7 chapters
Show chapters Hide chapters

6. Gitignore After the Fact
Written by Jawwad Ahmad & Chris Belanger

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

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
Your branch is up to date with 'origin/yDoublyEven'.

nothing to commit, working tree clean
git checkout main
Please ignore this file. It's unimportant.
.
├── .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 main
Your branch is ahead of 'origin/main' by 21 commits.
  (use "git push" to publish your local commits)

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <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 main
Your branch is ahead of 'origin/main' by 21 commits.
  (use "git push" to publish your local commits)

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <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 main
Your branch is ahead of 'origin/main' by 21 commits.
  (use "git push" to publish your local commits)

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 main
Your branch is ahead of 'origin/main' by 21 commits.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git restore --staged <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 (origin/main, origin/HEAD)
Author: Yasmin <yasmin@example.com>
Date:   Mon Jul 3 17:34:22 2017 +0700

    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 baf24aa 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 baf24aa 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 --oneline --graph --all
.
.
.
| * | | 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 baf24aa0291b3364a0762c36509ab4c433527546 (39/40) (2 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: 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.

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 accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now