Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZ.J. van de Weg <git@zjvandeweg.nl>2017-06-26 12:41:04 +0300
committerZ.J. van de Weg <git@zjvandeweg.nl>2017-07-03 14:05:05 +0300
commitfd220a3c953f767f0ce4b6d2eed2f3f0e7366c9f (patch)
tree0ccf1d71f5f307782600fa888ec449c4dffdf7b0
parent2a702cf905656e7fd2a0a02f04af1ea7eecb2bcf (diff)
Implement RepositoryExists request and response
Needed to perform this request was only the helper already implemented, however this one method RepoPath(), first checked the storage, constructed a path, guarded against path traversal, and checked if the path passed was a git repo. My implementation tries to split these concern without changing the API for now. The request can only respond with an `InvalidArgument` error code. If it returns an error, the `Exists` property is not set. In other cases it checks if the directory has the `objects` folder and `HEAD` file.
-rw-r--r--CHANGELOG.md2
-rw-r--r--internal/helper/repo.go40
-rw-r--r--internal/service/repository/repository.go21
-rw-r--r--internal/service/repository/repository_test.go91
-rw-r--r--internal/service/repository/server.go12
-rw-r--r--internal/service/repository/testhelper_test.go64
6 files changed, 224 insertions, 6 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 873fff428..7eded313c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,8 @@ v0.14.0
- Override gRPC code to Canceled/DeadlineExceeded on requests with
canceled contexts
https://gitlab.com/gitlab-org/gitaly/merge_requests/199
+- Add RepositoryExists Implementation
+ https://gitlab.com/gitlab-org/gitaly/merge_requests/200
v0.13.0
diff --git a/internal/helper/repo.go b/internal/helper/repo.go
index a3cd3363c..2c3a1f51c 100644
--- a/internal/helper/repo.go
+++ b/internal/helper/repo.go
@@ -18,6 +18,26 @@ import (
// relevant error codes and should be passed back to gRPC without further
// decoration.
func GetRepoPath(repo *pb.Repository) (string, error) {
+ repoPath, err := GetPath(repo)
+ if err != nil {
+ return "", err
+ }
+
+ if repoPath == "" {
+ return "", grpc.Errorf(codes.InvalidArgument, "GetRepoPath: empty repo")
+ }
+
+ if IsGitDirectory(repoPath) {
+ return repoPath, nil
+ }
+
+ return "", grpc.Errorf(codes.NotFound, "GetRepoPath: not a git repository '%s'", repoPath)
+}
+
+// GetPath returns the path of the repo passed as first argument. An error is
+// returned when either the storage can't be found or the path includes
+// constructs trying to perform directory traversal.
+func GetPath(repo *pb.Repository) (string, error) {
storagePath, ok := config.StoragePath(repo.GetStorageName())
if !ok {
return "", grpc.Errorf(codes.InvalidArgument, "GetRepoPath: invalid storage name '%s'", repo.GetStorageName())
@@ -33,15 +53,23 @@ func GetRepoPath(repo *pb.Repository) (string, error) {
return "", grpc.Errorf(codes.InvalidArgument, "GetRepoPath: relative path can't contain directory traversal")
}
- repoPath := path.Join(storagePath, relativePath)
+ return path.Join(storagePath, relativePath), nil
+}
- if repoPath == "" {
- return "", grpc.Errorf(codes.InvalidArgument, "GetRepoPath: empty repo")
+// IsGitDirectory checks if the directory passed as first argument looks like
+// a valid git directory.
+func IsGitDirectory(dir string) bool {
+ if dir == "" {
+ return false
+ }
+
+ if _, err := os.Stat(path.Join(dir, "objects")); err != nil {
+ return false
}
- if _, err := os.Stat(path.Join(repoPath, "objects")); err != nil {
- return "", grpc.Errorf(codes.NotFound, "GetRepoPath: not a git repository '%s'", repoPath)
+ if _, err := os.Stat(path.Join(dir, "HEAD")); err != nil {
+ return false
}
- return repoPath, nil
+ return true
}
diff --git a/internal/service/repository/repository.go b/internal/service/repository/repository.go
new file mode 100644
index 000000000..61ff2040d
--- /dev/null
+++ b/internal/service/repository/repository.go
@@ -0,0 +1,21 @@
+package repository
+
+import (
+ "golang.org/x/net/context"
+
+ pb "gitlab.com/gitlab-org/gitaly-proto/go"
+ "gitlab.com/gitlab-org/gitaly/internal/helper"
+)
+
+func (s *server) Exists(ctx context.Context, in *pb.RepositoryExistsRequest) (*pb.RepositoryExistsResponse, error) {
+ path, err := helper.GetPath(in.Repository)
+ if err != nil {
+ return nil, err
+ }
+
+ if helper.IsGitDirectory(path) {
+ return &pb.RepositoryExistsResponse{Exists: true}, nil
+ }
+
+ return &pb.RepositoryExistsResponse{Exists: false}, nil
+}
diff --git a/internal/service/repository/repository_test.go b/internal/service/repository/repository_test.go
new file mode 100644
index 000000000..01ef5cbf5
--- /dev/null
+++ b/internal/service/repository/repository_test.go
@@ -0,0 +1,91 @@
+package repository
+
+import (
+ "testing"
+
+ pb "gitlab.com/gitlab-org/gitaly-proto/go"
+ "gitlab.com/gitlab-org/gitaly/internal/config"
+ "gitlab.com/gitlab-org/gitaly/internal/testhelper"
+
+ "github.com/stretchr/testify/require"
+ "golang.org/x/net/context"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/codes"
+)
+
+func TestRepositoryExists(t *testing.T) {
+ server := runRepoServer(t)
+ defer server.Stop()
+
+ client := newRepositoryClient(t)
+
+ // Setup storage paths
+ testStorages := []config.Storage{
+ {Name: "default", Path: testhelper.GitlabTestStoragePath()},
+ {Name: "other", Path: "/home/git/repositories2"},
+ }
+
+ defer func(oldStorages []config.Storage) {
+ config.Config.Storages = oldStorages
+ }(config.Config.Storages)
+ config.Config.Storages = testStorages
+
+ queries := []struct {
+ Request *pb.RepositoryExistsRequest
+ ErrorCode codes.Code
+ Exists bool
+ }{
+ {
+ Request: &pb.RepositoryExistsRequest{
+ Repository: nil,
+ },
+ ErrorCode: codes.InvalidArgument,
+ },
+ {
+ Request: &pb.RepositoryExistsRequest{
+ Repository: &pb.Repository{
+ StorageName: "",
+ RelativePath: testhelper.TestRelativePath,
+ },
+ },
+ ErrorCode: codes.InvalidArgument,
+ },
+ {
+ Request: &pb.RepositoryExistsRequest{
+ Repository: &pb.Repository{
+ StorageName: "default",
+ RelativePath: "",
+ },
+ },
+ ErrorCode: codes.InvalidArgument,
+ },
+ {
+ Request: &pb.RepositoryExistsRequest{
+ Repository: &pb.Repository{
+ StorageName: "default",
+ RelativePath: testhelper.TestRelativePath,
+ },
+ },
+ Exists: true,
+ },
+ {
+ Request: &pb.RepositoryExistsRequest{
+ Repository: &pb.Repository{
+ StorageName: "other",
+ RelativePath: testhelper.TestRelativePath,
+ },
+ },
+ Exists: false,
+ },
+ }
+
+ for _, tc := range queries {
+ response, err := client.Exists(context.Background(), tc.Request)
+ if err != nil {
+ require.Equal(t, tc.ErrorCode, grpc.Code(err))
+ continue
+ }
+
+ require.Equal(t, tc.Exists, response.Exists)
+ }
+}
diff --git a/internal/service/repository/server.go b/internal/service/repository/server.go
new file mode 100644
index 000000000..d47c1a9b8
--- /dev/null
+++ b/internal/service/repository/server.go
@@ -0,0 +1,12 @@
+package repository
+
+import (
+ pb "gitlab.com/gitlab-org/gitaly-proto/go"
+)
+
+type server struct{}
+
+// NewServer creates a new instance of a gRPC repo server
+func NewServer() pb.RepositoryServiceServer {
+ return &server{}
+}
diff --git a/internal/service/repository/testhelper_test.go b/internal/service/repository/testhelper_test.go
new file mode 100644
index 000000000..0972baa75
--- /dev/null
+++ b/internal/service/repository/testhelper_test.go
@@ -0,0 +1,64 @@
+package repository
+
+import (
+ "net"
+ "os"
+ "path"
+ "testing"
+ "time"
+
+ log "github.com/Sirupsen/logrus"
+ pb "gitlab.com/gitlab-org/gitaly-proto/go"
+ "gitlab.com/gitlab-org/gitaly/internal/testhelper"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/reflection"
+)
+
+const scratchDir = "testdata/scratch"
+
+var (
+ serverSocketPath = path.Join(scratchDir, "gitaly.sock")
+ testRepo *pb.Repository
+)
+
+func TestMain(m *testing.M) {
+ testRepo = testhelper.TestRepository()
+
+ if err := os.MkdirAll(scratchDir, 0755); err != nil {
+ log.WithError(err).Fatal("mkdirall failed")
+ }
+
+ os.Exit(func() int {
+ return m.Run()
+ }())
+}
+
+func newRepositoryClient(t *testing.T) pb.RepositoryServiceClient {
+ connOpts := []grpc.DialOption{
+ grpc.WithInsecure(),
+ grpc.WithDialer(func(addr string, _ time.Duration) (net.Conn, error) {
+ return net.Dial("unix", addr)
+ }),
+ }
+ conn, err := grpc.Dial(serverSocketPath, connOpts...)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ return pb.NewRepositoryServiceClient(conn)
+}
+
+func runRepoServer(t *testing.T) *grpc.Server {
+ server := grpc.NewServer()
+ listener, err := net.Listen("unix", serverSocketPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ pb.RegisterRepositoryServiceServer(server, NewServer())
+ reflection.Register(server)
+
+ go server.Serve(listener)
+
+ return server
+}