feat: version history colors and pruning

This commit is contained in:
2026-02-16 14:23:21 +01:00
parent b19e92f729
commit 1cd7d4f6ef
6 changed files with 379 additions and 17 deletions

View File

@@ -46,6 +46,9 @@
margin-top: 12px;
border-top: 1px solid var(--vscode-editorWidget-border);
padding-top: 8px;
--git-history-synced-color: var(--vscode-gitDecoration-addedResourceForeground, var(--vscode-charts-green, #89d185));
--git-history-local-color: var(--vscode-gitDecoration-untrackedResourceForeground, var(--vscode-charts-blue, #75beff));
--git-history-remote-color: var(--vscode-gitDecoration-deletedResourceForeground, var(--vscode-charts-yellow, #cca700));
}
.git-sidebar-empty-state {
@@ -111,6 +114,40 @@
padding: 0 12px 8px;
}
.git-sidebar-history-legend {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 0 12px 8px;
font-size: 10px;
color: var(--vscode-descriptionForeground);
}
.git-sidebar-history-legend-item {
display: inline-flex;
align-items: center;
gap: 4px;
}
.git-sidebar-history-legend-dot {
width: 8px;
height: 8px;
border-radius: 999px;
display: inline-block;
}
.git-sidebar-history-legend-dot--both {
background: var(--git-history-synced-color);
}
.git-sidebar-history-legend-dot--local-only {
background: var(--git-history-local-color);
}
.git-sidebar-history-legend-dot--remote-only {
background: var(--git-history-remote-color);
}
.git-sidebar-history-item {
width: 100%;
border: none;
@@ -126,6 +163,18 @@
background: var(--vscode-list-hoverBackground);
}
.git-sidebar-history-item--both {
border-left: 3px solid var(--git-history-synced-color);
}
.git-sidebar-history-item--local-only {
border-left: 3px solid var(--git-history-local-color);
}
.git-sidebar-history-item--remote-only {
border-left: 3px solid var(--git-history-remote-color);
}
.git-sidebar-history-subject {
font-size: 12px;
color: var(--vscode-sideBar-foreground);
@@ -141,6 +190,23 @@
color: var(--vscode-descriptionForeground);
}
.git-sidebar-history-status {
font-size: 10px;
font-weight: 600;
}
.git-sidebar-history-status--both {
color: var(--git-history-synced-color);
}
.git-sidebar-history-status--local-only {
color: var(--git-history-local-color);
}
.git-sidebar-history-status--remote-only {
color: var(--git-history-remote-color);
}
.git-sidebar-main {
min-height: 0;
}

View File

@@ -10,7 +10,7 @@ export const GitSidebar: React.FC = () => {
const [loading, setLoading] = useState(true);
const [initializing, setInitializing] = useState(false);
const [statusLoading, setStatusLoading] = useState(false);
const [actionLoading, setActionLoading] = useState<'fetch' | 'pull' | 'push' | 'commit' | null>(null);
const [actionLoading, setActionLoading] = useState<'fetch' | 'pull' | 'push' | 'prune-lfs' | 'commit' | null>(null);
const [error, setError] = useState<string | null>(null);
const [errorGuidance, setErrorGuidance] = useState<string[]>([]);
const [isRepo, setIsRepo] = useState(false);
@@ -28,7 +28,7 @@ export const GitSidebar: React.FC = () => {
const getDiffTabId = (filePath: string): string => `git-diff:${filePath}`;
const getCommitDiffTabId = (commitHash: string): string => `git-diff:commit:${commitHash}`;
const getActionProgressMessage = (action: 'fetch' | 'pull' | 'push' | 'commit'): string => {
const getActionProgressMessage = (action: 'fetch' | 'pull' | 'push' | 'prune-lfs' | 'commit'): string => {
if (action === 'push') {
return 'Pushing commits to remote... this can take a while for large uploads.';
}
@@ -38,9 +38,22 @@ export const GitSidebar: React.FC = () => {
if (action === 'pull') {
return 'Pulling latest changes...';
}
if (action === 'prune-lfs') {
return 'Pruning local Git LFS cache...';
}
return 'Creating commit...';
};
const getHistoryStatusLabel = (status: GitHistoryEntry['syncStatus']): string => {
if (status === 'local-only') {
return 'Local only';
}
if (status === 'remote-only') {
return 'Remote only';
}
return 'Synced';
};
const openDiffTab = useCallback(
(filePath: string, isTransient: boolean) => {
openTab({
@@ -179,7 +192,7 @@ export const GitSidebar: React.FC = () => {
}
};
const handleRepoAction = async (action: 'fetch' | 'pull' | 'push') => {
const handleRepoAction = async (action: 'fetch' | 'pull' | 'push' | 'prune-lfs') => {
if (actionLoading) {
return;
}
@@ -202,10 +215,15 @@ export const GitSidebar: React.FC = () => {
? await window.electronAPI.git.fetch(effectiveProjectPath)
: action === 'pull'
? await window.electronAPI.git.pull(effectiveProjectPath)
: await window.electronAPI.git.push(effectiveProjectPath);
: action === 'push'
? await window.electronAPI.git.push(effectiveProjectPath)
: await window.electronAPI.git.pruneLfs(effectiveProjectPath, {
dryRun: false,
verifyRemote: true,
});
if (!result.success) {
setError(result.error || `Failed to ${action}.`);
setErrorGuidance(result.guidance || []);
setErrorGuidance('guidance' in result ? result.guidance || [] : []);
return;
}
await loadRepoState();
@@ -317,6 +335,14 @@ export const GitSidebar: React.FC = () => {
>
{actionLoading === 'push' ? 'Pushing...' : 'Push'}
</button>
<button
type="button"
className="git-sidebar-button"
onClick={() => handleRepoAction('prune-lfs')}
disabled={actionLoading !== null}
>
{actionLoading === 'prune-lfs' ? 'Pruning...' : 'Prune LFS'}
</button>
</div>
{actionLoading && (
<div className="git-sidebar-empty-state git-sidebar-progress" role="status">
@@ -372,6 +398,29 @@ export const GitSidebar: React.FC = () => {
<div className="git-sidebar-section git-sidebar-history">
<div className="sidebar-section-title">Version History ({historyEntries.length})</div>
<div className="git-sidebar-history-legend" aria-label="Commit status legend">
<span className="git-sidebar-history-legend-item">
<span
className="git-sidebar-history-legend-dot git-sidebar-history-legend-dot--both"
data-testid="git-history-legend-both"
/>
Synced
</span>
<span className="git-sidebar-history-legend-item">
<span
className="git-sidebar-history-legend-dot git-sidebar-history-legend-dot--local-only"
data-testid="git-history-legend-local-only"
/>
Local only
</span>
<span className="git-sidebar-history-legend-item">
<span
className="git-sidebar-history-legend-dot git-sidebar-history-legend-dot--remote-only"
data-testid="git-history-legend-remote-only"
/>
Remote only
</span>
</div>
{historyLoading ? (
<div className="git-sidebar-empty-state">Loading history...</div>
) : historyEntries.length === 0 ? (
@@ -382,7 +431,7 @@ export const GitSidebar: React.FC = () => {
<button
key={entry.hash}
type="button"
className="git-sidebar-history-item"
className={`git-sidebar-history-item git-sidebar-history-item--${entry.syncStatus ?? 'both'}`}
onClick={() => openCommitDiffTab(entry.hash, true)}
onDoubleClick={() => openCommitDiffTab(entry.hash, false)}
title={`${entry.shortHash}: ${entry.subject}`}
@@ -392,6 +441,9 @@ export const GitSidebar: React.FC = () => {
<span>{entry.shortHash}</span>
<span>{entry.author}</span>
<span>{new Date(entry.date).toLocaleDateString()}</span>
<span className={`git-sidebar-history-status git-sidebar-history-status--${entry.syncStatus ?? 'both'}`}>
{getHistoryStatusLabel(entry.syncStatus)}
</span>
</div>
</button>
))}