Prevent and detect overlapping dependencies The fetch rewrite will use these to get a simpler, more consistent behavior. Updates #32
diff --git a/delete.go b/delete.go index a1fe3b4..899a993 100644 --- a/delete.go +++ b/delete.go
@@ -51,6 +51,10 @@ if err != nil { return fmt.Errorf("could not get dependency: %v", err) } + if p != dependency.Importpath { + return fmt.Errorf("a parent of the specified dependency is vendored, remove that instead: %v", + dependency.Importpath) + } dependencies = append(dependencies, dependency) }
diff --git a/gbvendor/manifest.go b/gbvendor/manifest.go index 6bb0e27..6d68e74 100644 --- a/gbvendor/manifest.go +++ b/gbvendor/manifest.go
@@ -3,11 +3,13 @@ import ( "bytes" "encoding/json" + "errors" "fmt" "io" "os" "reflect" "sort" + "strings" ) // gb-vendor manifest support @@ -21,11 +23,20 @@ Dependencies []Dependency `json:"dependencies"` } +var ( + depPresent = errors.New("dependency already present") + depSubPkgPresent = errors.New("subpackages of this dependency are already present") + depMissing = errors.New("dependency does not exist") +) + // AddDependency adds a Dependency to the current Manifest. // If the dependency exists already then it returns and error. func (m *Manifest) AddDependency(dep Dependency) error { if m.HasImportpath(dep.Importpath) { - return fmt.Errorf("already registered") + return depPresent + } + if m.GetSubpackages(dep.Importpath) != nil { + return depSubPkgPresent } m.Dependencies = append(m.Dependencies, dep) return nil @@ -40,26 +51,39 @@ return nil } } - return fmt.Errorf("dependency does not exist") + return depMissing } -// HasImportpath reports whether the Manifest contains the import path. +// HasImportpath reports whether the Manifest contains the import path, +// or a parent of it. func (m *Manifest) HasImportpath(path string) bool { _, err := m.GetDependencyForImportpath(path) return err == nil } -// GetDependencyForRepository return a dependency for specified URL -// If the dependency does not exist it returns an error +// GetDependencyForRepository return a dependency for specified import +// path. Note that it might be a parent of the specified path. +// If the dependency does not exist it returns an error. func (m *Manifest) GetDependencyForImportpath(path string) (Dependency, error) { for _, d := range m.Dependencies { - if d.Importpath == path { + if strings.HasPrefix(path, d.Importpath) { return d, nil } } return Dependency{}, fmt.Errorf("dependency for %s does not exist", path) } +// GetSubpackages returns any Dependency in the Manifest that is a subpackage +// of the given import path. +func (m *Manifest) GetSubpackages(path string) (deps []Dependency) { + for _, d := range m.Dependencies { + if path != d.Importpath && strings.HasPrefix(d.Importpath, path) { + deps = append(deps, d) + } + } + return +} + // Dependency describes one vendored import path of code // A Dependency is an Importpath sources from a Respository // at Revision from Path. @@ -142,13 +166,24 @@ return nil, err } defer f.Close() - return readManifest(f) -} -func readManifest(r io.Reader) (*Manifest, error) { var m Manifest - d := json.NewDecoder(r) - err := d.Decode(&m) + d := json.NewDecoder(f) + err = d.Decode(&m) + + // Pass all dependencies through AddDependency to detect overlap + deps := m.Dependencies + m.Dependencies = nil + sort.Sort(byImportpath(deps)) // so that subpackages come after parents + for _, d := range deps { + if err := m.AddDependency(d); err == depPresent { + fmt.Fprintln(os.Stderr, "WARNING: overlapping dependency detected:", d.Importpath) + fmt.Fprintln(os.Stderr, "The subpackage will be ignored to fix undefined behavior. See https://git.io/vwK4B") + } else if err != nil { + return nil, err + } + } + return &m, err }