Skip to content

Commit 849a94c

Browse files
authored
fix: handle replace directives in go.mod files (#162)
1 parent 1346e20 commit 849a94c

File tree

8 files changed

+229
-4
lines changed

8 files changed

+229
-4
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
require (
2+
golang.org/x/net v1.2.3
3+
golang.org/x/net v0.5.6
4+
)
5+
6+
replace (
7+
golang.org/x/net v1.2.3 => example.com/fork/foe v1.4.5
8+
golang.org/x/net v0.5.6 => example.com/fork/foe v1.4.2
9+
)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
require (
2+
golang.org/x/net v1.2.3
3+
github.com/BurntSushi/toml v1.0.0
4+
)
5+
6+
replace (
7+
golang.org/x/net v1.2.3 => ./fork/net
8+
)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
require (
2+
golang.org/x/net v1.2.3
3+
golang.org/x/net v0.5.6
4+
)
5+
6+
replace (
7+
golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5
8+
)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
require (
2+
golang.org/x/net v1.2.3
3+
golang.org/x/net v0.5.6
4+
)
5+
6+
replace (
7+
golang.org/x/net => example.com/fork/net v1.4.5
8+
)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
require (
2+
golang.org/x/net v0.5.6
3+
github.com/BurntSushi/toml v1.0.0
4+
)
5+
6+
replace (
7+
golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5
8+
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
require (
2+
golang.org/x/net v1.2.3
3+
)
4+
5+
replace golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5

pkg/lockfile/parse-go-lock.go

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ import (
99

1010
const GoEcosystem Ecosystem = "Go"
1111

12+
func deduplicatePackages(packages map[string]PackageDetails) map[string]PackageDetails {
13+
details := map[string]PackageDetails{}
14+
15+
for _, detail := range packages {
16+
details[detail.Name+"@"+detail.Version] = detail
17+
}
18+
19+
return details
20+
}
21+
1222
func ParseGoLock(pathToLockfile string) ([]PackageDetails, error) {
1323
lockfileContents, err := os.ReadFile(pathToLockfile)
1424

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

25-
packages := make([]PackageDetails, 0, len(parsedLockfile.Require))
35+
packages := map[string]PackageDetails{}
2636

2737
for _, require := range parsedLockfile.Require {
28-
packages = append(packages, PackageDetails{
38+
packages[require.Mod.Path+"@"+require.Mod.Version] = PackageDetails{
2939
Name: require.Mod.Path,
3040
Version: strings.TrimPrefix(require.Mod.Version, "v"),
3141
Ecosystem: GoEcosystem,
3242
CompareAs: GoEcosystem,
33-
})
43+
}
44+
}
45+
46+
for _, replace := range parsedLockfile.Replace {
47+
var replacements []string
48+
49+
if replace.Old.Version == "" {
50+
// If the left version is omitted, all versions of the module are replaced.
51+
for k, pkg := range packages {
52+
if pkg.Name == replace.Old.Path {
53+
replacements = append(replacements, k)
54+
}
55+
}
56+
} else {
57+
// If a version is present on the left side of the arrow (=>),
58+
// only that specific version of the module is replaced
59+
s := replace.Old.Path + "@" + replace.Old.Version
60+
61+
// A `replace` directive has no effect if the module version on the left side is not required.
62+
if _, ok := packages[s]; ok {
63+
replacements = []string{s}
64+
}
65+
}
66+
67+
for _, replacement := range replacements {
68+
packages[replacement] = PackageDetails{
69+
Name: replace.New.Path,
70+
Version: strings.TrimPrefix(replace.New.Version, "v"),
71+
Ecosystem: GoEcosystem,
72+
CompareAs: GoEcosystem,
73+
}
74+
}
3475
}
3576

36-
return packages, nil
77+
return pkgDetailsMapToSlice(deduplicatePackages(packages)), nil
3778
}

pkg/lockfile/parse-go-lock_test.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,141 @@ func TestParseGoLock_IndirectPackages(t *testing.T) {
121121
},
122122
})
123123
}
124+
125+
func TestParseGoLock_Replacements_One(t *testing.T) {
126+
t.Parallel()
127+
128+
packages, err := lockfile.ParseGoLock("fixtures/go/replace-one.mod")
129+
130+
if err != nil {
131+
t.Errorf("Got unexpected error: %v", err)
132+
}
133+
134+
expectPackages(t, packages, []lockfile.PackageDetails{
135+
{
136+
Name: "example.com/fork/net",
137+
Version: "1.4.5",
138+
Ecosystem: lockfile.GoEcosystem,
139+
CompareAs: lockfile.GoEcosystem,
140+
},
141+
})
142+
}
143+
144+
func TestParseGoLock_Replacements_Mixed(t *testing.T) {
145+
t.Parallel()
146+
147+
packages, err := lockfile.ParseGoLock("fixtures/go/replace-mixed.mod")
148+
149+
if err != nil {
150+
t.Errorf("Got unexpected error: %v", err)
151+
}
152+
153+
expectPackages(t, packages, []lockfile.PackageDetails{
154+
{
155+
Name: "example.com/fork/net",
156+
Version: "1.4.5",
157+
Ecosystem: lockfile.GoEcosystem,
158+
CompareAs: lockfile.GoEcosystem,
159+
},
160+
{
161+
Name: "golang.org/x/net",
162+
Version: "0.5.6",
163+
Ecosystem: lockfile.GoEcosystem,
164+
CompareAs: lockfile.GoEcosystem,
165+
},
166+
})
167+
}
168+
169+
func TestParseGoLock_Replacements_Local(t *testing.T) {
170+
t.Parallel()
171+
172+
packages, err := lockfile.ParseGoLock("fixtures/go/replace-local.mod")
173+
174+
if err != nil {
175+
t.Errorf("Got unexpected error: %v", err)
176+
}
177+
178+
expectPackages(t, packages, []lockfile.PackageDetails{
179+
{
180+
Name: "./fork/net",
181+
Version: "",
182+
Ecosystem: lockfile.GoEcosystem,
183+
CompareAs: lockfile.GoEcosystem,
184+
},
185+
{
186+
Name: "github.com/BurntSushi/toml",
187+
Version: "1.0.0",
188+
Ecosystem: lockfile.GoEcosystem,
189+
CompareAs: lockfile.GoEcosystem,
190+
},
191+
})
192+
}
193+
194+
func TestParseGoLock_Replacements_Different(t *testing.T) {
195+
t.Parallel()
196+
197+
packages, err := lockfile.ParseGoLock("fixtures/go/replace-different.mod")
198+
199+
if err != nil {
200+
t.Errorf("Got unexpected error: %v", err)
201+
}
202+
203+
expectPackages(t, packages, []lockfile.PackageDetails{
204+
{
205+
Name: "example.com/fork/foe",
206+
Version: "1.4.5",
207+
Ecosystem: lockfile.GoEcosystem,
208+
CompareAs: lockfile.GoEcosystem,
209+
},
210+
{
211+
Name: "example.com/fork/foe",
212+
Version: "1.4.2",
213+
Ecosystem: lockfile.GoEcosystem,
214+
CompareAs: lockfile.GoEcosystem,
215+
},
216+
})
217+
}
218+
219+
func TestParseGoLock_Replacements_NotRequired(t *testing.T) {
220+
t.Parallel()
221+
222+
packages, err := lockfile.ParseGoLock("fixtures/go/replace-not-required.mod")
223+
224+
if err != nil {
225+
t.Errorf("Got unexpected error: %v", err)
226+
}
227+
228+
expectPackages(t, packages, []lockfile.PackageDetails{
229+
{
230+
Name: "golang.org/x/net",
231+
Version: "0.5.6",
232+
Ecosystem: lockfile.GoEcosystem,
233+
CompareAs: lockfile.GoEcosystem,
234+
},
235+
{
236+
Name: "github.com/BurntSushi/toml",
237+
Version: "1.0.0",
238+
Ecosystem: lockfile.GoEcosystem,
239+
CompareAs: lockfile.GoEcosystem,
240+
},
241+
})
242+
}
243+
244+
func TestParseGoLock_Replacements_NoVersion(t *testing.T) {
245+
t.Parallel()
246+
247+
packages, err := lockfile.ParseGoLock("fixtures/go/replace-no-version.mod")
248+
249+
if err != nil {
250+
t.Errorf("Got unexpected error: %v", err)
251+
}
252+
253+
expectPackages(t, packages, []lockfile.PackageDetails{
254+
{
255+
Name: "example.com/fork/net",
256+
Version: "1.4.5",
257+
Ecosystem: lockfile.GoEcosystem,
258+
CompareAs: lockfile.GoEcosystem,
259+
},
260+
})
261+
}

0 commit comments

Comments
 (0)