gitserver

package
v0.12.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Index

Constants

View Source
const NearExpiryThreshold = 1 * time.Hour

NearExpiryThreshold is how close to expiry credentials must be to trigger refresh

Variables

View Source
var (
	// ErrPathExists is returned when the checkout path already exists and is not empty
	ErrPathExists = errors.New("path already exists and is not empty")

	// ErrNoCredentials is returned when credentials are not available
	ErrNoCredentials = errors.New("git credentials not available")

	// ErrCloneFailed is returned when git clone fails
	ErrCloneFailed = errors.New("git clone failed")

	// ErrEmptyURL is returned when an empty repo URL is provided
	ErrEmptyURL = errors.New("repo URL cannot be empty")
)
View Source
var TestAllowFileTransport bool

TestAllowFileTransport is a test-only escape hatch that disables the `-c protocol.file.allow=never` hardening in TwoPhaseClone. The Blue-green GC tests clone from a local bare repo via file:// to simulate a remote; they MUST set this to true for the duration of the test (the production default is false). Setting this in non-test code is a security bug.

We use a package var (not a parameter) to keep the production call sites in sync.go / sync_gc.go / sync_team.go unchanged — the hardening still applies to every production code path because none of them flip the var.

Functions

func BuildAuthURL added in v0.3.0

func BuildAuthURL(repoURL string, creds *GitCredentials) (string, error)

BuildAuthURL embeds credentials into the git URL for authentication. Uses the PAT token with oauth2 username for GitLab-style auth. SSH URLs are returned unchanged since they use SSH key auth. Supports https:// URLs and http://localhost URLs (for local development).

func CacheFilesTracked added in v0.3.0

func CacheFilesTracked(repoPath string) bool

CacheFilesTracked returns true if any .sageox/cache/ files are tracked by git. Used by doctor checks to detect cache files that were committed before .gitignore was in place.

func CheckoutGitignoreNeedsFix added in v0.3.0

func CheckoutGitignoreNeedsFix(repoPath string) bool

CheckoutGitignoreNeedsFix returns true if .sageox/.gitignore is missing or doesn't contain all required entries. Used by doctor checks to detect whether EnsureCheckoutGitignore needs to run.

func ClearCredentialHelperEntry added in v0.6.0

func ClearCredentialHelperEntry(serverURL string)

ClearCredentialHelperEntry evicts any stored credential for the given git server from all configured credential helpers (osxkeychain, libsecret, wincred, etc.) using `git credential reject`. This prevents stale entries from a previous install from silently overriding GIT_ASKPASS in future git operations.

Failures are logged but not returned — this is best-effort cleanup. serverURL should be the git server base URL (e.g. "https://git.sageox.ai").

func CloneFromURLWithEndpoint

func CloneFromURLWithEndpoint(ctx context.Context, repoURL, path, endpointURL string, opts *CheckoutOptions) error

CloneFromURLWithEndpoint clones using endpoint-specific credentials. Falls back to default credentials if no endpoint-specific ones exist.

func CreateAgentsMD

func CreateAgentsMD(ctx context.Context, repoPath string, opts *AgentsMDOptions) error

CreateAgentsMD creates an AGENTS.md file in the cloned ledger repository. This file explains the repository's purpose to AI agents and humans. Commits and pushes the file if the repo has a remote.

func CredentialHelperArgs added in v0.10.0

func CredentialHelperArgs() []string

CredentialHelperArgs returns the `-c` flags that install the ox-managed credential helper for a single git invocation, without touching the repo's persisted .git/config. The leading empty `credential.helper=` clears any inherited helpers so ours is authoritative for that one command; the second installs the ox helper.

Use this on network git operations that run before the helper has been written into .git/config — notably the initial clone (the repo doesn't exist yet, so InstallCredentialHelper can't be called first). Both the ledger full-clone and the team-context two-phase clone build their clone argv from this so the two paths cannot drift.

func DefaultCheckoutPath

func DefaultCheckoutPath(repoName, workDir string) string

DefaultCheckoutPath returns the default checkout path for a repo. For ledger repos, defaults to sibling directory of the working directory. For team repos, defaults to a team-specific subdirectory.

func DefaultHelperCommand added in v0.8.0

func DefaultHelperCommand() string

DefaultHelperCommand returns the current helper command, or a sensible fallback ("!ox git-credential-helper") if the binary location wasn't registered. The fallback still works as long as `ox` is on $PATH, which is the install default.

func DisableCommitSigning added in v0.12.0

func DisableCommitSigning(repoPath string) (changed bool, err error)

DisableCommitSigning forces commit and tag signing OFF in a single repo's .git/config. ox-managed repos (ledgers, team contexts) are committed non-interactively by the daemon and CLI; if they inherit a user's global commit.gpgsign=true with an SSH/GPG signing key, every commit blocks on a passphrase prompt that has no TTY to answer it and dies with "fatal: failed to write commit object". The result is silent: sessions stage but never commit, never push, never sync.

Writing the override into the repo's LOCAL config (not --global) keeps the user's own repos free to sign while guaranteeing ox-managed repos never do. Idempotent: skips the write when the key is already "false" in the repo's LOCAL config specifically.

func EnsureCheckoutGitignore added in v0.3.0

func EnsureCheckoutGitignore(repoPath string) error

EnsureCheckoutGitignore ensures .sageox/.gitignore exists in the given repo with required entries to prevent daemon-written files from appearing as untracked. Without this, isCheckoutClean() in the GC path sees these files as dirty and permanently blocks blue-green reclone.

Writes the file and commits it so the gitignore itself doesn't appear as untracked. The commit propagates upstream on the next daemon push cycle.

Idempotent: reads existing content, only writes/commits if entries are missing. Preserves any existing custom entries in the file.

func EnsureCheckoutGitignoreCtx added in v0.3.0

func EnsureCheckoutGitignoreCtx(ctx context.Context, repoPath string) error

EnsureCheckoutGitignoreCtx is like EnsureCheckoutGitignore but accepts a context.

func EnsureGitignoreBeforeCommit added in v0.3.0

func EnsureGitignoreBeforeCommit(repoPath string)

EnsureGitignoreBeforeCommit is a guard that MUST be called before any git commit to a ledger or team context. It ensures .sageox/.gitignore is in place so that local-only cache files (e.g., sync-state.json) are never committed.

Without this guard, broad operations like `git add -A` will stage cache files. Even with explicit file lists, this guard prevents future regressions.

Also untracks any cache files that were committed before .gitignore existed (git rm --cached does not delete the local file).

func EnsureGitignoreBeforeCommitCtx added in v0.3.0

func EnsureGitignoreBeforeCommitCtx(ctx context.Context, repoPath string)

EnsureGitignoreBeforeCommitCtx is like EnsureGitignoreBeforeCommit but accepts a context.

func GetBareRemoteURL added in v0.6.0

func GetBareRemoteURL(repoPath string) (string, error)

GetBareRemoteURL returns the origin remote URL with credentials stripped. Useful when you need the repo URL for API derivation (e.g., LFS batch endpoint) without embedding the PAT. offline-safe: returns error for repos with no origin remote; callers must handle

func GetGitVersion

func GetGitVersion() (string, error)

GetGitVersion returns the installed git version

func GetStorageBackend

func GetStorageBackend() string

GetStorageBackend returns the currently active storage backend. Returns "keychain" or "file" depending on what's available.

func InstallCredentialHelper added in v0.8.0

func InstallCredentialHelper(repoPath string, cfg HelperConfig) error

InstallCredentialHelper writes the ox-managed credential helper config into a repo's .git/config under a host-scoped section. Idempotent: if the same helper is already configured, the function is a no-op.

Rewrites (not appends) the per-host helper value so a repo whose .git/config previously had an embedded oauth2:TOKEN URL gets a clean single entry instead of stacked helpers. Other unrelated [credential] sections (e.g. for github.com, third-party hosts) are preserved.

Per ox-eeqi: this is the migration target. After this is called and StripRemoteCredentials runs against the same repo, future git fetch / push operations resolve credentials via the helper instead of reading them out of the embedded origin URL.

func IsGitInstalled

func IsGitInstalled() bool

IsGitInstalled checks if git is available in PATH

func MigrateLedgerCredentials added in v0.8.0

func MigrateLedgerCredentials(repoPath string, helperCmd string) (changed bool, err error)

MigrateLedgerCredentials performs the one-shot migration for a single ledger: disable commit signing, strip any embedded oauth2:TOKEN from the origin URL, then install the ox credential helper for the resulting bare host. Idempotent — safe to invoke on every daemon startup.

Returns ok=true if the migration ran (signing disabled, stripped, or installed); ok=false if there was nothing to do. The error result is reserved for genuine failures (git command errors, malformed origin URLs).

func RefreshRemoteCredentials added in v0.1.1

func RefreshRemoteCredentials(repoPath, endpointURL string) error

RefreshRemoteCredentials reconciles a repo's git auth setup with the current credential store. Per ox-eeqi, this no longer embeds the PAT into the origin URL. Instead it:

  • strips any leftover embedded oauth2:TOKEN from origin (one-time migration for ledgers cloned by pre-eeqi ox versions), and
  • installs/refreshes the ox credential helper in .git/config so future fetch/push operations resolve auth via the helper.

The endpointURL parameter is retained for API compatibility with existing callers (login.go, session_upload.go, import.go) but is now used only to surface a clearer warning when no credentials are stored for the endpoint the repo is going to push to. The helper itself looks credentials up by git host at invocation time.

No-op for SSH URLs, local remotes, and non-oauth2 userinfo (deploy tokens). Returns nil on success or no-op. offline-safe: missing origin is a clean "nothing to do."

func RejFilesTracked added in v0.12.0

func RejFilesTracked(repoPath string) (bool, error)

RejFilesTracked reports whether any *.rej patch-reject files are tracked by git. Used by doctor to detect .rej artifacts swept into ledger history. A non-nil error means detection itself failed (e.g. git unavailable, not a repo) — callers MUST NOT treat that as "no .rej tracked", or doctor could report a clean ledger when the check never ran.

func RemoveCredentials

func RemoveCredentials() error

RemoveCredentials deletes git server credentials from all storage locations. Removes from both keychain and file storage to ensure complete cleanup.

func RemoveCredentialsForEndpoint

func RemoveCredentialsForEndpoint(endpointURL string) error

RemoveCredentialsForEndpoint deletes git credentials for a specific endpoint. Removes from both keychain and file storage to ensure complete cleanup.

func SanitizeRemoteURL added in v0.1.1

func SanitizeRemoteURL(rawURL string) string

SanitizeRemoteURL removes credentials from a URL for safe display. Returns the original string for SSH URLs or unparseable URLs.

func SaveCredentialsForEndpoint

func SaveCredentialsForEndpoint(endpointURL string, creds GitCredentials) error

SaveCredentialsForEndpoint saves git credentials for a specific endpoint. Uses OS keychain as primary storage (with endpoint-specific key). Falls back to file storage for CI/headless environments.

func SetHelperCommand added in v0.8.0

func SetHelperCommand(cmd string)

SetHelperCommand records the credential helper invocation string for use by MigrateLedgerCredentials and other gitserver callers. Idempotent.

func StripRemoteCredentials added in v0.1.1

func StripRemoteCredentials(repoPath string) error

StripRemoteCredentials removes embedded credentials from a repo's git remote URL. Transforms https://oauth2:TOKEN@host/repo.githttps://host/repo.git No-op for SSH URLs, bare URLs, or URLs without oauth2 userinfo.

func TestResetKeyringProbeCache added in v0.12.0

func TestResetKeyringProbeCache()

TestResetKeyringProbeCache clears the cached probe result, forcing the next probeKeyringCached call to re-probe live.

func TestSetConfigDirOverride

func TestSetConfigDirOverride(dir string) string

TestSetConfigDirOverride sets the config directory override for testing. Returns the previous value so it can be restored. This function should only be called from tests.

func TestSetForceFileStorage

func TestSetForceFileStorage(force bool) bool

TestSetForceFileStorage forces file-based storage for testing. Returns the previous value so it can be restored. This function should only be called from tests.

func TestSetKeyringProbeFunc added in v0.12.0

func TestSetKeyringProbeFunc(fn func() bool) func() bool

TestSetKeyringProbeFunc overrides the live probe function for testing. Returns the previous value so it can be restored.

func TestSetKeyringProbeNow added in v0.12.0

func TestSetKeyringProbeNow(fn func() time.Time) func() time.Time

TestSetKeyringProbeNow overrides the clock used for cache TTL checks. Returns the previous value so it can be restored.

func ValidateTeamContextClone added in v0.3.0

func ValidateTeamContextClone(repoPath string, cfg *manifest.ManifestConfig)

ValidateTeamContextClone checks that a freshly cloned team context has expected content. All checks are warning-only — a missing file does not fail the clone.

Types

type AgentsMDOptions

type AgentsMDOptions struct {
	// RepoURL is the linked repository URL (optional)
	RepoURL string

	// TeamID is the team identifier (optional)
	TeamID string

	// Endpoint is the SageOx API endpoint (optional, defaults to production)
	Endpoint string

	// RepoType is the type of repository ("ledger" or "team-context")
	RepoType string
}

AgentsMDOptions configures AGENTS.md generation.

type CheckoutOptions

type CheckoutOptions struct {
	// Depth sets shallow clone depth (0 = full clone)
	Depth int

	// Branch specifies the branch to checkout (empty = default branch)
	Branch string

	// SingleBranch clones only the specified branch (or default branch if Branch is empty)
	SingleBranch bool

	// PartialClone enables --filter=blob:none (treeless clone, blobs fetched on demand)
	PartialClone bool

	// Sparse enables --sparse (sparse checkout mode)
	Sparse bool

	// NoCheckout enables --no-checkout (skip working tree creation after clone)
	NoCheckout bool
}

CheckoutOptions configures the checkout behavior

type CredentialFetcher

type CredentialFetcher func() (*GitCredentials, error)

CredentialFetcher is a function that fetches new credentials from the API. Returns the new credentials or an error. The caller is responsible for providing authentication context.

type CredentialStatus

type CredentialStatus struct {
	// Valid is true if credentials exist and are not expired
	Valid bool
	// Reason describes why credentials are invalid (empty if valid)
	Reason string
	// RepoCount is the number of repos in credentials (0 if invalid)
	RepoCount int
	// ExpiresAt is when credentials expire (zero if unknown)
	ExpiresAt time.Time
	// TimeUntilExpiry is the duration until expiry (negative if expired)
	TimeUntilExpiry time.Duration
}

CredentialStatus represents the current state of git credentials

func CheckCredentialStatusForEndpoint

func CheckCredentialStatusForEndpoint(endpointURL string) CredentialStatus

CheckCredentialStatusForEndpoint checks the status of credentials for a specific endpoint.

func EnsureValidCredentialsForEndpoint

func EnsureValidCredentialsForEndpoint(endpointURL string, fetcher CredentialFetcher) (CredentialStatus, error)

EnsureValidCredentialsForEndpoint checks credentials for a specific endpoint and refreshes if needed. This is the endpoint-aware version that should be used in multi-endpoint setups.

func (CredentialStatus) FormatExpiry

func (s CredentialStatus) FormatExpiry() string

FormatExpiry returns a human-readable expiry string

func (CredentialStatus) NeedsRefresh

func (s CredentialStatus) NeedsRefresh() bool

NeedsRefresh returns true if credentials need to be refreshed

type GitCredentials

type GitCredentials struct {
	Token     string               `json:"token"`
	ServerURL string               `json:"server_url"`
	Username  string               `json:"username"`
	ExpiresAt time.Time            `json:"expires_at"`
	Repos     map[string]RepoEntry `json:"repos,omitempty"` // indexed by team ID
}

GitCredentials holds the Git PAT and repo URLs for git/LFS operations. This is SEPARATE from OAuth (auth.json). The PAT is used for:

  • LFS blob upload (HTTP Basic auth)
  • git push/pull (embedded in remote URL as oauth2:<token>)

The PAT is refreshed lazily by the daemon when near expiry. Repos contains team-context repos indexed by team ID. Ledger URLs are NOT stored here.

func LoadCredentialsForEndpoint

func LoadCredentialsForEndpoint(endpointURL string) (*GitCredentials, error)

LoadCredentialsForEndpoint loads git credentials for a specific endpoint. Tries filesystem first (fast, no OS prompts), falls back to OS keychain.

func (*GitCredentials) AddRepo

func (c *GitCredentials) AddRepo(entry RepoEntry)

AddRepo adds or updates a repo entry

func (*GitCredentials) GetRepo

func (c *GitCredentials) GetRepo(name string) *RepoEntry

GetRepo returns a repo entry by name, or nil if not found

func (*GitCredentials) IsExpired

func (c *GitCredentials) IsExpired() bool

IsExpired checks if the credentials are expired

type HelperConfig added in v0.8.0

type HelperConfig struct {
	// Host is the git server host (e.g. "git.sageox.ai"). The credential
	// scope is set to "https://<host>" so the helper only fires for that
	// exact host — never for third-party remotes that may live in the
	// same repo (forks, upstream pointers).
	Host string

	// Command is the shell command git invokes for credential resolution.
	// Conventionally prefixed with "!" (per git-credential helper docs)
	// and includes the absolute path to the ox binary when available.
	// See cmd/ox/git_credential_helper.go:HelperCommandString.
	Command string
}

HelperConfig holds the parameters needed to install an ox-managed git credential helper into a single repo's .git/config. The shell command the helper invokes is produced by the cmd/ox layer (see HelperCommandString) and threaded down here so internal/gitserver doesn't have to know the path to the running ox binary.

type PATLivenessResult added in v0.6.0

type PATLivenessResult struct {
	// Valid is true if the PAT authenticated successfully
	Valid bool
	// Reason describes the failure (empty if valid)
	Reason string
	// Skipped is true if the check couldn't run (no creds, no repo URL)
	Skipped bool
}

PATLivenessResult describes the outcome of a PAT liveness check.

func ValidatePATLiveness added in v0.6.0

func ValidatePATLiveness(ctx context.Context, creds *GitCredentials) PATLivenessResult

ValidatePATLiveness probes the git server with the stored PAT to verify it actually authenticates. Uses `git ls-remote` against a known repo URL with the PAT provided via GIT_ASKPASS (never on the command line). This only requires basic git access (no extra API scopes like read_api).

If the token is valid, git ls-remote returns refs (exit 0). If the token is expired/revoked, git returns exit 128 with a 401 error.

Requires at least one repo URL in credentials to probe against. Timeout should be kept short (3s) so callers (doctor, status) stay responsive. offline-safe: returns Skipped when no credentials or repo URLs available

type RefreshResult

type RefreshResult struct {
	// Refreshed is true if new credentials were fetched and saved
	Refreshed bool
	// Skipped is true if refresh was skipped (credentials still valid)
	Skipped bool
	// Error is set if refresh failed (old credentials preserved)
	Error error
	// Status is the credential status after refresh attempt
	Status CredentialStatus
}

RefreshResult describes the outcome of a credential refresh attempt

func RefreshCredentialsForEndpoint

func RefreshCredentialsForEndpoint(endpointURL string, fetcher CredentialFetcher, force bool) RefreshResult

RefreshCredentialsForEndpoint checks and refreshes credentials for a specific endpoint. This is the endpoint-aware version that saves credentials to the correct per-endpoint file.

type RepoEntry

type RepoEntry struct {
	Name   string `json:"name"`              // display name (e.g., "Ox CLI Test")
	Type   string `json:"type"`              // "team-context"
	URL    string `json:"url"`               // git clone URL
	TeamID string `json:"team_id,omitempty"` // stable team identifier (e.g., "team_jij1bg2btu")
	Slug   string `json:"slug,omitempty"`    // kebab-case team slug (server-provided)
}

RepoEntry represents a single git repository from the credentials API. NOTE: GET /api/v1/cli/repos only returns team-context repos, not ledgers. Ledger URLs come from GET /api/v1/repos/{repo_id}/ledger-status separately.

func (RepoEntry) StableID

func (r RepoEntry) StableID() string

StableID returns the stable team identifier (team_xxx) for path construction and lookups.

type TwoPhaseCloneResult added in v0.3.0

type TwoPhaseCloneResult struct {
	ManifestConfig *manifest.ManifestConfig
	SparsePaths    []string
}

TwoPhaseCloneResult holds the result of a two-phase team context clone.

func TwoPhaseClone added in v0.3.0

func TwoPhaseClone(ctx context.Context, cloneURL, repoPath string) (*TwoPhaseCloneResult, error)

TwoPhaseClone performs a two-phase partial clone for team context repos.

Phase 1: Clone with --filter=blob:none --depth=1 --sparse --no-checkout, materialize only .sageox/ to read the manifest.

Phase 2: Read manifest, compute sparse set, apply sparse checkout to materialize only allowed paths. Unshallow for pull --rebase compatibility.

This function is used by both the daemon (normal path) and the CLI (doctor fallback when daemon unavailable).

cloneURL MUST be bare (no embedded oauth2:TOKEN userinfo). Credentials are resolved via the ox credential helper — installed as argv flags on the phase-1 clone and persisted into .git/config afterward. Passing a token-embedded URL would leak the PAT into .git/config (forbidden by ox-eeqi) without changing behavior.

TODO(investigate): go-git v6 has native support for this entire sequence:

  • CloneOptions.Filter, Depth, NoCheckout, SingleBranch for phase 1
  • CheckoutOptions.SparseCheckoutDirectories for phase 2 This could replace 6 sequential exec.Command calls with ~10 lines of typed Go. Blockers to verify before migrating: 1. SparseCheckoutDirectories uses --no-cone mode (ox uses file-level patterns) 2. go-git network performance vs native git for clone 3. credential injection (URL-embedded tokens) works correctly

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL