diff --git a/cmd/dep/status.go b/cmd/dep/status.go index e995277bf6..8904cf0128 100644 --- a/cmd/dep/status.go +++ b/cmd/dep/status.go @@ -5,7 +5,9 @@ package main import ( + "bufio" "bytes" + "context" "encoding/json" "flag" "fmt" @@ -14,12 +16,14 @@ import ( "io/ioutil" "log" "sort" + "strings" "sync" "text/tabwriter" "github.com/golang/dep" "github.com/golang/dep/gps" "github.com/golang/dep/gps/paths" + "github.com/golang/dep/gps/pkgtree" "github.com/pkg/errors" ) @@ -237,6 +241,10 @@ func (cmd *statusCommand) Run(ctx *dep.Ctx, args []string) error { return err } + if len(args) > 0 { + return runProjectStatus(ctx, args, p, sm) + } + var buf bytes.Buffer var out outputter switch { @@ -740,6 +748,10 @@ type projectConstraint struct { Constraint gps.Constraint } +func (pc projectConstraint) String() string { + return fmt.Sprintf("%s(%s)", pc.Constraint.String(), string(pc.Project)) +} + // constraintsCollection is a map of ProjectRoot(dependency) and a collection of // projectConstraint for the dependencies. This can be used to find constraints // on a dependency and the projects that apply those constraints. @@ -831,3 +843,417 @@ type byProject []projectConstraint func (p byProject) Len() int { return len(p) } func (p byProject) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p byProject) Less(i, j int) bool { return p[i].Project < p[j].Project } + +// pubVersion type to store Public Version data of a project. +type pubVersions map[string][]string + +// TabString returns a tabwriter compatible string of pubVersion. +func (pv pubVersions) TabString() string { + var buf bytes.Buffer + w := bufio.NewWriter(&buf) + + // Create a list of version categories and sort it for consistent results. + var catgs []string + + for catg := range pv { + catgs = append(catgs, catg) + } + + // Sort the list of categories. + sort.Strings(catgs) + + // Count the number of different version categories. Use this count to add + // a newline("\n") and tab("\t") in all the version string list except the + // first one. This is required to maintain the indentation of the strings + // when used with tabwriter. + // semver:...\n \tbranches:...\n \tnonsemvers:... + count := 0 + for _, catg := range catgs { + count++ + if count > 1 { + fmt.Fprintf(w, "\n \t") + } + + vers := pv[catg] + + // Sort the versions list for consistent result. + sort.Strings(vers) + + fmt.Fprintf(w, "%s: %s", catg, strings.Join(vers, ", ")) + } + w.Flush() + + return buf.String() +} + +// projectImporters stores a map of project names that import a specific project. +type projectImporters map[string]bool + +func (pi projectImporters) String() string { + var projects []string + + for p := range pi { + projects = append(projects, p) + } + + // Sort the projects for a consistent result. + sort.Strings(projects) + + return strings.Join(projects, ", ") +} + +// packageImporters stores a map of package and projects that import them. +type packageImporters map[string][]string + +func (pi packageImporters) TabString() string { + var buf bytes.Buffer + w := bufio.NewWriter(&buf) + + // Create a list of packages in the map and sort it for consistent results. + var pkgs []string + + for pkg := range pi { + pkgs = append(pkgs, pkg) + } + + // Sort the list of packages. + sort.Strings(pkgs) + + // Count the number of different packages. Use this count to add + // a newline("\n") and tab("\t") in all the package string header except the + // first one. This is required to maintain the indentation of the strings + // when used with tabwriter. + // github.com/x/y\n \t github.com/a/b/foo\n \t github.com/a/b/bar + count := 0 + for _, pkg := range pkgs { + count++ + if count > 1 { + fmt.Fprintf(w, "\n \t") + } + + fmt.Fprintf(w, "%s", pkg) + + importers := pi[pkg] + + // Sort the importers list for consistent result. + sort.Strings(importers) + + for _, p := range importers { + fmt.Fprintf(w, "\n \t %s", p) + } + } + w.Flush() + + return buf.String() +} + +// projectConstraints is a slice of projectConstraint +type projectConstraints []projectConstraint + +func (pcs projectConstraints) TabString() string { + var buf bytes.Buffer + w := bufio.NewWriter(&buf) + + // Sort for consistent result. + sort.Sort(byProject(pcs)) + + // Count lines and add newlines("\n") and tabs("\t"), compatible with + // tabwriter. + // ^0.5.0(btb.com/x/y)\n \t^1.0.0(gh.com/f/b)\t \t^1.5.0(gh.com/a/c) + count := 0 + for _, c := range pcs { + count++ + if count > 1 { + fmt.Fprintf(w, "\n \t") + } + + fmt.Fprintf(w, "%s", c) + } + w.Flush() + + return buf.String() +} + +type projectStatus struct { + Project string + Version string + Constraints projectConstraints + Source string + AltSource string + PubVersions pubVersions + Revision string + LatestAllowed string + SourceType string + Packages []string + ProjectImporters projectImporters + PackageImporters packageImporters + UpstreamExists bool + UpstreamVersionExists bool +} + +func (ps projectStatus) String() string { + var buf bytes.Buffer + + w := tabwriter.NewWriter(&buf, 0, 4, 2, ' ', 0) + + upstreamExists := "no" + if ps.UpstreamExists { + upstreamExists = "yes" + } + + upstreamVersionExists := "no" + if ps.UpstreamVersionExists { + upstreamVersionExists = "yes" + } + + fmt.Fprintf(w, "\n"+ + "PROJECT:\t%s\n"+ + "VERSION:\t%s\n"+ + "CONSTRAINTS:\t%s\n"+ + "SOURCE:\t%s\n"+ + "ALT SOURCE:\t%s\n"+ + "PUB VERSION:\t%s\n"+ + "REVISION:\t%s\n"+ + "LATEST ALLOWED:\t%s\n"+ + "SOURCE TYPE:\t%s\n"+ + "PACKAGES:\t%s\n"+ + "PROJECT IMPORTERS:\t%s\n"+ + "PACKAGE IMPORTERS:\t%s\n"+ + "UPSTREAM EXISTS:\t%s\n"+ + "UPSTREAM VERSION EXISTS:\t%s", + ps.Project, ps.Version, ps.Constraints.TabString(), ps.Source, ps.AltSource, + ps.PubVersions.TabString(), ps.Revision, ps.LatestAllowed, ps.SourceType, + strings.Join(ps.Packages, ", "), ps.ProjectImporters, + ps.PackageImporters.TabString(), upstreamExists, upstreamVersionExists, + ) + w.Flush() + + return buf.String() +} + +func runProjectStatus(ctx *dep.Ctx, args []string, p *dep.Project, sm gps.SourceManager) error { + // Collect all the project roots from the arguments. + var prs []gps.ProjectRoot + + // Collect pointers to resultant projectStatus. + var resultStatus []*projectStatus + + // Get the proper project root of the projects. + for _, arg := range args { + pr, err := sm.DeduceProjectRoot(arg) + if err != nil { + return err + } + + // Check if the target project is in the lock. + if !p.Lock.HasProjectWithRoot(pr) { + return errors.Errorf("%s is not in the lock file. Ensure that the project is being used and run `dep ensure` to generate a new lock file.", pr) + } + + prs = append(prs, pr) + } + + // Collect all the constraints. + cc, ccerrs := collectConstraints(ctx, p, sm) + // If found any errors, print to stderr. + if len(ccerrs) > 0 { + if ctx.Verbose { + for _, e := range ccerrs { + ctx.Err.Println(e) + } + } else { + ctx.Out.Println("Got errors while collecting constraints. Rerun with `-v` flag to see details.") + } + return errors.New("errors while collecting constraints") + } + + // Collect list of packages in target projects. + pkgs := make(map[gps.ProjectRoot][]string) + + // Collect reachmap of all the locked projects. + reachmaps := make(map[string]pkgtree.ReachMap) + + // Create a list of depgraph packages to be used to exclude packages that + // the root project does not use. + rootPkgTree, err := p.ParseRootPackageTree() + if err != nil { + return err + } + rootrm, _ := rootPkgTree.ToReachMap(true, true, false, p.Manifest.IgnoredPackages()) + depgraphPkgs := rootrm.FlattenFn(paths.IsStandardImportPath) + + // Collect and store all the necessary data from pkgtree(s). + // TODO: Make this concurrent. + for _, pl := range p.Lock.Projects() { + pkgtree, err := sm.ListPackages(pl.Ident(), pl.Version()) + if err != nil { + return err + } + + // Collect reachmaps. + prm, _ := pkgtree.ToReachMap(true, true, false, p.Manifest.IgnoredPackages()) + reachmaps[string(pl.Ident().ProjectRoot)] = prm + + // Collect list of packages if it's one of the target projects. + for _, pr := range prs { + if pr == pl.Ident().ProjectRoot { + for pkg := range pkgtree.Packages { + pkgs[pr] = append(pkgs[pr], pkg) + } + } + } + } + + for _, pr := range prs { + // Create projectStatus and add project name and source. + projStatus := projectStatus{ + Project: string(pr), + Source: string(pr), + } + resultStatus = append(resultStatus, &projStatus) + + // Gather PROJECT IMPORTERS & PACKAGE IMPORTERS data. + for projectroot, rmap := range reachmaps { + // If it's not the target project, check if it imports the target + // project. + if string(pr) == projectroot { + continue + } + + for pkg, ie := range rmap { + // Exclude packages not used by root project. + if !contains(depgraphPkgs, pkg) { + continue + } + + // Iterate through the external imports and check if they + // import any package from the target project. + for _, p := range ie.External { + if strings.HasPrefix(p, string(pr)) { + // Initialize ProjectImporters map if it's the first entry. + if len(projStatus.ProjectImporters) == 0 { + projStatus.ProjectImporters = make(map[string]bool) + } + // Add to ProjectImporters if it doesn't exists. + if _, ok := projStatus.ProjectImporters[projectroot]; !ok { + projStatus.ProjectImporters[projectroot] = true + } + // Initialize PackageImporters map if it's the first entry. + if len(projStatus.PackageImporters[p]) == 0 { + projStatus.PackageImporters = make(map[string][]string) + } + // List Packages that import packages from target project. + projStatus.PackageImporters[p] = append(projStatus.PackageImporters[p], pkg) + } + } + } + } + + // Gather data from the locked project. + for _, pl := range p.Lock.Projects() { + if pr != pl.Ident().ProjectRoot { + continue + } + + // VERSION + projStatus.Version = pl.Version().String() + // ALT SOURCE + projStatus.AltSource = pl.Ident().Source + // REVISION + projStatus.Revision, _, _ = gps.VersionComponentStrings(pl.Version()) + + srcType, err := sm.GetSourceType(pl.Ident()) + if err != nil { + return err + } + // SOURCE TYPE + projStatus.SourceType = srcType.String() + + existsUpstream, err := sm.ExistsUpstream(pl.Ident()) + if err != nil { + return err + } + // UPSTREAM EXISTS + projStatus.UpstreamExists = existsUpstream + + // Fetch all the versions. + pvs, err := sm.ListVersions(pl.Ident()) + if err != nil { + return err + } + + // UPSTREAM VERSION EXISTS + for _, pv := range pvs { + if pv.Unpair().String() == pl.Version().String() { + projStatus.UpstreamVersionExists = true + break + } + } + + // PACKAGES + projStatus.Packages = pkgs[pr] + + // PUB VERSION + var semvers, branches, nonsemvers []string + for _, pv := range pvs { + switch pv.Type() { + case gps.IsSemver: + semvers = append(semvers, pv.Unpair().String()) + case gps.IsBranch: + branches = append(branches, pv.Unpair().String()) + default: + nonsemvers = append(nonsemvers, pv.Unpair().String()) + } + } + projStatus.PubVersions = make(map[string][]string) + projStatus.PubVersions["semvers"] = semvers + projStatus.PubVersions["branches"] = branches + projStatus.PubVersions["nonsemvers"] = nonsemvers + + // CONSTRAINTS + constraints := cc[string(pr)] + for _, c := range constraints { + projStatus.Constraints = append(projStatus.Constraints, c) + } + } + } + + // LATEST ALLOWED + // Perform a solve to get the latest allowed revisions. + params := p.MakeParams() + if ctx.Verbose { + params.TraceLogger = ctx.Err + } + + params.RootPackageTree, err = p.ParseRootPackageTree() + if err != nil { + return err + } + + // Add all the target projects in params.ToChange. + params.ToChange = append(params.ToChange, prs...) + + solver, err := gps.Prepare(params, sm) + if err != nil { + return err + } + + solution, err := solver.Solve(context.TODO()) + if err != nil { + return err + } + + // Iterate through the solution and get the revisions for the target projects. + projects := solution.Projects() + for _, rs := range resultStatus { + for _, project := range projects { + if gps.ProjectRoot(rs.Project) == project.Ident().ProjectRoot { + r, _, _ := gps.VersionComponentStrings(project.Version()) + rs.LatestAllowed = r + } + } + ctx.Out.Println(rs) + } + + return nil +} diff --git a/cmd/dep/status_test.go b/cmd/dep/status_test.go index c65ed0c67e..91a35a21f0 100644 --- a/cmd/dep/status_test.go +++ b/cmd/dep/status_test.go @@ -480,3 +480,100 @@ func TestValidateFlags(t *testing.T) { }) } } + +func TestProjectStatusString(t *testing.T) { + ver1, _ := gps.NewSemverConstraintIC("v1.0.0") + ver05, _ := gps.NewSemverConstraintIC("v0.5.0") + + testCases := []struct { + name string + ps projectStatus + wantString string + }{ + { + name: "basic projectStatus", + ps: projectStatus{ + Project: "github.com/x/y", + Version: "v1.0", + Constraints: projectConstraints{ + {"gh.com/f/b", ver1}, + {"btb.com/x/y", ver05}, + }, + Source: "github.com/x/y", + AltSource: "https://github.com/z/y", + PubVersions: pubVersions{ + "semvers": []string{"v0.5", "v0.7", "v1.0", "v1.5"}, + "branches": []string{"master", "dev"}, + "nonsemvers": []string{"v1.0-rc1", "v1.5-rc"}, + }, + Revision: "some-rev", + LatestAllowed: "some-other-rev", + SourceType: "git", + Packages: []string{"github.com/x/y/pkgA", "github.com/x/y/pkgB", "github.com/x/y/pkgB/foo"}, + ProjectImporters: projectImporters{ + "github.com/a/b": true, + "github.com/foo/bar": true, + }, + PackageImporters: packageImporters{ + "github.com/x/y/pkgA": []string{"github.com/a/b/z", "github.com/foo/bar/k"}, + "github.com/x/y/pkgB/foo": []string{"github.com/a/b/j"}, + }, + UpstreamExists: true, + UpstreamVersionExists: true, + }, + wantString: ` +PROJECT: github.com/x/y +VERSION: v1.0 +CONSTRAINTS: ^0.5.0(btb.com/x/y) + ^1.0.0(gh.com/f/b) +SOURCE: github.com/x/y +ALT SOURCE: https://github.com/z/y +PUB VERSION: branches: dev, master + nonsemvers: v1.0-rc1, v1.5-rc + semvers: v0.5, v0.7, v1.0, v1.5 +REVISION: some-rev +LATEST ALLOWED: some-other-rev +SOURCE TYPE: git +PACKAGES: github.com/x/y/pkgA, github.com/x/y/pkgB, github.com/x/y/pkgB/foo +PROJECT IMPORTERS: github.com/a/b, github.com/foo/bar +PACKAGE IMPORTERS: github.com/x/y/pkgA + github.com/a/b/z + github.com/foo/bar/k + github.com/x/y/pkgB/foo + github.com/a/b/j +UPSTREAM EXISTS: yes +UPSTREAM VERSION EXISTS: yes`, + }, + { + name: "projectStatus with no upstream exists", + ps: projectStatus{ + UpstreamExists: false, + UpstreamVersionExists: false, + }, + wantString: ` +PROJECT: +VERSION: +CONSTRAINTS: +SOURCE: +ALT SOURCE: +PUB VERSION: +REVISION: +LATEST ALLOWED: +SOURCE TYPE: +PACKAGES: +PROJECT IMPORTERS: +PACKAGE IMPORTERS: +UPSTREAM EXISTS: no +UPSTREAM VERSION EXISTS: no`, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + gotString := tc.ps.String() + if gotString != tc.wantString { + t.Errorf("unexpected projectStatus string: \n\t(GOT): %v\n\t(WNT): %v", gotString, tc.wantString) + } + }) + } +} diff --git a/cmd/dep/testdata/harness_tests/status/project_status/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/status/project_status/final/Gopkg.lock new file mode 100644 index 0000000000..326bc04f3c --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/project_status/final/Gopkg.lock @@ -0,0 +1,27 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + name = "github.com/darkowlzz/deptest-project-status" + packages = ["pkgfoo"] + revision = "bddc1e2c8e4f0f264223389adf345d3858823677" + +[[projects]] + name = "github.com/sdboyer/deptest" + packages = ["."] + revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" + version = "v1.0.0" + +[[projects]] + name = "github.com/sdboyer/deptestdos" + packages = ["."] + revision = "5c607206be5decd28e6263ffffdcee067266015e" + version = "v2.0.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "36a300774881748908b8209ab36dbfda73e8c51145d6bdbc9d87afd2655186c9" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/status/project_status/final/Gopkg.toml b/cmd/dep/testdata/harness_tests/status/project_status/final/Gopkg.toml new file mode 100644 index 0000000000..6dbb73c194 --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/project_status/final/Gopkg.toml @@ -0,0 +1,11 @@ +[[constraint]] + branch = "master" + name = "github.com/darkowlzz/deptest-project-status" + +[[constraint]] + name = "github.com/sdboyer/deptest" + version = "1.0.0" + +[[constraint]] + name = "github.com/sdboyer/deptestdos" + version = "2.0.0" diff --git a/cmd/dep/testdata/harness_tests/status/project_status/final/main.go b/cmd/dep/testdata/harness_tests/status/project_status/final/main.go new file mode 100644 index 0000000000..579d6d2a29 --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/project_status/final/main.go @@ -0,0 +1,13 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + _ "github.com/darkowlzz/deptest-project-status/pkgfoo" + _ "github.com/sdboyer/deptest" + _ "github.com/sdboyer/deptestdos" +) + +func main() {} diff --git a/cmd/dep/testdata/harness_tests/status/project_status/initial/Gopkg.lock b/cmd/dep/testdata/harness_tests/status/project_status/initial/Gopkg.lock new file mode 100644 index 0000000000..326bc04f3c --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/project_status/initial/Gopkg.lock @@ -0,0 +1,27 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + name = "github.com/darkowlzz/deptest-project-status" + packages = ["pkgfoo"] + revision = "bddc1e2c8e4f0f264223389adf345d3858823677" + +[[projects]] + name = "github.com/sdboyer/deptest" + packages = ["."] + revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" + version = "v1.0.0" + +[[projects]] + name = "github.com/sdboyer/deptestdos" + packages = ["."] + revision = "5c607206be5decd28e6263ffffdcee067266015e" + version = "v2.0.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "36a300774881748908b8209ab36dbfda73e8c51145d6bdbc9d87afd2655186c9" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/status/project_status/initial/Gopkg.toml b/cmd/dep/testdata/harness_tests/status/project_status/initial/Gopkg.toml new file mode 100644 index 0000000000..6dbb73c194 --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/project_status/initial/Gopkg.toml @@ -0,0 +1,11 @@ +[[constraint]] + branch = "master" + name = "github.com/darkowlzz/deptest-project-status" + +[[constraint]] + name = "github.com/sdboyer/deptest" + version = "1.0.0" + +[[constraint]] + name = "github.com/sdboyer/deptestdos" + version = "2.0.0" diff --git a/cmd/dep/testdata/harness_tests/status/project_status/initial/main.go b/cmd/dep/testdata/harness_tests/status/project_status/initial/main.go new file mode 100644 index 0000000000..579d6d2a29 --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/project_status/initial/main.go @@ -0,0 +1,13 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + _ "github.com/darkowlzz/deptest-project-status/pkgfoo" + _ "github.com/sdboyer/deptest" + _ "github.com/sdboyer/deptestdos" +) + +func main() {} diff --git a/cmd/dep/testdata/harness_tests/status/project_status/stdout.txt b/cmd/dep/testdata/harness_tests/status/project_status/stdout.txt new file mode 100644 index 0000000000..34942c7449 --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/project_status/stdout.txt @@ -0,0 +1,19 @@ + +PROJECT: github.com/sdboyer/deptest +VERSION: v1.0.0 +CONSTRAINTS: +SOURCE: github.com/sdboyer/deptest +ALT SOURCE: +PUB VERSION: branches: master + nonsemvers: + semvers: v0.8.0, v0.8.1, v1.0.0 +REVISION: ff2948a2ac8f538c4ecd55962e919d1e13e74baf +LATEST ALLOWED: ff2948a2ac8f538c4ecd55962e919d1e13e74baf +SOURCE TYPE: git +PACKAGES: github.com/sdboyer/deptest +PROJECT IMPORTERS: github.com/darkowlzz/deptest-project-status, github.com/sdboyer/deptestdos +PACKAGE IMPORTERS: github.com/sdboyer/deptest + github.com/darkowlzz/deptest-project-status/pkgfoo + github.com/sdboyer/deptestdos +UPSTREAM EXISTS: yes +UPSTREAM VERSION EXISTS: yes diff --git a/cmd/dep/testdata/harness_tests/status/project_status/testcase.json b/cmd/dep/testdata/harness_tests/status/project_status/testcase.json new file mode 100644 index 0000000000..0509f7d837 --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/project_status/testcase.json @@ -0,0 +1,12 @@ +{ + "commands": [ + ["ensure"], + ["status", "github.com/sdboyer/deptest"] + ], + "error-expected": "", + "vendor-final": [ + "github.com/darkowlzz/deptest-project-status", + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptestdos" + ] +} diff --git a/cmd/dep/testdata/harness_tests/status/project_status_not_in_lock/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/status/project_status_not_in_lock/final/Gopkg.lock new file mode 100644 index 0000000000..326bc04f3c --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/project_status_not_in_lock/final/Gopkg.lock @@ -0,0 +1,27 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + name = "github.com/darkowlzz/deptest-project-status" + packages = ["pkgfoo"] + revision = "bddc1e2c8e4f0f264223389adf345d3858823677" + +[[projects]] + name = "github.com/sdboyer/deptest" + packages = ["."] + revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" + version = "v1.0.0" + +[[projects]] + name = "github.com/sdboyer/deptestdos" + packages = ["."] + revision = "5c607206be5decd28e6263ffffdcee067266015e" + version = "v2.0.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "36a300774881748908b8209ab36dbfda73e8c51145d6bdbc9d87afd2655186c9" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/status/project_status_not_in_lock/final/Gopkg.toml b/cmd/dep/testdata/harness_tests/status/project_status_not_in_lock/final/Gopkg.toml new file mode 100644 index 0000000000..6dbb73c194 --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/project_status_not_in_lock/final/Gopkg.toml @@ -0,0 +1,11 @@ +[[constraint]] + branch = "master" + name = "github.com/darkowlzz/deptest-project-status" + +[[constraint]] + name = "github.com/sdboyer/deptest" + version = "1.0.0" + +[[constraint]] + name = "github.com/sdboyer/deptestdos" + version = "2.0.0" diff --git a/cmd/dep/testdata/harness_tests/status/project_status_not_in_lock/final/main.go b/cmd/dep/testdata/harness_tests/status/project_status_not_in_lock/final/main.go new file mode 100644 index 0000000000..579d6d2a29 --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/project_status_not_in_lock/final/main.go @@ -0,0 +1,13 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + _ "github.com/darkowlzz/deptest-project-status/pkgfoo" + _ "github.com/sdboyer/deptest" + _ "github.com/sdboyer/deptestdos" +) + +func main() {} diff --git a/cmd/dep/testdata/harness_tests/status/project_status_not_in_lock/initial/Gopkg.lock b/cmd/dep/testdata/harness_tests/status/project_status_not_in_lock/initial/Gopkg.lock new file mode 100644 index 0000000000..326bc04f3c --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/project_status_not_in_lock/initial/Gopkg.lock @@ -0,0 +1,27 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + name = "github.com/darkowlzz/deptest-project-status" + packages = ["pkgfoo"] + revision = "bddc1e2c8e4f0f264223389adf345d3858823677" + +[[projects]] + name = "github.com/sdboyer/deptest" + packages = ["."] + revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" + version = "v1.0.0" + +[[projects]] + name = "github.com/sdboyer/deptestdos" + packages = ["."] + revision = "5c607206be5decd28e6263ffffdcee067266015e" + version = "v2.0.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "36a300774881748908b8209ab36dbfda73e8c51145d6bdbc9d87afd2655186c9" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/status/project_status_not_in_lock/initial/Gopkg.toml b/cmd/dep/testdata/harness_tests/status/project_status_not_in_lock/initial/Gopkg.toml new file mode 100644 index 0000000000..6dbb73c194 --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/project_status_not_in_lock/initial/Gopkg.toml @@ -0,0 +1,11 @@ +[[constraint]] + branch = "master" + name = "github.com/darkowlzz/deptest-project-status" + +[[constraint]] + name = "github.com/sdboyer/deptest" + version = "1.0.0" + +[[constraint]] + name = "github.com/sdboyer/deptestdos" + version = "2.0.0" diff --git a/cmd/dep/testdata/harness_tests/status/project_status_not_in_lock/initial/main.go b/cmd/dep/testdata/harness_tests/status/project_status_not_in_lock/initial/main.go new file mode 100644 index 0000000000..579d6d2a29 --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/project_status_not_in_lock/initial/main.go @@ -0,0 +1,13 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + _ "github.com/darkowlzz/deptest-project-status/pkgfoo" + _ "github.com/sdboyer/deptest" + _ "github.com/sdboyer/deptestdos" +) + +func main() {} diff --git a/cmd/dep/testdata/harness_tests/status/project_status_not_in_lock/testcase.json b/cmd/dep/testdata/harness_tests/status/project_status_not_in_lock/testcase.json new file mode 100644 index 0000000000..2bbbc78cdb --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/project_status_not_in_lock/testcase.json @@ -0,0 +1,12 @@ +{ + "commands": [ + ["ensure"], + ["status", "github.com/golang/dep"] + ], + "error-expected": "github.com/golang/dep is not in the lock file. Ensure that the project is being used and run `dep ensure` to generate a new lock file.", + "vendor-final": [ + "github.com/darkowlzz/deptest-project-status", + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptestdos" + ] +} diff --git a/gps/solve_basic_test.go b/gps/solve_basic_test.go index 910b9d622b..434256119d 100644 --- a/gps/solve_basic_test.go +++ b/gps/solve_basic_test.go @@ -1487,6 +1487,14 @@ func (sm *depspecSourceManager) SourceURLsForPath(ip string) ([]*url.URL, error) return nil, fmt.Errorf("dummy sm doesn't implement SourceURLsForPath") } +func (sm *depspecSourceManager) GetSourceType(id ProjectIdentifier) (SourceType, error) { + return InvalidSource, fmt.Errorf("dummy sm doesn't implement GetSourceType") +} + +func (sm *depspecSourceManager) ExistsUpstream(id ProjectIdentifier) (bool, error) { + return false, fmt.Errorf("dummy sm doesn't implement ExistsUpstream") +} + func (sm *depspecSourceManager) rootSpec() depspec { return sm.specs[0] } diff --git a/gps/source.go b/gps/source.go index 54723754d0..7aef47b1f8 100644 --- a/gps/source.go +++ b/gps/source.go @@ -388,6 +388,10 @@ func (sg *sourceGateway) getManifestAndLock(ctx context.Context, pr ProjectRoot, return m, l, nil } +func (sg *sourceGateway) getSourceType() SourceType { + return sg.src.sourceType() +} + // FIXME ProjectRoot input either needs to parameterize the cache, or be // incorporated on the fly on egress...? func (sg *sourceGateway) listPackages(ctx context.Context, pr ProjectRoot, v Version) (pkgtree.PackageTree, error) { @@ -570,7 +574,7 @@ func (sg *sourceGateway) require(ctx context.Context, wanted sourceState) (errSt case sourceIsSetUp: sg.src, addlState, err = sg.maybe.try(ctx, sg.cachedir, sg.cache, sg.suprvsr) case sourceExistsUpstream: - err = sg.suprvsr.do(ctx, sg.src.sourceType(), ctSourcePing, func(ctx context.Context) error { + err = sg.suprvsr.do(ctx, sg.src.sourceType().String(), ctSourcePing, func(ctx context.Context) error { if !sg.src.existsUpstream(ctx) { return fmt.Errorf("%s does not exist upstream", sg.src.upstreamURL()) } @@ -578,7 +582,7 @@ func (sg *sourceGateway) require(ctx context.Context, wanted sourceState) (errSt }) case sourceExistsLocally: if !sg.src.existsLocally(ctx) { - err = sg.suprvsr.do(ctx, sg.src.sourceType(), ctSourceInit, func(ctx context.Context) error { + err = sg.suprvsr.do(ctx, sg.src.sourceType().String(), ctSourceInit, func(ctx context.Context) error { return sg.src.initLocal(ctx) }) @@ -590,7 +594,7 @@ func (sg *sourceGateway) require(ctx context.Context, wanted sourceState) (errSt } case sourceHasLatestVersionList: var pvl []PairedVersion - err = sg.suprvsr.do(ctx, sg.src.sourceType(), ctListVersions, func(ctx context.Context) error { + err = sg.suprvsr.do(ctx, sg.src.sourceType().String(), ctListVersions, func(ctx context.Context) error { pvl, err = sg.src.listVersions(ctx) return err }) @@ -599,7 +603,7 @@ func (sg *sourceGateway) require(ctx context.Context, wanted sourceState) (errSt sg.cache.setVersionMap(pvl) } case sourceHasLatestLocally: - err = sg.suprvsr.do(ctx, sg.src.sourceType(), ctSourceFetch, func(ctx context.Context) error { + err = sg.suprvsr.do(ctx, sg.src.sourceType().String(), ctSourceFetch, func(ctx context.Context) error { return sg.src.updateLocal(ctx) }) } @@ -634,5 +638,5 @@ type source interface { revisionPresentIn(Revision) (bool, error) disambiguateRevision(context.Context, Revision) (Revision, error) exportRevisionTo(context.Context, Revision, string) error - sourceType() string + sourceType() SourceType } diff --git a/gps/source_manager.go b/gps/source_manager.go index 489084d7ef..f7598ab1c3 100644 --- a/gps/source_manager.go +++ b/gps/source_manager.go @@ -26,6 +26,33 @@ import ( "github.com/sdboyer/constext" ) +// SourceType is the type of project source (VCS, packaged file, etc). +type SourceType uint8 + +// SourceType constants. +const ( + InvalidSource = iota + VcsGit + VcsHg + VcsBzr + VcsSvn +) + +func (st SourceType) String() string { + switch st { + case VcsGit: + return "git" + case VcsHg: + return "hg" + case VcsBzr: + return "bzr" + case VcsSvn: + return "svn" + default: + return fmt.Sprintf("%#v does not represent a known source type, this probably indicates a bug", st) + } +} + // Used to compute a friendly filepath from a URL-shaped input. var sanitizer = strings.NewReplacer("-", "--", ":", "-", "/", "-", "+", "-") @@ -100,6 +127,12 @@ type SourceManager interface { // repository root. GetManifestAndLock(ProjectIdentifier, Version, ProjectAnalyzer) (Manifest, Lock, error) + // GetSourceType returns SourceType for the provided ProjectIdentifier. + GetSourceType(ProjectIdentifier) (SourceType, error) + + // ExistsUpstream indicates whether the provided Project exists upstream. + ExistsUpstream(ProjectIdentifier) (bool, error) + // ExportProject writes out the tree of the provided import path, at the // provided version, to the provided directory. ExportProject(context.Context, ProjectIdentifier, Version, string) error @@ -422,6 +455,34 @@ func (sm *SourceMgr) GetManifestAndLock(id ProjectIdentifier, v Version, an Proj return srcg.getManifestAndLock(context.TODO(), id.ProjectRoot, v, an) } +// GetSourceType returns SourceType for the provided ProjectIdentifier. +func (sm *SourceMgr) GetSourceType(id ProjectIdentifier) (SourceType, error) { + if atomic.LoadInt32(&sm.releasing) == 1 { + return InvalidSource, ErrSourceManagerIsReleased + } + + srcg, err := sm.srcCoord.getSourceGatewayFor(context.TODO(), id) + if err != nil { + return InvalidSource, err + } + + return srcg.getSourceType(), nil +} + +// ExistsUpstream indicates whether the provided Project exists upstream. +func (sm *SourceMgr) ExistsUpstream(id ProjectIdentifier) (bool, error) { + if atomic.LoadInt32(&sm.releasing) == 1 { + return false, ErrSourceManagerIsReleased + } + + srcg, err := sm.srcCoord.getSourceGatewayFor(context.TODO(), id) + if err != nil { + return false, err + } + + return srcg.existsUpstream(context.TODO()), nil +} + // ListPackages parses the tree of the Go packages at and below the ProjectRoot // of the given ProjectIdentifier, at the given version. func (sm *SourceMgr) ListPackages(id ProjectIdentifier, v Version) (pkgtree.PackageTree, error) { diff --git a/gps/vcs_source.go b/gps/vcs_source.go index f6b7aef136..3e1e5324a6 100644 --- a/gps/vcs_source.go +++ b/gps/vcs_source.go @@ -14,6 +14,7 @@ import ( "strings" "github.com/Masterminds/semver" + "github.com/Masterminds/vcs" "github.com/golang/dep/gps/pkgtree" "github.com/golang/dep/internal/fs" "github.com/pkg/errors" @@ -23,8 +24,19 @@ type baseVCSSource struct { repo ctxRepo } -func (bs *baseVCSSource) sourceType() string { - return string(bs.repo.Vcs()) +func (bs *baseVCSSource) sourceType() SourceType { + switch bs.repo.Vcs() { + case vcs.Git: + return VcsGit + case vcs.Hg: + return VcsHg + case vcs.Bzr: + return VcsBzr + case vcs.Svn: + return VcsSvn + default: + return InvalidSource + } } func (bs *baseVCSSource) existsLocally(ctx context.Context) bool {