1package plumbing
2
3import (
4	"errors"
5	"fmt"
6	"strings"
7)
8
9const (
10	refPrefix       = "refs/"
11	refHeadPrefix   = refPrefix + "heads/"
12	refTagPrefix    = refPrefix + "tags/"
13	refRemotePrefix = refPrefix + "remotes/"
14	refNotePrefix   = refPrefix + "notes/"
15	symrefPrefix    = "ref: "
16)
17
18// RefRevParseRules are a set of rules to parse references into short names.
19// These are the same rules as used by git in shorten_unambiguous_ref.
20// See: https://github.com/git/git/blob/e0aaa1b6532cfce93d87af9bc813fb2e7a7ce9d7/refs.c#L417
21var RefRevParseRules = []string{
22	"refs/%s",
23	"refs/tags/%s",
24	"refs/heads/%s",
25	"refs/remotes/%s",
26	"refs/remotes/%s/HEAD",
27}
28
29var (
30	ErrReferenceNotFound = errors.New("reference not found")
31)
32
33// ReferenceType reference type's
34type ReferenceType int8
35
36const (
37	InvalidReference  ReferenceType = 0
38	HashReference     ReferenceType = 1
39	SymbolicReference ReferenceType = 2
40)
41
42func (r ReferenceType) String() string {
43	switch r {
44	case InvalidReference:
45		return "invalid-reference"
46	case HashReference:
47		return "hash-reference"
48	case SymbolicReference:
49		return "symbolic-reference"
50	}
51
52	return ""
53}
54
55// ReferenceName reference name's
56type ReferenceName string
57
58// NewBranchReferenceName returns a reference name describing a branch based on
59// his short name.
60func NewBranchReferenceName(name string) ReferenceName {
61	return ReferenceName(refHeadPrefix + name)
62}
63
64// NewNoteReferenceName returns a reference name describing a note based on his
65// short name.
66func NewNoteReferenceName(name string) ReferenceName {
67	return ReferenceName(refNotePrefix + name)
68}
69
70// NewRemoteReferenceName returns a reference name describing a remote branch
71// based on his short name and the remote name.
72func NewRemoteReferenceName(remote, name string) ReferenceName {
73	return ReferenceName(refRemotePrefix + fmt.Sprintf("%s/%s", remote, name))
74}
75
76// NewRemoteHEADReferenceName returns a reference name describing a the HEAD
77// branch of a remote.
78func NewRemoteHEADReferenceName(remote string) ReferenceName {
79	return ReferenceName(refRemotePrefix + fmt.Sprintf("%s/%s", remote, HEAD))
80}
81
82// NewTagReferenceName returns a reference name describing a tag based on short
83// his name.
84func NewTagReferenceName(name string) ReferenceName {
85	return ReferenceName(refTagPrefix + name)
86}
87
88// IsBranch check if a reference is a branch
89func (r ReferenceName) IsBranch() bool {
90	return strings.HasPrefix(string(r), refHeadPrefix)
91}
92
93// IsNote check if a reference is a note
94func (r ReferenceName) IsNote() bool {
95	return strings.HasPrefix(string(r), refNotePrefix)
96}
97
98// IsRemote check if a reference is a remote
99func (r ReferenceName) IsRemote() bool {
100	return strings.HasPrefix(string(r), refRemotePrefix)
101}
102
103// IsTag check if a reference is a tag
104func (r ReferenceName) IsTag() bool {
105	return strings.HasPrefix(string(r), refTagPrefix)
106}
107
108func (r ReferenceName) String() string {
109	return string(r)
110}
111
112// Short returns the short name of a ReferenceName
113func (r ReferenceName) Short() string {
114	s := string(r)
115	res := s
116	for _, format := range RefRevParseRules {
117		_, err := fmt.Sscanf(s, format, &res)
118		if err == nil {
119			continue
120		}
121	}
122
123	return res
124}
125
126const (
127	HEAD   ReferenceName = "HEAD"
128	Master ReferenceName = "refs/heads/master"
129)
130
131// Reference is a representation of git reference
132type Reference struct {
133	t      ReferenceType
134	n      ReferenceName
135	h      Hash
136	target ReferenceName
137}
138
139// NewReferenceFromStrings creates a reference from name and target as string,
140// the resulting reference can be a SymbolicReference or a HashReference base
141// on the target provided
142func NewReferenceFromStrings(name, target string) *Reference {
143	n := ReferenceName(name)
144
145	if strings.HasPrefix(target, symrefPrefix) {
146		target := ReferenceName(target[len(symrefPrefix):])
147		return NewSymbolicReference(n, target)
148	}
149
150	return NewHashReference(n, NewHash(target))
151}
152
153// NewSymbolicReference creates a new SymbolicReference reference
154func NewSymbolicReference(n, target ReferenceName) *Reference {
155	return &Reference{
156		t:      SymbolicReference,
157		n:      n,
158		target: target,
159	}
160}
161
162// NewHashReference creates a new HashReference reference
163func NewHashReference(n ReferenceName, h Hash) *Reference {
164	return &Reference{
165		t: HashReference,
166		n: n,
167		h: h,
168	}
169}
170
171// Type return the type of a reference
172func (r *Reference) Type() ReferenceType {
173	return r.t
174}
175
176// Name return the name of a reference
177func (r *Reference) Name() ReferenceName {
178	return r.n
179}
180
181// Hash return the hash of a hash reference
182func (r *Reference) Hash() Hash {
183	return r.h
184}
185
186// Target return the target of a symbolic reference
187func (r *Reference) Target() ReferenceName {
188	return r.target
189}
190
191// Strings dump a reference as a [2]string
192func (r *Reference) Strings() [2]string {
193	var o [2]string
194	o[0] = r.Name().String()
195
196	switch r.Type() {
197	case HashReference:
198		o[1] = r.Hash().String()
199	case SymbolicReference:
200		o[1] = symrefPrefix + r.Target().String()
201	}
202
203	return o
204}
205
206func (r *Reference) String() string {
207	s := r.Strings()
208	return fmt.Sprintf("%s %s", s[1], s[0])
209}
210