Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add annotated tag support #109

Merged
merged 6 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions chronicle/release/changelog_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ func ChangelogInfo(summer Summarizer, config ChangelogInfoConfig) (*Release, *De
}

if startRelease != nil {
log.WithFields("tag", startRelease.Version, "release-timestamp", internal.FormatDateTime(startRelease.Date)).Info("since")
log.WithFields("tag", startRelease.Version, "release-timestamp", internal.FormatDateTime(startRelease.Date)).Trace("since")
} else {
log.Info("since the beginning of history")
log.Trace("since the beginning of git history")
}

releaseVersion, changes, err := changelogChanges(startRelease.Version, summer, config)
Expand Down Expand Up @@ -96,7 +96,7 @@ func speculateNextVersion(speculator VersionSpeculator, startReleaseVersion stri
if nextUniqueVersion != nextIdealVersion {
log.Debugf("speculated a release version that matches an existing tag=%q, selecting the next best version...", nextIdealVersion)
}
log.Infof("speculative release version=%q", nextUniqueVersion)
log.WithFields("version", nextUniqueVersion).Info("speculative release version")
return nextUniqueVersion, nil
}

Expand Down
4 changes: 2 additions & 2 deletions chronicle/release/releasers/github/find_changelog_end_tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ func FindChangelogEndTag(summer release.Summarizer, gitter git.Interface) (strin
// no release found, assume that this is the correct release info
return "", fmt.Errorf("unable to fetch release=%q : %w", currentTag, err)
} else if taggedRelease != nil {
log.Debugf("found existing tag=%q however, it already has an associated release. ignoring...", currentTag)
log.WithFields("tag", currentTag).Debug("found existing tag however, it already has an associated release. ignoring...")
// return commitRef, nil
return "", nil
}

log.Debugf("found existing tag=%q at HEAD which does not have an associated release", currentTag)
log.WithFields("tag", currentTag).Debug("found existing tag at HEAD which does not have an associated release")

// a tag was found and there is no existing release for this tag
return currentTag, nil
Expand Down
2 changes: 1 addition & 1 deletion chronicle/release/releasers/github/gh_issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ func fetchClosedIssues(user, repo string, since *time.Time) ([]ghIssue, error) {
httpClient := oauth2.NewClient(context.Background(), src)
client := githubv4.NewClient(httpClient)
var (
pages = 0
pages = 1
saw = 0
allIssues []ghIssue
)
Expand Down
2 changes: 1 addition & 1 deletion chronicle/release/releasers/github/gh_pull_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ func fetchMergedPRs(user, repo string, since *time.Time) ([]ghPullRequest, error
httpClient := oauth2.NewClient(context.Background(), src)
client := githubv4.NewClient(httpClient)
var (
pages = 0
pages = 1
saw = 0
allPRs []ghPullRequest
)
Expand Down
53 changes: 44 additions & 9 deletions chronicle/release/releasers/github/summarizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/anchore/chronicle/internal"
"github.com/anchore/chronicle/internal/git"
"github.com/anchore/chronicle/internal/log"
"github.com/anchore/go-logger"
)

const (
Expand Down Expand Up @@ -127,6 +128,8 @@ func (s *Summarizer) Changes(sinceRef, untilRef string) ([]change.Change, error)
return nil, fmt.Errorf("unable to find start and end of changes: %w", err)
}

logChangeScope(*scope, s.config.ConsiderPRMergeCommits)

return s.changes(*scope)
}

Expand All @@ -141,13 +144,13 @@ func (s *Summarizer) getChangeScope(sinceRef, untilRef string) (*changeScope, er
if untilRef != "" {
untilTag, err = s.git.SearchForTag(untilRef)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to find git tag %q: %w", untilRef, err)
}
untilTime = &untilTag.Timestamp
} else {
untilRef, err = s.git.HeadTagOrCommit()
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to find git head reference: %w", err)
}
}

Expand Down Expand Up @@ -189,7 +192,7 @@ func (s *Summarizer) getSince(sinceRef string) (*git.Tag, string, bool, *time.Ti
if sinceRef != "" {
sinceTag, err = s.git.SearchForTag(sinceRef)
if err != nil {
return nil, "", false, nil, err
return nil, "", false, nil, fmt.Errorf("unable to find git tag %q: %w", sinceRef, err)
}
}

Expand Down Expand Up @@ -218,11 +221,6 @@ func (s *Summarizer) getSince(sinceRef string) (*git.Tag, string, bool, *time.Ti
func (s *Summarizer) changes(scope changeScope) ([]change.Change, error) {
var changes []change.Change

if s.config.ConsiderPRMergeCommits {
log.Debugf("release comprises %d commits", len(scope.Commits))
logCommits(scope.Commits)
}

allMergedPRs, err := fetchMergedPRs(s.userName, s.repoName, scope.Start.Timestamp)
if err != nil {
return nil, err
Expand Down Expand Up @@ -264,13 +262,50 @@ func (s *Summarizer) changes(scope changeScope) ([]change.Change, error) {
return changes, nil
}

func logChangeScope(c changeScope, considerCommits bool) {
log.WithFields("since", c.Start.Ref, "until", c.End.Ref).Info("searching for changes")
log.WithFields(changePointFields(c.Start)).Debug(" ├── since")
log.WithFields(changePointFields(c.End)).Debug(" └── until")

if considerCommits {
log.Debugf("release comprises %d commits", len(c.Commits))
logCommits(c.Commits)
}

// in a release process there tends to be a start point that is a github release and an end point that is a git tag.
// in cases where the git tag is a lightweight tag encourage users to migrate to using annotated tags since
// the annotated tag will have a timestamp associated with when the tag action was done and not when the PR merge
// to main was done.
// From https://git-scm.com/docs/git-tag:
// > Annotated tags are meant for release while lightweight tags are meant for private or temporary object labels.
if c.End.Tag != nil && !c.End.Tag.Annotated {
log.WithFields("tag", c.End.Tag.Name).Warn("use of a lightweight git tag found, use annotated git tags for more accurate results")
wagoodman marked this conversation as resolved.
Show resolved Hide resolved
}
}

func changePointFields(p changePoint) logger.Fields {
fields := logger.Fields{}
if p.Tag != nil {
fields["tag"] = p.Tag.Name
fields["commit"] = p.Tag.Commit
} else if p.Ref != "" {
fields["commit"] = p.Ref
}
fields["inclusive"] = p.Inclusive
if p.Timestamp != nil {
fields["timestamp"] = internal.FormatDateTime(*p.Timestamp)
}

return fields
}

func logCommits(commits []string) {
for idx, commit := range commits {
var branch = treeBranch
if idx == len(commits)-1 {
branch = treeLeaf
}
log.Tracef(" %s %s", branch, commit)
log.Debugf(" %s %s", branch, commit)
}
}

Expand Down
4 changes: 2 additions & 2 deletions cmd/chronicle/cli/commands/create_github_worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ func createChangelogFromGithub(appConfig *createConfig) (*release.Release, *rele
}

if untilTag != "" {
log.WithFields("tag", untilTag).Infof("until")
log.WithFields("tag", untilTag).Trace("until the given tag")
} else {
log.Infof("until the current revision")
log.Trace("until the current revision")
}

var speculator release.VersionSpeculator
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/coreos/go-semver v0.3.1
github.com/gkampitakis/go-snaps v0.4.10
github.com/go-git/go-git/v5 v5.9.0
github.com/google/go-cmp v0.5.9
github.com/leodido/go-conventionalcommits v0.11.0
github.com/scylladb/go-set v1.0.2
github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa
Expand Down
51 changes: 28 additions & 23 deletions internal/git/head.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ import (
)

func HeadTagOrCommit(repoPath string) (string, error) {
return headTag(repoPath, true)
}

func HeadTag(repoPath string) (string, error) {
return headTag(repoPath, false)
}

func headTag(repoPath string, orCommit bool) (string, error) {
r, err := git.PlainOpen(repoPath)
if err != nil {
return "", fmt.Errorf("unable to open repo: %w", err)
Expand All @@ -16,46 +24,43 @@ func HeadTagOrCommit(repoPath string) (string, error) {
if err != nil {
return "", fmt.Errorf("unable fetch head: %w", err)
}

tagRefs, _ := r.Tags()
var tagName string

_ = tagRefs.ForEach(func(t *plumbing.Reference) error {
if t.Hash().String() == ref.Hash().String() {
// for lightweight tags
tagName = t.Name().Short()
return fmt.Errorf("found")
}
return nil
})

if tagName != "" {
return tagName, nil
}

return ref.Hash().String(), nil
}
// this is an annotated tag... since annotated tags are stored within their own commit we need to resolve the
// revision to get the commit the tag object points to (that is the commit with the code blob).
revHash, err := r.ResolveRevision(plumbing.Revision(t.Name()))
if err != nil {
return nil
}

func HeadTag(repoPath string) (string, error) {
r, err := git.PlainOpen(repoPath)
if err != nil {
return "", fmt.Errorf("unable to open repo: %w", err)
}
ref, err := r.Head()
if err != nil {
return "", fmt.Errorf("unable fetch head: %w", err)
}
tagRefs, _ := r.Tags()
var tagName string
if revHash == nil {
return nil
}

_ = tagRefs.ForEach(func(t *plumbing.Reference) error {
if t.Hash().String() == ref.Hash().String() {
if *revHash == ref.Hash() {
tagName = t.Name().Short()
return fmt.Errorf("found")
}
return nil
})

// note: if there is no tag, then an empty value is returned
return tagName, nil
if tagName != "" {
return tagName, nil
}

if orCommit {
return ref.Hash().String(), nil
}
return "", nil
}

func HeadCommit(repoPath string) (string, error) {
Expand Down
5 changes: 5 additions & 0 deletions internal/git/head_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ func TestHeadTagOrCommit(t *testing.T) {
path: "test-fixtures/repos/tagged-repo",
expects: "v0.1.0",
},
{
name: "head has annotated tag",
path: "test-fixtures/repos/annotated-tagged-repo",
expects: "v0.1.0",
},
{
name: "head has no tag",
path: "test-fixtures/repos/commit-in-repo",
Expand Down
74 changes: 55 additions & 19 deletions internal/git/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Tag struct {
Name string
Timestamp time.Time
Commit string
Annotated bool
}

type Range struct {
Expand Down Expand Up @@ -50,7 +51,7 @@ func CommitsBetween(repoPath string, cfg Range) ([]string, error) {
return nil, fmt.Errorf("unable to find until git log for ref=%q: %w", cfg.UntilRef, err)
}

log.WithFields("since", sinceHash, "until", untilHash, "include-end", cfg.IncludeEnd, "include-start", cfg.IncludeStart).Trace("searching commit range")
log.WithFields("since", sinceHash, "until", untilHash).Trace("searching commit range")

var commits []string
err = iter.ForEach(func(c *object.Commit) (retErr error) {
Expand Down Expand Up @@ -91,16 +92,7 @@ func SearchForTag(repoPath, tagRef string) (*Tag, error) {
return nil, fmt.Errorf("unable to find git ref=%q", tagRef)
}

commit, err := r.CommitObject(ref.Hash())
if err != nil {
return nil, err
}

return &Tag{
Name: tagRef,
Timestamp: commit.Committer.When,
Commit: commit.Hash.String(),
}, nil
return newTag(r, ref)
}

func TagsFromLocal(repoPath string) ([]Tag, error) {
Expand All @@ -109,31 +101,75 @@ func TagsFromLocal(repoPath string) ([]Tag, error) {
return nil, err
}

tagrefs, err := r.Tags()
tagRefs, err := r.Tags()
if err != nil {
return nil, err
}

var tags []Tag
for {
t, err := tagrefs.Next()
t, err := tagRefs.Next()
if err == io.EOF || t == nil {
break
} else if err != nil {
return nil, err
}

c, err := r.CommitObject(t.Hash())
tag, err := newTag(r, t)
if err != nil {
log.Debugf("unable to get tag '%s' info from commit=%q: %w", t.Name().String(), t.Hash().String(), err)
return nil, err
}
if tag == nil {
continue
}

tags = append(tags, Tag{
tags = append(tags, *tag)
}
return tags, nil
}

func newTag(r *git.Repository, t *plumbing.Reference) (*Tag, error) {
// the plumbing reference is to the tag. For a lightweight tag, the tag object points directly to the commit
// with the code blob. For an annotated tag, the tag object has a commit for the tag itself, but resolves to
// the commit with the code blob. It's important to use the timestamp from the tag object when available
// for annotated tags and to use the commit timestamp for lightweight tags.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this comment! It's really helpful.


if !t.Name().IsTag() {
return nil, nil
}

c, err := r.CommitObject(t.Hash())
if err == nil && c != nil {
// this is a lightweight tag... the tag hash points directly to the commit object
return &Tag{
Name: t.Name().Short(),
Timestamp: c.Committer.When,
Commit: t.Hash().String(),
})
Commit: c.Hash.String(),
Annotated: false,
}, nil
}
return tags, nil

// this is an annotated tag... the tag hash points to a tag object, which points to the commit object
// use the timestamp info from the tag object

tagObj, err := object.GetTag(r.Storer, t.Hash())
if err != nil {
return nil, fmt.Errorf("unable to resolve tag for %q: %w", t.Name(), err)
}

if tagObj == nil {
return nil, fmt.Errorf("unable to resolve tag for %q", t.Name())
}

return &Tag{
Name: t.Name().Short(),
// it is possible for this git lib to return timestamps parsed from the underlying data that have the timezone
// but not the name of the timezone. This can result in odd suffixes like "-0400 -0400" instead of "-0400 EDT".
// This causes some difficulty in testing since the user's local git config and env may result in different
// values. Here I've normalized to the local timezone which tends to be the most common case. Downstream of
// this function, the timestamp is converted to UTC.
Timestamp: tagObj.Tagger.When.In(time.Local),
wagoodman marked this conversation as resolved.
Show resolved Hide resolved
Commit: tagObj.Target.String(),
Annotated: true,
}, nil
}
Loading