On this page
The one reader of the state git writes while an operation is in flight -- merge, cherry-pick, revert, rebase, mailbox -- read from its files, no policy.
#internal/sequencer
#internal/sequencer
Package sequencer is the single authority on the state git writes into the git directory while an operation is in flight -- a conflicted merge, a cherry-pick or revert (single or queued), a rebase, or a mailbox application.
The package does three things and nothing else: it REPORTS the state, it CLEARS the state of the operations safegit owns, and it writes the ONE state file git declines to write for a cherry-pick computed with --no-commit (see MarkCherryPick, and the file it lives in for why that write belongs beside the reader of the same format). It holds no policy. It does not decide whether an in-flight operation should block a commit, which command an operator ought to run next, or whether a conclusion is legal -- those decisions belong to the refusal checks and the conclusion engine that call it.
Read is deliberately filesystem-only: it never starts a git subprocess, so the refusal checks on safegit's hot paths pay a handful of stat calls rather than a fork. The one fact that is not on disk -- the identity recorded on the commit a cherry-pick or revert is applying -- is resolved on demand by SourceAuthor, which is the only function here that runs git (through internal/git, safegit's git boundary).
The git directory always arrives as a parameter. The package never discovers it, which is what makes it correct in a linked worktree: pass the worktree's own git directory (.git/worktrees/
#FileMergeHead
const FileMergeHead = "MERGE_HEAD"The state files and directories git writes, named relative to the git directory. These names are the vocabulary of the whole package: Read probes them, Paths groups them per operation, and Cleanup removes them.
#FileMergeMode
const FileMergeMode = "MERGE_MODE"#FileMergeMsg
const FileMergeMsg = "MERGE_MSG"#FileCherryPickHead
const FileCherryPickHead = "CHERRY_PICK_HEAD"#FileRevertHead
const FileRevertHead = "REVERT_HEAD"#FileAutoMerge
const FileAutoMerge = "AUTO_MERGE"#FileMergeRR
const FileMergeRR = "MERGE_RR"FileMergeRR is rerere's resolution index for the conflict in flight. git writes it whenever rerere is enabled and a merge-like operation conflicts, and removes it when git itself commits the resolution.
#FileMergeAutostash
const FileMergeAutostash = "MERGE_AUTOSTASH"FileMergeAutostash holds the object name of the stash-shaped commit git set aside before a merge began (merge --autostash, merge.autoStash, pull --autostash).
It is the one file in this vocabulary that is NOT in Paths, and therefore the one Cleanup never removes: the file is a POINTER TO THE OPERATOR'S UNCOMMITTED WORK, and deleting it without applying that work loses content that exists nowhere else. It is consumed by the conclusion, which applies the stash and then removes the file -- see Autostash.
#DirSequencer
const DirSequencer = "sequencer"#DirRebaseMerge
const DirRebaseMerge = "rebase-merge"#DirRebaseApply
const DirRebaseApply = "rebase-apply"#KindNone
const KindNone Kind = iotaKindNone means no operation is in flight.
#KindMerge
const KindMergeKindMerge is a merge stopped before its commit: .git/MERGE_HEAD exists. An octopus merge is still one KindMerge; MergeHeads then carries several commits.
#KindCherryPick
const KindCherryPickKindCherryPick is a cherry-pick stopped before its commit, single or queued. Queued distinguishes the two.
#KindRevert
const KindRevertKindRevert is a revert stopped before its commit, single or queued.
#KindRebase
const KindRebaseKindRebase is a rebase in progress, under either backend. Backend says which.
#KindAM
const KindAMKindAM is a git am in progress. It shares the rebase-apply directory with the apply-backend rebase and is told apart by the marker file git writes there, so reporting it as a rebase would be a factual error and a refusal message derived from it would name the wrong way out.
#BackendNone
const BackendNone Backend = iotaBackendNone is the value on every state that is not a rebase.
#BackendMerge
const BackendMergeBackendMerge is the merge backend, which keeps its state in .git/rebase-merge. It is git's default and the backend every interactive rebase uses.
#BackendApply
const BackendApplyBackendApply is the apply backend, which keeps its state in .git/rebase-apply. git rebase --apply selects it.
#Kind
type Kind intKind names the operation in flight.
#Backend
type Backend intBackend names which of git's two rebase implementations is running.
#State
type State structState is one reading of the git directory: what is in flight, and the facts about it that live in the state files.
Fields not relevant to Kind hold their zero values. A caller reads Kind first and only then the fields that kind populates.
#SourceAuthor
func SourceAuthor(ctx context.Context, s State) (git.AuthorInfo, error)SourceAuthor returns the author identity recorded on the commit a cherry-pick or revert is applying -- the commit State.Source names. It is the one fact a conclusion needs that is not written into any state file, so it is the one function in this package that runs git.
It is a separate call rather than a field on State because Read is on every refusal check's path and must not fork a subprocess, and because the identity is a property of a commit object rather than of the sequencer state. A caller that needs it asks for it.
What the identity means is the caller's business: git's cherry-pick preserves it on the new commit while git's revert does not, and this function reports the fact for both without taking a position.
It is an error to call it on any other kind, or on a queued state that is between steps and therefore names no source commit.
#Paths
func Paths(k Kind) []stringPaths returns the state files and directories that constitute an operation, named relative to the git directory, in a stable order. It is the declared set Cleanup removes, exposed so a caller (or a test) can see the contract rather than infer it.
The sets:
merge MERGE_HEAD, MERGE_MODE, MERGE_MSG, AUTO_MERGE, MERGE_RR cherry-pick CHERRY_PICK_HEAD, MERGE_MSG, AUTO_MERGE, MERGE_RR, sequencer/ revert REVERT_HEAD, MERGE_MSG, AUTO_MERGE, MERGE_RR, sequencer/
MERGE_MSG, AUTO_MERGE and MERGE_RR belong to all three: git writes them for every one of these operations and removes them when the operation concludes, so they are not "another operation's files" that a cleanup could leave behind. (MERGE_RR is rerere's, and only exists where rerere is enabled; an absent path is not an error, so the entry costs nothing where it is not written.)
MERGE_AUTOSTASH is deliberately NOT in any set, and that is the one exclusion worth stating: it names a stash-shaped commit holding the operator's uncommitted work, so removing it is only correct AFTER that work has been put back. Deleting it here would turn every conclusion of an autostashed merge into silent data loss. The conclusion consumes it instead -- see State.Autostash.
Paths returns nil for KindNone, KindRebase and KindAM. safegit owns no part of a rebase's or a mailbox application's state: those are concluded and abandoned by git's own commands, which restore a great deal more than a set of files (the original branch tip, the remaining todo, the reflog trail). Cleanup refuses them for the same reason.
#Cleanup
func Cleanup(gitDir string, k Kind) errorCleanup removes exactly the state files and directories of operation k from gitDir -- Paths(k), nothing more and nothing less -- and is the single implementation of that removal: the conclusion commands and the restructured revert all call it rather than each deleting their own idea of the set.
A path that is already absent is not an error; git writes several of these only under some conditions (an octopus merge leaves no AUTO_MERGE, a single pick leaves no sequencer directory) and a concluded operation is a concluded operation either way. Every path is attempted even when an earlier one fails, and the failures are returned joined, so a partial removal is reported in full rather than one path at a time.
Removing the set is the last step of concluding an operation, never a way to dispose of one: it drops the state without touching HEAD, the index or the working tree, so calling it on an operation that was not actually concluded abandons that operation's work silently. For a cherry-pick or revert the set includes the sequencer directory, i.e. the whole queue -- concluding one step of a sequence natively and then calling Cleanup would strand the remaining commits. The caller decides; this function only removes.
KindNone, KindRebase and KindAM are refused: there is no set to remove for the first, and safegit does not own the state of the other two.
#MarkCherryPick
func MarkCherryPick(gitDir, sha string) errorMarkCherryPick writes CHERRY_PICK_HEAD naming the commit being applied, in git's own format, so a cherry-pick computed with --no-commit is in flight exactly as far as git, Read and every conclusion path are concerned.
The write is atomic in content: a temporary sibling is renamed into place, so a process killed mid-write leaves either the previous file or the complete new one and never a truncated object name -- which Read would refuse, taking every other safegit command in the repository down with it.
#Read
func Read(gitDir string) (State, error)Read reports the operation in flight in the repository whose git directory is gitDir. A repository with nothing in flight yields a State with Kind KindNone and a nil error; an error means the git directory could not be read or holds a state file git could not have written.
It never starts a git subprocess.
When several operations' markers are present at once -- which git does not normally produce, but a crashed process or a stray file can leave behind -- the probe order decides, and it is git's own: merge, then rebase (merge backend before apply backend), then cherry-pick, then revert, then a sequencer queue with no current step. Reporting the first match rather than erroring keeps every caller's behavior deterministic; a caller that considers leftovers an error (a conclusion refusing to run against residue) checks for them itself.
#Kind.String
func (k Kind) String() stringString returns the operation's name in git's own vocabulary.
#Backend.String
func (b Backend) String() stringString returns the backend's name as git's own documentation spells it.
#Backend.Dir
func (b Backend) Dir() stringDir returns the git-directory-relative directory the backend keeps its state in, or "" for BackendNone.
#State.InProgress
func (s State) InProgress() bool { return s.Kind != KindNone }InProgress reports whether any operation is in flight.
#State.String
func (s State) String() stringString describes the state in one factual phrase, suitable for embedding in a caller's message. It states what is in flight, never what to do about it.