diff --git a/cmd/dep/status.go b/cmd/dep/status.go index 50af108b9c..42b6dd32ea 100644 --- a/cmd/dep/status.go +++ b/cmd/dep/status.go @@ -6,6 +6,7 @@ package main import ( "bytes" + "context" "encoding/json" "flag" "fmt" @@ -120,6 +121,13 @@ type outputter interface { MissingFooter() error } +// Only a subset of the outputters should be able to output old statuses. +type oldOutputter interface { + OldHeader() error + OldLine(*OldStatus) error + OldFooter() error +} + type tableOutput struct{ w *tabwriter.Writer } func (out *tableOutput) BasicHeader() error { @@ -162,10 +170,31 @@ func (out *tableOutput) MissingFooter() error { return out.w.Flush() } +func (out *tableOutput) OldHeader() error { + _, err := fmt.Fprintf(out.w, "PROJECT\tCONSTRAINT\tREVISION\tLATEST\n") + return err +} + +func (out *tableOutput) OldLine(os *OldStatus) error { + _, err := fmt.Fprintf(out.w, + "%s\t%s\t%s\t%s\t\n", + os.ProjectRoot, + os.getConsolidatedConstraint(), + formatVersion(os.Revision), + os.getConsolidatedLatest(shortRev), + ) + return err +} + +func (out *tableOutput) OldFooter() error { + return out.w.Flush() +} + type jsonOutput struct { w io.Writer basic []*rawStatus missing []*MissingStatus + old []*rawOldStatus } func (out *jsonOutput) BasicHeader() error { @@ -196,6 +225,20 @@ func (out *jsonOutput) MissingFooter() error { return json.NewEncoder(out.w).Encode(out.missing) } +func (out *jsonOutput) OldHeader() error { + out.old = []*rawOldStatus{} + return nil +} + +func (out *jsonOutput) OldLine(os *OldStatus) error { + out.old = append(out.old, os.marshalJSON()) + return nil +} + +func (out *jsonOutput) OldFooter() error { + return json.NewEncoder(out.w).Encode(out.old) +} + type dotOutput struct { w io.Writer o string @@ -237,7 +280,6 @@ type templateOutput struct { func (out *templateOutput) BasicHeader() error { return nil } func (out *templateOutput) BasicFooter() error { return nil } - func (out *templateOutput) BasicLine(bs *BasicStatus) error { data := rawStatus{ ProjectRoot: bs.ProjectRoot, @@ -250,9 +292,14 @@ func (out *templateOutput) BasicLine(bs *BasicStatus) error { return out.tmpl.Execute(out.w, data) } +func (out *templateOutput) OldHeader() error { return nil } +func (out *templateOutput) OldFooter() error { return nil } +func (out *templateOutput) OldLine(os *OldStatus) error { + return out.tmpl.Execute(out.w, os) +} + func (out *templateOutput) MissingHeader() error { return nil } func (out *templateOutput) MissingFooter() error { return nil } - func (out *templateOutput) MissingLine(ms *MissingStatus) error { return out.tmpl.Execute(out.w, ms) } @@ -288,8 +335,6 @@ func (cmd *statusCommand) Run(ctx *dep.Ctx, args []string) error { switch { case cmd.missing: return errors.Errorf("not implemented") - case cmd.old: - return errors.Errorf("not implemented") case cmd.json: out = &jsonOutput{ w: &buf, @@ -320,6 +365,15 @@ func (cmd *statusCommand) Run(ctx *dep.Ctx, args []string) error { return errors.Errorf("no Gopkg.lock found. Run `dep ensure` to generate lock file") } + if cmd.old { + if _, ok := out.(oldOutputter); !ok { + return errors.Errorf("invalid output format used") + } + err = cmd.runOld(ctx, out.(oldOutputter), p, sm) + ctx.Out.Print(buf.String()) + return err + } + hasMissingPkgs, errCount, err := runStatusAll(ctx, out, p, sm) if err != nil { switch err { @@ -387,6 +441,140 @@ func (cmd *statusCommand) validateFlags() error { return nil } +// OldStatus contains information about all the out of date packages in a project. +type OldStatus struct { + ProjectRoot string + Constraint gps.Constraint + Revision gps.Revision + Latest gps.Version +} + +type rawOldStatus struct { + ProjectRoot, Constraint, Revision, Latest string +} + +func (os OldStatus) getConsolidatedConstraint() string { + var constraint string + if os.Constraint != nil { + if v, ok := os.Constraint.(gps.Version); ok { + constraint = formatVersion(v) + } else { + constraint = os.Constraint.String() + } + } + return constraint +} + +func (os OldStatus) getConsolidatedLatest(revSize uint8) string { + latest := "" + if os.Latest != nil { + switch revSize { + case shortRev: + latest = formatVersion(os.Latest) + case longRev: + latest = os.Latest.String() + } + } + return latest +} + +func (os OldStatus) marshalJSON() *rawOldStatus { + return &rawOldStatus{ + ProjectRoot: os.ProjectRoot, + Constraint: os.getConsolidatedConstraint(), + Revision: string(os.Revision), + Latest: os.getConsolidatedLatest(longRev), + } +} + +func (cmd *statusCommand) runOld(ctx *dep.Ctx, out oldOutputter, p *dep.Project, sm gps.SourceManager) error { + // While the network churns on ListVersions() requests, statically analyze + // code from the current project. + ptree, err := p.ParseRootPackageTree() + if err != nil { + return err + } + + // Set up a solver in order to check the InputHash. + params := gps.SolveParameters{ + ProjectAnalyzer: dep.Analyzer{}, + RootDir: p.AbsRoot, + RootPackageTree: ptree, + Manifest: p.Manifest, + // Locks aren't a part of the input hash check, so we can omit it. + } + + logger := ctx.Err + if ctx.Verbose { + params.TraceLogger = ctx.Err + } else { + logger = log.New(ioutil.Discard, "", 0) + } + + // Check update for all the projects. + params.ChangeAll = true + + solver, err := gps.Prepare(params, sm) + if err != nil { + return errors.Wrap(err, "fastpath solver prepare") + } + + logger.Println("Solving dependency graph to determine which dependencies can be updated.") + solution, err := solver.Solve(context.TODO()) + if err != nil { + return errors.Wrap(err, "runOld") + } + + var oldStatuses []OldStatus + solutionProjects := solution.Projects() + + for _, proj := range p.Lock.Projects() { + for _, sProj := range solutionProjects { + // Look for the same project in solution and lock. + if sProj.Ident().ProjectRoot != proj.Ident().ProjectRoot { + continue + } + + // If revisions are not the same then it is old and we should display it. + latestRev, _, _ := gps.VersionComponentStrings(sProj.Version()) + atRev, _, _ := gps.VersionComponentStrings(proj.Version()) + if atRev == latestRev { + continue + } + + var constraint gps.Constraint + // Getting Constraint. + if pp, has := p.Manifest.Ovr[proj.Ident().ProjectRoot]; has && pp.Constraint != nil { + // manifest has override for project. + constraint = pp.Constraint + } else if pp, has := p.Manifest.Constraints[proj.Ident().ProjectRoot]; has && pp.Constraint != nil { + // manifest has normal constraint. + constraint = pp.Constraint + } else { + // No constraint exists. No need to worry about displaying it. + continue + } + + // Generate the old status data and append it. + os := OldStatus{ + ProjectRoot: proj.Ident().String(), + Revision: gps.Revision(atRev), + Latest: gps.Revision(latestRev), + Constraint: constraint, + } + oldStatuses = append(oldStatuses, os) + } + } + + out.OldHeader() + for _, ostat := range oldStatuses { + out.OldLine(&ostat) + } + out.OldFooter() + + return nil +} + type rawStatus struct { ProjectRoot string Constraint string diff --git a/cmd/dep/status_test.go b/cmd/dep/status_test.go index 9285b5a547..509a6749d3 100644 --- a/cmd/dep/status_test.go +++ b/cmd/dep/status_test.go @@ -529,6 +529,16 @@ func TestValidateFlags(t *testing.T) { cmd: statusCommand{missing: true, old: true}, wantErr: errors.Wrapf(errors.New("cannot pass multiple operating mode flags"), "[-old -missing]"), }, + { + name: "old with -dot", + cmd: statusCommand{dot: true, old: true}, + wantErr: errors.New("-dot generates dependency graph; cannot pass other flags"), + }, + { + name: "old with template", + cmd: statusCommand{old: true, template: "foo"}, + wantErr: nil, + }, } for _, tc := range testCases { diff --git a/cmd/dep/testdata/harness_tests/status/old_constraints/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/status/old_constraints/final/Gopkg.lock new file mode 100644 index 0000000000..7f844d35d6 --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/old_constraints/final/Gopkg.lock @@ -0,0 +1,27 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/carolynvs/go-dep-test" + packages = ["."] + revision = "b9c5511fa463628e6251554db29a4be161d02aed" + version = "0.1.0" + +[[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 = "c89811fc98c9a1310c94dc63b84f364d13c46ea3a40bd2cba7d77377ab346543" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/status/old_constraints/final/Gopkg.toml b/cmd/dep/testdata/harness_tests/status/old_constraints/final/Gopkg.toml new file mode 100644 index 0000000000..eeb589024a --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/old_constraints/final/Gopkg.toml @@ -0,0 +1,11 @@ +[[constraint]] + name = "github.com/sdboyer/deptestdos" + version = "v2.0.0" + +[[constraint]] + name = "github.com/sdboyer/deptest" + version = "v1.0.0" + +[[constraint]] + name = "github.com/carolynvs/go-dep-test" + version = "v0.1.0" diff --git a/cmd/dep/testdata/harness_tests/status/old_constraints/initial/Gopkg.lock b/cmd/dep/testdata/harness_tests/status/old_constraints/initial/Gopkg.lock new file mode 100644 index 0000000000..7f844d35d6 --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/old_constraints/initial/Gopkg.lock @@ -0,0 +1,27 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/carolynvs/go-dep-test" + packages = ["."] + revision = "b9c5511fa463628e6251554db29a4be161d02aed" + version = "0.1.0" + +[[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 = "c89811fc98c9a1310c94dc63b84f364d13c46ea3a40bd2cba7d77377ab346543" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/status/old_constraints/initial/Gopkg.toml b/cmd/dep/testdata/harness_tests/status/old_constraints/initial/Gopkg.toml new file mode 100644 index 0000000000..eeb589024a --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/old_constraints/initial/Gopkg.toml @@ -0,0 +1,11 @@ +[[constraint]] + name = "github.com/sdboyer/deptestdos" + version = "v2.0.0" + +[[constraint]] + name = "github.com/sdboyer/deptest" + version = "v1.0.0" + +[[constraint]] + name = "github.com/carolynvs/go-dep-test" + version = "v0.1.0" diff --git a/cmd/dep/testdata/harness_tests/status/old_constraints/initial/main.go b/cmd/dep/testdata/harness_tests/status/old_constraints/initial/main.go new file mode 100644 index 0000000000..ec059eadb6 --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/old_constraints/initial/main.go @@ -0,0 +1,14 @@ +// Copyright 2018 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/carolynvs/go-dep-test" + _ "github.com/sdboyer/deptest" + _ "github.com/sdboyer/deptestdos" +) + +func main() { +} diff --git a/cmd/dep/testdata/harness_tests/status/old_constraints/stdout.txt b/cmd/dep/testdata/harness_tests/status/old_constraints/stdout.txt new file mode 100644 index 0000000000..ad557f414b --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/old_constraints/stdout.txt @@ -0,0 +1,2 @@ +PROJECT CONSTRAINT REVISION LATEST +github.com/carolynvs/go-dep-test ^0.1.0 b9c5511 4069198 diff --git a/cmd/dep/testdata/harness_tests/status/old_constraints/testcase.json b/cmd/dep/testdata/harness_tests/status/old_constraints/testcase.json new file mode 100644 index 0000000000..a136acd187 --- /dev/null +++ b/cmd/dep/testdata/harness_tests/status/old_constraints/testcase.json @@ -0,0 +1,12 @@ +{ + "commands": [ + ["ensure"], + ["status", "-old"] + ], + "error-expected": "", + "vendor-final": [ + "github.com/carolynvs/go-dep-test", + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptestdos" + ] +}