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
|
package localrepo
import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"io/ioutil"
"strings"
"gitlab.com/gitlab-org/gitaly/internal/command"
"gitlab.com/gitlab-org/gitaly/internal/git"
)
// HasRevision checks if a revision in the repository exists. This will not
// verify whether the target object exists. To do so, you can peel the revision
// to a given object type, e.g. by passing `refs/heads/master^{commit}`.
func (repo *Repo) HasRevision(ctx context.Context, revision git.Revision) (bool, error) {
if _, err := repo.ResolveRevision(ctx, revision); err != nil {
if errors.Is(err, git.ErrReferenceNotFound) {
return false, nil
}
return false, err
}
return true, nil
}
// ResolveRevision resolves the given revision to its object ID. This will not
// verify whether the target object exists. To do so, you can peel the
// reference to a given object type, e.g. by passing
// `refs/heads/master^{commit}`. Returns an ErrReferenceNotFound error in case
// the revision does not exist.
func (repo *Repo) ResolveRevision(ctx context.Context, revision git.Revision) (git.ObjectID, error) {
if revision.String() == "" {
return "", errors.New("repository cannot contain empty reference name")
}
var stdout bytes.Buffer
if err := repo.ExecAndWait(ctx,
git.SubCmd{
Name: "rev-parse",
Flags: []git.Option{git.Flag{Name: "--verify"}},
Args: []string{revision.String()},
},
git.WithStderr(ioutil.Discard),
git.WithStdout(&stdout),
); err != nil {
if _, ok := command.ExitStatus(err); ok {
return "", git.ErrReferenceNotFound
}
return "", err
}
hex := strings.TrimSpace(stdout.String())
oid, err := git.NewObjectIDFromHex(hex)
if err != nil {
return "", fmt.Errorf("unsupported object hash %q: %w", hex, err)
}
return oid, nil
}
// GetReference looks up and returns the given reference. Returns a
// ReferenceNotFound error if the reference was not found.
func (repo *Repo) GetReference(ctx context.Context, reference git.ReferenceName) (git.Reference, error) {
refs, err := repo.getReferences(ctx, 1, reference.String())
if err != nil {
return git.Reference{}, err
}
if len(refs) == 0 {
return git.Reference{}, git.ErrReferenceNotFound
}
if refs[0].Name != reference {
return git.Reference{}, git.ErrReferenceAmbiguous
}
return refs[0], nil
}
// HasBranches determines whether there is at least one branch in the
// repository.
func (repo *Repo) HasBranches(ctx context.Context) (bool, error) {
refs, err := repo.getReferences(ctx, 1, "refs/heads/")
return len(refs) > 0, err
}
// GetReferences returns references matching any of the given patterns. If no patterns are given,
// all references are returned.
func (repo *Repo) GetReferences(ctx context.Context, patterns ...string) ([]git.Reference, error) {
return repo.getReferences(ctx, 0, patterns...)
}
func (repo *Repo) getReferences(ctx context.Context, limit uint, patterns ...string) ([]git.Reference, error) {
flags := []git.Option{git.Flag{Name: "--format=%(refname)%00%(objectname)%00%(symref)"}}
if limit > 0 {
flags = append(flags, git.Flag{Name: fmt.Sprintf("--count=%d", limit)})
}
cmd, err := repo.Exec(ctx, git.SubCmd{
Name: "for-each-ref",
Flags: flags,
Args: patterns,
})
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(cmd)
var refs []git.Reference
for scanner.Scan() {
line := bytes.SplitN(scanner.Bytes(), []byte{0}, 3)
if len(line) != 3 {
return nil, errors.New("unexpected reference format")
}
if len(line[2]) == 0 {
refs = append(refs, git.NewReference(git.ReferenceName(line[0]), string(line[1])))
} else {
refs = append(refs, git.NewSymbolicReference(git.ReferenceName(line[0]), string(line[1])))
}
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("reading standard input: %v", err)
}
if err := cmd.Wait(); err != nil {
return nil, err
}
return refs, nil
}
// GetBranches returns all branches.
func (repo *Repo) GetBranches(ctx context.Context) ([]git.Reference, error) {
return repo.GetReferences(ctx, "refs/heads/")
}
// UpdateRef updates reference from oldValue to newValue. If oldValue is a
// non-empty string, the update will fail it the reference is not currently at
// that revision. If newValue is the ZeroOID, the reference will be deleted.
// If oldValue is the ZeroOID, the reference will created.
func (repo *Repo) UpdateRef(ctx context.Context, reference git.ReferenceName, newValue, oldValue git.ObjectID) error {
var stderr bytes.Buffer
if err := repo.ExecAndWait(ctx,
git.SubCmd{
Name: "update-ref",
Flags: []git.Option{git.Flag{Name: "-z"}, git.Flag{Name: "--stdin"}},
},
git.WithStdin(strings.NewReader(fmt.Sprintf("update %s\x00%s\x00%s\x00", reference, newValue.String(), oldValue.String()))),
git.WithStderr(&stderr),
git.WithRefTxHook(ctx, repo, repo.cfg),
); err != nil {
return fmt.Errorf("UpdateRef: failed updating reference %q from %q to %q: %w", reference, oldValue, newValue, errorWithStderr(err, stderr.Bytes()))
}
return nil
}
// GetRemoteReferences lists references of the remote. Multiple patterns can be supplied to filter the references on the
// remote down to the needed ones. Symbolic references are dereferenced. Peeled tags are not returned.
func (repo *Repo) GetRemoteReferences(ctx context.Context, remote string, patterns ...string) ([]git.Reference, error) {
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
if err := repo.ExecAndWait(ctx,
git.SubCmd{
Name: "ls-remote",
Flags: []git.Option{
git.Flag{Name: "--refs"},
},
Args: append([]string{remote}, patterns...),
},
git.WithStdout(stdout),
git.WithStderr(stderr),
); err != nil {
return nil, fmt.Errorf("create git ls-remote: %w, stderr: %q", err, stderr)
}
var refs []git.Reference
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
split := strings.SplitN(string(scanner.Bytes()), "\t", 2)
if len(split) != 2 {
return nil, fmt.Errorf("invalid ls-remote output line: %q", scanner.Bytes())
}
refs = append(refs, git.NewReference(git.ReferenceName(split[1]), split[0]))
}
return refs, nil
}
|