Skip to content

Commit 0da9d6a

Browse files
Merge pull request #1 from errordeveloper/semver-tagger
Automatic VCS tagging
2 parents 2d961d7 + 61583a2 commit 0da9d6a

File tree

10 files changed

+560
-66
lines changed

10 files changed

+560
-66
lines changed

attest/manifest/manifest.go

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@ import (
44
"cmp"
55

66
"github.com/errordeveloper/tape/attest/types"
7+
attestTypes "github.com/errordeveloper/tape/attest/types"
78
)
89

910
const (
10-
ManifestDirPredicateType = "docker.com/tape/ManifestDir/v0.1"
11+
ManifestDirPredicateType = "docker.com/tape/ManifestDir/v0.2"
1112
)
1213

1314
var (
1415
_ types.Statement = (*DirContents)(nil)
1516
)
1617

1718
type DirContents struct {
18-
types.GenericStatement[SourceDirectory]
19+
types.GenericStatement[SourceDirectoryContents]
1920
}
2021

2122
type SourceDirectory struct {
@@ -24,14 +25,16 @@ type SourceDirectory struct {
2425
VCSEntries *types.PathCheckSummaryCollection `json:"vcsEntries"`
2526
}
2627

28+
type SourceDirectoryContents struct {
29+
SourceDirectory `json:"containedInDirectory"`
30+
}
31+
2732
func MakeDirContentsStatement(dir string, entries *types.PathCheckSummaryCollection) types.Statement {
2833
return &DirContents{
29-
types.MakeStatement[SourceDirectory](
34+
types.MakeStatement[SourceDirectoryContents](
3035
ManifestDirPredicateType,
31-
struct {
32-
SourceDirectory `json:"containedInDirectory"`
33-
}{
34-
SourceDirectory{
36+
SourceDirectoryContents{
37+
SourceDirectory: SourceDirectory{
3538
Path: dir,
3639
VCSEntries: entries,
3740
},
@@ -41,6 +44,14 @@ func MakeDirContentsStatement(dir string, entries *types.PathCheckSummaryCollect
4144
}
4245
}
4346

47+
func MakeDirContentsStatementFrom(statement types.Statement) DirContents {
48+
dirContents := DirContents{
49+
GenericStatement: attestTypes.GenericStatement[SourceDirectoryContents]{},
50+
}
51+
dirContents.ConvertFrom(statement)
52+
return dirContents
53+
}
54+
4455
func (a SourceDirectory) Compare(b SourceDirectory) types.Cmp {
4556
if cmp := cmp.Compare(a.Path, b.Path); cmp != 0 {
4657
return &cmp
@@ -54,3 +65,7 @@ func (a SourceDirectory) Compare(b SourceDirectory) types.Cmp {
5465
cmp := a.VCSEntries.Compare(*b.VCSEntries)
5566
return &cmp
5667
}
68+
69+
func (a SourceDirectoryContents) Compare(b SourceDirectoryContents) types.Cmp {
70+
return a.SourceDirectory.Compare(b.SourceDirectory)
71+
}

attest/registry_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ func makeRegistryTest(tc testdata.TestCase) func(t *testing.T) {
4848
loader := loader.NewRecursiveManifestDirectoryLoader(tc.Directory)
4949
g.Expect(loader.Load()).To(Succeed())
5050

51-
pathChecker, attreg, err := DetectVCS(tc.Directory)
51+
repoDetected, attreg, err := DetectVCS(tc.Directory)
5252
g.Expect(err).NotTo(HaveOccurred())
53-
g.Expect(pathChecker).ToNot(BeNil())
53+
g.Expect(repoDetected).To(BeTrue())
5454
g.Expect(attreg).ToNot(BeNil())
5555

5656
scanner := imagescanner.NewDefaultImageScanner()

attest/types/types.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,30 @@ func Export(s ExportableStatement) toto.Statement {
119119
}
120120
}
121121

122+
func FilterByPredicateType(t string, s Statements) Statements {
123+
results := Statements{}
124+
for i := range s {
125+
if s[i].GetType() == t {
126+
results = append(results, s[i])
127+
}
128+
}
129+
return results
130+
}
131+
132+
type StamentConverter[T any] struct {
133+
Statement
134+
}
135+
136+
func (s *GenericStatement[T]) ConvertFrom(statement Statement) error {
137+
predicate, ok := statement.GetPredicate().(ComparablePredicate[T])
138+
if !ok {
139+
return fmt.Errorf("cannot convert statement with predicte of type %T into %T", statement.GetPredicate(), GenericStatement[T]{})
140+
}
141+
142+
*s = MakeStatement[T](statement.GetType(), predicate, statement.GetSubject()...)
143+
return nil
144+
}
145+
122146
func (s Statements) Export() []toto.Statement {
123147
statements := make([]toto.Statement, len(s))
124148
for i := range s {
@@ -368,8 +392,9 @@ func comparePathCheckSummaries(a, b PathCheckSummary) int {
368392
return cmp.Compare(a.Common().Path, b.Common().Path)
369393
}
370394

371-
func (p Predicate[T]) GetType() string { return p.Type }
372-
func (p Predicate[T]) GetPredicate() any { return p.ComparablePredicate }
395+
func (p Predicate[T]) GetType() string { return p.Type }
396+
func (p Predicate[T]) GetPredicate() any { return p.ComparablePredicate }
397+
func (p Predicate[T]) GetUnderlyingPredicate() T { return p.ComparablePredicate.(T) }
373398

374399
func (p Predicate[T]) Compare(b any) Cmp {
375400
if b, ok := b.(Predicate[T]); ok {

attest/vcs/git/git.go

Lines changed: 129 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ const (
2323
DefaultPrimaryRemoteName = "origin"
2424
)
2525

26-
// TODO: need a way to detect multiple repos, for now PathChecker is only meant
27-
// to be used for the manifest dir iteself, and assume there is no nested repos
28-
2926
func NewPathChecker(path string, digest digest.SHA256) types.PathChecker {
3027
return &PathChecker{
3128
path: path,
@@ -47,16 +44,35 @@ type (
4744
}
4845

4946
GitSummary struct {
50-
ObjectHash *string `json:"objectHash,omitempty"`
51-
Remotes map[string][]string `json:"remotes,omitempty"`
52-
Reference GitReference `json:"reference,omitempty"`
47+
Object GitObject `json:"object,omitempty"`
48+
Remotes map[string][]string `json:"remotes,omitempty"`
49+
Reference GitReference `json:"reference,omitempty"`
50+
}
51+
52+
GitObject struct {
53+
TreeHash string `json:"treeHash,omitempty"`
54+
CommitHash string `json:"commitHash,omitempty"`
55+
}
56+
57+
Signature struct {
58+
PGP []byte `json:"pgp"`
59+
Validated bool `json:"validated"`
60+
}
61+
62+
GitTag struct {
63+
Name string `json:"name"`
64+
Hash string `json:"hash,omitempty"`
65+
Target string `json:"target,omitempty"`
66+
Signature *Signature `json:"signature,omitempty"`
5367
}
5468

5569
GitReference struct {
56-
Name string `json:"name,omitempty"`
57-
Hash string `json:"hash,omitempty"`
58-
Type string `json:"type,omitempty"`
59-
Target string `json:"target,omitempty"`
70+
Name string `json:"name,omitempty"`
71+
Hash string `json:"hash,omitempty"`
72+
Type string `json:"type,omitempty"`
73+
Target string `json:"target,omitempty"`
74+
Tags []GitTag `json:"tags,omitempty"`
75+
Signature *Signature `json:"signature,omitempty"`
6076
}
6177
)
6278

@@ -142,19 +158,101 @@ func (c *PathChecker) MakeSummary() (types.PathCheckSummary, error) {
142158
Git: &git,
143159
}
144160

145-
// TODO: determine position of local branch against remote
146-
// TODO: introduce notion of primary remote branch to determine the possition of the working branch
147-
// TODO: determine if a tag is used
148-
// TODO: also check if local tag in sync wirth remote tag
149-
// TODO: provide info on singed tags/commits
161+
head, err := c.cache.repo.Head()
162+
if err != nil {
163+
return nil, err
164+
}
165+
166+
ref := GitReference{
167+
Name: head.Name().String(),
168+
Hash: head.Hash().String(),
169+
Type: head.Type().String(),
170+
Target: head.Target().String(),
171+
}
150172

173+
obj := &GitObject{}
151174
if summary.Unmodified {
152-
git.ObjectHash = new(string)
153-
*git.ObjectHash = c.cache.obj.ID().String()
175+
obj.TreeHash = c.cache.obj.ID().String()
154176
} else if c.IsBlob() {
155177
// there is currently no easy way to obtain a hash for a subtree
156-
git.ObjectHash = new(string)
157-
*git.ObjectHash = c.cache.blobHash
178+
obj.TreeHash = c.cache.blobHash
179+
}
180+
181+
headCommit, err := c.cache.repo.CommitObject(head.Hash())
182+
if err != nil {
183+
return nil, err
184+
}
185+
if headCommit.PGPSignature != "" {
186+
ref.Signature = &Signature{
187+
PGP: []byte(headCommit.PGPSignature),
188+
Validated: false,
189+
}
190+
}
191+
192+
if summary.Unmodified {
193+
commitIter := object.NewCommitPathIterFromIter(
194+
func(path string) bool {
195+
switch {
196+
case c.IsTree():
197+
return strings.HasPrefix(c.cache.repoPath, path)
198+
case c.IsBlob():
199+
return c.cache.repoPath == path
200+
default:
201+
return false
202+
}
203+
},
204+
object.NewCommitIterCTime(headCommit, nil, nil),
205+
true,
206+
)
207+
defer commitIter.Close()
208+
// only need first commit, avoid looping over all commits with ForEach
209+
commit, err := commitIter.Next()
210+
if err == nil {
211+
obj.CommitHash = commit.Hash.String()
212+
} else if err != io.EOF {
213+
return nil, err
214+
}
215+
}
216+
217+
tags, err := c.cache.repo.Tags()
218+
if err != nil {
219+
return nil, err
220+
}
221+
222+
if err := tags.ForEach(func(t *plumbing.Reference) error {
223+
target, err := c.cache.repo.ResolveRevision(plumbing.Revision(t.Name()))
224+
if err != nil {
225+
return err
226+
}
227+
if *target != head.Hash() {
228+
// doesn't point to HEAD
229+
return nil
230+
}
231+
232+
tag := GitTag{
233+
Name: t.Name().Short(),
234+
Hash: t.Hash().String(),
235+
Target: target.String(),
236+
}
237+
238+
if tag.Target != tag.Hash {
239+
// annotated tags have own object hash, while has of a leightweight tag is the same as target
240+
tagObject, err := c.cache.repo.TagObject(t.Hash())
241+
if err != nil {
242+
return err
243+
}
244+
if tagObject.PGPSignature != "" {
245+
tag.Signature = &Signature{
246+
PGP: []byte(tagObject.PGPSignature),
247+
Validated: false,
248+
}
249+
}
250+
}
251+
252+
ref.Tags = append(ref.Tags, tag)
253+
return nil
254+
}); err != nil {
255+
return nil, err
158256
}
159257

160258
remotes, err := c.cache.repo.Remotes()
@@ -189,17 +287,8 @@ func (c *PathChecker) MakeSummary() (types.PathCheckSummary, error) {
189287
git.Remotes[remoteConfig.Name] = remoteConfig.URLs
190288
}
191289

192-
head, err := c.cache.repo.Head()
193-
if err != nil {
194-
return nil, err
195-
}
196-
197-
git.Reference = GitReference{
198-
Name: head.Name().String(),
199-
Hash: head.Hash().String(),
200-
Type: head.Type().String(),
201-
Target: head.Target().String(),
202-
}
290+
git.Reference = ref
291+
git.Object = *obj
203292

204293
return summary, nil
205294
}
@@ -288,6 +377,10 @@ func (c *PathChecker) Check() (bool, bool, error) {
288377
}
289378
unmodified, _, err := isBlobUnmodified(worktree, &f.Blob, filepath.Join(repoPath, f.Name))
290379
if err != nil {
380+
if f.Mode == filemode.Symlink {
381+
// TODO: should at least log a warning for broken symlink
382+
return nil
383+
}
291384
return err
292385
}
293386
if !unmodified {
@@ -419,6 +512,9 @@ func findByPath(repo *gogit.Repository, path string) (object.Object, error) {
419512
if err != nil {
420513
return nil, err
421514
}
515+
if path == "." {
516+
return tree, nil
517+
}
422518
treeEntry, err := tree.FindEntry(path)
423519
switch err {
424520
case nil:
@@ -438,12 +534,12 @@ func findByPath(repo *gogit.Repository, path string) (object.Object, error) {
438534
}
439535

440536
func detectRepo(path string) (*gogit.Repository, bool) {
537+
if repo, err := gogit.PlainOpen(path); err == nil {
538+
return repo, true
539+
}
441540
dir := filepath.Dir(path)
442541
if dir == path { // reached root
443542
return nil, false
444543
}
445-
if repo, err := gogit.PlainOpen(dir); err == nil {
446-
return repo, true
447-
}
448544
return detectRepo(dir)
449545
}

0 commit comments

Comments
 (0)