• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2020 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package workspace
16
17import (
18	"fmt"
19	"io"
20	"io/ioutil"
21	"os"
22	"path/filepath"
23	"strings"
24)
25
26type FileCopier struct {
27}
28
29func NewFileCopier() *FileCopier {
30	var f FileCopier
31	return &f
32}
33
34func (f FileCopier) GetIsGitProjectFunc(codebaseDir string, gitProjects []string) func(string) (bool, error) {
35	//Convert the git project list to a set to speed up lookups
36	gitProjectSet := make(map[string]struct{})
37	var exists = struct{}{}
38	for _, project := range gitProjects {
39		gitProjectSet[project] = exists
40	}
41
42	return func(pathToCheck string) (bool, error) {
43		var err error
44		if pathToCheck, err = filepath.Rel(codebaseDir, pathToCheck); err != nil {
45			return false, err
46		}
47		if _, ok := gitProjectSet[pathToCheck]; ok {
48			return true, err
49		}
50		return false, err
51	}
52}
53
54func (f FileCopier) GetContainsGitProjectFunc(codebaseDir string, gitProjects []string) func(string) (bool, error) {
55	//Extract the set of dirs that contain git projects
56	containsGitSet := make(map[string]struct{})
57	var exists = struct{}{}
58	for _, project := range gitProjects {
59		for dir := project; dir != "." && dir != "/"; dir = filepath.Dir(dir) {
60			containsGitSet[dir] = exists
61		}
62	}
63
64	return func(pathToCheck string) (bool, error) {
65		var err error
66		if pathToCheck, err = filepath.Rel(codebaseDir, pathToCheck); err != nil {
67			return false, err
68		}
69		if _, ok := containsGitSet[pathToCheck]; ok {
70			return true, err
71		}
72		return false, err
73	}
74}
75
76//gitProjects is relative to codebaseDir
77func (f FileCopier) Copy(codebaseDir string, gitProjects []string, workspaceDir string) error {
78	isGitProject := f.GetIsGitProjectFunc(codebaseDir, gitProjects)
79	containsGitProject := f.GetContainsGitProjectFunc(codebaseDir, gitProjects)
80
81	return filepath.Walk(codebaseDir,
82		func(path string, info os.FileInfo, err error) error {
83			if err != nil {
84				return err
85			}
86
87			// Copy files
88			if !info.IsDir() {
89				return f.CopyNode(info, codebaseDir, path, workspaceDir)
90			}
91
92			if path == filepath.Clean(codebaseDir) {
93				return nil
94			}
95
96			// Always skip traversal of root repo directories
97			if path == filepath.Join(codebaseDir, ".repo") {
98				return filepath.SkipDir
99			}
100
101			// Skip all git projects
102			var isGitProj bool
103			if isGitProj, err = isGitProject(path); err != nil {
104				return err
105			}
106			if isGitProj {
107				return filepath.SkipDir
108			}
109
110			// Copy over files
111			var containsGitProj bool
112			if containsGitProj, err = containsGitProject(path); err != nil {
113				return err
114			}
115			if !containsGitProj {
116				destPath, err := f.GetDestPath(codebaseDir, path, workspaceDir)
117				if err != nil {
118					return err
119				}
120				if err = f.CopyDirRecursive(info, path, destPath); err != nil {
121					return err
122				}
123				return filepath.SkipDir
124			}
125			return f.CopyNode(info, codebaseDir, path, workspaceDir)
126		})
127}
128
129func (f FileCopier) GetDestPath(codebaseDir, sourcePath, workspaceDir string) (string, error) {
130	if !strings.HasPrefix(sourcePath+"/", codebaseDir+"/") {
131		return "", fmt.Errorf("%s is not contained in %s", sourcePath, codebaseDir)
132	}
133	relPath, err := filepath.Rel(codebaseDir, sourcePath)
134	if err != nil {
135		return "", err
136	}
137	destPath := filepath.Join(workspaceDir, relPath)
138	return destPath, err
139}
140
141// Copy any single file, symlink or dir non-recursively
142// sourcePath must be contained in codebaseDir
143func (f FileCopier) CopyNode(sourceInfo os.FileInfo, codebaseDir, sourcePath, workspaceDir string) error {
144	destPath, err := f.GetDestPath(codebaseDir, sourcePath, workspaceDir)
145	if err != nil {
146		return err
147	}
148	switch {
149	case sourceInfo.Mode()&os.ModeSymlink == os.ModeSymlink:
150		return f.CopySymlink(sourcePath, destPath)
151	case sourceInfo.Mode().IsDir():
152		return f.CopyDirOnly(sourceInfo, destPath)
153	default:
154		return f.CopyFile(sourceInfo, sourcePath, destPath)
155	}
156}
157
158func (f FileCopier) CopySymlink(sourcePath string, destPath string) error {
159	// Skip symlink if it already exists at the destination
160	_, err := os.Lstat(destPath)
161	if err == nil {
162		return nil
163	}
164
165	target, err := os.Readlink(sourcePath)
166	if err != nil {
167		return err
168	}
169
170	return os.Symlink(target, destPath)
171}
172
173// CopyDirOnly copies a directory non-recursively
174// sourcePath must be contained in codebaseDir
175func (f FileCopier) CopyDirOnly(sourceInfo os.FileInfo, destPath string) error {
176	_, err := os.Stat(destPath)
177	if err == nil {
178		// Dir already exists, nothing to do
179		return err
180	} else if os.IsNotExist(err) {
181		return os.Mkdir(destPath, sourceInfo.Mode())
182	}
183	return err
184}
185
186// CopyFile copies a single file
187// sourcePath must be contained in codebaseDir
188func (f FileCopier) CopyFile(sourceInfo os.FileInfo, sourcePath, destPath string) error {
189	//Skip file if it already exists at the destination
190	_, err := os.Lstat(destPath)
191	if err == nil {
192		return nil
193	}
194
195	sourceFile, err := os.Open(sourcePath)
196	if err != nil {
197		return err
198	}
199	defer sourceFile.Close()
200
201	destFile, err := os.Create(destPath)
202	if err != nil {
203		return err
204	}
205	defer destFile.Close()
206
207	_, err = io.Copy(destFile, sourceFile)
208	if err != nil {
209		return err
210	}
211	return os.Chmod(destPath, sourceInfo.Mode())
212}
213
214func (f FileCopier) CopyDirRecursive(sourceInfo os.FileInfo, sourcePath, destPath string) error {
215	if err := f.CopyDirOnly(sourceInfo, destPath); err != nil {
216		return err
217	}
218	childNodes, err := ioutil.ReadDir(sourcePath)
219	if err != nil {
220		return err
221	}
222	for _, childInfo := range childNodes {
223		childSourcePath := filepath.Join(sourcePath, childInfo.Name())
224		childDestPath := filepath.Join(destPath, childInfo.Name())
225		switch {
226		case childInfo.Mode()&os.ModeSymlink == os.ModeSymlink:
227			if err = f.CopySymlink(childSourcePath, childDestPath); err != nil {
228				return err
229			}
230		case childInfo.Mode().IsDir():
231			if err = f.CopyDirRecursive(childInfo, childSourcePath, childDestPath); err != nil {
232				return err
233			}
234		default:
235			if err = f.CopyFile(childInfo, childSourcePath, childDestPath); err != nil {
236				return err
237			}
238		}
239	}
240	return err
241}
242