Skip to content

Commit

Permalink
feat(vcs): Correctly infer VCS settings when within a repo but not at…
Browse files Browse the repository at this point in the history
… the root (#205)

Fixes #190.
  • Loading branch information
delikat authored and elldritch committed Aug 1, 2018
1 parent 488b88c commit dbabe3e
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 89 deletions.
23 changes: 1 addition & 22 deletions analyzers/golang/find.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,6 @@ import (
"github.com/fossas/fossa-cli/monad"
)

// finder, bindFinder, and friends are EitherStr functions for finding whether
// one of many files exist.
type finder func(pathElems ...string) (bool, error)

func bindFinder(name string, find finder, pathElems ...string) monad.EitherStrFunc {
return func(prev string) (string, error) {
ok, err := find(pathElems...)
if err != nil {
return "", err
}
if ok {
return name, nil
}
return prev, nil
}
}

func findFile(tool string, pathElems ...string) monad.EitherStrFunc {
return bindFinder(tool, files.Exists, pathElems...)
}

func findFolder(tool string, pathElems ...string) monad.EitherStrFunc {
return bindFinder(tool, files.ExistsFolder, pathElems...)
return files.BindFinder(tool, files.Exists, pathElems...)
}
3 changes: 2 additions & 1 deletion analyzers/golang/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package golang
import (
"github.com/fossas/fossa-cli/analyzers/golang/resolver"
"github.com/fossas/fossa-cli/log"
"github.com/fossas/fossa-cli/vcs"
)

// A Project is a single folder that forms a coherent "project" for a developer
Expand Down Expand Up @@ -69,7 +70,7 @@ func (a *Analyzer) Project(pkg string) (Project, error) {
tool, manifestDir, err := NearestLockfile(dir)

// Find the nearest VCS repository.
_, repoRoot, err := NearestVCS(dir)
_, repoRoot, err := vcs.NearestVCS(dir)
if err != nil {
return Project{}, err
}
Expand Down
53 changes: 0 additions & 53 deletions analyzers/golang/vcs.go

This file was deleted.

13 changes: 13 additions & 0 deletions cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Package cli encompasses domain types for the CLI app.
package cli

// VCS represents a type of version control system.
type VCS int

const (
_ VCS = iota
Subversion
Git
Mercurial
Bazaar
)
11 changes: 9 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
v1 "github.com/fossas/fossa-cli/config/file.v1"
"github.com/fossas/fossa-cli/files"
"github.com/fossas/fossa-cli/log"
"github.com/fossas/fossa-cli/vcs"
)

var (
Expand All @@ -42,8 +43,14 @@ func Init(c *cli.Context) error {
filename = fname

// Third, try to open the local VCS repository.
// TODO: this will break if _within_ but not _at the root_ of a git repository
r, err := git.PlainOpen(".")
vcsDir, err := vcs.GetRepository(".")
if err == vcs.ErrNoNearestVCS {
return nil
}
if err != nil {
return err
}
r, err := git.PlainOpen(vcsDir)
if err == git.ErrRepositoryNotExists {
return nil
}
Expand Down
19 changes: 19 additions & 0 deletions files/find.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package files
import (
"errors"
"path/filepath"

"github.com/fossas/fossa-cli/monad"
)

// blaf
Expand Down Expand Up @@ -48,3 +50,20 @@ func WalkUp(startdir string, walker WalkUpFunc) (string, error) {
}
return "", ErrDirNotFound
}

// finder, bindFinder, and friends are EitherStr functions for finding whether
// one of many files exist.
type finder func(pathElems ...string) (bool, error)

func BindFinder(name string, find finder, pathElems ...string) monad.EitherStrFunc {
return func(prev string) (string, error) {
ok, err := find(pathElems...)
if err != nil {
return "", err
}
if ok {
return name, nil
}
return prev, nil
}
}
27 changes: 27 additions & 0 deletions monad/monad.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Package monad implements common monomorphic monads.
package monad

import "github.com/fossas/fossa-cli"

// EitherStr is a monomorphic Either monad specialized to string. I miss Haskell.
type EitherStr struct {
Result string
Expand All @@ -25,3 +27,28 @@ func (r *EitherStr) Bind(f EitherStrFunc) *EitherStr {
r.Result = result
return r
}

// EitherVCS is an Either monad specialized to VCS.
type EitherVCS struct {
Result cli.VCS
Err error
}

// EitherVCSFunc defines monadic EitherVCS functions.
type EitherVCSFunc func(previous cli.VCS) (cli.VCS, error)

// BindVCS lifts EitherVCSFuncs into the EitherVCS monad.
func (r *EitherVCS) BindVCS(f EitherVCSFunc) *EitherVCS {
if r.Err != nil {
return r
}

result, err := f(r.Result)
if err != nil {
r.Err = err
return r
}

r.Result = result
return r
}
28 changes: 28 additions & 0 deletions vcs/find.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package vcs

import (
"github.com/fossas/fossa-cli"
"github.com/fossas/fossa-cli/files"
"github.com/fossas/fossa-cli/monad"
)

// vcsFinder, bindVCSFinder, and friends are EitherVCS functions for finding whether
// one of many files exist.
type vcsFinder func(pathElems ...string) (bool, error)

func bindVCSFinder(tool cli.VCS, find vcsFinder, pathElems ...string) monad.EitherVCSFunc {
return func(prev cli.VCS) (cli.VCS, error) {
ok, err := find(pathElems...)
if err != nil {
return 0, err
}
if ok {
return tool, nil
}
return prev, nil
}
}

func findVCSFolder(tool cli.VCS, pathElems ...string) monad.EitherVCSFunc {
return bindVCSFinder(tool, files.ExistsFolder, pathElems...)
}
83 changes: 72 additions & 11 deletions vcs/vcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,81 @@
// systems.
package vcs

import "github.com/fossas/fossa-cli/errutil"
import (
"errors"
"path/filepath"

type Type int
"github.com/fossas/fossa-cli"
"github.com/fossas/fossa-cli/errutil"
"github.com/fossas/fossa-cli/files"
"github.com/fossas/fossa-cli/monad"
)

const (
_ Type = iota
Subversion
Git
Mercurial
Bazaar
// Errors that occur when finding VCS repositories.
var (
ErrNoVCSInDir = errors.New("could not find VCS repository in directory")
ErrNoNearestVCS = errors.New("could not find nearest VCS repository in directory")
)

// GetRepository returns the location of the repository containing dirname, or
// errutil.ErrRepositoryNotFound on error.
// VCSIn returns the type of VCS repository rooted at a directory, or
// ErrNoVCSInDir if none is found.
func vcsIn(dirname string) (cli.VCS, error) {
either := monad.EitherVCS{}
result := either.
BindVCS(findVCSFolder(cli.Git, filepath.Join(dirname, ".git"))).
BindVCS(findVCSFolder(cli.Subversion, filepath.Join(dirname, ".svn"))).
BindVCS(findVCSFolder(cli.Mercurial, filepath.Join(dirname, ".hg"))).
BindVCS(findVCSFolder(cli.Bazaar, filepath.Join(dirname, ".bzr")))
if result.Err != nil {
return 0, result.Err
}
if result.Result == 0 {
return 0, ErrNoVCSInDir
}
return result.Result, nil
}

// NearestVCS returns the type and directory of the nearest VCS repository
// containing the directory, or ErrNoNearestVCS if none is found.
func NearestVCS(dirname string) (vcsType cli.VCS, vcsDir string, err error) {
vcsDir, err = files.WalkUp(dirname, func(d string) error {
tool, err := vcsIn(d)
if err == ErrNoVCSInDir {
return nil
}
if err != nil {
return err
}
vcsType = tool
return files.ErrStopWalk
})
if err == files.ErrDirNotFound {
return 0, "", ErrNoNearestVCS
}
return vcsType, vcsDir, err
}

// GetRepository returns the location of the repository containing dirname,
// errutil.ErrRepositoryNotFound if none is found, or errutil.ErrNotImplemented
// if an unsupported VCS is found.
func GetRepository(dirname string) (string, error) {
return "", errutil.ErrNotImplemented
vcsType, vcsDir, err := NearestVCS(dirname)
if err == ErrNoNearestVCS {
return "", errutil.ErrRepositoryNotFound
}
if err != nil {
return "", err
}
switch vcsType {
case cli.Git:
return vcsDir, nil
case cli.Subversion:
return "", errutil.ErrNotImplemented
case cli.Mercurial:
return "", errutil.ErrNotImplemented
case cli.Bazaar:
return "", errutil.ErrNotImplemented
default:
return "", errutil.ErrNotImplemented
}
}
32 changes: 32 additions & 0 deletions vcs/vcs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package vcs_test

import (
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"

"github.com/fossas/fossa-cli/errutil"
"github.com/fossas/fossa-cli/vcs"
)

func TestGetRepositoryAtRoot(t *testing.T) {
wd, _ := os.Getwd()
vcsDir, err := vcs.GetRepository(filepath.Join("testdata", "git"))
assert.NoError(t, err)
assert.Equal(t, filepath.Join(wd, "testdata", "git"), vcsDir)
}

func TestGetRepositoryBelowRoot(t *testing.T) {
wd, _ := os.Getwd()
vcsDir, err := vcs.GetRepository(filepath.Join("testdata", "git", "nested", "directory"))
assert.NoError(t, err)
assert.Equal(t, filepath.Join(wd, "testdata", "git"), vcsDir)
}

func TestGetRepositoryUnsupportedVCS(t *testing.T) {
vcsDir, err := vcs.GetRepository(filepath.Join("testdata", "subversion", "nested", "directory"))
assert.Error(t, err, errutil.ErrNotImplemented)
assert.Equal(t, "", vcsDir)
}

0 comments on commit dbabe3e

Please sign in to comment.