Skip to content

Commit

Permalink
Merge pull request #2 from utilitywarehouse/add-comments
Browse files Browse the repository at this point in the history
Add comments and remove exported methods
  • Loading branch information
rentziass authored Oct 21, 2021
2 parents e1daba9 + d846f08 commit acb5260
Showing 1 changed file with 97 additions and 48 deletions.
145 changes: 97 additions & 48 deletions patrol/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,23 @@ type Repo struct {
Packages map[string]*Package
}

type Package struct {
Name string
PartOfModule bool
Dependants []*Package
Changed bool
}

// NewRepo constructs a Repo from path, which needs to contain a go.mod file.
// It builds a map of all packages found in that repo and the dependencies
// between them.
func NewRepo(path string) (*Repo, error) {
repo := &Repo{
path: path,
Packages: map[string]*Package{},
}

// Parse go.mod
b, err := os.ReadFile(filepath.Join(path, "go.mod"))
if err != nil {
return nil, err
Expand All @@ -40,9 +51,12 @@ func NewRepo(path string) (*Repo, error) {

repo.Module = mod

// Find all go packages starting from path
err = filepath.Walk(path, func(p string, f os.FileInfo, err error) error {
if f.IsDir() && !directoryShouldBeIgnored(p) {
fset := token.NewFileSet()

// We're interested in each package imports at this point
pkgs, err := parser.ParseDir(fset, p, nil, parser.ImportsOnly)
if err != nil {
return err
Expand All @@ -55,13 +69,14 @@ func NewRepo(path string) (*Repo, error) {

var imports []string
for _, file := range pkg.Files {
// Don't map test packages
if !strings.HasSuffix(file.Name.Name, "_test") {
for _, imp := range file.Imports {
imports = append(imports, strings.ReplaceAll(imp.Path.Value, `"`, ""))
}
}
}
repo.AddPackage(strings.TrimPrefix(p, path+"/"), imports)
repo.addPackage(strings.TrimPrefix(p, path+"/"), imports)
}
}
return nil
Expand All @@ -73,18 +88,51 @@ func NewRepo(path string) (*Repo, error) {
return repo, nil
}

func (r *Repo) AddPackage(path string, imports []string) {
// ChangesFrom returns a list of all packages within the repository (excluding
// packages in vendor/) that changed since the given revision. A package will
// be flagged as change if any file within the package itself changed or if any
// packages it imports (whether local, vendored or external modules) changed
// since the given revision.
func (r *Repo) ChangesFrom(revision string) ([]string, error) {
err := r.detectInternalChangesFrom(revision)
if err != nil {
return nil, err
}

err = r.detectGoModulesChanges(revision)
if err != nil {
return nil, err
}

var changedOwnedPackages []string
for _, pkg := range r.Packages {
if pkg.PartOfModule && pkg.Changed {
changedOwnedPackages = append(changedOwnedPackages, pkg.Name)
}
}

return changedOwnedPackages, nil
}

// addPackage adds the package found at path to the repo, and also adds it as a
// dependant to all of the packages it imports.
func (r *Repo) addPackage(path string, imports []string) {
var pkgName string

// if path has vendor/ prefix, that needs to be removed to get the actual
// package name
if strings.HasPrefix(path, "vendor/") {
pkgName = strings.TrimPrefix(path, "vendor/")
} else {
// if it doesn't have a vendor/ prefix it means it's part of our module and
// path should be prefixed with the module name.
pkgName = r.ModuleName()
if path != r.path {
pkgName += "/" + path
}
}

// add the new package to the repo if it didn't exist already
pkg, exists := r.Packages[pkgName]
if !exists {
pkg = &Package{
Expand All @@ -94,17 +142,21 @@ func (r *Repo) AddPackage(path string, imports []string) {
r.Packages[pkgName] = pkg
}

// imports might not be a unique list, but we only want to add pkg as a
// dependant to those packages once
alreadyProcessedImports := map[string]interface{}{}
for _, dependency := range imports {
if _, alreadyProcessed := alreadyProcessedImports[dependency]; alreadyProcessed {
continue
}
r.AddDependant(pkg, dependency)
r.addDependant(pkg, dependency)
alreadyProcessedImports[dependency] = struct{}{}
}
}

func (r *Repo) AddDependant(dependant *Package, dependencyName string) {
// addDependant adds dependant as one of the dependants of the package
// identified by dependencyName (if it doesn't exist yet, it will be created).
func (r *Repo) addDependant(dependant *Package, dependencyName string) {
dependency, exists := r.Packages[dependencyName]
if !exists {
dependency = &Package{
Expand All @@ -117,29 +169,10 @@ func (r *Repo) AddDependant(dependant *Package, dependencyName string) {
dependency.Dependants = append(dependency.Dependants, dependant)
}

func (r *Repo) ChangesFrom(revision string) ([]string, error) {
err := r.detectInternalChangesFrom(revision)
if err != nil {
return nil, err
}

err = r.detectGoModulesChanges(revision)
if err != nil {
return nil, err
}

var changedOwnedPackages []string
for _, pkg := range r.Packages {
if pkg.PartOfModule && pkg.Changed {
changedOwnedPackages = append(changedOwnedPackages, pkg.Name)
}
}

return changedOwnedPackages, nil
}

// detectInternalChangesFrom will run a git diff (revision...HEAD) and flag as
// changed any packages (part of the module in repo or vendored packages) that
// have *.go files that are part of the that diff and packages that depend on them
func (r *Repo) detectInternalChangesFrom(revision string) error {
// git diff go files
repo, err := git.PlainOpen(r.path)
if err != nil {
return err
Expand All @@ -150,11 +183,13 @@ func (r *Repo) detectInternalChangesFrom(revision string) error {
return err
}

// Get the HEAD commit
now, err := repo.CommitObject(head.Hash())
if err != nil {
return err
}

// Get the tree for HEAD
nowTree, err := now.Tree()
if err != nil {
return err
Expand All @@ -165,31 +200,38 @@ func (r *Repo) detectInternalChangesFrom(revision string) error {
return err
}

// Find the commit for given revision
then, err := repo.CommitObject(*ref)
if err != nil {
return err
}

// Get the tree for given revision
thenTree, err := then.Tree()
if err != nil {
return err
}

// Get a diff between the two trees
diff, err := nowTree.Diff(thenTree)
if err != nil {
return err
}

for _, change := range diff {
// we're only interested in Go files
if !strings.HasSuffix(change.From.Name, ".go") {
continue
}

var pkgName string
// if the changed file is in vendor/ stripping "vendor/" will give us the
// package name
if strings.HasPrefix(change.From.Name, "vendor/") {
pkgName = strings.TrimPrefix(filepath.Dir(change.From.Name), "vendor/")
}

// package is part of our module
if pkgName == "" {
pkgName = r.ModuleName() + "/" + filepath.Dir(change.From.Name)
}
Expand All @@ -200,53 +242,67 @@ func (r *Repo) detectInternalChangesFrom(revision string) error {
return nil
}

// detectGoModulesChanges finds differences in dependencies required by
// HEAD:go.mod and {revision}:go.mod and flags as changed any packages
// depending on any of the changed dependencies.
func (r *Repo) detectGoModulesChanges(revision string) error {
// get old go.mod
// find differences with current one
repo, err := git.PlainOpen(r.path)
oldGoMod, err := r.getGoModFromRevision(revision)
if err != nil {
return err
}

differentModules := goModDifferences(oldGoMod, r.Module)
for _, module := range differentModules {
r.flagPackageAsChanged(module)
}

return nil
}

// getGoModFromRevision returns (if found) the go.mod file from the given
// revision.
func (r *Repo) getGoModFromRevision(revision string) (*modfile.File, error) {
repo, err := git.PlainOpen(r.path)
if err != nil {
return nil, err
}

ref, err := repo.ResolveRevision(plumbing.Revision(revision))
if err != nil {
return err
return nil, err
}

then, err := repo.CommitObject(*ref)
if err != nil {
return err
return nil, err
}

file, err := then.File("go.mod")
if err != nil {
return err
return nil, err
}

reader, err := file.Reader()
if err != nil {
return err
return nil, err
}
defer reader.Close()

b, err := ioutil.ReadAll(reader)
if err != nil {
return err
return nil, err
}

mod, err := modfile.Parse(filepath.Join(r.path, "go.mod"), b, nil)
if err != nil {
return err
}

differentModules := goModDifferences(mod, r.Module)
for _, module := range differentModules {
r.flagPackageAsChanged(module)
return nil, err
}

return nil
return mod, nil
}

// flagPackageAsChanged flags the package with the given name and all of its
// dependant as changed, recursively.
func (r *Repo) flagPackageAsChanged(name string) {
pkg, exists := r.Packages[name]
if !exists {
Expand All @@ -273,13 +329,6 @@ func (r *Repo) OwnsPackage(pkgName string) bool {
return strings.HasPrefix(pkgName, r.ModuleName())
}

type Package struct {
Name string
PartOfModule bool
Dependants []*Package
Changed bool
}

func directoryShouldBeIgnored(path string) bool {
return strings.Contains(path, ".git")
}
Expand Down

0 comments on commit acb5260

Please sign in to comment.