package chglog import ( "fmt" "os" "sort" "github.com/Masterminds/semver/v3" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" ) func versionsInRepo(gitRepo *git.Repository) (map[plumbing.Hash]*semver.Version, error) { tagRefs, err := gitRepo.Tags() if err != nil { return nil, err } defer tagRefs.Close() tags := make(map[plumbing.Hash]*semver.Version) err = tagRefs.ForEach(func(t *plumbing.Reference) error { var ( version *semver.Version tag *object.Tag ) tagName := t.Name().Short() hash := t.Hash() if version, err = semver.NewVersion(tagName); err != nil || version == nil { fmt.Fprintf(os.Stderr, "Warning: unable to parse version from tag: %s : %v\n", tagName, err) return nil } // If this is an annotated tag look up the hash of the commit and use that. if tag, err = gitRepo.TagObject(t.Hash()); err == nil { var c *object.Commit if c, err = tag.Commit(); err != nil { return fmt.Errorf("cannot dereference annotated tag: %s : %w", tagName, err) } hash = c.Hash } tags[hash] = version return nil }) if err != nil { return nil, err } return tags, nil } func versionsOnBranch(gitRepo *git.Repository) (map[*semver.Version]plumbing.Hash, error) { repoVersions, err := versionsInRepo(gitRepo) if err != nil { return nil, err } refs, err := gitRepo.Log(&git.LogOptions{}) if err != nil { return nil, err } defer refs.Close() versions := make(map[*semver.Version]plumbing.Hash) err = refs.ForEach(func(c *object.Commit) error { if v, ok := repoVersions[c.Hash]; ok { versions[v] = c.Hash } return nil }) return versions, err } // InitChangelog create a new ChangeLogEntries from a git repo. func InitChangelog(gitRepo *git.Repository, owner string, notes *ChangeLogNotes, deb *ChangelogDeb, useConventionalCommits bool, excludeMergeCommits bool) (cle ChangeLogEntries, err error) { var start, end plumbing.Hash cle = make(ChangeLogEntries, 0) end = plumbing.ZeroHash versions, err := versionsOnBranch(gitRepo) if err != nil { return nil, err } tags := make([]*semver.Version, 0, len(versions)) for v := range versions { tags = append(tags, v) } sort.Slice(tags, func(i, j int) bool { return tags[i].LessThan(tags[j]) }) for _, version := range tags { var ( commits []*object.Commit commitObject *object.Commit versionOwner = owner ) if version.Prerelease() != "" { // Do not need change logs for pre-release entries continue } start = versions[version] if commitObject, err = gitRepo.CommitObject(start); err != nil { return nil, fmt.Errorf("unable to fetch commit from tag %v: %w", version.Original(), err) } if versionOwner == "" { versionOwner = fmt.Sprintf("%s <%s>", commitObject.Author.Name, commitObject.Author.Email) } if commits, err = CommitsBetween(gitRepo, start, end, excludeMergeCommits); err != nil { return nil, fmt.Errorf("unable to find commits between %s & %s: %w", end, start, err) } changelog := CreateEntry(commitObject.Committer.When, version, versionOwner, notes, deb, commits, useConventionalCommits) cle = append(cle, changelog) end = start } sort.Sort(sort.Reverse(cle)) return cle, nil }