DEV Community

Cover image for The Problem With `git add .`
Jerry Schneider
Jerry Schneider

Posted on

The Problem With `git add .`

When I first learned I could run:

git add .
Enter fullscreen mode Exit fullscreen mode

Instead of adding files one at a time, I felt like I had discovered some secret Git trick.

No more:

git add file1
git add file2
git add file3
Enter fullscreen mode Exit fullscreen mode

Just stage everything and move on.

And honestly, for a while, that was an upgrade. It removed friction. It made committing faster. It felt like the kind of thing that separated people who were comfortable in Git from people who were still fumbling through it.

But like a lot of “smart” shortcuts, it worked best right up until real life got involved.

Sometimes I had a tmp file sitting around and committed it by accident.

Sometimes I had changes I liked in one file and changes I definitely did not like in another.

Sometimes I forgot to switch branches before starting a new ticket and ended up with a mix of unrelated edits.

And lately, with more AI-assisted coding tools in the mix, I’ve ended up with even more repo noise than I used to. Cursor in particular is great at helping me think and explore, but it also tends to leave behind markdown files and other artifacts that are useful while I’m working and absolutely not something I want in a commit.

That was the point where git add . stopped feeling smart.

The problem wasn’t that Git was bad. The problem was that git add . assumes everything in your working tree belongs together. And a lot of the time, that’s just not true.

Real Work Is Messy (for me at least, maybe you're different)

In theory, a commit is supposed to represent one coherent idea.

In practice, a working tree often looks more like this:

  • one file with the actual fix
  • one file with a failed attempted fix
  • one file with a cleanup you’re not sure you want
  • one file you edited while debugging
  • one random note or markdown file created during exploration
  • one change that really belongs on another branch entirely

How often does your working tree actually contain just one coherent change?

You can deal with that using standard Git commands. You can manually add files one by one. You can use patch mode. You can keep opening git status and re-reading the same list over and over until you convince yourself you know what should be staged.

I’ve done all of that.

But in a normal day of development, the issue usually is not “is this possible in Git?” The issue is “what is the lowest-friction way to do the right thing?”

That is the bar I care about.

Because if the right thing is annoying, I am much more likely to do the easy thing.

And for a long time, the easy thing was git add ..

What I Actually Wanted

I didn’t need some giant Git UI.

I wanted a very small workflow improvement:

  • show me the files that matter
  • let me inspect them one at a time
  • let me stage only the ones I want
  • make the whole thing fast enough that I’ll actually use it

That was it.

So I wrote a tiny add function for my dotfiles.

It uses git status --porcelain to collect files that need attention, pipes them into fzf, gives me a preview for whatever file is currently selected, and then stages only the files I choose.

That sounds simple because it is simple. But it ended up changing my workflow more than I expected.

The Function

Here’s the function from my dotfiles:

function add() {
  # Get files that need to be staged during rebase
  # This includes: unmerged paths (conflicts), modified files, new files, etc.
  local files
  files=$(git status --porcelain | grep -E '^(UU|AA|DD|AU|UA|DU|UD)' | sed 's/^...//')

  # If no merge conflicts, check for regular unstaged files
  # Include MM (staged but edited again), M (modified), D (deleted), and ?? (untracked)
  if [[ -z "$files" ]]; then
    files=$(git status --porcelain | grep -E '^MM |^ M |^ D |^D |^\?\?' | sed 's/^...//')
  fi

  # Sort and remove duplicates
  files=$(printf '%s\n' "$files" | sort -u)

  if [[ -z "$files" ]]; then
    echo "No files need to be staged."
    return 0
  fi

  # Use fzf to select files with appropriate preview
  local selected_files
  selected_files=$(printf '%s\n' "$files" | fzf -m \
    --header='Select files to stage (Tab: multi-select, Enter: confirm)' \
    --preview 'cd $(git rev-parse --show-toplevel) && {
      file={};
      status=$(git status --porcelain "$file" 2>/dev/null | cut -c1-2);

      if echo "$status" | grep -q "??"; then
        # New file: show contents in green
        while IFS= read -r line || [ -n "$line" ]; do
          printf "\x1b[32m%s\x1b[0m\n" "$line";
        done < "$file" 2>/dev/null

      elif echo "$status" | grep -qE " D|D "; then
        # Deleted file: show contents in red from HEAD
        git show HEAD:"$file" 2>/dev/null | while IFS= read -r line || [ -n "$line" ]; do
          printf "\x1b[31m%s\x1b[0m\n" "$line";
        done

      else
        # Modified file: show diff
        git diff --color=always "$file" 2>/dev/null ||
        git diff --cached --color=always "$file" 2>/dev/null ||
        echo "Binary file or no changes"
      fi
    }' \
    --preview-window=right:60%:wrap)

  if [[ -n "$selected_files" ]]; then
    # Convert newlines to array and add each file
    while IFS= read -r file; do
      (
        cd $(git rev-parse --show-toplevel) &&
        [[ -n "$file" ]] &&
        git add "$file" &&
        echo "Staged: $file"
      )
    done <<< "$selected_files"
  fi
}
Enter fullscreen mode Exit fullscreen mode

It is just a focused wrapper around one very common decision: which files should go into this commit?

Why This Feels Better Than git status + git add

Instead of treating git add as the mechanical thing I do before git commit, I use it as a first review of the changes themselves.

screenshot of add command in action

I can move through the list file by file and immediately see:

  • yes, this belongs
  • no, that was an experiment
  • that markdown file is just tooling noise
  • this file is from a different line of work and should stay out
  • this one needs a second look before I stage it

That sounds minor, but it changes the feel of the whole workflow.

A normal git status gives you a list.

This enables intention.

A Few Details That Make It Great

What makes this more useful than a generic fzf wrapper is that it is opinionated in the right places.

If I’m in the middle of a rebase, it surfaces conflicted files first. It also catches easy-to-miss states like files that were staged and then edited again. And the preview adapts to the file: file contents for new files, file contents from HEAD for deleted files, diff for modified files.

That means I’m not staging based on filenames and vague recollection. I’m staging based on the actual code changes.

Why AI Makes This Matter More

I think this kind of workflow matters more now than it did a few years ago.

AI coding tools are genuinely useful, but they also make it much easier to generate clutter. Scratch markdown files. notes-to-self. temporary planning documents. throwaway code paths. files that were useful during exploration but were never meant to become part of the final change.

That is not really a criticism. It is just a side effect of a more exploratory workflow.

The more your tools help you branch out while thinking, the more you need a clean way to narrow back down before committing.

That is where selective staging becomes more important.

Not because the repo is huge.

Not because Git got harder.

Just because the path from “working” to “ready to commit” now tends to produce more debris.

The Branch Mistake Case

One of the most common real-world cases for this is embarrassingly simple:

I start changing files before I switch branches.

Now I have some work that belongs to the new ticket and some work that definitely does not.

Could I stash things? Sure.

Could I do some more elaborate cleanup? Of course.

But sometimes the fastest route is just being able to stage exactly the files that belong to the commit I’m making right now.

It gets me out of a very normal mess efficiently.

This Made Me Commit More Often

The most important effect of this function is not that it helps me avoid bad commits.

It is that it lowers the cost of good commits.

When I know I can quickly inspect what changed, skip the junk, and stage only the relevant files, I feel much less resistance to committing early.

“Commit early and often” is one of those bits of advice that is obviously correct and weirdly hard to follow in practice.

The reason it is hard is not philosophical. It is usually just friction.

If every commit feels like I have to manually sort through a pile of ambiguous changes, I am more likely to wait.

If I wait, the pile gets bigger.

If the pile gets bigger, the commit gets messier.

If the commit gets messier, I wait even longer.

This function helps break that cycle.

When committing becomes cheap again, git history gets cleaner almost automatically.

Small Shell Improvements Compound

I like this kind of shell customization because it solves a very specific annoyance without introducing much complexity.

It's a tiny tool that makes one repeated decision easier.

That is the sweet spot I keep coming back to in my dotfiles.

The reason I like little workflow tools like this is not that they are clever. It is that they can quietly change your defaults.

Most of the time, the gap between a sloppy workflow and a clean one is not knowledge. It is friction.

If the better path asks for more effort, the shortcut usually wins.

If you can remove enough friction that the better path becomes the simpler one, your habits improve almost by accident.

That is what this function did for me. It made a careful commit easier to make than a careless one.

And that is usually when the right choice starts winning without a fight.

Top comments (0)