Update diff viewer
This commit is contained in:
parent
e04c67133b
commit
35983681e4
47
renderer.ts
47
renderer.ts
|
|
@ -1579,14 +1579,39 @@ function renderDiffFileList() {
|
|||
const item = document.createElement("div");
|
||||
item.className = `diff-file-item ${index === selectedFileIndex ? 'active' : ''}`;
|
||||
|
||||
item.innerHTML = `
|
||||
<div class="diff-file-path">${file.path}</div>
|
||||
<div class="diff-file-stats">
|
||||
<span class="diff-file-status ${file.status}">${file.status}</span>
|
||||
${file.additions > 0 ? `<span class="diff-file-additions">+${file.additions}</span>` : ''}
|
||||
${file.deletions > 0 ? `<span class="diff-file-deletions">-${file.deletions}</span>` : ''}
|
||||
</div>
|
||||
`;
|
||||
// Create file path div and set text content securely
|
||||
const pathDiv = document.createElement("div");
|
||||
pathDiv.className = "diff-file-path";
|
||||
pathDiv.textContent = file.path;
|
||||
|
||||
// Create stats div
|
||||
const statsDiv = document.createElement("div");
|
||||
statsDiv.className = "diff-file-stats";
|
||||
|
||||
// Create status badge
|
||||
const statusSpan = document.createElement("span");
|
||||
statusSpan.className = `diff-file-status ${file.status}`;
|
||||
statusSpan.textContent = file.status;
|
||||
statsDiv.appendChild(statusSpan);
|
||||
|
||||
// Add additions count if present
|
||||
if (file.additions > 0) {
|
||||
const additionsSpan = document.createElement("span");
|
||||
additionsSpan.className = "diff-file-additions";
|
||||
additionsSpan.textContent = `+${file.additions}`;
|
||||
statsDiv.appendChild(additionsSpan);
|
||||
}
|
||||
|
||||
// Add deletions count if present
|
||||
if (file.deletions > 0) {
|
||||
const deletionsSpan = document.createElement("span");
|
||||
deletionsSpan.className = "diff-file-deletions";
|
||||
deletionsSpan.textContent = `-${file.deletions}`;
|
||||
statsDiv.appendChild(deletionsSpan);
|
||||
}
|
||||
|
||||
item.appendChild(pathDiv);
|
||||
item.appendChild(statsDiv);
|
||||
|
||||
item.addEventListener("click", () => {
|
||||
selectedFileIndex = index;
|
||||
|
|
@ -1610,6 +1635,12 @@ async function renderSelectedDiff() {
|
|||
const file = currentDiffFiles[selectedFileIndex];
|
||||
|
||||
try {
|
||||
// Check if file is binary
|
||||
if (file.binary) {
|
||||
diffDisplay.innerHTML = '<div class="diff-empty">Binary file changed</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pierreLib) {
|
||||
diffDisplay.innerHTML = '<div class="diff-empty">Loading diff viewer...</div>';
|
||||
return;
|
||||
|
|
|
|||
190
server.ts
190
server.ts
|
|
@ -234,6 +234,74 @@ async function removeGitBranch(projectDir: string, branchName: string) {
|
|||
}
|
||||
}
|
||||
|
||||
function parsePatchToFileDiffs(patch: string): FileDiff[] {
|
||||
const fileDiffs: FileDiff[] = [];
|
||||
|
||||
// Split patch into individual file chunks
|
||||
// Each chunk starts with "diff --git"
|
||||
const fileChunks = patch.split(/(?=diff --git )/g).filter(chunk => chunk.trim());
|
||||
|
||||
for (const chunk of fileChunks) {
|
||||
const lines = chunk.split('\n');
|
||||
|
||||
// Extract file paths from "diff --git a/... b/..."
|
||||
const diffGitMatch = lines[0].match(/^diff --git a\/(.+?) b\/(.+?)$/);
|
||||
if (!diffGitMatch) continue;
|
||||
|
||||
let oldPath = diffGitMatch[1];
|
||||
let newPath = diffGitMatch[2];
|
||||
|
||||
// Determine status and paths
|
||||
let status: FileDiff['status'] = 'modified';
|
||||
let finalPath = newPath;
|
||||
let finalOldPath: string | undefined;
|
||||
let isBinary = false;
|
||||
|
||||
// Check for file mode changes and status indicators
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('new file mode')) {
|
||||
status = 'added';
|
||||
finalOldPath = undefined;
|
||||
} else if (line.startsWith('deleted file mode')) {
|
||||
status = 'deleted';
|
||||
finalPath = oldPath;
|
||||
finalOldPath = undefined;
|
||||
} else if (line.startsWith('rename from')) {
|
||||
status = 'renamed';
|
||||
finalOldPath = line.substring('rename from '.length).trim();
|
||||
} else if (line.startsWith('rename to')) {
|
||||
finalPath = line.substring('rename to '.length).trim();
|
||||
} else if (line.startsWith('Binary files')) {
|
||||
isBinary = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Count additions and deletions
|
||||
let additions = 0;
|
||||
let deletions = 0;
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('+') && !line.startsWith('+++')) {
|
||||
additions++;
|
||||
} else if (line.startsWith('-') && !line.startsWith('---')) {
|
||||
deletions++;
|
||||
}
|
||||
}
|
||||
|
||||
fileDiffs.push({
|
||||
path: finalPath,
|
||||
oldPath: finalOldPath,
|
||||
status,
|
||||
diff: chunk,
|
||||
additions,
|
||||
deletions,
|
||||
binary: isBinary,
|
||||
});
|
||||
}
|
||||
|
||||
return fileDiffs;
|
||||
}
|
||||
|
||||
// Socket.IO handlers
|
||||
io.on("connection", (socket) => {
|
||||
console.log("Client connected:", socket.id);
|
||||
|
|
@ -508,97 +576,53 @@ io.on("connection", (socket) => {
|
|||
const git = simpleGit(session.worktreePath);
|
||||
const parentBranch = session.config.parentBranch;
|
||||
|
||||
// Get diff between parent branch and working directory (includes all changes: staged, unstaged, committed)
|
||||
const diffOutput = await git.diff([parentBranch, '--numstat']);
|
||||
const fullDiffOutput = await git.diff([parentBranch]);
|
||||
console.log(`[${sessionId}] Generating unified diff for session, parent branch: ${parentBranch}`);
|
||||
console.log(`[${sessionId}] Worktree path: ${session.worktreePath}`);
|
||||
|
||||
// Also get untracked files (new files not yet added to git)
|
||||
// Get unified patch for tracked changes (includes staged, unstaged, and committed)
|
||||
const trackedPatch = await git.raw([
|
||||
'diff',
|
||||
parentBranch,
|
||||
'--patch',
|
||||
'--find-renames',
|
||||
'--binary'
|
||||
]);
|
||||
|
||||
// Get untracked files
|
||||
const statusSummary = await git.status();
|
||||
|
||||
console.log(`[${sessionId}] Diff request for session, parent branch: ${parentBranch}`);
|
||||
console.log(`[${sessionId}] Worktree path: ${session.worktreePath}`);
|
||||
console.log(`[${sessionId}] Diff output length: ${diffOutput.length} chars`);
|
||||
console.log(`[${sessionId}] Untracked files: ${statusSummary.not_added.length}`);
|
||||
|
||||
// Parse diff output into FileDiff array
|
||||
const fileDiffs: FileDiff[] = [];
|
||||
const lines = diffOutput.split('\n').filter(l => l.trim());
|
||||
|
||||
for (const line of lines) {
|
||||
// Parse numstat line: "additions deletions filename"
|
||||
const match = line.match(/^(\d+|-)\s+(\d+|-)\s+(.+)$/);
|
||||
if (match) {
|
||||
const [, addStr, delStr, filepath] = match;
|
||||
const additions = addStr === '-' ? 0 : parseInt(addStr, 10);
|
||||
const deletions = delStr === '-' ? 0 : parseInt(delStr, 10);
|
||||
|
||||
// Determine file status
|
||||
let status: FileDiff['status'] = 'modified';
|
||||
let path = filepath;
|
||||
let oldPath: string | undefined;
|
||||
|
||||
// Check if file is renamed (contains " => ")
|
||||
if (filepath.includes(' => ')) {
|
||||
status = 'renamed';
|
||||
const parts = filepath.split(' => ');
|
||||
oldPath = parts[0].replace(/^\{|\}$/g, '');
|
||||
path = parts[1].replace(/^\{|\}$/g, '');
|
||||
} else if (additions > 0 && deletions === 0) {
|
||||
status = 'added';
|
||||
} else if (additions === 0 && deletions > 0) {
|
||||
status = 'deleted';
|
||||
}
|
||||
|
||||
// Extract individual file diff from full output
|
||||
const fileDiffMatch = fullDiffOutput.match(
|
||||
new RegExp(`diff --git a/${path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')} b/${path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?(?=diff --git|$)`)
|
||||
);
|
||||
const diff = fileDiffMatch ? fileDiffMatch[0] : '';
|
||||
|
||||
fileDiffs.push({
|
||||
path,
|
||||
oldPath,
|
||||
status,
|
||||
diff,
|
||||
additions,
|
||||
deletions,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Add untracked files as "added" with full file content as diff
|
||||
// Generate patches for untracked files using --no-index
|
||||
const untrackedPatches: string[] = [];
|
||||
for (const untrackedFile of statusSummary.not_added) {
|
||||
try {
|
||||
// Read the file content
|
||||
const fileContent = await fs.promises.readFile(
|
||||
path.join(session.worktreePath, untrackedFile),
|
||||
'utf-8'
|
||||
);
|
||||
const lines = fileContent.split('\n');
|
||||
|
||||
// Create a diff in unified format for new file
|
||||
const diffLines = [
|
||||
`diff --git a/${untrackedFile} b/${untrackedFile}`,
|
||||
`new file mode 100644`,
|
||||
`--- /dev/null`,
|
||||
`+++ b/${untrackedFile}`,
|
||||
`@@ -0,0 +1,${lines.length} @@`,
|
||||
...lines.map(line => `+${line}`)
|
||||
];
|
||||
|
||||
fileDiffs.push({
|
||||
path: untrackedFile,
|
||||
status: 'added',
|
||||
diff: diffLines.join('\n'),
|
||||
additions: lines.length,
|
||||
deletions: 0,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`[${sessionId}] Error reading untracked file ${untrackedFile}:`, error);
|
||||
const untrackedPatch = await git.raw([
|
||||
'diff',
|
||||
'--no-index',
|
||||
'--binary',
|
||||
'/dev/null',
|
||||
untrackedFile
|
||||
]);
|
||||
untrackedPatches.push(untrackedPatch);
|
||||
} catch (error: any) {
|
||||
// git diff --no-index exits with code 1 when there are differences, which is expected
|
||||
if (error.message && typeof error.message === 'string') {
|
||||
untrackedPatches.push(error.message);
|
||||
} else {
|
||||
console.error(`[${sessionId}] Error generating patch for untracked file ${untrackedFile}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[${sessionId}] Parsed ${fileDiffs.length} file diffs (${statusSummary.not_added.length} untracked)`);
|
||||
// Combine all patches
|
||||
const combinedPatch = [trackedPatch, ...untrackedPatches].filter(p => p).join('\n');
|
||||
|
||||
console.log(`[${sessionId}] Combined patch length: ${combinedPatch.length} chars`);
|
||||
console.log(`[${sessionId}] Untracked files: ${statusSummary.not_added.length}`);
|
||||
|
||||
// Parse the combined patch into FileDiff array
|
||||
const fileDiffs: FileDiff[] = parsePatchToFileDiffs(combinedPatch);
|
||||
|
||||
console.log(`[${sessionId}] Parsed ${fileDiffs.length} file diffs`);
|
||||
socket.emit("session-diff-data", sessionId, fileDiffs);
|
||||
} catch (error) {
|
||||
console.error("Error getting session diff:", error);
|
||||
|
|
|
|||
Loading…
Reference in a new issue