diff options
author | Jacob Vosmaer <jacob@gitlab.com> | 2019-04-10 15:15:14 +0300 |
---|---|---|
committer | Jacob Vosmaer <jacob@gitlab.com> | 2019-04-10 15:15:14 +0300 |
commit | fd1fae703034bf6b4a6972b8b83c034e27d93d2c (patch) | |
tree | 2cb80c6cda8ee206c4c16f3f88759f29d160c131 | |
parent | b97345604f05c9d983da00b3caf573c9ed49689f (diff) | |
parent | 14c69d1950a82893ac1baed25686d9a0c8170386 (diff) |
Merge branch 'list-git-remotes' into 'master'
Implement ListRemotes
Closes #1422
See merge request gitlab-org/gitaly!1019
-rw-r--r-- | changelogs/unreleased/list-git-remotes.yml | 5 | ||||
-rw-r--r-- | internal/service/remote/list_remotes.go | 9 | ||||
-rw-r--r-- | internal/service/remote/remotes.go | 80 | ||||
-rw-r--r-- | internal/service/remote/remotes_test.go | 149 |
4 files changed, 233 insertions, 10 deletions
diff --git a/changelogs/unreleased/list-git-remotes.yml b/changelogs/unreleased/list-git-remotes.yml new file mode 100644 index 000000000..60d5c9d2a --- /dev/null +++ b/changelogs/unreleased/list-git-remotes.yml @@ -0,0 +1,5 @@ +--- +title: Implement ListRemotes +merge_request: 1019 +author: +type: added diff --git a/internal/service/remote/list_remotes.go b/internal/service/remote/list_remotes.go deleted file mode 100644 index 8e4119cd9..000000000 --- a/internal/service/remote/list_remotes.go +++ /dev/null @@ -1,9 +0,0 @@ -package remote - -import ( - "gitlab.com/gitlab-org/gitaly-proto/go/gitalypb" -) - -func (s *server) ListRemotes(*gitalypb.ListRemotesRequest, gitalypb.RemoteService_ListRemotesServer) error { - return nil -} diff --git a/internal/service/remote/remotes.go b/internal/service/remote/remotes.go index ea77c2030..f680a3492 100644 --- a/internal/service/remote/remotes.go +++ b/internal/service/remote/remotes.go @@ -1,17 +1,19 @@ package remote import ( + "bufio" "bytes" "context" "fmt" "io/ioutil" "strings" + "gitlab.com/gitlab-org/gitaly/internal/git/remote" "gitlab.com/gitlab-org/gitaly/internal/rubyserver" "gitlab.com/gitlab-org/gitaly-proto/go/gitalypb" "gitlab.com/gitlab-org/gitaly/internal/git" - "gitlab.com/gitlab-org/gitaly/internal/git/remote" + "gitlab.com/gitlab-org/gitaly/internal/helper/chunk" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -102,3 +104,79 @@ func validateRemoveRemoteRequest(req *gitalypb.RemoveRemoteRequest) error { return nil } + +func (s *server) ListRemotes(req *gitalypb.ListRemotesRequest, stream gitalypb.RemoteService_ListRemotesServer) error { + repo := req.GetRepository() + + ctx := stream.Context() + cmd, err := git.Command(ctx, repo, "remote", "-v") + if err != nil { + return err + } + + scanner := bufio.NewScanner(cmd) + remotesMap := make(map[string]*gitalypb.ListRemotesResponse_Remote) + + for scanner.Scan() { + text := scanner.Text() + splitLine := strings.Fields(text) + if len(splitLine) != 3 { + continue + } + + remote := &gitalypb.ListRemotesResponse_Remote{Name: splitLine[0]} + if splitLine[2] == "(fetch)" { + remote.FetchUrl = splitLine[1] + } else if splitLine[2] == "(push)" { + remote.PushUrl = splitLine[1] + } + + oldRemote := remotesMap[splitLine[0]] + remotesMap[splitLine[0]] = mergeGitalyRemote(oldRemote, remote) + } + + sender := chunk.New(&listRemotesSender{stream: stream}) + for _, remote := range remotesMap { + if err := sender.Send(remote); err != nil { + return err + } + } + + return sender.Flush() +} + +func mergeGitalyRemote(oldRemote *gitalypb.ListRemotesResponse_Remote, newRemote *gitalypb.ListRemotesResponse_Remote) *gitalypb.ListRemotesResponse_Remote { + if oldRemote == nil { + return &gitalypb.ListRemotesResponse_Remote{Name: newRemote.Name, FetchUrl: newRemote.FetchUrl, PushUrl: newRemote.PushUrl} + } + + newRemoteInstance := &gitalypb.ListRemotesResponse_Remote{Name: oldRemote.Name, FetchUrl: oldRemote.FetchUrl, PushUrl: oldRemote.PushUrl} + if newRemote.Name != "" { + newRemoteInstance.Name = newRemote.Name + } + + if newRemote.PushUrl != "" { + newRemoteInstance.PushUrl = newRemote.PushUrl + } + + if newRemote.FetchUrl != "" { + newRemoteInstance.FetchUrl = newRemote.PushUrl + } + + return newRemoteInstance +} + +type listRemotesSender struct { + stream gitalypb.RemoteService_ListRemotesServer + remotes []*gitalypb.ListRemotesResponse_Remote +} + +func (l *listRemotesSender) Append(it chunk.Item) { + l.remotes = append(l.remotes, it.(*gitalypb.ListRemotesResponse_Remote)) +} + +func (l *listRemotesSender) Send() error { + return l.stream.Send(&gitalypb.ListRemotesResponse{Remotes: l.remotes}) +} + +func (l *listRemotesSender) Reset() { l.remotes = nil } diff --git a/internal/service/remote/remotes_test.go b/internal/service/remote/remotes_test.go index b90d8c8b4..80c1e6dbb 100644 --- a/internal/service/remote/remotes_test.go +++ b/internal/service/remote/remotes_test.go @@ -270,3 +270,152 @@ func TestFailedFindRemoteRepository(t *testing.T) { require.Equal(t, tc.exists, resp.GetExists(), tc.description) } } + +func TestListDifferentPushUrlRemote(t *testing.T) { + server, serverSocketPath := runRemoteServiceServer(t) + defer server.Stop() + + client, conn := NewRemoteClient(t, serverSocketPath) + defer conn.Close() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + testRepo, testRepoPath, cleanupFn := testhelper.NewTestRepo(t) + defer cleanupFn() + + client.RemoveRemote(ctx, &gitalypb.RemoveRemoteRequest{ + Repository: testRepo, + Name: "origin", + }) + + branchName := "my-remote" + fetchURL := "http://my-repo.git" + pushURL := "http://my-other-repo.git" + + testhelper.MustRunCommand(t, nil, "git", "-C", testRepoPath, "remote", "add", branchName, fetchURL) + testhelper.MustRunCommand(t, nil, "git", "-C", testRepoPath, "remote", "set-url", "--push", branchName, pushURL) + + testCases := []*gitalypb.ListRemotesResponse_Remote{ + { + Name: branchName, + FetchUrl: fetchURL, + PushUrl: pushURL, + }, + } + + request := &gitalypb.ListRemotesRequest{Repository: testRepo} + + resp, err := client.ListRemotes(ctx, request) + if err != nil { + t.Fatal(err) + } + + receivedRemotes := consumeListRemotesResponse(t, resp) + require.NoError(t, err) + + require.Len(t, receivedRemotes, len(testCases)) + require.ElementsMatch(t, testCases, receivedRemotes) + +} + +func TestListRemotes(t *testing.T) { + server, serverSocketPath := runRemoteServiceServer(t) + defer server.Stop() + + client, conn := NewRemoteClient(t, serverSocketPath) + defer conn.Close() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + repoWithSingleRemote, _, cleanupFn := testhelper.NewTestRepo(t) + defer cleanupFn() + + repoWithMultipleRemotes, _, cleanupFn := testhelper.NewTestRepo(t) + defer cleanupFn() + + repoWithEmptyRemote, _, cleanupFn := testhelper.NewTestRepo(t) + defer cleanupFn() + + singleRemote := []*gitalypb.ListRemotesResponse_Remote{ + {Name: "my-remote", FetchUrl: "http://my-repo.git", PushUrl: "http://my-repo.git"}, + } + + multipleRemotes := []*gitalypb.ListRemotesResponse_Remote{ + {Name: "my-other-remote", FetchUrl: "johndoe@host:my-new-repo.git", PushUrl: "johndoe@host:my-new-repo.git"}, + {Name: "my-remote", FetchUrl: "http://my-repo.git", PushUrl: "http://my-repo.git"}, + } + + testCases := []struct { + description string + repository *gitalypb.Repository + remotes []*gitalypb.ListRemotesResponse_Remote + }{ + { + description: "empty remote", + repository: repoWithEmptyRemote, + remotes: []*gitalypb.ListRemotesResponse_Remote{}, + }, + { + description: "single remote", + repository: repoWithSingleRemote, + remotes: singleRemote, + }, + { + description: "multiple remotes", + repository: repoWithMultipleRemotes, + remotes: multipleRemotes, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + client.RemoveRemote(ctx, &gitalypb.RemoveRemoteRequest{ + Repository: tc.repository, + Name: "origin", + }) + + for _, r := range tc.remotes { + request := &gitalypb.AddRemoteRequest{ + Repository: tc.repository, + Name: r.Name, + Url: r.FetchUrl, + } + + _, err := client.AddRemote(ctx, request) + require.NoError(t, err) + } + + request := &gitalypb.ListRemotesRequest{Repository: tc.repository} + + resp, err := client.ListRemotes(ctx, request) + if err != nil { + t.Fatal(err) + } + + receivedRemotes := consumeListRemotesResponse(t, resp) + require.NoError(t, err) + + require.Len(t, receivedRemotes, len(tc.remotes)) + require.ElementsMatch(t, tc.remotes, receivedRemotes) + }) + } + +} + +func consumeListRemotesResponse(t *testing.T, l gitalypb.RemoteService_ListRemotesClient) []*gitalypb.ListRemotesResponse_Remote { + receivedRemotes := []*gitalypb.ListRemotesResponse_Remote{} + for { + resp, err := l.Recv() + if err == io.EOF { + break + } + + require.NoError(t, err) + + receivedRemotes = append(receivedRemotes, resp.GetRemotes()...) + } + + return receivedRemotes +} |