Skip to content

Commit 4ccf91a

Browse files
authored
[vergo:minor-release] Look for head commit at any point of versioned branch (#14)
* Look for head commit at any point of versioned branch - This is an attempt to fix #13, using code shamelessly stolen from src-d/go-git#611 (comment) - Working on this in the background so opening a draft PR for now, with plans to extract this code, test it more thorougly and add error handling Required by #13 * Renames and remove unneeded map * Handle errors, log and default to false * Add changelog entry * Fix func tests * Naming improvements * Add memorization to speed up execution * Avoid recursion by iterating over the git log * Improve error messages
1 parent f9118b5 commit 4ccf91a

6 files changed

Lines changed: 90 additions & 23 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## [0.27.0] - 08-02-2023
4+
Fixed bug introduced in version `0.21.0` where `vergo bump` and `vergo check` would fail if the current commit is not
5+
the latest on a versioned branch e.g. `master` or `main`.
6+
37
## [0.26.0] - 15-11-2022
48
Fixed bug in tag prefix trimming.
59

bump/bump_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package bump_test
22

33
import (
4+
"fmt"
45
"github.com/Masterminds/semver/v3"
56
"github.com/go-git/go-billy/v5/memfs"
67
gogit "github.com/go-git/go-git/v5"
@@ -115,7 +116,7 @@ func TestBumpShouldFailWhenNotOnMainBranch(t *testing.T) {
115116
r.BranchExists(branchName)
116117
assert.Equal(t, branchName, r.Head().Name().Short())
117118
_, err = Bump(r.Repo, increment, Options{TagPrefix: prefix, VersionedBranches: mainBranch})
118-
assert.Regexp(t, "branch apple is not in main branches list: master, main", err)
119+
assert.Regexp(t, "branch apple is not in versioned branches list: master, main", err)
119120
})
120121
}
121122
}
@@ -161,7 +162,7 @@ func TestBumpShouldNOTWorkWhenHeadlessCheckoutOfOtherBranch(t *testing.T) {
161162
assert.Equal(t, plumbing.HEAD.String(), r.Head().Name().Short())
162163

163164
_, err = Bump(r.Repo, increment, Options{TagPrefix: prefix, VersionedBranches: mainBranch})
164-
assert.Regexp(t, "invalid headless checkout", err)
165+
assert.Equal(t, fmt.Sprintf("commit %s is not on a versioned branch: master, main", latestHashOnApple.String()), err.Error())
165166
})
166167
}
167168
}

cmd/cmd_check_test.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cmd_test
22

33
import (
4+
"fmt"
45
. "github.com/sky-uk/vergo/internal-test"
56
"github.com/sky-uk/vergo/release"
67
"github.com/stretchr/testify/assert"
@@ -27,7 +28,8 @@ func TestCheckIncrementHintSuccess(t *testing.T) {
2728

2829
func TestCheckIncrementHintSkipHint(t *testing.T) {
2930
_, tempDir := PersistentRepository(t)
30-
cmd, buffer := makeCheckFail(t, release.ErrSkipRelease, release.ErrInvalidHeadless, release.ErrNoIncrement)
31+
expectedError := fmt.Errorf("commit %s is not on a versioned branch: %s", "blah", "blah")
32+
cmd, buffer := makeCheckFail(t, release.ErrSkipRelease, expectedError, release.ErrNoIncrement)
3133
cmd.SetArgs([]string{"check", "increment-hint", "--repository-location", tempDir, "-t", "some-prefix", "--log-level", "error"})
3234
err := cmd.Execute()
3335
assert.Nil(t, err)
@@ -54,20 +56,22 @@ func TestCheckIncrementHintFailure(t *testing.T) {
5456

5557
func TestCheckReleaseManyFailures(t *testing.T) {
5658
_, tempDir := PersistentRepository(t)
57-
cmd, buffer := makeCheckFail(t, release.ErrSkipRelease, release.ErrInvalidHeadless, success)
59+
expectedError := fmt.Errorf("commit %s is not on a versioned branch: %s", "blah", "blah")
60+
cmd, buffer := makeCheckFail(t, release.ErrSkipRelease, expectedError, success)
5861
cmd.SetArgs([]string{"check", "release", "--repository-location", tempDir, "-t", "some-prefix", "--log-level", "error"})
5962
err := cmd.Execute()
6063
assert.NotNil(t, err)
6164
assert.Equal(t, `Error: skip release hint present
62-
HEAD validation: invalid headless checkout
65+
commit blah is not on a versioned branch: blah
6366
`, readBuffer(t, buffer))
6467
}
6568

6669
func TestCheckReleaseInvalidHeadless(t *testing.T) {
6770
_, tempDir := PersistentRepository(t)
68-
cmd, buffer := makeCheckFail(t, success, release.ErrInvalidHeadless, success)
71+
expectedError := fmt.Errorf("commit %s is not on a versioned branch: %s", "blah", "blah")
72+
cmd, buffer := makeCheckFail(t, success, expectedError, success)
6973
cmd.SetArgs([]string{"check", "release", "--repository-location", tempDir, "-t", "some-prefix", "--log-level", "error"})
7074
err := cmd.Execute()
7175
assert.NotNil(t, err)
72-
assert.Equal(t, "Error: HEAD validation: invalid headless checkout\n", readBuffer(t, buffer))
76+
assert.Equal(t, "Error: commit blah is not on a versioned branch: blah\n", readBuffer(t, buffer))
7377
}

fun-tests/test.sh

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,15 +174,15 @@ remote_tags=$(git ls-remote --tags origin apple-0.2.0 app-0.1.1 banana-2.0.0 ora
174174
setUpLikeCI
175175

176176
[[ "$(git branch -l | grep -v HEAD)" == "" ]] #make sure no local branch
177-
git checkout 117443bb
178-
[[ "$(vergo check release --tag-prefix=apple 2>&1)" == *'invalid headless checkout'* ]]
179-
[[ "$(vergo bump minor --tag-prefix=apple 2>&1)" == *'invalid headless checkout'* ]]
177+
git checkout d13ae40 #commit on the `test-branch` branch
178+
[[ "$(vergo check release --tag-prefix=apple 2>&1)" == *' is not on a versioned branch'* ]]
179+
[[ "$(vergo bump minor --tag-prefix=apple 2>&1)" == *' is not on a versioned branch'* ]]
180180

181181
[[ "$(git branch -l | grep -v HEAD)" == "" ]] #make sure no local branch
182182
git checkout a54f1f7
183183
vergo check release --tag-prefix=apple
184184
[[ "$(vergo bump minor --tag-prefix=apple --log-level=trace 2>&1)" == *'Set tag apple-0.2.0'* ]]
185185

186186
git checkout origin/master -b master #create local branch
187-
[[ "$(vergo check release --tag-prefix=apple --log-level=trace --versioned-branch-names main 2>&1)" == *'branch master is not in main branches list: main'* ]]
188-
[[ "$(vergo bump minor --tag-prefix=apple --log-level=trace --versioned-branch-names main 2>&1)" == *'branch master is not in main branches list: main'* ]]
187+
[[ "$(vergo check release --tag-prefix=apple --log-level=trace --versioned-branch-names main 2>&1)" == *'branch master is not in versioned branches list: main'* ]]
188+
[[ "$(vergo bump minor --tag-prefix=apple --log-level=trace --versioned-branch-names main 2>&1)" == *'branch master is not in versioned branches list: main'* ]]

release/release.go

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@ import (
66
"github.com/Masterminds/semver/v3"
77
gogit "github.com/go-git/go-git/v5"
88
"github.com/go-git/go-git/v5/plumbing"
9+
"github.com/go-git/go-git/v5/plumbing/object"
10+
"github.com/go-git/go-git/v5/plumbing/storer"
911
log "github.com/sirupsen/logrus"
1012
"github.com/thoas/go-funk"
1113
"regexp"
1214
"strings"
1315
)
1416

1517
var (
16-
ErrNoIncrement = errors.New("increment hint not present")
17-
ErrSkipRelease = errors.New("skip release hint present")
18-
ErrHEADValidation = errors.New("HEAD validation")
19-
ErrInvalidHeadless = fmt.Errorf("%w: %s", ErrHEADValidation, "invalid headless checkout")
18+
ErrNoIncrement = errors.New("increment hint not present")
19+
ErrSkipRelease = errors.New("skip release hint present")
2020
)
2121

2222
func checkSkipHint(aString, tagPrefix string) bool {
@@ -85,7 +85,8 @@ func ValidateHEAD(repo *gogit.Repository, remoteName string, versionedBranches [
8585
return err
8686
}
8787
log.Debugf("Current branch:%v, short: %v", head.Name(), head.Name().Short())
88-
if head.Name() == plumbing.HEAD {
88+
isHeadlessCheckout := head.Name() == plumbing.HEAD
89+
if isHeadlessCheckout {
8990
validRef := false
9091
for _, mainBranchName := range versionedBranches {
9192
remote, err := repo.Remote(remoteName)
@@ -102,20 +103,27 @@ func ValidateHEAD(repo *gogit.Repository, remoteName string, versionedBranches [
102103
if err != nil {
103104
log.WithError(err).Debugf("branchRef could not be resolved: %s\n", branchRef.String())
104105
} else {
105-
if revision.String() == head.Hash().String() {
106+
commitOnVersionedBranch, err := isCommitOnBranch(repo, head.Hash(), branchRef)
107+
if err != nil {
108+
log.WithError(err).Errorf("Failed to check if commit %s is on branch %s\n",
109+
head.Hash().String(), branchRef.String())
110+
}
111+
112+
if commitOnVersionedBranch {
106113
validRef = true
107114
break
108115
} else {
109-
log.Tracef("Invalid ref [branch: %s, head: %s, ref: %s]\n",
116+
log.Warnf("Commit not found on branch [branch: %s, head: %s, ref: %s]\n",
110117
branchRef.String(), head.Hash().String(), revision.String())
111118
}
112119
}
113120
}
114121
if !validRef {
115-
return ErrInvalidHeadless
122+
return fmt.Errorf("commit %s is not on a versioned branch: %s",
123+
head.Hash(), strings.Join(versionedBranches, ", "))
116124
}
117125
} else if !funk.ContainsString(versionedBranches, head.Name().Short()) {
118-
return fmt.Errorf("%w: branch %s is not in main branches list: %s", ErrHEADValidation,
126+
return fmt.Errorf("branch %s is not in versioned branches list: %s",
119127
head.Name().Short(), strings.Join(versionedBranches, ", "))
120128
}
121129
return nil
@@ -142,3 +150,33 @@ func PreRelease(repo *gogit.Repository, options PreReleaseOptions) PreReleaseFun
142150
return pre, nil
143151
}
144152
}
153+
154+
func isCommitOnBranch(repo *gogit.Repository, commit plumbing.Hash, branch plumbing.ReferenceName) (bool, error) {
155+
branchRef, err := repo.Reference(branch, true)
156+
if err != nil {
157+
return false, err
158+
}
159+
160+
branchGitLog, err := repo.Log(&gogit.LogOptions{
161+
From: branchRef.Hash(),
162+
})
163+
164+
if err != nil {
165+
return false, err
166+
}
167+
168+
reaches := false
169+
err = branchGitLog.ForEach(func(branchCommit *object.Commit) error {
170+
if branchCommit.Hash == commit {
171+
reaches = true
172+
return storer.ErrStop
173+
}
174+
return nil
175+
})
176+
177+
if err != nil {
178+
return false, err
179+
}
180+
181+
return reaches, nil
182+
}

release/release_test.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package release_test
22

33
import (
4+
"fmt"
45
gogit "github.com/go-git/go-git/v5"
56
"github.com/go-git/go-git/v5/plumbing"
67
. "github.com/sky-uk/vergo/internal-test"
@@ -157,7 +158,7 @@ func TestShouldFailWhenNotOnMainBranch(t *testing.T) {
157158
r.BranchExists(branchName)
158159
assert.Equal(t, branchName, r.Head().Name().Short())
159160
err = release.ValidateHEAD(r.Repo, remoteName, mainBranch)
160-
assert.Regexp(t, "branch apple is not in main branches list: master, main", err)
161+
assert.Regexp(t, "branch apple is not in versioned branches list: master, main", err)
161162
})
162163
}
163164
}
@@ -180,6 +181,25 @@ func TestShouldWorkWhenHeadlessCheckoutOfMainBranch(t *testing.T) {
180181
}
181182
}
182183

184+
//nolint:scopelint,paralleltest
185+
func TestShouldWorkWhenOnHeadlessCheckoutOfOldCommitOnMainBranch(t *testing.T) {
186+
for _, prefix := range prefixes {
187+
for _, increment := range increments {
188+
t.Run(prefix+"-"+increment, func(t *testing.T) {
189+
r := NewTestRepo(t)
190+
oldCommit := r.Head().Hash()
191+
r.DoCommit("foo")
192+
err := r.Worktree().Checkout(&gogit.CheckoutOptions{Hash: oldCommit})
193+
assert.Nil(t, err)
194+
assert.Equal(t, plumbing.HEAD.String(), r.Head().Name().Short())
195+
196+
err = release.ValidateHEAD(r.Repo, remoteName, mainBranch)
197+
assert.Nil(t, err)
198+
})
199+
}
200+
}
201+
}
202+
183203
//nolint:scopelint,paralleltest
184204
func TestShouldNOTWorkWhenHeadlessCheckoutOfOtherBranch(t *testing.T) {
185205
for _, prefix := range prefixes {
@@ -203,7 +223,7 @@ func TestShouldNOTWorkWhenHeadlessCheckoutOfOtherBranch(t *testing.T) {
203223
assert.Equal(t, plumbing.HEAD.String(), r.Head().Name().Short())
204224

205225
err = release.ValidateHEAD(r.Repo, remoteName, mainBranch)
206-
assert.Regexp(t, "invalid headless checkout", err)
226+
assert.Equal(t, fmt.Sprintf("commit %s is not on a versioned branch: master, main", latestHashOnApple.String()), err.Error())
207227
})
208228
}
209229
}

0 commit comments

Comments
 (0)