mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-12 19:55:19 +02:00
fix code diff view
This commit is contained in:
parent
52772dd8dd
commit
65f0d518eb
1 changed files with 39 additions and 7 deletions
|
|
@ -67,6 +67,17 @@ export async function repoInfo(cwd: string): Promise<GitRepoInfo> {
|
|||
return { isGitRepo: true, branch, hasCommits, dirtyCount };
|
||||
}
|
||||
|
||||
// git status/diff report paths relative to the REPO ROOT, which is not the
|
||||
// session cwd when the user opened a subdirectory of a repo as their project.
|
||||
// Disk reads must resolve against the root, not cwd.
|
||||
async function repoToplevel(cwd: string): Promise<string> {
|
||||
try {
|
||||
return (await git(cwd, ['rev-parse', '--show-toplevel'])).trim() || cwd;
|
||||
} catch {
|
||||
return cwd;
|
||||
}
|
||||
}
|
||||
|
||||
function stateFromPorcelain(xy: string): GitFileState {
|
||||
if (xy === '??') return 'untracked';
|
||||
if (xy.includes('R')) return 'renamed';
|
||||
|
|
@ -75,10 +86,14 @@ function stateFromPorcelain(xy: string): GitFileState {
|
|||
return 'modified';
|
||||
}
|
||||
|
||||
// Working-tree changes vs HEAD with insertion/deletion counts. Untracked files
|
||||
// get their line count from disk (capped) since numstat doesn't cover them.
|
||||
// Working-tree changes vs HEAD with insertion/deletion counts, scoped to the
|
||||
// session directory's subtree (`-- .`): a project opened inside a bigger repo
|
||||
// only shows its own changes. Result paths are repo-root-relative (git's
|
||||
// porcelain format). Untracked files get their line count from disk (capped)
|
||||
// since numstat doesn't cover them.
|
||||
export async function status(cwd: string): Promise<GitStatusFile[]> {
|
||||
const out = await git(cwd, ['status', '--porcelain=v1', '-z']);
|
||||
const root = await repoToplevel(cwd);
|
||||
const out = await git(cwd, ['status', '--porcelain=v1', '-z', '--', '.']);
|
||||
const entries: Array<{ path: string; state: GitFileState }> = [];
|
||||
// -z format: "XY path\0" and for renames "XY newPath\0oldPath\0"
|
||||
const parts = out.split('\0');
|
||||
|
|
@ -94,7 +109,7 @@ export async function status(cwd: string): Promise<GitStatusFile[]> {
|
|||
|
||||
const counts = new Map<string, { insertions: number | null; deletions: number | null }>();
|
||||
try {
|
||||
const numstat = await git(cwd, ['diff', 'HEAD', '--numstat', '-z']);
|
||||
const numstat = await git(cwd, ['diff', 'HEAD', '--numstat', '-z', '--', '.']);
|
||||
// -z numstat rows: "ins\tdel\tpath\0" (renames: "ins\tdel\0old\0new\0")
|
||||
const rows = numstat.split('\0');
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
|
|
@ -126,7 +141,7 @@ export async function status(cwd: string): Promise<GitStatusFile[]> {
|
|||
deletions = counted.deletions;
|
||||
} else if (entry.state === 'untracked') {
|
||||
try {
|
||||
const full = path.join(cwd, entry.path);
|
||||
const full = path.join(root, entry.path);
|
||||
const stat = await fs.stat(full);
|
||||
if (stat.isFile() && stat.size <= MAX_TEXT_BYTES) {
|
||||
const content = await fs.readFile(full, 'utf8');
|
||||
|
|
@ -154,16 +169,33 @@ export interface FileDiff {
|
|||
}
|
||||
|
||||
export async function fileDiff(cwd: string, relPath: string): Promise<FileDiff> {
|
||||
// Paths from `status` are repo-root-relative; paths clicked in the chat
|
||||
// timeline are cwd-relative. Resolve whichever interpretation points at a
|
||||
// real file (deleted files fall back to the root interpretation, which is
|
||||
// also what `git show` uses).
|
||||
const root = await repoToplevel(cwd);
|
||||
let gitPath = relPath;
|
||||
let full = path.join(root, relPath);
|
||||
const existsAt = async (p: string) => fs.stat(p).then((s) => s.isFile()).catch(() => false);
|
||||
if (!await existsAt(full)) {
|
||||
const cwdFull = path.join(cwd, relPath);
|
||||
if (await existsAt(cwdFull)) {
|
||||
full = cwdFull;
|
||||
// Realpath both sides — git reports the real toplevel, while the
|
||||
// session cwd may reach it through a symlink (e.g. /tmp on macOS).
|
||||
const realFull = await fs.realpath(cwdFull).catch(() => cwdFull);
|
||||
gitPath = path.relative(root, realFull).split(path.sep).join('/');
|
||||
}
|
||||
}
|
||||
let oldText = '';
|
||||
try {
|
||||
oldText = await git(cwd, ['show', `HEAD:${relPath}`]);
|
||||
oldText = await git(cwd, ['show', `HEAD:${gitPath}`]);
|
||||
} catch {
|
||||
// untracked / newly added / no commits — diff against empty
|
||||
oldText = '';
|
||||
}
|
||||
let newText = '';
|
||||
try {
|
||||
const full = path.join(cwd, relPath);
|
||||
const stat = await fs.stat(full);
|
||||
if (stat.size > MAX_TEXT_BYTES) {
|
||||
return { oldText: '', newText: '', isBinary: false, tooLarge: true };
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue