Practical developer and AI tutorials

Solutions, software guides, and clear step-by-step learning resources.

Sponsored

Sunday, November 15, 2026

Git troubleshooting: choose the right fix without losing work

Choose a Git command from the change you want to inspect, not from a list of commands to try. Staged edits, unstaged edits, untracked files and committed history are different things. This guide points to focused walkthroughs for each case, with a short diagnostic before you change anything.

Start inside your repository:

git status --short
git diff --stat
git diff --cached --stat

These inspect the current state. They do not clean your working folder, switch branches or rewrite commits. An empty diff is not proof that you have no work: the comparison may simply exclude the changes you are looking for.

A file is staged, but it should not be in the next commit

Use the guide to unstaging a file without losing its edits. This is for a change that should remain on disk while you decide what to commit. It is different from discarding changes or removing a tracked file from future snapshots.

Before running a command, identify the exact path and review its cached diff. Use the path separator -- and quote filenames containing spaces. The walkthrough covers new files and partial staging as well as ordinary tracked edits.

git diff is empty after git add

The default diff compares your working copy with the index. After staging a whole file, those versions can match even though the index differs from your current commit. Read why git diff is empty after git add to choose the right comparison.

Try git diff --cached -- your-file to inspect the staged patch. This does not require staging again or resetting anything. For the full distinction, consult the official diff reference.

A file still appears after adding it to .gitignore

Ignore rules do not make an already tracked file disappear from the index. The tracked-file ignore walkthrough helps distinguish an unmatched pattern from an already tracked path.

Stopping tracking a configuration file changes future commits and can affect collaborators when they pull. It also does not erase an old secret from repository history. If credentials were committed, treat rotation and history cleanup as separate work; an ignore rule is not a security fix.

git stash says there are no local changes

First check whether the files are tracked, untracked or ignored. The untracked-file stash guide explains why a normal stash can omit the work you see in your folder.

Review what would be included before widening a stash operation. Ignored build output and local settings may be large or sensitive. Avoid adding extra flags merely to suppress a confusing message.

You want to remove generated files, but need a preview first

Use the git clean dry-run guide before any deletion. Read the candidate paths and check ignored-file behavior. The preview is an opportunity to stop, not a promise that everything shown is safe to delete.

Untracked files can contain work that Git has never stored. A clean operation is different from unstaging a tracked edit and is not a general repair command.

You need to compare branches without disturbing local work

The branch-comparison walkthrough compares committed snapshots without checking either branch out. It separates an endpoint comparison from a three-dot merge-base comparison.

Choose the question first: do you want the difference between two branch tips, or the changes on one side since a shared base? Local references may be stale; a read-only comparison does not automatically fetch newer remote history.

You need the commit that last changed a line

Use the line-history guide using git blame, then inspect the resulting commit patch. Line attribution is a starting point for investigation, not proof that its author caused a bug.

Whitespace-only changes, renames and local edits can change what you see. The walkthrough shows how to narrow the line range and compare against committed content.

When to stop and inspect further

If short status reports an unresolved conflict, or a command refuses to overwrite a differing staged version, do not force it to match a tutorial. Inspect the index and working copy first. The status output reference explains the columns and conflict states; git restore documents the difference between restoring the index and restoring working files.

Pick the single walkthrough that matches your current state. Keeping a narrow path and reviewing the proposed patch is more useful than running several unrelated cleanup commands.

Saturday, November 14, 2026

Why git status shows a file as both staged and modified

A file can appear under both “Changes to be committed” and “Changes not staged for commit” when you stage one version, then edit it again. Git keeps the staged snapshot in its index. Your later edits remain in the working copy until you stage them too.

Start with these commands, replacing notes.txt with your file:

git status --short
git diff --cached -- notes.txt
git diff -- notes.txt

The second command shows what the next ordinary commit would include for that path. The third shows edits made after its staged snapshot. Neither command changes your file.

Read the two status columns

For an ordinary tracked file outside a merge conflict, short status has two columns before the filename. The first compares the index with the current commit. The second compares your working copy with the index. See the official status output reference for the complete table, including conflict codes.

MM notes.txt

The first M means a modification is staged. The second means the working file also differs from that staged version. It is one path with two comparisons, not two copies competing to be committed. A space in either column matters:

  • M : a modification is staged, with no further working-copy difference for that file.
  • M: the tracked file has an unstaged modification.
  • MM: it has both.
  • AM: a newly added file was staged and then modified again.

See exactly which version Git staged

Consider a committed file containing two lines:

title=old
body=old

You change the first line to title=new, run git add -- notes.txt, then change the second line to body=new. The index still has the new title with the old body. The working copy has both changes.

git show :notes.txt
git diff --cached -- notes.txt
git diff -- notes.txt
git diff HEAD -- notes.txt

In this example, the cached diff contains the title edit; the ordinary diff contains the body edit. The HEAD comparison shows the combined working-copy changes relative to the current commit. These comparison modes are documented in git diff. An ordinary git commit uses the index; do not assume everything currently visible in your editor is staged.

Choose what belongs in the next commit

If the staged version is intentional, leave it staged. Review the cached diff before committing. This is useful when a later edit belongs in a separate change.

If you want the current whole file staged, run:

git add -- notes.txt
git diff --cached -- notes.txt
git status --short

That updates the staged snapshot. If you only want selected later changes, inspect them with git add -p -- notes.txt instead of staging the whole path.

If you want none of this file staged, use Git 2.23 or newer:

git restore --staged -- notes.txt
git status --short
git diff -- notes.txt

With an existing HEAD commit, this restores the index entry from HEAD and leaves the working file alone. Keep --staged: a working-tree restore has a different purpose and can discard edits. The restore reference explains the destination and default source.

What was checked

An isolated Git 2.55.0 Windows fixture verified the MM example, the separate patches, the staged snapshot, restaging, and byte-for-byte preservation after unstaging. It also verified that unstaging a newly added file changes AM to an untracked entry while retaining its contents. These examples assume a repository with an existing commit and no unresolved merge conflict.

For the next step, use the full unstaging walkthrough. If the ordinary diff is unexpectedly empty after staging, see why git diff is empty after git add.

Tuesday, November 10, 2026

How to find which Git commit last changed a line

A retry limit in a configuration file looks wrong. Before editing it, you want to know when it changed and what the commit was trying to fix. Start with git blame for the line, then inspect the commit patch. Both commands let you investigate without switching branches or rewriting history.

Find the commit for one line

Run this from the repository root, replacing the example path with your tracked file. The quotes keep a path containing spaces together:

git blame --date=short -L 2,2 -- "app config.txt"

-L 2,2 selects line 2 only. For lines 10 through 20, use -L 10,20. The separator -- marks the following argument as a file path.

In a small test repository, line 2 was changed from retries=2 to retries=5. The command produced:

da07bf05 (Tutorial Author 2026-11-09 2) retries=5

The first field identifies the commit. The parentheses contain the recorded author, date and line number, followed by the line itself. Your hash and author will differ. Blame identifies the last attributed change to that surviving line; it does not establish who introduced a bug or explain the decision. The Git blame reference describes line ranges and attribution options.

Read the patch before drawing a conclusion

Copy the commit hash from your own result. In the example repository, the next command is:

git show da07bf05 -- "app config.txt"

Replace da07bf05 with your commit hash. The relevant patch in this example contains:

-retries=2
+retries=5

The minus line shows the previous value and the plus line shows the replacement. Read the commit message and surrounding patch too. A timeout change elsewhere in the same commit may explain the retry adjustment. The Git show reference covers viewing commit messages and changes.

Look past a whitespace-only edit

A later formatting commit changed the example to retries = 5. Ordinary blame then pointed to the formatting commit. To compare while ignoring whitespace changes, try:

git blame -w -L 2,2 -- "app config.txt"

In the tested example, this reached the earlier retry-limit commit. Check both patches if spacing can affect your file's meaning. Ignoring whitespace is an investigative option, not a reason to dismiss a change automatically.

Handle a line you edited locally

When the working-copy line was changed to retries = 9 without a commit, blame displayed an all-zero hash and Not Committed Yet. There is no saved commit for that local version. To inspect the committed version instead, specify HEAD:

git blame HEAD -L 2,2 -- "app config.txt"

Line numbers here belong to the committed file. If local edits inserted or removed lines, check that you selected the intended line. These inspections leave the file and staging state alone. If staged edits are confusing the comparison, see why git diff can be empty after git add.

Trace the file's earlier changes

For the history of one file, including earlier names when Git can follow its renames, use:

git log --oneline --follow -- "app config.txt"

This lists commits for the file, not only the selected line. Use its results to inspect older patches. The Git log reference documents the single-file scope of --follow. Blame also follows whole-file renames automatically, while copied or moved blocks can need additional investigation.

Blame cannot annotate a line that has already been deleted from the version you are examining. Inspect an older revision or the file's patches in that case. If the behavior differs between branches, continue with comparing branches without switching. Keep the line attribution, commit explanation and actual behavior as separate pieces of evidence.

Monday, November 9, 2026

Why git stash says no local changes when you have untracked files

You created a new file, ran git stash, and received No local changes to save. The file is still there. If your tracked files are unchanged, an ordinary untracked file can explain that result.

Start by saving open editor buffers. The example below uses a repository with an existing commit, an unchanged tracked.txt, an untracked scratch.txt, and a debug.log ignored by a *.log rule. It was checked with Git 2.55.0 on Windows in a separate test repository.

1. Identify the file category

git status --short --untracked-files=all

In that example, the relevant output is:

?? scratch.txt

The two question marks identify a file outside the index. debug.log is absent from this ordinary status listing because it is ignored. An edited tracked file would have a different status. Review every listed path before deciding what should go into your stash.

The official git status reference describes the status categories. If your question is about an already staged file, use the separate unstaging walkthrough.

2. Include ordinary untracked files deliberately

git stash push -u -m "include scratch notes"

In the test, scratch.txt disappeared from the working directory after this command, while the ignored log stayed in place. The new file had moved into the stash, rather than being committed to the branch.

Review tracked modifications too: adding -u does not restrict the operation to new files. A second check with an edited tracked.txt saved both that edit and scratch.txt. This distinction matters if you intended to set aside only one part of your current work.

3. Inspect the entry before restoring it

git stash list
git stash show --include-untracked --name-only "stash@{0}"

Select the entry with your message in the list. The example uses the newest entry, stash@{0}; substitute the appropriate entry if you have made another stash since then. Including untracked files in the show command made scratch.txt visible in the tested path list.

For a content-level inspection, the stash command also supports patch output. The git stash reference documents the push, show and apply options.

4. Restore, then verify the files

git stash apply "stash@{0}"
git status --short --untracked-files=all

In the clean test state, apply restored the exact bytes of scratch.txt and retained the stash entry. The tracked-and-untracked check also restored both files. Open your files and inspect their contents after applying; an entry in the stash list alone does not verify the result.

If a file with the same name already exists, or the branch has changed, applying can fail or conflict. Read the command's output before trying another operation. Keep the stash entry while resolving and verifying your work.

What about ignored files?

git check-ignore -v -- debug.log

This identifies the matching ignore rule when the path is ignored. The check-ignore documentation explains its verbose output. The tested -u operation left debug.log unchanged; do not assume it saved every file on disk. For tracked-file ignore problems, see why .gitignore does not ignore a tracked file.

The checks above cover ordinary tracked, untracked and ignored files in a repository with a commit. They do not establish conflict recovery, submodule behavior or recovery from losing the repository itself. Keep an independent copy of work you cannot afford to lose.

How to preview git clean without deleting files

Before cleaning a repository, make Git show the candidate paths. The commands in this guide all include -n, so they are previews. Start from the repository root and save any open editor buffers before inspecting the files on disk.

git status --short --untracked-files=all
git clean -nd

A short status listing and a cleanup preview answer different questions. Status helps identify tracked and untracked work. The cleanup preview shows paths selected by the clean command's options. Read both before deciding what should happen next.

A small example you can compare with your own repository

The checks for this guide used Git 2.55.0 on Windows, an existing commit containing tracked.txt, and these ignore rules:

build/
*.log

The working directory also contained notes.txt, draft notes.txt, drafts/todo.txt, build/result.txt and debug.log. Only the first three paths were ordinary untracked work; the build output and log matched the ignore rules.

For that fixture, the ordinary preview produced:

git clean -nd

Would remove draft notes.txt
Would remove drafts/
Would remove notes.txt

Every example file remained present with identical contents afterwards. The tracked file, commit and staged diff were unchanged too. Your output depends on your paths and ignore configuration; it need not match these example lines.

Choose the preview that matches your question

CommandWhat the example selected
git clean -ndOrdinary untracked files and the untracked drafts directory
git clean -ndXIgnored build output and debug.log only
git clean -ndxBoth groups of untracked paths, including ignored ones

The capital X and lowercase x are different options. In the tests, the capital-X preview listed build/ and debug.log but left out notes.txt. The lowercase-x preview listed all five untracked paths or directories. None of these commands changed their bytes.

The official git clean reference defines the dry-run, directory and ignore-selection options. Keep -n in place while comparing the candidate lists. Including -d makes ordinary untracked directories part of the preview.

Restrict the preview to a named path

git clean -nd -- "draft notes.txt"

The double hyphen separates options from the path. Quotes keep the filename with spaces together in PowerShell and common Unix shells. In the isolated check, the result named draft notes.txt without selecting the other example paths. That file's bytes were unchanged afterwards.

Use your own path, then read the entire result. A preview is an observation of the current working directory; creating another file or changing ignore rules can change a later result.

Explain a surprising omission

git check-ignore -v -- build/result.txt debug.log

The test output identified the build/ and *.log rules in .gitignore. The verbose check-ignore reference explains the rule and pathname fields. Ignore behavior can also come from other configured ignore sources, so inspect the reported rule rather than assuming it must be in the repository file.

A tracked file is a different case. Use git status to identify its state, and see the tracked-file ignore walkthrough when changing .gitignore has no effect. If your goal is to remove something from the next commit, unstage it while retaining your edits instead.

This guide stops at inspection. Untracked files may have no committed copy for Git to recover. Keep an independent copy of anything important before moving beyond a preview. The tests cover ordinary files and directories, not nested repositories, submodules or recovery after deletion.

Sunday, November 8, 2026

How to compare two Git branches without switching branches

To compare the committed contents of two Git branches, give both names to git diff:

git diff main feature/login --

You can run this while staying on your current branch. The comparison uses the commits named by main and feature/login, rather than your current working files. The git diff reference documents this two-commit form.

Check which branches you are comparing

git status --short
git branch --show-current
git branch --list main feature/login

Replace the example names with branches that exist in your repository. The branch listing lets you check the names before running the comparison. As the git branch documentation explains, --show-current prints your current branch; it prints nothing when HEAD is detached.

A dirty working tree does not require a branch switch for this comparison. Keep the initial status output as a record of any staged, unstaged or untracked work. The status reference describes those separate categories.

Compare the two branch tips

Start with a summary, then inspect the patch for a particular path:

git diff --stat main feature/login --
git diff main feature/login -- settings.txt

Here, main is the before side and feature/login is the after side. Minus lines belong to the first snapshot; plus lines belong to the second. The two-dot spelling, git diff main..feature/login --, makes the same endpoint comparison.

Consider this small history. A is the shared starting commit. B adds a line to README.md on main. C changes color=blue to color=green in settings.txt on feature/login.

      B  main
     /
A---+
     \
      C  feature/login

In the verified practice repository, this command listed two paths:

git diff --name-only main feature/login --

README.md
settings.txt

README.md appears because B and C contain different versions of it: C does not contain B's added line. That output does not mean the feature commit edited both files. The command is comparing snapshots, not listing commits.

Show the feature side since the shared ancestor

For the changes between the shared starting point and the feature tip, use three dots:

git diff --name-only main...feature/login --

settings.txt

In this example, that compares A with C, leaving out B's later README edit. You can inspect the common ancestor with:

git merge-base main feature/login

The merge-base reference explains how Git finds a shared ancestor. The history here has one merge base; more complicated histories can have several.

Argument order matters. In this same repository, git diff --name-only feature/login...main -- lists README.md because the comparison now follows A to B. Three dots are not simply a shorter way to request the endpoint diff.

What happened to the uncommitted work?

The command checks used a staged red version of settings.txt, an unstaged purple version, and an untracked scratch.txt. The committed branch comparison still showed blue changing to green. After all comparisons, the current branch, HEAD, index contents and both local files matched their initial state.

That result is specific to the branch-to-branch commands above. Plain git diff examines a different comparison. If you need to change what is staged, the guide to unstaging a file while keeping its edits covers that operation separately.

These commands inspect references already present locally. They do not contact a remote server or guarantee that a branch name reflects its latest remote state. If a name is missing, check the repository and available branches before trying a different command.

Saturday, November 7, 2026

Why .gitignore does not ignore a tracked file

You added a filename to .gitignore, saved the file, and Git still reports changes to it. The first question is whether Git already tracks that path. Ignore rules are intended for untracked files; they do not remove an existing file from the index. The gitignore reference explains that distinction.

This example uses settings.local.json in a repository that already has a commit. Save open editor buffers and keep a backup of any valuable local settings before changing tracking. Check the repository root and use your actual filename in each command.

1. Check whether the file is tracked

git status --short
git ls-files --error-unmatch -- settings.local.json

If the second command prints settings.local.json, the path is in the index. If it exits with an error, check the spelling and repository location; the file may already be untracked. The git ls-files documentation defines this index check. Do not assume that every file visible in your editor is tracked.

2. Check the ignore rule separately

For a file at the repository root, add this line to the root .gitignore:

/settings.local.json

Then inspect the matching rule:

git check-ignore -v --no-index -- settings.local.json

The verbose output identifies the ignore file, line number and matching pattern. The --no-index option lets this diagnostic examine a path even when it is tracked. Without it, tracked paths are normally omitted. See git check-ignore.

3. Remove only that path from tracking

First preview the operation, then perform it if the path is correct:

git rm -n --cached -- settings.local.json
git rm --cached -- settings.local.json

The first command is a dry run. The second removes the path from the index while leaving the working file on disk. Keep --cached in the command. If Git refuses because the staged content differs, inspect those versions rather than adding --force. The git rm reference covers the option and its checks.

4. Review the staged change before committing

git add -- .gitignore
git status --short
git diff --cached --stat

A staged deletion for settings.local.json is expected: the next commit will omit that path. Confirm the local file still opens and contains the settings you need. Review the complete staged changes before making your normal commit.

In the isolated verification for this example, the local file stayed byte-for-byte unchanged after cached removal and after the commit. An older commit still contained it. A clean collaborator clone lost the tracked file when it pulled the removal commit. Discuss that effect with your team if everyone needs a local configuration file; a separate example configuration can document the required fields.

If you only meant to unstage an edit

Stopping tracking changes future commits. If the file should remain tracked and you only want to remove an edit from the next commit, follow the guide to unstaging a file without losing its current edits instead. Choose the operation based on what the next commit should contain.

Dev & AI Toolbox

Practical developer tutorials, AI tool comparisons, troubleshooting guides and free software resources.

Search This Blog

Powered by Blogger.

Blog Archive

Git troubleshooting: choose the right fix without losing work

Choose a Git command from the change you want to inspect, not from a list of commands to try. Staged edits, unstaged edits, untracked files ...