MM-21722 - Repository synchronization tool (#86)
This commit is contained in:
parent
4e7a2d3734
commit
0688e8df4c
18 changed files with 1589 additions and 0 deletions
214
build/sync/plan/actions.go
Normal file
214
build/sync/plan/actions.go
Normal file
|
@ -0,0 +1,214 @@
|
|||
package plan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ActionConditions adds condition support to actions.
|
||||
type ActionConditions struct {
|
||||
// Conditions are checkers run before executing the
|
||||
// action. If any one fails (returns an error), the action
|
||||
// itself is not executed.
|
||||
Conditions []Check
|
||||
}
|
||||
|
||||
// Check runs the conditions associated with the action and returns
|
||||
// the first error (if any).
|
||||
func (c ActionConditions) Check(path string, setup Setup) error {
|
||||
if len(c.Conditions) > 0 {
|
||||
setup.Logf("checking action conditions")
|
||||
}
|
||||
for _, condition := range c.Conditions {
|
||||
err := condition.Check(path, setup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OverwriteFileAction is used to overwrite a file.
|
||||
type OverwriteFileAction struct {
|
||||
ActionConditions
|
||||
Params struct {
|
||||
// Create determines whether the target directory
|
||||
// will be created if it does not exist.
|
||||
Create bool `json:"create"`
|
||||
}
|
||||
}
|
||||
|
||||
// Run implements plan.Action.Run.
|
||||
func (a OverwriteFileAction) Run(path string, setup Setup) error {
|
||||
setup.Logf("overwriting file %q", path)
|
||||
src := setup.PathInRepo(SourceRepo, path)
|
||||
dst := setup.PathInRepo(TargetRepo, path)
|
||||
|
||||
dstInfo, err := os.Stat(dst)
|
||||
switch {
|
||||
case os.IsNotExist(err):
|
||||
if !a.Params.Create {
|
||||
return fmt.Errorf("path %q does not exist, not creating", dst)
|
||||
}
|
||||
case err != nil:
|
||||
return fmt.Errorf("failed to check path %q: %v", dst, err)
|
||||
case dstInfo.IsDir():
|
||||
return fmt.Errorf("path %q is a directory", dst)
|
||||
}
|
||||
|
||||
srcInfo, err := os.Stat(src)
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("file %q does not exist", src)
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("failed to check path %q: %v", src, err)
|
||||
}
|
||||
if srcInfo.IsDir() {
|
||||
return fmt.Errorf("path %q is a directory", src)
|
||||
}
|
||||
|
||||
srcF, err := os.Open(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open %q: %v", src, err)
|
||||
}
|
||||
defer srcF.Close()
|
||||
dstF, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, srcInfo.Mode())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open %q: %v", src, err)
|
||||
}
|
||||
defer dstF.Close()
|
||||
_, err = io.Copy(dstF, srcF)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to copy file %q: %v", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OverwriteDirectoryAction is used to completely overwrite directories.
|
||||
// If the target directory exists, it will be removed first.
|
||||
type OverwriteDirectoryAction struct {
|
||||
ActionConditions
|
||||
Params struct {
|
||||
// Create determines whether the target directory
|
||||
// will be created if it does not exist.
|
||||
Create bool `json:"create"`
|
||||
}
|
||||
}
|
||||
|
||||
// Run implements plan.Action.Run.
|
||||
func (a OverwriteDirectoryAction) Run(path string, setup Setup) error {
|
||||
setup.Logf("overwriting directory %q", path)
|
||||
src := setup.PathInRepo(SourceRepo, path)
|
||||
dst := setup.PathInRepo(TargetRepo, path)
|
||||
|
||||
dstInfo, err := os.Stat(dst)
|
||||
switch {
|
||||
case os.IsNotExist(err):
|
||||
if !a.Params.Create {
|
||||
return fmt.Errorf("path %q does not exist, not creating", dst)
|
||||
}
|
||||
case err != nil:
|
||||
return fmt.Errorf("failed to check path %q: %v", dst, err)
|
||||
default:
|
||||
if !dstInfo.IsDir() {
|
||||
return fmt.Errorf("path %q is not a directory", dst)
|
||||
}
|
||||
err = os.RemoveAll(dst)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove directory %q: %v", dst, err)
|
||||
}
|
||||
}
|
||||
|
||||
srcInfo, err := os.Stat(src)
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("directory %q does not exist", src)
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("failed to check path %q: %v", src, err)
|
||||
}
|
||||
if !srcInfo.IsDir() {
|
||||
return fmt.Errorf("path %q is not a directory", src)
|
||||
}
|
||||
|
||||
err = CopyDirectory(src, dst)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to copy path %q: %v", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyDirectory copies the directory src to dst so that after
|
||||
// a successful operation the contents of src and dst are equal.
|
||||
func CopyDirectory(src, dst string) error {
|
||||
copier := dirCopier{dst: dst, src: src}
|
||||
return filepath.Walk(src, copier.Copy)
|
||||
}
|
||||
|
||||
type dirCopier struct {
|
||||
dst string
|
||||
src string
|
||||
}
|
||||
|
||||
// Convert a path in the source directory to a path in the destination
|
||||
// directory.
|
||||
func (d dirCopier) srcToDst(path string) (string, error) {
|
||||
suff := strings.TrimPrefix(path, d.src)
|
||||
if suff == path {
|
||||
return "", fmt.Errorf("path %q is not in %q", path, d.src)
|
||||
}
|
||||
return filepath.Join(d.dst, suff), nil
|
||||
}
|
||||
|
||||
// Copy is an implementation of filepatch.WalkFunc that copies the
|
||||
// source directory to target with all subdirectories.
|
||||
func (d dirCopier) Copy(srcPath string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to copy directory: %v", err)
|
||||
}
|
||||
trgPath, err := d.srcToDst(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
err = os.MkdirAll(trgPath, info.Mode())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create directory %q: %v", trgPath, err)
|
||||
}
|
||||
err = os.Chtimes(trgPath, info.ModTime(), info.ModTime())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create directory %q: %v", trgPath, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
err = copyFile(srcPath, trgPath, info)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to copy file %q: %v", srcPath, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFile(src, dst string, info os.FileInfo) error {
|
||||
srcF, err := os.Open(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open source file %q: %v", src, err)
|
||||
}
|
||||
defer srcF.Close()
|
||||
dstF, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, info.Mode())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open destination file %q: %v", dst, err)
|
||||
}
|
||||
_, err = io.Copy(dstF, srcF)
|
||||
if err != nil {
|
||||
dstF.Close()
|
||||
return fmt.Errorf("failed to copy file %q: %v", src, err)
|
||||
}
|
||||
if err = dstF.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close file %q: %v", dst, err)
|
||||
}
|
||||
err = os.Chtimes(dst, info.ModTime(), info.ModTime())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to adjust file modification time for %q: %v", dst, err)
|
||||
}
|
||||
return nil
|
||||
}
|
Reference in a new issue