Merge task: Improve changes view into main
This commit is contained in:
@@ -941,10 +941,15 @@ impl TaskAttempt {
|
|||||||
// second parent is the branch that was merged
|
// second parent is the branch that was merged
|
||||||
let parents: Vec<_> = merge_commit.parents().collect();
|
let parents: Vec<_> = merge_commit.parents().collect();
|
||||||
|
|
||||||
|
// Create diff options with more context
|
||||||
|
let mut diff_opts = git2::DiffOptions::new();
|
||||||
|
diff_opts.context_lines(10); // Include 10 lines of context around changes
|
||||||
|
diff_opts.interhunk_lines(0); // Don't merge hunks
|
||||||
|
|
||||||
let diff = if parents.len() >= 2 {
|
let diff = if parents.len() >= 2 {
|
||||||
let base_tree = parents[0].tree()?; // Main branch before merge
|
let base_tree = parents[0].tree()?; // Main branch before merge
|
||||||
let merged_tree = parents[1].tree()?; // The branch that was merged
|
let merged_tree = parents[1].tree()?; // The branch that was merged
|
||||||
main_repo.diff_tree_to_tree(Some(&base_tree), Some(&merged_tree), None)?
|
main_repo.diff_tree_to_tree(Some(&base_tree), Some(&merged_tree), Some(&mut diff_opts))?
|
||||||
} else {
|
} else {
|
||||||
// Fast-forward merge or single parent - compare merge commit with its parent
|
// Fast-forward merge or single parent - compare merge commit with its parent
|
||||||
let base_tree = if !parents.is_empty() {
|
let base_tree = if !parents.is_empty() {
|
||||||
@@ -954,7 +959,7 @@ impl TaskAttempt {
|
|||||||
main_repo.find_tree(git2::Oid::zero())?
|
main_repo.find_tree(git2::Oid::zero())?
|
||||||
};
|
};
|
||||||
let merged_tree = merge_commit.tree()?;
|
let merged_tree = merge_commit.tree()?;
|
||||||
main_repo.diff_tree_to_tree(Some(&base_tree), Some(&merged_tree), None)?
|
main_repo.diff_tree_to_tree(Some(&base_tree), Some(&merged_tree), Some(&mut diff_opts))?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Process each diff delta (file change)
|
// Process each diff delta (file change)
|
||||||
@@ -1028,9 +1033,13 @@ impl TaskAttempt {
|
|||||||
let current_commit = worktree_repo.find_commit(worktree_head_oid)?;
|
let current_commit = worktree_repo.find_commit(worktree_head_oid)?;
|
||||||
let current_tree = current_commit.tree()?;
|
let current_tree = current_commit.tree()?;
|
||||||
|
|
||||||
// Create a diff between the base tree and current tree
|
// Create a diff between the base tree and current tree with more context
|
||||||
|
let mut diff_opts = git2::DiffOptions::new();
|
||||||
|
diff_opts.context_lines(10); // Include 10 lines of context around changes
|
||||||
|
diff_opts.interhunk_lines(0); // Don't merge hunks
|
||||||
|
|
||||||
let diff =
|
let diff =
|
||||||
worktree_repo.diff_tree_to_tree(Some(&base_tree), Some(¤t_tree), None)?;
|
worktree_repo.diff_tree_to_tree(Some(&base_tree), Some(¤t_tree), Some(&mut diff_opts))?;
|
||||||
|
|
||||||
// Process each diff delta (file change)
|
// Process each diff delta (file change)
|
||||||
diff.foreach(
|
diff.foreach(
|
||||||
@@ -1114,14 +1123,18 @@ impl TaskAttempt {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate patch using Git's diff algorithm
|
// Generate patch using Git's diff algorithm with context
|
||||||
|
let mut diff_opts = git2::DiffOptions::new();
|
||||||
|
diff_opts.context_lines(10); // Include 10 lines of context around changes
|
||||||
|
diff_opts.interhunk_lines(0); // Don't merge hunks
|
||||||
|
|
||||||
let patch = match (old_blob.as_ref(), new_blob.as_ref()) {
|
let patch = match (old_blob.as_ref(), new_blob.as_ref()) {
|
||||||
(Some(old_b), Some(new_b)) => git2::Patch::from_blobs(
|
(Some(old_b), Some(new_b)) => git2::Patch::from_blobs(
|
||||||
old_b,
|
old_b,
|
||||||
Some(Path::new(file_path)),
|
Some(Path::new(file_path)),
|
||||||
new_b,
|
new_b,
|
||||||
Some(Path::new(file_path)),
|
Some(Path::new(file_path)),
|
||||||
None,
|
Some(&mut diff_opts),
|
||||||
)?,
|
)?,
|
||||||
(None, Some(new_b)) => {
|
(None, Some(new_b)) => {
|
||||||
// File was added - diff from empty buffer to new blob content
|
// File was added - diff from empty buffer to new blob content
|
||||||
@@ -1130,7 +1143,7 @@ impl TaskAttempt {
|
|||||||
Some(Path::new(file_path)),
|
Some(Path::new(file_path)),
|
||||||
new_b.content(), // new blob content as buffer
|
new_b.content(), // new blob content as buffer
|
||||||
Some(Path::new(file_path)),
|
Some(Path::new(file_path)),
|
||||||
None,
|
Some(&mut diff_opts),
|
||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
(Some(old_b), None) => {
|
(Some(old_b), None) => {
|
||||||
@@ -1140,7 +1153,7 @@ impl TaskAttempt {
|
|||||||
Some(Path::new(file_path)),
|
Some(Path::new(file_path)),
|
||||||
&[],
|
&[],
|
||||||
Some(Path::new(file_path)),
|
Some(Path::new(file_path)),
|
||||||
None,
|
Some(&mut diff_opts),
|
||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
(None, None) => {
|
(None, None) => {
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import {
|
|||||||
RefreshCw,
|
RefreshCw,
|
||||||
GitBranch,
|
GitBranch,
|
||||||
Trash2,
|
Trash2,
|
||||||
|
Eye,
|
||||||
|
EyeOff,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { makeRequest } from "@/lib/api";
|
import { makeRequest } from "@/lib/api";
|
||||||
import type {
|
import type {
|
||||||
@@ -53,6 +55,7 @@ export function TaskAttemptComparePage() {
|
|||||||
const [expandedSections, setExpandedSections] = useState<Set<string>>(
|
const [expandedSections, setExpandedSections] = useState<Set<string>>(
|
||||||
new Set()
|
new Set()
|
||||||
);
|
);
|
||||||
|
const [showAllUnchanged, setShowAllUnchanged] = useState(false);
|
||||||
const [deletingFiles, setDeletingFiles] = useState<Set<string>>(new Set());
|
const [deletingFiles, setDeletingFiles] = useState<Set<string>>(new Set());
|
||||||
const [fileToDelete, setFileToDelete] = useState<string | null>(null);
|
const [fileToDelete, setFileToDelete] = useState<string | null>(null);
|
||||||
|
|
||||||
@@ -184,13 +187,13 @@ export function TaskAttemptComparePage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getChunkClassName = (chunkType: DiffChunkType) => {
|
const getChunkClassName = (chunkType: DiffChunkType) => {
|
||||||
const baseClass = "font-mono text-sm whitespace-pre px-3 py-1";
|
const baseClass = "font-mono text-sm whitespace-pre py-1 flex";
|
||||||
|
|
||||||
switch (chunkType) {
|
switch (chunkType) {
|
||||||
case "Insert":
|
case "Insert":
|
||||||
return `${baseClass} bg-green-50 text-green-800 border-l-2 border-green-400`;
|
return `${baseClass} bg-green-50 dark:bg-green-900/20 text-green-800 dark:text-green-200 border-l-2 border-green-400 dark:border-green-500`;
|
||||||
case "Delete":
|
case "Delete":
|
||||||
return `${baseClass} bg-red-50 text-red-800 border-l-2 border-red-400`;
|
return `${baseClass} bg-red-50 dark:bg-red-900/20 text-red-800 dark:text-red-200 border-l-2 border-red-400 dark:border-red-500`;
|
||||||
case "Equal":
|
case "Equal":
|
||||||
default:
|
default:
|
||||||
return `${baseClass} text-muted-foreground`;
|
return `${baseClass} text-muted-foreground`;
|
||||||
@@ -212,7 +215,8 @@ export function TaskAttemptComparePage() {
|
|||||||
interface ProcessedLine {
|
interface ProcessedLine {
|
||||||
content: string;
|
content: string;
|
||||||
chunkType: DiffChunkType;
|
chunkType: DiffChunkType;
|
||||||
lineNumber: number;
|
oldLineNumber?: number;
|
||||||
|
newLineNumber?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ProcessedSection {
|
interface ProcessedSection {
|
||||||
@@ -226,7 +230,8 @@ export function TaskAttemptComparePage() {
|
|||||||
const processFileChunks = (chunks: DiffChunk[], fileIndex: number) => {
|
const processFileChunks = (chunks: DiffChunk[], fileIndex: number) => {
|
||||||
const CONTEXT_LINES = 3;
|
const CONTEXT_LINES = 3;
|
||||||
const lines: ProcessedLine[] = [];
|
const lines: ProcessedLine[] = [];
|
||||||
let currentLineNumber = 1;
|
let oldLineNumber = 1;
|
||||||
|
let newLineNumber = 1;
|
||||||
|
|
||||||
// Convert chunks to lines with line numbers
|
// Convert chunks to lines with line numbers
|
||||||
chunks.forEach((chunk) => {
|
chunks.forEach((chunk) => {
|
||||||
@@ -234,11 +239,28 @@ export function TaskAttemptComparePage() {
|
|||||||
chunkLines.forEach((line, index) => {
|
chunkLines.forEach((line, index) => {
|
||||||
if (index < chunkLines.length - 1 || line !== "") {
|
if (index < chunkLines.length - 1 || line !== "") {
|
||||||
// Skip empty last line from split
|
// Skip empty last line from split
|
||||||
lines.push({
|
const processedLine: ProcessedLine = {
|
||||||
content: line,
|
content: line,
|
||||||
chunkType: chunk.chunk_type,
|
chunkType: chunk.chunk_type,
|
||||||
lineNumber: currentLineNumber++,
|
};
|
||||||
});
|
|
||||||
|
// Set line numbers based on chunk type
|
||||||
|
switch (chunk.chunk_type) {
|
||||||
|
case "Equal":
|
||||||
|
processedLine.oldLineNumber = oldLineNumber++;
|
||||||
|
processedLine.newLineNumber = newLineNumber++;
|
||||||
|
break;
|
||||||
|
case "Delete":
|
||||||
|
processedLine.oldLineNumber = oldLineNumber++;
|
||||||
|
// No new line number for deletions
|
||||||
|
break;
|
||||||
|
case "Insert":
|
||||||
|
processedLine.newLineNumber = newLineNumber++;
|
||||||
|
// No old line number for insertions
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(processedLine);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -267,9 +289,10 @@ export function TaskAttemptComparePage() {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
contextLength <= CONTEXT_LINES * 2 ||
|
contextLength <= CONTEXT_LINES * 2 ||
|
||||||
(!hasPrevChange && !hasNextChange)
|
(!hasPrevChange && !hasNextChange) ||
|
||||||
|
showAllUnchanged
|
||||||
) {
|
) {
|
||||||
// Show all context if it's short or if there are no changes around it
|
// Show all context if it's short, no changes around it, or global toggle is on
|
||||||
sections.push({
|
sections.push({
|
||||||
type: "context",
|
type: "context",
|
||||||
lines: lines.slice(i, nextChangeIndex),
|
lines: lines.slice(i, nextChangeIndex),
|
||||||
@@ -292,7 +315,7 @@ export function TaskAttemptComparePage() {
|
|||||||
|
|
||||||
if (expandEnd > expandStart) {
|
if (expandEnd > expandStart) {
|
||||||
const expandKey = `${fileIndex}-${expandStart}-${expandEnd}`;
|
const expandKey = `${fileIndex}-${expandStart}-${expandEnd}`;
|
||||||
const isExpanded = expandedSections.has(expandKey);
|
const isExpanded = expandedSections.has(expandKey) || showAllUnchanged;
|
||||||
|
|
||||||
if (isExpanded) {
|
if (isExpanded) {
|
||||||
sections.push({
|
sections.push({
|
||||||
@@ -514,13 +537,35 @@ export function TaskAttemptComparePage() {
|
|||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-lg">
|
<div className="flex items-center justify-between">
|
||||||
Diff: Base Commit vs. Current Worktree
|
<div>
|
||||||
</CardTitle>
|
<CardTitle className="text-lg">
|
||||||
<p className="text-sm text-muted-foreground">
|
Diff: Base Commit vs. Current Worktree
|
||||||
Shows changes made in the task attempt worktree compared to the base
|
</CardTitle>
|
||||||
commit
|
<p className="text-sm text-muted-foreground">
|
||||||
</p>
|
Shows changes made in the task attempt worktree compared to the base
|
||||||
|
commit
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setShowAllUnchanged(!showAllUnchanged)}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
{showAllUnchanged ? (
|
||||||
|
<>
|
||||||
|
<EyeOff className="h-4 w-4" />
|
||||||
|
Hide Unchanged
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Eye className="h-4 w-4" />
|
||||||
|
Show All Unchanged
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{!diff || diff.files.length === 0 ? (
|
{!diff || diff.files.length === 0 ? (
|
||||||
@@ -562,43 +607,45 @@ export function TaskAttemptComparePage() {
|
|||||||
{processFileChunks(file.chunks, fileIndex).map(
|
{processFileChunks(file.chunks, fileIndex).map(
|
||||||
(section, sectionIndex) => {
|
(section, sectionIndex) => {
|
||||||
if (
|
if (
|
||||||
section.type === "context" &&
|
section.type === "context" &&
|
||||||
section.lines.length === 0 &&
|
section.lines.length === 0 &&
|
||||||
section.expandKey
|
section.expandKey &&
|
||||||
|
!showAllUnchanged
|
||||||
) {
|
) {
|
||||||
// Render expand button
|
// Render expand button (only when global toggle is off)
|
||||||
const lineCount =
|
const lineCount =
|
||||||
parseInt(section.expandKey.split("-")[2]) -
|
parseInt(section.expandKey.split("-")[2]) -
|
||||||
parseInt(section.expandKey.split("-")[1]);
|
parseInt(section.expandKey.split("-")[1]);
|
||||||
return (
|
return (
|
||||||
<div key={`expand-${section.expandKey}`}>
|
<div key={`expand-${section.expandKey}`}>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
toggleExpandSection(section.expandKey!)
|
toggleExpandSection(section.expandKey!)
|
||||||
}
|
|
||||||
className="w-full h-8 text-xs text-blue-600 hover:text-blue-800 hover:bg-blue-50 border-t border-b border-gray-200 rounded-none"
|
|
||||||
>
|
|
||||||
<ChevronDown className="h-3 w-3 mr-1" />
|
|
||||||
Show {lineCount} more lines
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
className="w-full h-8 text-xs text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 hover:bg-blue-50 dark:hover:bg-blue-950/50 border-t border-b border-gray-200 dark:border-gray-700 rounded-none"
|
||||||
|
>
|
||||||
|
<ChevronDown className="h-3 w-3 mr-1" />
|
||||||
|
Show {lineCount} more lines
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Render lines (context, change, or expanded)
|
// Render lines (context, change, or expanded)
|
||||||
return (
|
return (
|
||||||
<div key={`section-${sectionIndex}`}>
|
<div key={`section-${sectionIndex}`}>
|
||||||
{section.type === "expanded" &&
|
{section.type === "expanded" &&
|
||||||
section.expandKey && (
|
section.expandKey &&
|
||||||
|
!showAllUnchanged && (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
toggleExpandSection(section.expandKey!)
|
toggleExpandSection(section.expandKey!)
|
||||||
}
|
}
|
||||||
className="w-full h-8 text-xs text-blue-600 hover:text-blue-800 hover:bg-blue-50 border-t border-b border-gray-200 rounded-none"
|
className="w-full h-8 text-xs text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 hover:bg-blue-50 dark:hover:bg-blue-950/50 border-t border-b border-gray-200 dark:border-gray-700 rounded-none"
|
||||||
>
|
>
|
||||||
<ChevronUp className="h-3 w-3 mr-1" />
|
<ChevronUp className="h-3 w-3 mr-1" />
|
||||||
Hide expanded lines
|
Hide expanded lines
|
||||||
@@ -609,8 +656,20 @@ export function TaskAttemptComparePage() {
|
|||||||
key={`${sectionIndex}-${lineIndex}`}
|
key={`${sectionIndex}-${lineIndex}`}
|
||||||
className={getChunkClassName(line.chunkType)}
|
className={getChunkClassName(line.chunkType)}
|
||||||
>
|
>
|
||||||
{getChunkPrefix(line.chunkType)}
|
<div className="flex-shrink-0 w-16 px-2 text-xs text-gray-500 dark:text-gray-400 bg-gray-50 dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 select-none">
|
||||||
{line.content}
|
<span className="inline-block w-6 text-right">
|
||||||
|
{line.oldLineNumber || ""}
|
||||||
|
</span>
|
||||||
|
<span className="inline-block w-6 text-right ml-1">
|
||||||
|
{line.newLineNumber || ""}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 px-3">
|
||||||
|
<span className="inline-block w-4">
|
||||||
|
{getChunkPrefix(line.chunkType)}
|
||||||
|
</span>
|
||||||
|
<span>{line.content}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user