git

package
v0.32.2 Latest Latest
Warning

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

Go to latest
Published: Oct 18, 2026 License: Apache-2.0 Imports: 25 Imported by: 0

Documentation

Overview

ETH-1 "sync-on-resume": reconcile the git state of a workspace container and report it as a single JSON object.

The report is consumed by the platform (sprout-foundry) when a stopped workspace machine is resumed, and by the browser tab through the daemon's GET /api/sync endpoint and the "sync" field of /api/bootstrap. The JSON shape below is a PINNED CONTRACT — field names, nesting and zero-values must not change without coordinating with the platform parser.

Everything here is intentionally dependency-free: it shells out to git, touches no configuration, no LLM and no agent state, so the same entry point serves the CLI (`sprout sync`) and the daemon (running in-container as root against /work) with identical output.

Index

Constants

View Source
const (
	// SyncPullNotAttempted is used when no pull was requested at all
	// (attemptPull=false, or the directory is not a git repository).
	SyncPullNotAttempted = "not_attempted"
	// SyncPullUpToDate: `git pull --ff-only` ran and HEAD already matched
	// the upstream.
	SyncPullUpToDate = "up_to_date"
	// SyncPullFastForwarded: `git pull --ff-only` ran and moved HEAD
	// forward to the upstream.
	SyncPullFastForwarded = "fast_forwarded"
	// SyncPullSkippedNoUpstream: pull was requested but the branch has no
	// @{upstream} configured.
	SyncPullSkippedNoUpstream = "skipped_no_upstream"
	// SyncPullSkippedDirty: pull was requested but tracked files have
	// uncommitted changes — dirty work is never put at risk.
	SyncPullSkippedDirty = "skipped_dirty"
	// SyncPullError: git refused the pull (diverged history, an untracked
	// file would be clobbered, timeout, …). git's own message is preserved
	// in SyncPull.Error; we never work around a refusal.
	SyncPullError = "error"
)

Pull result values (pull.result in the JSON contract).

View Source
const AssistedByKey = "Assisted-by"

AssistedByKey is the git trailer key that records AI assistance on a commit. Sprout never writes Co-Authored-By for a tool: GitHub renders those as contributors, while a plain trailer states the fact without advertising it.

View Source
const SproutToolName = "sprout"

SproutToolName is the tool name sprout writes in its own Assisted-by trailers.

Variables

View Source
var ErrNoGitHubAuth = errors.New("no GitHub authentication available")

ErrNoGitHubAuth is the sentinel returned when neither a GH_TOKEN nor the gh CLI are available for PR creation. The wrapped error message contains the exact gh pr create command the user can run manually.

View Source
var GetDefaultBranch = func(ctx context.Context, repoDir string) (string, error) {
	cmd := exec.CommandContext(ctx, "git", "symbolic-ref", "--short", "refs/remotes/origin/HEAD")
	cmd.Dir = repoDir
	out, err := cmd.Output()
	if err != nil {
		return "main", nil
	}
	return strings.TrimSpace(string(out)), nil
}

GetDefaultBranch returns the default branch name for the repo rooted at repoDir. By default it reads

git symbolic-ref refs/remotes/origin/HEAD

and falls back to "main". Tests may override this variable.

View Source
var GitHubAPIBaseURL = "https://api.github.com"

GitHubAPIBaseURL is the base URL for the GitHub REST API. Tests may override it to point at an httptest server.

View Source
var PushBranch = func(ctx context.Context, repoDir, head string) error {
	cmd := exec.CommandContext(ctx, "git", "push", "-u", "origin", head)
	cmd.Dir = repoDir
	out, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("push branch %q to origin: %w (output: %s)", head, err, strings.TrimSpace(string(out)))
	}
	return nil
}

PushBranch executes "git push -u origin <head>". Tests may override this variable to avoid network access.

NOTE: intentionally exported only for testability — follow the same pattern as RunGhCommand / GetDefaultBranch. Not safe for concurrent writes; override in init() or TestMain, not mid-flight.

View Source
var RunGhCommand = func(ctx context.Context, dir string, args ...string) ([]byte, error) {
	cmd := exec.CommandContext(ctx, "gh", args...)
	cmd.Dir = dir
	out, err := cmd.CombinedOutput()
	return out, err
}

RunGhCommand is the function used to invoke the gh CLI. The default implementation simply calls exec.CommandContext("gh", args...). Tests may override this variable to avoid a real binary.

NOTE: this variable is intentionally not safe for concurrent writes — override it in init() or TestMain, not mid-flight.

View Source
var SyncGitTimeout = 10 * time.Second

SyncGitTimeout bounds each individual git invocation. It is a var (not a const) so tests — or a caller with unusual latency requirements — can tighten or relax it.

Functions

func AddAllAndCommit

func AddAllAndCommit(dir, message string, timeoutSeconds int) error

AddAllAndCommit commits all staged changes inside dir with the provided message (non-interactive). dir MUST be non-empty.

func AddAndCommitFile

func AddAndCommitFile(dir, newFilename, message string) error

AddAndCommitFile stages the specified file and commits it with the given message inside dir. dir MUST be non-empty — passing "" would let the operation hit the test process's CWD (the host repo on developer machines) and is refused by SafeGitCmd under `go test`.

func AppendAssistedBy added in v0.30.0

func AppendAssistedBy(message string, assistants ...Assistant) string

AppendAssistedBy appends one Assisted-by trailer per distinct assistant to message, git-trailer compatible: when the message already ends in a trailer block (a final paragraph, after the subject, made only of "Token: value" lines) the new trailers join it; otherwise they start a new block after a blank line. Trailers already present (an amend or a re-run) are not duplicated, and assistants without a known provider/model are skipped. A message with no valid assistants is returned unchanged.

func CheckStagedChanges

func CheckStagedChanges(dir string) error

CheckStagedChanges verifies if there are staged changes in the given directory. If dir is empty, it runs in the process CWD.

func CleanCommitMessage

func CleanCommitMessage(message string) string

CleanCommitMessage cleans up LLM-generated commit messages

func CommittedFilePaths added in v0.16.18

func CommittedFilePaths(workDir string) (map[string]bool, error)

CommittedFilePaths returns a set of absolute filesystem paths for files that are tracked by git AND whose working-tree content is identical to HEAD (committed-clean). This is the batch equivalent of IsFileContentCommitted: instead of two subprocess calls per file, it runs just two commands total (git ls-files + git diff --name-only HEAD) and builds the full set in one pass.

All git commands are run with cmd.Dir=workDir so the function does not depend on the process CWD — it resolves the repo containing workDir regardless of where the agent process is running.

Callers use this to identify working-tree deltas caused by git operations (merge, checkout, reset, pull) that should NOT be recorded as recoverable agent edits — a file whose post-operation content matches HEAD was aligned to a committed state by git, not edited by the agent, so there is nothing legitimate to "recover" back to.

Returns (nil, nil) when workDir is not inside a git repository — no git protection applies and callers should record all deltas.

func GetFileGitPath

func GetFileGitPath(filename string) (string, error)

GetFileGitPath returns the path of the given filename relative to the Git repository root.

func GetGitRemoteURL

func GetGitRemoteURL() (string, error)

GetGitRemoteURL returns the remote URL of the current Git repository.

func GetGitRootDir

func GetGitRootDir() (string, error)

GetGitRootDir returns the absolute path to the root directory of the current Git repository.

func GetGitStatus

func GetGitStatus() (currentBranch string, uncommittedChanges int, stagedChanges int, err error)

GetGitStatus returns the current branch, number of uncommitted changes, and number of staged changes.

func GetRecentFileLog

func GetRecentFileLog(filePath string, limit int) (string, error)

GetRecentFileLog returns a short summary of recent commits for a file

func GetRecentTouchedFiles

func GetRecentTouchedFiles(numCommits int) ([]string, error)

GetRecentTouchedFiles returns a de-duplicated list of files touched in the last N commits

func GetStagedChanges

func GetStagedChanges() (string, error)

GetStagedChanges returns detailed information about staged changes in the repository.

func GetStagedDiff

func GetStagedDiff(dir string) (string, error)

GetStagedDiff returns the diff of staged changes in the given directory. If dir is empty, it runs in the process CWD.

func GetUncommittedChanges

func GetUncommittedChanges() (string, error)

GetUncommittedChanges returns detailed information about uncommitted changes in the repository.

func IsFileContentCommitted added in v0.16.18

func IsFileContentCommitted(filePath string) (bool, error)

IsFileContentCommitted reports whether filePath is tracked by git and its working-tree content matches HEAD (committed-clean). Outside a repository, or for untracked files, it reports false with no error.

func NormalizeShortTitle

func NormalizeShortTitle(raw string) string

func ParseCommitMessage

func ParseCommitMessage(commitMessage string) (string, string, error)

ParseCommitMessage parses a commit message into note and description

func ParseGitHubRemoteURL added in v0.16.12

func ParseGitHubRemoteURL(remoteURL string) (owner, repo string, err error)

ParseGitHubRemoteURL extracts the owner and repository name from a GitHub remote URL. It supports both HTTPS and SSH formats:

https://github.com/owner/repo.git     -> owner, repo
git@github.com:owner/repo.git         -> owner, repo

Returns an error for non-GitHub remotes or unrecognised formats.

func PerformGitCommit

func PerformGitCommit(dir, message string) error

PerformGitCommit executes the git commit command safely using stdin inside dir. dir MUST be non-empty — see SafeGitCmd for why an empty dir is refused under `go test`.

func SafeGitCmd added in v0.16.4

func SafeGitCmd(dir string, args ...string) *exec.Cmd

SafeGitCmd constructs an *exec.Cmd for a git invocation with built-in test-mode safety:

  • Production: behaves like exec.Command("git", args...) with cmd.Dir set to dir when dir != "".
  • Test mode (`go test`): when dir is empty AND the subcommand is mutating, returns a Cmd that points at an unrunnable sentinel path so the test fails loudly instead of mutating the host repo.

Pass dir="" only for read-only commands (status, diff, log, …) when you intentionally want the process CWD. Mutating commands MUST supply a working directory.

func SproutVersion added in v0.30.0

func SproutVersion() string

SproutVersion returns the build version used in Assisted-by trailers.

func StripToolAttribution added in v0.30.0

func StripToolAttribution(message string) string

StripToolAttribution removes Co-Authored-By trailers and "Generated with …" footers from model-generated commit text.

func TruncateRunes

func TruncateRunes(s string, max int) string

func WrapText

func WrapText(text string, lineLength int) string

Types

type Assistant added in v0.30.0

type Assistant struct {
	Tool     string
	Version  string
	Provider string
	Model    string
}

Assistant identifies one tool+model combination that helped produce a commit. It renders as

Assisted-by: <tool>[/<version>] (<provider>/<model>)

func SproutAssistant added in v0.30.0

func SproutAssistant(provider, model string) Assistant

SproutAssistant returns the Assistant for this sprout build running provider/model. Release builds report the semantic version without the leading "v"; source builds report "dev".

func (Assistant) Trailer added in v0.30.0

func (a Assistant) Trailer() string

Trailer renders the full trailer line.

func (Assistant) Value added in v0.30.0

func (a Assistant) Value() string

Value renders the trailer value, e.g. "sprout/0.30.0 (deepinfra/deepseek-ai/DeepSeek-V4.1-Flash)".

type CommitExecutor

type CommitExecutor struct {
	Client           api.ClientInterface
	UserMessage      string
	UserInstructions string
	// Dir is the working directory for git commands. If empty, the current directory is used.
	Dir string

	// AttributeMessage, when set, rewrites the final commit message before
	// the commit is created — used to append the opt-in Assisted-by
	// trailer. generated is true when the message came from the LLM
	// rather than from the caller.
	AttributeMessage func(message string, generated bool) string
	// contains filtered or unexported fields
}

CommitExecutor provides methods for executing git commits with message generation.

func NewCommitExecutor

func NewCommitExecutor(client api.ClientInterface, userMessage, userInstructions string) *CommitExecutor

NewCommitExecutor creates a new commit executor with the given configuration.

func NewCommitExecutorInDir

func NewCommitExecutorInDir(client api.ClientInterface, userMessage, userInstructions, dir string) *CommitExecutor

NewCommitExecutorInDir creates a new commit executor that runs git commands in the specified directory.

func NewCommitExecutorWithSecurityCheck

func NewCommitExecutorWithSecurityCheck(client api.ClientInterface, userMessage, userInstructions string, secretCheck SecretCheckHandler) *CommitExecutor

NewCommitExecutorWithSecurityCheck creates a CommitExecutor with a security check callback.

func (*CommitExecutor) ExecuteCommit

func (e *CommitExecutor) ExecuteCommit() (string, error)

ExecuteCommit performs the complete commit operation: 1. Gets staged files and validates there are changes 2. Gets the current branch name 3. Parses file changes into structured format 4. Gets the staged diff 5. Generates commit message (using provided message, LLM, or fallback) 6. Creates commit using a secure temp file 7. Returns the commit hash

This function is designed to be reusable by both the commit tool and commit flow.

type CommitFileChange

type CommitFileChange struct {
	Status string
	Path   string
}

CommitFileChange describes a staged file with git status code.

type CommitMessageOptions

type CommitMessageOptions struct {
	Diff             string
	Branch           string
	FileChanges      []CommitFileChange
	UserInstructions string
}

CommitMessageOptions configures commit message generation behavior.

type CommitMessageResult

type CommitMessageResult struct {
	Message      string
	ApproxTokens int
	Warnings     []string
}

CommitMessageResult contains generated message and diagnostics.

func GenerateCommitMessageFromStagedDiff

func GenerateCommitMessageFromStagedDiff(client api.ClientInterface, opts CommitMessageOptions) (*CommitMessageResult, error)

GenerateCommitMessageFromStagedDiff generates commit text using the same two-pass title+description algorithm used by /commit.

type CommitOptions

type CommitOptions struct {
	SkipPrompt   bool
	AllowSecrets bool
	Model        string
}

CommitOptions contains options for commit operations

type CommitSecurityResult

type CommitSecurityResult struct {
	HasConcerns bool
	Concerns    []security.DetectedSecret
}

CommitSecurityResult holds the result of security checking staged files.

func CheckStagedFilesForSecurityCredentials

func CheckStagedFilesForSecurityCredentials(logger *utils.Logger, dir string) CommitSecurityResult

CheckStagedFilesForSecurityCredentials checks staged files for security credentials and returns a detailed result with the specific concerns found.

type FileCommitInfo added in v0.21.5

type FileCommitInfo struct {
	// CommittedClean: tracked, and the working-tree content matches HEAD.
	CommittedClean bool
	// LastCommit is the commit time of the latest commit touching the
	// file (zero when untracked or unknown).
	LastCommit time.Time
}

FileCommitInfo describes a file's state relative to git HEAD.

func FileCommitState added in v0.21.5

func FileCommitState(filePath string) (FileCommitInfo, error)

FileCommitState inspects filePath in the repository that contains it. The repository is found from the file's own directory, not the process CWD: the two differ whenever sprout runs outside the workspace (daemon, WebUI), and resolving from the CWD silently reported every file as uncommitted there — disabling the guards that keep a revert from undoing committed work.

type PullRequestRequest added in v0.16.12

type PullRequestRequest struct {
	Title     string // PR title (required)
	Body      string // PR body; synthesised from commits when empty
	Base      string // target branch; default = repo default branch
	Head      string // source branch; default = current HEAD branch
	Draft     bool
	Reviewers []string // usernames (API-only; ignored by gh CLI fallback)
}

PullRequestRequest describes a pull request to create.

type PullRequestResult added in v0.16.12

type PullRequestResult struct {
	URL    string `json:"html_url"`
	Number int    `json:"number"`
	State  string `json:"state"` // "open" or "closed"
}

PullRequestResult holds the outcome of a successful PR creation.

func CreatePullRequest added in v0.16.12

func CreatePullRequest(ctx context.Context, repoDir string, req PullRequestRequest) (*PullRequestResult, error)

CreatePullRequest creates a pull request on GitHub for the given repoDir.

Resolution order:

  1. GitHub REST API (credential store → GH_TOKEN env var)
  2. gh pr create shell-out (fallback when no token available)
  3. Structured error with the exact gh command the user can run manually

If req.Head is empty, the current branch name is used. If req.Base is empty, the repository's default branch is inferred. If req.Body is empty, a body is synthesised from commit messages between base and head.

type SecretCheckHandler

type SecretCheckHandler func(securityResult CommitSecurityResult) bool

SecretCheckHandler is a callback for handling detected secrets in commit flow. It receives the security result and returns whether to proceed with the commit. Return true to proceed, false to abort.

type SyncLastCommit added in v0.17.20

type SyncLastCommit struct {
	Sha       string `json:"sha"`
	Subject   string `json:"subject"`
	Author    string `json:"author"`
	Timestamp string `json:"timestamp"`
}

SyncLastCommit is the "last_commit" object of the report. Empty object when the repository has no commits (it is a value type, never null).

type SyncPull added in v0.17.20

type SyncPull struct {
	Attempted bool   `json:"attempted"`
	Result    string `json:"result"`
	Error     string `json:"error"`
}

SyncPull is the "pull" object of the report.

Attempted is true iff a `git pull --ff-only` subprocess actually ran; every skipped_* outcome therefore reports Attempted=false, and only up_to_date / fast_forwarded / error report Attempted=true.

type SyncReport added in v0.17.20

type SyncReport struct {
	InGitRepo  bool           `json:"in_git_repo"`
	Branch     string         `json:"branch"`
	DirtyFiles []string       `json:"dirty_files"`
	Ahead      int            `json:"ahead"`
	Behind     int            `json:"behind"`
	LastCommit SyncLastCommit `json:"last_commit"`
	Pull       SyncPull       `json:"pull"`
}

SyncReport is the pinned ETH-1 JSON contract:

{
  "in_git_repo": true, "branch": "main",
  "dirty_files": ["cmd/foo.go", "notes.txt"],
  "ahead": 1, "behind": 2,
  "last_commit": {"sha": "...", "subject": "...", "author": "...", "timestamp": "..."},
  "pull": {"attempted": true, "result": "fast_forwarded", "error": ""}
}

func RunSync added in v0.17.20

func RunSync(ctx context.Context, repoDir string, attemptPull bool) (SyncReport, error)

RunSync inspects the git repository containing repoDir and returns the ETH-1 reconciliation report.

attemptPull=true additionally attempts a non-destructive `git pull --ff-only`. The fetch is implicit in that pull: when attemptPull is false NO network/remote access happens and ahead/behind reflect the last-known remote-tracking state. Pull is attempted only when the branch has an upstream AND no tracked file is dirty (untracked-only is allowed — git itself refuses a pull that would clobber them, and that refusal is reported as result "error" with git's message rather than worked around).

NEVER destructive: no reset, checkout, stash, clean or force anything.

Error handling follows the contract: "not a git repository" is a reportable state (InGitRepo=false, zeroed fields, nil error); only catastrophic failures (unreadable directory, git binary failure, corrupt index) return a non-nil error. Field-level git failures degrade to zero values instead of failing the whole report.

Jump to

Keyboard shortcuts

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