From f1b27297312c28ffbd77fcce39905eaaa7ee2fb6 Mon Sep 17 00:00:00 2001 From: Joshua Benjamin Date: Wed, 17 Oct 2018 09:27:01 -0700 Subject: [PATCH] (+semver: feature) Handle merge commits in master (#12) Signed-off-by: Joshua Benjamin --- pkg/git/branchWalker.go | 223 ++++++++++++++++++++++++++++++++++++++++ pkg/git/git.go | 122 ++-------------------- 2 files changed, 234 insertions(+), 111 deletions(-) create mode 100644 pkg/git/branchWalker.go diff --git a/pkg/git/branchWalker.go b/pkg/git/branchWalker.go new file mode 100644 index 0000000..3059a02 --- /dev/null +++ b/pkg/git/branchWalker.go @@ -0,0 +1,223 @@ +package git + +import ( + "regexp" + + "gopkg.in/src-d/go-git.v4/plumbing" + + "github.com/coreos/go-semver/semver" + "github.com/pkg/errors" + "gopkg.in/src-d/go-git.v4" + "gopkg.in/src-d/go-git.v4/plumbing/object" +) + +type branchWalker struct { + repository *git.Repository + head *object.Commit + tagMap map[string]string + settings *Settings + isMaster bool + endHash string + + visited map[string]bool + commitsToReconcile map[string]*gitVersion +} + +type versionHolder struct { + versionMap []*gitVersion +} + +func newBranchWalker(repository *git.Repository, head *object.Commit, tagMap map[string]string, settings *Settings, isMaster bool, endHash string) *branchWalker { + return &branchWalker{ + repository: repository, + head: head, + settings: settings, + tagMap: tagMap, + isMaster: isMaster, + endHash: endHash, + visited: make(map[string]bool), + commitsToReconcile: make(map[string]*gitVersion), + } +} + +func (b *branchWalker) GetVersion() (*semver.Version, error) { + versionMap, err := b.GetVersionMap() + if err != nil { + return nil, err + } + + var baseVersion *semver.Version + index := len(versionMap) - 1 + if versionMap[index].IsSolid { + baseVersion = versionMap[index].Name + index-- + } else { + baseVersion, err = semver.NewVersion("0.0.0") + if err != nil { + return nil, err + } + } + + if index < 0 { + return baseVersion, nil + } + + for ; index >= 0; index-- { + v := versionMap[index] + switch { + case v.MajorBump: + baseVersion.BumpMajor() + case v.MinorBump: + baseVersion.BumpMinor() + case v.PatchBump: + baseVersion.BumpPatch() + default: // every commit in master has at least a patch bump + baseVersion.BumpPatch() + } + } + + return baseVersion, nil +} + +func (b *branchWalker) GetVersionMap() ([]*gitVersion, error) { + versionMap := versionHolder{ + versionMap: []*gitVersion{}, + } + + err := b.walkVersion(b.head, &versionMap, false) + if err != nil { + return nil, err + } + + if b.isMaster { + for hash, version := range b.commitsToReconcile { + err = b.reconcileCommit(hash, version) + if err != nil { + return nil, err + } + } + } + + return versionMap.versionMap, nil +} + +func (b *branchWalker) walkVersion(ref *object.Commit, version *versionHolder, tilVisited bool) error { + if _, visited := b.visited[ref.Hash.String()]; tilVisited && visited { + return nil + } + + b.visited[ref.Hash.String()] = true + + tag, ok := b.tagMap[ref.Hash.String()] + if ok { + tagVersion, err := semver.NewVersion(tag) + if err != nil { + return err + } + version.versionMap = append(version.versionMap, &gitVersion{IsSolid: true, Name: tagVersion}) + return nil + } + + parents := ref.NumParents() + if parents > 1 { + versionToReconcile := gitVersion{IsSolid: false} + version.versionMap = append(version.versionMap, &versionToReconcile) + + b.commitsToReconcile[ref.Hash.String()] = &versionToReconcile + return b.checkWalkParent(ref, version, tilVisited) + } + + matched, err := regexp.MatchString(b.settings.MajorPattern, ref.Message) + if err != nil { + return err + } + if matched { + version.versionMap = append(version.versionMap, &gitVersion{IsSolid: false, MajorBump: true}) + return b.checkWalkParent(ref, version, tilVisited) + } + + matched, err = regexp.MatchString(b.settings.MinorPattern, ref.Message) + if err != nil { + return err + } + if matched { + version.versionMap = append(version.versionMap, &gitVersion{IsSolid: false, MinorBump: true}) + return b.checkWalkParent(ref, version, tilVisited) + } + + matched, err = regexp.MatchString(b.settings.PatchPattern, ref.Message) + if err != nil { + return err + } + if matched { + version.versionMap = append(version.versionMap, &gitVersion{IsSolid: false, PatchBump: true}) + return b.checkWalkParent(ref, version, tilVisited) + } + + version.versionMap = append(version.versionMap, &gitVersion{IsSolid: false}) + + return b.checkWalkParent(ref, version, tilVisited) +} + +func (b *branchWalker) checkWalkParent(ref *object.Commit, version *versionHolder, tilVisited bool) error { + parents := ref.NumParents() + if parents == 0 { + return nil + } + + parent, err := ref.Parent(0) + if err != nil { + return nil + } + + if parent.Hash.String() == b.endHash { + return nil + } + + return b.walkVersion(parent, version, tilVisited) +} + +func (b *branchWalker) reconcileCommit(hash string, version *gitVersion) error { + versionMap := versionHolder{ + versionMap: []*gitVersion{}, + } + + commit, err := b.repository.CommitObject(plumbing.NewHash(hash)) + if err != nil { + return errors.Wrap(err, "failed to get commit in reconcile") + } + + if commit.NumParents() <= 1 { + return nil + } + + parentToWalk, err := commit.Parent(1) + if err != nil { + return errors.Wrap(err, "failed to get parent in reconcile") + } + + err = b.walkVersion(parentToWalk, &versionMap, true) + if err != nil { + return err + } + + var hasMajor, hasMinor bool + for _, bump := range versionMap.versionMap { + if bump.MajorBump { + hasMajor = true + } + if bump.MinorBump { + hasMinor = true + } + } + + if hasMajor { + version.MajorBump = true + } else if hasMinor { + version.MinorBump = true + } else { + version.PatchBump = true + } + + return nil +} diff --git a/pkg/git/git.go b/pkg/git/git.go index 8753dfa..fcde820 100644 --- a/pkg/git/git.go +++ b/pkg/git/git.go @@ -3,7 +3,6 @@ package git import ( "fmt" "os" - "regexp" "strings" "github.com/coreos/go-semver/semver" @@ -90,9 +89,15 @@ func getVersion(r *git.Repository, h *plumbing.Reference, tagMap map[string]stri return nil, errors.Wrap(err, "getVersion failed") } - masterVersion, err := getMasterVersion(r, masterHead, tagMap, settings) + masterCommit, err := r.CommitObject(masterHead.Hash()) if err != nil { - return nil, errors.Wrap(err, "getVersion failed") + return nil, errors.Wrap(err, "failed to get master commit from reference") + } + + masterWalker := newBranchWalker(r, masterCommit, tagMap, settings, true, "") + masterVersion, err := masterWalker.GetVersion() + if err != nil { + return nil, err } if h.Hash() == masterHead.Hash() { @@ -104,10 +109,10 @@ func getVersion(r *git.Repository, h *plumbing.Reference, tagMap map[string]stri return nil, errors.Wrap(err, "getVersion failed") } - versionMap := []gitVersion{} - err = walkVersion(r, c, tagMap, &versionMap, masterHead.Hash().String(), settings) + walker := newBranchWalker(r, c, tagMap, settings, false, masterHead.Hash().String()) + versionMap, err := walker.GetVersionMap() if err != nil { - return nil, errors.Wrap(err, "getVersion failed") + return nil, err } var baseVersion *semver.Version @@ -149,111 +154,6 @@ func getVersion(r *git.Repository, h *plumbing.Reference, tagMap map[string]stri return baseVersion, nil } -func getMasterVersion(r *git.Repository, masterHead *plumbing.Reference, tagMap map[string]string, settings *Settings) (version *semver.Version, err error) { - versionMap := []gitVersion{} - - c, err := r.CommitObject(masterHead.Hash()) - if err != nil { - return nil, err - } - err = walkVersion(r, c, tagMap, &versionMap, "", settings) - if err != nil { - return nil, err - } - - var baseVersion *semver.Version - index := len(versionMap) - 1 - if versionMap[index].IsSolid { - baseVersion = versionMap[index].Name - index-- - } else { - baseVersion, err = semver.NewVersion("0.0.0") - if err != nil { - return nil, err - } - } - - if index < 0 { - return baseVersion, nil - } - - for ; index >= 0; index-- { - v := versionMap[index] - switch { - case v.MajorBump: - baseVersion.BumpMajor() - case v.MinorBump: - baseVersion.BumpMinor() - case v.PatchBump: - baseVersion.BumpPatch() - default: // every commit in master has at least a patch bump - baseVersion.BumpPatch() - } - } - - return baseVersion, nil -} - -func walkVersion(r *git.Repository, ref *object.Commit, tagMap map[string]string, versionMap *[]gitVersion, endHash string, settings *Settings) error { - tag, ok := tagMap[ref.Hash.String()] - if ok { - tagVersion, err := semver.NewVersion(tag) - if err != nil { - return err - } - *versionMap = append(*versionMap, gitVersion{IsSolid: true, Name: tagVersion}) - return nil - } - - matched, err := regexp.MatchString(settings.MajorPattern, ref.Message) - if err != nil { - return err - } - if matched { - *versionMap = append(*versionMap, gitVersion{IsSolid: false, MajorBump: true}) - return checkWalkParent(r, ref, tagMap, versionMap, endHash, settings) - } - - matched, err = regexp.MatchString(settings.MinorPattern, ref.Message) - if err != nil { - return err - } - if matched { - *versionMap = append(*versionMap, gitVersion{IsSolid: false, MinorBump: true}) - return checkWalkParent(r, ref, tagMap, versionMap, endHash, settings) - } - - matched, err = regexp.MatchString(settings.PatchPattern, ref.Message) - if err != nil { - return err - } - if matched { - *versionMap = append(*versionMap, gitVersion{IsSolid: false, PatchBump: true}) - return checkWalkParent(r, ref, tagMap, versionMap, endHash, settings) - } - - *versionMap = append(*versionMap, gitVersion{IsSolid: false}) - - return checkWalkParent(r, ref, tagMap, versionMap, endHash, settings) -} - -func checkWalkParent(r *git.Repository, ref *object.Commit, tagMap map[string]string, versionMap *[]gitVersion, endHash string, settings *Settings) error { - if ref.NumParents() == 0 { - return nil - } - - parent, err := ref.Parent(0) - if err != nil { - return nil - } - - if parent.Hash.String() == endHash { - return nil - } - - return walkVersion(r, parent, tagMap, versionMap, endHash, settings) -} - func getCurrentBranch(r *git.Repository, h *plumbing.Reference) (name string, err error) { branchName := ""