From 8b88bc15100fada4160d2909f59f23123fd8314e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kim=20=22BKC=22=20Carlb=C3=A4cker?= Date: Tue, 14 Feb 2017 22:23:33 +0100 Subject: [PATCH] ci-status.json & ci-environment-status.json --- .gitlab-ci.yml | 2 +- internal/helper/cmd.go | 15 ++ internal/service/commit/isancestor.go | 52 ++++++ internal/service/commit/isancestor_test.go | 185 +++++++++++++++++++++ internal/service/commit/server.go | 10 ++ internal/service/ref/main_test.go | 79 +++++++++ internal/service/ref/refname.go | 59 +++++++ internal/service/ref/refname_test.go | 131 +++++++++++++++ internal/service/ref/refs.go | 5 - internal/service/ref/refs_test.go | 68 -------- internal/service/register.go | 2 + 11 files changed, 534 insertions(+), 74 deletions(-) create mode 100644 internal/service/commit/isancestor.go create mode 100644 internal/service/commit/isancestor_test.go create mode 100644 internal/service/commit/server.go create mode 100644 internal/service/ref/main_test.go create mode 100644 internal/service/ref/refname.go create mode 100644 internal/service/ref/refname_test.go diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8a14b7f68c..b228769b02 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,3 @@ -image: golang:1.7 image: registry.gitlab.com/gitlab-org/gitlab-build-images:golang-1.7-git-2.8.4 stages: @@ -10,6 +9,7 @@ stages: stage: test script: - go version + - git version - make - make test diff --git a/internal/helper/cmd.go b/internal/helper/cmd.go index e90c809477..ccd973b511 100644 --- a/internal/helper/cmd.go +++ b/internal/helper/cmd.go @@ -40,3 +40,18 @@ func CleanUpProcessGroup(cmd *exec.Cmd) { // reap our child process cmd.Wait() } + +// ExitStatus will return the exit-code from an error +func ExitStatus(err error) (int, bool) { + exitError, ok := err.(*exec.ExitError) + if !ok { + return 0, false + } + + waitStatus, ok := exitError.Sys().(syscall.WaitStatus) + if !ok { + return 0, false + } + + return waitStatus.ExitStatus(), true +} diff --git a/internal/service/commit/isancestor.go b/internal/service/commit/isancestor.go new file mode 100644 index 0000000000..60a520799e --- /dev/null +++ b/internal/service/commit/isancestor.go @@ -0,0 +1,52 @@ +package commit + +import ( + "log" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + + "golang.org/x/net/context" + + pb "gitlab.com/gitlab-org/gitaly-proto/go" + "gitlab.com/gitlab-org/gitaly/internal/helper" +) + +func (s *server) CommitIsAncestor(ctx context.Context, in *pb.CommitIsAncestorRequest) (*pb.CommitIsAncestorResponse, error) { + repo := in.GetRepository() + if repo == nil || repo.GetPath() == "" { + message := "Bad Request (empty repository)" + log.Printf("CommitIsAncestor: %q", message) + return nil, grpc.Errorf(codes.InvalidArgument, message) + } + if in.AncestorId == "" { + message := "Bad Request (empty ancestor sha)" + log.Printf("CommitIsAncestor: %q", message) + return nil, grpc.Errorf(codes.InvalidArgument, message) + } + if in.ChildId == "" { + message := "Bad Request (empty child sha)" + log.Printf("CommitIsAncestor: %q", message) + return nil, grpc.Errorf(codes.InvalidArgument, message) + } + + ret, err := commitIsAncestorName(repo.Path, in.AncestorId, in.ChildId) + return &pb.CommitIsAncestorResponse{Value: ret}, err +} + +// Assumes that `path`, `ancestorID` and `childID` are populated :trollface: +func commitIsAncestorName(path, ancestorID, childID string) (bool, error) { + cmd := helper.GitCommand("git", "--git-dir", path, "merge-base", "--is-ancestor", ancestorID, childID) + + log.Printf("commitIsAncestor: RepoPath=%q ancestorSha=%s childSha=%s", path, ancestorID, childID) + + if err := cmd.Run(); err != nil { + if code, ok := helper.ExitStatus(err); ok && code == 1 { + // This is not really an error, this is `git` saying "This is not an ancestor" + return false, nil + } + return false, grpc.Errorf(codes.Internal, err.Error()) + } + + return true, nil +} diff --git a/internal/service/commit/isancestor_test.go b/internal/service/commit/isancestor_test.go new file mode 100644 index 0000000000..66a8012665 --- /dev/null +++ b/internal/service/commit/isancestor_test.go @@ -0,0 +1,185 @@ +package commit + +import ( + "log" + "net" + "os" + "os/exec" + "path" + "testing" + "time" + + "golang.org/x/net/context" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/reflection" + + pb "gitlab.com/gitlab-org/gitaly-proto/go" +) + +const ( + scratchDir = "testdata/scratch" + testRepoRoot = "testdata/data" + testRepo = "group/test.git" +) + +var serverSocketPath = path.Join(scratchDir, "gitaly.sock") + +func TestMain(m *testing.M) { + source := "https://gitlab.com/gitlab-org/gitlab-test.git" + clonePath := path.Join(testRepoRoot, testRepo) + if _, err := os.Stat(clonePath); err != nil { + testCmd := exec.Command("git", "clone", "--bare", source, clonePath) + testCmd.Stdout = os.Stdout + testCmd.Stderr = os.Stderr + + if err := testCmd.Run(); err != nil { + log.Printf("Test setup: failed to run %v", testCmd) + os.Exit(-1) + } + } + + if err := os.MkdirAll(scratchDir, 0755); err != nil { + log.Fatal(err) + } + + os.Exit(func() int { + os.Remove(serverSocketPath) + server := runCommitServer(m) + defer func() { + server.Stop() + os.Remove(serverSocketPath) + }() + + return m.Run() + }()) +} + +func TestCommitIsAncestorFailure(t *testing.T) { + client := newCommitClient(t) + repo := &pb.Repository{Path: path.Join(testRepoRoot, testRepo)} + + queries := []struct { + Request *pb.CommitIsAncestorRequest + ErrorCode codes.Code + ErrMsg string + }{ + { + Request: &pb.CommitIsAncestorRequest{ + Repository: nil, + AncestorId: "b83d6e391c22777fca1ed3012fce84f633d7fed0", + ChildId: "8a0f2ee90d940bfb0ba1e14e8214b0649056e4ab", + }, + ErrorCode: codes.InvalidArgument, + ErrMsg: "Expected to throw invalid argument got: %s", + }, + { + Request: &pb.CommitIsAncestorRequest{ + Repository: repo, + AncestorId: "", + ChildId: "8a0f2ee90d940bfb0ba1e14e8214b0649056e4ab", + }, + ErrorCode: codes.InvalidArgument, + ErrMsg: "Expected to throw invalid argument got: %s", + }, + { + Request: &pb.CommitIsAncestorRequest{ + Repository: repo, + AncestorId: "b83d6e391c22777fca1ed3012fce84f633d7fed0", + ChildId: "", + }, + ErrorCode: codes.InvalidArgument, + ErrMsg: "Expected to throw invalid argument got: %s", + }, + { + Request: &pb.CommitIsAncestorRequest{ + Repository: &pb.Repository{Path: path.Join(testRepoRoot, testRepo, "2")}, + AncestorId: "b83d6e391c22777fca1ed3012fce84f633d7fed0", + ChildId: "8a0f2ee90d940bfb0ba1e14e8214b0649056e4ab", + }, + ErrorCode: codes.Internal, + ErrMsg: "Expected to throw internal got: %s", + }, + } + + for _, v := range queries { + if _, err := client.CommitIsAncestor(context.Background(), v.Request); err == nil { + t.Error("Expected to throw an error") + } else if grpc.Code(err) != v.ErrorCode { + t.Errorf(v.ErrMsg, err) + } + } +} + +func TestCommitIsAncestorSuccess(t *testing.T) { + client := newCommitClient(t) + repo := &pb.Repository{Path: path.Join(testRepoRoot, testRepo)} + + queries := []struct { + Request *pb.CommitIsAncestorRequest + Response bool + ErrMsg string + }{ + { + Request: &pb.CommitIsAncestorRequest{ + Repository: repo, + AncestorId: "8a0f2ee90d940bfb0ba1e14e8214b0649056e4ab", + ChildId: "372ab6950519549b14d220271ee2322caa44d4eb", + }, + Response: true, + ErrMsg: "Expected commit to be ancestor", + }, + { + Request: &pb.CommitIsAncestorRequest{ + Repository: repo, + AncestorId: "b83d6e391c22777fca1ed3012fce84f633d7fed0", + ChildId: "38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e", + }, + Response: false, + ErrMsg: "Expected commit to not be ancestor", + }, + } + + for _, v := range queries { + c, err := client.CommitIsAncestor(context.Background(), v.Request) + if err != nil { + t.Fatalf("CommitIsAncestor threw error unexpectedly: %v", err) + } + + response := c.GetValue() + if response != v.Response { + t.Errorf(v.ErrMsg) + } + } +} + +func runCommitServer(m *testing.M) *grpc.Server { + server := grpc.NewServer() + listener, err := net.Listen("unix", serverSocketPath) + if err != nil { + log.Fatal(err) + } + + pb.RegisterCommitServer(server, NewServer()) + reflection.Register(server) + + go server.Serve(listener) + + return server +} + +func newCommitClient(t *testing.T) pb.CommitClient { + 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.NewCommitClient(conn) +} diff --git a/internal/service/commit/server.go b/internal/service/commit/server.go new file mode 100644 index 0000000000..31c4d25162 --- /dev/null +++ b/internal/service/commit/server.go @@ -0,0 +1,10 @@ +package commit + +import pb "gitlab.com/gitlab-org/gitaly-proto/go" + +type server struct{} + +// NewServer creates a new instance of a grpc CommitServer +func NewServer() pb.CommitServer { + return &server{} +} diff --git a/internal/service/ref/main_test.go b/internal/service/ref/main_test.go new file mode 100644 index 0000000000..1ffd2f76c4 --- /dev/null +++ b/internal/service/ref/main_test.go @@ -0,0 +1,79 @@ +package ref + +import ( + "log" + "net" + "os" + "os/exec" + "path" + "testing" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" + + pb "gitlab.com/gitlab-org/gitaly-proto/go" +) + +const ( + scratchDir = "testdata/scratch" + testRepoRoot = "testdata/data" + testRepo = "group/test.git" +) + +var serverSocketPath = path.Join(scratchDir, "gitaly.sock") + +func TestMain(m *testing.M) { + source := "https://gitlab.com/gitlab-org/gitlab-test.git" + clonePath := path.Join(testRepoRoot, testRepo) + if _, err := os.Stat(clonePath); err != nil { + testCmd := exec.Command("git", "clone", "--bare", source, clonePath) + testCmd.Stdout = os.Stdout + testCmd.Stderr = os.Stderr + + if err := testCmd.Run(); err != nil { + log.Printf("Test setup: failed to run %v", testCmd) + os.Exit(-1) + } + } + + if err := os.MkdirAll(scratchDir, 0755); err != nil { + log.Fatal(err) + } + + os.Exit(func() int { + return m.Run() + }()) +} + +func runRefServer(t *testing.T) *grpc.Server { + os.Remove(serverSocketPath) + grpcServer := grpc.NewServer() + listener, err := net.Listen("unix", serverSocketPath) + if err != nil { + t.Fatal(err) + } + + // Use 100 bytes as the maximum message size to test that fragmenting the ref list works correctly + pb.RegisterRefServer(grpcServer, &server{MaxMsgSize: 100}) + reflection.Register(grpcServer) + + go grpcServer.Serve(listener) + + return grpcServer +} + +func newRefClient(t *testing.T) pb.RefClient { + 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.NewRefClient(conn) +} diff --git a/internal/service/ref/refname.go b/internal/service/ref/refname.go new file mode 100644 index 0000000000..f1f8b5a69c --- /dev/null +++ b/internal/service/ref/refname.go @@ -0,0 +1,59 @@ +package ref + +import ( + "fmt" + "log" + "strings" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + + "golang.org/x/net/context" + + pb "gitlab.com/gitlab-org/gitaly-proto/go" + "gitlab.com/gitlab-org/gitaly/internal/helper" +) + +// FindRefName returns a ref that starts with the given prefix, if one exists. +// If there is more than one such ref there is no guarantee which one is +// returned or that the same one is returned on each call. +func (s *server) FindRefName(ctx context.Context, in *pb.FindRefNameRequest) (*pb.FindRefNameResponse, error) { + repo := in.GetRepository() + if repo == nil || repo.GetPath() == "" { + message := "Bad Request (empty repository)" + log.Printf("FindRefName: %q", message) + return nil, grpc.Errorf(codes.InvalidArgument, message) + } + if in.CommitId == "" { + message := "Bad Request (empty commit sha)" + log.Printf("FindRefName: %q", message) + return nil, grpc.Errorf(codes.InvalidArgument, message) + } + + ref, err := findRefName(repo.Path, in.CommitId, string(in.Prefix)) + if err != nil { + return nil, grpc.Errorf(codes.Internal, err.Error()) + } + + return &pb.FindRefNameResponse{Name: []byte(ref)}, nil +} + +// We assume `path` and `commitID` and `prefix` are non-empty +func findRefName(path, commitID, prefix string) (string, error) { + cmd := helper.GitCommand("git", "--git-dir", path, "for-each-ref", "--format=%(refname)", "--count=1", prefix, "--contains", commitID) + + log.Printf("findRefName: RepoPath=%q commitSha=%s prefix=%s", path, commitID, prefix) + + output, err := cmd.Output() + + line := string(output) + if err != nil { + return "", fmt.Errorf("findRefName: stdout: %q", line) + } + + // Trailing spaces are not allowed per the documentation + // https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html + refName := strings.TrimSpace(line) // Remove new-line + + return refName, nil +} diff --git a/internal/service/ref/refname_test.go b/internal/service/ref/refname_test.go new file mode 100644 index 0000000000..4afe498a81 --- /dev/null +++ b/internal/service/ref/refname_test.go @@ -0,0 +1,131 @@ +package ref + +import ( + "path" + "testing" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + + "golang.org/x/net/context" + + pb "gitlab.com/gitlab-org/gitaly-proto/go" +) + +func TestFindRefNameSuccess(t *testing.T) { + server := runRefServer(t) + defer server.Stop() + + client := newRefClient(t) + repo := &pb.Repository{Path: path.Join(testRepoRoot, testRepo)} + rpcRequest := &pb.FindRefNameRequest{ + Repository: repo, + CommitId: "0b4bc9a49b562e85de7cc9e834518ea6828729b9", + Prefix: []byte(`refs/heads/`), + } + + c, err := client.FindRefName(context.Background(), rpcRequest) + if err != nil { + t.Fatal(err) + } + + response := string(c.GetName()) + + if response != `refs/heads/expand-collapse-diffs` { + t.Errorf("Expected FindRefName to return `refs/heads/expand-collapse-diffs`, got `%#v`", response) + } +} + +func TestFindRefNameEmptyCommit(t *testing.T) { + server := runRefServer(t) + defer server.Stop() + + client := newRefClient(t) + repo := &pb.Repository{Path: path.Join(testRepoRoot, testRepo)} + rpcRequest := &pb.FindRefNameRequest{ + Repository: repo, + CommitId: "", + Prefix: []byte(`refs/heads/`), + } + + c, err := client.FindRefName(context.Background(), rpcRequest) + if err == nil { + t.Fatalf("Expected FindRefName to throw an error") + } + if grpc.Code(err) != codes.InvalidArgument { + t.Errorf("Expected FindRefName to throw InvalidArgument, got %v", err) + } + + response := string(c.GetName()) + if response != `` { + t.Errorf("Expected FindRefName to return empty-string, got %q", response) + } +} + +func TestFindRefNameInvalidRepo(t *testing.T) { + server := runRefServer(t) + defer server.Stop() + + client := newRefClient(t) + repo := &pb.Repository{Path: ""} + rpcRequest := &pb.FindRefNameRequest{ + Repository: repo, + CommitId: "0b4bc9a49b562e85de7cc9e834518ea6828729b9", + Prefix: []byte(`refs/heads/`), + } + + c, err := client.FindRefName(context.Background(), rpcRequest) + if err == nil { + t.Fatalf("Expected FindRefName to throw an error") + } + if grpc.Code(err) != codes.InvalidArgument { + t.Errorf("Expected FindRefName to throw InvalidArgument, got %v", err) + } + + response := string(c.GetName()) + if response != `` { + t.Errorf("Expected FindRefName to return empty-string, got %q", response) + } +} + +func TestFindRefNameInvalidPrefix(t *testing.T) { + server := runRefServer(t) + defer server.Stop() + + client := newRefClient(t) + repo := &pb.Repository{Path: path.Join(testRepoRoot, testRepo)} + rpcRequest := &pb.FindRefNameRequest{ + Repository: repo, + CommitId: "0b4bc9a49b562e85de7cc9e834518ea6828729b9", + Prefix: []byte(`refs/nonexistant/`), + } + + c, err := client.FindRefName(context.Background(), rpcRequest) + if err != nil { + t.Fatalf("Expected FindRefName to not throw an error: %v", err) + } + if len(c.Name) > 0 { + t.Errorf("Expected empty name, got %q instead", c.Name) + } +} + +func TestFindRefNameInvalidObject(t *testing.T) { + server := runRefServer(t) + defer server.Stop() + + client := newRefClient(t) + repo := &pb.Repository{Path: path.Join(testRepoRoot, testRepo)} + rpcRequest := &pb.FindRefNameRequest{ + Repository: repo, + CommitId: "dead1234dead1234dead1234dead1234dead1234", + } + + c, err := client.FindRefName(context.Background(), rpcRequest) + if grpc.Code(err) != codes.Internal { + t.Fatalf("Expected FindRefName to throw an error") + } + + if len(c.GetName()) > 0 { + t.Errorf("Expected FindRefName to return empty-string, got %q", string(c.GetName())) + } +} diff --git a/internal/service/ref/refs.go b/internal/service/ref/refs.go index 7bf7bf896f..f3393ce786 100644 --- a/internal/service/ref/refs.go +++ b/internal/service/ref/refs.go @@ -171,8 +171,3 @@ func (s *server) FindDefaultBranchName(ctx context.Context, in *pb.FindDefaultBr return &pb.FindDefaultBranchNameResponse{Name: defaultBranchName}, nil } - -// FindRefName returns the first refname of a Repository -func (s *server) FindRefName(ctx context.Context, in *pb.FindRefNameRequest) (*pb.FindRefNameResponse, error) { - return nil, nil -} diff --git a/internal/service/ref/refs_test.go b/internal/service/ref/refs_test.go index 03873302ce..0a62aa6786 100644 --- a/internal/service/ref/refs_test.go +++ b/internal/service/ref/refs_test.go @@ -3,29 +3,15 @@ package ref import ( "bytes" "io" - "log" - "net" - "os" - "os/exec" "path" "testing" - "time" pb "gitlab.com/gitlab-org/gitaly-proto/go" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" - "google.golang.org/grpc/reflection" ) -const ( - scratchDir = "testdata/scratch" - testRepoRoot = "testdata/data" - testRepo = "group/test.git" -) - -var serverSocketPath = path.Join(scratchDir, "gitaly.sock") - func containsRef(refs [][]byte, ref string) bool { for _, b := range refs { if string(b) == ref { @@ -35,29 +21,6 @@ func containsRef(refs [][]byte, ref string) bool { return false } -func TestMain(m *testing.M) { - source := "https://gitlab.com/gitlab-org/gitlab-test.git" - clonePath := path.Join(testRepoRoot, testRepo) - if _, err := os.Stat(clonePath); err != nil { - testCmd := exec.Command("git", "clone", "--bare", source, clonePath) - testCmd.Stdout = os.Stdout - testCmd.Stderr = os.Stderr - - if err := testCmd.Run(); err != nil { - log.Printf("Test setup: failed to run %v", testCmd) - os.Exit(-1) - } - } - - if err := os.MkdirAll(scratchDir, 0755); err != nil { - log.Fatal(err) - } - - os.Exit(func() int { - return m.Run() - }()) -} - func TestSuccessfulFindAllBranchNames(t *testing.T) { server := runRefServer(t) defer server.Stop() @@ -274,34 +237,3 @@ func TestEmptyFindDefaultBranchNameRequest(t *testing.T) { t.Fatal(err) } } - -func runRefServer(t *testing.T) *grpc.Server { - grpcServer := grpc.NewServer() - listener, err := net.Listen("unix", serverSocketPath) - if err != nil { - t.Fatal(err) - } - - // Use 100 bytes as the maximum message size to test that fragmenting the ref list works correctly - pb.RegisterRefServer(grpcServer, &server{MaxMsgSize: 100}) - reflection.Register(grpcServer) - - go grpcServer.Serve(listener) - - return grpcServer -} - -func newRefClient(t *testing.T) pb.RefClient { - 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.NewRefClient(conn) -} diff --git a/internal/service/register.go b/internal/service/register.go index e5d4fe0227..54465d2fbd 100644 --- a/internal/service/register.go +++ b/internal/service/register.go @@ -2,6 +2,7 @@ package service import ( pb "gitlab.com/gitlab-org/gitaly-proto/go" + "gitlab.com/gitlab-org/gitaly/internal/service/commit" "gitlab.com/gitlab-org/gitaly/internal/service/diff" "gitlab.com/gitlab-org/gitaly/internal/service/notifications" "gitlab.com/gitlab-org/gitaly/internal/service/ref" @@ -17,4 +18,5 @@ func RegisterAll(grpcServer *grpc.Server) { pb.RegisterRefServer(grpcServer, ref.NewServer()) pb.RegisterSmartHTTPServer(grpcServer, smarthttp.NewServer()) pb.RegisterDiffServer(grpcServer, diff.NewServer()) + pb.RegisterCommitServer(grpcServer, commit.NewServer()) } -- GitLab