Documentation
¶
Overview ¶
Package git implements the daemon's git operation surface — a thin shell- out wrapper around the system `git` binary with path allowlisting, timeout, and byte caps. The daemon does NOT embed a git library (libgit2 etc.); it execs the host's git so behavior matches exactly what the user sees on the command line (same config, hooks, credentials).
Implementation of Git/GitService.swift (~2400 LOC). The Go port is ~30% smaller because Go's exec.Command + bytes.Buffer replaces Swift's Process + Pipe ceremony, and the path allowlist is shared with the security package (the Mac has its own PathValidator).
Security: every Run call validates cwd against security.PathAllowlist ($HOME + ~/.rmote/allowed-paths.txt). A git operation outside the allowlist returns ErrPathNotAllowed (HTTP 403). This prevents a compromised token from reading repo state in /etc or /root.
Index ¶
- Variables
- func IsValidBranchName(name string) bool
- func IsValidRef(ref string) bool
- func ProcessCwd(pid int) string
- func ValidateRelativePath(p, cwd string) (string, bool)
- type CommitMessageResult
- type CommitRequest
- type DiffRequest
- type GitBranch
- type GitCommit
- type GitCommitResponse
- type GitDiff
- type GitEntry
- type GitLogResponse
- type GitPushResponse
- type GitStatus
- type LogRequest
- type PathValidator
- type Result
- type Service
- func (s *Service) BranchesJSON(cwd string) ([]byte, error)
- func (s *Service) CheckoutJSON(cwd, branch string) ([]byte, error)
- func (s *Service) CommitJSON(req CommitRequest) ([]byte, error)
- func (s *Service) CommitMessage(cwd string, paths []string, agent string) CommitMessageResult
- func (s *Service) DiffJSON(req DiffRequest) ([]byte, error)
- func (s *Service) DiscardJSON(cwd string, paths []string) ([]byte, error)
- func (s *Service) LogJSON(req LogRequest) ([]byte, error)
- func (s *Service) MergeJSON(cwd, branch string) ([]byte, error)
- func (s *Service) PullJSON(cwd string) ([]byte, error)
- func (s *Service) PushJSON(cwd string) ([]byte, error)
- func (s *Service) ResetJSON(cwd, commit string) ([]byte, error)
- func (s *Service) RevertJSON(cwd, commit string) ([]byte, error)
- func (s *Service) Run(cwd string, args ...string) (*Result, error)
- func (s *Service) RunSuccess(cwd string, args ...string) ([]byte, error)
- func (s *Service) RunTimeout(cwd string, timeout time.Duration, args ...string) (*Result, error)
- func (s *Service) ShowJSON(cwd, commit string) ([]byte, error)
- func (s *Service) StageJSON(cwd string, paths []string, stage bool) ([]byte, error)
- func (s *Service) StatusJSON(cwd string) ([]byte, error)
- func (s *Service) ValidateCwd(cwd string) bool
Constants ¶
This section is empty.
Variables ¶
var ErrDetachedHead = errors.New("git: detached HEAD (no branch to push)")
ErrDetachedHead is returned when push is attempted on a detached HEAD.
var ErrGitNotFound = errors.New("git: binary not found on PATH")
ErrGitNotFound is returned when no `git` binary is on PATH.
var ErrIdentityMissing = errors.New("git: identity missing (configure user.email)")
ErrIdentityMissing is returned when git user.email isn't configured.
var ErrNoUpstream = errors.New("git: no upstream configured for this branch")
ErrNoUpstream is returned when the branch has no upstream tracking.
var ErrPathNotAllowed = errors.New("git: path not allowed")
ErrPathNotAllowed is returned when cwd is outside the allowlist.
var ErrTimeout = errors.New("git: command timed out")
ErrTimeout is returned when the git command exceeds the timeout.
Functions ¶
func IsValidBranchName ¶
IsValidBranchName rejects shell metacharacters. Allows alphanumerics, dash, underscore, slash, dot — the standard git branch name charset.
func IsValidRef ¶
IsValidRef validates a commit ref (hash, short hash, or refname). Same charset as branch names plus the refspec characters git accepts.
func ProcessCwd ¶
ProcessCwd returns the current working directory of the process with the given PID. On Linux, reads /proc/<pid>/cwd (symlink). On macOS, shells out to `lsof`. Returns "" if the cwd can't be determined (process exited, permission denied, etc.) — the caller falls back to the session's stored cwd.
func ValidateRelativePath ¶ added in v0.2.0
ValidateRelativePath ensures p is relative + stays under cwd. Returns the cleaned relative path, or ok=false on absolute, traversal, or escape.
Types ¶
type CommitMessageResult ¶
type CommitMessageResult struct {
Message string `json:"message"`
Available bool `json:"available"`
}
CommitMessageResult is the JSON response shape. iOS's GitCommitMessageResponse REQUIRES both fields or the decode fails.
type CommitRequest ¶
type CommitRequest struct {
Cwd string
Paths []string // relative paths to stage + commit
Message string // commit message
}
CommitRequest carries the commit body.
type DiffRequest ¶
type DiffRequest struct {
Cwd string
Path string // required, relative to cwd
Ref string // optional: show diff for this ref
Staged bool // optional: diff --cached
Full bool // optional: raise line cap to 50000
}
DiffRequest carries the query params for a diff lookup.
type GitCommit ¶
type GitCommit struct {
Hash string `json:"hash"`
ShortHash string `json:"short_hash"`
Subject string `json:"subject"`
Author string `json:"author"`
AuthorDate string `json:"author_date"` // ISO-8601 (%aI)
IsMerge bool `json:"is_merge"`
Body string `json:"body"` // always "" here (fetched on-demand by commit-detail)
Parents []string `json:"parents"` // full hashes from %P (NOT %p) — graph correctness
Branches []string `json:"branches"` // branch tips pointing at this commit
Tags []string `json:"tags"` // tag tips pointing at this commit
}
GitCommit is one entry in the log. Matches iOS GitCommit Codable.
type GitCommitResponse ¶
type GitCommitResponse struct {
OK bool `json:"ok"`
Short string `json:"short,omitempty"` // new HEAD short rev
}
GitCommitResponse is the response for POST /api/git/commit.
type GitDiff ¶
type GitDiff struct {
Path string `json:"path"`
Staged bool `json:"staged"`
Ref string `json:"ref,omitempty"` // only present when a ref was queried
Text string `json:"text"` // empty when binary
Binary bool `json:"binary"`
Truncated bool `json:"truncated"`
}
GitDiff is the response for the diff route. Matches iOS GitDiff Codable.
type GitEntry ¶
type GitEntry struct {
Path string `json:"path"`
OldPath string `json:"old_path,omitempty"` // rename source (format 2 only)
Status string `json:"status"` // porcelain-v2 XY, padded to 2 chars; "??" for untracked
Adds *int `json:"adds,omitempty"` // lines added (numstat); nil if binary/untracked
Dels *int `json:"dels,omitempty"` // lines deleted (numstat); nil if binary/untracked
}
GitEntry is one file in the status response. Matches iOS GitEntry Codable.
type GitLogResponse ¶
type GitLogResponse struct {
Commits []GitCommit `json:"commits"`
Limit int `json:"limit"`
Head string `json:"head,omitempty"` // full hash of current HEAD; empty on unborn repo
}
GitLogResponse is the response for GET /api/git/log.
type GitPushResponse ¶
type GitPushResponse struct {
OK bool `json:"ok"`
}
GitPushResponse is the response for POST /api/git/push.
type GitStatus ¶
type GitStatus struct {
Branch string `json:"branch"` // branch name or short rev if detached
Upstream string `json:"upstream,omitempty"`
Ahead int `json:"ahead"`
Behind int `json:"behind"`
Changes []GitEntry `json:"changes"` // tracked modified/added/renamed/deleted
Untracked []GitEntry `json:"untracked"` // untracked files
Truncated bool `json:"truncated"` // true if entry cap hit
InMerge bool `json:"in_merge"` // mid-merge/mid-revert
}
GitStatus is the response shape for GET /api/git/status. Matches iOS GitStatus Codable field names verbatim.
func ParseStatus ¶
ParseStatus parses `git status --porcelain=v2 --untracked-files=all -z -b` output into a GitStatus struct. The output is NUL-separated records; each record is a single line. Branch headers start with `# `.
type LogRequest ¶
type LogRequest struct {
Cwd string
Limit int // clamped 1..200, default 200
Skip int // >= 0, default 0
All bool // include local + remote branches (excluding tool-owned Entire refs)
}
LogRequest carries query params for the log lookup.
type PathValidator ¶
PathValidator is the interface for path-allowlist checks. Implemented by security.PathAllowlist; extracted as an interface so tests can substitute a test double without depending on $HOME.
type Result ¶
type Result struct {
Stdout []byte
Stderr []byte
ExitCode int // 0 = success; non-zero = git reported an error (stderr has the message)
Truncated bool // stdout or stderr exceeded the shared capture cap
}
Result captures one git invocation's output.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service wraps the system git binary with path validation + timeout. One instance serves all /api/git/* requests; goroutine-safe via stateless Run (each call spawns its own exec.Command).
func NewService ¶
func NewService() *Service
NewService builds a git service with the default path allowlist. The allowlist confines operations to $HOME + user-added paths.
func (*Service) BranchesJSON ¶
BranchesJSON lists local branches. `git branch --format=...` with a custom format that marks the current branch with `*`.
func (*Service) CheckoutJSON ¶
CheckoutJSON switches to a branch. Validates the branch name (no shell metacharacters) before passing to git.
func (*Service) CommitJSON ¶
func (s *Service) CommitJSON(req CommitRequest) ([]byte, error)
CommitJSON stages paths + commits. Multi-step:
- Check identity (user.email configured) → ErrIdentityMissing
- Sanitize message (strip NUL/VT/FF, trim, cap)
- git add -- <paths>
- git commit -m <msg>
- git rev-parse --short HEAD
func (*Service) CommitMessage ¶
func (s *Service) CommitMessage(cwd string, paths []string, agent string) CommitMessageResult
CommitMessage runs the agent CLI to draft a single Conventional Commits subject for the selection. Returns available:false on ANY failure (the route is always 200; never an error token).
func (*Service) DiffJSON ¶
func (s *Service) DiffJSON(req DiffRequest) ([]byte, error)
DiffJSON runs the appropriate git diff command and returns JSON-encoded GitDiff. Validates cwd via Service.Run. The 4-way branching matches the Mac exactly:
ref set → git show --no-color --format= --diff-merges=first-parent <ref> -- <path> staged=true → git diff --no-color --cached -- <path> untracked → git diff --no-color --no-index -- /dev/null <path> else → git diff --no-color -- <path>
Untracked detection: a separate `git ls-files --others --exclude-standard` check confirms the path is actually untracked before running --no-index (which would error on a tracked path).
func (*Service) DiscardJSON ¶
DiscardJSON discards working-tree changes for paths. Uses git checkout -- <paths> (NOT git reset --hard, which would also reset staged content).
func (*Service) LogJSON ¶
func (s *Service) LogJSON(req LogRequest) ([]byte, error)
LogJSON runs `git log` + for-each-ref and returns the JSON-encoded GitLogResponse. Uses %P (full parent hashes) not %p (abbreviated) for graph layout correctness. --topo-order is mandatory (child-before-parent). The all-branches view preserves every commit and its real parents. The iOS graph layout needs those intermediate commits to keep a branch lane continuous; decoration simplification rewrites ancestry and collapses branches into short tip-to-ancestor arcs.
func (*Service) PushJSON ¶
PushJSON pushes the current branch to its upstream. Multi-step:
- Check branch is not detached → ErrDetachedHead
- Check upstream exists → ErrNoUpstream
- git push --no-progress -- <remote> <branch>
- NEVER uses --force (matches Mac safety).
func (*Service) ResetJSON ¶
ResetJSON hard-resets to a commit. DANGEROUS — discards uncommitted changes. Audit-logged by the caller. The caller MUST confirm with the user.
func (*Service) RevertJSON ¶
RevertJSON reverts a commit. Uses --no-edit (no commit message editor).
func (*Service) Run ¶
Run executes `git <args>` in cwd. Validates cwd first, resolves the git binary lazily (cached), then execs with a timeout + output cap. A non-zero exit code is NOT an error from Run's perspective — it's returned in Result.ExitCode so the caller can surface the git error message to the user (e.g., "nothing to commit" is exit 1 but not a daemon error).
Returns an error only for: path not allowed, git not found, timeout, or spawn failure. These are HTTP 4xx/5xx level failures.
func (*Service) RunSuccess ¶
RunSuccess is a convenience that runs git + returns stdout on success, or the stderr message on non-zero exit. Used by read-only routes where the caller just wants the output or the error string.
func (*Service) RunTimeout ¶
RunTimeout is Run with a caller-supplied deadline capped by the service default. Multi-command read surfaces use it to enforce one aggregate budget.
func (*Service) ShowJSON ¶
ShowJSON returns the patch for a commit. `git show --no-color <commit>`. Capped at maxDiffBytes (4 MB).
func (*Service) StageJSON ¶
StageJSON stages or unstages paths. stage=true → git add; stage=false → git reset (unstage).
func (*Service) StatusJSON ¶
StatusJSON runs `git status --porcelain=v2` + numstat + detached-HEAD resolution and returns the JSON-encoded GitStatus. cwd is validated by the Service.Run call. Best-effort numstat — if it fails (e.g., empty repo), adds/dels are simply nil.
func (*Service) ValidateCwd ¶ added in v0.2.0
ValidateCwd applies the same canonical path policy used before every Git command. Callers that must touch repository files directly use this first so they cannot bypass the service's allowlist.