Skip to content

fix(git): include untracked files in one-ref diffs#210

Merged
benvinegar merged 2 commits intomainfrom
fix/untracked-working-tree-diffs
Apr 19, 2026
Merged

fix(git): include untracked files in one-ref diffs#210
benvinegar merged 2 commits intomainfrom
fix/untracked-working-tree-diffs

Conversation

@benvinegar
Copy link
Copy Markdown
Member

Summary

  • include untracked files when hunk diff <ref> still compares the live working tree against a single ref
  • keep explicit revset diffs like a..b, a...b, and HEAD^! commit-to-commit only
  • add loader and watch regression coverage for both the one-ref and revset cases

Testing

  • bun run typecheck
  • bun test src/core/loaders.test.ts -t "includes untracked files when diff compares the working tree against one ref|excludes untracked files for explicit git ranges that do not include the working tree|excludes untracked files for revset diffs like HEAD^! that do not include the working tree"
  • bun test src/core/watch.test.ts

This PR description was generated by Pi using OpenAI o3

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 19, 2026

Greptile Summary

This PR teaches hunk diff <ref> to include untracked files when the range resolves to exactly one positive revision (i.e., the working tree is still one side of the diff), while keeping explicit revsets like a..b, a...b, and HEAD^! strictly commit-to-commit. The detection is done by running git rev-parse --revs-only <range> and counting positive vs. negative revisions, with comprehensive integration tests added for both the loader and watch paths.

Confidence Score: 5/5

Safe to merge; the logic is correct and well-tested across all relevant range forms.

All findings are P2. The single-ref vs. revset heuristic using rev-parse --revs-only is the right primitive for this distinction, error handling flows correctly through the existing translation layer, and the new tests pin both the loader and watch paths for the three important cases.

No files require special attention.

Important Files Changed

Filename Overview
src/core/git.ts Adds isWorkingTreeGitDiffInput (via git rev-parse --revs-only) and shouldIncludeUntrackedFiles to gate untracked-file injection; logic is correct for single-ref, double-dot, triple-dot and ^! forms. Minor: extra subprocess per watch tick when a range is set.
src/core/loaders.test.ts Adds three new integration tests covering single-ref (includes untracked), main..HEAD explicit range (excludes untracked), and HEAD^! revset (excludes untracked). Well-structured and consistent with the surrounding test suite.
src/core/watch.test.ts Adds two watch-signature tests: excludeUntracked=true suppresses sensitivity to untracked changes; single-ref diff (range="main") does detect them. Both cases are important regression anchors for the watch path.
CHANGELOG.md Changelog entry added documenting the untracked-file fix for one-ref diffs.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["hunk diff [range]"] --> B{input.staged?}
    B -- yes --> C["Skip untracked files"]
    B -- no --> D{input.range set?}
    D -- no --> E["Include untracked files\n(plain working-tree diff)"]
    D -- yes --> F["git rev-parse --revs-only range"]
    F --> G{positiveRevs == 1\nAND negativeRevs == 0?}
    G -- yes --> E
    G -- no --> C
    E --> H["git status --porcelain=v1 -z\n--untracked-files=all"]
    H --> I["Filter: isReviewableUntrackedPath\n(skip dirs, dir-symlinks)"]
    I --> J["git diff --no-index /dev/null file\n(synthetic patch per file)"]
    J --> K["Append to changeset files"]
    C --> L["Return tracked files only"]
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/core/git.ts
Line: 320-333

Comment:
**Extra `git rev-parse` subprocess on every untracked-file check**

`isWorkingTreeGitDiffInput` spawns a synchronous `git rev-parse --revs-only <range>` subprocess on every call to `listGitUntrackedFiles`. In watch mode `gitWorkingTreeWatchSignature` calls `listGitUntrackedFiles` on each poll tick, so this adds a full extra git process per watch cycle whenever a range is set. The result never changes within a session, so it could be computed once and memoized/passed in rather than re-run every time.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(git): include untracked files in one..." | Re-trigger Greptile

Comment thread src/core/git.ts Outdated
Comment on lines +320 to +333
const revs = runGitText({
input,
args: ["rev-parse", "--revs-only", input.range],
cwd,
gitExecutable,
})
.split("\n")
.map((line) => line.trim())
.filter(Boolean);

const positiveRevs = revs.filter((line) => !line.startsWith("^"));
const negativeRevs = revs.filter((line) => line.startsWith("^"));

return positiveRevs.length === 1 && negativeRevs.length === 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Extra git rev-parse subprocess on every untracked-file check

isWorkingTreeGitDiffInput spawns a synchronous git rev-parse --revs-only <range> subprocess on every call to listGitUntrackedFiles. In watch mode gitWorkingTreeWatchSignature calls listGitUntrackedFiles on each poll tick, so this adds a full extra git process per watch cycle whenever a range is set. The result never changes within a session, so it could be computed once and memoized/passed in rather than re-run every time.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/core/git.ts
Line: 320-333

Comment:
**Extra `git rev-parse` subprocess on every untracked-file check**

`isWorkingTreeGitDiffInput` spawns a synchronous `git rev-parse --revs-only <range>` subprocess on every call to `listGitUntrackedFiles`. In watch mode `gitWorkingTreeWatchSignature` calls `listGitUntrackedFiles` on each poll tick, so this adds a full extra git process per watch cycle whenever a range is set. The result never changes within a session, so it could be computed once and memoized/passed in rather than re-run every time.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I cached the range classification per repo/range so watch-mode polls no longer spawn an extra git rev-parse once the one-ref vs revset decision has been computed.

This comment was generated by Pi using OpenAI o3

@benvinegar benvinegar merged commit 078c08d into main Apr 19, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant