Skip to content
internal/conflict
On this page

Reads what git recorded about a conflicted path -- attributes, index stages, AUTO_MERGE -- so a conclusion checks a resolution against git's own bytes.

#internal/conflict

#internal/conflict

Package conflict reads and reproduces what git recorded about a conflicted path, so a conclusion can verify a resolution against what git actually wrote instead of against a guess.

Four facts live here, each with one authority:

- the ATTRIBUTES in force on a path (the conflict marker size and the conflict style), resolved from a named tree so a conflicted .gitattributes cannot decide how its own conflict is read; - the index's three STAGES per conflicted path (base, ours, theirs), with an absent stage reported as absent rather than as an empty file; - git's AUTO_MERGE tree, which for an ort merge holds the conflict-marked content git wrote into the working tree, verbatim; - the RECONSTRUCTION of that content from the stages, via git's own merge-file, for the cases where AUTO_MERGE does not exist.

Everything runs through internal/git, safegit's git boundary. The package holds no policy: it does not decide whether a surviving marker is an error, which conflicts are exempt, or what a conclusion should refuse -- those decisions belong to the conclusion engine and its marker verification, which call this. A caller that needs another attribute (the merge driver, an exemption attribute) asks git.CheckAttr with the same attribute source.

One limit is structural rather than a matter of effort, and octopus_test.go asserts it against a real conflicted octopus merge: an octopus records no AUTO_MERGE, its index stages describe only the LAST pairwise step (stage 2 is an intermediate blob belonging to no commit), and its marker labels are random temporary file names. Region CONTENT is reproducible there; a byte-identical file is not.

#DefaultMarkerSize

Go go
const DefaultMarkerSize = 7

DefaultMarkerSize is the conflict marker length git uses when the conflict-marker-size attribute says nothing.

#MarkerSizeAttr

Go go
const MarkerSizeAttr = "conflict-marker-size"

MarkerSizeAttr is the attribute name git reads a per-path marker length from.

#ConflictStyleKey

Go go
const ConflictStyleKey = "merge.conflictStyle"

ConflictStyleKey is the configuration key that decides the shape of a conflicted region.

#AutoMergeRef

Go go
const AutoMergeRef = "AUTO_MERGE"

AutoMergeRef is the ref git's ort merge writes: a tree holding, per conflicted path, exactly the bytes git put in the working tree.

#Attrs

Go go
type Attrs struct

Attrs is everything that shaped the conflict markers git wrote for one path: how long the markers are, and which of git's conflicted-region styles was in force.

The marker size is per path (an attribute); the style is per repository (a configuration value) and is copied onto every path so a consumer holds one complete answer per path rather than two half-answers.

#Sides

Go go
type Sides struct

Sides holds the three merge stages of one conflicted path. A nil field means the path did not exist on that side: an add/add conflict has no Base, and a delete/modify conflict has no Ours or no Theirs.

#Labels

Go go
type Labels struct

Labels are the three names git writes onto the conflict marker lines. They are part of the file's bytes, so a reconstruction that has to match git's own output byte for byte has to reproduce them.

How git derives them, recorded from observation (git 2.54) because none of it is documented in a form a program can read:

- Ours is always "HEAD". - For a MERGE, Theirs is the name the operator wrote on the command line ("feature", or a raw object name). Git records it NOWHERE machine-readable -- not in MERGE_HEAD, not in MERGE_MODE -- so a caller that needs byte identity for a merge must supply it (MERGE_MSG's "Merge branch 'x'" is the only trace, and it is prose). Base is the abbreviated object name of the single merge base, "merged common ancestors" when there are several, and "empty tree" when there is none. - For a CHERRY-PICK, Theirs is " ()" of the commit being applied and Base is "parent of ()". - For a REVERT the two swap: Base names the commit and Theirs names its parent, which is the concrete meaning of "theirs = the result of undoing that commit".

#Region

Go go
type Region struct

Region is one complete conflict block: an opening marker line, the sides, and a closing marker line.

Bytes is the block verbatim, including both marker lines and the newline that ends the closing one when the content has it. Verbatim is what the caller needs: attributing a block to something git wrote, or to a blob a parent already carried, is a byte comparison, never a similarity judgment.

#Resolve

Go go
func Resolve(ctx context.Context, attrSource string, paths []string) (map[string]Attrs, error)

Resolve answers the conflict attributes for paths.

attrSource is a tree-ish whose .gitattributes files are read instead of the working tree's. During a conclusion it is the FIRST PARENT's tree: an attribute that decides how safegit treats a conflict has to predate the conflict, and the working tree's own .gitattributes may be conflicted -- marker-laden and meaningless -- at exactly the moment the question is asked. An empty attrSource reads the working tree, which is only right outside a conflict.

An unreadable or non-positive conflict-marker-size is DELIBERATELY treated the way git treats it -- as absent, so the default applies. The purpose of this answer is to reproduce the bytes git wrote; being stricter than git would produce a faithful reading of the operator's intent and an unfaithful reconstruction of the file.

#Style

Go go
func Style(ctx context.Context) (git.ConflictStyle, error)

Style reports the conflicted-region shape this repository writes, from merge.conflictStyle. An unset key is git's default; an unrecognized value is an error, never a silent default.

#Stages

Go go
func Stages(ctx context.Context, indexPath string) (map[string]Sides, error)

Stages groups an index's unmerged entries by path.

indexPath names the index to read; an empty indexPath reads the repository's shared index -- which is where git leaves a conflict, and which safegit only ever reads.

#AutoMergeTree

Go go
func AutoMergeTree(ctx context.Context) (tree string, present bool, err error)

AutoMergeTree resolves AUTO_MERGE and reports whether it exists.

Absence is a FACT, not a failure: git writes no AUTO_MERGE for an octopus merge (recorded and pinned in this package's tests), and it is gone entirely once an operation concludes. A caller that needs the recorded content for a content conflict and finds none must say so rather than verify less.

The version floor is checked here because on a git older than the ort merge that introduced AUTO_MERGE the ref is absent for EVERY merge, and reporting that as "this merge simply has none" would silently downgrade every verification built on it.

PRESENCE is not evidence of an operation in flight. A completed rebase leaves AUTO_MERGE behind -- git's own rebase --continue does it too, so it is not an artifact of how safegit finishes anything (asserted by internal/git's TestRebaseContinueControl). Anything that reads a leftover AUTO_MERGE as an interrupted operation will fire on a repository whose rebase finished normally; the operation's OWN state files are what say an operation is in flight.

#AutoMergeBlob

Go go
func AutoMergeBlob(ctx context.Context, path string) (content []byte, present bool, err error)

AutoMergeBlob returns the conflict-marked content git recorded for one path, and whether AUTO_MERGE holds that path at all. A clean path in a conflicted merge is present in the tree with its merged content; a path AUTO_MERGE does not carry reports present=false.

#MergeLabels

Go go
func MergeLabels(ctx context.Context, theirs string, bases []string) (Labels, error)

MergeLabels derives the labels a conflicted merge wrote. theirs is the name the merge named on the command line; bases are the merge bases, in any order.

#PickLabels

Go go
func PickLabels(ctx context.Context, source string) (Labels, error)

PickLabels derives the labels a conflicted cherry-pick of source wrote.

#RevertLabels

Go go
func RevertLabels(ctx context.Context, source string) (Labels, error)

RevertLabels derives the labels a conflicted revert of source wrote. It is PickLabels with the base and theirs sides exchanged, because a revert applies the inverse patch: the incoming side is what the commit's PARENT held.

#Describe

Go go
func Describe(ctx context.Context, sha string) (string, error)

Describe renders a commit the way git's sequencer names it on a marker line: the abbreviated object name, then the subject in parentheses.

It is exported because the conclusion commands say the same thing in prose: a listing that explains what theirs resolves to for a revert names the commit being undone in exactly the spelling its conflict markers used.

#Reconstruct

Go go
func Reconstruct(ctx context.Context, sides Sides, attrs Attrs, labels Labels) (content []byte, conflicted bool, err error)

Reconstruct reproduces the conflict-marked content git wrote for one path, from the index's stages plus the attributes that were in force.

It is git's own three-way file merge, so for a two-sided content conflict the result is byte-identical to what git put in the working tree and recorded in AUTO_MERGE -- provided the labels match, which is why Labels is an explicit argument and not something this function guesses.

A side the conflict does not have is passed as empty content, which is what git's own merge does for an add/add conflict.

#Regions

Go go
func Regions(content []byte, markerSize int) []Region

Regions finds every complete conflict block in content.

COMPLETE is the operative word: an opening marker with no separator, or a separator with no closing marker, is not a region. A file can hold such a fragment for reasons that have nothing to do with a conflict (a document about conflict markers, a test fixture, a diff quoted in a comment), and treating a fragment as a conflict would refuse work over prose.

The marker length is a MINIMUM, matching how git's own conflict-marker detection reads a line (git diff --check): a run of at least markerSize identical marker characters, ending the line or followed by a space. An operator who lengthened a marker line while editing has still left a conflict, and a shorter run is not one.

A nested opening marker inside a block is deliberately not treated as a new region: git never writes one, and the bytes are covered by the enclosing block either way.

#ContainsRegion

Go go
func ContainsRegion(content, block []byte) (line int, found bool)

ContainsRegion reports whether content holds a block byte-identical to the given one, and the 1-based line it starts on.

It is a substring search anchored to a line boundary, because a block that appears in the middle of a line is not the block: the marker lines that make it one have to start where a line starts.

#Sides.ContentConflict

Go go
func (s Sides) ContentConflict() bool { return s.Ours != nil && s.Theirs != nil }

ContentConflict reports whether both sides are present, which is the only case that has a marked-up conflict region to reason about. A delete/modify or add/delete conflict has stages but nothing merge-file could have written.

Search