You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
242 lines
6.3 KiB
242 lines
6.3 KiB
// Copyright 2020 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package workspace
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
type FileCopier struct {
|
|
}
|
|
|
|
func NewFileCopier() *FileCopier {
|
|
var f FileCopier
|
|
return &f
|
|
}
|
|
|
|
func (f FileCopier) GetIsGitProjectFunc(codebaseDir string, gitProjects []string) func(string) (bool, error) {
|
|
//Convert the git project list to a set to speed up lookups
|
|
gitProjectSet := make(map[string]struct{})
|
|
var exists = struct{}{}
|
|
for _, project := range gitProjects {
|
|
gitProjectSet[project] = exists
|
|
}
|
|
|
|
return func(pathToCheck string) (bool, error) {
|
|
var err error
|
|
if pathToCheck, err = filepath.Rel(codebaseDir, pathToCheck); err != nil {
|
|
return false, err
|
|
}
|
|
if _, ok := gitProjectSet[pathToCheck]; ok {
|
|
return true, err
|
|
}
|
|
return false, err
|
|
}
|
|
}
|
|
|
|
func (f FileCopier) GetContainsGitProjectFunc(codebaseDir string, gitProjects []string) func(string) (bool, error) {
|
|
//Extract the set of dirs that contain git projects
|
|
containsGitSet := make(map[string]struct{})
|
|
var exists = struct{}{}
|
|
for _, project := range gitProjects {
|
|
for dir := project; dir != "." && dir != "/"; dir = filepath.Dir(dir) {
|
|
containsGitSet[dir] = exists
|
|
}
|
|
}
|
|
|
|
return func(pathToCheck string) (bool, error) {
|
|
var err error
|
|
if pathToCheck, err = filepath.Rel(codebaseDir, pathToCheck); err != nil {
|
|
return false, err
|
|
}
|
|
if _, ok := containsGitSet[pathToCheck]; ok {
|
|
return true, err
|
|
}
|
|
return false, err
|
|
}
|
|
}
|
|
|
|
//gitProjects is relative to codebaseDir
|
|
func (f FileCopier) Copy(codebaseDir string, gitProjects []string, workspaceDir string) error {
|
|
isGitProject := f.GetIsGitProjectFunc(codebaseDir, gitProjects)
|
|
containsGitProject := f.GetContainsGitProjectFunc(codebaseDir, gitProjects)
|
|
|
|
return filepath.Walk(codebaseDir,
|
|
func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Copy files
|
|
if !info.IsDir() {
|
|
return f.CopyNode(info, codebaseDir, path, workspaceDir)
|
|
}
|
|
|
|
if path == filepath.Clean(codebaseDir) {
|
|
return nil
|
|
}
|
|
|
|
// Always skip traversal of root repo directories
|
|
if path == filepath.Join(codebaseDir, ".repo") {
|
|
return filepath.SkipDir
|
|
}
|
|
|
|
// Skip all git projects
|
|
var isGitProj bool
|
|
if isGitProj, err = isGitProject(path); err != nil {
|
|
return err
|
|
}
|
|
if isGitProj {
|
|
return filepath.SkipDir
|
|
}
|
|
|
|
// Copy over files
|
|
var containsGitProj bool
|
|
if containsGitProj, err = containsGitProject(path); err != nil {
|
|
return err
|
|
}
|
|
if !containsGitProj {
|
|
destPath, err := f.GetDestPath(codebaseDir, path, workspaceDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = f.CopyDirRecursive(info, path, destPath); err != nil {
|
|
return err
|
|
}
|
|
return filepath.SkipDir
|
|
}
|
|
return f.CopyNode(info, codebaseDir, path, workspaceDir)
|
|
})
|
|
}
|
|
|
|
func (f FileCopier) GetDestPath(codebaseDir, sourcePath, workspaceDir string) (string, error) {
|
|
if !strings.HasPrefix(sourcePath+"/", codebaseDir+"/") {
|
|
return "", fmt.Errorf("%s is not contained in %s", sourcePath, codebaseDir)
|
|
}
|
|
relPath, err := filepath.Rel(codebaseDir, sourcePath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
destPath := filepath.Join(workspaceDir, relPath)
|
|
return destPath, err
|
|
}
|
|
|
|
// Copy any single file, symlink or dir non-recursively
|
|
// sourcePath must be contained in codebaseDir
|
|
func (f FileCopier) CopyNode(sourceInfo os.FileInfo, codebaseDir, sourcePath, workspaceDir string) error {
|
|
destPath, err := f.GetDestPath(codebaseDir, sourcePath, workspaceDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch {
|
|
case sourceInfo.Mode()&os.ModeSymlink == os.ModeSymlink:
|
|
return f.CopySymlink(sourcePath, destPath)
|
|
case sourceInfo.Mode().IsDir():
|
|
return f.CopyDirOnly(sourceInfo, destPath)
|
|
default:
|
|
return f.CopyFile(sourceInfo, sourcePath, destPath)
|
|
}
|
|
}
|
|
|
|
func (f FileCopier) CopySymlink(sourcePath string, destPath string) error {
|
|
// Skip symlink if it already exists at the destination
|
|
_, err := os.Lstat(destPath)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
target, err := os.Readlink(sourcePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.Symlink(target, destPath)
|
|
}
|
|
|
|
// CopyDirOnly copies a directory non-recursively
|
|
// sourcePath must be contained in codebaseDir
|
|
func (f FileCopier) CopyDirOnly(sourceInfo os.FileInfo, destPath string) error {
|
|
_, err := os.Stat(destPath)
|
|
if err == nil {
|
|
// Dir already exists, nothing to do
|
|
return err
|
|
} else if os.IsNotExist(err) {
|
|
return os.Mkdir(destPath, sourceInfo.Mode())
|
|
}
|
|
return err
|
|
}
|
|
|
|
// CopyFile copies a single file
|
|
// sourcePath must be contained in codebaseDir
|
|
func (f FileCopier) CopyFile(sourceInfo os.FileInfo, sourcePath, destPath string) error {
|
|
//Skip file if it already exists at the destination
|
|
_, err := os.Lstat(destPath)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
sourceFile, err := os.Open(sourcePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer sourceFile.Close()
|
|
|
|
destFile, err := os.Create(destPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer destFile.Close()
|
|
|
|
_, err = io.Copy(destFile, sourceFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.Chmod(destPath, sourceInfo.Mode())
|
|
}
|
|
|
|
func (f FileCopier) CopyDirRecursive(sourceInfo os.FileInfo, sourcePath, destPath string) error {
|
|
if err := f.CopyDirOnly(sourceInfo, destPath); err != nil {
|
|
return err
|
|
}
|
|
childNodes, err := ioutil.ReadDir(sourcePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, childInfo := range childNodes {
|
|
childSourcePath := filepath.Join(sourcePath, childInfo.Name())
|
|
childDestPath := filepath.Join(destPath, childInfo.Name())
|
|
switch {
|
|
case childInfo.Mode()&os.ModeSymlink == os.ModeSymlink:
|
|
if err = f.CopySymlink(childSourcePath, childDestPath); err != nil {
|
|
return err
|
|
}
|
|
case childInfo.Mode().IsDir():
|
|
if err = f.CopyDirRecursive(childInfo, childSourcePath, childDestPath); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
if err = f.CopyFile(childInfo, childSourcePath, childDestPath); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return err
|
|
}
|