internal/feedback/feedback.go
// 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 feedback
import (
"encoding/hex"
"fmt"
"log"
"github.com/golang/dep/gps"
)
const (
// ConsTypeConstraint represents a constraint
ConsTypeConstraint = "constraint"
// ConsTypeHint represents a constraint type hint
ConsTypeHint = "hint"
// DepTypeDirect represents a direct dependency
DepTypeDirect = "direct dep"
// DepTypeTransitive represents a transitive dependency,
// or a dependency of a dependency
DepTypeTransitive = "transitive dep"
// DepTypeImported represents a dependency imported by an external tool
DepTypeImported = "imported dep"
)
// ConstraintFeedback holds project constraint feedback data
type ConstraintFeedback struct {
Constraint, LockedVersion, Revision, ConstraintType, DependencyType, ProjectPath string
}
// NewConstraintFeedback builds a feedback entry for a constraint in the manifest.
func NewConstraintFeedback(pc gps.ProjectConstraint, depType string) *ConstraintFeedback {
cf := &ConstraintFeedback{
Constraint: pc.Constraint.String(),
ProjectPath: string(pc.Ident.ProjectRoot),
DependencyType: depType,
}
if _, ok := pc.Constraint.(gps.Revision); ok {
cf.ConstraintType = ConsTypeHint
} else {
cf.ConstraintType = ConsTypeConstraint
}
return cf
}
// NewLockedProjectFeedback builds a feedback entry for a project in the lock.
func NewLockedProjectFeedback(lp gps.LockedProject, depType string) *ConstraintFeedback {
cf := &ConstraintFeedback{
ProjectPath: string(lp.Ident().ProjectRoot),
DependencyType: depType,
}
switch vt := lp.Version().(type) {
case gps.PairedVersion:
cf.LockedVersion = vt.String()
cf.Revision = vt.Revision().String()
case gps.UnpairedVersion: // Logically this should never occur, but handle for completeness sake
cf.LockedVersion = vt.String()
case gps.Revision:
cf.Revision = vt.String()
}
return cf
}
// LogFeedback logs feedback on changes made to the manifest or lock.
func (cf ConstraintFeedback) LogFeedback(logger *log.Logger) {
if cf.Constraint != "" {
logger.Printf(" %v", GetUsingFeedback(cf.Constraint, cf.ConstraintType, cf.DependencyType, cf.ProjectPath))
}
if cf.Revision != "" {
logger.Printf(" %v", GetLockingFeedback(cf.LockedVersion, cf.Revision, cf.DependencyType, cf.ProjectPath))
}
}
type brokenImport interface {
String() string
}
type modifiedImport struct {
source, branch, revision, version *StringDiff
projectPath string
}
func (mi modifiedImport) String() string {
var pv string
var pr string
pp := mi.projectPath
var cr string
var cv string
cp := ""
if mi.revision != nil {
pr = fmt.Sprintf("(%s)", trimSHA(mi.revision.Previous))
cr = fmt.Sprintf("(%s)", trimSHA(mi.revision.Current))
}
if mi.version != nil {
pv = mi.version.Previous
cv = mi.version.Current
} else if mi.branch != nil {
pv = mi.branch.Previous
cv = mi.branch.Current
}
if mi.source != nil {
pp = fmt.Sprintf("%s(%s)", mi.projectPath, mi.source.Previous)
cp = fmt.Sprintf(" for %s(%s)", mi.projectPath, mi.source.Current)
}
// Warning: Unable to preserve imported lock VERSION/BRANCH (REV) for PROJECT(SOURCE). Locking in VERSION/BRANCH (REV) for PROJECT(SOURCE)
return fmt.Sprintf("%v %s for %s. Locking in %v %s%s", pv, pr, pp, cv, cr, cp)
}
type removedImport struct {
source, branch, revision, version *StringDiff
projectPath string
}
func (ri removedImport) String() string {
var pr string
var pv string
pp := ri.projectPath
if ri.revision != nil {
pr = fmt.Sprintf("(%s)", trimSHA(ri.revision.Previous))
}
if ri.version != nil {
pv = ri.version.Previous
} else if ri.branch != nil {
pv = ri.branch.Previous
}
if ri.source != nil {
pp = fmt.Sprintf("%s(%s)", ri.projectPath, ri.source.Previous)
}
// Warning: Unable to preserve imported lock VERSION/BRANCH (REV) for PROJECT(SOURCE). Locking in VERSION/BRANCH (REV) for PROJECT(SOURCE)
return fmt.Sprintf("%v %s for %s. The project was removed from the lock because it is not used.", pv, pr, pp)
}
// BrokenImportFeedback holds information on changes to locks pre- and post- solving.
type BrokenImportFeedback struct {
brokenImports []brokenImport
}
// NewBrokenImportFeedback builds a feedback entry that compares an initially
// imported, unsolved lock to the same lock after it has been solved.
func NewBrokenImportFeedback(ld *LockDiff) *BrokenImportFeedback {
bi := &BrokenImportFeedback{}
if ld == nil {
return bi
}
for _, lpd := range ld.Modify {
if lpd.Branch == nil && lpd.Revision == nil && lpd.Source == nil && lpd.Version == nil {
continue
}
bi.brokenImports = append(bi.brokenImports, modifiedImport{
projectPath: string(lpd.Name),
source: lpd.Source,
branch: lpd.Branch,
revision: lpd.Revision,
version: lpd.Version,
})
}
for _, lpd := range ld.Remove {
bi.brokenImports = append(bi.brokenImports, removedImport{
projectPath: string(lpd.Name),
source: lpd.Source,
branch: lpd.Branch,
revision: lpd.Revision,
version: lpd.Version,
})
}
return bi
}
// LogFeedback logs a warning for all changes between the initially imported and post- solve locks
func (b BrokenImportFeedback) LogFeedback(logger *log.Logger) {
for _, bi := range b.brokenImports {
logger.Printf("Warning: Unable to preserve imported lock %v\n", bi)
}
}
// GetUsingFeedback returns a dependency "using" feedback message. For example:
//
// Using ^1.0.0 as constraint for direct dep github.com/foo/bar
// Using 1b8edb3 as hint for direct dep github.com/bar/baz
func GetUsingFeedback(version, consType, depType, projectPath string) string {
if depType == DepTypeImported {
return fmt.Sprintf("Using %s as initial %s for %s %s", version, consType, depType, projectPath)
}
return fmt.Sprintf("Using %s as %s for %s %s", version, consType, depType, projectPath)
}
// GetLockingFeedback returns a dependency "locking" feedback message. For
// example:
//
// Locking in v1.1.4 (bc29b4f) for direct dep github.com/foo/bar
// Locking in master (436f39d) for transitive dep github.com/baz/qux
func GetLockingFeedback(version, revision, depType, projectPath string) string {
revision = trimSHA(revision)
if depType == DepTypeImported {
if version == "" {
version = "*"
}
return fmt.Sprintf("Trying %s (%s) as initial lock for %s %s", version, revision, depType, projectPath)
}
return fmt.Sprintf("Locking in %s (%s) for %s %s", version, revision, depType, projectPath)
}
// trimSHA checks if revision is a valid SHA1 digest and trims to 7 characters.
func trimSHA(revision string) string {
if len(revision) == 40 {
if _, err := hex.DecodeString(revision); err == nil {
// Valid SHA1 digest
revision = revision[0:7]
}
}
return revision
}