Commit e231f245 authored by Alejandro Rodríguez's avatar Alejandro Rodríguez

Implement Gitaly call for archive requests

parent a531ccd5
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
Formerly known as 'gitlab-git-http-server'. Formerly known as 'gitlab-git-http-server'.
UNRELEASED
- Implement Gitaly call for archive requests !199
v3.1.0 v3.1.0
- Add histograms to routes !184 - Add histograms to routes !184
......
...@@ -371,6 +371,62 @@ func TestGetBlobProxiedToGitalyInterruptedStream(t *testing.T) { ...@@ -371,6 +371,62 @@ func TestGetBlobProxiedToGitalyInterruptedStream(t *testing.T) {
} }
} }
func TestGetArchiveProxiedToGitalySuccessfully(t *testing.T) {
gitalyServer, socketPath := startGitalyServer(t, codes.OK)
defer gitalyServer.Stop()
gitalyAddress := "unix://" + socketPath
repoStorage := "default"
oid := "54fcc214b94e78d7a41a9a8fe6d87a5e59500e51"
repoRelativePath := "foo/bar.git"
archivePath := "my/path"
archivePrefix := "repo-1"
jsonParams := fmt.Sprintf(`{"GitalyServer":{"Address":"%s","Token":""},"GitalyRepository":{"storage_name":"%s","relative_path":"%s"},"ArchivePath":"%s","ArchivePrefix":"%s","CommitId":"%s"}`,
gitalyAddress, repoStorage, repoRelativePath, archivePath, archivePrefix, oid)
expectedBody := testhelper.GitalyGetArchiveResponseMock
archiveLength := len(expectedBody)
resp, body, err := doSendDataRequest("/archive.tar.gz", "git-archive", jsonParams)
require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode, "GET %q: status code", resp.Request.URL)
assert.Equal(t, expectedBody, string(body), "GET %q: response body", resp.Request.URL)
assert.Equal(t, archiveLength, len(body), "GET %q: body size", resp.Request.URL)
}
func TestGetArchiveProxiedToGitalyInterruptedStream(t *testing.T) {
gitalyServer, socketPath := startGitalyServer(t, codes.OK)
defer gitalyServer.Stop()
gitalyAddress := "unix://" + socketPath
repoStorage := "default"
oid := "54fcc214b94e78d7a41a9a8fe6d87a5e59500e51"
repoRelativePath := "foo/bar.git"
archivePath := "my/path"
archivePrefix := "repo-1"
jsonParams := fmt.Sprintf(`{"GitalyServer":{"Address":"%s","Token":""},"GitalyRepository":{"storage_name":"%s","relative_path":"%s"},"ArchivePath":"%s","ArchivePrefix":"%s","CommitId":"%s"}`,
gitalyAddress, repoStorage, repoRelativePath, archivePath, archivePrefix, oid)
resp, _, err := doSendDataRequest("/archive.tar.gz", "git-archive", jsonParams)
require.NoError(t, err)
// This causes the server stream to be interrupted instead of consumed entirely.
resp.Body.Close()
done := make(chan struct{})
go func() {
gitalyServer.WaitGroup.Wait()
close(done)
}()
select {
case <-done:
return
case <-time.After(10 * time.Second):
t.Fatal("time out waiting for gitaly handler to return")
}
}
type combinedServer struct { type combinedServer struct {
*grpc.Server *grpc.Server
*testhelper.GitalyTestServer *testhelper.GitalyTestServer
...@@ -388,6 +444,7 @@ func startGitalyServer(t *testing.T, finalMessageCode codes.Code) (*combinedServ ...@@ -388,6 +444,7 @@ func startGitalyServer(t *testing.T, finalMessageCode codes.Code) (*combinedServ
gitalyServer := testhelper.NewGitalyServer(finalMessageCode) gitalyServer := testhelper.NewGitalyServer(finalMessageCode)
pb.RegisterSmartHTTPServiceServer(server, gitalyServer) pb.RegisterSmartHTTPServiceServer(server, gitalyServer)
pb.RegisterBlobServiceServer(server, gitalyServer) pb.RegisterBlobServiceServer(server, gitalyServer)
pb.RegisterRepositoryServiceServer(server, gitalyServer)
go server.Serve(listener) go server.Serve(listener)
......
...@@ -14,6 +14,8 @@ import ( ...@@ -14,6 +14,8 @@ import (
"path/filepath" "path/filepath"
"time" "time"
pb "gitlab.com/gitlab-org/gitaly-proto/go"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/gitaly"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/helper" "gitlab.com/gitlab-org/gitlab-workhorse/internal/helper"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/senddata" "gitlab.com/gitlab-org/gitlab-workhorse/internal/senddata"
...@@ -22,10 +24,12 @@ import ( ...@@ -22,10 +24,12 @@ import (
type archive struct{ senddata.Prefix } type archive struct{ senddata.Prefix }
type archiveParams struct { type archiveParams struct {
RepoPath string RepoPath string
ArchivePath string ArchivePath string
ArchivePrefix string ArchivePrefix string
CommitId string CommitId string
GitalyServer gitaly.Server
GitalyRepository pb.Repository
} }
var ( var (
...@@ -84,7 +88,16 @@ func (a *archive) Inject(w http.ResponseWriter, r *http.Request, sendData string ...@@ -84,7 +88,16 @@ func (a *archive) Inject(w http.ResponseWriter, r *http.Request, sendData string
defer tempFile.Close() defer tempFile.Close()
defer os.Remove(tempFile.Name()) defer os.Remove(tempFile.Name())
archiveReader, err := newArchiveReader(r.Context(), params.RepoPath, format, params.ArchivePrefix, params.CommitId) var archiveReader io.Reader
if params.GitalyServer.Address != "" {
archiveReader, err = handleArchiveWithGitaly(r, params, format)
if err != nil {
err = fmt.Errorf("operations.GetArchive: %v", err)
}
} else {
archiveReader, err = newArchiveReader(r.Context(), params.RepoPath, format, params.ArchivePrefix, params.CommitId)
}
if err != nil { if err != nil {
helper.Fail500(w, r, err) helper.Fail500(w, r, err)
return return
...@@ -106,10 +119,26 @@ func (a *archive) Inject(w http.ResponseWriter, r *http.Request, sendData string ...@@ -106,10 +119,26 @@ func (a *archive) Inject(w http.ResponseWriter, r *http.Request, sendData string
} }
} }
func setArchiveHeaders(w http.ResponseWriter, format ArchiveFormat, archiveFilename string) { func handleArchiveWithGitaly(r *http.Request, params archiveParams, format pb.GetArchiveRequest_Format) (io.Reader, error) {
c, err := gitaly.NewRepositoryClient(params.GitalyServer)
if err != nil {
return nil, err
}
request := &pb.GetArchiveRequest{
Repository: &params.GitalyRepository,
CommitId: params.CommitId,
Prefix: params.ArchivePrefix,
Format: format,
}
return c.ArchiveReader(r.Context(), request)
}
func setArchiveHeaders(w http.ResponseWriter, format pb.GetArchiveRequest_Format, archiveFilename string) {
w.Header().Del("Content-Length") w.Header().Del("Content-Length")
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, archiveFilename)) w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, archiveFilename))
if format == ZipFormat { if format == pb.GetArchiveRequest_ZIP {
w.Header().Set("Content-Type", "application/zip") w.Header().Set("Content-Type", "application/zip")
} else { } else {
w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Type", "application/octet-stream")
...@@ -136,20 +165,20 @@ func finalizeCachedArchive(tempFile *os.File, archivePath string) error { ...@@ -136,20 +165,20 @@ func finalizeCachedArchive(tempFile *os.File, archivePath string) error {
return nil return nil
} }
func parseBasename(basename string) (ArchiveFormat, bool) { func parseBasename(basename string) (pb.GetArchiveRequest_Format, bool) {
var format ArchiveFormat var format pb.GetArchiveRequest_Format
switch basename { switch basename {
case "archive.zip": case "archive.zip":
format = ZipFormat format = pb.GetArchiveRequest_ZIP
case "archive.tar": case "archive.tar":
format = TarFormat format = pb.GetArchiveRequest_TAR
case "archive", "archive.tar.gz", "archive.tgz", "archive.gz": case "archive", "archive.tar.gz", "archive.tgz", "archive.gz":
format = TarGzFormat format = pb.GetArchiveRequest_TAR_GZ
case "archive.tar.bz2", "archive.tbz", "archive.tbz2", "archive.tb2", "archive.bz2": case "archive.tar.bz2", "archive.tbz", "archive.tbz2", "archive.tb2", "archive.bz2":
format = TarBz2Format format = pb.GetArchiveRequest_TAR_BZ2
default: default:
return InvalidFormat, false return format, false
} }
return format, true return format, true
......
...@@ -5,23 +5,24 @@ import ( ...@@ -5,23 +5,24 @@ import (
"net/http/httptest" "net/http/httptest"
"testing" "testing"
pb "gitlab.com/gitlab-org/gitaly-proto/go"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/testhelper" "gitlab.com/gitlab-org/gitlab-workhorse/internal/testhelper"
) )
func TestParseBasename(t *testing.T) { func TestParseBasename(t *testing.T) {
for _, testCase := range []struct { for _, testCase := range []struct {
in string in string
out ArchiveFormat out pb.GetArchiveRequest_Format
}{ }{
{"", TarGzFormat}, {"", pb.GetArchiveRequest_TAR_GZ},
{".tar.gz", TarGzFormat}, {".tar.gz", pb.GetArchiveRequest_TAR_GZ},
{".tgz", TarGzFormat}, {".tgz", pb.GetArchiveRequest_TAR_GZ},
{".gz", TarGzFormat}, {".gz", pb.GetArchiveRequest_TAR_GZ},
{".tar.bz2", TarBz2Format}, {".tar.bz2", pb.GetArchiveRequest_TAR_BZ2},
{".tbz", TarBz2Format}, {".tbz", pb.GetArchiveRequest_TAR_BZ2},
{".tbz2", TarBz2Format}, {".tbz2", pb.GetArchiveRequest_TAR_BZ2},
{".tb2", TarBz2Format}, {".tb2", pb.GetArchiveRequest_TAR_BZ2},
{".bz2", TarBz2Format}, {".bz2", pb.GetArchiveRequest_TAR_BZ2},
} { } {
basename := "archive" + testCase.in basename := "archive" + testCase.in
out, ok := parseBasename(basename) out, ok := parseBasename(basename)
...@@ -51,12 +52,13 @@ func TestFinalizeArchive(t *testing.T) { ...@@ -51,12 +52,13 @@ func TestFinalizeArchive(t *testing.T) {
func TestSetArchiveHeaders(t *testing.T) { func TestSetArchiveHeaders(t *testing.T) {
for _, testCase := range []struct { for _, testCase := range []struct {
in ArchiveFormat in pb.GetArchiveRequest_Format
out string out string
}{ }{
{ZipFormat, "application/zip"}, {pb.GetArchiveRequest_ZIP, "application/zip"},
{TarFormat, "application/octet-stream"}, {pb.GetArchiveRequest_TAR, "application/octet-stream"},
{InvalidFormat, "application/octet-stream"}, {pb.GetArchiveRequest_TAR_GZ, "application/octet-stream"},
{pb.GetArchiveRequest_TAR_BZ2, "application/octet-stream"},
} { } {
w := httptest.NewRecorder() w := httptest.NewRecorder()
......
...@@ -7,28 +7,19 @@ import ( ...@@ -7,28 +7,19 @@ import (
"os/exec" "os/exec"
"syscall" "syscall"
pb "gitlab.com/gitlab-org/gitaly-proto/go"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/helper" "gitlab.com/gitlab-org/gitlab-workhorse/internal/helper"
) )
type ArchiveFormat int func parseArchiveFormat(format pb.GetArchiveRequest_Format) (*exec.Cmd, string) {
const (
InvalidFormat ArchiveFormat = iota
ZipFormat
TarFormat
TarGzFormat
TarBz2Format
)
func parseArchiveFormat(format ArchiveFormat) (*exec.Cmd, string) {
switch format { switch format {
case TarFormat: case pb.GetArchiveRequest_TAR:
return nil, "tar" return nil, "tar"
case TarGzFormat: case pb.GetArchiveRequest_TAR_GZ:
return exec.Command("gzip", "-c", "-n"), "tar" return exec.Command("gzip", "-c", "-n"), "tar"
case TarBz2Format: case pb.GetArchiveRequest_TAR_BZ2:
return exec.Command("bzip2", "-c"), "tar" return exec.Command("bzip2", "-c"), "tar"
case ZipFormat: case pb.GetArchiveRequest_ZIP:
return nil, "zip" return nil, "zip"
default: default:
return nil, "invalid format" return nil, "invalid format"
...@@ -70,7 +61,7 @@ func (a *archiveReader) wait() error { ...@@ -70,7 +61,7 @@ func (a *archiveReader) wait() error {
return nil return nil
} }
func newArchiveReader(ctx context.Context, repoPath string, format ArchiveFormat, archivePrefix string, commitId string) (a *archiveReader, err error) { func newArchiveReader(ctx context.Context, repoPath string, format pb.GetArchiveRequest_Format, archivePrefix string, commitId string) (a *archiveReader, err error) {
a = &archiveReader{} a = &archiveReader{}
compressCmd, formatArg := parseArchiveFormat(format) compressCmd, formatArg := parseArchiveFormat(format)
......
...@@ -41,6 +41,15 @@ func NewBlobClient(server Server) (*BlobClient, error) { ...@@ -41,6 +41,15 @@ func NewBlobClient(server Server) (*BlobClient, error) {
return &BlobClient{grpcClient}, nil return &BlobClient{grpcClient}, nil
} }
func NewRepositoryClient(server Server) (*RepositoryClient, error) {
conn, err := getOrCreateConnection(server)
if err != nil {
return nil, err
}
grpcClient := pb.NewRepositoryServiceClient(conn)
return &RepositoryClient{grpcClient}, nil
}
func getOrCreateConnection(server Server) (*grpc.ClientConn, error) { func getOrCreateConnection(server Server) (*grpc.ClientConn, error) {
cache.RLock() cache.RLock()
conn := cache.connections[server] conn := cache.connections[server]
......
package gitaly
import (
"context"
"fmt"
"io"
pb "gitlab.com/gitlab-org/gitaly-proto/go"
"gitlab.com/gitlab-org/gitaly/streamio"
)
// RepositoryClient encapsulates RepositoryService calls
type RepositoryClient struct {
pb.RepositoryServiceClient
}
// ArchiveReader performs a GetArchive Gitaly request and returns an io.Reader
// for the response
func (client *RepositoryClient) ArchiveReader(ctx context.Context, request *pb.GetArchiveRequest) (io.Reader, error) {
c, err := client.GetArchive(ctx, request)
if err != nil {
return nil, fmt.Errorf("RepositoryService::GetArchive: %v", err)
}
return streamio.NewReader(func() ([]byte, error) {
resp, err := c.Recv()
return resp.GetData(), err
}), nil
}
...@@ -24,6 +24,7 @@ type GitalyTestServer struct { ...@@ -24,6 +24,7 @@ type GitalyTestServer struct {
var ( var (
GitalyInfoRefsResponseMock = strings.Repeat("Mock Gitaly InfoRefsResponse data", 100000) GitalyInfoRefsResponseMock = strings.Repeat("Mock Gitaly InfoRefsResponse data", 100000)
GitalyGetBlobResponseMock = strings.Repeat("Mock Gitaly GetBlobResponse data", 100000) GitalyGetBlobResponseMock = strings.Repeat("Mock Gitaly GetBlobResponse data", 100000)
GitalyGetArchiveResponseMock = strings.Repeat("Mock Gitaly GetArchiveResponse data", 100000)
GitalyReceivePackResponseMock []byte GitalyReceivePackResponseMock []byte
GitalyUploadPackResponseMock []byte GitalyUploadPackResponseMock []byte
) )
...@@ -208,6 +209,63 @@ func (s *GitalyTestServer) GetBlob(in *pb.GetBlobRequest, stream pb.BlobService_ ...@@ -208,6 +209,63 @@ func (s *GitalyTestServer) GetBlob(in *pb.GetBlobRequest, stream pb.BlobService_
return s.finalError() return s.finalError()
} }
func (s *GitalyTestServer) GetArchive(in *pb.GetArchiveRequest, stream pb.RepositoryService_GetArchiveServer) error {
s.WaitGroup.Add(1)
defer s.WaitGroup.Done()
if err := validateRepository(in.GetRepository()); err != nil {
return err
}
nSends, err := sendBytes([]byte(GitalyGetArchiveResponseMock), 100, func(p []byte) error {
return stream.Send(&pb.GetArchiveResponse{Data: p})
})
if err != nil {
return err
}
if nSends <= 1 {
panic("should have sent more than one message")
}
return s.finalError()
}
func (s *GitalyTestServer) RepositoryExists(context.Context, *pb.RepositoryExistsRequest) (*pb.RepositoryExistsResponse, error) {
return nil, nil
}
func (s *GitalyTestServer) RepackIncremental(context.Context, *pb.RepackIncrementalRequest) (*pb.RepackIncrementalResponse, error) {
return nil, nil
}
func (s *GitalyTestServer) RepackFull(context.Context, *pb.RepackFullRequest) (*pb.RepackFullResponse, error) {
return nil, nil
}
func (s *GitalyTestServer) GarbageCollect(context.Context, *pb.GarbageCollectRequest) (*pb.GarbageCollectResponse, error) {
return nil, nil
}
func (s *GitalyTestServer) RepositorySize(context.Context, *pb.RepositorySizeRequest) (*pb.RepositorySizeResponse, error) {
return nil, nil
}
func (s *GitalyTestServer) ApplyGitattributes(context.Context, *pb.ApplyGitattributesRequest) (*pb.ApplyGitattributesResponse, error) {
return nil, nil
}
func (s *GitalyTestServer) FetchRemote(context.Context, *pb.FetchRemoteRequest) (*pb.FetchRemoteResponse, error) {
return nil, nil
}
func (s *GitalyTestServer) CreateRepository(context.Context, *pb.CreateRepositoryRequest) (*pb.CreateRepositoryResponse, error) {
return nil, nil
}
func (s *GitalyTestServer) Exists(context.Context, *pb.RepositoryExistsRequest) (*pb.RepositoryExistsResponse, error) {
return nil, nil
}
// sendBytes returns the number of times the 'sender' function was called and an error. // sendBytes returns the number of times the 'sender' function was called and an error.
func sendBytes(data []byte, chunkSize int, sender func([]byte) error) (int, error) { func sendBytes(data []byte, chunkSize int, sender func([]byte) error) (int, error) {
i := 0 i := 0
......
...@@ -69,8 +69,12 @@ It has these top-level messages: ...@@ -69,8 +69,12 @@ It has these top-level messages:
PostReceiveResponse PostReceiveResponse
UserCreateBranchRequest UserCreateBranchRequest
UserCreateBranchResponse UserCreateBranchResponse
UserDeleteBranchRequest
UserDeleteBranchResponse
UserDeleteTagRequest UserDeleteTagRequest
UserDeleteTagResponse UserDeleteTagResponse
UserCreateTagRequest
UserCreateTagResponse
FindDefaultBranchNameRequest FindDefaultBranchNameRequest
FindDefaultBranchNameResponse FindDefaultBranchNameResponse
FindAllBranchNamesRequest FindAllBranchNamesRequest
...@@ -111,11 +115,14 @@ It has these top-level messages: ...@@ -111,11 +115,14 @@ It has these top-level messages:
FetchRemoteResponse FetchRemoteResponse
CreateRepositoryRequest CreateRepositoryRequest
CreateRepositoryResponse CreateRepositoryResponse
GetArchiveRequest
GetArchiveResponse
Repository Repository
GitCommit GitCommit
CommitAuthor CommitAuthor
ExitStatus ExitStatus
Branch Branch
Tag
User User
InfoRefsRequest InfoRefsRequest
InfoRefsResponse InfoRefsResponse
......
...@@ -197,6 +197,46 @@ func (m *Branch) GetTargetCommit() *GitCommit { ...@@ -197,6 +197,46 @@ func (m *Branch) GetTargetCommit() *GitCommit {
return nil return nil
} }
type Tag struct {
Name []byte `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Id string `protobuf:"bytes,2,opt,name=id" json:"id,omitempty"`
TargetCommit *GitCommit `protobuf:"bytes,3,opt,name=target_commit,json=targetCommit" json:"target_commit,omitempty"`
Message []byte `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"`
}
func (m *Tag) Reset() { *m = Tag{} }
func (m *Tag) String() string { return proto.CompactTextString(m) }
func (*Tag) ProtoMessage() {}
func (*Tag) Descriptor() ([]byte, []int) { return fileDescriptor9, []int{5} }
func (m *Tag) GetName() []byte {
if m != nil {
return m.Name
}
return nil
}
func (m *Tag) GetId() string {
if m != nil {
return m.Id
}
return ""
}
func (m *Tag) GetTargetCommit() *GitCommit {
if m != nil {
return m.TargetCommit
}
return nil
}
func (m *Tag) GetMessage() []byte {
if m != nil {
return m.Message
}
return nil
}
type User struct { type User struct {
GlId string `protobuf:"bytes,1,opt,name=gl_id,json=glId" json:"gl_id,omitempty"` GlId string `protobuf:"bytes,1,opt,name=gl_id,json=glId" json:"gl_id,omitempty"`
Name []byte `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Name []byte `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
...@@ -206,7 +246,7 @@ type User struct { ...@@ -206,7 +246,7 @@ type User struct {
func (m *User) Reset() { *m = User{} } func (m *User) Reset() { *m = User{} }
func (m *User) String() string { return proto.CompactTextString(m) } func (m *User) String() string { return proto.CompactTextString(m) }
func (*User) ProtoMessage() {} func (*User) ProtoMessage() {}
func (*User) Descriptor() ([]byte, []int) { return fileDescriptor9, []int{5} } func (*User) Descriptor() ([]byte, []int) { return fileDescriptor9, []int{6} }
func (m *User) GetGlId() string { func (m *User) GetGlId() string {
if m != nil { if m != nil {
...@@ -235,41 +275,44 @@ func init() { ...@@ -235,41 +275,44 @@ func init() {
proto.RegisterType((*CommitAuthor)(nil), "gitaly.CommitAuthor") proto.RegisterType((*CommitAuthor)(nil), "gitaly.CommitAuthor")
proto.RegisterType((*ExitStatus)(nil), "gitaly.ExitStatus") proto.RegisterType((*ExitStatus)(nil), "gitaly.ExitStatus")
proto.RegisterType((*Branch)(nil), "gitaly.Branch") proto.RegisterType((*Branch)(nil), "gitaly.Branch")
proto.RegisterType((*Tag)(nil), "gitaly.Tag")
proto.RegisterType((*User)(nil), "gitaly.User") proto.RegisterType((*User)(nil), "gitaly.User")
} }
func init() { proto.RegisterFile("shared.proto", fileDescriptor9) } func init() { proto.RegisterFile("shared.proto", fileDescriptor9) }
var fileDescriptor9 = []byte{ var fileDescriptor9 = []byte{
// 468 bytes of a gzipped FileDescriptorProto // 500 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x52, 0xc1, 0x6e, 0xd3, 0x40, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x52, 0xc1, 0x6e, 0xd3, 0x40,
0x10, 0x55, 0x1c, 0xc7, 0xe0, 0x89, 0x8b, 0x60, 0xc9, 0xc1, 0xaa, 0x54, 0x11, 0xcc, 0xa5, 0x07, 0x10, 0x55, 0x1c, 0xc7, 0x90, 0x89, 0x8b, 0x60, 0xe9, 0xc1, 0xaa, 0x54, 0x11, 0xcc, 0xa5, 0x07,
0xe4, 0xa2, 0x20, 0x71, 0x2f, 0x50, 0x55, 0xe5, 0x00, 0x68, 0x29, 0x67, 0x6b, 0x13, 0x0f, 0xeb, 0xe4, 0xa2, 0x20, 0x71, 0x2f, 0x50, 0x55, 0xe5, 0x00, 0x68, 0x09, 0x67, 0x6b, 0x12, 0x0f, 0xeb,
0x45, 0xeb, 0xac, 0xb5, 0x3b, 0xae, 0xc8, 0x8d, 0xef, 0xe3, 0xab, 0x90, 0x77, 0xe3, 0xb4, 0xa0, 0x45, 0x76, 0x36, 0xda, 0x9d, 0x54, 0x44, 0x5c, 0xf8, 0x3e, 0xbe, 0x0a, 0x79, 0x37, 0x4e, 0x0b,
0xaa, 0xb7, 0x9d, 0xd9, 0xf7, 0x66, 0xde, 0x9b, 0x19, 0xc8, 0x5c, 0x23, 0x2c, 0xd6, 0x65, 0x67, 0x44, 0x88, 0xdb, 0xce, 0xec, 0x9b, 0x99, 0xf7, 0xe6, 0x0d, 0xa4, 0xae, 0x46, 0x4b, 0x55, 0xb1,
0x0d, 0x19, 0x96, 0x48, 0x45, 0x42, 0xef, 0x8e, 0x5f, 0x48, 0x63, 0xa4, 0xc6, 0x33, 0x9f, 0x5d, 0xb6, 0x86, 0x8d, 0x48, 0x94, 0x66, 0x6c, 0xb6, 0x27, 0x4f, 0x94, 0x31, 0xaa, 0xa1, 0x73, 0x9f,
0xf7, 0x3f, 0xce, 0x48, 0xb5, 0xe8, 0x48, 0xb4, 0x5d, 0x00, 0x16, 0xbf, 0x23, 0x00, 0x8e, 0x9d, 0x5d, 0x6c, 0xbe, 0x9c, 0xb3, 0x6e, 0xc9, 0x31, 0xb6, 0xeb, 0x00, 0xcc, 0x7f, 0x44, 0x00, 0x92,
0x71, 0x8a, 0x8c, 0xdd, 0xb1, 0x97, 0x90, 0x39, 0x32, 0x56, 0x48, 0xac, 0xb6, 0xa2, 0xc5, 0x3c, 0xd6, 0xc6, 0x69, 0x36, 0x76, 0x2b, 0x9e, 0x42, 0xea, 0xd8, 0x58, 0x54, 0x54, 0xae, 0xb0, 0xa5,
0x5a, 0x4e, 0x4e, 0x53, 0x3e, 0xdf, 0xe7, 0x3e, 0x8b, 0x16, 0xd9, 0x2b, 0x38, 0xb2, 0xa8, 0x05, 0x2c, 0x9a, 0x0e, 0xce, 0xc6, 0x72, 0xb2, 0xcb, 0xbd, 0xc7, 0x96, 0xc4, 0x33, 0x38, 0xb2, 0xd4,
0xa9, 0x1b, 0xac, 0x3a, 0x41, 0x4d, 0x3e, 0xf5, 0x98, 0x6c, 0x4c, 0x7e, 0x15, 0xd4, 0xb0, 0x37, 0x20, 0xeb, 0x1b, 0x2a, 0xd7, 0xc8, 0x75, 0x36, 0xf4, 0x98, 0xb4, 0x4f, 0x7e, 0x44, 0xae, 0xc5,
0xb0, 0x90, 0x8a, 0x2a, 0xb3, 0xfe, 0x89, 0x1b, 0xaa, 0x6a, 0x65, 0x71, 0x33, 0xd4, 0xcf, 0x63, 0x0b, 0x38, 0x56, 0x9a, 0x4b, 0xb3, 0xf8, 0x4a, 0x4b, 0x2e, 0x2b, 0x6d, 0x69, 0xd9, 0xf5, 0xcf,
0x8f, 0x65, 0x52, 0xd1, 0x17, 0xff, 0xf5, 0x71, 0xfc, 0x61, 0x97, 0xb0, 0x1c, 0x18, 0x42, 0x13, 0x62, 0x8f, 0x15, 0x4a, 0xf3, 0x07, 0xff, 0xf5, 0xb6, 0xff, 0x11, 0x57, 0x30, 0xed, 0x2a, 0xb0,
0xda, 0xad, 0x20, 0xfc, 0x9f, 0xab, 0xd0, 0xe5, 0xb3, 0xe5, 0xf4, 0x34, 0xe5, 0x27, 0x52, 0xd1, 0x61, 0xb2, 0x2b, 0x64, 0xfa, 0xb3, 0x56, 0x93, 0xcb, 0x46, 0xd3, 0xe1, 0xd9, 0x58, 0x9e, 0x2a,
0xf9, 0x08, 0xfb, 0xb7, 0x8c, 0x42, 0x37, 0xe8, 0x93, 0xba, 0xb2, 0x07, 0x4f, 0x79, 0x12, 0xf4, 0xcd, 0x17, 0x3d, 0xec, 0xf7, 0x36, 0x9a, 0x5c, 0xc7, 0x4f, 0x35, 0xa5, 0xdd, 0x6b, 0xca, 0x92,
0x49, 0x7d, 0xeb, 0xf3, 0x53, 0xfc, 0x78, 0xf2, 0x34, 0xe2, 0xf1, 0xa0, 0xbf, 0xf8, 0x33, 0x81, 0xc0, 0x4f, 0x35, 0xb7, 0x3a, 0xdf, 0xc5, 0xf7, 0x07, 0x0f, 0x23, 0x19, 0x77, 0xfc, 0xf3, 0x9f,
0xf4, 0x52, 0xd1, 0x07, 0xd3, 0xb6, 0x8a, 0xd8, 0x13, 0x88, 0x54, 0x9d, 0x4f, 0x3c, 0x27, 0x52, 0x03, 0x18, 0x5f, 0x69, 0x7e, 0x63, 0xda, 0x56, 0xb3, 0x78, 0x00, 0x91, 0xae, 0xb2, 0x81, 0xaf,
0x35, 0xcb, 0xe1, 0x91, 0xeb, 0x7d, 0x13, 0x3f, 0x8c, 0x8c, 0x8f, 0x21, 0x63, 0x10, 0xaf, 0x4d, 0x89, 0x74, 0x25, 0x32, 0xb8, 0xe7, 0x36, 0x7e, 0x88, 0x5f, 0x46, 0x2a, 0xfb, 0x50, 0x08, 0x88,
0xbd, 0xf3, 0xfe, 0x33, 0xee, 0xdf, 0xec, 0x35, 0x24, 0xa2, 0xa7, 0xc6, 0x58, 0xef, 0x74, 0xbe, 0x17, 0xa6, 0xda, 0x7a, 0xfd, 0xa9, 0xf4, 0x6f, 0xf1, 0x1c, 0x12, 0xdc, 0x70, 0x6d, 0xac, 0x57,
0x5a, 0x94, 0x61, 0x11, 0x65, 0xa8, 0x7e, 0xee, 0xff, 0xf8, 0x1e, 0xc3, 0x56, 0x90, 0x6e, 0x7c, 0x3a, 0x99, 0x1d, 0x17, 0xc1, 0x88, 0x22, 0x74, 0xbf, 0xf0, 0x7f, 0x72, 0x87, 0x11, 0x33, 0x18,
0x9e, 0xd0, 0xe6, 0xb3, 0x07, 0x08, 0xb7, 0x30, 0x76, 0x02, 0xd0, 0x09, 0x8b, 0x5b, 0xaa, 0x54, 0x2f, 0x7d, 0x9e, 0xc9, 0x66, 0xa3, 0x7f, 0x14, 0xdc, 0xc2, 0xc4, 0x29, 0xc0, 0x1a, 0x2d, 0xad,
0xed, 0xf2, 0xc4, 0x4f, 0x24, 0x0d, 0x99, 0xab, 0xda, 0x15, 0x0d, 0x64, 0x77, 0x99, 0x83, 0x48, 0xb8, 0xd4, 0x95, 0xcb, 0x12, 0xbf, 0x91, 0x71, 0xc8, 0x5c, 0x57, 0x2e, 0xaf, 0x21, 0xbd, 0x5b,
0xbf, 0xc8, 0x49, 0x10, 0x39, 0xbc, 0xd9, 0x02, 0x66, 0xd8, 0x0a, 0xa5, 0xf7, 0x86, 0x42, 0xc0, 0xd9, 0x91, 0xf4, 0x46, 0x0e, 0x02, 0xc9, 0xee, 0x2d, 0x8e, 0x61, 0x44, 0x2d, 0xea, 0x66, 0x27,
0x4a, 0x88, 0x6b, 0x41, 0xe8, 0xed, 0xcc, 0x57, 0xc7, 0x65, 0xb8, 0x9c, 0x72, 0xbc, 0x9c, 0xf2, 0x28, 0x04, 0xa2, 0x80, 0xb8, 0x42, 0x26, 0x2f, 0x67, 0x32, 0x3b, 0x29, 0xc2, 0xe5, 0x14, 0xfd,
0x7a, 0xbc, 0x1c, 0xee, 0x71, 0x45, 0x01, 0x70, 0xf1, 0x4b, 0xd1, 0x37, 0x12, 0xd4, 0xbb, 0xa1, 0xe5, 0x14, 0xf3, 0xfe, 0x72, 0xa4, 0xc7, 0xe5, 0x39, 0xc0, 0xe5, 0x37, 0xcd, 0x9f, 0x18, 0x79,
0xe6, 0x8d, 0xd0, 0x7d, 0x68, 0x34, 0xe3, 0x21, 0x28, 0xae, 0x21, 0x79, 0x6f, 0xc5, 0x76, 0xd3, 0xe3, 0xba, 0x9e, 0x37, 0xd8, 0x6c, 0xc2, 0xa0, 0x91, 0x0c, 0x41, 0x3e, 0x87, 0xe4, 0xb5, 0xc5,
0xdc, 0xab, 0xe3, 0x1d, 0x1c, 0x91, 0xb0, 0x12, 0xa9, 0x0a, 0xf6, 0xbc, 0x9e, 0xf9, 0xea, 0xd9, 0xd5, 0xb2, 0x3e, 0xc8, 0xe3, 0x15, 0x1c, 0x31, 0x5a, 0x45, 0x5c, 0x06, 0x79, 0x9e, 0xcf, 0x64,
0x38, 0x82, 0xc3, 0x52, 0x78, 0x16, 0x70, 0x21, 0x2a, 0x2e, 0x20, 0xfe, 0xee, 0xd0, 0xb2, 0xe7, 0xf6, 0xa8, 0x5f, 0xc1, 0xde, 0x14, 0x99, 0x06, 0x5c, 0x88, 0xf2, 0xef, 0x30, 0x9c, 0xa3, 0x3a,
0x30, 0x93, 0xba, 0x3a, 0x6c, 0x2b, 0x96, 0xfa, 0xaa, 0x3e, 0x34, 0x8a, 0xee, 0x33, 0x3c, 0xbd, 0xd8, 0x32, 0xb8, 0x17, 0xed, 0xdd, 0xfb, 0x6b, 0xc4, 0xf0, 0xbf, 0x46, 0x74, 0xae, 0xb7, 0xe4,
0x63, 0x78, 0x9d, 0x78, 0x6b, 0x6f, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0x86, 0x24, 0x75, 0x89, 0x1c, 0x2a, 0xf2, 0x46, 0xa6, 0xb2, 0x0f, 0xf3, 0x4b, 0x88, 0x3f, 0x3b, 0xb2, 0xe2, 0x31, 0x8c,
0x3a, 0x03, 0x00, 0x00, 0x54, 0x53, 0xee, 0x4f, 0x25, 0x56, 0xcd, 0x75, 0xb5, 0xa7, 0x14, 0x1d, 0xda, 0xf6, 0xf0, 0xce,
0xb6, 0x17, 0x89, 0xdf, 0xeb, 0xcb, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xaa, 0x7f, 0xf5, 0xea,
0xb7, 0x03, 0x00, 0x00,
} }
...@@ -152,12 +152,12 @@ ...@@ -152,12 +152,12 @@
"revisionTime": "2016-11-17T07:43:51Z" "revisionTime": "2016-11-17T07:43:51Z"
}, },
{ {
"checksumSHA1": "A2jWY7L3EazZt0xdKFKMDOGXCdk=", "checksumSHA1": "tisAil16tojFqhqWYbs2kXwBYyk=",
"path": "gitlab.com/gitlab-org/gitaly-proto/go", "path": "gitlab.com/gitlab-org/gitaly-proto/go",
"revision": "b61fee8cd76e282d15a3c719f7f71a4f71ef0d6c", "revision": "12872bd8dad9dc72328b2c590386e67a17c65612",
"revisionTime": "2017-09-20T19:16:33Z", "revisionTime": "2017-09-27T21:53:01Z",
"version": "v0.35.0", "version": "v0.38.0",
"versionExact": "v0.35.0" "versionExact": "v0.38.0"
}, },
{ {
"checksumSHA1": "dUHJbKas746n5fLzlwxHb6FOCxs=", "checksumSHA1": "dUHJbKas746n5fLzlwxHb6FOCxs=",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment