mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-01 12:02:05 +00:00
refactor(desktop): centralize remote git REST routing
Keep the remote git mirror as a thin facade: route all GETs through gitGet, all mutations through gitPost, and keep consumers on desktopGit(). On the backend, route git paths through a single _git_path helper instead of repeating str(_fs_path(...)) in every endpoint. Behavior unchanged.
This commit is contained in:
parent
4e9439cc3b
commit
453f134b3b
2 changed files with 57 additions and 48 deletions
|
|
@ -16,8 +16,6 @@ import { isDesktopFsRemoteMode } from './desktop-fs'
|
|||
|
||||
type GitBridge = NonNullable<NonNullable<Window['hermesDesktop']>['git']>
|
||||
|
||||
const q = (value: string) => encodeURIComponent(value)
|
||||
|
||||
function desktopApi<T>(path: string, body?: Record<string, unknown>): Promise<T> {
|
||||
const desktop = window.hermesDesktop
|
||||
|
||||
|
|
@ -28,60 +26,67 @@ function desktopApi<T>(path: string, body?: Record<string, unknown>): Promise<T>
|
|||
return desktop.api<T>(body ? { body, method: 'POST', path } : { path })
|
||||
}
|
||||
|
||||
function gitGet<T>(route: string, params: Record<string, boolean | null | string | undefined>): Promise<T> {
|
||||
const query = new URLSearchParams()
|
||||
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== null && value !== undefined) {
|
||||
query.set(key, String(value))
|
||||
}
|
||||
}
|
||||
|
||||
return desktopApi<T>(`/api/git/${route}?${query.toString()}`)
|
||||
}
|
||||
|
||||
function gitPost<T>(route: string, body: Record<string, unknown>): Promise<T> {
|
||||
return desktopApi<T>(`/api/git/${route}`, body)
|
||||
}
|
||||
|
||||
const remoteGit: GitBridge = {
|
||||
worktreeList: async repoPath =>
|
||||
(await desktopApi<{ worktrees: HermesGitWorktree[] }>(`/api/git/worktrees?path=${q(repoPath)}`)).worktrees,
|
||||
(await gitGet<{ worktrees: HermesGitWorktree[] }>('worktrees', { path: repoPath })).worktrees,
|
||||
|
||||
worktreeAdd: (repoPath, options) => desktopApi(`/api/git/worktree/add`, { path: repoPath, ...options }),
|
||||
worktreeAdd: (repoPath, options) => gitPost('worktree/add', { path: repoPath, ...options }),
|
||||
|
||||
worktreeRemove: (repoPath, worktreePath, options) =>
|
||||
desktopApi(`/api/git/worktree/remove`, { force: options?.force ?? false, path: repoPath, worktreePath }),
|
||||
gitPost('worktree/remove', { force: options?.force ?? false, path: repoPath, worktreePath }),
|
||||
|
||||
branchSwitch: (repoPath, branch) => desktopApi(`/api/git/branch/switch`, { branch, path: repoPath }),
|
||||
branchSwitch: (repoPath, branch) => gitPost('branch/switch', { branch, path: repoPath }),
|
||||
|
||||
branchList: async repoPath =>
|
||||
(await desktopApi<{ branches: HermesGitBranch[] }>(`/api/git/branches?path=${q(repoPath)}`)).branches,
|
||||
(await gitGet<{ branches: HermesGitBranch[] }>('branches', { path: repoPath })).branches,
|
||||
|
||||
repoStatus: repoPath => desktopApi<HermesRepoStatus | null>(`/api/git/status?path=${q(repoPath)}`),
|
||||
repoStatus: repoPath => gitGet<HermesRepoStatus | null>('status', { path: repoPath }),
|
||||
|
||||
fileDiff: async (repoPath, filePath) =>
|
||||
(await desktopApi<{ diff: string }>(`/api/git/file-diff?path=${q(repoPath)}&file=${q(filePath)}`)).diff,
|
||||
(await gitGet<{ diff: string }>('file-diff', { file: filePath, path: repoPath })).diff,
|
||||
|
||||
review: {
|
||||
list: (repoPath, scope, baseRef) =>
|
||||
desktopApi<HermesReviewList>(
|
||||
`/api/git/review/list?path=${q(repoPath)}&scope=${q(scope)}${baseRef ? `&base=${q(baseRef)}` : ''}`
|
||||
),
|
||||
gitGet<HermesReviewList>('review/list', { base: baseRef, path: repoPath, scope }),
|
||||
|
||||
diff: async (repoPath, filePath, scope, baseRef, staged) =>
|
||||
(
|
||||
await desktopApi<{ diff: string }>(
|
||||
`/api/git/review/diff?path=${q(repoPath)}&file=${q(filePath)}&scope=${q(scope)}&staged=${staged ? 'true' : 'false'}${baseRef ? `&base=${q(baseRef)}` : ''}`
|
||||
)
|
||||
).diff,
|
||||
(await gitGet<{ diff: string }>('review/diff', { base: baseRef, file: filePath, path: repoPath, scope, staged }))
|
||||
.diff,
|
||||
|
||||
stage: (repoPath, filePath) => desktopApi(`/api/git/review/stage`, { file: filePath ?? null, path: repoPath }),
|
||||
stage: (repoPath, filePath) => gitPost('review/stage', { file: filePath ?? null, path: repoPath }),
|
||||
|
||||
unstage: (repoPath, filePath) => desktopApi(`/api/git/review/unstage`, { file: filePath ?? null, path: repoPath }),
|
||||
unstage: (repoPath, filePath) => gitPost('review/unstage', { file: filePath ?? null, path: repoPath }),
|
||||
|
||||
revert: (repoPath, filePath) => desktopApi(`/api/git/review/revert`, { file: filePath ?? null, path: repoPath }),
|
||||
revert: (repoPath, filePath) => gitPost('review/revert', { file: filePath ?? null, path: repoPath }),
|
||||
|
||||
revParse: async (repoPath, ref) =>
|
||||
(
|
||||
await desktopApi<{ sha: null | string }>(
|
||||
`/api/git/review/rev-parse?path=${q(repoPath)}${ref ? `&ref=${q(ref)}` : ''}`
|
||||
)
|
||||
).sha,
|
||||
(await gitGet<{ sha: null | string }>('review/rev-parse', { path: repoPath, ref })).sha,
|
||||
|
||||
commit: (repoPath, message, push) => desktopApi(`/api/git/review/commit`, { message, path: repoPath, push }),
|
||||
commit: (repoPath, message, push) => gitPost('review/commit', { message, path: repoPath, push }),
|
||||
|
||||
commitContext: repoPath => desktopApi(`/api/git/review/commit-context?path=${q(repoPath)}`),
|
||||
commitContext: repoPath => gitGet('review/commit-context', { path: repoPath }),
|
||||
|
||||
push: repoPath => desktopApi(`/api/git/review/push`, { path: repoPath }),
|
||||
push: repoPath => gitPost('review/push', { path: repoPath }),
|
||||
|
||||
shipInfo: repoPath => desktopApi<HermesReviewShipInfo>(`/api/git/review/ship-info?path=${q(repoPath)}`),
|
||||
shipInfo: repoPath => gitGet<HermesReviewShipInfo>('review/ship-info', { path: repoPath }),
|
||||
|
||||
createPr: repoPath => desktopApi(`/api/git/review/create-pr`, { path: repoPath })
|
||||
createPr: repoPath => gitPost('review/create-pr', { path: repoPath })
|
||||
},
|
||||
|
||||
// Repo discovery is a local-disk crawl; on a remote gateway the backend
|
||||
|
|
|
|||
|
|
@ -1931,6 +1931,10 @@ async def _git_op(fn, *args):
|
|||
raise HTTPException(status_code=400, detail=str(exc) or "git operation failed")
|
||||
|
||||
|
||||
def _git_path(path: str) -> str:
|
||||
return str(_fs_path(path))
|
||||
|
||||
|
||||
class GitPathBody(BaseModel):
|
||||
path: str
|
||||
|
||||
|
|
@ -1967,79 +1971,79 @@ class GitBranchSwitchBody(BaseModel):
|
|||
|
||||
@app.get("/api/git/status")
|
||||
async def git_status_route(path: str):
|
||||
return await _git_op(_web_git.repo_status, str(_fs_path(path)))
|
||||
return await _git_op(_web_git.repo_status, _git_path(path))
|
||||
|
||||
|
||||
@app.get("/api/git/worktrees")
|
||||
async def git_worktrees_route(path: str):
|
||||
return {"worktrees": await _git_op(_web_git.worktree_list, str(_fs_path(path)))}
|
||||
return {"worktrees": await _git_op(_web_git.worktree_list, _git_path(path))}
|
||||
|
||||
|
||||
@app.get("/api/git/branches")
|
||||
async def git_branches_route(path: str):
|
||||
return {"branches": await _git_op(_web_git.branch_list, str(_fs_path(path)))}
|
||||
return {"branches": await _git_op(_web_git.branch_list, _git_path(path))}
|
||||
|
||||
|
||||
@app.get("/api/git/review/list")
|
||||
async def git_review_list_route(path: str, scope: str = "uncommitted", base: Optional[str] = None):
|
||||
return await _git_op(_web_git.review_list, str(_fs_path(path)), scope, base)
|
||||
return await _git_op(_web_git.review_list, _git_path(path), scope, base)
|
||||
|
||||
|
||||
@app.get("/api/git/review/diff")
|
||||
async def git_review_diff_route(
|
||||
path: str, file: str, scope: str = "uncommitted", base: Optional[str] = None, staged: bool = False
|
||||
):
|
||||
return {"diff": await _git_op(_web_git.review_diff, str(_fs_path(path)), file, scope, base, staged)}
|
||||
return {"diff": await _git_op(_web_git.review_diff, _git_path(path), file, scope, base, staged)}
|
||||
|
||||
|
||||
@app.get("/api/git/file-diff")
|
||||
async def git_file_diff_route(path: str, file: str):
|
||||
return {"diff": await _git_op(_web_git.file_diff_vs_head, str(_fs_path(path)), file)}
|
||||
return {"diff": await _git_op(_web_git.file_diff_vs_head, _git_path(path), file)}
|
||||
|
||||
|
||||
@app.get("/api/git/review/commit-context")
|
||||
async def git_commit_context_route(path: str):
|
||||
return await _git_op(_web_git.review_commit_context, str(_fs_path(path)))
|
||||
return await _git_op(_web_git.review_commit_context, _git_path(path))
|
||||
|
||||
|
||||
@app.get("/api/git/review/rev-parse")
|
||||
async def git_rev_parse_route(path: str, ref: Optional[str] = None):
|
||||
return {"sha": await _git_op(_web_git.review_rev_parse, str(_fs_path(path)), ref)}
|
||||
return {"sha": await _git_op(_web_git.review_rev_parse, _git_path(path), ref)}
|
||||
|
||||
|
||||
@app.get("/api/git/review/ship-info")
|
||||
async def git_ship_info_route(path: str):
|
||||
return await _git_op(_web_git.review_ship_info, str(_fs_path(path)))
|
||||
return await _git_op(_web_git.review_ship_info, _git_path(path))
|
||||
|
||||
|
||||
@app.post("/api/git/review/stage")
|
||||
async def git_stage_route(body: GitFileBody):
|
||||
return await _git_op(_web_git.review_stage, str(_fs_path(body.path)), body.file)
|
||||
return await _git_op(_web_git.review_stage, _git_path(body.path), body.file)
|
||||
|
||||
|
||||
@app.post("/api/git/review/unstage")
|
||||
async def git_unstage_route(body: GitFileBody):
|
||||
return await _git_op(_web_git.review_unstage, str(_fs_path(body.path)), body.file)
|
||||
return await _git_op(_web_git.review_unstage, _git_path(body.path), body.file)
|
||||
|
||||
|
||||
@app.post("/api/git/review/revert")
|
||||
async def git_revert_route(body: GitFileBody):
|
||||
return await _git_op(_web_git.review_revert, str(_fs_path(body.path)), body.file)
|
||||
return await _git_op(_web_git.review_revert, _git_path(body.path), body.file)
|
||||
|
||||
|
||||
@app.post("/api/git/review/commit")
|
||||
async def git_commit_route(body: GitCommitBody):
|
||||
return await _git_op(_web_git.review_commit, str(_fs_path(body.path)), body.message, body.push)
|
||||
return await _git_op(_web_git.review_commit, _git_path(body.path), body.message, body.push)
|
||||
|
||||
|
||||
@app.post("/api/git/review/push")
|
||||
async def git_push_route(body: GitPathBody):
|
||||
return await _git_op(_web_git.review_push, str(_fs_path(body.path)))
|
||||
return await _git_op(_web_git.review_push, _git_path(body.path))
|
||||
|
||||
|
||||
@app.post("/api/git/review/create-pr")
|
||||
async def git_create_pr_route(body: GitPathBody):
|
||||
return await _git_op(_web_git.review_create_pr, str(_fs_path(body.path)))
|
||||
return await _git_op(_web_git.review_create_pr, _git_path(body.path))
|
||||
|
||||
|
||||
@app.post("/api/git/worktree/add")
|
||||
|
|
@ -2054,19 +2058,19 @@ async def git_worktree_add_route(body: GitWorktreeAddBody):
|
|||
}.items()
|
||||
if value
|
||||
}
|
||||
return await _git_op(_web_git.worktree_add, str(_fs_path(body.path)), options)
|
||||
return await _git_op(_web_git.worktree_add, _git_path(body.path), options)
|
||||
|
||||
|
||||
@app.post("/api/git/worktree/remove")
|
||||
async def git_worktree_remove_route(body: GitWorktreeRemoveBody):
|
||||
return await _git_op(
|
||||
_web_git.worktree_remove, str(_fs_path(body.path)), str(_fs_path(body.worktreePath)), body.force
|
||||
_web_git.worktree_remove, _git_path(body.path), _git_path(body.worktreePath), body.force
|
||||
)
|
||||
|
||||
|
||||
@app.post("/api/git/branch/switch")
|
||||
async def git_branch_switch_route(body: GitBranchSwitchBody):
|
||||
return await _git_op(_web_git.branch_switch, str(_fs_path(body.path)), body.branch)
|
||||
return await _git_op(_web_git.branch_switch, _git_path(body.path), body.branch)
|
||||
|
||||
|
||||
@app.get("/api/status")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue