fix: Correct parsing of git output blocks and improve stash UI accessibility

This commit is contained in:
gsxdsm
2026-02-17 23:15:16 -08:00
parent dd4c738e91
commit 887e2ea76b
3 changed files with 54 additions and 29 deletions

View File

@@ -48,7 +48,16 @@ export function createCommitLogHandler() {
const commitBlocks = logOutput.split('---END---').filter((block) => block.trim()); const commitBlocks = logOutput.split('---END---').filter((block) => block.trim());
for (const block of commitBlocks) { for (const block of commitBlocks) {
const lines = block.split('\n'); const allLines = block.split('\n');
// Skip leading empty lines that result from the split.
// After splitting on ---END---, subsequent blocks start with a newline,
// which creates an empty first element that shifts all field indices
// (hash becomes empty, shortHash becomes hash, etc.).
let startIndex = 0;
while (startIndex < allLines.length && allLines[startIndex].trim() === '') {
startIndex++;
}
const lines = allLines.slice(startIndex);
if (lines.length >= 6) { if (lines.length >= 6) {
const hash = lines[0].trim(); const hash = lines[0].trim();

View File

@@ -69,10 +69,19 @@ export async function getBranchCommitLog(
// Parse the output into structured commit objects // Parse the output into structured commit objects
const commits: BranchCommit[] = []; const commits: BranchCommit[] = [];
const commitBlocks = logOutput.split('---END---\n').filter((block) => block.trim()); const commitBlocks = logOutput.split('---END---').filter((block) => block.trim());
for (const block of commitBlocks) { for (const block of commitBlocks) {
const lines = block.split('\n'); const allLines = block.split('\n');
// Skip leading empty lines that result from the split.
// After splitting on ---END---, subsequent blocks start with a newline,
// which creates an empty first element that shifts all field indices
// (hash becomes empty, shortHash becomes hash, etc.).
let startIndex = 0;
while (startIndex < allLines.length && allLines[startIndex].trim() === '') {
startIndex++;
}
const lines = allLines.slice(startIndex);
if (lines.length >= 6) { if (lines.length >= 6) {
const hash = lines[0].trim(); const hash = lines[0].trim();
@@ -80,13 +89,22 @@ export async function getBranchCommitLog(
let files: string[] = []; let files: string[] = [];
try { try {
const filesOutput = await execGitCommand( const filesOutput = await execGitCommand(
['diff-tree', '--no-commit-id', '--name-only', '-r', hash], // -m causes merge commits to be diffed against each parent,
// showing all files touched by the merge (without -m, diff-tree
// produces no output for merge commits because they have 2+ parents)
['diff-tree', '--no-commit-id', '--name-only', '-r', '-m', hash],
worktreePath worktreePath
); );
files = filesOutput // Deduplicate: -m can list the same file multiple times
.trim() // (once per parent diff for merge commits)
.split('\n') files = [
.filter((f) => f.trim()); ...new Set(
filesOutput
.trim()
.split('\n')
.filter((f) => f.trim())
),
];
} catch { } catch {
// Ignore errors getting file list // Ignore errors getting file list
} }

View File

@@ -83,6 +83,7 @@ function StashEntryItem({
}) { }) {
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);
const isBusy = isApplying || isDropping; const isBusy = isApplying || isDropping;
const hasFiles = stash.files && stash.files.length > 0;
// Clean up the stash message for display // Clean up the stash message for display
const displayMessage = const displayMessage =
@@ -97,30 +98,17 @@ function StashEntryItem({
> >
{/* Header */} {/* Header */}
<div className="flex items-start gap-3 p-3"> <div className="flex items-start gap-3 p-3">
{/* Expand toggle & stash icon */} {/* Stash icon (static) */}
<button <div className="flex items-center pt-0.5 text-muted-foreground">
onClick={() => setExpanded(!expanded)}
className="flex items-center gap-1 pt-0.5 text-muted-foreground hover:text-foreground transition-colors"
disabled={stash.files.length === 0}
>
{stash.files.length > 0 ? (
expanded ? (
<ChevronDown className="w-3.5 h-3.5" />
) : (
<ChevronRight className="w-3.5 h-3.5" />
)
) : (
<span className="w-3.5" />
)}
<Archive className="w-3.5 h-3.5" /> <Archive className="w-3.5 h-3.5" />
</button> </div>
{/* Content */} {/* Content */}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-start justify-between gap-2"> <div className="flex items-start justify-between gap-2">
<div className="min-w-0"> <div className="min-w-0">
<p className="text-sm font-medium leading-snug break-words">{displayMessage}</p> <p className="text-sm font-medium leading-snug break-words">{displayMessage}</p>
<div className="flex flex-wrap items-center gap-x-2 gap-y-1 mt-1 text-xs text-muted-foreground"> <div className="flex flex-wrap items-center gap-x-3 gap-y-1 mt-1.5 text-xs text-muted-foreground">
<span className="inline-flex items-center gap-1 font-mono bg-muted px-1.5 py-0.5 rounded text-[10px]"> <span className="inline-flex items-center gap-1 font-mono bg-muted px-1.5 py-0.5 rounded text-[10px]">
stash@{'{' + stash.index + '}'} stash@{'{' + stash.index + '}'}
</span> </span>
@@ -143,11 +131,21 @@ function StashEntryItem({
{formatRelativeDate(stash.date)} {formatRelativeDate(stash.date)}
</time> </time>
</span> </span>
{stash.files.length > 0 && ( {hasFiles && (
<span className="inline-flex items-center gap-1"> <button
onClick={() => setExpanded(!expanded)}
className="inline-flex items-center gap-1 hover:text-foreground transition-colors cursor-pointer"
aria-expanded={expanded}
aria-label={`${expanded ? 'Collapse' : 'Expand'} file list, ${stash.files.length} file${stash.files.length !== 1 ? 's' : ''}`}
>
{expanded ? (
<ChevronDown className="w-3 h-3" />
) : (
<ChevronRight className="w-3 h-3" />
)}
<FileText className="w-3 h-3" /> <FileText className="w-3 h-3" />
{stash.files.length} file{stash.files.length !== 1 ? 's' : ''} {stash.files.length} file{stash.files.length !== 1 ? 's' : ''}
</span> </button>
)} )}
</div> </div>
</div> </div>
@@ -191,7 +189,7 @@ function StashEntryItem({
</div> </div>
{/* Expanded file list */} {/* Expanded file list */}
{expanded && stash.files.length > 0 && ( {expanded && hasFiles && (
<div className="border-t px-3 py-2 bg-muted/30"> <div className="border-t px-3 py-2 bg-muted/30">
<div className="space-y-0.5"> <div className="space-y-0.5">
{stash.files.map((file) => ( {stash.files.map((file) => (