зеркало из 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": [
|
"projects": [
|
||||||
{
|
{
|
||||||
"name": "github.com/Masterminds/semver",
|
"name": "github.com/Masterminds/semver",
|
||||||
|
@ -35,16 +35,8 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "github.com/sdboyer/gps",
|
"name": "github.com/sdboyer/gps",
|
||||||
"version": "v0.13.1",
|
|
||||||
"revision": "e4435f58dfa1aee0c5324667f5b6cbad668db8fc",
|
|
||||||
"packages": [
|
|
||||||
"."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "github.com/termie/go-shutil",
|
|
||||||
"branch": "master",
|
"branch": "master",
|
||||||
"revision": "bcacb06fecaeec8dc42af03c87c6949f4a05c74c",
|
"revision": "b27173f3bf78a69a70dba35122ce24eb1679a53a",
|
||||||
"packages": [
|
"packages": [
|
||||||
"."
|
"."
|
||||||
]
|
]
|
||||||
|
|
|
@ -4,13 +4,13 @@
|
||||||
"branch": "2.x"
|
"branch": "2.x"
|
||||||
},
|
},
|
||||||
"github.com/Masterminds/vcs": {
|
"github.com/Masterminds/vcs": {
|
||||||
"version": ">=1.8.0, <2.0.0"
|
"version": "^1.8.0"
|
||||||
},
|
},
|
||||||
"github.com/pkg/errors": {
|
"github.com/pkg/errors": {
|
||||||
"version": ">=0.8.0, <1.0.0"
|
"version": ">=0.8.0, <1.0.0"
|
||||||
},
|
},
|
||||||
"github.com/sdboyer/gps": {
|
"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)
|
* 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)
|
* 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)
|
* 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)
|
* 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 ([not complete](https://github.com/sdboyer/gps/issues/42))
|
* Specifying additional input/source packages not reachable from the root import graph
|
||||||
|
|
||||||
This list may not be exhaustive - see the
|
This list may not be exhaustive - see the
|
||||||
[implementor's guide](https://github.com/sdboyer/gps/wiki/gps-for-Implementors)
|
[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
|
// operations attempt each member, and will take the most open/optimistic
|
||||||
// answer.
|
// answer.
|
||||||
//
|
//
|
||||||
// This technically does allow tags to match branches - something we
|
// This technically does allow tags to match branches - something we otherwise
|
||||||
// otherwise try hard to avoid - but because the original input constraint never
|
// try hard to avoid - but because the original input constraint never actually
|
||||||
// actually changes (and is never written out in the Result), there's no harmful
|
// changes (and is never written out in the Solution), there's no harmful case
|
||||||
// case of a user suddenly riding a branch when they expected a fixed tag.
|
// of a user suddenly riding a branch when they expected a fixed tag.
|
||||||
type versionTypeUnion []Version
|
type versionTypeUnion []Version
|
||||||
|
|
||||||
// This should generally not be called, but is required for the interface. If it
|
// 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 {
|
for _, pc := range l {
|
||||||
final[pc.Ident.ProjectRoot] = ProjectProperties{
|
final[pc.Ident.ProjectRoot] = ProjectProperties{
|
||||||
NetworkName: pc.Ident.NetworkName,
|
Source: pc.Ident.Source,
|
||||||
Constraint: pc.Constraint,
|
Constraint: pc.Constraint,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,8 +213,8 @@ func pcSliceToMap(l []ProjectConstraint, r ...[]ProjectConstraint) ProjectConstr
|
||||||
final[pc.Ident.ProjectRoot] = pp
|
final[pc.Ident.ProjectRoot] = pp
|
||||||
} else {
|
} else {
|
||||||
final[pc.Ident.ProjectRoot] = ProjectProperties{
|
final[pc.Ident.ProjectRoot] = ProjectProperties{
|
||||||
NetworkName: pc.Ident.NetworkName,
|
Source: pc.Ident.Source,
|
||||||
Constraint: pc.Constraint,
|
Constraint: pc.Constraint,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,7 +231,7 @@ func (m ProjectConstraints) asSortedSlice() []ProjectConstraint {
|
||||||
pcs[k] = ProjectConstraint{
|
pcs[k] = ProjectConstraint{
|
||||||
Ident: ProjectIdentifier{
|
Ident: ProjectIdentifier{
|
||||||
ProjectRoot: pr,
|
ProjectRoot: pr,
|
||||||
NetworkName: pp.NetworkName,
|
Source: pp.Source,
|
||||||
},
|
},
|
||||||
Constraint: pp.Constraint,
|
Constraint: pp.Constraint,
|
||||||
}
|
}
|
||||||
|
@ -262,8 +262,8 @@ func (m ProjectConstraints) merge(other ...ProjectConstraints) (out ProjectConst
|
||||||
for pr, pp := range pcm {
|
for pr, pp := range pcm {
|
||||||
if rpp, exists := out[pr]; exists {
|
if rpp, exists := out[pr]; exists {
|
||||||
pp.Constraint = pp.Constraint.Intersect(rpp.Constraint)
|
pp.Constraint = pp.Constraint.Intersect(rpp.Constraint)
|
||||||
if pp.NetworkName == "" {
|
if pp.Source == "" {
|
||||||
pp.NetworkName = rpp.NetworkName
|
pp.Source = rpp.Source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out[pr] = pp
|
out[pr] = pp
|
||||||
|
@ -297,7 +297,7 @@ func (m ProjectConstraints) override(pr ProjectRoot, pp ProjectProperties) worki
|
||||||
wc := workingConstraint{
|
wc := workingConstraint{
|
||||||
Ident: ProjectIdentifier{
|
Ident: ProjectIdentifier{
|
||||||
ProjectRoot: pr,
|
ProjectRoot: pr,
|
||||||
NetworkName: pp.NetworkName,
|
Source: pp.Source,
|
||||||
},
|
},
|
||||||
Constraint: pp.Constraint,
|
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
|
// from. Such disagreement is exactly what overrides preclude, so
|
||||||
// there's no need to preserve the meaning of "" here - thus, we can
|
// 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.
|
// treat it as a zero value and ignore it, rather than applying it.
|
||||||
if opp.NetworkName != "" {
|
if opp.Source != "" {
|
||||||
wc.Ident.NetworkName = opp.NetworkName
|
wc.Ident.Source = opp.Source
|
||||||
wc.overrNet = true
|
wc.overrNet = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ func (s *solver) HashInputs() []byte {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
for _, pd := range p {
|
for _, pd := range p {
|
||||||
buf.WriteString(string(pd.Ident.ProjectRoot))
|
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
|
// FIXME Constraint.String() is a surjective-only transformation - tags
|
||||||
// and branches with the same name are written out as the same string.
|
// and branches with the same name are written out as the same string.
|
||||||
// This could, albeit rarely, result in input collisions when a real
|
// 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.CommentPath)
|
||||||
buf.WriteString(perr.P.ImportPath)
|
buf.WriteString(perr.P.ImportPath)
|
||||||
for _, imp := range perr.P.Imports {
|
for _, imp := range perr.P.Imports {
|
||||||
buf.WriteString(imp)
|
if !isStdLib(imp) {
|
||||||
|
buf.WriteString(imp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, imp := range perr.P.TestImports {
|
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() {
|
for _, pc := range s.ovr.asSortedSlice() {
|
||||||
buf.WriteString(string(pc.Ident.ProjectRoot))
|
buf.WriteString(string(pc.Ident.ProjectRoot))
|
||||||
if pc.Ident.NetworkName != "" {
|
if pc.Ident.Source != "" {
|
||||||
buf.WriteString(pc.Ident.NetworkName)
|
buf.WriteString(pc.Ident.Source)
|
||||||
}
|
}
|
||||||
if pc.Constraint != nil {
|
if pc.Constraint != nil {
|
||||||
buf.WriteString(pc.Constraint.String())
|
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
|
// First case - override something not in the root, just with network name
|
||||||
rm.ovr = map[ProjectRoot]ProjectProperties{
|
rm.ovr = map[ProjectRoot]ProjectProperties{
|
||||||
"c": ProjectProperties{
|
"c": ProjectProperties{
|
||||||
NetworkName: "car",
|
Source: "car",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
params := SolveParameters{
|
params := SolveParameters{
|
||||||
|
@ -259,8 +259,8 @@ func TestHashInputsOverrides(t *testing.T) {
|
||||||
|
|
||||||
// Override not in root, both constraint and network name
|
// Override not in root, both constraint and network name
|
||||||
rm.ovr["e"] = ProjectProperties{
|
rm.ovr["e"] = ProjectProperties{
|
||||||
NetworkName: "groucho",
|
Source: "groucho",
|
||||||
Constraint: NewBranch("plexiglass"),
|
Constraint: NewBranch("plexiglass"),
|
||||||
}
|
}
|
||||||
dig = s.HashInputs()
|
dig = s.HashInputs()
|
||||||
h = sha256.New()
|
h = sha256.New()
|
||||||
|
@ -334,7 +334,7 @@ func TestHashInputsOverrides(t *testing.T) {
|
||||||
|
|
||||||
// Override in root, only network name
|
// Override in root, only network name
|
||||||
rm.ovr["a"] = ProjectProperties{
|
rm.ovr["a"] = ProjectProperties{
|
||||||
NetworkName: "nota",
|
Source: "nota",
|
||||||
}
|
}
|
||||||
dig = s.HashInputs()
|
dig = s.HashInputs()
|
||||||
h = sha256.New()
|
h = sha256.New()
|
||||||
|
@ -373,8 +373,8 @@ func TestHashInputsOverrides(t *testing.T) {
|
||||||
|
|
||||||
// Override in root, network name and constraint
|
// Override in root, network name and constraint
|
||||||
rm.ovr["a"] = ProjectProperties{
|
rm.ovr["a"] = ProjectProperties{
|
||||||
NetworkName: "nota",
|
Source: "nota",
|
||||||
Constraint: NewVersion("fluglehorn"),
|
Constraint: NewVersion("fluglehorn"),
|
||||||
}
|
}
|
||||||
dig = s.HashInputs()
|
dig = s.HashInputs()
|
||||||
h = sha256.New()
|
h = sha256.New()
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package gps
|
package gps
|
||||||
|
|
||||||
import "sort"
|
import (
|
||||||
|
"bytes"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
// Lock represents data from a lock file (or however the implementing tool
|
// 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
|
// chooses to store it) at a particular version that is relevant to the
|
||||||
|
@ -20,6 +23,43 @@ type Lock interface {
|
||||||
Projects() []LockedProject
|
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
|
// 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
|
// 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
|
// 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)
|
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
|
// Packages returns the list of packages from within the LockedProject that are
|
||||||
// actually used in the import graph. Some caveats:
|
// 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)
|
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
|
// 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)
|
src3, err := sm.getSourceFor(lpi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("(src %q) unexpected error getting explicit https source: %s", nn, err)
|
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
|
// 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)
|
src4, err := sm.getSourceFor(lpi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("(src %q) unexpected error getting explicit http source: %s", nn, err)
|
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
|
// normalize between these two by omitting such instances entirely, as
|
||||||
// it negates some possibility for false mismatches in input hashing.
|
// it negates some possibility for false mismatches in input hashing.
|
||||||
if d.Constraint == nil {
|
if d.Constraint == nil {
|
||||||
if d.NetworkName == "" {
|
if d.Source == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
d.Constraint = anyConstraint{}
|
d.Constraint = anyConstraint{}
|
||||||
|
@ -169,7 +169,7 @@ func prepManifest(m Manifest) Manifest {
|
||||||
|
|
||||||
for k, d := range ddeps {
|
for k, d := range ddeps {
|
||||||
if d.Constraint == nil {
|
if d.Constraint == nil {
|
||||||
if d.NetworkName == "" {
|
if d.Source == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
d.Constraint = anyConstraint{}
|
d.Constraint = anyConstraint{}
|
||||||
|
|
|
@ -8,13 +8,13 @@ func TestPrepManifest(t *testing.T) {
|
||||||
Deps: ProjectConstraints{
|
Deps: ProjectConstraints{
|
||||||
ProjectRoot("foo"): ProjectProperties{},
|
ProjectRoot("foo"): ProjectProperties{},
|
||||||
ProjectRoot("bar"): ProjectProperties{
|
ProjectRoot("bar"): ProjectProperties{
|
||||||
NetworkName: "whatever",
|
Source: "whatever",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TestDeps: ProjectConstraints{
|
TestDeps: ProjectConstraints{
|
||||||
ProjectRoot("baz"): ProjectProperties{},
|
ProjectRoot("baz"): ProjectProperties{},
|
||||||
ProjectRoot("qux"): 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
|
src.baseVCSSource.lvfunc = src.listVersions
|
||||||
|
if !r.CheckLocal() {
|
||||||
_, err = src.listVersions()
|
_, err = src.listVersions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return src, ustr, nil
|
return src, ustr, nil
|
||||||
|
@ -129,10 +130,11 @@ func (m maybeGopkginSource) try(cachedir string, an ProjectAnalyzer) (source, st
|
||||||
}
|
}
|
||||||
|
|
||||||
src.baseVCSSource.lvfunc = src.listVersions
|
src.baseVCSSource.lvfunc = src.listVersions
|
||||||
|
if !r.CheckLocal() {
|
||||||
_, err = src.listVersions()
|
_, err = src.listVersions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return src, ustr, nil
|
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,
|
// 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
|
// has the same Source as what's already been selected (assuming anything's been
|
||||||
// been selected).
|
// selected).
|
||||||
//
|
//
|
||||||
// In other words, this ensures that the solver never simultaneously selects two
|
// In other words, this ensures that the solver never simultaneously selects two
|
||||||
// identifiers with the same local name, but that disagree about where their
|
// 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 ") {
|
if strings.Contains(info, " from ") {
|
||||||
parts := regfrom.FindStringSubmatch(info)
|
parts := regfrom.FindStringSubmatch(info)
|
||||||
info = parts[1] + " " + parts[3]
|
info = parts[1] + " " + parts[3]
|
||||||
id.NetworkName = parts[2]
|
id.Source = parts[2]
|
||||||
}
|
}
|
||||||
|
|
||||||
s := strings.SplitN(info, " ", 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 ") {
|
if strings.Contains(info, " from ") {
|
||||||
parts := regfrom.FindStringSubmatch(info)
|
parts := regfrom.FindStringSubmatch(info)
|
||||||
info = fmt.Sprintf("%s %s", parts[1], parts[3])
|
info = fmt.Sprintf("%s %s", parts[1], parts[3])
|
||||||
id.NetworkName = parts[2]
|
id.Source = parts[2]
|
||||||
}
|
}
|
||||||
|
|
||||||
s := strings.SplitN(info, " ", 3)
|
s := strings.SplitN(info, " ", 3)
|
||||||
|
@ -205,7 +205,7 @@ type depspec struct {
|
||||||
// treated as a test-only dependency.
|
// treated as a test-only dependency.
|
||||||
func mkDepspec(pi string, deps ...string) depspec {
|
func mkDepspec(pi string, deps ...string) depspec {
|
||||||
pa := mkAtom(pi)
|
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")
|
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
|
// 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.
|
// equal to the ProjectRoot.
|
||||||
func mkPI(root string) ProjectIdentifier {
|
func mkPI(root string) ProjectIdentifier {
|
||||||
return ProjectIdentifier{
|
return ProjectIdentifier{
|
||||||
|
@ -1274,7 +1274,7 @@ var basicFixtures = map[string]basicFixture{
|
||||||
},
|
},
|
||||||
ovr: ProjectConstraints{
|
ovr: ProjectConstraints{
|
||||||
ProjectRoot("bar"): ProjectProperties{
|
ProjectRoot("bar"): ProjectProperties{
|
||||||
NetworkName: "bar",
|
Source: "bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
r: mksolution(
|
r: mksolution(
|
||||||
|
|
|
@ -640,7 +640,7 @@ var bimodalFixtures = map[string]bimodalFixture{
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
// When a given project is initially brought in using the default (i.e.,
|
// 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
|
// 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
|
// have to fail - even though, if the deps were visited in the opposite
|
||||||
// order (deeper dep w/the alternate location first, default location
|
// order (deeper dep w/the alternate location first, default location
|
||||||
|
@ -719,7 +719,7 @@ var bimodalFixtures = map[string]bimodalFixture{
|
||||||
},
|
},
|
||||||
ovr: ProjectConstraints{
|
ovr: ProjectConstraints{
|
||||||
ProjectRoot("bar"): ProjectProperties{
|
ProjectRoot("bar"): ProjectProperties{
|
||||||
NetworkName: "baz",
|
Source: "baz",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
r: mksolution(
|
r: mksolution(
|
||||||
|
@ -740,7 +740,7 @@ var bimodalFixtures = map[string]bimodalFixture{
|
||||||
},
|
},
|
||||||
ovr: ProjectConstraints{
|
ovr: ProjectConstraints{
|
||||||
ProjectRoot("bar"): ProjectProperties{
|
ProjectRoot("bar"): ProjectProperties{
|
||||||
NetworkName: "baz",
|
Source: "baz",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
r: mksolution(
|
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) {
|
func fixtureSolveSimpleChecks(fix specfix, soln Solution, err error, t *testing.T) (Solution, error) {
|
||||||
ppi := func(id ProjectIdentifier) string {
|
ppi := func(id ProjectIdentifier) string {
|
||||||
// need this so we can clearly tell if there's a NetworkName or not
|
// need this so we can clearly tell if there's a Source or not
|
||||||
if id.NetworkName == "" {
|
if id.Source == "" {
|
||||||
return string(id.ProjectRoot)
|
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 {
|
pv := func(v Version) string {
|
||||||
|
|
|
@ -252,7 +252,7 @@ func Prepare(params SolveParameters, sm SourceManager) (Solver, error) {
|
||||||
// Validate no empties in the overrides map
|
// Validate no empties in the overrides map
|
||||||
var eovr []string
|
var eovr []string
|
||||||
for pr, pp := range s.ovr {
|
for pr, pp := range s.ovr {
|
||||||
if pp.Constraint == nil && pp.NetworkName == "" {
|
if pp.Constraint == nil && pp.Source == "" {
|
||||||
eovr = append(eovr, string(pr))
|
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
|
// 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
|
// it. See longer explanation in selectAtom() for how we benefit from
|
||||||
// parallelism here.
|
// parallelism here.
|
||||||
if _, has := s.rlm[dep.Ident.ProjectRoot]; !has {
|
if s.needVersionsFor(dep.Ident.ProjectRoot) {
|
||||||
go s.b.SyncSourceFor(dep.Ident)
|
go s.b.SyncSourceFor(dep.Ident)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -696,8 +696,8 @@ func (s *solver) createVersionQueue(bmi bimodalIdentifier) (*versionQueue, error
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if exists {
|
if exists {
|
||||||
// Project exists only in vendor (and in some manifest somewhere)
|
// Project exists only in vendor
|
||||||
// TODO(sdboyer) mark this for special handling, somehow?
|
// FIXME(sdboyer) this just totally doesn't work at all right now
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("project '%s' could not be located", id)
|
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
|
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
|
// backtrack works backwards from the current failed solution to find the next
|
||||||
// solution to try.
|
// solution to try.
|
||||||
func (s *solver) backtrack() bool {
|
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
|
// 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
|
// come up next, but some other dep comes up that wasn't prefetched, and
|
||||||
// both fetches proceed in parallel.
|
// 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)
|
go s.b.SyncSourceFor(dep.Ident)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.sel.pushDep(dependency{depender: a.a, dep: dep})
|
s.sel.pushDep(dependency{depender: a.a, dep: dep})
|
||||||
// Go through all the packages introduced on this dep, selecting only
|
// 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.
|
// pushed in. Then, put those into the unselected queue.
|
||||||
rpm := s.sel.getRequiredPackagesIn(dep.Ident)
|
rpm := s.sel.getRequiredPackagesIn(dep.Ident)
|
||||||
var newp []string
|
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.
|
// Cache didn't help; ensure our local is fully up to date.
|
||||||
err = bs.syncLocal()
|
do := func() (err error) {
|
||||||
if err != nil {
|
bs.crepo.mut.Lock()
|
||||||
return nil, nil, err
|
// 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()
|
if err = do(); err != nil {
|
||||||
// Always prefer a rev, if it's available
|
// minimize network activity: only force local syncing if we had an err
|
||||||
if pv, ok := v.(PairedVersion); ok {
|
err = bs.syncLocal()
|
||||||
err = bs.crepo.r.UpdateVersion(pv.Underlying().String())
|
if err != nil {
|
||||||
} else {
|
return nil, nil, err
|
||||||
err = bs.crepo.r.UpdateVersion(v.String())
|
}
|
||||||
}
|
|
||||||
bs.crepo.mut.Unlock()
|
|
||||||
|
|
||||||
if err != nil {
|
if err = do(); err != nil {
|
||||||
// TODO(sdboyer) More-er proper-er error
|
// 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)))
|
panic(fmt.Sprintf("canary - why is checkout/whatever failing: %s %s %s", bs.crepo.r.LocalPath(), v.String(), unwrapVcsErr(err)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bs.crepo.mut.RLock()
|
bs.crepo.mut.RLock()
|
||||||
|
|
|
@ -305,7 +305,6 @@ func (sm *SourceMgr) DeduceProjectRoot(ip string) (ProjectRoot, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sm *SourceMgr) getSourceFor(id ProjectIdentifier) (source, error) {
|
func (sm *SourceMgr) getSourceFor(id ProjectIdentifier) (source, error) {
|
||||||
//pretty.Println(id.ProjectRoot)
|
|
||||||
nn := id.netName()
|
nn := id.netName()
|
||||||
|
|
||||||
sm.srcmut.RLock()
|
sm.srcmut.RLock()
|
||||||
|
|
|
@ -44,10 +44,10 @@ type ProjectRoot string
|
||||||
// ProjectRoot. In gps' current design, this ProjectRoot almost always
|
// ProjectRoot. In gps' current design, this ProjectRoot almost always
|
||||||
// corresponds to the root of a repository.
|
// 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.
|
// 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.
|
// 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/sdboyer/gps
|
||||||
// github.com/fork/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
|
// 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
|
// 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
|
// import paths, gps' solving operations will error. (gps does no import
|
||||||
// rewriting.)
|
// rewriting.)
|
||||||
//
|
//
|
||||||
// Also note that if different projects' manifests report a different
|
// 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.
|
// 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`.
|
// the ImportRoot using a similar algorithm to that utilized by `go get`.
|
||||||
type ProjectIdentifier struct {
|
type ProjectIdentifier struct {
|
||||||
ProjectRoot ProjectRoot
|
ProjectRoot ProjectRoot
|
||||||
NetworkName string
|
Source string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i ProjectIdentifier) less(j ProjectIdentifier) bool {
|
func (i ProjectIdentifier) less(j ProjectIdentifier) bool {
|
||||||
|
@ -91,12 +91,12 @@ func (i ProjectIdentifier) eq(j ProjectIdentifier) bool {
|
||||||
if i.ProjectRoot != j.ProjectRoot {
|
if i.ProjectRoot != j.ProjectRoot {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if i.NetworkName == j.NetworkName {
|
if i.Source == j.Source {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i.NetworkName == "" && j.NetworkName == string(j.ProjectRoot)) ||
|
if (i.Source == "" && j.Source == string(j.ProjectRoot)) ||
|
||||||
(j.NetworkName == "" && i.NetworkName == string(i.ProjectRoot)) {
|
(j.Source == "" && i.Source == string(i.ProjectRoot)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,22 +108,22 @@ func (i ProjectIdentifier) eq(j ProjectIdentifier) bool {
|
||||||
//
|
//
|
||||||
// Given that the ProjectRoots are equal (==), equivalency occurs if:
|
// Given that the ProjectRoots are equal (==), equivalency occurs if:
|
||||||
//
|
//
|
||||||
// 1. The NetworkNames are equal (==), OR
|
// 1. The Sources are equal (==), OR
|
||||||
// 2. The LEFT (the receiver) NetworkName is non-empty, and the right
|
// 2. The LEFT (the receiver) Source is non-empty, and the right
|
||||||
// NetworkName is empty.
|
// Source is empty.
|
||||||
//
|
//
|
||||||
// *This is asymmetry in this binary relation is intentional.* It facilitates
|
// *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.
|
// to match one without.
|
||||||
func (i ProjectIdentifier) equiv(j ProjectIdentifier) bool {
|
func (i ProjectIdentifier) equiv(j ProjectIdentifier) bool {
|
||||||
if i.ProjectRoot != j.ProjectRoot {
|
if i.ProjectRoot != j.ProjectRoot {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if i.NetworkName == j.NetworkName {
|
if i.Source == j.Source {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if i.NetworkName != "" && j.NetworkName == "" {
|
if i.Source != "" && j.Source == "" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,22 +131,22 @@ func (i ProjectIdentifier) equiv(j ProjectIdentifier) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i ProjectIdentifier) netName() string {
|
func (i ProjectIdentifier) netName() string {
|
||||||
if i.NetworkName == "" {
|
if i.Source == "" {
|
||||||
return string(i.ProjectRoot)
|
return string(i.ProjectRoot)
|
||||||
}
|
}
|
||||||
return i.NetworkName
|
return i.Source
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i ProjectIdentifier) errString() string {
|
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 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 {
|
func (i ProjectIdentifier) normalize() ProjectIdentifier {
|
||||||
if i.NetworkName == "" {
|
if i.Source == "" {
|
||||||
i.NetworkName = string(i.ProjectRoot)
|
i.Source = string(i.ProjectRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
return i
|
return i
|
||||||
|
@ -159,8 +159,8 @@ func (i ProjectIdentifier) normalize() ProjectIdentifier {
|
||||||
// ProjectProperties; they make little sense without their corresponding
|
// ProjectProperties; they make little sense without their corresponding
|
||||||
// ProjectRoot.
|
// ProjectRoot.
|
||||||
type ProjectProperties struct {
|
type ProjectProperties struct {
|
||||||
NetworkName string
|
Source string
|
||||||
Constraint Constraint
|
Constraint Constraint
|
||||||
}
|
}
|
||||||
|
|
||||||
// Package represents a Go package. It contains a subset of the information
|
// 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/semver"
|
||||||
"github.com/Masterminds/vcs"
|
"github.com/Masterminds/vcs"
|
||||||
"github.com/termie/go-shutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Kept here as a reference in case it does become important to implement a
|
// 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
|
// Back up original index
|
||||||
idx, bak := filepath.Join(r.LocalPath(), ".git", "index"), filepath.Join(r.LocalPath(), ".git", "origindex")
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// could have an err here...but it's hard to imagine how?
|
// could have an err here...but it's hard to imagine how?
|
||||||
defer os.Rename(bak, idx)
|
defer renameWithFallback(bak, idx)
|
||||||
|
|
||||||
vstr := v.String()
|
vstr := v.String()
|
||||||
if rv, ok := v.(PairedVersion); ok {
|
if rv, ok := v.(PairedVersion); ok {
|
||||||
|
@ -635,30 +634,10 @@ func (r *repo) exportVersionTo(v Version, to string) error {
|
||||||
|
|
||||||
r.r.UpdateVersion(v.String())
|
r.r.UpdateVersion(v.String())
|
||||||
|
|
||||||
// TODO(sdboyer) This is a dumb, slow approach, but we're punting on making
|
// TODO(sdboyer) this is a simplistic approach and relying on the tools
|
||||||
// these fast for now because git is the OVERWHELMING case (it's handled in
|
// themselves might make it faster, but git's the overwhelming case (and has
|
||||||
// its own method)
|
// its own method) so fine for now
|
||||||
|
return copyDir(r.rpath, to)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This func copied from Masterminds/vcs so we can exec our own commands
|
// 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
|
|
Загрузка…
Ссылка в новой задаче