1// Copyright 2015 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package context
6
7import (
8	"fmt"
9	"hash"
10	"io"
11	"os"
12	"path"
13	"path/filepath"
14	"sort"
15	"strings"
16
17	"github.com/kardianos/govendor/internal/pathos"
18	"github.com/pkg/errors"
19)
20
21type fileInfoSort []os.FileInfo
22
23func (l fileInfoSort) Len() int {
24	return len(l)
25}
26func (l fileInfoSort) Less(i, j int) bool {
27	a := l[i]
28	b := l[j]
29	if a.IsDir() == b.IsDir() {
30		return l[i].Name() < l[j].Name()
31	}
32	return !a.IsDir()
33}
34func (l fileInfoSort) Swap(i, j int) {
35	l[i], l[j] = l[j], l[i]
36}
37
38// CopyPackage copies the files from the srcPath to the destPath, destPath
39// folder and parents are are created if they don't already exist.
40func (ctx *Context) CopyPackage(destPath, srcPath, lookRoot, pkgPath string, ignoreFiles []string, tree bool, h hash.Hash, beforeCopy func(deps []string) error) error {
41	if pathos.FileStringEquals(destPath, srcPath) {
42		return fmt.Errorf("Attempting to copy package to same location %q.", destPath)
43	}
44	err := os.MkdirAll(destPath, 0777)
45	if err != nil {
46		return err
47	}
48
49	// Ensure the dest is empty of files.
50	destDir, err := os.Open(destPath)
51	if err != nil {
52		return err
53	}
54	ignoreTest := false
55	for _, ignore := range ctx.ignoreTag {
56		if ignore == "test" {
57			ignoreTest = true
58			break
59		}
60	}
61
62	fl, err := destDir.Readdir(-1)
63	destDir.Close()
64	if err != nil {
65		return err
66	}
67	for _, fi := range fl {
68		if fi.IsDir() {
69			if tree {
70				err = errors.Wrap(os.RemoveAll(filepath.Join(destPath, fi.Name())), "remove all existing tree entries")
71				if err != nil {
72					return err
73				}
74			}
75			continue
76		}
77		err = errors.Wrap(os.Remove(filepath.Join(destPath, fi.Name())), "remove existing file")
78		if err != nil {
79			return err
80		}
81	}
82
83	// Copy files into dest.
84	srcDir, err := os.Open(srcPath)
85	if err != nil {
86		return errors.Wrap(err, "open srcPath directory")
87	}
88
89	fl, err = srcDir.Readdir(-1)
90	srcDir.Close()
91	if err != nil {
92		return errors.Wrap(err, "src readdir")
93	}
94	if h != nil {
95		// Write relative path to GOPATH.
96		h.Write([]byte(strings.Trim(pkgPath, "/")))
97		// Sort file list to present a stable hash.
98		sort.Sort(fileInfoSort(fl))
99	}
100fileLoop:
101	for _, fi := range fl {
102		name := fi.Name()
103		if name[0] == '.' {
104			continue
105		}
106		if fi.IsDir() {
107			isTestdata := name == "testdata"
108			if !tree && !isTestdata {
109				continue
110			}
111			if name[0] == '_' {
112				continue
113			}
114			if ignoreTest {
115				if strings.HasSuffix(name, "_test") || isTestdata {
116					continue
117				}
118			}
119			nextDestPath := filepath.Join(destPath, name)
120			nextSrcPath := filepath.Join(srcPath, name)
121			var nextIgnoreFiles, deps []string
122			if !isTestdata && !strings.Contains(pkgPath, "/testdata/") {
123				nextIgnoreFiles, deps, err = ctx.getIgnoreFiles(nextSrcPath)
124				if err != nil {
125					return err
126				}
127			}
128			if beforeCopy != nil {
129				err = beforeCopy(deps)
130				if err != nil {
131					return errors.Wrap(err, "beforeCopy")
132				}
133			}
134			err = ctx.CopyPackage(nextDestPath, nextSrcPath, lookRoot, path.Join(pkgPath, name), nextIgnoreFiles, true, h, beforeCopy)
135			if err != nil {
136				return errors.Wrapf(err,
137					"CopyPackage dest=%q src=%q lookRoot=%q pkgPath=%q ignoreFiles=%q tree=%t has beforeCopy=%t",
138					nextDestPath, nextSrcPath, lookRoot, path.Join(pkgPath, name), nextIgnoreFiles, true, beforeCopy != nil,
139				)
140			}
141			continue
142		}
143		for _, ignore := range ignoreFiles {
144			if pathos.FileStringEquals(name, ignore) {
145				continue fileLoop
146			}
147		}
148		if h != nil {
149			h.Write([]byte(name))
150		}
151		err = copyFile(
152			filepath.Join(destPath, name),
153			filepath.Join(srcPath, name),
154			h,
155		)
156		if err != nil {
157			return errors.Wrapf(err, "copyFile dest=%q src=%q", filepath.Join(destPath, name), filepath.Join(srcPath, name))
158		}
159	}
160
161	return errors.Wrapf(licenseCopy(lookRoot, srcPath, filepath.Join(ctx.RootDir, ctx.VendorFolder), pkgPath), "licenseCopy srcPath=%q", srcPath)
162}
163
164func copyFile(destPath, srcPath string, h hash.Hash) error {
165	ss, err := os.Stat(srcPath)
166	if err != nil {
167		return errors.Wrap(err, "copyFile Stat")
168	}
169	src, err := os.Open(srcPath)
170	if err != nil {
171		return errors.Wrapf(err, "open src=%q", srcPath)
172	}
173	defer src.Close()
174	// Ensure we are not trying to copy a directory. May happen with symlinks.
175	if st, err := src.Stat(); err == nil {
176		if st.IsDir() {
177			return nil
178		}
179	}
180
181	dest, err := os.Create(destPath)
182	if err != nil {
183		return errors.Wrapf(err, "create dest=%q", destPath)
184	}
185
186	r := io.Reader(src)
187
188	if h != nil {
189		r = io.TeeReader(src, h)
190	}
191
192	_, err = io.Copy(dest, r)
193	// Close before setting mod and time.
194	dest.Close()
195	if err != nil {
196		return errors.Wrap(err, "copy")
197	}
198	err = os.Chmod(destPath, ss.Mode())
199	if err != nil {
200		return err
201	}
202	return os.Chtimes(destPath, ss.ModTime(), ss.ModTime())
203}
204