git

package module
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: Aug 16, 2026 License: MIT Imports: 20 Imported by: 0

README

git

Git and GitHub handlers for TinyWasm projects. Extracted from tinywasm/devflow so consumers (e.g. tinywasm/sitepub) don't drag the whole devflow dependency tree.

Features

  • Git handler (NewGit): add/commit/tag/push, clone/pull/fetch, tags, .gitignore entries.
  • GitHub client (NewGitHub): repos, releases, gh CLI integration.
  • GitHub auth: OAuth Device Flow (NewGitHubOAuth), PAT recovery (NewGitHubPATAuth), EnsureGHSession.
  • Keyring (NewKeyring): secure token storage via the system keyring.
  • Secrets (GitHubSecrets): repository secrets via gh CLI.
  • TestCache (NewTestCache): git-based test cache used by gotest.
  • Helpers: CompareVersions, commit message builders, WorkTreeDirtyBeyond, publish objector types (PublishContext, PublishAction).

Usage

import "github.com/tinywasm/git"

g, err := git.NewGit()
if err != nil {
    log.Fatal(err)
}

Installing tools

Use goinstall to (re)build the TinyWasm CLI tools from tinywasm/devflow.

License

MIT

Documentation

Index

Constants

View Source
const (
	// DepsCommitPrefix is the prefix for dependency update commit messages
	DepsCommitPrefix = "deps: "
	// CauseLinePrefix is the prefix for the root cause line in commit messages
	CauseLinePrefix = "cause: "
)
View Source
const (
	ObjectionCodejobSession = "codejob session active"
	ObjectionOtherReplaces  = "other replaces exist"
	ObjectionPlanPending    = "docs/PLAN.md pending"
	ObjectionDirtyTree      = "dirty tree"
)
View Source
const DevflowOAuthClientID = "Ov23lijHU2vxBCpShn1Q"

DevflowOAuthClientID is the OAuth App Client ID for devflow.

IMPORTANT: This Client ID is intentionally hardcoded and is NOT a secret. OAuth Client IDs are public identifiers (like a username, not a password). The Client Secret is NEVER included in the code - Device Flow doesn't need it. This is the standard approach used by CLI tools like gh, goreleaser, hub, etc.

The OAuth App is registered under a personal GitHub account (not organization). Manage the app at: https://github.com/settings/developers -> OAuth Apps -> devflow

Variables

View Source
var ErrDirtyWorkTree = errors.New("cannot pull: working tree has uncommitted changes")

ErrDirtyWorkTree is returned by Pull when the working tree has uncommitted changes.

Functions

func BuildDepsCommitMessage added in v0.0.2

func BuildDepsCommitMessage(bumps []DepBump, rootCause string) string

BuildDepsCommitMessage constructs a standard dependency update commit message. rootCause is the original commit message that triggered the cascade.

func CompareVersions added in v0.0.2

func CompareVersions(v1, v2 string) int

CompareVersions compares two semantic version strings (e.g., "v1.2.3"). It returns -1 if v1 < v2, 0 if v1 == v2, 1 if v1 > v2. It handles "v" prefix gracefully.

func EnsureGHSession added in v0.0.2

func EnsureGHSession(runner Runner) error

EnsureGHSession verifies the gh session and, if expired, restores it non-interactively from the keyring PAT via `gh auth login --with-token`. No-op when the session is healthy. The probe and verification run through runner so tests can inject a double and never touch a real gh CLI; restore itself always uses the real process (only reached when the probe genuinely fails, i.e. never under an injected test Runner that reports success).

func FormatCommitMessage added in v0.0.2

func FormatCommitMessage(message string) string

FormatCommitMessage ensures the message is trimmed.

func ValidateCommitMessage added in v0.0.2

func ValidateCommitMessage(message string) error

ValidateCommitMessage ensures that a commit message is provided and is valid. It trims whitespace and returns an error if the message is empty.

func ValidateShellSafeMessage added in v0.0.2

func ValidateShellSafeMessage(message string) string

ValidateShellSafeMessage provides a warning if the message contains characters that might need escaping in certain shells (like backticks, dollar signs, or single quotes) if it were to be used in a shell script, even though exec.Command is safe.

func WorkTreeDirtyBeyond added in v0.0.2

func WorkTreeDirtyBeyond(git GitClient, allowed ...string) (bool, error)

WorkTreeDirtyBeyond returns true if the git worktree has changes beyond the allowed files. It ignores .env and .gitignore files automatically.

Types

type DepBump added in v0.0.2

type DepBump struct {
	ModulePath string
	OldVersion string
	NewVersion string
}

DepBump represents a single module dependency update

type Git

type Git struct {
	// contains filtered or unexported fields
}

Git handler for Git operations

func NewGit added in v0.0.2

func NewGit() (*Git, error)

NewGit creates a new Git handler and verifies git is available

func (*Git) Add added in v0.0.2

func (g *Git) Add() error

Add adds all changes to staging

func (*Git) CheckRemoteAccess added in v0.0.2

func (g *Git) CheckRemoteAccess() error

CheckRemoteAccess verifies connectivity to the remote repository. If an auth error is detected and an authRetrier is configured, it triggers the Device Flow auth automatically and retries once.

func (*Git) Clone added in v0.0.2

func (g *Git) Clone(repoURL string) (alreadyPresent bool, err error)

Clone clones repoURL into the working copy. If the destination already contains a repository, it is NOT an error: it does nothing and returns alreadyPresent == true, so an unattended publisher can call Clone unconditionally at startup.

func (*Git) Commit added in v0.0.2

func (g *Git) Commit(message string) (bool, error)

Commit creates a commit with the given message Returns true if a commit was created

func (*Git) CommitPaths added in v0.0.2

func (g *Git) CommitPaths(message string, paths ...string) (bool, error)

CommitPaths adds specific paths and creates a commit. It returns true if a commit was created, false if no changes in those paths. This is safer than Add() + Commit() as it only touches specific files.

func (*Git) CreateTag added in v0.0.2

func (g *Git) CreateTag(tag string) (bool, error)

CreateTag creates a new tag

func (*Git) DiffShortStat added in v0.0.2

func (g *Git) DiffShortStat() (string, error)

DiffShortStat returns the output of git diff HEAD --shortstat

func (*Git) Fetch added in v0.0.2

func (g *Git) Fetch() error

Fetch fetches refs from remote without touching the working tree.

func (*Git) GenerateNextTag added in v0.0.2

func (g *Git) GenerateNextTag() (string, error)

GenerateNextTag calculates the next semantic version

func (*Git) GetConfigUserEmail added in v0.0.2

func (g *Git) GetConfigUserEmail() (string, error)

GetConfigUserEmail gets the git user.email

func (*Git) GetConfigUserName added in v0.0.2

func (g *Git) GetConfigUserName() (string, error)

GetConfigUserName gets the git user.name

func (*Git) GetLatestTag added in v0.0.2

func (g *Git) GetLatestTag() (string, error)

GetLatestTag gets the latest tag

func (*Git) GitIgnoreAdd added in v0.0.2

func (g *Git) GitIgnoreAdd(entry string) error

GitIgnoreAdd adds entry to .gitignore if shouldWrite allows and entry not present. Creates .gitignore if it doesn't exist.

func (*Git) HasChanges added in v0.0.2

func (g *Git) HasChanges() (bool, error)

HasChanges checks if there are staged changes

func (*Git) HasPendingChanges added in v0.0.2

func (g *Git) HasPendingChanges() (bool, error)

HasPendingChanges returns true if there are uncommitted or unpushed changes. Used by CodeJob to ensure the file is visible to Jules before dispatching. It ignores changes to .env and .gitignore files.

func (*Git) HasUpstream added in v0.0.2

func (g *Git) HasUpstream() (bool, error)

HasUpstream checks if the branch has upstream

func (*Git) IncrementTag added in v0.0.2

func (g *Git) IncrementTag(tag string) (string, error)

IncrementTag increments a specific tag (e.g., v0.0.12 -> v0.0.13)

func (*Git) InitRepo added in v0.0.2

func (g *Git) InitRepo(dir string) error

InitRepo initializes a new git repository

func (*Git) IsAheadOfRemote added in v0.0.2

func (g *Git) IsAheadOfRemote() (bool, error)

IsAheadOfRemote checks if local branch is ahead of remote

func (*Git) ObjectsToPublish added in v0.0.2

func (g *Git) ObjectsToPublish(_ PublishContext) (PublishAction, string)

func (*Git) Pull added in v0.0.2

func (g *Git) Pull() error

Pull updates the working copy from upstream. Returns ErrDirtyWorkTree if the working tree has uncommitted changes.

func (*Git) Push added in v0.0.2

func (g *Git) Push(message, tag string) (PushResult, error)

Push executes the complete push workflow (add, commit, tag, push) Returns a PushResult and error if any.

func (*Git) PushWithTags added in v0.0.2

func (g *Git) PushWithTags(tag string) (bool, error)

PushWithTags pushes commits and tag

func (*Git) PushWithoutTags added in v0.0.2

func (g *Git) PushWithoutTags() (bool, error)

PushWithoutTags pushes commits without pushing tags

func (*Git) SetAuthRetrier added in v0.0.2

func (g *Git) SetAuthRetrier(a GitHubAuthenticator)

SetAuthRetrier injects an authenticator to use for auto-recovery on access errors

func (*Git) SetLog added in v0.0.2

func (g *Git) SetLog(fn func(...any))

SetLog sets the logger function

func (*Git) SetRootDir added in v0.0.2

func (g *Git) SetRootDir(path string)

SetRootDir sets the root directory for git operations

func (*Git) SetShouldWrite added in v0.0.2

func (g *Git) SetShouldWrite(f func() bool)

SetShouldWrite sets a function that determines if Git write operations (like updating .gitignore) should be allowed.

func (*Git) SetUserConfig added in v0.0.2

func (g *Git) SetUserConfig(name, email string) error

SetUserConfig sets git user name and email

func (*Git) StatusPorcelain added in v0.0.2

func (g *Git) StatusPorcelain() (string, error)

StatusPorcelain returns the output of git status --porcelain

func (*Git) TagExists added in v0.0.2

func (g *Git) TagExists(tag string) (bool, error)

TagExists checks if a tag exists

type GitClient added in v0.0.2

type GitClient interface {
	CheckRemoteAccess() error
	Push(message, tag string) (PushResult, error)
	GetLatestTag() (string, error)
	SetLog(fn func(...any))
	SetShouldWrite(fn func() bool)
	SetRootDir(path string)
	GitIgnoreAdd(entry string) error
	GetConfigUserName() (string, error)
	GetConfigUserEmail() (string, error)
	InitRepo(dir string) error
	Add() error
	Commit(message string) (bool, error)
	CommitPaths(message string, paths ...string) (bool, error)
	CreateTag(tag string) (bool, error)
	PushWithTags(tag string) (bool, error)
	PushWithoutTags() (bool, error)
	HasPendingChanges() (bool, error)
	StatusPorcelain() (string, error)
	DiffShortStat() (string, error)
	GenerateNextTag() (string, error)
	Clone(repoURL string) (alreadyPresent bool, err error)
	Pull() error
	Fetch() error
}

GitClient defines the interface for Git operations.

type GitHub added in v0.0.2

type GitHub struct {
	SecretRunner SecretRunner
	// contains filtered or unexported fields
}

GitHub handler for GitHub operations

func NewGitHub added in v0.0.2

func NewGitHub(logFn func(...any), auth ...GitHubAuthenticator) (*GitHub, error)

NewGitHub creates handler and verifies gh CLI availability. logFn is used to display authentication messages during Device Flow. If not authenticated, it initiates OAuth Device Flow automatically.

func (*GitHub) CreateRelease added in v0.0.2

func (gh *GitHub) CreateRelease(tag string, assets []string, targetRepo string) (string, error)

CreateRelease creates a GitHub Release and uploads assets. If targetRepo is not empty, it uses the --repo flag to publish to that repository.

func (*GitHub) CreateRepo added in v0.0.2

func (gh *GitHub) CreateRepo(owner, name, description, visibility string) error

CreateRepo creates a new empty repository on GitHub If owner is provided, creates repo under that organization

func (*GitHub) DeleteRepo added in v0.0.2

func (gh *GitHub) DeleteRepo(owner, name string) error

DeleteRepo deletes a repository on GitHub. WARNING: This permanently deletes the repository and cannot be undone. Use with caution, primarily for test cleanup.

func (*GitHub) DeleteSecret added in v0.0.2

func (gh *GitHub) DeleteSecret(repo, name string) error

DeleteSecret removes a GitHub Actions secret. gh secret delete NAME --repo=OWNER/REPO

func (*GitHub) GetCurrentUser added in v0.0.2

func (gh *GitHub) GetCurrentUser() (string, error)

GetCurrentUser gets the current authenticated user

func (*GitHub) GetHelpfulErrorMessage added in v0.0.2

func (gh *GitHub) GetHelpfulErrorMessage(err error) string

GetHelpfulErrorMessage returns a helpful message for common errors

func (*GitHub) IsNetworkError added in v0.0.2

func (gh *GitHub) IsNetworkError(err error) bool

IsNetworkError checks if an error is likely a network error

func (*GitHub) ListSecrets added in v0.0.2

func (gh *GitHub) ListSecrets(repo string) ([]string, error)

ListSecrets returns the names of secrets registered in the repo. Values are never accessible via API — GitHub only exposes names. gh secret list --repo=OWNER/REPO --json name --jq '[.[].name]'

func (*GitHub) RepoExists added in v0.0.2

func (gh *GitHub) RepoExists(owner, name string) (bool, error)

RepoExists checks if a repository exists

func (*GitHub) RepoInfo added in v0.0.3

func (gh *GitHub) RepoInfo(repoRef string) (owner, name, visibility string, err error)

repoInfo returns basic information about a repository. If repoRef is empty, it queries the repository in the current directory.

func (*GitHub) SetLog added in v0.0.2

func (gh *GitHub) SetLog(fn func(...any))

SetLog sets the logger function

func (*GitHub) SetSecret added in v0.0.2

func (gh *GitHub) SetSecret(repo, name, value string) error

SetSecret registers a secret in GitHub Actions via gh CLI. The value is passed via stdin — gh CLI encrypts it with the repo's public key before transmitting it. It does not appear in system ps/logs.

func (*GitHub) SetSecretWithScope added in v0.0.2

func (gh *GitHub) SetSecretWithScope(repo, name, value, org, visibility string) error

SetSecretWithScope registers a secret either at the repository level or organization level.

type GitHubAuthHandler added in v0.0.2

type GitHubAuthHandler interface {
	GitHubAuthenticator
	Name() string
}

GitHubAuthHandler defines the interface for GitHub auth as a TUI handler.

type GitHubAuthenticator added in v0.0.2

type GitHubAuthenticator interface {
	EnsureGitHubAuth() error
	SetLog(fn func(...any))
}

GitHubAuthenticator defines the interface for GitHub authentication. This allows mocking authentication in tests.

type GitHubClient added in v0.0.2

type GitHubClient interface {
	SetLog(fn func(...any))
	GetCurrentUser() (string, error)
	RepoExists(owner, name string) (bool, error)
	CreateRepo(owner, name, description, visibility string) error
	DeleteRepo(owner, name string) error
	IsNetworkError(err error) bool
	GetHelpfulErrorMessage(err error) string
	CreateRelease(tag string, assets []string, targetRepo string) (string, error)
}

GitHubClient defines the interface for GitHub operations. This allows mocking the GitHub dependency in tests.

type GitHubOAuth added in v0.0.2

type GitHubOAuth struct {
	// contains filtered or unexported fields
}

GitHubOAuth handles GitHub authentication and token management via Device Flow

func NewGitHubOAuth added in v0.0.2

func NewGitHubOAuth() *GitHubOAuth

NewGitHubOAuth creates a new GitHub authentication handler

func (*GitHubOAuth) DeviceFlowAuth added in v0.0.2

func (a *GitHubOAuth) DeviceFlowAuth(kr *Keyring) (string, error)

DeviceFlowAuth initiates GitHub OAuth Device Flow and returns an access token

func (*GitHubOAuth) EnsureGitHubAuth added in v0.0.2

func (a *GitHubOAuth) EnsureGitHubAuth() error

EnsureGitHubAuth checks if GitHub is authenticated via keyring, and if not, initiates Device Flow

func (*GitHubOAuth) Name added in v0.0.2

func (a *GitHubOAuth) Name() string

Name returns the handler name for TUI display.

func (*GitHubOAuth) SetLog added in v0.0.2

func (a *GitHubOAuth) SetLog(fn func(...any))

SetLog sets the logger function

type GitHubPATAuth added in v0.0.2

type GitHubPATAuth struct {
	// contains filtered or unexported fields
}

GitHubPATAuth manages the GitHub PAT via the system keyring. It is used to recover the gh CLI session non-interactively.

func NewGitHubPATAuth added in v0.0.2

func NewGitHubPATAuth() (*GitHubPATAuth, error)

NewGitHubPATAuth creates a GitHubPATAuth with an initialized keyring.

func (*GitHubPATAuth) EnsureGitHubAuth added in v0.0.2

func (a *GitHubPATAuth) EnsureGitHubAuth() error

EnsureGitHubAuth fulfills the GitHubAuthenticator interface.

func (*GitHubPATAuth) EnsureToken added in v0.0.2

func (a *GitHubPATAuth) EnsureToken() (string, error)

EnsureToken returns the PAT from the environment or keyring; if absent, prompts once and persists.

func (*GitHubPATAuth) HasToken added in v0.0.2

func (a *GitHubPATAuth) HasToken() bool

HasToken returns true if the GitHub PAT is already stored in the environment or keyring.

func (*GitHubPATAuth) Reset added in v0.0.2

func (a *GitHubPATAuth) Reset() error

Reset removes the GitHub PAT from the keyring.

func (*GitHubPATAuth) SetLog added in v0.0.2

func (a *GitHubPATAuth) SetLog(fn func(...any))

SetLog sets the logging function.

type Keyring added in v0.0.2

type Keyring struct {
	// contains filtered or unexported fields
}

Keyring provides secure credential storage using the system keyring

func NewKeyring added in v0.0.2

func NewKeyring() (*Keyring, error)

NewKeyring creates a keyring handler and ensures dependencies are installed

func (*Keyring) Delete added in v0.0.2

func (k *Keyring) Delete(key string) error

Delete removes a secret from the keyring

func (*Keyring) Get added in v0.0.2

func (k *Keyring) Get(key string) (string, error)

Get retrieves a secret from the keyring

func (*Keyring) Set added in v0.0.2

func (k *Keyring) Set(key, value string) error

Set stores a secret in the keyring

func (*Keyring) SetLog added in v0.0.2

func (k *Keyring) SetLog(fn func(...any))

SetLog sets the logging function for the keyring handler

type MockGitHubAuth added in v0.0.2

type MockGitHubAuth struct {
	EnsureAuthError error // Set this to simulate auth failure
	// contains filtered or unexported fields
}

MockGitHubAuth is a mock implementation of GitHubAuthenticator for testing.

func NewMockGitHubAuth added in v0.0.2

func NewMockGitHubAuth() *MockGitHubAuth

NewMockGitHubAuth creates a new mock authenticator.

func (*MockGitHubAuth) EnsureGitHubAuth added in v0.0.2

func (m *MockGitHubAuth) EnsureGitHubAuth() error

EnsureGitHubAuth simulates the authentication process.

func (*MockGitHubAuth) Name added in v0.0.2

func (m *MockGitHubAuth) Name() string

Name returns the handler name for TUI display.

func (*MockGitHubAuth) SetLog added in v0.0.2

func (m *MockGitHubAuth) SetLog(fn func(...any))

SetLog sets the logger function.

type PublishAction added in v0.0.2

type PublishAction int
const (
	ActionNone     PublishAction = iota // no objection: full publication (tag + cascade)
	ActionDepsOnly                      // commit go.mod/go.sum, push without tag, no cascade
	ActionSkip                          // do not touch the repo at all
)

func ResolvePublishAction added in v0.0.2

func ResolvePublishAction(objectors []PublishObjector, ctx PublishContext) (PublishAction, string)

ResolvePublishAction returns the strongest action any objector requires (Skip > DepsOnly > None) and the reason of the objector that set it.

type PublishContext added in v0.0.2

type PublishContext struct {
	RepoDir     string   // dependent repo being evaluated
	ModulePaths []string // upstream module paths being updated in this wave
}

type PublishObjector added in v0.0.2

type PublishObjector interface {
	ObjectsToPublish(ctx PublishContext) (PublishAction, string) // action + readable reason
}

type PushResult added in v0.0.2

type PushResult struct {
	Summary string // Human-readable summary of operations performed
	Tag     string // The tag that was created and pushed
}

PushResult contains the results of a Git push operation

type RealRunner added in v0.0.2

type RealRunner struct{}

RealRunner runs actual system commands.

func (RealRunner) Run added in v0.0.2

func (RealRunner) Run(name string, args ...string) (string, error)

Run executes the command using the command package.

type Runner added in v0.0.2

type Runner interface {
	Run(name string, args ...string) (string, error)
}

Runner abstracts command execution (git, gh, etc.) for testing.

type SecretRunner added in v0.0.2

type SecretRunner interface {
	Run(name string, args ...string) (string, error)
	RunSilent(name string, args ...string) (string, error)
	RunWithStdin(input, name string, args ...string) (string, error)
}

SecretRunner abstracts command execution for testability. Exported to be implemented in external tests.

type TestCache added in v0.0.2

type TestCache struct {
	CacheDir string
	RootDir  string
}

TestCache provides git-based test caching to avoid re-running tests when the code hasn't changed since the last successful test run.

func NewTestCache added in v0.0.2

func NewTestCache(rootDir string) *TestCache

NewTestCache creates a new TestCache instance

func (*TestCache) GetCacheKey added in v0.0.2

func (tc *TestCache) GetCacheKey() (string, error)

GetCacheKey returns a unique key for the current module based on its path

func (*TestCache) GetCachePath added in v0.0.2

func (tc *TestCache) GetCachePath() (string, error)

GetCachePath returns the full path to the cache file

func (*TestCache) GetCachedMessage added in v0.0.2

func (tc *TestCache) GetCachedMessage() string

GetCachedMessage returns the cached test output message

func (*TestCache) GetGitState added in v0.0.2

func (tc *TestCache) GetGitState() (string, error)

GetGitState returns current git state: commit hash + diff hash This uniquely identifies the exact state of the code

func (*TestCache) InvalidateCache added in v0.0.2

func (tc *TestCache) InvalidateCache() error

InvalidateCache removes the cache file

func (*TestCache) IsCacheValid added in v0.0.2

func (tc *TestCache) IsCacheValid() bool

IsCacheValid checks if tests were already run successfully with the current code

func (*TestCache) SaveCache added in v0.0.2

func (tc *TestCache) SaveCache(message string) error

SaveCache saves the current git state and test message

Jump to

Keyboard shortcuts

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