package plumbing import ( "errors" "fmt" "strings" ) const ( refPrefix = "refs/" refHeadPrefix = refPrefix + "heads/" refTagPrefix = refPrefix + "tags/" refRemotePrefix = refPrefix + "remotes/" refNotePrefix = refPrefix + "notes/" symrefPrefix = "ref: " ) // RefRevParseRules are a set of rules to parse references into short names. // These are the same rules as used by git in shorten_unambiguous_ref. // See: https://github.com/git/git/blob/e0aaa1b6532cfce93d87af9bc813fb2e7a7ce9d7/refs.c#L417 var RefRevParseRules = []string{ "refs/%s", "refs/tags/%s", "refs/heads/%s", "refs/remotes/%s", "refs/remotes/%s/HEAD", } var ( ErrReferenceNotFound = errors.New("reference not found") ) // ReferenceType reference type's type ReferenceType int8 const ( InvalidReference ReferenceType = 0 HashReference ReferenceType = 1 SymbolicReference ReferenceType = 2 ) func (r ReferenceType) String() string { switch r { case InvalidReference: return "invalid-reference" case HashReference: return "hash-reference" case SymbolicReference: return "symbolic-reference" } return "" } // ReferenceName reference name's type ReferenceName string // NewBranchReferenceName returns a reference name describing a branch based on // his short name. func NewBranchReferenceName(name string) ReferenceName { return ReferenceName(refHeadPrefix + name) } // NewNoteReferenceName returns a reference name describing a note based on his // short name. func NewNoteReferenceName(name string) ReferenceName { return ReferenceName(refNotePrefix + name) } // NewRemoteReferenceName returns a reference name describing a remote branch // based on his short name and the remote name. func NewRemoteReferenceName(remote, name string) ReferenceName { return ReferenceName(refRemotePrefix + fmt.Sprintf("%s/%s", remote, name)) } // NewRemoteHEADReferenceName returns a reference name describing a the HEAD // branch of a remote. func NewRemoteHEADReferenceName(remote string) ReferenceName { return ReferenceName(refRemotePrefix + fmt.Sprintf("%s/%s", remote, HEAD)) } // NewTagReferenceName returns a reference name describing a tag based on short // his name. func NewTagReferenceName(name string) ReferenceName { return ReferenceName(refTagPrefix + name) } // IsBranch check if a reference is a branch func (r ReferenceName) IsBranch() bool { return strings.HasPrefix(string(r), refHeadPrefix) } // IsNote check if a reference is a note func (r ReferenceName) IsNote() bool { return strings.HasPrefix(string(r), refNotePrefix) } // IsRemote check if a reference is a remote func (r ReferenceName) IsRemote() bool { return strings.HasPrefix(string(r), refRemotePrefix) } // IsTag check if a reference is a tag func (r ReferenceName) IsTag() bool { return strings.HasPrefix(string(r), refTagPrefix) } func (r ReferenceName) String() string { return string(r) } // Short returns the short name of a ReferenceName func (r ReferenceName) Short() string { s := string(r) res := s for _, format := range RefRevParseRules { _, err := fmt.Sscanf(s, format, &res) if err == nil { continue } } return res } const ( HEAD ReferenceName = "HEAD" Master ReferenceName = "refs/heads/master" Main ReferenceName = "refs/heads/main" ) // Reference is a representation of git reference type Reference struct { t ReferenceType n ReferenceName h Hash target ReferenceName } // NewReferenceFromStrings creates a reference from name and target as string, // the resulting reference can be a SymbolicReference or a HashReference base // on the target provided func NewReferenceFromStrings(name, target string) *Reference { n := ReferenceName(name) if strings.HasPrefix(target, symrefPrefix) { target := ReferenceName(target[len(symrefPrefix):]) return NewSymbolicReference(n, target) } return NewHashReference(n, NewHash(target)) } // NewSymbolicReference creates a new SymbolicReference reference func NewSymbolicReference(n, target ReferenceName) *Reference { return &Reference{ t: SymbolicReference, n: n, target: target, } } // NewHashReference creates a new HashReference reference func NewHashReference(n ReferenceName, h Hash) *Reference { return &Reference{ t: HashReference, n: n, h: h, } } // Type returns the type of a reference func (r *Reference) Type() ReferenceType { return r.t } // Name returns the name of a reference func (r *Reference) Name() ReferenceName { return r.n } // Hash returns the hash of a hash reference func (r *Reference) Hash() Hash { return r.h } // Target returns the target of a symbolic reference func (r *Reference) Target() ReferenceName { return r.target } // Strings dump a reference as a [2]string func (r *Reference) Strings() [2]string { var o [2]string o[0] = r.Name().String() switch r.Type() { case HashReference: o[1] = r.Hash().String() case SymbolicReference: o[1] = symrefPrefix + r.Target().String() } return o } func (r *Reference) String() string { ref := "" switch r.Type() { case HashReference: ref = r.Hash().String() case SymbolicReference: ref = symrefPrefix + r.Target().String() default: return "" } name := r.Name().String() var v strings.Builder v.Grow(len(ref) + len(name) + 1) v.WriteString(ref) v.WriteString(" ") v.WriteString(name) return v.String() }