MM-21722 - Repository synchronization tool (#86)
This commit is contained in:
parent
4e7a2d3734
commit
0688e8df4c
18 changed files with 1589 additions and 0 deletions
147
build/sync/plan/checks.go
Normal file
147
build/sync/plan/checks.go
Normal file
|
@ -0,0 +1,147 @@
|
|||
package plan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/mattermost/mattermost-plugin-starter-template/build/sync/plan/git"
|
||||
)
|
||||
|
||||
// CheckFail is a custom error type used to indicate a
|
||||
// check that did not pass (but did not fail due to external
|
||||
// causes.
|
||||
// Use `IsCheckFail` to check if an error is a check failure.
|
||||
type CheckFail string
|
||||
|
||||
func (e CheckFail) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// CheckFailf creates an error with the specified message string.
|
||||
// The error will pass the IsCheckFail filter.
|
||||
func CheckFailf(msg string, args ...interface{}) CheckFail {
|
||||
if len(args) > 0 {
|
||||
msg = fmt.Sprintf(msg, args...)
|
||||
}
|
||||
return CheckFail(msg)
|
||||
}
|
||||
|
||||
// IsCheckFail determines if an error is a check fail error.
|
||||
func IsCheckFail(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := err.(CheckFail)
|
||||
return ok
|
||||
}
|
||||
|
||||
// RepoIsCleanChecker checks whether the git repository is clean.
|
||||
type RepoIsCleanChecker struct {
|
||||
Params struct {
|
||||
Repo RepoID
|
||||
}
|
||||
}
|
||||
|
||||
// Check implements the Checker interface.
|
||||
// The path parameter is ignored because this checker checks the state of a repository.
|
||||
func (r RepoIsCleanChecker) Check(_ string, ctx Setup) error {
|
||||
ctx.Logf("checking if repository %q is clean", r.Params.Repo)
|
||||
rc := ctx.GetRepo(r.Params.Repo)
|
||||
repo := rc.Git
|
||||
worktree, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get worktree: %v", err)
|
||||
}
|
||||
status, err := worktree.Status()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get worktree status: %v", err)
|
||||
}
|
||||
if !status.IsClean() {
|
||||
return CheckFailf("%q repository is not clean", r.Params.Repo)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PathExistsChecker checks whether the fle or directory with the
|
||||
// path exists. If it does not, an error is returned.
|
||||
type PathExistsChecker struct {
|
||||
Params struct {
|
||||
Repo RepoID
|
||||
}
|
||||
}
|
||||
|
||||
// Check implements the Checker interface.
|
||||
func (r PathExistsChecker) Check(path string, ctx Setup) error {
|
||||
repo := r.Params.Repo
|
||||
if repo == "" {
|
||||
repo = TargetRepo
|
||||
}
|
||||
ctx.Logf("checking if path %q exists in repo %q", path, repo)
|
||||
absPath := ctx.PathInRepo(repo, path)
|
||||
_, err := os.Stat(absPath)
|
||||
if os.IsNotExist(err) {
|
||||
return CheckFailf("path %q does not exist", path)
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("failed to stat path %q: %v", absPath, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FileUnalteredChecker checks whether the file in Repo is
|
||||
// an unaltered version of that same file in ReferenceRepo.
|
||||
//
|
||||
// Its purpose is to check that a file has not been changed after forking a repository.
|
||||
// It could be an old unaltered version, so the git history of the file is traversed
|
||||
// until a matching version is found.
|
||||
//
|
||||
// If the repositories in the parameters are not specified,
|
||||
// reference will default to the source repository and repo - to the target.
|
||||
type FileUnalteredChecker struct {
|
||||
Params struct {
|
||||
ReferenceRepo RepoID `json:"compared-to"`
|
||||
Repo RepoID `json:"in"`
|
||||
}
|
||||
}
|
||||
|
||||
// Check implements the Checker interface.
|
||||
func (f FileUnalteredChecker) Check(path string, setup Setup) error {
|
||||
setup.Logf("checking if file %q has not been altered", path)
|
||||
repo := f.Params.Repo
|
||||
if repo == "" {
|
||||
repo = TargetRepo
|
||||
}
|
||||
reference := f.Params.ReferenceRepo
|
||||
if reference == "" {
|
||||
reference = SourceRepo
|
||||
}
|
||||
absPath := setup.PathInRepo(repo, path)
|
||||
|
||||
info, err := os.Stat(absPath)
|
||||
if os.IsNotExist(err) {
|
||||
return CheckFailf("file %q has been deleted", absPath)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get stat for %q: %v", absPath, err)
|
||||
}
|
||||
if info.IsDir() {
|
||||
return fmt.Errorf("%q is a directory", absPath)
|
||||
}
|
||||
|
||||
fileHashes, err := git.FileHistory(path, setup.GetRepo(reference).Git)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentHash, err := git.GetFileHash(absPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sort.Strings(fileHashes)
|
||||
idx := sort.SearchStrings(fileHashes, currentHash)
|
||||
if idx < len(fileHashes) && fileHashes[idx] == currentHash {
|
||||
return nil
|
||||
}
|
||||
return CheckFailf("file %q has been altered", absPath)
|
||||
}
|
Reference in a new issue