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.
293 lines
8.0 KiB
293 lines
8.0 KiB
package controllers
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
ent "repodiff/entities"
|
|
"repodiff/interactors"
|
|
"repodiff/mappers"
|
|
"repodiff/persistence/filesystem"
|
|
"repodiff/repositories"
|
|
)
|
|
|
|
var expectedOutputFilenames = []string{
|
|
"project.csv",
|
|
"commit.csv",
|
|
}
|
|
|
|
// Executes all of the differentials specified in the application config.
|
|
// While each target is executed synchronously, the differential script is already multi-threaded
|
|
// across all of the local machine's cores, so there is no benefit to parallelizing multiple differential
|
|
// targets
|
|
func ExecuteDifferentials(config ent.ApplicationConfig) error {
|
|
err := createWorkingPath(config.OutputDirectory)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Could not create working path")
|
|
}
|
|
|
|
commonManifest, err := defineCommonManifest(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, target := range config.DiffTargets {
|
|
fmt.Printf("Processing differential from %s to %s\n", target.Upstream.Branch, target.Downstream.Branch)
|
|
err = clearOutputDirectory(config)
|
|
commitCSV, projectCSV, err := runPyScript(config, target)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Error running python differential script")
|
|
}
|
|
err = TransferScriptOutputToDownstream(config, target, projectCSV, commitCSV, commonManifest)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Error transferring script output to downstream")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func defineCommonManifest(config ent.ApplicationConfig) (*ent.ManifestFile, error) {
|
|
workingDirectory := filepath.Join(config.OutputDirectory, "common_upstream")
|
|
if err := createWorkingPath(workingDirectory); err != nil {
|
|
return nil, err
|
|
}
|
|
cmd := exec.Command(
|
|
"bash",
|
|
"-c",
|
|
fmt.Sprintf(
|
|
"repo init -u %s -b %s",
|
|
config.CommonUpstream.URL,
|
|
config.CommonUpstream.Branch,
|
|
),
|
|
)
|
|
cmd.Dir = workingDirectory
|
|
if _, err := cmd.Output(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var manifest ent.ManifestFile
|
|
err := filesystem.ReadXMLAsEntity(
|
|
// the output of repo init will generate a manifest file at this location
|
|
filepath.Join(workingDirectory, ".repo/manifest.xml"),
|
|
&manifest,
|
|
)
|
|
return &manifest, err
|
|
}
|
|
|
|
func createWorkingPath(folderPath string) error {
|
|
return os.MkdirAll(folderPath, os.ModePerm)
|
|
}
|
|
|
|
func printFunctionDuration(fnLabel string, start time.Time) {
|
|
fmt.Printf("Finished '%s' in %s\n", fnLabel, time.Now().Sub(start))
|
|
}
|
|
|
|
func clearOutputDirectory(config ent.ApplicationConfig) error {
|
|
return exec.Command(
|
|
"/bin/sh",
|
|
"-c",
|
|
fmt.Sprintf("rm -rf %s/*", config.OutputDirectory),
|
|
).Run()
|
|
}
|
|
|
|
func setupCommand(pyScript string, config ent.ApplicationConfig, target ent.DiffTarget) *exec.Cmd {
|
|
cmd := exec.Command(
|
|
"python",
|
|
pyScript,
|
|
"--manifest-url",
|
|
target.Downstream.URL,
|
|
"--manifest-branch",
|
|
target.Downstream.Branch,
|
|
"--upstream-manifest-url",
|
|
target.Upstream.URL,
|
|
"--upstream-manifest-branch",
|
|
target.Upstream.Branch,
|
|
)
|
|
cmd.Dir = config.OutputDirectory
|
|
return cmd
|
|
}
|
|
|
|
func runPyScript(config ent.ApplicationConfig, target ent.DiffTarget) (projectCSV string, commitCSV string, err error) {
|
|
pyScript := filepath.Join(
|
|
config.AndroidProjectDir,
|
|
config.DiffScript,
|
|
)
|
|
outFilesBefore := filesystem.FindFnamesInDir(config.OutputDirectory, expectedOutputFilenames...)
|
|
err = diffTarget(pyScript, config, target)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
outFilesAfter := filesystem.FindFnamesInDir(config.OutputDirectory, expectedOutputFilenames...)
|
|
newFiles := interactors.DistinctValues(outFilesBefore, outFilesAfter)
|
|
if len(newFiles) != 2 {
|
|
return "", "", errors.New("Expected 1 new output filent. A race condition exists")
|
|
}
|
|
return newFiles[0], newFiles[1], nil
|
|
}
|
|
|
|
func diffTarget(pyScript string, config ent.ApplicationConfig, target ent.DiffTarget) error {
|
|
defer printFunctionDuration("Run Differential", time.Now())
|
|
cmd := setupCommand(pyScript, config, target)
|
|
|
|
displayStr := strings.Join(cmd.Args, " ")
|
|
fmt.Printf("Executing command:\n\n%s\n\n", displayStr)
|
|
|
|
return errors.Wrap(
|
|
cmd.Run(),
|
|
fmt.Sprintf(
|
|
"Failed to execute (%s). Ensure glogin has been run or update application config to provide correct parameters",
|
|
displayStr,
|
|
),
|
|
)
|
|
}
|
|
|
|
// SBL need to add test coverage here
|
|
func TransferScriptOutputToDownstream(
|
|
config ent.ApplicationConfig,
|
|
target ent.DiffTarget,
|
|
projectCSVFile, commitCSVFile string,
|
|
common *ent.ManifestFile) error {
|
|
|
|
diffRows, commitRows, err := readCSVFiles(projectCSVFile, commitCSVFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
manifestFileGroup, err := loadTargetManifests(config, common)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
analyzedDiffRows, analyzedCommitRows := interactors.ApplyApplicationMutations(
|
|
interactors.AppProcessingParameters{
|
|
DiffRows: diffRows,
|
|
CommitRows: commitRows,
|
|
Manifests: manifestFileGroup,
|
|
},
|
|
)
|
|
return persistEntities(target, analyzedDiffRows, analyzedCommitRows)
|
|
}
|
|
|
|
func loadTargetManifests(config ent.ApplicationConfig, common *ent.ManifestFile) (*ent.ManifestFileGroup, error) {
|
|
var upstream, downstream ent.ManifestFile
|
|
dirToLoadAddress := map[string]*ent.ManifestFile{
|
|
"upstream": &upstream,
|
|
"downstream": &downstream,
|
|
}
|
|
|
|
for dir, addr := range dirToLoadAddress {
|
|
if err := filesystem.ReadXMLAsEntity(
|
|
filepath.Join(config.OutputDirectory, dir, ".repo/manifest.xml"),
|
|
addr,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return &ent.ManifestFileGroup{
|
|
Common: *common,
|
|
Upstream: upstream,
|
|
Downstream: downstream,
|
|
}, nil
|
|
}
|
|
|
|
func readCSVFiles(projectCSVFile, commitCSVFile string) ([]ent.DiffRow, []ent.CommitRow, error) {
|
|
diffRows, err := csvFileToDiffRows(projectCSVFile)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "Error converting CSV file to entities")
|
|
}
|
|
commitRows, err := CSVFileToCommitRows(commitCSVFile)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "Error converting CSV file to entities")
|
|
}
|
|
return diffRows, commitRows, nil
|
|
}
|
|
|
|
func persistEntities(target ent.DiffTarget, diffRows []ent.AnalyzedDiffRow, commitRows []ent.AnalyzedCommitRow) error {
|
|
sourceRepo, err := repositories.NewSourceRepository()
|
|
if err != nil {
|
|
return errors.Wrap(err, "Error initializing Source Repository")
|
|
}
|
|
mappedTarget, err := sourceRepo.DiffTargetToMapped(target)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Error mapping diff targets; a race condition is possible")
|
|
}
|
|
err = persistDiffRowsDownstream(mappedTarget, diffRows)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Error persisting diff rows")
|
|
}
|
|
|
|
return MaybeNullObjectCommitRepository(
|
|
mappedTarget,
|
|
).InsertCommitRows(
|
|
commitRows,
|
|
)
|
|
}
|
|
|
|
func csvFileToDiffRows(csvFile string) ([]ent.DiffRow, error) {
|
|
entities, err := filesystem.CSVFileToEntities(
|
|
csvFile,
|
|
func(cols []string) (interface{}, error) {
|
|
return mappers.CSVLineToDiffRow(cols)
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return toDiffRows(entities)
|
|
}
|
|
|
|
func toDiffRows(entities []interface{}) ([]ent.DiffRow, error) {
|
|
diffRows := make([]ent.DiffRow, len(entities))
|
|
for i, entity := range entities {
|
|
diffRow, ok := entity.(*ent.DiffRow)
|
|
if !ok {
|
|
return nil, errors.New("Error casting to DiffRow")
|
|
}
|
|
diffRows[i] = *diffRow
|
|
}
|
|
return diffRows, nil
|
|
}
|
|
|
|
func CSVFileToCommitRows(csvFile string) ([]ent.CommitRow, error) {
|
|
entities, err := filesystem.CSVFileToEntities(
|
|
csvFile,
|
|
func(cols []string) (interface{}, error) {
|
|
return mappers.CSVLineToCommitRow(cols)
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return toCommitRows(entities)
|
|
}
|
|
|
|
func toCommitRows(entities []interface{}) ([]ent.CommitRow, error) {
|
|
commitRows := make([]ent.CommitRow, len(entities))
|
|
for i, entity := range entities {
|
|
commitRow, ok := entity.(*ent.CommitRow)
|
|
if !ok {
|
|
return nil, errors.New("Error casting to CommitRow")
|
|
}
|
|
commitRows[i] = *commitRow
|
|
}
|
|
return commitRows, nil
|
|
}
|
|
|
|
func persistDiffRowsDownstream(mappedTarget ent.MappedDiffTarget, diffRows []ent.AnalyzedDiffRow) error {
|
|
p, err := repositories.NewProjectRepository(mappedTarget)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Error instantiating a new project repository")
|
|
}
|
|
err = p.InsertDiffRows(diffRows)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Error inserting rows from controller")
|
|
}
|
|
return nil
|
|
}
|