aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing/protocol/packp/advrefs.go
blob: ab286c63861bcfbf2e1183ee0262046f9bf40a0e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
package packp

import (
	"fmt"
	"sort"
	"strings"

	"github.com/go-git/go-git/v5/plumbing"
	"github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
	"github.com/go-git/go-git/v5/plumbing/storer"
	"github.com/go-git/go-git/v5/storage/memory"
)

// AdvRefs values represent the information transmitted on an
// advertised-refs message.  Values from this type are not zero-value
// safe, use the New function instead.
type AdvRefs struct {
	// Prefix stores prefix payloads.
	//
	// When using this message over (smart) HTTP, you have to add a pktline
	// before the whole thing with the following payload:
	//
	// '# service=$servicename" LF
	//
	// Moreover, some (all) git HTTP smart servers will send a flush-pkt
	// just after the first pkt-line.
	//
	// To accommodate both situations, the Prefix field allow you to store
	// any data you want to send before the actual pktlines.  It will also
	// be filled up with whatever is found on the line.
	Prefix [][]byte
	// Head stores the resolved HEAD reference if present.
	// This can be present with git-upload-pack, not with git-receive-pack.
	Head *plumbing.Hash
	// Capabilities are the capabilities.
	Capabilities *capability.List
	// References are the hash references.
	References map[string]plumbing.Hash
	// Peeled are the peeled hash references.
	Peeled map[string]plumbing.Hash
	// Shallows are the shallow object ids.
	Shallows []plumbing.Hash
}

// NewAdvRefs returns a pointer to a new AdvRefs value, ready to be used.
func NewAdvRefs() *AdvRefs {
	return &AdvRefs{
		Prefix:       [][]byte{},
		Capabilities: capability.NewList(),
		References:   make(map[string]plumbing.Hash),
		Peeled:       make(map[string]plumbing.Hash),
		Shallows:     []plumbing.Hash{},
	}
}

func (a *AdvRefs) AddReference(r *plumbing.Reference) error {
	switch r.Type() {
	case plumbing.SymbolicReference:
		v := fmt.Sprintf("%s:%s", r.Name().String(), r.Target().String())
		a.Capabilities.Add(capability.SymRef, v)
	case plumbing.HashReference:
		a.References[r.Name().String()] = r.Hash()
	default:
		return plumbing.ErrInvalidType
	}

	return nil
}

func (a *AdvRefs) AllReferences() (memory.ReferenceStorage, error) {
	s := memory.ReferenceStorage{}
	if err := a.addRefs(s); err != nil {
		return s, plumbing.NewUnexpectedError(err)
	}

	return s, nil
}

func (a *AdvRefs) addRefs(s storer.ReferenceStorer) error {
	for name, hash := range a.References {
		ref := plumbing.NewReferenceFromStrings(name, hash.String())
		if err := s.SetReference(ref); err != nil {
			return err
		}
	}

	if a.supportSymrefs() {
		return a.addSymbolicRefs(s)
	}

	return a.resolveHead(s)
}

// If the server does not support symrefs capability,
// we need to guess the reference where HEAD is pointing to.
//
// Git versions prior to 1.8.4.3 has an special procedure to get
// the reference where is pointing to HEAD:
// - Check if a reference called master exists. If exists and it
//	 has the same hash as HEAD hash, we can say that HEAD is pointing to master
// - If master does not exists or does not have the same hash as HEAD,
//   order references and check in that order if that reference has the same
//   hash than HEAD. If yes, set HEAD pointing to that branch hash
// - If no reference is found, throw an error
func (a *AdvRefs) resolveHead(s storer.ReferenceStorer) error {
	if a.Head == nil {
		return nil
	}

	ref, err := s.Reference(plumbing.Master)

	// check first if HEAD is pointing to master
	if err == nil {
		ok, err := a.createHeadIfCorrectReference(ref, s)
		if err != nil {
			return err
		}

		if ok {
			return nil
		}
	}

	if err != nil && err != plumbing.ErrReferenceNotFound {
		return err
	}

	// From here we are trying to guess the branch that HEAD is pointing
	refIter, err := s.IterReferences()
	if err != nil {
		return err
	}

	var refNames []string
	err = refIter.ForEach(func(r *plumbing.Reference) error {
		refNames = append(refNames, string(r.Name()))
		return nil
	})
	if err != nil {
		return err
	}

	sort.Strings(refNames)

	var headSet bool
	for _, refName := range refNames {
		ref, err := s.Reference(plumbing.ReferenceName(refName))
		if err != nil {
			return err
		}
		ok, err := a.createHeadIfCorrectReference(ref, s)
		if err != nil {
			return err
		}
		if ok {
			headSet = true
			break
		}
	}

	if !headSet {
		return plumbing.ErrReferenceNotFound
	}

	return nil
}

func (a *AdvRefs) createHeadIfCorrectReference(
	reference *plumbing.Reference,
	s storer.ReferenceStorer) (bool, error) {
	if reference.Hash() == *a.Head {
		headRef := plumbing.NewSymbolicReference(plumbing.HEAD, reference.Name())
		if err := s.SetReference(headRef); err != nil {
			return false, err
		}

		return true, nil
	}

	return false, nil
}

func (a *AdvRefs) addSymbolicRefs(s storer.ReferenceStorer) error {
	for _, symref := range a.Capabilities.Get(capability.SymRef) {
		chunks := strings.Split(symref, ":")
		if len(chunks) != 2 {
			err := fmt.Errorf("bad number of `:` in symref value (%q)", symref)
			return plumbing.NewUnexpectedError(err)
		}
		name := plumbing.ReferenceName(chunks[0])
		target := plumbing.ReferenceName(chunks[1])
		ref := plumbing.NewSymbolicReference(name, target)
		if err := s.SetReference(ref); err != nil {
			return nil
		}
	}

	return nil
}

func (a *AdvRefs) supportSymrefs() bool {
	return a.Capabilities.Supports(capability.SymRef)
}