Skip to content

Commit

Permalink
fix: handle replace directives in go.mod files (#162)
Browse files Browse the repository at this point in the history
  • Loading branch information
G-Rath committed Dec 16, 2022
1 parent 1346e20 commit 849a94c
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 4 deletions.
9 changes: 9 additions & 0 deletions pkg/lockfile/fixtures/go/replace-different.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require (
golang.org/x/net v1.2.3
golang.org/x/net v0.5.6
)

replace (
golang.org/x/net v1.2.3 => example.com/fork/foe v1.4.5
golang.org/x/net v0.5.6 => example.com/fork/foe v1.4.2
)
8 changes: 8 additions & 0 deletions pkg/lockfile/fixtures/go/replace-local.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
require (
golang.org/x/net v1.2.3
github.com/BurntSushi/toml v1.0.0
)

replace (
golang.org/x/net v1.2.3 => ./fork/net
)
8 changes: 8 additions & 0 deletions pkg/lockfile/fixtures/go/replace-mixed.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
require (
golang.org/x/net v1.2.3
golang.org/x/net v0.5.6
)

replace (
golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5
)
8 changes: 8 additions & 0 deletions pkg/lockfile/fixtures/go/replace-no-version.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
require (
golang.org/x/net v1.2.3
golang.org/x/net v0.5.6
)

replace (
golang.org/x/net => example.com/fork/net v1.4.5
)
8 changes: 8 additions & 0 deletions pkg/lockfile/fixtures/go/replace-not-required.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
require (
golang.org/x/net v0.5.6
github.com/BurntSushi/toml v1.0.0
)

replace (
golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5
)
5 changes: 5 additions & 0 deletions pkg/lockfile/fixtures/go/replace-one.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
require (
golang.org/x/net v1.2.3
)

replace golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5
49 changes: 45 additions & 4 deletions pkg/lockfile/parse-go-lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ import (

const GoEcosystem Ecosystem = "Go"

func deduplicatePackages(packages map[string]PackageDetails) map[string]PackageDetails {
details := map[string]PackageDetails{}

for _, detail := range packages {
details[detail.Name+"@"+detail.Version] = detail
}

return details
}

func ParseGoLock(pathToLockfile string) ([]PackageDetails, error) {
lockfileContents, err := os.ReadFile(pathToLockfile)

Expand All @@ -22,16 +32,47 @@ func ParseGoLock(pathToLockfile string) ([]PackageDetails, error) {
return []PackageDetails{}, fmt.Errorf("could not parse %s: %w", pathToLockfile, err)
}

packages := make([]PackageDetails, 0, len(parsedLockfile.Require))
packages := map[string]PackageDetails{}

for _, require := range parsedLockfile.Require {
packages = append(packages, PackageDetails{
packages[require.Mod.Path+"@"+require.Mod.Version] = PackageDetails{
Name: require.Mod.Path,
Version: strings.TrimPrefix(require.Mod.Version, "v"),
Ecosystem: GoEcosystem,
CompareAs: GoEcosystem,
})
}
}

for _, replace := range parsedLockfile.Replace {
var replacements []string

if replace.Old.Version == "" {
// If the left version is omitted, all versions of the module are replaced.
for k, pkg := range packages {
if pkg.Name == replace.Old.Path {
replacements = append(replacements, k)
}
}
} else {
// If a version is present on the left side of the arrow (=>),
// only that specific version of the module is replaced
s := replace.Old.Path + "@" + replace.Old.Version

// A `replace` directive has no effect if the module version on the left side is not required.
if _, ok := packages[s]; ok {
replacements = []string{s}
}
}

for _, replacement := range replacements {
packages[replacement] = PackageDetails{
Name: replace.New.Path,
Version: strings.TrimPrefix(replace.New.Version, "v"),
Ecosystem: GoEcosystem,
CompareAs: GoEcosystem,
}
}
}

return packages, nil
return pkgDetailsMapToSlice(deduplicatePackages(packages)), nil
}
138 changes: 138 additions & 0 deletions pkg/lockfile/parse-go-lock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,141 @@ func TestParseGoLock_IndirectPackages(t *testing.T) {
},
})
}

func TestParseGoLock_Replacements_One(t *testing.T) {
t.Parallel()

packages, err := lockfile.ParseGoLock("fixtures/go/replace-one.mod")

if err != nil {
t.Errorf("Got unexpected error: %v", err)
}

expectPackages(t, packages, []lockfile.PackageDetails{
{
Name: "example.com/fork/net",
Version: "1.4.5",
Ecosystem: lockfile.GoEcosystem,
CompareAs: lockfile.GoEcosystem,
},
})
}

func TestParseGoLock_Replacements_Mixed(t *testing.T) {
t.Parallel()

packages, err := lockfile.ParseGoLock("fixtures/go/replace-mixed.mod")

if err != nil {
t.Errorf("Got unexpected error: %v", err)
}

expectPackages(t, packages, []lockfile.PackageDetails{
{
Name: "example.com/fork/net",
Version: "1.4.5",
Ecosystem: lockfile.GoEcosystem,
CompareAs: lockfile.GoEcosystem,
},
{
Name: "golang.org/x/net",
Version: "0.5.6",
Ecosystem: lockfile.GoEcosystem,
CompareAs: lockfile.GoEcosystem,
},
})
}

func TestParseGoLock_Replacements_Local(t *testing.T) {
t.Parallel()

packages, err := lockfile.ParseGoLock("fixtures/go/replace-local.mod")

if err != nil {
t.Errorf("Got unexpected error: %v", err)
}

expectPackages(t, packages, []lockfile.PackageDetails{
{
Name: "./fork/net",
Version: "",
Ecosystem: lockfile.GoEcosystem,
CompareAs: lockfile.GoEcosystem,
},
{
Name: "github.com/BurntSushi/toml",
Version: "1.0.0",
Ecosystem: lockfile.GoEcosystem,
CompareAs: lockfile.GoEcosystem,
},
})
}

func TestParseGoLock_Replacements_Different(t *testing.T) {
t.Parallel()

packages, err := lockfile.ParseGoLock("fixtures/go/replace-different.mod")

if err != nil {
t.Errorf("Got unexpected error: %v", err)
}

expectPackages(t, packages, []lockfile.PackageDetails{
{
Name: "example.com/fork/foe",
Version: "1.4.5",
Ecosystem: lockfile.GoEcosystem,
CompareAs: lockfile.GoEcosystem,
},
{
Name: "example.com/fork/foe",
Version: "1.4.2",
Ecosystem: lockfile.GoEcosystem,
CompareAs: lockfile.GoEcosystem,
},
})
}

func TestParseGoLock_Replacements_NotRequired(t *testing.T) {
t.Parallel()

packages, err := lockfile.ParseGoLock("fixtures/go/replace-not-required.mod")

if err != nil {
t.Errorf("Got unexpected error: %v", err)
}

expectPackages(t, packages, []lockfile.PackageDetails{
{
Name: "golang.org/x/net",
Version: "0.5.6",
Ecosystem: lockfile.GoEcosystem,
CompareAs: lockfile.GoEcosystem,
},
{
Name: "github.com/BurntSushi/toml",
Version: "1.0.0",
Ecosystem: lockfile.GoEcosystem,
CompareAs: lockfile.GoEcosystem,
},
})
}

func TestParseGoLock_Replacements_NoVersion(t *testing.T) {
t.Parallel()

packages, err := lockfile.ParseGoLock("fixtures/go/replace-no-version.mod")

if err != nil {
t.Errorf("Got unexpected error: %v", err)
}

expectPackages(t, packages, []lockfile.PackageDetails{
{
Name: "example.com/fork/net",
Version: "1.4.5",
Ecosystem: lockfile.GoEcosystem,
CompareAs: lockfile.GoEcosystem,
},
})
}

0 comments on commit 849a94c

Please sign in to comment.