Skip to content
This repository was archived by the owner on Sep 9, 2020. It is now read-only.

Commit 43252e0

Browse files
authored
Merge pull request #1027 from spenczar/infer_git_abbreviated_shas
internal/gps: Parse abbreviated git revisions
2 parents 892b5ca + 49bf340 commit 43252e0

File tree

4 files changed

+130
-82
lines changed

4 files changed

+130
-82
lines changed

internal/gps/source.go

+13
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,18 @@ func (sg *sourceGateway) revisionPresentIn(ctx context.Context, r Revision) (boo
450450
return present, err
451451
}
452452

453+
func (sg *sourceGateway) disambiguateRevision(ctx context.Context, r Revision) (Revision, error) {
454+
sg.mu.Lock()
455+
defer sg.mu.Unlock()
456+
457+
_, err := sg.require(ctx, sourceIsSetUp|sourceExistsLocally)
458+
if err != nil {
459+
return "", err
460+
}
461+
462+
return sg.src.disambiguateRevision(ctx, r)
463+
}
464+
453465
func (sg *sourceGateway) sourceURL(ctx context.Context) (string, error) {
454466
sg.mu.Lock()
455467
defer sg.mu.Unlock()
@@ -550,6 +562,7 @@ type source interface {
550562
getManifestAndLock(context.Context, ProjectRoot, Revision, ProjectAnalyzer) (Manifest, Lock, error)
551563
listPackages(context.Context, ProjectRoot, Revision) (pkgtree.PackageTree, error)
552564
revisionPresentIn(Revision) (bool, error)
565+
disambiguateRevision(context.Context, Revision) (Revision, error)
553566
exportRevisionTo(context.Context, Revision, string) error
554567
sourceType() string
555568
}

internal/gps/source_manager.go

+22-33
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,11 @@ package gps
66

77
import (
88
"context"
9-
"encoding/hex"
109
"fmt"
1110
"os"
1211
"os/signal"
1312
"path/filepath"
1413
"runtime"
15-
"strconv"
1614
"strings"
1715
"sync"
1816
"sync/atomic"
@@ -512,42 +510,13 @@ func (sm *SourceMgr) DeduceProjectRoot(ip string) (ProjectRoot, error) {
512510
}
513511

514512
// InferConstraint tries to puzzle out what kind of version is given in a
515-
// string. Preference is given first for revisions, then branches, then semver
516-
// constraints, and then plain tags.
513+
// string. Preference is given first for branches, then semver constraints, then
514+
// plain tags, and then revisions.
517515
func (sm *SourceMgr) InferConstraint(s string, pi ProjectIdentifier) (Constraint, error) {
518516
if s == "" {
519517
return Any(), nil
520518
}
521519

522-
slen := len(s)
523-
if slen == 40 {
524-
if _, err := hex.DecodeString(s); err == nil {
525-
// Whether or not it's intended to be a SHA1 digest, this is a
526-
// valid byte sequence for that, so go with Revision. This
527-
// covers git and hg
528-
return Revision(s), nil
529-
}
530-
}
531-
532-
// Next, try for bzr, which has a three-component GUID separated by
533-
// dashes. There should be two, but the email part could contain
534-
// internal dashes
535-
if strings.Contains(s, "@") && strings.Count(s, "-") >= 2 {
536-
// Work from the back to avoid potential confusion from the email
537-
i3 := strings.LastIndex(s, "-")
538-
// Skip if - is last char, otherwise this would panic on bounds err
539-
if slen == i3+1 {
540-
return NewVersion(s), nil
541-
}
542-
543-
i2 := strings.LastIndex(s[:i3], "-")
544-
if _, err := strconv.ParseUint(s[i2+1:i3], 10, 64); err == nil {
545-
// Getting this far means it'd pretty much be nuts if it's not a
546-
// bzr rev, so don't bother parsing the email.
547-
return Revision(s), nil
548-
}
549-
}
550-
551520
// Lookup the string in the repository
552521
var version PairedVersion
553522
versions, err := sm.ListVersions(pi)
@@ -578,9 +547,29 @@ func (sm *SourceMgr) InferConstraint(s string, pi ProjectIdentifier) (Constraint
578547
return version.Unpair(), nil
579548
}
580549

550+
// Revision, possibly abbreviated
551+
r, err := sm.disambiguateRevision(context.TODO(), pi, Revision(s))
552+
if err == nil {
553+
return r, nil
554+
}
555+
581556
return nil, errors.Errorf("%s is not a valid version for the package %s(%s)", s, pi.ProjectRoot, pi.Source)
582557
}
583558

559+
// disambiguateRevision looks up a revision in the underlying source, spitting
560+
// it back out in an unabbreviated, disambiguated form.
561+
//
562+
// For example, if pi refers to a git-based project, then rev could be an
563+
// abbreviated git commit hash. disambiguateRevision would return the complete
564+
// hash.
565+
func (sm *SourceMgr) disambiguateRevision(ctx context.Context, pi ProjectIdentifier, rev Revision) (Revision, error) {
566+
srcg, err := sm.srcCoord.getSourceGatewayFor(context.TODO(), pi)
567+
if err != nil {
568+
return "", err
569+
}
570+
return srcg.disambiguateRevision(ctx, rev)
571+
}
572+
584573
type timeCount struct {
585574
count int
586575
start time.Time

internal/gps/source_manager_test.go

+72-49
Original file line numberDiff line numberDiff line change
@@ -13,71 +13,94 @@ import (
1313

1414
func TestSourceManager_InferConstraint(t *testing.T) {
1515
t.Parallel()
16-
h := test.NewHelper(t)
17-
cacheDir := "gps-repocache"
18-
h.TempDir(cacheDir)
19-
sm, err := NewSourceManager(h.Path(cacheDir))
20-
h.Must(err)
2116

22-
sv, err := NewSemverConstraintIC("v0.8.1")
17+
// Used in git subtests:
18+
v081, err := NewSemverConstraintIC("v0.8.1")
2319
if err != nil {
2420
t.Fatal(err)
2521
}
26-
27-
svs, err := NewSemverConstraintIC("v0.12.0-12-de4dcafe0")
22+
v012, err := NewSemverConstraintIC("v0.12.0-12-de4dcafe0")
2823
if err != nil {
2924
t.Fatal(err)
3025
}
3126

32-
constraints := map[string]Constraint{
33-
"": Any(),
34-
"v0.8.1": sv,
35-
"v2": NewBranch("v2"),
36-
"v0.12.0-12-de4dcafe0": svs,
37-
"master": NewBranch("master"),
38-
"5b3352dc16517996fb951394bcbbe913a2a616e3": Revision("5b3352dc16517996fb951394bcbbe913a2a616e3"),
39-
40-
// valid bzr rev
41-
42-
// invalid bzr rev
43-
27+
// Used in hg and bzr subtests:
28+
v1, err := NewSemverConstraintIC("v1.0.0")
29+
if err != nil {
30+
t.Fatal(err)
4431
}
4532

46-
pi := ProjectIdentifier{ProjectRoot: "github.com/carolynvs/deptest"}
47-
for str, want := range constraints {
48-
got, err := sm.InferConstraint(str, pi)
49-
h.Must(err)
33+
var (
34+
gitProj = ProjectIdentifier{ProjectRoot: "github.com/carolynvs/deptest"}
35+
bzrProj = ProjectIdentifier{ProjectRoot: "launchpad.net/govcstestbzrrepo"}
36+
hgProj = ProjectIdentifier{ProjectRoot: "bitbucket.org/golang-dep/dep-test"}
5037

51-
wantT := reflect.TypeOf(want)
52-
gotT := reflect.TypeOf(got)
53-
if wantT != gotT {
54-
t.Errorf("expected type: %s, got %s, for input %s", wantT, gotT, str)
38+
testcases = []struct {
39+
project ProjectIdentifier
40+
name string
41+
str string
42+
want Constraint
43+
}{
44+
{gitProj, "empty", "", Any()},
45+
{gitProj, "semver-short", "v0.8.1", v081},
46+
{gitProj, "long semver constraint", "v0.12.0-12-de4dcafe0", v012},
47+
{gitProj, "branch v2", "v2", NewBranch("v2")},
48+
{gitProj, "branch master", "master", NewBranch("master")},
49+
{gitProj, "long revision", "3f4c3bea144e112a69bbe5d8d01c1b09a544253f",
50+
Revision("3f4c3bea144e112a69bbe5d8d01c1b09a544253f")},
51+
{gitProj, "short revision", "3f4c3bea",
52+
Revision("3f4c3bea144e112a69bbe5d8d01c1b09a544253f")},
53+
54+
{bzrProj, "empty", "", Any()},
55+
{bzrProj, "semver", "v1.0.0", v1},
56+
{bzrProj, "revision", "[email protected]",
57+
Revision("[email protected]")},
58+
59+
{hgProj, "empty", "", Any()},
60+
{hgProj, "semver", "v1.0.0", v1},
61+
{hgProj, "default branch", "default", NewBranch("default")},
62+
{hgProj, "revision", "6f55e1f03d91f8a7cce35d1968eb60a2352e4d59",
63+
Revision("6f55e1f03d91f8a7cce35d1968eb60a2352e4d59")},
64+
{hgProj, "short revision", "6f55e1f03d91",
65+
Revision("6f55e1f03d91f8a7cce35d1968eb60a2352e4d59")},
5566
}
56-
if got.String() != want.String() {
57-
t.Errorf("expected value: %s, got %s for input %s", want, got, str)
67+
)
68+
69+
for _, tc := range testcases {
70+
var subtestName string
71+
switch tc.project {
72+
case gitProj:
73+
subtestName = "git-" + tc.name
74+
case bzrProj:
75+
subtestName = "bzr-" + tc.name
76+
case hgProj:
77+
subtestName = "hg-" + tc.name
78+
default:
79+
subtestName = tc.name
5880
}
59-
}
60-
}
6181

62-
func TestSourceManager_InferConstraint_InvalidInput(t *testing.T) {
63-
h := test.NewHelper(t)
82+
t.Run(subtestName, func(t *testing.T) {
83+
t.Parallel()
84+
h := test.NewHelper(t)
85+
defer h.Cleanup()
6486

65-
cacheDir := "gps-repocache"
66-
h.TempDir(cacheDir)
67-
sm, err := NewSourceManager(h.Path(cacheDir))
68-
h.Must(err)
87+
cacheDir := "gps-repocache"
88+
h.TempDir(cacheDir)
6989

70-
constraints := []string{
71-
// invalid bzr revs
72-
73-
"20120425195858-psty8c35ve2oej8t",
74-
}
90+
sm, err := NewSourceManager(h.Path(cacheDir))
91+
h.Must(err)
7592

76-
pi := ProjectIdentifier{ProjectRoot: "github.com/sdboyer/deptest"}
77-
for _, str := range constraints {
78-
_, err := sm.InferConstraint(str, pi)
79-
if err == nil {
80-
t.Errorf("expected %s to produce an error", str)
81-
}
93+
got, err := sm.InferConstraint(tc.str, tc.project)
94+
h.Must(err)
95+
96+
wantT := reflect.TypeOf(tc.want)
97+
gotT := reflect.TypeOf(got)
98+
if wantT != gotT {
99+
t.Errorf("expected type: %s, got %s, for input %s", wantT, gotT, tc.str)
100+
}
101+
if got.String() != tc.want.String() {
102+
t.Errorf("expected value: %s, got %s for input %s", tc.want, got, tc.str)
103+
}
104+
})
82105
}
83106
}

internal/gps/vcs_source.go

+23
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ func (bs *baseVCSSource) upstreamURL() string {
4040
return bs.repo.Remote()
4141
}
4242

43+
func (bs *baseVCSSource) disambiguateRevision(ctx context.Context, r Revision) (Revision, error) {
44+
ci, err := bs.repo.CommitInfo(string(r))
45+
if err != nil {
46+
return "", err
47+
}
48+
return Revision(ci.Commit), nil
49+
}
50+
4351
func (bs *baseVCSSource) getManifestAndLock(ctx context.Context, pr ProjectRoot, r Revision, an ProjectAnalyzer) (Manifest, Lock, error) {
4452
err := bs.repo.updateVersion(ctx, r.String())
4553
if err != nil {
@@ -406,6 +414,21 @@ func (s *bzrSource) listVersions(ctx context.Context) ([]PairedVersion, error) {
406414
return vlist, nil
407415
}
408416

417+
func (s *bzrSource) disambiguateRevision(ctx context.Context, r Revision) (Revision, error) {
418+
// If we used the default baseVCSSource behavior here, we would return the
419+
// bazaar revision number, which is not a globally unique identifier - it is
420+
// only unique within a branch. This is just the way that
421+
// github.com/Masterminds/vcs chooses to handle bazaar. We want a
422+
// disambiguated unique ID, though, so we need slightly different behavior:
423+
// check whether r doesn't error when we try to look it up. If so, trust that
424+
// it's a revision.
425+
_, err := s.repo.CommitInfo(string(r))
426+
if err != nil {
427+
return "", err
428+
}
429+
return r, nil
430+
}
431+
409432
// hgSource is a generic hg repository implementation that should work with
410433
// all standard mercurial servers.
411434
type hgSource struct {

0 commit comments

Comments
 (0)