diff --git a/internal/devconfig/config_test.go b/internal/devconfig/config_test.go index 9f747796156..f7152eec384 100644 --- a/internal/devconfig/config_test.go +++ b/internal/devconfig/config_test.go @@ -1,6 +1,7 @@ package devconfig import ( + "fmt" "io/fs" "os" "path/filepath" @@ -430,6 +431,34 @@ func TestDefault(t *testing.T) { } } +// TestPackagesDoesNotDedupDistinctGitFlakes is a regression test for issue +// #2704. Two git flake references that point at the same repository but +// different subdirectories (via the "dir" query parameter) are distinct +// inputs and must both be preserved by Config.Packages(). Previously they +// were deduplicated because the "@" in the "git@host" portion of the URL was +// mistaken for a version delimiter, giving both packages the same parsed name. +func TestPackagesDoesNotDedupDistinctGitFlakes(t *testing.T) { + pkgA := "git+ssh://git@gitlab.com/org/repo.git?dir=betteralign&ref=master&rev=17d2bedca4884176e0d08078aa42311053e531c2" + pkgB := "git+ssh://git@gitlab.com/org/repo.git?dir=recovergoroutine&ref=master&rev=593d44945851d912f0c2158c46cc76da445adc43" + + jsonConfig := fmt.Sprintf(`{"packages":[%q,%q]}`, pkgA, pkgB) + cfg, err := loadBytes([]byte(jsonConfig)) + if err != nil { + t.Fatalf("load error: %v", err) + } + + packages := cfg.Packages(false /*includeRemovedTriggerPackages*/) + got := make([]string, len(packages)) + for i, p := range packages { + got[i] = p.VersionedName() + } + + want := []string{pkgA, pkgB} + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("Config.Packages() dropped a distinct git flake input (-want +got):\n%s", diff) + } +} + func TestOSExpandIfPossible(t *testing.T) { tests := []struct { name string diff --git a/internal/devconfig/configfile/packages.go b/internal/devconfig/configfile/packages.go index 2c445d33d1d..3589af0e66b 100644 --- a/internal/devconfig/configfile/packages.go +++ b/internal/devconfig/configfile/packages.go @@ -11,6 +11,7 @@ import ( "github.com/pkg/errors" orderedmap "github.com/wk8/go-ordered-map/v2" "go.jetify.com/devbox/internal/boxcli/usererr" + "go.jetify.com/devbox/internal/devpkg/pkgtype" "go.jetify.com/devbox/internal/nix" "go.jetify.com/devbox/internal/searcher" "go.jetify.com/devbox/internal/ux" @@ -361,6 +362,17 @@ func (p *Package) UnmarshalJSON(data []byte) error { // parseVersionedName parses the name and version from package@version representation func parseVersionedName(versionedName string) (name, version string) { + // Flake references are not versioned Devbox packages, and they may + // legitimately contain an "@" in their URL (for example + // "git+ssh://git@gitlab.com/org/repo.git?dir=foo"). Splitting such a + // reference on "@" produces a name that is not unique across flakes that + // differ only by their query parameters (such as "dir"), which causes + // distinct inputs to be incorrectly deduplicated. Treat the whole + // reference as the name in that case. See issue #2704. + if pkgtype.IsFlake(versionedName) { + return versionedName, "" /*version*/ + } + var found bool name, version, found = searcher.ParseVersionedPackage(versionedName) if !found { diff --git a/internal/devconfig/configfile/packages_test.go b/internal/devconfig/configfile/packages_test.go index 38aefa46cd0..0f2d3c20cda 100644 --- a/internal/devconfig/configfile/packages_test.go +++ b/internal/devconfig/configfile/packages_test.go @@ -288,6 +288,22 @@ func TestParseVersionedName(t *testing.T) { expectedName: "github:nixos/nixpkgs/5233fd2ba76a3accb5aaa999c00509a11fd0793c#hello", expectedVersion: "", }, + { + // Git flake refs contain "@" in their URL (git@host) but are + // not versioned packages. The whole ref must be the name so + // that refs differing only by query params (e.g. "dir") stay + // distinct. See issue #2704. + name: "git-ssh-flake-with-dir", + input: "git+ssh://git@gitlab.com/org/repo.git?dir=betteralign&ref=master&rev=17d2bedca4884176e0d08078aa42311053e531c2", + expectedName: "git+ssh://git@gitlab.com/org/repo.git?dir=betteralign&ref=master&rev=17d2bedca4884176e0d08078aa42311053e531c2", + expectedVersion: "", + }, + { + name: "git-ssh-flake-without-query", + input: "git+ssh://git@github.com/org/repo.git", + expectedName: "git+ssh://git@github.com/org/repo.git", + expectedVersion: "", + }, } for _, testCase := range testCases {