-
Notifications
You must be signed in to change notification settings - Fork 131
Expand file tree
/
Copy pathgit.go
More file actions
278 lines (242 loc) · 8.83 KB
/
git.go
File metadata and controls
278 lines (242 loc) · 8.83 KB
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
/*
Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package git
import (
"errors"
"fmt"
"strings"
"time"
"github.com/fluxcd/pkg/git/signatures"
)
const (
// HashTypeSHA1 is the SHA1 hash algorithm.
HashTypeSHA1 = "sha1"
// HashTypeUnknown is an unknown hash algorithm.
HashTypeUnknown = "<unknown>"
)
// Hash is the (non-truncated) SHA-1 or SHA-256 hash of a Git commit.
type Hash []byte
// Algorithm returns the algorithm of the hash based on its length.
// This is heuristic, and may not be accurate for truncated user constructed
// hashes. The library itself does not produce truncated hashes.
func (h Hash) Algorithm() string {
switch len(h) {
case 40:
return HashTypeSHA1
default:
return HashTypeUnknown
}
}
// Digest returns a digest of the commit, in the format of "<algorithm>:<hash>".
func (h Hash) Digest() string {
if len(h) == 0 {
return ""
}
return fmt.Sprintf("%s:%s", h.Algorithm(), h)
}
// String returns the Hash as a string.
func (h Hash) String() string {
return string(h)
}
// Signature represents an entity which associates a person and a time
// with a commit.
type Signature struct {
Name string
Email string
When time.Time
}
// Commit contains all possible information about a Git commit.
type Commit struct {
// Hash is the hash of the commit.
Hash Hash
// Reference is the original reference of the commit, for example:
// 'refs/tags/foo'.
Reference string
// Author is the original author of the commit.
Author Signature
// Committer is the one performing the commit, might be different from
// Author.
Committer Signature
// Signature is the PGP signature of the commit.
Signature string
// Encoded is the encoded commit, without any signature.
Encoded []byte
// Message is the commit message, containing arbitrary text.
Message string
// ReferencingTag is the tag that points to this commit.
ReferencingTag *Tag
}
// String returns a string representation of the Commit, composed
// out of the last part of the Reference element (if not empty) and Hash.
// For example: 'tag-1@sha1:a0c14dc8580a23f79bc654faa79c4f62b46c2c22',
// for a "refs/tags/tag-1" Reference.
func (c *Commit) String() string {
if short := strings.SplitAfterN(c.Reference, "/", 3); len(short) == 3 {
return fmt.Sprintf("%s@%s", short[2], c.Hash.Digest())
}
return c.Hash.Digest()
}
// AbsoluteReference returns a string representation of the Commit, composed
// out of the Reference element (if not empty) and Hash.
// For example: 'refs/tags/tag-1@sha1:a0c14dc8580a23f79bc654faa79c4f62b46c2c22'
// for a "refs/tags/tag-1" Reference.
func (c *Commit) AbsoluteReference() string {
if c.Reference != "" {
return fmt.Sprintf("%s@%s", c.Reference, c.Hash.Digest())
}
return c.Hash.Digest()
}
// Deprecated: Verify is deprecated, use VerifySSH or VerifyGPG
// wrapper function to ensure backwards compatibility
func (c *Commit) Verify(keyRings ...string) (string, error) {
return c.VerifyGPG(keyRings...)
}
// Verify the Signature of the commit with the given key rings.
// It returns the fingerprint of the key the signature was verified
// with, or an error. It does not verify the signature of the referencing
// tag (if present). Users are expected to explicitly verify the referencing
// tag's signature using `c.ReferencingTag.Verify()`
func (c *Commit) VerifyGPG(keyRings ...string) (string, error) {
fingerprint, err := signatures.VerifyPGPSignature(c.Signature, c.Encoded, keyRings...)
if err != nil {
return "", fmt.Errorf("unable to verify Git commit: %w", err)
}
return fingerprint, nil
}
// VerifySSH verifies the SSH signature of the commit with the given authorized keys.
// It returns the fingerprint of the key the signature was verified with, or an error.
// It does not verify the signature of the referencing tag (if present). Users are
// expected to explicitly verify the referencing tag's signature using `c.ReferencingTag.VerifySSH()`
func (c *Commit) VerifySSH(authorizedKeys ...string) (string, error) {
fingerprint, err := signatures.VerifySSHSignature(c.Signature, c.Encoded, authorizedKeys...)
if err != nil {
return "", fmt.Errorf("unable to verify Git commit SSH signature: %w", err)
}
return fingerprint, nil
}
// ShortMessage returns the first 50 characters of a commit subject.
func (c *Commit) ShortMessage() string {
subject := strings.Split(c.Message, "\n")[0]
r := []rune(subject)
if len(r) > 50 {
return fmt.Sprintf("%s...", string(r[0:50]))
}
return subject
}
// Tag represents a Git tag.
type Tag struct {
// Hash is the hash of the tag.
Hash Hash
// Name is the name of the tag.
Name string
// Author is the original author of the tag.
Author Signature
// Signature is the PGP signature of the tag.
Signature string
// Encoded is the encoded tag, without any signature.
Encoded []byte
// Message is the tag message, containing arbitrary text.
Message string
}
// Deprecated: Verify is deprecated, use VerifySSH or VerifyGPG
// wrapper function to ensure backwards compatibility
func (t *Tag) Verify(keyRings ...string) (string, error) {
return t.VerifyGPG(keyRings...)
}
// Verify the Signature of the tag with the given key rings.
// It returns the fingerprint of the key the signature was verified
// with, or an error.
func (t *Tag) VerifyGPG(keyRings ...string) (string, error) {
fingerprint, err := signatures.VerifyPGPSignature(t.Signature, t.Encoded, keyRings...)
if err != nil {
return "", fmt.Errorf("unable to verify Git tag: %w", err)
}
return fingerprint, nil
}
// VerifySSH verifies the SSH signature of the tag with the given authorized keys.
// It returns the fingerprint of the key the signature was verified with, or an error.
func (t *Tag) VerifySSH(authorizedKeys ...string) (string, error) {
fingerprint, err := signatures.VerifySSHSignature(t.Signature, t.Encoded, authorizedKeys...)
if err != nil {
return "", fmt.Errorf("unable to verify Git tag SSH signature: %w", err)
}
return fingerprint, nil
}
// String returns a short string representation of the tag in the format
// of <name@hash>, for eg: "1.0.0@a0c14dc8580a23f79bc654faa79c4f62b46c2c22"
// If the tag is lightweight, it won't have a hash, so it'll simply return
// the tag name, i.e. "1.0.0".
func (t *Tag) String() string {
if len(t.Hash) == 0 {
return t.Name
}
return fmt.Sprintf("%s@%s", t.Name, t.Hash.String())
}
// ErrRepositoryNotFound indicates that the repository (or the ref in
// question) does not exist at the given URL.
type ErrRepositoryNotFound struct {
Message string
URL string
}
func (e ErrRepositoryNotFound) Error() string {
return fmt.Sprintf("%s: git repository: '%s'", e.Message, e.URL)
}
var (
ErrNoGitRepository = errors.New("no git repository")
ErrNoStagedFiles = errors.New("no staged files")
)
// IsConcreteCommit returns if a given commit is a concrete commit. Concrete
// commits have most of the commit metadata and content. In contrast, a partial
// commit may only have some metadata and no commit content.
func IsConcreteCommit(c Commit) bool {
if c.Hash != nil && c.Encoded != nil {
return true
}
return false
}
// IsAnnotatedTag returns true if the provided tag is annotated.
func IsAnnotatedTag(t Tag) bool {
return len(t.Encoded) > 0
}
// IsSignedTag returns true if the provided tag has a signature.
func IsSignedTag(t Tag) bool {
return t.Signature != ""
}
// IsPGPSigned returns true if the commit has a PGP signature.
func (c *Commit) IsPGPSigned() bool {
return signatures.IsPGPSignature(c.Signature)
}
// IsSSHSigned returns true if the commit has an SSH signature.
func (c *Commit) IsSSHSigned() bool {
return signatures.IsSSHSignature(c.Signature)
}
// SignatureType returns the type of the commit signature as a string.
// It returns "pgp" for PGP signatures, "ssh" for SSH signatures,
// and "unknown" for unrecognized or empty signatures.
func (c *Commit) SignatureType() string {
return signatures.GetSignatureType(c.Signature)
}
// IsPGPSigned returns true if the tag has a PGP signature.
func (t *Tag) IsPGPSigned() bool {
return signatures.IsPGPSignature(t.Signature)
}
// IsSSHSigned returns true if the tag has an SSH signature.
func (t *Tag) IsSSHSigned() bool {
return signatures.IsSSHSignature(t.Signature)
}
// SignatureType returns the type of the tag signature as a string.
// It returns "pgp" for PGP signatures, "ssh" for SSH signatures,
// and "unknown" for unrecognized or empty signatures.
func (t *Tag) SignatureType() string {
return signatures.GetSignatureType(t.Signature)
}