Documentation
¶
Overview ¶
Package gitrepo reads and changes the git repository a session is running in.
Every git invocation goes through a Runner the caller supplies, which is what lets the parsing here be tested against fixture bytes rather than against a repository someone has to build first.
Index ¶
- Constants
- Variables
- func AmendCommand(dir string) proc.Command
- func CommitCommand(dir, messageFile string) proc.Command
- func FetchCommand(dir, remote string) proc.Command
- func FixupCommand(dir, hash string) proc.Command
- func PullCommand(dir, remote, branch string) proc.Command
- func PushCommand(dir, remote, branch string) proc.Command
- func RebaseCommand(dir, base string) proc.Command
- type Branch
- type Change
- type Commit
- type DatedCommit
- type Finishing
- type Remote
- type Repo
- type Repository
- func (r Repository) BaseRemote(ctx context.Context) string
- func (r Repository) ChangedPaths(ctx context.Context, base string) ([]string, error)
- func (r Repository) CheckIgnored(ctx context.Context, path string) (bool, error)
- func (r Repository) Checkout(ctx context.Context, name string) error
- func (r Repository) ClearIssueLink(ctx context.Context, branch string) error
- func (r Repository) CodeOwnersAt(ctx context.Context, base string, dialect codeowners.Dialect) (codeowners.File, bool, error)
- func (r Repository) CommitsBetween(ctx context.Context, start, end time.Time) ([]DatedCommit, error)
- func (r Repository) CreateBranch(ctx context.Context, name, start string) error
- func (r Repository) Describe(ctx context.Context) (Repo, error)
- func (r Repository) Diff(ctx context.Context, change Change) ([]string, error)
- func (r Repository) Discard(ctx context.Context, change Change) error
- func (r Repository) FileAt(ctx context.Context, ref string, paths []string) ([]byte, bool, error)
- func (r Repository) FinishBranch(ctx context.Context, finishing Finishing, pull func() error) error
- func (r Repository) HooksDir(ctx context.Context) (string, error)
- func (r Repository) IssueLink(ctx context.Context, branch string) string
- func (r Repository) IssueLinks(ctx context.Context) map[string]string
- func (r Repository) LocalBranches(ctx context.Context) ([]string, error)
- func (r Repository) PushRemote(ctx context.Context, branch string) string
- func (r Repository) ReadBranch(ctx context.Context) (Branch, error)
- func (r Repository) ReadBranchOnto(ctx context.Context, base string) (Branch, error)
- func (r Repository) RecentSubjects(ctx context.Context) ([]string, error)
- func (r Repository) RemoteBranches(ctx context.Context) ([]string, error)
- func (r Repository) RemoteURL(ctx context.Context, name string) string
- func (r Repository) SetIssueLink(ctx context.Context, branch, issueKey string) error
- func (r Repository) SharedDir(ctx context.Context) (string, error)
- func (r Repository) Stage(ctx context.Context, change Change) error
- func (r Repository) Status(ctx context.Context) ([]Change, error)
- func (r Repository) Unstage(ctx context.Context, change Change) error
- func (r Repository) WorktreeAdd(ctx context.Context, name, start string) (string, error)
- func (r Repository) Worktrees(ctx context.Context) ([]Worktree, error)
- type Runner
- type Worktree
Constants ¶
const DefaultRemote = "origin"
DefaultRemote is the remote a clone names for where it came from, and the answer to each remote question here that git's own configuration leaves open.
Variables ¶
var ErrIssueLinkNotSaved = errors.New("git could not change the branch's issue link")
ErrIssueLinkNotSaved reports git failing to write or remove a branch's issue link in the repository's configuration.
var ErrNoIdentity = errors.New("git has no user.email to tell your commits by; set it with git config user.email")
ErrNoIdentity is a repository with no user.email to tell your commits by.
var ErrNoSuchBase = errors.New("no branch of that name to merge into")
ErrNoSuchBase refuses a base to merge into that neither the base remote nor the repository holds a branch of.
var ErrNotARepository = errors.New("not a git repository")
ErrNotARepository reports a directory outside any git work tree.
var ErrOptionLikeRef = errors.New("a ref cannot start with a dash")
ErrOptionLikeRef refuses a ref starting with a dash, which git would read as an option rather than a revision.
var ErrUnshowableName = errors.New("a branch name holds characters that cannot be shown as they are")
ErrUnshowableName reports a branch whose name cannot be shown as it is.
Functions ¶
func AmendCommand ¶ added in v0.0.7
AmendCommand folds the index into the last commit, keeping its message. Env is nil, like CommitCommand, so the repository's hooks run.
func CommitCommand ¶
CommitCommand commits the index with the message in a file. It is a plain git commit, so the repository's hooks run exactly as they do in a terminal.
func FetchCommand ¶
FetchCommand updates the remote-tracking refs from remote, the base remote, so a branch starts from what it holds now rather than from whenever the user last fetched.
Prompts are off, as they are for a push: a fetch can need a credential, and nobody can answer for one from inside the interface. "--" ends git's options, so the remote's name is never read as one.
func FixupCommand ¶ added in v0.0.7
FixupCommand records a fixup! commit of hash — a commit git squashes into that one on the next autosquash rebase. Env is nil so the repository's hooks run.
func PullCommand ¶ added in v0.4.0
PullCommand fast-forwards the checked-out branch from branch on remote, and refuses rather than merge when it cannot: the pull a finish runs once it has switched to the base, from the base remote — a fork's clone's main tracks the fork, whose main is not what the branch merged into.
Prompts are off, as they are for a fetch: a pull can need a credential, and nobody can answer for one from inside the interface. "--" ends git's options, so neither name is ever read as one.
func PushCommand ¶
PushCommand pushes a branch to remote and makes it the upstream.
Prompts are off: nobody can answer a credential prompt from inside the interface, so git must fail with a reason rather than wait forever for input that is never coming. "--" ends git's options, so neither name is ever read as one.
func RebaseCommand ¶ added in v0.0.4
RebaseCommand replays the branch onto base, the branch's fetched merge target. A conflict stops git with a non-zero exit and leaves the repository mid-rebase for the shell to finish, which the interface cannot do.
Types ¶
type Branch ¶
type Branch struct {
Name string
Detached bool
// Head is the commit checked out, or empty before the first commit.
Head string
// Upstream is the remote branch it tracks, or empty if never pushed.
Upstream string
// PushRemote is the remote a push of it goes to.
PushRemote string
// BaseRemote is the remote the base is read from: fetched before work
// starts, and pulled from when the branch is finished.
BaseRemote string
// Ahead and Behind count commits against the upstream.
Ahead, Behind int
// Base is the branch this one would merge into — the base remote's
// default branch wherever that can be found, origin/main or, in a fork's
// clone, upstream/main.
Base string
// BaseUpdated is when the base last moved, so the interface can say how
// stale the branch it started from is. Zero when it cannot be read.
BaseUpdated time.Time
// Commits are those on this branch and not on Base, oldest first.
Commits []Commit
// Truncated is set when the branch has more commits than are kept, so a
// reader knows Commits is the oldest of a longer history, not the whole of it.
Truncated bool
// IssueLink is the issue the branch was linked to by hand, for work begun
// on a branch whose name names none; empty when it was never linked.
IssueLink string
}
Branch is where the checked-out branch stands.
func (Branch) BaseName ¶ added in v0.0.6
BaseName is the base branch without its remote prefix — "main" for "origin/main" — which is the name to show and to rebase onto. A base with no remote (a local "main") is returned as it is.
func (Branch) Finishing ¶ added in v0.10.0
Finishing is how this branch finishes: onto its base, pulled from the base remote.
func (Branch) PushTarget ¶ added in v0.4.0
PushTarget is the remote branch a push of this one lands on, "fork/feat" for feat pushed to fork.
func (Branch) Pushed ¶
Pushed reports whether the remote a push goes to holds this branch, under its own name, with every commit. The name matters: a branch created from origin/main without --no-track tracks origin/main, is ahead of it by nothing, and is not on the remote at all.
func (Branch) Unpushed ¶ added in v0.0.7
Unpushed are the commits on this branch that have not reached the upstream, oldest first, so amending or fixing one up rewrites only local history. With no upstream every commit is unpushed; otherwise it is the last Ahead of them.
A branch long enough to be truncated returns none: Commits then keeps the oldest, while Ahead is counted against the full range, so the last Ahead of the retained commits are the wrong ones — some already pushed. Rather than risk offering pushed history to rewrite, a truncated branch offers nothing.
func (Branch) UpstreamHoldsAll ¶ added in v0.10.0
UpstreamHoldsAll reports an upstream that holds every commit on this branch: it has one and is not ahead of it. It is one of the two things that make a force delete safe. With no upstream — never pushed under its own name, or one the forge deleted after a merge and a prune then dropped — nothing is known to hold the commits, so it reports false.
type Change ¶
type Change struct {
// Path is where the file is now, exactly as git wrote it. It is the path to
// hand back to git, so it is never altered; anything showing it on screen
// must neutralize it first, because a file name may hold any byte but / and
// NUL — escape sequences included.
Path string
// OriginalPath is where a renamed or copied file came from.
OriginalPath string
// Staged is what the index holds against HEAD, and Unstaged what the work
// tree holds against the index: git's two status letters, a space meaning
// no change.
Staged byte
Unstaged byte
}
Change is one changed file, as git status reports it.
func (Change) Conflicted ¶
Conflicted reports a file a merge left unresolved: either side unmerged, or both sides adding or deleting it.
func (Change) HasUnstaged ¶
HasUnstaged reports changes in the work tree not yet in the index, including a file git does not track at all.
type DatedCommit ¶ added in v0.6.0
DatedCommit is a commit you wrote, with when you wrote it.
type Finishing ¶ added in v0.10.0
type Finishing struct {
Branch, Base, Remote string
}
Finishing is what finishing the branch acts on: the branch, the base it returns to by its bare name, and the remote that base is pulled from.
type Remote ¶ added in v0.10.0
type Remote struct {
Name, URL string
}
Remote is a git remote by name and URL; the URL is empty when git holds none for that name.
type Repo ¶
type Repo struct {
// Root is the absolute path of the work tree.
Root string
// Branch is the checked-out branch, empty when Detached is true.
Branch string
// Detached reports a HEAD that is not on a branch. It is not an error — the
// repository still reads — but it is why a caller cannot branch from here.
Detached bool
// Origin is origin's URL, or empty when the repository has no origin.
Origin string
// Base is the base remote: where pull requests merge into, as BaseRemote
// reads it — origin itself outside a fork's clone.
Base Remote
}
Repo describes the repository a session is running in.
type Repository ¶ added in v0.0.6
type Repository struct {
// contains filtered or unexported fields
}
Repository is a git work tree a caller reads and changes: the directory it works in and the Runner every git command goes through, bundled so the two stop traveling as a pair through every function's signature.
func At ¶ added in v0.0.6
func At(run Runner, dir string) Repository
At is a Repository working in dir, running git through run. For Status and Stage, dir must be the work-tree root — git reports and stages paths relative to it — so pass the Root that Describe found; for the questions that name their own path, such as CheckIgnored, dir is only where git is run from.
func (Repository) BaseRemote ¶ added in v0.10.0
func (r Repository) BaseRemote(ctx context.Context) string
BaseRemote is the remote pull requests merge into, as git's own configuration says: the one gh marked as the base (remote.<name>.gh-resolved set to base), else a remote named upstream — a fork's clone — else DefaultRemote. A configuration git cannot read answers DefaultRemote, as a repository with no remote does.
func (Repository) ChangedPaths ¶ added in v0.5.0
ChangedPaths lists the paths the checked-out branch changes since it left base: `git diff base...HEAD`, renames read as a delete and an add so both paths are listed. base is a branch name, read as the base remote's copy when it has one, since that is what a pull request merges into. The paths are for matching, not for showing, so one that would draw differently — a joined emoji, a tab — is kept; only one holding a control character other than a tab, which no one names a file with to own it, is left out.
func (Repository) CheckIgnored ¶ added in v0.0.6
CheckIgnored reports whether git ignores path. Outside a work tree it returns ErrNotARepository, because the question has no answer there — a caller warning about a file that is not ignored simply stays quiet — and without git, the missing program.
func (Repository) Checkout ¶ added in v0.0.6
func (r Repository) Checkout(ctx context.Context, name string) error
Checkout switches to a branch. It carries nothing across: the interface refuses a dirty tree before calling this, leaving stashing to the person.
A name only the remotes hold is created here from the base remote's branch when more than one remote holds it — a fork's clone has upstream's branches and the fork's copies of them — which git would otherwise refuse as matching several remote-tracking branches.
The name can come straight from the web request body, so "--" ends git's option parsing: a name beginning with a dash is then read as a ref, never as an option such as --orphan.
func (Repository) ClearIssueLink ¶ added in v0.5.0
func (r Repository) ClearIssueLink(ctx context.Context, branch string) error
ClearIssueLink forgets the issue branch was linked to, if it was — even a value IssueLink will not show, since it is the one way to be rid of it.
func (Repository) CodeOwnersAt ¶ added in v0.5.0
func (r Repository) CodeOwnersAt( ctx context.Context, base string, dialect codeowners.Dialect, ) (codeowners.File, bool, error)
CodeOwnersAt reads the CODEOWNERS file as it stands on base — the branch a pull request merges into, whose rules decide who reviews it — from the first place dialect looks, and parses it as dialect does. base is read as ChangedPaths reads it, so the owners and the paths they own agree. No file is not an error: it reports false, and the zero File owns nothing.
func (Repository) CommitsBetween ¶ added in v0.6.0
func (r Repository) CommitsBetween(ctx context.Context, start, end time.Time) ([]DatedCommit, error)
CommitsBetween reads the commits you wrote from start up to end, on any branch, merges and stashes left out. They are told by the email git commits under, and a repository with none is refused rather than read as everyone's. git can filter only by when a commit was made, which a rebase moves later, so every commit made since start is read and those written outside the period are left out by their author date. Every subject is sanitized, because a commit is anyone's to write.
func (Repository) CreateBranch ¶ added in v0.0.6
func (r Repository) CreateBranch(ctx context.Context, name, start string) error
CreateBranch creates a branch at start and switches to it, or at HEAD when start is empty.
--no-track matters when start is a remote branch such as origin/main: without it git makes origin/main the new branch's upstream, so the branch reads as ahead of main rather than as never pushed.
func (Repository) Describe ¶ added in v0.0.6
func (r Repository) Describe(ctx context.Context) (Repo, error)
Describe reads the repository this one works in.
func (Repository) Diff ¶ added in v0.0.7
Diff reads a change's diff against HEAD, line by line and sanitized, so it can be read before staging. An untracked file has no such diff — it is whole and new — so a note stands in for it.
func (Repository) Discard ¶ added in v0.7.0
func (r Repository) Discard(ctx context.Context, change Change) error
Discard drops a file's changes, staged and not, which cannot be undone: a tracked file is restored in the index and the work tree from HEAD — a rename comes back under its old name — and a file git does not track is removed. Before the first commit there is no HEAD to restore from, so the file is removed from both instead.
func (Repository) FileAt ¶ added in v0.5.0
FileAt reads the first of paths present at ref, reporting false when none is. A ref the repository does not have — a base never fetched — is read as HEAD instead: the branch's own copy of a file is usually the base's, and a guess beats nothing for a suggestion. Only a git that timed out is an error; any other failure to show a path means it is not there.
func (Repository) FinishBranch ¶ added in v0.0.7
FinishBranch finishes a merged branch: switch to its base, fast-forward it from the base remote through pull, and delete the branch. It is for after the branch's pull request has merged, so the local repository catches up and the branch is cleaned away. A step that fails stops the rest, so a base that did not catch up keeps the branch.
The pull is the caller's to run — PullCommand is the command — because it reaches the network, and so streams without the Runner's quick-read bound.
The delete is a force delete (-D) on purpose: a squash or rebase merge leaves the branch's own commits unreachable from base, so the safe -d would refuse a branch the forge has already merged. Its caller is what makes this safe: it offers the finish only for a merged branch that is at the commit its pull request merged or whose upstream holds every commit on it (see Branch.UpstreamHoldsAll), so the commits -D discards are ones the forge or the upstream already holds.
func (Repository) HooksDir ¶ added in v0.0.6
func (r Repository) HooksDir(ctx context.Context) (string, error)
HooksDir is where git looks for this repository's hooks, which core.hooksPath can move anywhere.
func (Repository) IssueLink ¶ added in v0.5.0
func (r Repository) IssueLink(ctx context.Context, branch string) string
IssueLink is the issue branch was linked to, or empty when it was never linked — or when what git holds there is not text to show.
func (Repository) IssueLinks ¶ added in v0.5.0
func (r Repository) IssueLinks(ctx context.Context) map[string]string
IssueLinks is every branch linked to an issue by hand, by branch name, for a list of branches to read at once. A repository with none, or whose configuration cannot be read, has none; an entry that is not text to show is left out.
func (Repository) LocalBranches ¶ added in v0.0.6
func (r Repository) LocalBranches(ctx context.Context) ([]string, error)
LocalBranches lists the repository's local branches, most recently committed to first, so a switcher offers the ones most likely to be picked up again.
func (Repository) PushRemote ¶ added in v0.0.6
func (r Repository) PushRemote(ctx context.Context, branch string) string
PushRemote is the remote a push of branch goes to, as git itself decides it: the branch's own branch.<name>.pushRemote, then the repository's remote.pushDefault — a fork's clone pushes to its own remote — then the remote the branch tracks, branch.<name>.remote, then DefaultRemote. git exits non-zero for a key that is unset, which optional reads as the empty string, so each unset key falls through cleanly. A detached HEAD, with no branch to keep a key under, asks only for the default.
func (Repository) ReadBranch ¶ added in v0.0.6
func (r Repository) ReadBranch(ctx context.Context) (Branch, error)
ReadBranch reads where the checked-out branch stands. Only failing to read the branch at all is an error: no commits, no upstream and no base are ordinary states for a repository to be in, and each is left empty.
func (Repository) ReadBranchOnto ¶ added in v0.10.0
ReadBranchOnto reads the checked-out branch as ReadBranch does, but against base, a branch a pull request is to merge into, rather than the base remote's default: its Base, and so its commits, are those past base. base is read as the base remote's copy when it has one, else the local branch; one neither holds is ErrNoSuchBase.
func (Repository) RecentSubjects ¶ added in v0.0.7
func (r Repository) RecentSubjects(ctx context.Context) ([]string, error)
RecentSubjects reads the subjects of the repository's most recent commits, across every author, so their Conventional Commit scopes can be offered as completions. Each subject is sanitized, because a commit is anyone's to write.
func (Repository) RemoteBranches ¶ added in v0.0.7
func (r Repository) RemoteBranches(ctx context.Context) ([]string, error)
RemoteBranches lists the branches that exist on the remotes, by name and most recently committed to first, so the base field can complete to one. The remote prefix is dropped ("origin/main" becomes "main") to match the name a base carries, a remote's symbolic HEAD pointer is left out (git abbreviates it to the bare remote name, "origin", which has no branch part), and a branch on more than one remote is listed once — Checkout takes the base remote's.
func (Repository) RemoteURL ¶ added in v0.10.0
func (r Repository) RemoteURL(ctx context.Context, name string) string
RemoteURL is the URL of the remote named name, or empty when the repository has no such remote.
func (Repository) SetIssueLink ¶ added in v0.5.0
func (r Repository) SetIssueLink(ctx context.Context, branch, issueKey string) error
SetIssueLink links branch to the issue issueKey names.
func (Repository) SharedDir ¶ added in v0.6.0
func (r Repository) SharedDir(ctx context.Context) (string, error)
SharedDir is the git directory every worktree of the repository shares, which tells one repository from another where each worktree's root differs.
func (Repository) Stage ¶ added in v0.0.6
func (r Repository) Stage(ctx context.Context, change Change) error
Stage puts a file's changes in the index — additions, edits and deletions alike, which is what --all is for. --literal-pathspecs turns off globbing and pathspec magic, so a name like `[id].tsx` stages only itself; `--` alone ends options but does not stop git reading the path as a pattern.
func (Repository) Status ¶ added in v0.0.6
func (r Repository) Status(ctx context.Context) ([]Change, error)
Status lists every changed file in the work tree, untracked files included. It reads without git's optional locks, as git advises for a status read in the background — the web's event stream reads it every few seconds: a plain status takes the index lock to write back what it refreshed, and a stage or a commit that meets the lock fails rather than waits.
func (Repository) Unstage ¶ added in v0.0.6
func (r Repository) Unstage(ctx context.Context, change Change) error
Unstage takes a file's changes out of the index and leaves the work tree alone. Before the first commit there is no HEAD to restore from, so the file is removed from the index instead.
func (Repository) WorktreeAdd ¶ added in v0.0.6
WorktreeAdd creates branch name in a new worktree beside the repository and returns where it put it, so two tasks can be open at once — one checkout per worktree — where switching branches in place cannot. It starts the branch from start, or from HEAD when start is empty.
type Runner ¶
Runner runs a program and returns its standard output. proc.Run satisfies it; tests supply a function returning canned bytes.
This is a function type rather than an interface because it has exactly one method's worth of surface, and a one-method seam is a func var.
type Worktree ¶ added in v0.6.0
type Worktree struct {
Dir string
Branch string
Head string
Detached bool
Locked bool
Missing bool
}
Worktree is one working tree of a repository: where it is, which branch it has checked out — none when its HEAD is detached — and whether git keeps it from being removed (Locked) or finds its directory gone (Missing).