зеркало из https://github.com/golang/dep.git
Chase latest gps master
This commit is contained in:
Родитель
e955d434e4
Коммит
96ea714ac2
12
lock.json
12
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": [
|
||||
"."
|
||||
]
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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:
|
||||
//
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
test/testfile3
|
|
@ -1 +0,0 @@
|
|||
I guess Python's? If that doesn't apply then MIT. Have fun.
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
// }
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
file1
|
|
@ -1 +0,0 @@
|
|||
file2
|
|
@ -1 +0,0 @@
|
|||
testfile
|
|
@ -1 +0,0 @@
|
|||
testfile2
|
Загрузка…
Ссылка в новой задаче