Skip to content

[DIT-13470] collect git file rename data - #158

Merged
jholiga merged 5 commits into
masterfrom
joey/dit-13470-rename-pass
Sep 2, 2026
Merged

[DIT-13470] collect git file rename data#158
jholiga merged 5 commits into
masterfrom
joey/dit-13470-rename-pass

Conversation

@jholiga

@jholiga jholiga commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Overview

CLI half of https://linear.app/dittowords/issue/DIT-13470/rename-pass. Before a scan starts, the CLI asks the server which commit the last scan read. It diffs that commit against HEAD with git diff -M and records what git says was moved or renamed.

Something to note is that git's similarity index decides what counts as a rename, not us. A heavily rewritten file is not a rename.

Every failure path returns no renames but never blocks the scan. That covers no git, no earlier scan, a shallow clone, an unreachable server, etc. A move of more than 1000 files is capped..

Context

Merge the ditto-app PR first.

Screenshots

Test Plan

  • Ensure a ditto scan runs successfully. We can't test a re-scan until reconciliation is wired in.

@jholiga
jholiga requested a review from laurakoye September 1, 2026 19:33
@jholiga
jholiga marked this pull request as ready for review September 1, 2026 19:33
Comment thread lib/src/http/scan.ts
});
return ZGetLastScanShaResponse.parse(response.data).lastScanSha ?? null;
} catch {
return null;

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.

Should we be logging here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

going to skip the logging here, since this is a valid path for initial scans where no existing scan exists yet.

Comment thread lib/src/commands/scan.ts Outdated
* keeps its links instead of reading as a delete plus a create. Empty when there is
* no git context, no earlier scan, or no way to reach the earlier commit.
*/
async function readRenamesSinceLastScan(gitContext: GitContext | null) {

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.

type the return

Comment thread lib/src/scan/git.ts
Comment on lines +140 to +160
const out = await git(
["diff", "-M", "--name-status", "-z", previousSha, "HEAD"],
repoRoot
);
if (!out) return [];

// `-z` gives NUL-separated fields: a status, then one path, or two for a rename or a copy.
const fields = out.split("\0");
const renames: GitRename[] = [];
for (let i = 0; i < fields.length && fields[i]; ) {
const status = fields[i][0];
if (status === "R" || status === "C") {
const [from, to] = [fields[i + 1], fields[i + 2]];
if (status === "R" && from && to) renames.push({ from, to });
i += 3;
} else {
i += 2;
}
}
return renames;
}

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.

I believe this can be simplified. Adding --diff-filter=R makes git send back only the renames. Every record is then the same shape, so the parser becomes a step by 3 loop.

Worth it mainly for the output size.

Suggested change
const out = await git(
["diff", "-M", "--name-status", "-z", previousSha, "HEAD"],
repoRoot
);
if (!out) return [];
// `-z` gives NUL-separated fields: a status, then one path, or two for a rename or a copy.
const fields = out.split("\0");
const renames: GitRename[] = [];
for (let i = 0; i < fields.length && fields[i]; ) {
const status = fields[i][0];
if (status === "R" || status === "C") {
const [from, to] = [fields[i + 1], fields[i + 2]];
if (status === "R" && from && to) renames.push({ from, to });
i += 3;
} else {
i += 2;
}
}
return renames;
}
const out = await git(
["diff", "-M","--diff-filter=R","--name-status","-z",previousSha,"HEAD"],
repoRoot
);
if (!out) return [];
const fields = out.split("\0").filter(Boolean);
const renames: GitRename[] = [];
for (let i = 0; i + 2 < fields.length; i += 3) {
renames.push({ from: fields[i + 1], to: fields[i + 2] });
}
return renames;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

nice catch!

@jholiga
jholiga merged commit 525db81 into master Sep 2, 2026
1 check 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.

2 participants