Add capture() to spawn, add rsync itemize parser

This commit is contained in:
2026-03-07 01:07:51 +00:00
parent 84801a7971
commit 30b90193d7
2 changed files with 82 additions and 7 deletions

56
lib/itemize.js Normal file
View File

@@ -0,0 +1,56 @@
/**
* Parse rsync --itemize-changes output into a structured change list.
*
* rsync itemize format: 11-character code + space + path
*
* Code structure: YXcstpoguax
* Y = update type: > (transfer), * (message/delete), c (local change), . (no update), h (hard link)
* X = file type: f (file), d (dir), L (symlink), D (device), S (special)
* remaining chars = what changed (size, time, perms, etc.) or '+++++++++' for new
*
* We care about:
* >f... = file transferred (new or modified)
* *deleting = file deleted
* cd... = directory (ignored for delta purposes)
*/
/**
* @typedef {{ status: 'added'|'modified'|'deleted', path: string }} Change
*/
/**
* Parse rsync --itemize-changes stdout into a list of file changes.
* @param {string} output
* @returns {Change[]}
*/
export function parseItemize(output) {
const changes = [];
for (const raw of output.split('\n')) {
const line = raw.trimEnd();
if (!line) continue;
// Deleted files: "*deleting path/to/file"
if (line.startsWith('*deleting ')) {
const path = line.slice('*deleting '.length).trimStart();
// Skip directory deletions (trailing slash)
if (!path.endsWith('/')) {
changes.push({ status: 'deleted', path });
}
continue;
}
// File transfers: ">f......... path" (new or modified)
if (line.length > 12 && line[0] === '>' && line[1] === 'f') {
const code = line.slice(0, 11);
const path = line.slice(12);
const isNew = code.slice(2) === '+++++++++';
changes.push({ status: isNew ? 'added' : 'modified', path });
continue;
}
// Everything else (dirs, symlinks, attribute-only changes) — ignore
}
return changes;
}