Chapter 11: Stashing and Cleaning in Git

Chapter Objectives

By the end of this chapter, you will be able to:

  • Temporarily shelve uncommitted changes using git stash.
  • List, apply, pop, and manage multiple stashes.
  • Include untracked or all files in a stash.
  • Safely preview files that would be removed by git clean.
  • Remove untracked files and directories from your working directory using git clean.
  • Use git clean interactively to selectively remove files.
  • Understand the importance of using git stash for context switching and git clean for maintaining a tidy working directory.

Introduction

In your day-to-day work as a developer, you’ll often find yourself needing to quickly switch contexts. Perhaps an urgent bug fix request comes in while you’re in the middle of a new feature, or you need to pull updates from a remote repository, but your working directory is messy with experimental changes. Committing half-finished work just to switch branches isn’t ideal, as it clutters your project history with incomplete snapshots.

This is where Git’s stash and clean commands come into play. git stash allows you to temporarily save your modified, uncommitted changes (both staged and unstaged) so you can get a clean working directory. You can then reapply these changes later. git clean helps you remove untracked files from your working directory, which is useful for getting rid of build artifacts or other temporary files that aren’t part of your project’s history.

Mastering these commands will significantly improve your workflow efficiency, allowing you to handle interruptions gracefully and maintain a pristine working environment. This chapter builds upon your understanding of the working directory, staging area, and commits, providing you with tools to manage changes that aren’t yet ready to be permanently recorded.

Theory

Shelving Changes with git stash

Imagine you’re working on a feature and have made several modifications to tracked files. Some changes might be staged, others just modified in your working directory. Suddenly, you need to switch to another branch to fix a critical bug. Your current work is not ready for a commit. What do you do?

git stash is designed for exactly this scenario. It takes your uncommitted changes (both staged and unstaged modifications to tracked files) and “stashes” them away, saving them in a special, temporary storage area. This reverts your working directory to the state of the last commit (HEAD), giving you a clean slate.

What is a Stash?

A stash is essentially a commit object that isn’t on any normal branch. It stores:

  1. The state of your working directory (changes to tracked files).
  2. The state of your staging area (changes that were git added).
  3. A reference to the commit you were on when you created the stash (e.g., HEAD).

Git maintains a stack of stashes. Each time you git stash, a new stash is pushed onto this stack.

%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%%
graph TB
    subgraph Stash Stack Area
        direction BT
        S_N["stash@{n}<br>(Oldest Stash)"] --> S_Ellipsis["..."] --> S1["stash@{1}"] --> S0["<b>stash@{0}</b><br>(Most Recent / Top of Stack)"]
    end

    WD_Changes["Working Directory<br>+ Index Changes<br>(Tracked files, optionally untracked)"]

    subgraph Stash Operations
        direction LR
        StashPush["`git stash push`"]
        StashPop["`git stash pop`"]
        StashApply["`git stash apply`"]
    end

    WD_Changes -- "Shelve Changes" --> StashPush
    StashPush -- "Adds New Stash to Top" --> S0

    S0 -- "Applies Top Stash & Removes it" --> StashPop
    StashPop -- "Restores to Working Directory" --> WD_Restored_Pop["Working Directory Updated"]

    S0 -- "Applies Top Stash & Keeps it" --> StashApply
    StashApply -- "Restores to Working Directory" --> WD_Restored_Apply["Working Directory Updated"]


    %% Styling
    classDef primary fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6;
    classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF;
    classDef success fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46;
    classDef datastructure fill:#FFFBEB,stroke:#F59E0B,stroke-width:1px,color:#B45309;


    class S0,S1,S_Ellipsis,S_N primary;
    class StashPush,StashPop,StashApply process;
    class WD_Changes datastructure;
    class WD_Restored_Pop,WD_Restored_Apply success;

Core Stash Operations:

  • git stash push [-m <message>] [-u | --include-untracked] [-a | --all] or git stash [save <message>] [-u | --include-untracked] [-a | --all]:
    • This is the command to create a new stash. Historically, git stash save "message" was common, but git stash push -m "message" is the more modern and recommended syntax, aligning with other push commands in Git. If no options are given, git stash (which defaults to git stash push) stashes changes to tracked files (both staged and unstaged).
    • -m <message> or (for save) just <message>: Allows you to provide a descriptive message for the stash, making it easier to identify later.
    • -u or --include-untracked: Includes untracked files in the stash.
    • -a or --all: Includes all untracked files and ignored files in the stash. Use with caution.
  • git stash list: Displays all the stashes you’ve saved in a LIFO (Last-In, First-Out) stack. Each stash is given an identifier like stash@{0}, stash@{1}, etc., with stash@{0} being the most recent.
  • git stash apply [<stash>]: Applies the changes from a stash to your working directory but keeps the stash in the stash list. If no <stash> is specified, it applies the latest one (stash@{0}).
  • git stash pop [<stash>]: Applies the changes from a stash to your working directory and then removes the stash from the stash list (if the application is successful without conflicts). If no <stash> is specified, it pops the latest one.
  • git stash drop [<stash>]: Removes a specific stash from the stash list without applying it. If no <stash> is specified, it drops the latest one.
  • git stash clear: Removes all stashes from the stash list. This is a destructive operation for the stashes themselves.
  • git stash show [<stash>]: Shows a summary of changes recorded in a stash (diffstat). Add -p or --patch to see the full diff.
  • git stash branch <branchname> [<stash>]: Creates a new branch with the given <branchname>, checks it out, and applies the specified stash (or stash@{0} if none is given). It then drops the stash if the application is successful. This is useful if the changes in your stash have diverged significantly from the branch you were on.
Core Stash Command Description
git stash push [-m <message>] [-u | –include-untracked] [-a | –all] Creates a new stash.
-m: Add a descriptive message.
-u: Include untracked files.
-a: Include all files (untracked and ignored). Use with caution.
(Can also use git stash or git stash save <message> for older syntax).
git stash list Displays all saved stashes in a LIFO (Last-In, First-Out) stack (e.g., stash@{0}, stash@{1}).
git stash apply [<stash>] Applies changes from a stash to your working directory but keeps the stash in the list. Defaults to the latest stash (stash@{0}).
git stash pop [<stash>] Applies changes from a stash and, if successful (no conflicts), removes the stash from the list. Defaults to stash@{0}. If conflicts occur, the stash is not dropped.
git stash drop [<stash>] Removes a specific stash (or stash@{0} if unspecified) from the list without applying it.
git stash clear Removes all stashes from the list. This is a destructive operation for the stashes.
git stash show [-p | –patch] [<stash>] Shows a summary of changes (diffstat) recorded in a stash.
-p or –patch: Display the full diff of the changes. Defaults to stash@{0}.
git stash branch <branchname> [<stash>] Creates a new branch named <branchname>, checks it out, applies the specified stash (or stash@{0}), and then drops the stash if the application is successful. Useful for turning a stash into a dedicated branch.

Stash Conflicts:

When you apply or pop a stash, Git tries to merge the stashed changes back into your working directory. If the files in your working directory have changed since you created the stash in a way that conflicts with the stashed changes, you’ll encounter a merge conflict, similar to conflicts during git merge or git rebase. You’ll need to resolve these conflicts manually, then git add the resolved files before committing. If git stash pop results in a conflict, the stash is not automatically dropped; you’ll need to git stash drop it manually after resolving conflicts and deciding you no longer need it.

%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%%
graph TD
    A["Start: Attempt <i>git stash pop</i> or <i>git stash apply</i>"] --> B{Conflicts Occur?};
    B -- No --> C["Success! Changes applied cleanly.<br>If <i>pop</i>, stash is dropped.<br>If <i>apply</i>, stash remains."];
    C --> Z["End: Continue working"];

    B -- Yes --> D["Git reports: 'Automatic merge failed; fix conflicts and then commit the result.'"];
    D --> E["Files with conflicts are marked (e.g. <i><<<<<<<</i>, <i>=======</i>, <i>>>>>>>></i>)"];
    E --> F["Working directory contains partially merged changes."];
    F --> G{"Action: If <i>git stash pop</i> was used..."};
    G --> H["<b>Important: The stash is NOT dropped automatically!</b>"];
    H --> I["Manually inspect and resolve conflicts in each affected file."];
    G -- "If <i>git stash apply</i> was used..." --> I;
    I --> J["Edit files to choose desired changes, remove conflict markers."];
    J --> K["Once conflicts are resolved: <i>git add <resolved_file1> <resolved_file2> ...</i>"];
    K --> L{"All conflicts resolved and staged?"};
    L -- No --> I;
    L -- Yes --> M["Optional: <i>git commit</i> the resolved changes (often good practice).<br>Alternatively, continue working and commit later."];
    M --> N{"Was the original command <i>git stash pop</i> (and stash still exists)?"};
    N -- Yes --> O["Manually drop the stash: <i>git stash drop <stash_id></i>"];
    O --> Z;
    N -- "No (was <i>apply</i> or stash already dropped)" --> Z;

    %% Styling
    classDef startNode fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6;
    classDef processNode fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF;
    classDef decisionNode fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E;
    classDef endNode fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46;
    classDef checkNode fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B; 
    classDef infoNode fill:#E0E7FF,stroke:#4338CA,stroke-width:1px,color:#3730A3; 

    class A startNode;
    class I,J,K,M,O processNode;
    class B,G,L,N decisionNode;
    class C,Z endNode;
    class D,E,F infoNode;
    class H checkNode;

Cleaning Your Working Directory with git clean

While git stash deals with uncommitted changes to tracked files (and optionally untracked/ignored files), git clean is specifically for removing untracked files from your working directory. Untracked files are those that have not been git added and are not listed in your .gitignore file (unless you explicitly tell git clean to also remove ignored files).

Why Clean?

Over time, your working directory can accumulate various untracked files:

  • Build artifacts (e.g., .o files, compiled binaries, dist folders).
  • Log files.
  • Temporary files created by editors or tools.
  • Files you downloaded or created experimentally.

These files can clutter your git status output and potentially be accidentally committed. git clean provides a way to get rid of them.

Warning: git clean is a destructive command. Files removed by git clean are not stored by Git anywhere (unlike git stash which saves changes). Once deleted, they are generally gone for good unless your OS or editor has a separate recovery mechanism. Always use git clean with caution.

Core Clean Operations:

  • git clean -n or git clean --dry-run: This is the most important first step. It shows you which files would be removed without actually deleting anything. Always run this before an actual clean.
  • git clean -f or git clean --force: This command is required to actually delete the untracked files. Git enforces this to prevent accidental data loss.
  • git clean -d: By default, git clean only removes untracked files. If you also want to remove untracked directories, you need to add the -d option.
  • git clean -x: This tells git clean to also remove files that are ignored by Git (i.e., files matching patterns in .gitignore). Use this with extreme caution, as you might be ignoring important local configuration files.
  • git clean -X: Only remove files ignored by Git. This is useful for cleaning up build artifacts if your .gitignore is set up to ignore them, but leaves other untracked files alone.
  • git clean -i or git clean --interactive: This presents an interactive interface allowing you to review and selectively choose which files or groups of files to delete. This is a much safer way to clean your directory if you’re unsure.
`git clean` Option Example Usage Description
Dry Run (-n or –dry-run) git clean -n
git clean -nd (incl. dirs)
Shows which files/directories would be removed without actually deleting anything. Always run this first!
Force (-f or –force) git clean -f Actually deletes the untracked files. Git requires this option to prevent accidental data loss. Use only after a dry run.
Remove Directories (-d) git clean -fd In addition to untracked files, also removes untracked directories. Often used with -f.
Remove Ignored Files Too (-x) git clean -fdx Removes untracked files AND files/directories that are normally ignored by Git (matching patterns in .gitignore). Use with EXTREME CAUTION.
Remove Only Ignored Files (-X) git clean -fX Removes ONLY files/directories that are ignored by Git, leaving other untracked files untouched. Useful for cleaning build artifacts if .gitignore is well-configured. Use with caution.
Interactive (-i or –interactive) git clean -id Presents an interactive interface, allowing you to review and selectively choose which files or groups of files to delete. A safer way to clean if you’re unsure. Can be combined with -d.

Combining Options:

You often combine these options, for example:

  • git clean -nd: Dry run, showing untracked files and directories that would be removed.
  • git clean -fd: Force remove untracked files and directories.
  • git clean -fdx: Force remove untracked files, directories, and ignored files (very aggressive).

[Insert diagram illustrating git clean: A working directory with tracked files (green), modified tracked files (yellow), and untracked files (red). git clean targets the red files.]

GUI Tools:

While this chapter focuses on CLI commands, many GUI tools like VS Code’s Git integration, GitKraken, or Sourcetree offer visual ways to manage stashes and sometimes to clean the working directory. Understanding the CLI commands, however, provides a deeper understanding of the underlying operations.

Git Stash vs Git Clean

Feature / Aspect git stash git clean
Primary Purpose Temporarily shelve uncommitted changes to tracked files. Remove untracked files (and optionally directories) from the working directory.
What it Affects Primarily modified tracked files (both staged and unstaged).
Optionally (-u): untracked files.
Optionally (-a): untracked and ignored files.
Primarily untracked files and directories.
Optionally (-x): ignored files.
Optionally (-X): only ignored files.
Safety & Data Integrity Changes are saved in a stash list and can be reapplied. Relatively safe. Deletes files permanently from the working directory. Potentially destructive if used carelessly.
Recovery Stashed changes can be listed (git stash list), applied (git stash apply), or popped (git stash pop). No built-in Git recovery for cleaned files. Recovery relies on OS recycle bin (if applicable) or external backups.
Typical Use Cases
  • Quickly switching branches.
  • Pulling updates when the working directory is “dirty”.
  • Saving work-in-progress that isn’t ready for a commit.
  • Removing build artifacts (e.g., .o, .class, dist/ folders).
  • Deleting temporary log files or editor backup files.
  • Cleaning up experimental files not intended for the project.
Key Commands git stash push, git stash pop, git stash apply, git stash list git clean -n (dry run), then git clean -f (force). Options like -d, -x, -i.
Analogy Putting your current work neatly on a temporary shelf to pick up later. Tidying up your workspace by throwing away clutter that doesn’t belong.

Practical Examples

Setup:

Let’s create a new repository and set up a scenario for practicing stashing and cleaning.

Bash
# Create and navigate to a new directory
mkdir git-stash-clean-practice
cd git-stash-clean-practice

# Initialize a Git repository
git init

# Configure user (if not set globally)
# git config user.name "Your Name"
# git config user.email "youremail@example.com"

# Create and commit some initial files
echo "Initial content for main file." > main.txt
git add main.txt
git commit -m "C1: Add main.txt"

echo "Feature A in progress" > feature_a.txt
git add feature_a.txt
git commit -m "C2: Start feature A"

# Now, let's create a messy working directory
# 1. Modify a tracked file
echo "Initial content for main file. With new updates." > main.txt

# 2. Stage another modification
echo "Feature A in progress - more work done" > feature_a.txt
git add feature_a.txt # Staged change

# 3. Create an untracked file
echo "This is a temporary log file." > temp.log

# 4. Create an untracked file in an untracked directory
mkdir build_artifacts
echo "Compiled output" > build_artifacts/output.bin

# 5. Create an ignored file
echo "*.log" > .gitignore
echo "Another log file, should be ignored." > ignored.log
git add .gitignore
git commit -m "Add .gitignore to ignore log files"

# Now, our temp.log is ignored. Let's create a new one that IS untracked.
echo "This is an untracked data file." > data.tmp

# Check status
git status

Expected git status output (will vary slightly based on exact file states and Git version, but generally):

Bash
On branch main
Your branch is up to date with 'origin/main'. # Assuming no remote yet, or up-to-date

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   feature_a.txt

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:   main.txt

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

nothing added to commit but untracked files present (use "git add" to track)

Note: ignored.log and temp.log won’t show as untracked if .gitignore is correctly processed and they match the ignore patterns. data.tmp and build_artifacts/ are untracked.

1. Basic Stashing (git stash push)

You need to switch branches, but your work on main.txt (unstaged) and feature_a.txt (staged) isn’t ready.

Bash
# Stash the current changes
git stash push -m "Work on main and feature_a"

Expected Output:

Bash
Saved working directory and index state On main: Work on main and feature_a

Now check your status:

Bash
git status

Expected Output:

Bash
On branch main
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        build_artifacts/
        data.tmp

nothing added to commit but untracked files present (use "git add" to track)

Your working directory is clean regarding tracked files! main.txt and feature_a.txt are back to their state at “C2: Start feature A”. The untracked files data.tmp and build_artifacts/ are still there because the default stash doesn’t include them.

2. Listing Stashes (git stash list)

Bash
git stash list

Expected Output:

Bash
stash@{0}: On main: Work on main and feature_a

This shows our stash, with stash@{0} being the most recent.

3. Stashing with Untracked Files (git stash push -u)

Let’s say you also want to stash data.tmp but not build_artifacts.

First, let’s make another change to a tracked file to have something new to stash.

Bash
echo "Another change to main.txt" >> main.txt
git status # Shows main.txt modified, data.tmp and build_artifacts untracked

Now, stash including untracked files:

Bash
git stash push -u -m "Updates to main.txt and untracked data.tmp"

Expected Output:

Bash
Saved working directory and index state On main: Updates to main.txt and untracked data.tmp

Check status:

Bash
git status

Expected Output:

Bash
On branch main
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        build_artifacts/

nothing added to commit but untracked files present (use "git add" to track)

Now data.tmp is also gone from the working directory (stashed). build_artifacts is still there.

List stashes again:

Bash
git stash list

Expected Output:

Bash
stash@{0}: On main: Updates to main.txt and untracked data.tmp
stash@{1}: On main: Work on main and feature_a
stash@{0} is the new stash with untracked files.

4. Applying a Stash (`git stash apply`)

Let’s apply the first stash we made (`stash@{1}`), which contained changes to `main.txt` and `feature_a.txt`.

Bash
git stash apply stash@{1}

Expected Output (might include a note about conflicts if any, but unlikely here):

Bash
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   feature_a.txt

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:   main.txt

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

The changes from stash@{1} are now back in your working directory and staging area, just as they were when you stashed them. data.tmp is not restored because it was part of stash@{0}.

Check the stash list:

Bash
git stash list

Expected Output:

stash@{0}: On main: Updates to main.txt and untracked data.tmp
stash@{1}: On main: Work on main and feature_a
stash@{1} is still there because `apply` doesn't remove it.




Tip: If you had conflicting changes in your working directory before applying, Git would attempt a merge and might report conflicts.

5. Popping a Stash (git stash pop)

Let’s assume the changes from stash@{1} are now good. We could commit them.
But first, let’s reset the working directory to HEAD to demonstrate pop cleanly.

Bash
git reset --hard HEAD # Careful: discards current uncommitted changes
# Our working dir is now clean (back to C2 state + .gitignore)
# Untracked files (build_artifacts/) might still be there.

Now, let’s pop the most recent stash (stash@{0}), which included main.txt changes and data.tmp.

Bash
git stash pop stash@{0} # You can omit stash@{0} to pop the latest

Expected Output:

Bash
On branch main
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:   main.txt

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

Dropped stash@{0} (e0f4e222f9c6...) # Hash will vary

The changes from stash@{0} (modified main.txt and the untracked data.tmp) are restored.

And stash@{0} has been removed from the list:

Bash
git stash list

Expected Output:

Bash
stash@{1}: On main: Work on main and feature_a

Only stash@{1} remains. (Note: The index stash@{1} now becomes stash@{0} effectively, as it’s the top of the stack).

6. Dropping a Stash (git stash drop)

We decided we don’t need stash@{1} (which is now stash@{0}) anymore.

Bash
git stash drop stash@{0} # Or just 'git stash drop' if it's the latest

Expected Output:

Bash
Dropped stash@{0} (abcdef123...) # Hash will vary

Check the list:

Bash
git stash list

Expected Output: (empty, or an error saying no stashes found)

Bash
# No output, or a message like "No stashes found."

All stashes are gone.

7. Cleaning the Working Directory (git clean)

Our git status currently shows:

Bash
On branch main
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:   main.txt # From the last pop

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

Let’s commit the changes to main.txt to have a cleaner state for git clean.

Bash
git add main.txt
git commit -m "Incorporate stashed changes into main.txt"
git status # Should show only untracked files now

a. Dry Run (git clean -n)

First, see what git clean would do:

Bash
git clean -n

Expected Output:

Bash
Would remove data.tmp

Notice it doesn’t mention build_artifacts/ yet, because by default clean doesn’t look into directories.

b. Dry Run with Directories (git clean -nd)

Bash
git clean -nd

Expected Output:

Bash
Would remove build_artifacts/
Would remove data.tmp

This shows it would remove the untracked data.tmp file and the untracked build_artifacts directory.

c. Force Clean Files and Directories (git clean -fd)

Warning: This will delete files! Make sure you’re okay with removing what the dry run showed.

Bash
git clean -fd

Expected Output (will vary if you have other untracked files/dirs):

Bash
Removing data.tmp
Removing build_artifacts/

Now check git status:

Bash
git status

Expected Output:

Bash
On branch main
nothing to commit, working tree clean

Your working directory is now free of untracked files and directories.

d. Interactive Clean (git clean -id)

Let’s create some more untracked files to demonstrate interactive cleaning.

Bash
echo "temp file 1" > temp1.txt
echo "temp file 2" > temp2.txt
mkdir temp_dir
echo "content" > temp_dir/temp3.txt
git status # Shows temp1.txt, temp2.txt, temp_dir/ as untracked

Now, run interactive clean (including directories):

Bash
git clean -id

Expected Output (an interactive prompt):

Bash
Would remove the following items:
  temp1.txt        temp2.txt        temp_dir/
*** Commands ***
    1: clean                2: filter by pattern    3: select by numbers
    4: ask each             5: quit                 6: help
What now>

You can type 1 to clean all listed, 4 to be asked for each item, or select specific items.

For example, type 4 (ask each):

Bash
What now> 4
Remove temp1.txt ([y]es/[n]o/[q]uit/[a]ll/[d]one/[?]help)? y
Remove temp2.txt ([y]es/[n]o/[q]uit/[a]ll/[d]one/[?]help)? n
Remove temp_dir/ ([y]es/[n]o/[q]uit/[a]ll/[d]one/[?]help)? y
Removing temp1.txt
Removing temp_dir/
temp1.txt and temp_dir/ would be removed, but temp2.txt would remain.

OS-Specific Notes

  • Path Syntax: Git CLI generally uses forward slashes (/) for paths, even on Windows where the native path separator is a backslash (\). PowerShell often handles this gracefully, but in CMD on Windows, you might need to be careful or use Git Bash which provides a Unix-like environment.
  • Line Endings: While not directly related to stash or clean functionality, remember that Git handles line endings (CRLF on Windows, LF on macOS/Linux). If you stash files and apply them on a different OS or with different core.autocrlf settings, you might see entire files marked as modified. This is a general Git consideration.
  • git clean and File Permissions/Locks:
    • Windows: If an untracked file is currently locked by another process (e.g., an open Word document, a running executable), git clean -f might fail to delete it, reporting a permission error. You’ll need to close the locking program.
    • macOS/Linux: If Git doesn’t have write permission in a directory or on a specific untracked file, git clean -f might fail. You might need to adjust permissions or run the command with sufficient privileges (though running git as root is generally discouraged for regular operations).
  • Case Sensitivity: macOS can be configured with a case-insensitive filesystem (default) or a case-sensitive one. Linux typically uses case-sensitive filesystems. Windows is generally case-insensitive but case-preserving. This can sometimes lead to subtle issues with untracked files if naming differs only by case, though git clean usually handles what git status reports as untracked.

Common Mistakes & Troubleshooting Tips

Git Issue / Error Symptom(s) Troubleshooting / Solution
git stash apply vs. pop misuse git stash list shows many similar/identical stashes. Stash not removed after applying. Remember: apply keeps the stash.
  • If happy with applied changes, use git stash drop <stash_id>.
  • Use git stash pop to apply and remove in one step (if no conflicts).
Accidental Deletion with git clean -f Important untracked files are permanently deleted from the working directory. Prevention is key:
  • ALWAYS run git clean -n (dry run) or git clean -nd (dry run with directories) first.
  • Carefully review the list of files that would be removed.
  • If unsure, use git clean -i for an interactive session.
  • Recovery outside of Git (OS recycle bin, backups) is the only hope if files are already cleaned.
Stash Doesn’t Include Untracked Files Ran git stash (or git stash push), but untracked files remain in the working directory. By default, stash only affects tracked files.
  • Use git stash push -u (or git stash -u) to include untracked files.
  • Use git stash push -a (or git stash -a) to include untracked and ignored files (use with caution).
Stash Conflicts on pop or apply Git reports merge conflicts (e.g., <<<<<<< markers) in files. If pop was used, the stash is not automatically dropped.
  1. Manually edit the conflicted files to resolve differences.
  2. Use git add <resolved_file> for each resolved file.
  3. Once all conflicts are resolved and staged, you can commit the changes.
  4. If you used git stash pop and had conflicts: The stash was not dropped. Manually run git stash drop <stash_id> after you’re sure the changes are correctly integrated.
Forgetting Stash Messages git stash list shows several entries like “WIP on branchname: …” making it hard to distinguish them. Use descriptive messages when stashing:
  • git stash push -m “Descriptive message about changes”
  • (Older syntax: git stash save “Descriptive message”)
This makes it much easier to identify and manage stashes later.
git clean not removing directories Ran git clean -f but untracked directories are still present. By default, git clean only removes files.
  • Use the -d option: git clean -fd to remove untracked directories as well.
  • Always preview with git clean -nd first.
git clean removing ignored files unintentionally Ran git clean -fx and it removed important local configuration files that were in .gitignore. The -x option tells git clean to also remove files that are ignored by Git.
  • Use -x with extreme caution.
  • Always use git clean -nx (dry run) to see what would be removed before running git clean -fx.
  • If you only want to clean ignored files (and not other untracked files), use -X (capital X). Preview with git clean -nX.

Exercises

Use the git-stash-clean-practice repository. You might need to reset its state or create new files/changes for each exercise.

  1. Context Switching with Stash:
    1. On your main branch, modify main.txt and create a new untracked file new_feature_notes.txt.
    2. Stage the changes to main.txt but leave new_feature_notes.txt untracked.
    3. You need to quickly switch to a new branch called hotfix (create it from main). Stash your current work (including the untracked notes file) with a message “Feature X work in progress”.
    4. On the hotfix branch, make a trivial change to main.txt (e.g., add a comment) and commit it.
    5. Switch back to the main branch.
    6. Apply your stashed changes. Verify that main.txt has your staged changes and new_feature_notes.txt is present.
    7. What happens to the commit you made on the hotfix branch regarding main.txt? How does Git handle applying the stashed changes to main.txt? (Hint: Think about the base of the stash).
    8. Assuming the applied changes are good, remove the stash from the list.
  2. Cleaning Up a Build Directory:
    1. Create the following untracked files and directories:
      • build/app.exe
      • build/lib.dll
      • logs/debug.log
      • temp_report.pdf
    2. Add *.log and *.pdf to your .gitignore file and commit it.
    3. Modify main.txt but don’t stage or commit it.
    4. Use git clean in dry-run mode to see what would be removed if you wanted to clean only files (not directories) that are not ignored.
    5. Use git clean in dry-run mode to see what would be removed if you wanted to clean all untracked files and directories, excluding ignored files.
    6. Use git clean in dry-run mode to see what would be removed if you wanted to clean all untracked files and directories, including ignored files.
    7. Interactively clean the directory, choosing to remove the build/ directory and temp_report.pdf, but keep logs/debug.log (even if it’s ignored, you might choose to keep it in the interactive step if not using -x). What happens to your modified main.txt?
  3. Managing Multiple Stashes:
    1. Modify feature_a.txt and stash the change with the message “Stash 1: Feature A tweak”.
    2. Modify main.txt and create a new untracked file config.yml. Stash these changes (including the untracked file) with the message “Stash 2: Main update and config”.
    3. Modify feature_a.txt again with a different change. Stash this with the message “Stash 3: Another Feature A idea”.
    4. List your stashes.
    5. Apply “Stash 2” (the one with main.txt and config.yml) without removing it from the list.
    6. Pop “Stash 1” (the first Feature A tweak). Resolve any conflicts if they occur (e.g., if the change from Stash 1 conflicts with the current state of feature_a.txt if it was somehow modified by Stash 2 – unlikely in this setup but good to think about).
    7. Drop “Stash 3” without applying it.
    8. Verify the final state of your files and the stash list.

Summary

  • git stash (or git stash push): Saves uncommitted changes (staged and unstaged in tracked files) to a temporary area, cleaning the working directory.
    • git stash push -m "message": Stash with a descriptive message.
    • git stash push -u or git stash -u: Includes untracked files.
    • git stash push -a or git stash -a: Includes all untracked and ignored files.
  • git stash list: Shows all current stashes.
  • git stash apply [<stash>]: Applies a stash’s changes but keeps the stash in the list.
  • git stash pop [<stash>]: Applies a stash’s changes and removes it from the list (if no conflicts).
  • git stash drop [<stash>]: Deletes a stash from the list.
  • git stash clear: Deletes all stashes.
  • git clean: Removes untracked files from the working directory. Use with caution.
    • git clean -n or --dry-run: Preview files to be deleted. Always use first.
    • git clean -f or --force: Required to actually delete files.
    • git clean -d: Also remove untracked directories.
    • git clean -x: Also remove ignored files.
    • git clean -i or --interactive: Interactively choose which files to delete.

Stashing is invaluable for context switching and keeping a clean working state without making premature commits. Cleaning helps manage build artifacts and other non-project files. Both contribute to a more organized and efficient Git workflow.

Further Reading

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top