feat(git-graph): VS Code-style git graph with commit actions in History modal (#1431)
* feat(types): add parents to GitLogEntry and new commit action types
* feat(git): add parent hashes and --all flag to getLog
* fix(git): move record separator to start of log format string
* feat(git): add checkoutCommit server function and route
* feat(git): add cherryPick server function and route
* feat(git): add revertCommit server function and route
* feat(git): add resetToCommit server function and route
* fix(tests): make git service tests branch-name portable, add error path tests
* feat(client): add checkoutCommit, cherryPick, revertCommit, resetToCommit API wrappers
* feat(git-graph): add lane assignment algorithm with tests
* feat(git-graph): add GitGraphSegment per-row SVG renderer
* feat(i18n): add locale strings for git graph action buttons
* fix(git-graph): handle lane convergence, fix SVG path coords, add connector tests
* feat(git-graph): add ref badges and action buttons to HistoryCommitRow
* fix(git-graph): add loading guards to reset actions, use theme tokens for ref badges
* fix(git-graph): conditional hooks, stale graph log, conflict handling, i18n
* fix(types): replace toBeDefined with toBeTruthy, fix toast API usage
* fix(lint): remove unused variables
* fix(git-graph): fix SVG height causing 150px row spacing
* fix(git-graph): smooth bezier curves, fill row height, round line caps
* fix(git-graph): non-scaling-stroke fixes bezier white spaces, sort curves on top
* fix(git-graph): remove viewBox scaling, match SVG height to actual row height
* fix(git-graph): ResizeObserver tracks actual row height, eliminates SVG height mismatch
* feat(git-graph): replace SVG with Canvas for graph rendering
* fix(git-graph): isolate canvas from flex layout to prevent replaced-element height leak
* feat(git-graph): align action buttons, add confirmation popups for all actions
* fix(git-graph): address code review findings CR-001 through CR-005
- CR-001: VS Code getGitLog now forwards 'all' option and parses %P parents
- CR-002: VS Code bridge/gitService implement checkoutCommit, cherryPick,
revertCommit, resetToCommit with conflict detection and hard-reset guard
- CR-003: server-side commit hash validated with /^[0-9a-fA-F]{7,40}$/
in both routes.js and service.js; 12 new rejection tests added
- CR-004: cherry-pick/revert conflict path now refreshes fetchStatus/
fetchBranches/fetchLog; conflict toast uses i18n keys in all 7 locales
- CR-005: corrected O(n) comment to O(n x lanes)
* fix(i18n): add zh-TW locale and common.language.traditionalChinese key to all locales
upstream/main added zh-TW.ts after branch diverged; CI type-check fails
when PR is merged because zh-TW.ts was missing all gitView.history.actions.*
keys and loadMore/loadingMore. Also adds common.language.traditionalChinese
to en.ts and all 6 non-English files to match upstream en.ts.
* fix: harden git history actions
* feat: split git history graph view
* chore: remove git graph planning docs
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Bohdan Triapitsyn
parent
cc3d1bd63c
commit
52ffe9daef
@@ -14,6 +14,10 @@ const requireDirectory = (id: string, type: string, directory?: string): BridgeR
|
||||
return null;
|
||||
};
|
||||
|
||||
const isValidCommitHash = (hash: string | undefined): hash is string => (
|
||||
typeof hash === 'string' && /^[0-9a-fA-F]{7,40}$/.test(hash)
|
||||
);
|
||||
|
||||
export async function handleStandardGitBridgeMessage(message: BridgeMessageInput): Promise<BridgeResponse | null> {
|
||||
const { id, type, payload } = message;
|
||||
|
||||
@@ -416,17 +420,70 @@ export async function handleStandardGitBridgeMessage(message: BridgeMessageInput
|
||||
return { id, type, success: true, data: result };
|
||||
}
|
||||
|
||||
case 'api:git/checkout-commit': {
|
||||
const { directory, hash } = (payload || {}) as { directory?: string; hash?: string };
|
||||
const dirError = requireDirectory(id, type, directory);
|
||||
if (dirError) return dirError;
|
||||
if (!isValidCommitHash(hash)) {
|
||||
return { id, type, success: false, error: 'Invalid commit hash' };
|
||||
}
|
||||
const result = await gitService.checkoutCommit(directory!, hash);
|
||||
return { id, type, success: true, data: result };
|
||||
}
|
||||
|
||||
case 'api:git/cherry-pick': {
|
||||
const { directory, hash } = (payload || {}) as { directory?: string; hash?: string };
|
||||
const dirError = requireDirectory(id, type, directory);
|
||||
if (dirError) return dirError;
|
||||
if (!isValidCommitHash(hash)) {
|
||||
return { id, type, success: false, error: 'Invalid commit hash' };
|
||||
}
|
||||
const result = await gitService.cherryPick(directory!, hash);
|
||||
return { id, type, success: true, data: result };
|
||||
}
|
||||
|
||||
case 'api:git/revert-commit': {
|
||||
const { directory, hash } = (payload || {}) as { directory?: string; hash?: string };
|
||||
const dirError = requireDirectory(id, type, directory);
|
||||
if (dirError) return dirError;
|
||||
if (!isValidCommitHash(hash)) {
|
||||
return { id, type, success: false, error: 'Invalid commit hash' };
|
||||
}
|
||||
const result = await gitService.revertCommit(directory!, hash);
|
||||
return { id, type, success: true, data: result };
|
||||
}
|
||||
|
||||
case 'api:git/reset-to-commit': {
|
||||
const { directory, hash, mode, force } = (payload || {}) as {
|
||||
directory?: string;
|
||||
hash?: string;
|
||||
mode?: 'soft' | 'mixed' | 'hard';
|
||||
force?: boolean;
|
||||
};
|
||||
const dirError = requireDirectory(id, type, directory);
|
||||
if (dirError) return dirError;
|
||||
if (!isValidCommitHash(hash)) {
|
||||
return { id, type, success: false, error: 'Invalid commit hash' };
|
||||
}
|
||||
if (!mode || !['soft', 'mixed', 'hard'].includes(mode)) {
|
||||
return { id, type, success: false, error: 'mode must be soft, mixed, or hard' };
|
||||
}
|
||||
const result = await gitService.resetToCommit(directory!, hash, mode, force);
|
||||
return { id, type, success: true, data: result };
|
||||
}
|
||||
|
||||
case 'api:git/log': {
|
||||
const { directory, maxCount, from, to, file } = (payload || {}) as {
|
||||
const { directory, maxCount, from, to, file, all } = (payload || {}) as {
|
||||
directory?: string;
|
||||
maxCount?: number;
|
||||
from?: string;
|
||||
to?: string;
|
||||
file?: string;
|
||||
all?: boolean;
|
||||
};
|
||||
const dirError = requireDirectory(id, type, directory);
|
||||
if (dirError) return dirError;
|
||||
const result = await gitService.getGitLog(directory!, { maxCount, from, to, file });
|
||||
const result = await gitService.getGitLog(directory!, { maxCount, from, to, file, all });
|
||||
return { id, type, success: true, data: result };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user