feat(git): stage / commit / push from the diff viewer (W4)
The walk-away endgame — review a diff on your phone, then land it without typing
git into a mobile terminal. MVP bounded to per-file stage/unstage, commit, and
push the current branch; discard/checkout/reset deliberately deferred (nothing here
mutates working-tree file contents).
- src/http/git-ops.ts (new): stageFiles/commit/push (execFile, no shell, never
throws). Path containment: every files[] entry realpath-contained under the repo,
argv after `--`, capped at diffMaxFiles. Commit message: empty/over-5000 → 400,
single -m argv. Push: current branch only — has-upstream → plain `git push`;
no-upstream + one remote → `git push -u <remote> <branch>`; 0 remotes → 400,
≥2 → 409, detached HEAD → 400. Remote AND branch read from the repo, never the
client. NEVER --force / +refspec. GIT_TERMINAL_PROMPT=0 + ssh BatchMode fail auth
fast (401). Errors classified to fixed safe strings (raw stderr never surfaced).
- POST /projects/git/{stage,commit,push} — all behind requireAllowedOrigin +
GIT_OPS_ENABLED kill-switch + per-IP rate limits (stage/commit 30/min, push 6/min)
+ isValidGitDir. public/diff.ts: per-file Stage/Unstage + a commit/push bar
(all text via textContent; re-loads the diff on success).
Verified: typecheck + build:web clean, git-ops tests 213 pass (unit covers escape
rejection / empty+overlong commit / push argv upstream-vs-no-upstream / classified
errors), full suite green at --test-timeout=30000.
This commit is contained in:
23
src/types.ts
23
src/types.ts
@@ -72,6 +72,11 @@ export interface Config {
|
||||
readonly worktreeEnabled: boolean; // WORKTREE_ENABLED, default true
|
||||
readonly worktreeRoot: string | undefined; // WORKTREE_ROOT (undefined → computed)
|
||||
readonly worktreeTimeoutMs: number; // WORKTREE_TIMEOUT_MS, default 10000
|
||||
// W4 git write (stage / commit / push) — the highest-risk write channel
|
||||
readonly gitOpsEnabled: boolean; // GIT_OPS_ENABLED, default true (kill-switch → all 3 routes 403)
|
||||
readonly gitOpsTimeoutMs: number; // GIT_OPS_TIMEOUT_MS, default 10000 (stage/commit exec bound)
|
||||
readonly gitPushTimeoutMs: number; // GIT_PUSH_TIMEOUT_MS, default 120000 (network-bound push)
|
||||
readonly commitMsgMaxLen: number; // COMMIT_MSG_MAX_LEN, default 5000 (message length cap)
|
||||
// B4 permission-mode relay
|
||||
readonly defaultPermissionMode: PermissionMode; // DEFAULT_PERMISSION_MODE, default 'default'
|
||||
readonly allowAutoMode: boolean; // ALLOW_AUTO_MODE, default false (SEC-M5)
|
||||
@@ -614,6 +619,24 @@ export interface PruneWorktreesResult {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/* ── W4 git write: stage / commit / push (§w4-commit-push) ── */
|
||||
|
||||
/** Result of the three W4 git-write routes (POST /projects/git/{stage,commit,push}).
|
||||
* ONE interface for all three (like CreateWorktreeResult) to keep the shared
|
||||
* contract small. `error` carries a SAFE message only — never raw git stderr
|
||||
* (SEC-M10); `status` is the HTTP status to return on failure. Success payloads
|
||||
* are route-specific and all optional. */
|
||||
export interface GitOpResult {
|
||||
ok: boolean;
|
||||
status?: number; // HTTP status on failure
|
||||
error?: string; // SAFE message only (never raw git stderr)
|
||||
staged?: boolean; // stage: direction applied (true = added, false = unstaged)
|
||||
count?: number; // stage: number of files affected
|
||||
commit?: string; // commit: short SHA of the new commit
|
||||
branch?: string; // push: branch that was pushed
|
||||
remote?: string; // push: remote it was pushed to
|
||||
}
|
||||
|
||||
/* ── v0.6 Projects UI preferences (server-persisted, cross-device) ── */
|
||||
|
||||
/** Cross-device UI preferences for the Projects launcher (impl: src/http/prefs-store.ts).
|
||||
|
||||
Reference in New Issue
Block a user