diff --git a/lock.json b/lock.json index f545daad..3f65e44f 100644 --- a/lock.json +++ b/lock.json @@ -1,5 +1,5 @@ { - "memo": "47c0ec3d677d1d5c01778bc81836801e009c641797708b8fcd773dce954c7714", + "memo": "56093ed07896d0d5c47ee3472d6ad16e9a7e6826598364caf01ac30dc9c277c5", "projects": [ { "name": "github.com/Masterminds/semver", @@ -35,16 +35,8 @@ }, { "name": "github.com/sdboyer/gps", - "version": "v0.13.1", - "revision": "e4435f58dfa1aee0c5324667f5b6cbad668db8fc", - "packages": [ - "." - ] - }, - { - "name": "github.com/termie/go-shutil", "branch": "master", - "revision": "bcacb06fecaeec8dc42af03c87c6949f4a05c74c", + "revision": "b27173f3bf78a69a70dba35122ce24eb1679a53a", "packages": [ "." ] diff --git a/manifest.json b/manifest.json index 46bb54c9..3ba303e8 100644 --- a/manifest.json +++ b/manifest.json @@ -4,13 +4,13 @@ "branch": "2.x" }, "github.com/Masterminds/vcs": { - "version": ">=1.8.0, <2.0.0" + "version": "^1.8.0" }, "github.com/pkg/errors": { "version": ">=0.8.0, <1.0.0" }, "github.com/sdboyer/gps": { - "version": ">=0.13.0, <1.0.0" + "branch": "master" } } } diff --git a/vendor/github.com/sdboyer/gps/README.md b/vendor/github.com/sdboyer/gps/README.md index 4c7c3c45..b7735ef9 100644 --- a/vendor/github.com/sdboyer/gps/README.md +++ b/vendor/github.com/sdboyer/gps/README.md @@ -101,8 +101,8 @@ general library could know _a priori_. * What dependency version constraints are declared by [all dependencies](https://github.com/sdboyer/gps/wiki/gps-for-Implementors#the-projectanalyzer) * Given a [previous solution](https://github.com/sdboyer/gps/wiki/gps-for-Implementors#lock-data), [which versions to let change, and how](https://github.com/sdboyer/gps/wiki/gps-for-Implementors#tochange-changeall-and-downgrade) * In the absence of a previous solution, whether or not to use [preferred versions](https://github.com/sdboyer/gps/wiki/gps-for-Implementors#preferred-versions) -* Allowing, or not, the user to [swap in different network names](https://github.com/sdboyer/gps/wiki/gps-for-Implementors#projectidentifier) for import paths (e.g. forks) -* Specifying additional input/source packages not reachable from the root import graph ([not complete](https://github.com/sdboyer/gps/issues/42)) +* Allowing, or not, the user to [swap in different source locations](https://github.com/sdboyer/gps/wiki/gps-for-Implementors#projectidentifier) for import paths (e.g. forks) +* Specifying additional input/source packages not reachable from the root import graph This list may not be exhaustive - see the [implementor's guide](https://github.com/sdboyer/gps/wiki/gps-for-Implementors) diff --git a/vendor/github.com/sdboyer/gps/bridge.go b/vendor/github.com/sdboyer/gps/bridge.go index ba304ccd..5d8c4c6a 100644 --- a/vendor/github.com/sdboyer/gps/bridge.go +++ b/vendor/github.com/sdboyer/gps/bridge.go @@ -361,10 +361,10 @@ func (b *bridge) SyncSourceFor(id ProjectIdentifier) error { // operations attempt each member, and will take the most open/optimistic // answer. // -// This technically does allow tags to match branches - something we -// otherwise try hard to avoid - but because the original input constraint never -// actually changes (and is never written out in the Result), there's no harmful -// case of a user suddenly riding a branch when they expected a fixed tag. +// This technically does allow tags to match branches - something we otherwise +// try hard to avoid - but because the original input constraint never actually +// changes (and is never written out in the Solution), there's no harmful case +// of a user suddenly riding a branch when they expected a fixed tag. type versionTypeUnion []Version // This should generally not be called, but is required for the interface. If it diff --git a/vendor/github.com/sdboyer/gps/constraints.go b/vendor/github.com/sdboyer/gps/constraints.go index 7eaad998..53dc6086 100644 --- a/vendor/github.com/sdboyer/gps/constraints.go +++ b/vendor/github.com/sdboyer/gps/constraints.go @@ -198,8 +198,8 @@ func pcSliceToMap(l []ProjectConstraint, r ...[]ProjectConstraint) ProjectConstr for _, pc := range l { final[pc.Ident.ProjectRoot] = ProjectProperties{ - NetworkName: pc.Ident.NetworkName, - Constraint: pc.Constraint, + Source: pc.Ident.Source, + Constraint: pc.Constraint, } } @@ -213,8 +213,8 @@ func pcSliceToMap(l []ProjectConstraint, r ...[]ProjectConstraint) ProjectConstr final[pc.Ident.ProjectRoot] = pp } else { final[pc.Ident.ProjectRoot] = ProjectProperties{ - NetworkName: pc.Ident.NetworkName, - Constraint: pc.Constraint, + Source: pc.Ident.Source, + Constraint: pc.Constraint, } } } @@ -231,7 +231,7 @@ func (m ProjectConstraints) asSortedSlice() []ProjectConstraint { pcs[k] = ProjectConstraint{ Ident: ProjectIdentifier{ ProjectRoot: pr, - NetworkName: pp.NetworkName, + Source: pp.Source, }, Constraint: pp.Constraint, } @@ -262,8 +262,8 @@ func (m ProjectConstraints) merge(other ...ProjectConstraints) (out ProjectConst for pr, pp := range pcm { if rpp, exists := out[pr]; exists { pp.Constraint = pp.Constraint.Intersect(rpp.Constraint) - if pp.NetworkName == "" { - pp.NetworkName = rpp.NetworkName + if pp.Source == "" { + pp.Source = rpp.Source } } out[pr] = pp @@ -297,7 +297,7 @@ func (m ProjectConstraints) override(pr ProjectRoot, pp ProjectProperties) worki wc := workingConstraint{ Ident: ProjectIdentifier{ ProjectRoot: pr, - NetworkName: pp.NetworkName, + Source: pp.Source, }, Constraint: pp.Constraint, } @@ -319,8 +319,8 @@ func (m ProjectConstraints) override(pr ProjectRoot, pp ProjectProperties) worki // from. Such disagreement is exactly what overrides preclude, so // there's no need to preserve the meaning of "" here - thus, we can // treat it as a zero value and ignore it, rather than applying it. - if opp.NetworkName != "" { - wc.Ident.NetworkName = opp.NetworkName + if opp.Source != "" { + wc.Ident.Source = opp.Source wc.overrNet = true } } diff --git a/vendor/github.com/sdboyer/gps/hash.go b/vendor/github.com/sdboyer/gps/hash.go index bb5fa420..c8bd4642 100644 --- a/vendor/github.com/sdboyer/gps/hash.go +++ b/vendor/github.com/sdboyer/gps/hash.go @@ -26,7 +26,7 @@ func (s *solver) HashInputs() []byte { buf := new(bytes.Buffer) for _, pd := range p { buf.WriteString(string(pd.Ident.ProjectRoot)) - buf.WriteString(pd.Ident.NetworkName) + buf.WriteString(pd.Ident.Source) // FIXME Constraint.String() is a surjective-only transformation - tags // and branches with the same name are written out as the same string. // This could, albeit rarely, result in input collisions when a real @@ -50,10 +50,14 @@ func (s *solver) HashInputs() []byte { buf.WriteString(perr.P.CommentPath) buf.WriteString(perr.P.ImportPath) for _, imp := range perr.P.Imports { - buf.WriteString(imp) + if !isStdLib(imp) { + buf.WriteString(imp) + } } for _, imp := range perr.P.TestImports { - buf.WriteString(imp) + if !isStdLib(imp) { + buf.WriteString(imp) + } } } } @@ -88,8 +92,8 @@ func (s *solver) HashInputs() []byte { for _, pc := range s.ovr.asSortedSlice() { buf.WriteString(string(pc.Ident.ProjectRoot)) - if pc.Ident.NetworkName != "" { - buf.WriteString(pc.Ident.NetworkName) + if pc.Ident.Source != "" { + buf.WriteString(pc.Ident.Source) } if pc.Constraint != nil { buf.WriteString(pc.Constraint.String()) diff --git a/vendor/github.com/sdboyer/gps/hash_test.go b/vendor/github.com/sdboyer/gps/hash_test.go index b2df5579..2aa8fb9d 100644 --- a/vendor/github.com/sdboyer/gps/hash_test.go +++ b/vendor/github.com/sdboyer/gps/hash_test.go @@ -182,7 +182,7 @@ func TestHashInputsOverrides(t *testing.T) { // First case - override something not in the root, just with network name rm.ovr = map[ProjectRoot]ProjectProperties{ "c": ProjectProperties{ - NetworkName: "car", + Source: "car", }, } params := SolveParameters{ @@ -259,8 +259,8 @@ func TestHashInputsOverrides(t *testing.T) { // Override not in root, both constraint and network name rm.ovr["e"] = ProjectProperties{ - NetworkName: "groucho", - Constraint: NewBranch("plexiglass"), + Source: "groucho", + Constraint: NewBranch("plexiglass"), } dig = s.HashInputs() h = sha256.New() @@ -334,7 +334,7 @@ func TestHashInputsOverrides(t *testing.T) { // Override in root, only network name rm.ovr["a"] = ProjectProperties{ - NetworkName: "nota", + Source: "nota", } dig = s.HashInputs() h = sha256.New() @@ -373,8 +373,8 @@ func TestHashInputsOverrides(t *testing.T) { // Override in root, network name and constraint rm.ovr["a"] = ProjectProperties{ - NetworkName: "nota", - Constraint: NewVersion("fluglehorn"), + Source: "nota", + Constraint: NewVersion("fluglehorn"), } dig = s.HashInputs() h = sha256.New() diff --git a/vendor/github.com/sdboyer/gps/lock.go b/vendor/github.com/sdboyer/gps/lock.go index fea53196..a349761c 100644 --- a/vendor/github.com/sdboyer/gps/lock.go +++ b/vendor/github.com/sdboyer/gps/lock.go @@ -1,6 +1,9 @@ package gps -import "sort" +import ( + "bytes" + "sort" +) // Lock represents data from a lock file (or however the implementing tool // chooses to store it) at a particular version that is relevant to the @@ -20,6 +23,43 @@ type Lock interface { Projects() []LockedProject } +// LocksAreEq checks if two locks are equivalent. This checks that +// all contained LockedProjects are equal, and optionally (if the third +// parameter is true) whether the locks' input hashes are equal. +func LocksAreEq(l1, l2 Lock, checkHash bool) bool { + // Cheapest ops first + if checkHash && !bytes.Equal(l1.InputHash(), l2.InputHash()) { + return false + } + + p1, p2 := l1.Projects(), l2.Projects() + if len(p1) != len(p2) { + return false + } + + // Check if the slices are sorted already. If they are, we can compare + // without copying. Otherwise, we have to copy to avoid altering the + // original input. + sp1, sp2 := lpsorter(p1), lpsorter(p2) + if len(p1) > 1 && !sort.IsSorted(sp1) { + p1 = make([]LockedProject, len(p1)) + copy(p1, l1.Projects()) + sort.Sort(lpsorter(p1)) + } + if len(p2) > 1 && !sort.IsSorted(sp2) { + p2 = make([]LockedProject, len(p2)) + copy(p2, l2.Projects()) + sort.Sort(lpsorter(p2)) + } + + for k, lp := range p1 { + if !lp.Eq(p2[k]) { + return false + } + } + return true +} + // LockedProject is a single project entry from a lock file. It expresses the // project's name, one or both of version and underlying revision, the network // URI for accessing it, the path at which it should be placed within a vendor @@ -107,6 +147,33 @@ func (lp LockedProject) Version() Version { return lp.v.Is(lp.r) } +// Eq checks if two LockedProject instances are equal. +func (lp LockedProject) Eq(lp2 LockedProject) bool { + if lp.pi != lp2.pi { + return false + } + + if lp.r != lp2.r { + return false + } + + if len(lp.pkgs) != len(lp2.pkgs) { + return false + } + + for k, v := range lp.pkgs { + if lp2.pkgs[k] != v { + return false + } + } + + if !lp.v.Matches(lp2.v) { + return false + } + + return true +} + // Packages returns the list of packages from within the LockedProject that are // actually used in the import graph. Some caveats: // diff --git a/vendor/github.com/sdboyer/gps/lock_test.go b/vendor/github.com/sdboyer/gps/lock_test.go index b5805029..f462b224 100644 --- a/vendor/github.com/sdboyer/gps/lock_test.go +++ b/vendor/github.com/sdboyer/gps/lock_test.go @@ -24,3 +24,98 @@ func TestLockedProjectSorting(t *testing.T) { t.Errorf("SortLockedProject did not sort as expected:\n\t(GOT) %s\n\t(WNT) %s", lps2, lps) } } + +func TestLockedProjectsEq(t *testing.T) { + lps := []LockedProject{ + NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0"), []string{"gps"}), + NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0"), nil), + NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0"), []string{"gps", "flugle"}), + NewLockedProject(mkPI("foo"), NewVersion("nada"), []string{"foo"}), + NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0"), []string{"flugle", "gps"}), + NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0").Is("278a227dfc3d595a33a77ff3f841fd8ca1bc8cd0"), []string{"gps"}), + NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.11.0"), []string{"gps"}), + } + + fix := []struct { + l1, l2 int + shouldeq bool + err string + }{ + {0, 0, true, "lp does not eq self"}, + {0, 5, false, "should not eq with different rev"}, + {0, 6, false, "should not eq with different version"}, + {5, 5, true, "should eq with same rev"}, + {0, 1, false, "should not eq when other pkg list is empty"}, + {0, 2, false, "should not eq when other pkg list is longer"}, + {2, 4, false, "should not eq when pkg lists are out of order"}, + {0, 3, false, "should not eq totally different lp"}, + } + + for _, f := range fix { + if f.shouldeq { + if !lps[f.l1].Eq(lps[f.l2]) { + t.Error(f.err) + } + if !lps[f.l2].Eq(lps[f.l1]) { + t.Error(f.err + (" (reversed)")) + } + } else { + if lps[f.l1].Eq(lps[f.l2]) { + t.Error(f.err) + } + if lps[f.l2].Eq(lps[f.l1]) { + t.Error(f.err + (" (reversed)")) + } + + } + } +} + +func TestLocksAreEq(t *testing.T) { + gpl := NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0").Is("278a227dfc3d595a33a77ff3f841fd8ca1bc8cd0"), []string{"gps"}) + svpl := NewLockedProject(mkPI("github.com/Masterminds/semver"), NewVersion("v2.0.0"), []string{"semver"}) + bbbt := NewLockedProject(mkPI("github.com/beeblebrox/browntown"), NewBranch("master").Is("63fc17eb7966a6f4cc0b742bf42731c52c4ac740"), []string{"browntown", "smoochies"}) + + l1 := solution{ + hd: []byte("foo"), + p: []LockedProject{ + gpl, + bbbt, + svpl, + }, + } + + l2 := solution{ + p: []LockedProject{ + svpl, + gpl, + }, + } + + if LocksAreEq(l1, l2, true) { + t.Fatal("should have failed on hash check") + } + + if LocksAreEq(l1, l2, false) { + t.Fatal("should have failed on length check") + } + + l2.p = append(l2.p, bbbt) + + if !LocksAreEq(l1, l2, false) { + t.Fatal("should be eq, must have failed on individual lp check") + } + + // ensure original input sort order is maintained + if !l1.p[0].Eq(gpl) { + t.Error("checking equality resorted l1") + } + if !l2.p[0].Eq(svpl) { + t.Error("checking equality resorted l2") + } + + l1.p[0] = NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.11.0"), []string{"gps"}) + if LocksAreEq(l1, l2, false) { + t.Error("should fail when individual lp were not eq") + } +} diff --git a/vendor/github.com/sdboyer/gps/manager_test.go b/vendor/github.com/sdboyer/gps/manager_test.go index 0970c595..4dbf75c6 100644 --- a/vendor/github.com/sdboyer/gps/manager_test.go +++ b/vendor/github.com/sdboyer/gps/manager_test.go @@ -358,7 +358,7 @@ func TestGetSources(t *testing.T) { } // All of them _should_ select https, so this should work - lpi.NetworkName = "https://" + lpi.NetworkName + lpi.Source = "https://" + lpi.Source src3, err := sm.getSourceFor(lpi) if err != nil { t.Errorf("(src %q) unexpected error getting explicit https source: %s", nn, err) @@ -367,7 +367,7 @@ func TestGetSources(t *testing.T) { } // Now put in http, and they should differ - lpi.NetworkName = "http://" + string(lpi.ProjectRoot) + lpi.Source = "http://" + string(lpi.ProjectRoot) src4, err := sm.getSourceFor(lpi) if err != nil { t.Errorf("(src %q) unexpected error getting explicit http source: %s", nn, err) diff --git a/vendor/github.com/sdboyer/gps/manifest.go b/vendor/github.com/sdboyer/gps/manifest.go index 4e3afb30..a5682992 100644 --- a/vendor/github.com/sdboyer/gps/manifest.go +++ b/vendor/github.com/sdboyer/gps/manifest.go @@ -158,7 +158,7 @@ func prepManifest(m Manifest) Manifest { // normalize between these two by omitting such instances entirely, as // it negates some possibility for false mismatches in input hashing. if d.Constraint == nil { - if d.NetworkName == "" { + if d.Source == "" { continue } d.Constraint = anyConstraint{} @@ -169,7 +169,7 @@ func prepManifest(m Manifest) Manifest { for k, d := range ddeps { if d.Constraint == nil { - if d.NetworkName == "" { + if d.Source == "" { continue } d.Constraint = anyConstraint{} diff --git a/vendor/github.com/sdboyer/gps/manifest_test.go b/vendor/github.com/sdboyer/gps/manifest_test.go index 8faaa189..50717b06 100644 --- a/vendor/github.com/sdboyer/gps/manifest_test.go +++ b/vendor/github.com/sdboyer/gps/manifest_test.go @@ -8,13 +8,13 @@ func TestPrepManifest(t *testing.T) { Deps: ProjectConstraints{ ProjectRoot("foo"): ProjectProperties{}, ProjectRoot("bar"): ProjectProperties{ - NetworkName: "whatever", + Source: "whatever", }, }, TestDeps: ProjectConstraints{ ProjectRoot("baz"): ProjectProperties{}, ProjectRoot("qux"): ProjectProperties{ - NetworkName: "whatever", + Source: "whatever", }, }, } diff --git a/vendor/github.com/sdboyer/gps/maybe_source.go b/vendor/github.com/sdboyer/gps/maybe_source.go index 8c5191cc..d59962ae 100644 --- a/vendor/github.com/sdboyer/gps/maybe_source.go +++ b/vendor/github.com/sdboyer/gps/maybe_source.go @@ -82,10 +82,11 @@ func (m maybeGitSource) try(cachedir string, an ProjectAnalyzer) (source, string } src.baseVCSSource.lvfunc = src.listVersions - - _, err = src.listVersions() - if err != nil { - return nil, "", err + if !r.CheckLocal() { + _, err = src.listVersions() + if err != nil { + return nil, "", err + } } return src, ustr, nil @@ -129,10 +130,11 @@ func (m maybeGopkginSource) try(cachedir string, an ProjectAnalyzer) (source, st } src.baseVCSSource.lvfunc = src.listVersions - - _, err = src.listVersions() - if err != nil { - return nil, "", err + if !r.CheckLocal() { + _, err = src.listVersions() + if err != nil { + return nil, "", err + } } return src, ustr, nil diff --git a/vendor/github.com/sdboyer/gps/satisfy.go b/vendor/github.com/sdboyer/gps/satisfy.go index d3a76b1f..d86c4e4a 100644 --- a/vendor/github.com/sdboyer/gps/satisfy.go +++ b/vendor/github.com/sdboyer/gps/satisfy.go @@ -195,8 +195,8 @@ func (s *solver) checkDepsDisallowsSelected(a atomWithPackages, cdep completeDep } // checkIdentMatches ensures that the LocalName of a dep introduced by an atom, -// has the same NetworkName as what's already been selected (assuming anything's -// been selected). +// has the same Source as what's already been selected (assuming anything's been +// selected). // // In other words, this ensures that the solver never simultaneously selects two // identifiers with the same local name, but that disagree about where their diff --git a/vendor/github.com/sdboyer/gps/solve_basic_test.go b/vendor/github.com/sdboyer/gps/solve_basic_test.go index 6f5f912c..982523ea 100644 --- a/vendor/github.com/sdboyer/gps/solve_basic_test.go +++ b/vendor/github.com/sdboyer/gps/solve_basic_test.go @@ -19,7 +19,7 @@ func nvSplit(info string) (id ProjectIdentifier, version string) { if strings.Contains(info, " from ") { parts := regfrom.FindStringSubmatch(info) info = parts[1] + " " + parts[3] - id.NetworkName = parts[2] + id.Source = parts[2] } s := strings.SplitN(info, " ", 2) @@ -42,7 +42,7 @@ func nvrSplit(info string) (id ProjectIdentifier, version string, revision Revis if strings.Contains(info, " from ") { parts := regfrom.FindStringSubmatch(info) info = fmt.Sprintf("%s %s", parts[1], parts[3]) - id.NetworkName = parts[2] + id.Source = parts[2] } s := strings.SplitN(info, " ", 3) @@ -205,7 +205,7 @@ type depspec struct { // treated as a test-only dependency. func mkDepspec(pi string, deps ...string) depspec { pa := mkAtom(pi) - if string(pa.id.ProjectRoot) != pa.id.NetworkName && pa.id.NetworkName != "" { + if string(pa.id.ProjectRoot) != pa.id.Source && pa.id.Source != "" { panic("alternate source on self makes no sense") } @@ -252,9 +252,9 @@ func mkADep(atom, pdep string, c Constraint, pl ...string) dependency { } // mkPI creates a ProjectIdentifier with the ProjectRoot as the provided -// string, and the NetworkName unset. +// string, and the Source unset. // -// Call normalize() on the returned value if you need the NetworkName to be be +// Call normalize() on the returned value if you need the Source to be be // equal to the ProjectRoot. func mkPI(root string) ProjectIdentifier { return ProjectIdentifier{ @@ -1274,7 +1274,7 @@ var basicFixtures = map[string]basicFixture{ }, ovr: ProjectConstraints{ ProjectRoot("bar"): ProjectProperties{ - NetworkName: "bar", + Source: "bar", }, }, r: mksolution( diff --git a/vendor/github.com/sdboyer/gps/solve_bimodal_test.go b/vendor/github.com/sdboyer/gps/solve_bimodal_test.go index 211887a8..d2b65c63 100644 --- a/vendor/github.com/sdboyer/gps/solve_bimodal_test.go +++ b/vendor/github.com/sdboyer/gps/solve_bimodal_test.go @@ -640,7 +640,7 @@ var bimodalFixtures = map[string]bimodalFixture{ ), }, // When a given project is initially brought in using the default (i.e., - // empty) ProjectIdentifier.NetworkName, and a later, presumably + // empty) ProjectIdentifier.Source, and a later, presumably // as-yet-undiscovered dependency specifies an alternate net addr for it, we // have to fail - even though, if the deps were visited in the opposite // order (deeper dep w/the alternate location first, default location @@ -719,7 +719,7 @@ var bimodalFixtures = map[string]bimodalFixture{ }, ovr: ProjectConstraints{ ProjectRoot("bar"): ProjectProperties{ - NetworkName: "baz", + Source: "baz", }, }, r: mksolution( @@ -740,7 +740,7 @@ var bimodalFixtures = map[string]bimodalFixture{ }, ovr: ProjectConstraints{ ProjectRoot("bar"): ProjectProperties{ - NetworkName: "baz", + Source: "baz", }, }, r: mksolution( diff --git a/vendor/github.com/sdboyer/gps/solve_test.go b/vendor/github.com/sdboyer/gps/solve_test.go index dd546f87..2d3de696 100644 --- a/vendor/github.com/sdboyer/gps/solve_test.go +++ b/vendor/github.com/sdboyer/gps/solve_test.go @@ -167,11 +167,11 @@ func solveBimodalAndCheck(fix bimodalFixture, t *testing.T) (res Solution, err e func fixtureSolveSimpleChecks(fix specfix, soln Solution, err error, t *testing.T) (Solution, error) { ppi := func(id ProjectIdentifier) string { - // need this so we can clearly tell if there's a NetworkName or not - if id.NetworkName == "" { + // need this so we can clearly tell if there's a Source or not + if id.Source == "" { return string(id.ProjectRoot) } - return fmt.Sprintf("%s (from %s)", id.ProjectRoot, id.NetworkName) + return fmt.Sprintf("%s (from %s)", id.ProjectRoot, id.Source) } pv := func(v Version) string { diff --git a/vendor/github.com/sdboyer/gps/solver.go b/vendor/github.com/sdboyer/gps/solver.go index 6bb34d3b..e855f063 100644 --- a/vendor/github.com/sdboyer/gps/solver.go +++ b/vendor/github.com/sdboyer/gps/solver.go @@ -252,7 +252,7 @@ func Prepare(params SolveParameters, sm SourceManager) (Solver, error) { // Validate no empties in the overrides map var eovr []string for pr, pp := range s.ovr { - if pp.Constraint == nil && pp.NetworkName == "" { + if pp.Constraint == nil && pp.Source == "" { eovr = append(eovr, string(pr)) } } @@ -537,7 +537,7 @@ func (s *solver) selectRoot() error { // If we have no lock, or if this dep isn't in the lock, then prefetch // it. See longer explanation in selectAtom() for how we benefit from // parallelism here. - if _, has := s.rlm[dep.Ident.ProjectRoot]; !has { + if s.needVersionsFor(dep.Ident.ProjectRoot) { go s.b.SyncSourceFor(dep.Ident) } @@ -696,8 +696,8 @@ func (s *solver) createVersionQueue(bmi bimodalIdentifier) (*versionQueue, error return nil, err } if exists { - // Project exists only in vendor (and in some manifest somewhere) - // TODO(sdboyer) mark this for special handling, somehow? + // Project exists only in vendor + // FIXME(sdboyer) this just totally doesn't work at all right now } else { return nil, fmt.Errorf("project '%s' could not be located", id) } @@ -922,6 +922,29 @@ func (s *solver) getLockVersionIfValid(id ProjectIdentifier) (Version, error) { return v, nil } +// needVersionListFor indicates whether we need a version list for a given +// project root, based solely on general solver inputs (no constraint checking +// required). This will be true if: +// +// - ChangeAll is on +// - The project is not in the lock at all +// - The project is in the lock, but is also in the list of projects to change +func (s *solver) needVersionsFor(pr ProjectRoot) bool { + if s.params.ChangeAll { + return true + } + + if _, has := s.rlm[pr]; !has { + // not in the lock + return true + } else if _, has := s.chng[pr]; has { + // in the lock, but marked for change + return true + } + // in the lock, not marked for change + return false +} + // backtrack works backwards from the current failed solution to find the next // solution to try. func (s *solver) backtrack() bool { @@ -1144,13 +1167,13 @@ func (s *solver) selectAtom(a atomWithPackages, pkgonly bool) { // few microseconds before blocking later. Best case, the dep doesn't // come up next, but some other dep comes up that wasn't prefetched, and // both fetches proceed in parallel. - if _, has := s.rlm[dep.Ident.ProjectRoot]; !has { + if s.needVersionsFor(dep.Ident.ProjectRoot) { go s.b.SyncSourceFor(dep.Ident) } s.sel.pushDep(dependency{depender: a.a, dep: dep}) // Go through all the packages introduced on this dep, selecting only - // the ones where the only depper on them is what the previous line just + // the ones where the only depper on them is what the preceding line just // pushed in. Then, put those into the unselected queue. rpm := s.sel.getRequiredPackagesIn(dep.Ident) var newp []string diff --git a/vendor/github.com/sdboyer/gps/source.go b/vendor/github.com/sdboyer/gps/source.go index 01bb8c01..e127da34 100644 --- a/vendor/github.com/sdboyer/gps/source.go +++ b/vendor/github.com/sdboyer/gps/source.go @@ -97,23 +97,30 @@ func (bs *baseVCSSource) getManifestAndLock(r ProjectRoot, v Version) (Manifest, } // Cache didn't help; ensure our local is fully up to date. - err = bs.syncLocal() - if err != nil { - return nil, nil, err + do := func() (err error) { + bs.crepo.mut.Lock() + // Always prefer a rev, if it's available + if pv, ok := v.(PairedVersion); ok { + err = bs.crepo.r.UpdateVersion(pv.Underlying().String()) + } else { + err = bs.crepo.r.UpdateVersion(v.String()) + } + + bs.crepo.mut.Unlock() + return } - bs.crepo.mut.Lock() - // Always prefer a rev, if it's available - if pv, ok := v.(PairedVersion); ok { - err = bs.crepo.r.UpdateVersion(pv.Underlying().String()) - } else { - err = bs.crepo.r.UpdateVersion(v.String()) - } - bs.crepo.mut.Unlock() + if err = do(); err != nil { + // minimize network activity: only force local syncing if we had an err + err = bs.syncLocal() + if err != nil { + return nil, nil, err + } - if err != nil { - // TODO(sdboyer) More-er proper-er error - panic(fmt.Sprintf("canary - why is checkout/whatever failing: %s %s %s", bs.crepo.r.LocalPath(), v.String(), unwrapVcsErr(err))) + if err = do(); err != nil { + // TODO(sdboyer) More-er proper-er error + panic(fmt.Sprintf("canary - why is checkout/whatever failing: %s %s %s", bs.crepo.r.LocalPath(), v.String(), unwrapVcsErr(err))) + } } bs.crepo.mut.RLock() diff --git a/vendor/github.com/sdboyer/gps/source_manager.go b/vendor/github.com/sdboyer/gps/source_manager.go index d5fe2526..ce863076 100644 --- a/vendor/github.com/sdboyer/gps/source_manager.go +++ b/vendor/github.com/sdboyer/gps/source_manager.go @@ -305,7 +305,6 @@ func (sm *SourceMgr) DeduceProjectRoot(ip string) (ProjectRoot, error) { } func (sm *SourceMgr) getSourceFor(id ProjectIdentifier) (source, error) { - //pretty.Println(id.ProjectRoot) nn := id.netName() sm.srcmut.RLock() diff --git a/vendor/github.com/sdboyer/gps/types.go b/vendor/github.com/sdboyer/gps/types.go index 25e9138f..8b842cc1 100644 --- a/vendor/github.com/sdboyer/gps/types.go +++ b/vendor/github.com/sdboyer/gps/types.go @@ -44,10 +44,10 @@ type ProjectRoot string // ProjectRoot. In gps' current design, this ProjectRoot almost always // corresponds to the root of a repository. // -// Second, ProjectIdentifiers can optionally carry a NetworkName, which +// Second, ProjectIdentifiers can optionally carry a Source, which // identifies where the underlying source code can be located on the network. // These can be either a full URL, including protocol, or plain import paths. -// So, these are all valid data for NetworkName: +// So, these are all valid data for Source: // // github.com/sdboyer/gps // github.com/fork/gps @@ -61,19 +61,19 @@ type ProjectRoot string // // Note that gps makes no guarantees about the actual import paths contained in // a repository aligning with ImportRoot. If tools, or their users, specify an -// alternate NetworkName that contains a repository with incompatible internal +// alternate Source that contains a repository with incompatible internal // import paths, gps' solving operations will error. (gps does no import // rewriting.) // // Also note that if different projects' manifests report a different -// NetworkName for a given ImportRoot, it is a solve failure. Everyone has to +// Source for a given ImportRoot, it is a solve failure. Everyone has to // agree on where a given import path should be sourced from. // -// If NetworkName is not explicitly set, gps will derive the network address from +// If Source is not explicitly set, gps will derive the network address from // the ImportRoot using a similar algorithm to that utilized by `go get`. type ProjectIdentifier struct { ProjectRoot ProjectRoot - NetworkName string + Source string } func (i ProjectIdentifier) less(j ProjectIdentifier) bool { @@ -91,12 +91,12 @@ func (i ProjectIdentifier) eq(j ProjectIdentifier) bool { if i.ProjectRoot != j.ProjectRoot { return false } - if i.NetworkName == j.NetworkName { + if i.Source == j.Source { return true } - if (i.NetworkName == "" && j.NetworkName == string(j.ProjectRoot)) || - (j.NetworkName == "" && i.NetworkName == string(i.ProjectRoot)) { + if (i.Source == "" && j.Source == string(j.ProjectRoot)) || + (j.Source == "" && i.Source == string(i.ProjectRoot)) { return true } @@ -108,22 +108,22 @@ func (i ProjectIdentifier) eq(j ProjectIdentifier) bool { // // Given that the ProjectRoots are equal (==), equivalency occurs if: // -// 1. The NetworkNames are equal (==), OR -// 2. The LEFT (the receiver) NetworkName is non-empty, and the right -// NetworkName is empty. +// 1. The Sources are equal (==), OR +// 2. The LEFT (the receiver) Source is non-empty, and the right +// Source is empty. // // *This is asymmetry in this binary relation is intentional.* It facilitates -// the case where we allow for a ProjectIdentifier with an explicit NetworkName +// the case where we allow for a ProjectIdentifier with an explicit Source // to match one without. func (i ProjectIdentifier) equiv(j ProjectIdentifier) bool { if i.ProjectRoot != j.ProjectRoot { return false } - if i.NetworkName == j.NetworkName { + if i.Source == j.Source { return true } - if i.NetworkName != "" && j.NetworkName == "" { + if i.Source != "" && j.Source == "" { return true } @@ -131,22 +131,22 @@ func (i ProjectIdentifier) equiv(j ProjectIdentifier) bool { } func (i ProjectIdentifier) netName() string { - if i.NetworkName == "" { + if i.Source == "" { return string(i.ProjectRoot) } - return i.NetworkName + return i.Source } func (i ProjectIdentifier) errString() string { - if i.NetworkName == "" || i.NetworkName == string(i.ProjectRoot) { + if i.Source == "" || i.Source == string(i.ProjectRoot) { return string(i.ProjectRoot) } - return fmt.Sprintf("%s (from %s)", i.ProjectRoot, i.NetworkName) + return fmt.Sprintf("%s (from %s)", i.ProjectRoot, i.Source) } func (i ProjectIdentifier) normalize() ProjectIdentifier { - if i.NetworkName == "" { - i.NetworkName = string(i.ProjectRoot) + if i.Source == "" { + i.Source = string(i.ProjectRoot) } return i @@ -159,8 +159,8 @@ func (i ProjectIdentifier) normalize() ProjectIdentifier { // ProjectProperties; they make little sense without their corresponding // ProjectRoot. type ProjectProperties struct { - NetworkName string - Constraint Constraint + Source string + Constraint Constraint } // Package represents a Go package. It contains a subset of the information diff --git a/vendor/github.com/sdboyer/gps/util.go b/vendor/github.com/sdboyer/gps/util.go new file mode 100644 index 00000000..45d3dff7 --- /dev/null +++ b/vendor/github.com/sdboyer/gps/util.go @@ -0,0 +1,165 @@ +package gps + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "runtime" + "syscall" +) + +// renameWithFallback attempts to rename a file or directory, but falls back to +// copying in the event of a cross-link device error. If the fallback copy +// succeeds, src is still removed, emulating normal rename behavior. +func renameWithFallback(src, dest string) error { + fi, err := os.Stat(src) + if err != nil { + return err + } + + err = os.Rename(src, dest) + if err == nil { + return nil + } + + terr, ok := err.(*os.LinkError) + if !ok { + return err + } + + // Rename may fail if src and dest are on different devices; fall back to + // copy if we detect that case. syscall.EXDEV is the common name for the + // cross device link error which has varying output text across different + // operating systems. + var cerr error + if terr.Err == syscall.EXDEV { + if fi.IsDir() { + cerr = copyDir(src, dest) + } else { + cerr = copyFile(src, dest) + } + } else if runtime.GOOS == "windows" { + // In windows it can drop down to an operating system call that + // returns an operating system error with a different number and + // message. Checking for that as a fall back. + noerr, ok := terr.Err.(syscall.Errno) + // 0x11 (ERROR_NOT_SAME_DEVICE) is the windows error. + // See https://msdn.microsoft.com/en-us/library/cc231199.aspx + if ok && noerr == 0x11 { + if fi.IsDir() { + cerr = copyDir(src, dest) + } else { + cerr = copyFile(src, dest) + } + } + } else { + return terr + } + + if cerr != nil { + return cerr + } + + return os.RemoveAll(src) +} + +// copyDir recursively copies a directory tree, attempting to preserve permissions. +// Source directory must exist, destination directory must *not* exist. +// Symlinks are ignored and skipped. +func copyDir(src string, dst string) (err error) { + src = filepath.Clean(src) + dst = filepath.Clean(dst) + + si, err := os.Stat(src) + if err != nil { + return err + } + if !si.IsDir() { + return fmt.Errorf("source is not a directory") + } + + _, err = os.Stat(dst) + if err != nil && !os.IsNotExist(err) { + return + } + if err == nil { + return fmt.Errorf("destination already exists") + } + + err = os.MkdirAll(dst, si.Mode()) + if err != nil { + return + } + + entries, err := ioutil.ReadDir(src) + if err != nil { + return + } + + for _, entry := range entries { + srcPath := filepath.Join(src, entry.Name()) + dstPath := filepath.Join(dst, entry.Name()) + + if entry.IsDir() { + err = copyDir(srcPath, dstPath) + if err != nil { + return + } + } else { + // This will include symlinks, which is what we want in all cases + // where gps is copying things. + err = copyFile(srcPath, dstPath) + if err != nil { + return + } + } + } + + return +} + +// copyFile copies the contents of the file named src to the file named +// by dst. The file will be created if it does not already exist. If the +// destination file exists, all it's contents will be replaced by the contents +// of the source file. The file mode will be copied from the source and +// the copied data is synced/flushed to stable storage. +func copyFile(src, dst string) (err error) { + in, err := os.Open(src) + if err != nil { + return + } + defer in.Close() + + out, err := os.Create(dst) + if err != nil { + return + } + defer func() { + if e := out.Close(); e != nil { + err = e + } + }() + + _, err = io.Copy(out, in) + if err != nil { + return + } + + err = out.Sync() + if err != nil { + return + } + + si, err := os.Stat(src) + if err != nil { + return + } + err = os.Chmod(dst, si.Mode()) + if err != nil { + return + } + + return +} diff --git a/vendor/github.com/sdboyer/gps/util_test.go b/vendor/github.com/sdboyer/gps/util_test.go new file mode 100644 index 00000000..036edbf7 --- /dev/null +++ b/vendor/github.com/sdboyer/gps/util_test.go @@ -0,0 +1,131 @@ +package gps + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" +) + +func isDir(name string) (bool, error) { + fi, err := os.Stat(name) + if os.IsNotExist(err) { + return false, nil + } + if err != nil { + return false, err + } + if !fi.IsDir() { + return false, fmt.Errorf("%q is not a directory", name) + } + return true, nil +} + +func TestCopyDir(t *testing.T) { + dir, err := ioutil.TempDir("", "gps") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + srcdir := filepath.Join(dir, "src") + if err := os.MkdirAll(srcdir, 0755); err != nil { + t.Fatal(err) + } + + srcf, err := os.Create(filepath.Join(srcdir, "myfile")) + if err != nil { + t.Fatal(err) + } + + contents := "hello world" + if _, err := srcf.Write([]byte(contents)); err != nil { + t.Fatal(err) + } + srcf.Close() + + destdir := filepath.Join(dir, "dest") + if err := copyDir(srcdir, destdir); err != nil { + t.Fatal(err) + } + + dirOK, err := isDir(destdir) + if err != nil { + t.Fatal(err) + } + if !dirOK { + t.Fatalf("expected %s to be a directory", destdir) + } + + destf := filepath.Join(destdir, "myfile") + destcontents, err := ioutil.ReadFile(destf) + if err != nil { + t.Fatal(err) + } + + if contents != string(destcontents) { + t.Fatalf("expected: %s, got: %s", contents, string(destcontents)) + } + + srcinfo, err := os.Stat(srcf.Name()) + if err != nil { + t.Fatal(err) + } + + destinfo, err := os.Stat(destf) + if err != nil { + t.Fatal(err) + } + + if srcinfo.Mode() != destinfo.Mode() { + t.Fatalf("expected %s: %#v\n to be the same mode as %s: %#v", srcf.Name(), srcinfo.Mode(), destf, destinfo.Mode()) + } +} + +func TestCopyFile(t *testing.T) { + dir, err := ioutil.TempDir("", "gps") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + srcf, err := os.Create(filepath.Join(dir, "srcfile")) + if err != nil { + t.Fatal(err) + } + + contents := "hello world" + if _, err := srcf.Write([]byte(contents)); err != nil { + t.Fatal(err) + } + srcf.Close() + + destf := filepath.Join(dir, "destf") + if err := copyFile(srcf.Name(), destf); err != nil { + t.Fatal(err) + } + + destcontents, err := ioutil.ReadFile(destf) + if err != nil { + t.Fatal(err) + } + + if contents != string(destcontents) { + t.Fatalf("expected: %s, got: %s", contents, string(destcontents)) + } + + srcinfo, err := os.Stat(srcf.Name()) + if err != nil { + t.Fatal(err) + } + + destinfo, err := os.Stat(destf) + if err != nil { + t.Fatal(err) + } + + if srcinfo.Mode() != destinfo.Mode() { + t.Fatalf("expected %s: %#v\n to be the same mode as %s: %#v", srcf.Name(), srcinfo.Mode(), destf, destinfo.Mode()) + } +} diff --git a/vendor/github.com/sdboyer/gps/vcs_source.go b/vendor/github.com/sdboyer/gps/vcs_source.go index 526ad52e..cc78be17 100644 --- a/vendor/github.com/sdboyer/gps/vcs_source.go +++ b/vendor/github.com/sdboyer/gps/vcs_source.go @@ -11,7 +11,6 @@ import ( "github.com/Masterminds/semver" "github.com/Masterminds/vcs" - "github.com/termie/go-shutil" ) // Kept here as a reference in case it does become important to implement a @@ -47,13 +46,13 @@ func (s *gitSource) exportVersionTo(v Version, to string) error { // Back up original index idx, bak := filepath.Join(r.LocalPath(), ".git", "index"), filepath.Join(r.LocalPath(), ".git", "origindex") - err := os.Rename(idx, bak) + err := renameWithFallback(idx, bak) if err != nil { return err } // could have an err here...but it's hard to imagine how? - defer os.Rename(bak, idx) + defer renameWithFallback(bak, idx) vstr := v.String() if rv, ok := v.(PairedVersion); ok { @@ -635,30 +634,10 @@ func (r *repo) exportVersionTo(v Version, to string) error { r.r.UpdateVersion(v.String()) - // TODO(sdboyer) This is a dumb, slow approach, but we're punting on making - // these fast for now because git is the OVERWHELMING case (it's handled in - // its own method) - - cfg := &shutil.CopyTreeOptions{ - Symlinks: true, - CopyFunction: shutil.Copy, - Ignore: func(src string, contents []os.FileInfo) (ignore []string) { - for _, fi := range contents { - if !fi.IsDir() { - continue - } - n := fi.Name() - switch n { - case "vendor", ".bzr", ".svn", ".hg": - ignore = append(ignore, n) - } - } - - return - }, - } - - return shutil.CopyTree(r.rpath, to, cfg) + // TODO(sdboyer) this is a simplistic approach and relying on the tools + // themselves might make it faster, but git's the overwhelming case (and has + // its own method) so fine for now + return copyDir(r.rpath, to) } // This func copied from Masterminds/vcs so we can exec our own commands diff --git a/vendor/github.com/termie/go-shutil/.gitignore b/vendor/github.com/termie/go-shutil/.gitignore deleted file mode 100644 index 139b1ee7..00000000 --- a/vendor/github.com/termie/go-shutil/.gitignore +++ /dev/null @@ -1 +0,0 @@ -test/testfile3 diff --git a/vendor/github.com/termie/go-shutil/LICENSE b/vendor/github.com/termie/go-shutil/LICENSE deleted file mode 100644 index 3890b94b..00000000 --- a/vendor/github.com/termie/go-shutil/LICENSE +++ /dev/null @@ -1 +0,0 @@ -I guess Python's? If that doesn't apply then MIT. Have fun. diff --git a/vendor/github.com/termie/go-shutil/README.rst b/vendor/github.com/termie/go-shutil/README.rst deleted file mode 100644 index b63b016f..00000000 --- a/vendor/github.com/termie/go-shutil/README.rst +++ /dev/null @@ -1,24 +0,0 @@ -========================================= -High-level Filesystem Operations (for Go) -========================================= - - -A direct port of a few of the functions from Python's shutil package for -high-level filesystem operations. - -This project pretty much only exists so that other people don't have to keep -re-writing this code in their projects, at this time we have been unable to -find any helpful packages for this in the stdlib or elsewhere. - -We don't expect it to be perfect, just better than whatever your first draft -would have been. Patches welcome. - -See also: https://docs.python.org/3.5/library/shutil.html - -================ -Functions So Far -================ - -We support Copy, CopyFile, CopyMode, and CopyTree. CopyStat would be nice if -anybody wants to write that. Also the other functions that might be useful in -the python library :D diff --git a/vendor/github.com/termie/go-shutil/shutil.go b/vendor/github.com/termie/go-shutil/shutil.go deleted file mode 100644 index 09fcd38a..00000000 --- a/vendor/github.com/termie/go-shutil/shutil.go +++ /dev/null @@ -1,326 +0,0 @@ -package shutil - -import ( - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" -) - - -type SameFileError struct { - Src string - Dst string -} - -func (e SameFileError) Error() string { - return fmt.Sprintf("%s and %s are the same file", e.Src, e.Dst) -} - -type SpecialFileError struct { - File string - FileInfo os.FileInfo -} - -func (e SpecialFileError) Error() string { - return fmt.Sprintf("`%s` is a named pipe", e.File) -} - -type NotADirectoryError struct { - Src string -} - -func (e NotADirectoryError) Error() string { - return fmt.Sprintf("`%s` is not a directory", e.Src) -} - - -type AlreadyExistsError struct { - Dst string -} - -func (e AlreadyExistsError) Error() string { - return fmt.Sprintf("`%s` already exists", e.Dst) -} - - -func samefile(src string, dst string) bool { - srcInfo, _ := os.Stat(src) - dstInfo, _ := os.Stat(dst) - return os.SameFile(srcInfo, dstInfo) -} - -func specialfile(fi os.FileInfo) bool { - return (fi.Mode() & os.ModeNamedPipe) == os.ModeNamedPipe -} - -func stringInSlice(a string, list []string) bool { - for _, b := range list { - if b == a { - return true - } - } - return false -} - -func IsSymlink(fi os.FileInfo) bool { - return (fi.Mode() & os.ModeSymlink) == os.ModeSymlink -} - - -// Copy data from src to dst -// -// If followSymlinks is not set and src is a symbolic link, a -// new symlink will be created instead of copying the file it points -// to. -func CopyFile(src, dst string, followSymlinks bool) (error) { - if samefile(src, dst) { - return &SameFileError{src, dst} - } - - // Make sure src exists and neither are special files - srcStat, err := os.Lstat(src) - if err != nil { - return err - } - if specialfile(srcStat) { - return &SpecialFileError{src, srcStat} - } - - dstStat, err := os.Stat(dst) - if err != nil && !os.IsNotExist(err) { - return err - } else if err == nil { - if specialfile(dstStat) { - return &SpecialFileError{dst, dstStat} - } - } - - // If we don't follow symlinks and it's a symlink, just link it and be done - if !followSymlinks && IsSymlink(srcStat) { - return os.Symlink(src, dst) - } - - // If we are a symlink, follow it - if IsSymlink(srcStat) { - src, err = os.Readlink(src) - if err != nil { - return err - } - srcStat, err = os.Stat(src) - if err != nil { - return err - } - } - - // Do the actual copy - fsrc, err := os.Open(src) - if err != nil { - return err - } - defer fsrc.Close() - - fdst, err := os.Create(dst) - if err != nil { - return err - } - defer fdst.Close() - - size, err := io.Copy(fdst, fsrc) - if err != nil { - return err - } - - if size != srcStat.Size() { - return fmt.Errorf("%s: %d/%d copied", src, size, srcStat.Size()) - } - - return nil -} - - -// Copy mode bits from src to dst. -// -// If followSymlinks is false, symlinks aren't followed if and only -// if both `src` and `dst` are symlinks. If `lchmod` isn't available -// and both are symlinks this does nothing. (I don't think lchmod is -// available in Go) -func CopyMode(src, dst string, followSymlinks bool) error { - srcStat, err := os.Lstat(src) - if err != nil { - return err - } - - dstStat, err := os.Lstat(dst) - if err != nil { - return err - } - - // They are both symlinks and we can't change mode on symlinks. - if !followSymlinks && IsSymlink(srcStat) && IsSymlink(dstStat) { - return nil - } - - // Atleast one is not a symlink, get the actual file stats - srcStat, _ = os.Stat(src) - err = os.Chmod(dst, srcStat.Mode()) - return err -} - - -// Copy data and mode bits ("cp src dst"). Return the file's destination. -// -// The destination may be a directory. -// -// If followSymlinks is false, symlinks won't be followed. This -// resembles GNU's "cp -P src dst". -// -// If source and destination are the same file, a SameFileError will be -// rased. -func Copy(src, dst string, followSymlinks bool) (string, error){ - dstInfo, err := os.Stat(dst) - - if err == nil && dstInfo.Mode().IsDir() { - dst = filepath.Join(dst, filepath.Base(src)) - } - - if err != nil && !os.IsNotExist(err) { - return dst, err - } - - err = CopyFile(src, dst, followSymlinks) - if err != nil { - return dst, err - } - - err = CopyMode(src, dst, followSymlinks) - if err != nil { - return dst, err - } - - return dst, nil -} - -type CopyTreeOptions struct { - Symlinks bool - IgnoreDanglingSymlinks bool - CopyFunction func (string, string, bool) (string, error) - Ignore func (string, []os.FileInfo) []string -} - -// Recursively copy a directory tree. -// -// The destination directory must not already exist. -// -// If the optional Symlinks flag is true, symbolic links in the -// source tree result in symbolic links in the destination tree; if -// it is false, the contents of the files pointed to by symbolic -// links are copied. If the file pointed by the symlink doesn't -// exist, an error will be returned. -// -// You can set the optional IgnoreDanglingSymlinks flag to true if you -// want to silence this error. Notice that this has no effect on -// platforms that don't support os.Symlink. -// -// The optional ignore argument is a callable. If given, it -// is called with the `src` parameter, which is the directory -// being visited by CopyTree(), and `names` which is the list of -// `src` contents, as returned by ioutil.ReadDir(): -// -// callable(src, entries) -> ignoredNames -// -// Since CopyTree() is called recursively, the callable will be -// called once for each directory that is copied. It returns a -// list of names relative to the `src` directory that should -// not be copied. -// -// The optional copyFunction argument is a callable that will be used -// to copy each file. It will be called with the source path and the -// destination path as arguments. By default, Copy() is used, but any -// function that supports the same signature (like Copy2() when it -// exists) can be used. -func CopyTree(src, dst string, options *CopyTreeOptions) error { - if options == nil { - options = &CopyTreeOptions{Symlinks:false, - Ignore:nil, - CopyFunction:Copy, - IgnoreDanglingSymlinks:false} - } - - - srcFileInfo, err := os.Stat(src) - if err != nil { - return err - } - - if !srcFileInfo.IsDir() { - return &NotADirectoryError{src} - } - - _, err = os.Open(dst) - if !os.IsNotExist(err) { - return &AlreadyExistsError{dst} - } - - entries, err := ioutil.ReadDir(src) - if err != nil { - return err - } - - err = os.MkdirAll(dst, srcFileInfo.Mode()) - if err != nil { - return err - } - - ignoredNames := []string{} - if options.Ignore != nil { - ignoredNames = options.Ignore(src, entries) - } - - for _, entry := range entries { - if stringInSlice(entry.Name(), ignoredNames) { - continue - } - srcPath := filepath.Join(src, entry.Name()) - dstPath := filepath.Join(dst, entry.Name()) - - entryFileInfo, err := os.Lstat(srcPath) - if err != nil { - return err - } - - // Deal with symlinks - if IsSymlink(entryFileInfo) { - linkTo, err := os.Readlink(srcPath) - if err != nil { - return err - } - if options.Symlinks { - os.Symlink(linkTo, dstPath) - //CopyStat(srcPath, dstPath, false) - } else { - // ignore dangling symlink if flag is on - _, err = os.Stat(linkTo) - if os.IsNotExist(err) && options.IgnoreDanglingSymlinks { - continue - } - _, err = options.CopyFunction(srcPath, dstPath, false) - if err != nil { - return err - } - } - } else if entryFileInfo.IsDir() { - err = CopyTree(srcPath, dstPath, options) - if err != nil { - return err - } - } else { - _, err = options.CopyFunction(srcPath, dstPath, false) - if err != nil { - return err - } - } - } - return nil -} diff --git a/vendor/github.com/termie/go-shutil/shutil_test.go b/vendor/github.com/termie/go-shutil/shutil_test.go deleted file mode 100644 index f6ec2619..00000000 --- a/vendor/github.com/termie/go-shutil/shutil_test.go +++ /dev/null @@ -1,156 +0,0 @@ -package shutil - -import ( - "bytes" - "io/ioutil" - "os" - "testing" -) - - -func filesMatch(src, dst string) (bool, error) { - srcContents, err := ioutil.ReadFile(src) - if err != nil { - return false, err - } - - dstContents, err := ioutil.ReadFile(dst) - if err != nil { - return false, err - } - - if bytes.Compare(srcContents, dstContents) != 0 { - return false, nil - } - return true, nil -} - - -func TestSameFileError(t *testing.T) { - _, err := Copy("test/testfile", "test/testfile", false) - _, ok := err.(*SameFileError) - if !ok { - t.Error(err) - } -} - - -func TestCopyFile(t *testing.T) { - // clear out existing files if they exist - os.Remove("test/testfile3") - - err := CopyFile("test/testfile", "test/testfile3", false) - if err != nil { - t.Error(err) - return - } - - match, err := filesMatch("test/testfile", "test/testfile3") - if err != nil { - t.Error(err) - return - } - if !match { - t.Fail() - return - } - - // And again without clearing the files - err = CopyFile("test/testfile2", "test/testfile3", false) - if err != nil { - t.Error(err) - return - } - - match2, err := filesMatch("test/testfile2", "test/testfile3") - if err != nil { - t.Error(err) - return - } - - if !match2 { - t.Fail() - return - } -} - - -func TestCopy(t *testing.T) { - // clear out existing files if they exist - os.Remove("test/testfile3") - - _, err := Copy("test/testfile", "test/testfile3", false) - if err != nil { - t.Error(err) - return - } - - match, err := filesMatch("test/testfile", "test/testfile3") - if err != nil { - t.Error(err) - return - } - if !match { - t.Fail() - return - } - - // And again without clearing the files - _, err = Copy("test/testfile2", "test/testfile3", false) - if err != nil { - t.Error(err) - return - } - - match2, err := filesMatch("test/testfile2", "test/testfile3") - if err != nil { - t.Error(err) - return - } - - if !match2 { - t.Fail() - return - } -} - - -func TestCopyTree(t *testing.T) { - // clear out existing files if they exist - os.RemoveAll("test/testdir3") - - err := CopyTree("test/testdir", "test/testdir3", nil) - if err != nil { - t.Error(err) - return - } - - match, err := filesMatch("test/testdir/file1", "test/testdir3/file1") - if err != nil { - t.Error(err) - return - } - if !match { - t.Fail() - return - } - - // // And again without clearing the files - // _, err = Copy("test/testfile2", "test/testfile3", false) - // if err != nil { - // t.Error(err) - // return - // } - - // match2, err := filesMatch("test/testfile2", "test/testfile3") - // if err != nil { - // t.Error(err) - // return - // } - - // if !match2 { - // t.Fail() - // return - // } -} - diff --git a/vendor/github.com/termie/go-shutil/test/testdir/file1 b/vendor/github.com/termie/go-shutil/test/testdir/file1 deleted file mode 100644 index e2129701..00000000 --- a/vendor/github.com/termie/go-shutil/test/testdir/file1 +++ /dev/null @@ -1 +0,0 @@ -file1 diff --git a/vendor/github.com/termie/go-shutil/test/testdir/file2 b/vendor/github.com/termie/go-shutil/test/testdir/file2 deleted file mode 100644 index 6c493ff7..00000000 --- a/vendor/github.com/termie/go-shutil/test/testdir/file2 +++ /dev/null @@ -1 +0,0 @@ -file2 diff --git a/vendor/github.com/termie/go-shutil/test/testfile b/vendor/github.com/termie/go-shutil/test/testfile deleted file mode 100644 index 26918572..00000000 --- a/vendor/github.com/termie/go-shutil/test/testfile +++ /dev/null @@ -1 +0,0 @@ -testfile diff --git a/vendor/github.com/termie/go-shutil/test/testfile2 b/vendor/github.com/termie/go-shutil/test/testfile2 deleted file mode 100644 index 7d576476..00000000 --- a/vendor/github.com/termie/go-shutil/test/testfile2 +++ /dev/null @@ -1 +0,0 @@ -testfile2