diff options
author | Santiago M. Mola <santi@mola.io> | 2017-01-04 11:18:41 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-01-04 11:18:41 +0100 |
commit | 841abfb7dc640755c443432064252907e3e55c95 (patch) | |
tree | 8af69dcd3b301a10a3e493e2cd805cdec6dcaecd | |
parent | 90d67bb648ae32d5b1a0f7b1af011da6dfb24315 (diff) | |
download | go-git-841abfb7dc640755c443432064252907e3e55c95.tar.gz |
server: add git server implementation (#190)
* server: add generic server implementation (transport-independent),
both for git-upload-pack and git-receive-pack.
* server: move internal functions to internal/common.
* cli: add git-receive-pack and git-upload-pack implementations.
* format/packfile: add UpdateObjectStorage function, extracted from
Remote.
* transport: implement tranport RPC-like, only with git-upload-pack and
git-receive-pack methods. Client renamed to Transport.
* storer: add storer.Storer interface.
* protocol/packp: add UploadPackResponse constructor with packfile.
* protocol/packp: fix UploadPackResponse encoding, add tests.
* protocol/packp/capability: implement All.
43 files changed, 1641 insertions, 308 deletions
diff --git a/cli/go-git/main.go b/cli/go-git/main.go new file mode 100644 index 0000000..97b8c3e --- /dev/null +++ b/cli/go-git/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "os" + "path/filepath" + + "github.com/jessevdk/go-flags" +) + +const ( + bin = "go-git" + receivePackBin = "git-receive-pack" + uploadPackBin = "git-upload-pack" +) + +func main() { + switch filepath.Base(os.Args[0]) { + case receivePackBin: + os.Args = append([]string{"git", "receive-pack"}, os.Args[1:]...) + case uploadPackBin: + os.Args = append([]string{"git", "upload-pack"}, os.Args[1:]...) + } + + parser := flags.NewNamedParser(bin, flags.Default) + parser.AddCommand("receive-pack", "", "", &CmdReceivePack{}) + parser.AddCommand("upload-pack", "", "", &CmdUploadPack{}) + parser.AddCommand("version", "Show the version information.", "", &CmdVersion{}) + + _, err := parser.Parse() + if err != nil { + if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrCommandRequired { + parser.WriteHelp(os.Stdout) + } + + os.Exit(1) + } +} + +type cmd struct { + Verbose bool `short:"v" description:"Activates the verbose mode"` +} diff --git a/cli/go-git/receive_pack.go b/cli/go-git/receive_pack.go new file mode 100644 index 0000000..0f96a28 --- /dev/null +++ b/cli/go-git/receive_pack.go @@ -0,0 +1,36 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + + "gopkg.in/src-d/go-git.v4/plumbing/transport/file" +) + +type CmdReceivePack struct { + cmd + + Args struct { + GitDir string `positional-arg-name:"git-dir" required:"true"` + } `positional-args:"yes"` +} + +func (CmdReceivePack) Usage() string { + //TODO: git-receive-pack returns error code 129 if arguments are invalid. + return fmt.Sprintf("usage: %s <git-dir>", os.Args[0]) +} + +func (c *CmdReceivePack) Execute(args []string) error { + gitDir, err := filepath.Abs(c.Args.GitDir) + if err != nil { + return err + } + + if err := file.ServeReceivePack(gitDir); err != nil { + fmt.Fprintln(os.Stderr, "ERR:", err) + os.Exit(128) + } + + return nil +} diff --git a/cli/go-git/upload_pack.go b/cli/go-git/upload_pack.go new file mode 100644 index 0000000..c45fe48 --- /dev/null +++ b/cli/go-git/upload_pack.go @@ -0,0 +1,37 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + + "gopkg.in/src-d/go-git.v4/plumbing/transport/file" +) + +//TODO: usage: git upload-pack [--strict] [--timeout=<n>] <dir> +type CmdUploadPack struct { + cmd + + Args struct { + GitDir string `positional-arg-name:"git-dir" required:"true"` + } `positional-args:"yes"` +} + +func (CmdUploadPack) Usage() string { + //TODO: git-upload-pack returns error code 129 if arguments are invalid. + return fmt.Sprintf("usage: %s <git-dir>", os.Args[0]) +} + +func (c *CmdUploadPack) Execute(args []string) error { + gitDir, err := filepath.Abs(c.Args.GitDir) + if err != nil { + return err + } + + if err := file.ServeUploadPack(gitDir); err != nil { + fmt.Fprintln(os.Stderr, "ERR:", err) + os.Exit(128) + } + + return nil +} diff --git a/cli/go-git/version.go b/cli/go-git/version.go new file mode 100644 index 0000000..b0e773d --- /dev/null +++ b/cli/go-git/version.go @@ -0,0 +1,14 @@ +package main + +import "fmt" + +var version string +var build string + +type CmdVersion struct{} + +func (c *CmdVersion) Execute(args []string) error { + fmt.Printf("%s (%s) - build %s\n", bin, version, build) + + return nil +} diff --git a/plumbing/format/packfile/common.go b/plumbing/format/packfile/common.go index 1656551..7da2b33 100644 --- a/plumbing/format/packfile/common.go +++ b/plumbing/format/packfile/common.go @@ -1,5 +1,11 @@ package packfile +import ( + "io" + + "gopkg.in/src-d/go-git.v4/plumbing/storer" +) + var signature = []byte{'P', 'A', 'C', 'K'} const ( @@ -13,3 +19,27 @@ const ( maskLength = uint8(127) // 0111 1111 maskType = uint8(112) // 0111 0000 ) + +// UpdateObjectStorage updates the given ObjectStorer with the contents of the +// packfile. +func UpdateObjectStorage(s storer.EncodedObjectStorer, packfile io.Reader) error { + if sw, ok := s.(storer.PackfileWriter); ok { + w, err := sw.PackfileWriter() + if err != nil { + return err + } + + defer w.Close() + _, err = io.Copy(w, packfile) + return err + } + + stream := NewScanner(packfile) + d, err := NewDecoder(stream, s) + if err != nil { + return err + } + + _, err = d.Decode() + return err +} diff --git a/plumbing/protocol/packp/capability/list.go b/plumbing/protocol/packp/capability/list.go index 2847580..69fdb51 100644 --- a/plumbing/protocol/packp/capability/list.go +++ b/plumbing/protocol/packp/capability/list.go @@ -167,6 +167,16 @@ func (l *List) Delete(capability Capability) { } } +// All returns a slice with all defined capabilities. +func (l *List) All() []Capability { + var cs []Capability + for _, key := range l.sort { + cs = append(cs, Capability(key)) + } + + return cs +} + // String generates the capabilities strings, the capabilities are sorted in // insertion order func (l *List) String() string { diff --git a/plumbing/protocol/packp/capability/list_test.go b/plumbing/protocol/packp/capability/list_test.go index 42f0179..0a0ad26 100644 --- a/plumbing/protocol/packp/capability/list_test.go +++ b/plumbing/protocol/packp/capability/list_test.go @@ -171,3 +171,14 @@ func (s *SuiteCapabilities) TestAddErrMultipleArgumentsAtTheSameTime(c *check.C) err := cap.Add(Agent, "foo", "bar") c.Assert(err, check.Equals, ErrMultipleArguments) } + +func (s *SuiteCapabilities) TestAll(c *check.C) { + cap := NewList() + c.Assert(NewList().All(), check.IsNil) + + cap.Add(Agent, "foo") + c.Assert(cap.All(), check.DeepEquals, []Capability{Agent}) + + cap.Add(OFSDelta) + c.Assert(cap.All(), check.DeepEquals, []Capability{Agent, OFSDelta}) +} diff --git a/plumbing/protocol/packp/shallowupd.go b/plumbing/protocol/packp/shallowupd.go index 89063de..40f58e8 100644 --- a/plumbing/protocol/packp/shallowupd.go +++ b/plumbing/protocol/packp/shallowupd.go @@ -24,6 +24,7 @@ func (r *ShallowUpdate) Decode(reader io.Reader) error { for s.Scan() { line := s.Bytes() + line = bytes.TrimSpace(line) var err error switch { @@ -71,3 +72,21 @@ func (r *ShallowUpdate) decodeLine(line, prefix []byte, expLen int) (plumbing.Ha raw := string(line[expLen-40 : expLen]) return plumbing.NewHash(raw), nil } + +func (r *ShallowUpdate) Encode(w io.Writer) error { + e := pktline.NewEncoder(w) + + for _, h := range r.Shallows { + if err := e.Encodef("%s%s\n", shallow, h.String()); err != nil { + return err + } + } + + for _, h := range r.Unshallows { + if err := e.Encodef("%s%s\n", unshallow, h.String()); err != nil { + return err + } + } + + return e.Flush() +} diff --git a/plumbing/protocol/packp/shallowupd_test.go b/plumbing/protocol/packp/shallowupd_test.go index d64fb5d..97a13fc 100644 --- a/plumbing/protocol/packp/shallowupd_test.go +++ b/plumbing/protocol/packp/shallowupd_test.go @@ -12,6 +12,26 @@ type ShallowUpdateSuite struct{} var _ = Suite(&ShallowUpdateSuite{}) +func (s *ShallowUpdateSuite) TestDecodeWithLF(c *C) { + raw := "" + + "0035shallow aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + + "0035shallow bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n" + + "0000" + + su := &ShallowUpdate{} + err := su.Decode(bytes.NewBufferString(raw)) + c.Assert(err, IsNil) + + plumbing.HashesSort(su.Shallows) + + c.Assert(su.Unshallows, HasLen, 0) + c.Assert(su.Shallows, HasLen, 2) + c.Assert(su.Shallows, DeepEquals, []plumbing.Hash{ + plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + plumbing.NewHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), + }) +} + func (s *ShallowUpdateSuite) TestDecode(c *C) { raw := "" + "0034shallow aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + @@ -61,3 +81,70 @@ func (s *ShallowUpdateSuite) TestDecodeMalformed(c *C) { err := su.Decode(bytes.NewBufferString(raw)) c.Assert(err, NotNil) } + +func (s *ShallowUpdateSuite) TestEncodeEmpty(c *C) { + su := &ShallowUpdate{} + buf := bytes.NewBuffer(nil) + c.Assert(su.Encode(buf), IsNil) + c.Assert(buf.String(), Equals, "0000") +} + +func (s *ShallowUpdateSuite) TestEncode(c *C) { + su := &ShallowUpdate{ + Shallows: []plumbing.Hash{ + plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + plumbing.NewHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), + }, + Unshallows: []plumbing.Hash{ + plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + plumbing.NewHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), + }, + } + buf := bytes.NewBuffer(nil) + c.Assert(su.Encode(buf), IsNil) + + expected := "" + + "0035shallow aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + + "0035shallow bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n" + + "0037unshallow aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + + "0037unshallow bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n" + + "0000" + + c.Assert(buf.String(), Equals, expected) +} + +func (s *ShallowUpdateSuite) TestEncodeShallow(c *C) { + su := &ShallowUpdate{ + Shallows: []plumbing.Hash{ + plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + plumbing.NewHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), + }, + } + buf := bytes.NewBuffer(nil) + c.Assert(su.Encode(buf), IsNil) + + expected := "" + + "0035shallow aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + + "0035shallow bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n" + + "0000" + + c.Assert(buf.String(), Equals, expected) +} + +func (s *ShallowUpdateSuite) TestEncodeUnshallow(c *C) { + su := &ShallowUpdate{ + Unshallows: []plumbing.Hash{ + plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + plumbing.NewHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), + }, + } + buf := bytes.NewBuffer(nil) + c.Assert(su.Encode(buf), IsNil) + + expected := "" + + "0037unshallow aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + + "0037unshallow bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n" + + "0000" + + c.Assert(buf.String(), Equals, expected) +} diff --git a/plumbing/protocol/packp/srvresp.go b/plumbing/protocol/packp/srvresp.go index 3284fa2..d41b50d 100644 --- a/plumbing/protocol/packp/srvresp.go +++ b/plumbing/protocol/packp/srvresp.go @@ -68,3 +68,17 @@ func (r *ServerResponse) decodeACKLine(line []byte) error { r.ACKs = append(r.ACKs, h) return nil } + +// Encode encodes the ServerResponse into a writer. +func (r *ServerResponse) Encode(w io.Writer) error { + if len(r.ACKs) > 1 { + return errors.New("multi_ack and multi_ack_detailed are not supported") + } + + e := pktline.NewEncoder(w) + if len(r.ACKs) == 0 { + return e.Encodef("%s\n", nak) + } + + return e.Encodef("%s %s\n", ack, r.ACKs[0].String()) +} diff --git a/plumbing/protocol/packp/updreq_decode.go b/plumbing/protocol/packp/updreq_decode.go index 51e8183..c15d49c 100644 --- a/plumbing/protocol/packp/updreq_decode.go +++ b/plumbing/protocol/packp/updreq_decode.go @@ -225,7 +225,7 @@ func parseCommand(b []byte) (*Command, error) { return nil, errInvalidNewObjId(err) } - return &Command{Old: oh, New: nh, Name: n}, nil + return &Command{Old: oh, New: nh, Name: plumbing.ReferenceName(n)}, nil } func parseHash(s string) (plumbing.Hash, error) { diff --git a/plumbing/protocol/packp/uppackresp.go b/plumbing/protocol/packp/uppackresp.go index a117956..ac456f3 100644 --- a/plumbing/protocol/packp/uppackresp.go +++ b/plumbing/protocol/packp/uppackresp.go @@ -5,6 +5,7 @@ import ( "io" "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability" + "gopkg.in/src-d/go-git.v4/utils/ioutil" ) // ErrUploadPackResponseNotDecoded is returned if Read is called without @@ -17,8 +18,8 @@ var ErrUploadPackResponseNotDecoded = errors.New("upload-pack-response should be type UploadPackResponse struct { ShallowUpdate ServerResponse - r io.ReadCloser + r io.ReadCloser isShallow bool isMultiACK bool isOk bool @@ -37,6 +38,16 @@ func NewUploadPackResponse(req *UploadPackRequest) *UploadPackResponse { } } +// NewUploadPackResponseWithPackfile creates a new UploadPackResponse instance, +// and sets its packfile reader. +func NewUploadPackResponseWithPackfile(req *UploadPackRequest, + pf io.ReadCloser) *UploadPackResponse { + + r := NewUploadPackResponse(req) + r.r = pf + return r +} + // Decode decodes all the responses sent by upload-pack service into the struct // and prepares it to read the packfile using the Read method func (r *UploadPackResponse) Decode(reader io.ReadCloser) error { @@ -56,6 +67,23 @@ func (r *UploadPackResponse) Decode(reader io.ReadCloser) error { return nil } +// Encode encodes an UploadPackResponse. +func (r *UploadPackResponse) Encode(w io.Writer) (err error) { + if r.isShallow { + if err := r.ShallowUpdate.Encode(w); err != nil { + return err + } + } + + if err := r.ServerResponse.Encode(w); err != nil { + return err + } + + defer ioutil.CheckClose(r.r, &err) + _, err = io.Copy(w, r.r) + return err +} + // Read reads the packfile data, if the request was done with any Sideband // capability the content read should be demultiplexed. If the methods wasn't // called before the ErrUploadPackResponseNotDecoded will be return diff --git a/plumbing/protocol/packp/uppackresp_test.go b/plumbing/protocol/packp/uppackresp_test.go index c81bb76..c27fdda 100644 --- a/plumbing/protocol/packp/uppackresp_test.go +++ b/plumbing/protocol/packp/uppackresp_test.go @@ -7,6 +7,7 @@ import ( "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability" . "gopkg.in/check.v1" + "gopkg.in/src-d/go-git.v4/plumbing" ) type UploadPackResponseSuite struct{} @@ -80,3 +81,46 @@ func (s *UploadPackResponseSuite) TestReadNoDecode(c *C) { c.Assert(err, Equals, ErrUploadPackResponseNotDecoded) c.Assert(n, Equals, 0) } + +func (s *UploadPackResponseSuite) TestEncodeNAK(c *C) { + pf := ioutil.NopCloser(bytes.NewBuffer([]byte("[PACK]"))) + req := NewUploadPackRequest() + res := NewUploadPackResponseWithPackfile(req, pf) + defer func() { c.Assert(res.Close(), IsNil) }() + + b := bytes.NewBuffer(nil) + c.Assert(res.Encode(b), IsNil) + + expected := "0008NAK\n[PACK]" + c.Assert(string(b.Bytes()), Equals, expected) +} + +func (s *UploadPackResponseSuite) TestEncodeDepth(c *C) { + pf := ioutil.NopCloser(bytes.NewBuffer([]byte("[PACK]"))) + req := NewUploadPackRequest() + req.Depth = DepthCommits(1) + + res := NewUploadPackResponseWithPackfile(req, pf) + defer func() { c.Assert(res.Close(), IsNil) }() + + b := bytes.NewBuffer(nil) + c.Assert(res.Encode(b), IsNil) + + expected := "00000008NAK\n[PACK]" + c.Assert(string(b.Bytes()), Equals, expected) +} + +func (s *UploadPackResponseSuite) TestEncodeMultiACK(c *C) { + pf := ioutil.NopCloser(bytes.NewBuffer([]byte("[PACK]"))) + req := NewUploadPackRequest() + + res := NewUploadPackResponseWithPackfile(req, pf) + defer func() { c.Assert(res.Close(), IsNil) }() + res.ACKs = []plumbing.Hash{ + plumbing.NewHash("5dc01c595e6c6ec9ccda4f6f69c131c0dd945f81"), + plumbing.NewHash("5dc01c595e6c6ec9ccda4f6f69c131c0dd945f82"), + } + + b := bytes.NewBuffer(nil) + c.Assert(res.Encode(b), NotNil) +} diff --git a/plumbing/storer/storer.go b/plumbing/storer/storer.go new file mode 100644 index 0000000..0b96c0e --- /dev/null +++ b/plumbing/storer/storer.go @@ -0,0 +1,7 @@ +package storer + +// Storer is a basic storer for encoded objects and references. +type Storer interface { + EncodedObjectStorer + ReferenceStorer +} diff --git a/plumbing/transport/client/client.go b/plumbing/transport/client/client.go index 095c51d..4615368 100644 --- a/plumbing/transport/client/client.go +++ b/plumbing/transport/client/client.go @@ -11,7 +11,7 @@ import ( ) // Protocols are the protocols supported by default. -var Protocols = map[string]transport.Client{ +var Protocols = map[string]transport.Transport{ "http": http.DefaultClient, "https": http.DefaultClient, "ssh": ssh.DefaultClient, @@ -20,14 +20,14 @@ var Protocols = map[string]transport.Client{ } // InstallProtocol adds or modifies an existing protocol. -func InstallProtocol(scheme string, c transport.Client) { +func InstallProtocol(scheme string, c transport.Transport) { Protocols[scheme] = c } // NewClient returns the appropriate client among of the set of known protocols: // http://, https://, ssh:// and file://. // See `InstallProtocol` to add or modify protocols. -func NewClient(endpoint transport.Endpoint) (transport.Client, error) { +func NewClient(endpoint transport.Endpoint) (transport.Transport, error) { f, ok := Protocols[endpoint.Scheme] if !ok { return nil, fmt.Errorf("unsupported scheme %q", endpoint.Scheme) diff --git a/plumbing/transport/client/client_test.go b/plumbing/transport/client/client_test.go index a0c8208..9af70b1 100644 --- a/plumbing/transport/client/client_test.go +++ b/plumbing/transport/client/client_test.go @@ -58,13 +58,13 @@ type dummyClient struct { *http.Client } -func (*dummyClient) NewFetchPackSession(transport.Endpoint) ( - transport.FetchPackSession, error) { +func (*dummyClient) NewUploadPackSession(transport.Endpoint) ( + transport.UploadPackSession, error) { return nil, nil } -func (*dummyClient) NewSendPackSession(transport.Endpoint) ( - transport.SendPackSession, error) { +func (*dummyClient) NewReceivePackSession(transport.Endpoint) ( + transport.ReceivePackSession, error) { return nil, nil } diff --git a/plumbing/transport/common.go b/plumbing/transport/common.go index 83fd5a3..00e8a30 100644 --- a/plumbing/transport/common.go +++ b/plumbing/transport/common.go @@ -37,21 +37,23 @@ const ( ReceivePackServiceName = "git-receive-pack" ) -// Client can initiate git-fetch-pack and git-send-pack processes. -type Client interface { - // NewFetchPackSession starts a git-fetch-pack session for an endpoint. - NewFetchPackSession(Endpoint) (FetchPackSession, error) - // NewSendPackSession starts a git-send-pack session for an endpoint. - NewSendPackSession(Endpoint) (SendPackSession, error) +// Transport can initiate git-upload-pack and git-receive-pack processes. +// It is implemented both by the client and the server, making this a RPC. +type Transport interface { + // NewUploadPackSession starts a git-upload-pack session for an endpoint. + NewUploadPackSession(Endpoint) (UploadPackSession, error) + // NewReceivePackSession starts a git-receive-pack session for an endpoint. + NewReceivePackSession(Endpoint) (ReceivePackSession, error) } type Session interface { - SetAuth(auth AuthMethod) error // AdvertisedReferences retrieves the advertised references for a // repository. // If the repository does not exist, returns ErrRepositoryNotFound. // If the repository exists, but is empty, returns ErrEmptyRemoteRepository. AdvertisedReferences() (*packp.AdvRefs, error) + //TODO: Move to Client level. + SetAuth(auth AuthMethod) error io.Closer } @@ -60,26 +62,30 @@ type AuthMethod interface { Name() string } -// FetchPackSession represents a git-fetch-pack session. -// A git-fetch-pack session has two steps: reference discovery -// (`AdvertisedReferences` function) and fetching pack (`FetchPack` function). -// In that order. -type FetchPackSession interface { +// UploadPackSession represents a git-upload-pack session. +// A git-upload-pack session has two steps: reference discovery +// (AdvertisedReferences) and uploading pack (UploadPack). +type UploadPackSession interface { Session - // FetchPack takes a request and returns a reader for the packfile - // received from the server. - FetchPack(*packp.UploadPackRequest) (*packp.UploadPackResponse, error) + // UploadPack takes a git-upload-pack request and returns a response, + // including a packfile. Don't be confused by terminology, the client + // side of a git-upload-pack is called git-fetch-pack, although here + // the same interface is used to make it RPC-like. + UploadPack(*packp.UploadPackRequest) (*packp.UploadPackResponse, error) } -// SendPackSession represents a git-send-pack session. -// A git-send-pack session has two steps: reference discovery -// (`AdvertisedReferences` function) and sending pack (`SendPack` function). +// ReceivePackSession represents a git-receive-pack session. +// A git-receive-pack session has two steps: reference discovery +// (AdvertisedReferences) and receiving pack (ReceivePack). // In that order. -type SendPackSession interface { +type ReceivePackSession interface { Session - // UpdateReferences sends an update references request and a packfile - // reader and returns a ReportStatus and error. - SendPack(*packp.ReferenceUpdateRequest) (*packp.ReportStatus, error) + // ReceivePack sends an update references request and a packfile + // reader and returns a ReportStatus and error. Don't be confused by + // terminology, the client side of a git-receive-pack is called + // git-send-pack, although here the same interface is used to make it + // RPC-like. + ReceivePack(*packp.ReferenceUpdateRequest) (*packp.ReportStatus, error) } type Endpoint url.URL diff --git a/plumbing/transport/file/common.go b/plumbing/transport/file/client.go index e7d18b2..5484009 100644 --- a/plumbing/transport/file/common.go +++ b/plumbing/transport/file/client.go @@ -21,7 +21,7 @@ type runner struct { // NewClient returns a new local client using the given git-upload-pack and // git-receive-pack binaries. -func NewClient(uploadPackBin, receivePackBin string) transport.Client { +func NewClient(uploadPackBin, receivePackBin string) transport.Transport { return common.NewClient(&runner{ UploadPackBin: uploadPackBin, ReceivePackBin: receivePackBin, diff --git a/plumbing/transport/file/client_test.go b/plumbing/transport/file/client_test.go new file mode 100644 index 0000000..220df3d --- /dev/null +++ b/plumbing/transport/file/client_test.go @@ -0,0 +1,40 @@ +package file + +import ( + "fmt" + "io" + "os" + "strings" + "testing" + + "gopkg.in/src-d/go-git.v4/plumbing/transport" + + . "gopkg.in/check.v1" +) + +func Test(t *testing.T) { TestingT(t) } + +const bareConfig = `[core] +repositoryformatversion = 0 +filemode = true +bare = true` + +func prepareRepo(c *C, path string) transport.Endpoint { + url := fmt.Sprintf("file://%s", path) + ep, err := transport.NewEndpoint(url) + c.Assert(err, IsNil) + + // git-receive-pack refuses to update refs/heads/master on non-bare repo + // so we ensure bare repo config. + config := fmt.Sprintf("%s/config", path) + if _, err := os.Stat(config); err == nil { + f, err := os.OpenFile(config, os.O_TRUNC|os.O_WRONLY, 0) + c.Assert(err, IsNil) + content := strings.NewReader(bareConfig) + _, err = io.Copy(f, content) + c.Assert(err, IsNil) + c.Assert(f.Close(), IsNil) + } + + return ep +} diff --git a/plumbing/transport/file/common_test.go b/plumbing/transport/file/common_test.go index 220df3d..cd7c400 100644 --- a/plumbing/transport/file/common_test.go +++ b/plumbing/transport/file/common_test.go @@ -1,40 +1,39 @@ package file import ( - "fmt" - "io" "os" - "strings" - "testing" + "os/exec" + "path/filepath" - "gopkg.in/src-d/go-git.v4/plumbing/transport" + "gopkg.in/src-d/go-git.v4/fixtures" . "gopkg.in/check.v1" + "io/ioutil" ) -func Test(t *testing.T) { TestingT(t) } +type CommonSuite struct { + fixtures.Suite + ReceivePackBin string + UploadPackBin string +} -const bareConfig = `[core] -repositoryformatversion = 0 -filemode = true -bare = true` +var _ = Suite(&CommonSuite{}) -func prepareRepo(c *C, path string) transport.Endpoint { - url := fmt.Sprintf("file://%s", path) - ep, err := transport.NewEndpoint(url) - c.Assert(err, IsNil) +func (s *CommonSuite) SetUpSuite(c *C) { + s.Suite.SetUpSuite(c) - // git-receive-pack refuses to update refs/heads/master on non-bare repo - // so we ensure bare repo config. - config := fmt.Sprintf("%s/config", path) - if _, err := os.Stat(config); err == nil { - f, err := os.OpenFile(config, os.O_TRUNC|os.O_WRONLY, 0) - c.Assert(err, IsNil) - content := strings.NewReader(bareConfig) - _, err = io.Copy(f, content) - c.Assert(err, IsNil) - c.Assert(f.Close(), IsNil) + if err := exec.Command("git", "--version").Run(); err != nil { + c.Skip("git command not found") } - return ep + binDir, err := ioutil.TempDir(os.TempDir(), "") + c.Assert(err, IsNil) + s.ReceivePackBin = filepath.Join(binDir, "git-receive-pack") + s.UploadPackBin = filepath.Join(binDir, "git-upload-pack") + bin := filepath.Join(binDir, "go-git") + cmd := exec.Command("go", "build", "-o", bin, + "../../../cli/go-git/...") + c.Assert(cmd.Run(), IsNil) + c.Assert(os.Symlink(bin, s.ReceivePackBin), IsNil) + c.Assert(os.Symlink(bin, s.UploadPackBin), IsNil) } diff --git a/plumbing/transport/file/send_pack_test.go b/plumbing/transport/file/receive_pack_test.go index fc7ea35..c07d4ed 100644 --- a/plumbing/transport/file/send_pack_test.go +++ b/plumbing/transport/file/receive_pack_test.go @@ -2,7 +2,6 @@ package file import ( "os" - "os/exec" "gopkg.in/src-d/go-git.v4/fixtures" "gopkg.in/src-d/go-git.v4/plumbing/transport/test" @@ -10,24 +9,19 @@ import ( . "gopkg.in/check.v1" ) -type SendPackSuite struct { - fixtures.Suite - test.SendPackSuite +type ReceivePackSuite struct { + CommonSuite + test.ReceivePackSuite } -var _ = Suite(&SendPackSuite{}) +var _ = Suite(&ReceivePackSuite{}) -func (s *SendPackSuite) SetUpSuite(c *C) { - s.Suite.SetUpSuite(c) - - if err := exec.Command("git", "--version").Run(); err != nil { - c.Skip("git command not found") - } - - s.SendPackSuite.Client = DefaultClient +func (s *ReceivePackSuite) SetUpSuite(c *C) { + s.CommonSuite.SetUpSuite(c) + s.ReceivePackSuite.Client = DefaultClient } -func (s *SendPackSuite) SetUpTest(c *C) { +func (s *ReceivePackSuite) SetUpTest(c *C) { fixture := fixtures.Basic().One() path := fixture.DotGit().Base() s.Endpoint = prepareRepo(c, path) @@ -39,12 +33,12 @@ func (s *SendPackSuite) SetUpTest(c *C) { s.NonExistentEndpoint = prepareRepo(c, "/non-existent") } -func (s *SendPackSuite) TearDownTest(c *C) { +func (s *ReceivePackSuite) TearDownTest(c *C) { s.Suite.TearDownSuite(c) } // TODO: fix test -func (s *SendPackSuite) TestCommandNoOutput(c *C) { +func (s *ReceivePackSuite) TestCommandNoOutput(c *C) { c.Skip("failing test") if _, err := os.Stat("/bin/true"); os.IsNotExist(err) { @@ -52,30 +46,30 @@ func (s *SendPackSuite) TestCommandNoOutput(c *C) { } client := NewClient("true", "true") - session, err := client.NewSendPackSession(s.Endpoint) + session, err := client.NewReceivePackSession(s.Endpoint) c.Assert(err, IsNil) ar, err := session.AdvertisedReferences() c.Assert(err, IsNil) c.Assert(ar, IsNil) } -func (s *SendPackSuite) TestMalformedInputNoErrors(c *C) { +func (s *ReceivePackSuite) TestMalformedInputNoErrors(c *C) { if _, err := os.Stat("/usr/bin/yes"); os.IsNotExist(err) { c.Skip("/usr/bin/yes not found") } client := NewClient("yes", "yes") - session, err := client.NewSendPackSession(s.Endpoint) + session, err := client.NewReceivePackSession(s.Endpoint) c.Assert(err, IsNil) ar, err := session.AdvertisedReferences() c.Assert(err, NotNil) c.Assert(ar, IsNil) } -func (s *SendPackSuite) TestNonExistentCommand(c *C) { +func (s *ReceivePackSuite) TestNonExistentCommand(c *C) { cmd := "/non-existent-git" client := NewClient(cmd, cmd) - session, err := client.NewSendPackSession(s.Endpoint) + session, err := client.NewReceivePackSession(s.Endpoint) c.Assert(err, ErrorMatches, ".*no such file or directory.*") c.Assert(session, IsNil) } diff --git a/plumbing/transport/file/server.go b/plumbing/transport/file/server.go new file mode 100644 index 0000000..d83d5d9 --- /dev/null +++ b/plumbing/transport/file/server.go @@ -0,0 +1,51 @@ +package file + +import ( + "fmt" + "os" + + "gopkg.in/src-d/go-git.v4/plumbing/transport" + "gopkg.in/src-d/go-git.v4/plumbing/transport/internal/common" + "gopkg.in/src-d/go-git.v4/plumbing/transport/server" + "gopkg.in/src-d/go-git.v4/utils/ioutil" +) + +// ServeUploadPack serves a git-upload-pack request using standard output, input +// and error. This is meant to be used when implementing a git-upload-pack +// command. +func ServeUploadPack(path string) error { + ep, err := transport.NewEndpoint(fmt.Sprintf("file://%s", path)) + if err != nil { + return err + } + + s, err := server.DefaultServer.NewUploadPackSession(ep) + if err != nil { + return fmt.Errorf("error creating session: %s", err) + } + + return common.ServeUploadPack(srvCmd, s) +} + +// ServeReceivePack serves a git-receive-pack request using standard output, +// input and error. This is meant to be used when implementing a +// git-receive-pack command. +func ServeReceivePack(path string) error { + ep, err := transport.NewEndpoint(fmt.Sprintf("file://%s", path)) + if err != nil { + return err + } + + s, err := server.DefaultServer.NewReceivePackSession(ep) + if err != nil { + return fmt.Errorf("error creating session: %s", err) + } + + return common.ServeReceivePack(srvCmd, s) +} + +var srvCmd = common.ServerCommand{ + Stdin: os.Stdin, + Stdout: ioutil.WriteNopCloser(os.Stdout), + Stderr: os.Stderr, +} diff --git a/plumbing/transport/file/server_test.go b/plumbing/transport/file/server_test.go new file mode 100644 index 0000000..ff462e2 --- /dev/null +++ b/plumbing/transport/file/server_test.go @@ -0,0 +1,105 @@ +package file + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + + "gopkg.in/src-d/go-git.v4/fixtures" + + . "gopkg.in/check.v1" +) + +type ServerSuite struct { + CommonSuite + RemoteName string + SrcPath string + DstPath string + DstURL string +} + +var _ = Suite(&ServerSuite{}) + +func (s *ServerSuite) SetUpSuite(c *C) { + s.CommonSuite.SetUpSuite(c) + + s.RemoteName = "test" + + fixture := fixtures.Basic().One() + s.SrcPath = fixture.DotGit().Base() + + fixture = fixtures.ByTag("empty").One() + s.DstPath = fixture.DotGit().Base() + s.DstURL = fmt.Sprintf("file://%s", s.DstPath) + + cmd := exec.Command("git", "remote", "add", s.RemoteName, s.DstURL) + cmd.Dir = s.SrcPath + c.Assert(cmd.Run(), IsNil) +} + +func (s *ServerSuite) TestPush(c *C) { + // git <2.0 cannot push to an empty repository without a refspec. + cmd := exec.Command("git", "push", + "--receive-pack", s.ReceivePackBin, + s.RemoteName, "refs/heads/*:refs/heads/*", + ) + cmd.Dir = s.SrcPath + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "GIT_TRACE=true", "GIT_TRACE_PACKET=true") + stdout, stderr, err := execAndGetOutput(c, cmd) + c.Assert(err, IsNil, Commentf("STDOUT:\n%s\nSTDERR:\n%s\n", stdout, stderr)) +} + +func (s *ServerSuite) TestClone(c *C) { + pathToClone := c.MkDir() + + cmd := exec.Command("git", "clone", + "--upload-pack", s.UploadPackBin, + s.SrcPath, pathToClone, + ) + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "GIT_TRACE=true", "GIT_TRACE_PACKET=true") + stdout, stderr, err := execAndGetOutput(c, cmd) + c.Assert(err, IsNil, Commentf("STDOUT:\n%s\nSTDERR:\n%s\n", stdout, stderr)) +} + +func execAndGetOutput(c *C, cmd *exec.Cmd) (stdout, stderr string, err error) { + sout, err := cmd.StdoutPipe() + c.Assert(err, IsNil) + serr, err := cmd.StderrPipe() + c.Assert(err, IsNil) + + outChan, outErr := readAllAsync(sout) + errChan, errErr := readAllAsync(serr) + + c.Assert(cmd.Start(), IsNil) + + if err = cmd.Wait(); err != nil { + return <-outChan, <-errChan, err + } + + if err := <-outErr; err != nil { + return <-outChan, <-errChan, err + } + + return <-outChan, <-errChan, <-errErr +} + +func readAllAsync(r io.Reader) (out chan string, err chan error) { + out = make(chan string, 1) + err = make(chan error, 1) + go func() { + b, e := ioutil.ReadAll(r) + if e != nil { + err <- e + } else { + err <- nil + } + + out <- string(b) + }() + + return out, err +} diff --git a/plumbing/transport/file/fetch_pack_test.go b/plumbing/transport/file/upload_pack_test.go index 25e3fef..de232c4 100644 --- a/plumbing/transport/file/fetch_pack_test.go +++ b/plumbing/transport/file/upload_pack_test.go @@ -3,7 +3,6 @@ package file import ( "fmt" "os" - "os/exec" "gopkg.in/src-d/go-git.v4/fixtures" "gopkg.in/src-d/go-git.v4/plumbing/transport" @@ -12,21 +11,17 @@ import ( . "gopkg.in/check.v1" ) -type FetchPackSuite struct { - fixtures.Suite - test.FetchPackSuite +type UploadPackSuite struct { + CommonSuite + test.UploadPackSuite } -var _ = Suite(&FetchPackSuite{}) +var _ = Suite(&UploadPackSuite{}) -func (s *FetchPackSuite) SetUpSuite(c *C) { - s.Suite.SetUpSuite(c) +func (s *UploadPackSuite) SetUpSuite(c *C) { + s.CommonSuite.SetUpSuite(c) - if err := exec.Command("git", "--version").Run(); err != nil { - c.Skip("git command not found") - } - - s.FetchPackSuite.Client = DefaultClient + s.UploadPackSuite.Client = DefaultClient fixture := fixtures.Basic().One() path := fixture.DotGit().Base() @@ -49,7 +44,7 @@ func (s *FetchPackSuite) SetUpSuite(c *C) { } // TODO: fix test -func (s *FetchPackSuite) TestCommandNoOutput(c *C) { +func (s *UploadPackSuite) TestCommandNoOutput(c *C) { c.Skip("failing test") if _, err := os.Stat("/bin/true"); os.IsNotExist(err) { @@ -57,30 +52,30 @@ func (s *FetchPackSuite) TestCommandNoOutput(c *C) { } client := NewClient("true", "true") - session, err := client.NewFetchPackSession(s.Endpoint) + session, err := client.NewUploadPackSession(s.Endpoint) c.Assert(err, IsNil) ar, err := session.AdvertisedReferences() c.Assert(err, IsNil) c.Assert(ar, IsNil) } -func (s *FetchPackSuite) TestMalformedInputNoErrors(c *C) { +func (s *UploadPackSuite) TestMalformedInputNoErrors(c *C) { if _, err := os.Stat("/usr/bin/yes"); os.IsNotExist(err) { c.Skip("/usr/bin/yes not found") } client := NewClient("yes", "yes") - session, err := client.NewFetchPackSession(s.Endpoint) + session, err := client.NewUploadPackSession(s.Endpoint) c.Assert(err, IsNil) ar, err := session.AdvertisedReferences() c.Assert(err, NotNil) c.Assert(ar, IsNil) } -func (s *FetchPackSuite) TestNonExistentCommand(c *C) { +func (s *UploadPackSuite) TestNonExistentCommand(c *C) { cmd := "/non-existent-git" client := NewClient(cmd, cmd) - session, err := client.NewFetchPackSession(s.Endpoint) + session, err := client.NewUploadPackSession(s.Endpoint) c.Assert(err, ErrorMatches, ".*no such file or directory.*") c.Assert(session, IsNil) } diff --git a/plumbing/transport/git/fetch_pack_test.go b/plumbing/transport/git/upload_pack_test.go index dc40240..d506075 100644 --- a/plumbing/transport/git/fetch_pack_test.go +++ b/plumbing/transport/git/upload_pack_test.go @@ -8,28 +8,28 @@ import ( . "gopkg.in/check.v1" ) -type FetchPackSuite struct { - test.FetchPackSuite +type UploadPackSuite struct { + test.UploadPackSuite fixtures.Suite } -var _ = Suite(&FetchPackSuite{}) +var _ = Suite(&UploadPackSuite{}) -func (s *FetchPackSuite) SetUpSuite(c *C) { +func (s *UploadPackSuite) SetUpSuite(c *C) { s.Suite.SetUpSuite(c) - s.FetchPackSuite.Client = DefaultClient + s.UploadPackSuite.Client = DefaultClient ep, err := transport.NewEndpoint("git://github.com/git-fixtures/basic.git") c.Assert(err, IsNil) - s.FetchPackSuite.Endpoint = ep + s.UploadPackSuite.Endpoint = ep ep, err = transport.NewEndpoint("git://github.com/git-fixtures/empty.git") c.Assert(err, IsNil) - s.FetchPackSuite.EmptyEndpoint = ep + s.UploadPackSuite.EmptyEndpoint = ep ep, err = transport.NewEndpoint("git://github.com/git-fixtures/non-existent.git") c.Assert(err, IsNil) - s.FetchPackSuite.NonExistentEndpoint = ep + s.UploadPackSuite.NonExistentEndpoint = ep } diff --git a/plumbing/transport/http/common.go b/plumbing/transport/http/common.go index aa3425e..957fd07 100644 --- a/plumbing/transport/http/common.go +++ b/plumbing/transport/http/common.go @@ -25,7 +25,7 @@ var DefaultClient = NewClient(nil) // Note that for HTTP client cannot distinguist between private repositories and // unexistent repositories on GitHub. So it returns `ErrAuthorizationRequired` // for both. -func NewClient(c *http.Client) transport.Client { +func NewClient(c *http.Client) transport.Transport { if c == nil { return &client{http.DefaultClient} } @@ -35,16 +35,16 @@ func NewClient(c *http.Client) transport.Client { } } -func (c *client) NewFetchPackSession(ep transport.Endpoint) ( - transport.FetchPackSession, error) { +func (c *client) NewUploadPackSession(ep transport.Endpoint) ( + transport.UploadPackSession, error) { - return newFetchPackSession(c.c, ep), nil + return newUploadPackSession(c.c, ep), nil } -func (c *client) NewSendPackSession(ep transport.Endpoint) ( - transport.SendPackSession, error) { +func (c *client) NewReceivePackSession(ep transport.Endpoint) ( + transport.ReceivePackSession, error) { - return newSendPackSession(c.c, ep), nil + return newReceivePackSession(c.c, ep), nil } type session struct { diff --git a/plumbing/transport/http/common_test.go b/plumbing/transport/http/common_test.go index 432bd07..217999d 100644 --- a/plumbing/transport/http/common_test.go +++ b/plumbing/transport/http/common_test.go @@ -26,7 +26,7 @@ func (s *ClientSuite) SetUpSuite(c *C) { c.Assert(err, IsNil) } -func (s *FetchPackSuite) TestNewClient(c *C) { +func (s *UploadPackSuite) TestNewClient(c *C) { roundTripper := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } @@ -76,10 +76,10 @@ func (s *ClientSuite) testNewHTTPError(c *C, code int, msg string) { func (s *ClientSuite) TestSetAuth(c *C) { auth := &BasicAuth{} - r, err := DefaultClient.NewFetchPackSession(s.Endpoint) + r, err := DefaultClient.NewUploadPackSession(s.Endpoint) c.Assert(err, IsNil) r.SetAuth(auth) - c.Assert(auth, Equals, r.(*fetchPackSession).auth) + c.Assert(auth, Equals, r.(*upSession).auth) } type mockAuth struct{} @@ -88,7 +88,7 @@ func (*mockAuth) Name() string { return "" } func (*mockAuth) String() string { return "" } func (s *ClientSuite) TestSetAuthWrongType(c *C) { - r, err := DefaultClient.NewFetchPackSession(s.Endpoint) + r, err := DefaultClient.NewUploadPackSession(s.Endpoint) c.Assert(err, IsNil) c.Assert(r.SetAuth(&mockAuth{}), Equals, transport.ErrInvalidAuthMethod) } diff --git a/plumbing/transport/http/receive_pack.go b/plumbing/transport/http/receive_pack.go new file mode 100644 index 0000000..a8384c7 --- /dev/null +++ b/plumbing/transport/http/receive_pack.go @@ -0,0 +1,30 @@ +package http + +import ( + "errors" + "net/http" + + "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp" + "gopkg.in/src-d/go-git.v4/plumbing/transport" +) + +var errReceivePackNotSupported = errors.New("receive-pack not supported yet") + +type rpSession struct { + *session +} + +func newReceivePackSession(c *http.Client, ep transport.Endpoint) transport.ReceivePackSession { + return &rpSession{&session{}} +} + +func (s *rpSession) AdvertisedReferences() (*packp.AdvRefs, error) { + + return nil, errReceivePackNotSupported +} + +func (s *rpSession) ReceivePack(*packp.ReferenceUpdateRequest) ( + *packp.ReportStatus, error) { + + return nil, errReceivePackNotSupported +} diff --git a/plumbing/transport/http/send_pack.go b/plumbing/transport/http/send_pack.go deleted file mode 100644 index 43464ae..0000000 --- a/plumbing/transport/http/send_pack.go +++ /dev/null @@ -1,30 +0,0 @@ -package http - -import ( - "errors" - "net/http" - - "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp" - "gopkg.in/src-d/go-git.v4/plumbing/transport" -) - -var errSendPackNotSupported = errors.New("send-pack not supported yet") - -type sendPackSession struct { - *session -} - -func newSendPackSession(c *http.Client, ep transport.Endpoint) transport.SendPackSession { - return &sendPackSession{&session{}} -} - -func (s *sendPackSession) AdvertisedReferences() (*packp.AdvRefs, error) { - - return nil, errSendPackNotSupported -} - -func (s *sendPackSession) SendPack(*packp.ReferenceUpdateRequest) ( - *packp.ReportStatus, error) { - - return nil, errSendPackNotSupported -} diff --git a/plumbing/transport/http/fetch_pack.go b/plumbing/transport/http/upload_pack.go index 0c85be4..26257f5 100644 --- a/plumbing/transport/http/fetch_pack.go +++ b/plumbing/transport/http/upload_pack.go @@ -15,14 +15,14 @@ import ( "gopkg.in/src-d/go-git.v4/utils/ioutil" ) -type fetchPackSession struct { +type upSession struct { *session } -func newFetchPackSession(c *http.Client, - ep transport.Endpoint) transport.FetchPackSession { +func newUploadPackSession(c *http.Client, + ep transport.Endpoint) transport.UploadPackSession { - return &fetchPackSession{ + return &upSession{ session: &session{ auth: basicAuthFromEndpoint(ep), client: c, @@ -31,7 +31,7 @@ func newFetchPackSession(c *http.Client, } } -func (s *fetchPackSession) AdvertisedReferences() (*packp.AdvRefs, error) { +func (s *upSession) AdvertisedReferences() (*packp.AdvRefs, error) { if s.advRefs != nil { return s.advRefs, nil } @@ -73,7 +73,7 @@ func (s *fetchPackSession) AdvertisedReferences() (*packp.AdvRefs, error) { return ar, nil } -func (s *fetchPackSession) FetchPack(req *packp.UploadPackRequest) (*packp.UploadPackResponse, error) { +func (s *upSession) UploadPack(req *packp.UploadPackRequest) (*packp.UploadPackResponse, error) { if req.IsEmpty() { return nil, transport.ErrEmptyUploadPackRequest } @@ -111,11 +111,11 @@ func (s *fetchPackSession) FetchPack(req *packp.UploadPackRequest) (*packp.Uploa } // Close does nothing. -func (s *fetchPackSession) Close() error { +func (s *upSession) Close() error { return nil } -func (s *fetchPackSession) doRequest(method, url string, content *bytes.Buffer) (*http.Response, error) { +func (s *upSession) doRequest(method, url string, content *bytes.Buffer) (*http.Response, error) { var body io.Reader if content != nil { body = content @@ -143,7 +143,7 @@ func (s *fetchPackSession) doRequest(method, url string, content *bytes.Buffer) } // it requires a bytes.Buffer, because we need to know the length -func (s *fetchPackSession) applyHeadersToRequest(req *http.Request, content *bytes.Buffer) { +func (s *upSession) applyHeadersToRequest(req *http.Request, content *bytes.Buffer) { req.Header.Add("User-Agent", "git/1.0") req.Header.Add("Host", s.endpoint.Host) diff --git a/plumbing/transport/http/fetch_pack_test.go b/plumbing/transport/http/upload_pack_test.go index 6c40e60..d3e4989 100644 --- a/plumbing/transport/http/fetch_pack_test.go +++ b/plumbing/transport/http/upload_pack_test.go @@ -11,37 +11,38 @@ import ( . "gopkg.in/check.v1" ) -type FetchPackSuite struct { - test.FetchPackSuite +type UploadPackSuite struct { + test.UploadPackSuite } -var _ = Suite(&FetchPackSuite{}) +var _ = Suite(&UploadPackSuite{}) -func (s *FetchPackSuite) SetUpSuite(c *C) { - s.FetchPackSuite.Client = DefaultClient +func (s *UploadPackSuite) SetUpSuite(c *C) { + s.UploadPackSuite.Client = DefaultClient ep, err := transport.NewEndpoint("https://github.com/git-fixtures/basic.git") c.Assert(err, IsNil) - s.FetchPackSuite.Endpoint = ep + s.UploadPackSuite.Endpoint = ep ep, err = transport.NewEndpoint("https://github.com/git-fixtures/empty.git") c.Assert(err, IsNil) - s.FetchPackSuite.EmptyEndpoint = ep + s.UploadPackSuite.EmptyEndpoint = ep ep, err = transport.NewEndpoint("https://github.com/git-fixtures/non-existent.git") c.Assert(err, IsNil) - s.FetchPackSuite.NonExistentEndpoint = ep + s.UploadPackSuite.NonExistentEndpoint = ep } -func (s *FetchPackSuite) TestInfoNotExists(c *C) { - r, err := s.Client.NewFetchPackSession(s.NonExistentEndpoint) +// Overwritten, different behaviour for HTTP. +func (s *UploadPackSuite) TestAdvertisedReferencesNotExists(c *C) { + r, err := s.Client.NewUploadPackSession(s.NonExistentEndpoint) c.Assert(err, IsNil) info, err := r.AdvertisedReferences() c.Assert(err, Equals, transport.ErrAuthorizationRequired) c.Assert(info, IsNil) } -func (s *FetchPackSuite) TestuploadPackRequestToReader(c *C) { +func (s *UploadPackSuite) TestuploadPackRequestToReader(c *C) { r := packp.NewUploadPackRequest() r.Wants = append(r.Wants, plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c")) r.Wants = append(r.Wants, plumbing.NewHash("2b41ef280fdb67a9b250678686a0c3e03b0a9989")) diff --git a/plumbing/transport/internal/common/common.go b/plumbing/transport/internal/common/common.go index 17c473e..831c4d7 100644 --- a/plumbing/transport/internal/common/common.go +++ b/plumbing/transport/internal/common/common.go @@ -77,20 +77,20 @@ type client struct { } // NewClient creates a new client using the given Commander. -func NewClient(runner Commander) transport.Client { +func NewClient(runner Commander) transport.Transport { return &client{runner} } -// NewFetchPackSession creates a new FetchPackSession. -func (c *client) NewFetchPackSession(ep transport.Endpoint) ( - transport.FetchPackSession, error) { +// NewUploadPackSession creates a new UploadPackSession. +func (c *client) NewUploadPackSession(ep transport.Endpoint) ( + transport.UploadPackSession, error) { return c.newSession(transport.UploadPackServiceName, ep) } -// NewSendPackSession creates a new SendPackSession. -func (c *client) NewSendPackSession(ep transport.Endpoint) ( - transport.SendPackSession, error) { +// NewReceivePackSession creates a new ReceivePackSession. +func (c *client) NewReceivePackSession(ep transport.Endpoint) ( + transport.ReceivePackSession, error) { return c.newSession(transport.ReceivePackServiceName, ep) } @@ -219,9 +219,9 @@ func (s *session) handleAdvRefDecodeError(err error) error { return err } -// FetchPack performs a request to the server to fetch a packfile. A reader is +// UploadPack performs a request to the server to fetch a packfile. A reader is // returned with the packfile content. The reader must be closed after reading. -func (s *session) FetchPack(req *packp.UploadPackRequest) (*packp.UploadPackResponse, error) { +func (s *session) UploadPack(req *packp.UploadPackRequest) (*packp.UploadPackResponse, error) { if req.IsEmpty() { return nil, transport.ErrEmptyUploadPackRequest } @@ -236,7 +236,7 @@ func (s *session) FetchPack(req *packp.UploadPackRequest) (*packp.UploadPackResp s.packRun = true - if err := fetchPack(s.Stdin, s.Stdout, req); err != nil { + if err := uploadPack(s.Stdin, s.Stdout, req); err != nil { return nil, err } @@ -259,7 +259,7 @@ func (s *session) FetchPack(req *packp.UploadPackRequest) (*packp.UploadPackResp return DecodeUploadPackResponse(rc, req) } -func (s *session) SendPack(req *packp.ReferenceUpdateRequest) (*packp.ReportStatus, error) { +func (s *session) ReceivePack(req *packp.ReferenceUpdateRequest) (*packp.ReportStatus, error) { if _, err := s.AdvertisedReferences(); err != nil { return nil, err } @@ -295,7 +295,7 @@ func (s *session) finish() error { s.finished = true - // If we did not run fetch-pack or send-pack, we close the connection + // If we did not run a upload/receive-pack, we close the connection // gracefully by sending a flush packet to the server. If the server // operates correctly, it will exit with status 0. if !s.packRun { @@ -367,13 +367,13 @@ var ( eol = []byte("\n") ) -// fetchPack implements the git-fetch-pack protocol. +// uploadPack implements the git-upload-pack protocol. // // TODO support multi_ack mode // TODO support multi_ack_detailed mode // TODO support acks for common objects // TODO build a proper state machine for all these processing options -func fetchPack(w io.WriteCloser, r io.Reader, req *packp.UploadPackRequest) error { +func uploadPack(w io.WriteCloser, r io.Reader, req *packp.UploadPackRequest) error { if err := req.UploadRequest.Encode(w); err != nil { return fmt.Errorf("sending upload-req message: %s", err) } diff --git a/plumbing/transport/internal/common/server.go b/plumbing/transport/internal/common/server.go new file mode 100644 index 0000000..dd6cfbe --- /dev/null +++ b/plumbing/transport/internal/common/server.go @@ -0,0 +1,72 @@ +package common + +import ( + "fmt" + "io" + + "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp" + "gopkg.in/src-d/go-git.v4/plumbing/transport" + "gopkg.in/src-d/go-git.v4/utils/ioutil" +) + +// ServerCommand is used for a single server command execution. +type ServerCommand struct { + Stderr io.Writer + Stdout io.WriteCloser + Stdin io.Reader +} + +func ServeUploadPack(cmd ServerCommand, s transport.UploadPackSession) (err error) { + ioutil.CheckClose(cmd.Stdout, &err) + + ar, err := s.AdvertisedReferences() + if err != nil { + return err + } + + if err := ar.Encode(cmd.Stdout); err != nil { + return err + } + + req := packp.NewUploadPackRequest() + if err := req.Decode(cmd.Stdin); err != nil { + return err + } + + var resp *packp.UploadPackResponse + resp, err = s.UploadPack(req) + if err != nil { + return err + } + + return resp.Encode(cmd.Stdout) +} + +func ServeReceivePack(cmd ServerCommand, s transport.ReceivePackSession) error { + ar, err := s.AdvertisedReferences() + if err != nil { + return fmt.Errorf("internal error in advertised references: %s", err) + } + + if err := ar.Encode(cmd.Stdout); err != nil { + return fmt.Errorf("error in advertised references encoding: %s", err) + } + + req := packp.NewReferenceUpdateRequest() + if err := req.Decode(cmd.Stdin); err != nil { + return fmt.Errorf("error decoding: %s", err) + } + + rs, err := s.ReceivePack(req) + if rs != nil { + if err := rs.Encode(cmd.Stdout); err != nil { + return fmt.Errorf("error in encoding report status %s", err) + } + } + + if err != nil { + return fmt.Errorf("error in receive pack: %s", err) + } + + return nil +} diff --git a/plumbing/transport/server/loader.go b/plumbing/transport/server/loader.go new file mode 100644 index 0000000..55bcf1d --- /dev/null +++ b/plumbing/transport/server/loader.go @@ -0,0 +1,59 @@ +package server + +import ( + "gopkg.in/src-d/go-git.v4/plumbing/storer" + "gopkg.in/src-d/go-git.v4/plumbing/transport" + "gopkg.in/src-d/go-git.v4/storage/filesystem" + + "srcd.works/go-billy.v1" + "srcd.works/go-billy.v1/os" +) + +// DefaultLoader is a filesystem loader ignoring host and resolving paths to /. +var DefaultLoader = NewFilesystemLoader(os.New("/")) + +// Loader loads repository's storer.Storer based on an optional host and a path. +type Loader interface { + // Load loads a storer.Storer given a transport.Endpoint. + // Returns transport.ErrRepositoryNotFound if the repository does not + // exist. + Load(ep transport.Endpoint) (storer.Storer, error) +} + +type fsLoader struct { + base billy.Filesystem +} + +// NewFilesystemLoader creates a Loader that ignores host and resolves paths +// with a given base filesystem. +func NewFilesystemLoader(base billy.Filesystem) Loader { + return &fsLoader{base} +} + +// Load looks up the endpoint's path in the base file system and returns a +// storer for it. Returns transport.ErrRepositoryNotFound if a repository does +// not exist in the given path. +func (l *fsLoader) Load(ep transport.Endpoint) (storer.Storer, error) { + fs := l.base.Dir(ep.Path) + if _, err := fs.Stat("config"); err != nil { + return nil, transport.ErrRepositoryNotFound + } + + return filesystem.NewStorage(fs) +} + +// MapLoader is a Loader that uses a lookup map of storer.Storer by +// transport.Endpoint. +type MapLoader map[transport.Endpoint]storer.Storer + +// Load returns a storer.Storer for given a transport.Endpoint by looking it up +// in the map. Returns transport.ErrRepositoryNotFound if the endpoint does not +// exist. +func (l MapLoader) Load(ep transport.Endpoint) (storer.Storer, error) { + s, ok := l[ep] + if !ok { + return nil, transport.ErrRepositoryNotFound + } + + return s, nil +} diff --git a/plumbing/transport/server/loader_test.go b/plumbing/transport/server/loader_test.go new file mode 100644 index 0000000..b4a8c37 --- /dev/null +++ b/plumbing/transport/server/loader_test.go @@ -0,0 +1,57 @@ +package server + +import ( + "fmt" + "os/exec" + "path/filepath" + + "gopkg.in/src-d/go-git.v4/plumbing/transport" + + . "gopkg.in/check.v1" +) + +type LoaderSuite struct { + RepoPath string +} + +var _ = Suite(&LoaderSuite{}) + +func (s *LoaderSuite) SetUpSuite(c *C) { + if err := exec.Command("git", "--version").Run(); err != nil { + c.Skip("git command not found") + } + + dir := c.MkDir() + s.RepoPath = filepath.Join(dir, "repo.git") + c.Assert(exec.Command("git", "init", "--bare", s.RepoPath).Run(), IsNil) +} + +func (s *LoaderSuite) endpoint(c *C, url string) transport.Endpoint { + ep, err := transport.NewEndpoint(url) + c.Assert(err, IsNil) + return ep +} + +func (s *LoaderSuite) TestLoadNonExistent(c *C) { + sto, err := DefaultLoader.Load(s.endpoint(c, "file:///does-not-exist")) + c.Assert(err, Equals, transport.ErrRepositoryNotFound) + c.Assert(sto, IsNil) +} + +func (s *LoaderSuite) TestLoadNonExistentIgnoreHost(c *C) { + sto, err := DefaultLoader.Load(s.endpoint(c, "https://github.com/does-not-exist")) + c.Assert(err, Equals, transport.ErrRepositoryNotFound) + c.Assert(sto, IsNil) +} + +func (s *LoaderSuite) TestLoad(c *C) { + sto, err := DefaultLoader.Load(s.endpoint(c, fmt.Sprintf("file://%s", s.RepoPath))) + c.Assert(err, IsNil) + c.Assert(sto, NotNil) +} + +func (s *LoaderSuite) TestLoadIgnoreHost(c *C) { + sto, err := DefaultLoader.Load(s.endpoint(c, fmt.Sprintf("file://%s", s.RepoPath))) + c.Assert(err, IsNil) + c.Assert(sto, NotNil) +} diff --git a/plumbing/transport/server/receive_pack_test.go b/plumbing/transport/server/receive_pack_test.go new file mode 100644 index 0000000..2c4036a --- /dev/null +++ b/plumbing/transport/server/receive_pack_test.go @@ -0,0 +1,40 @@ +package server_test + +import ( + "gopkg.in/src-d/go-git.v4/plumbing/transport" + "gopkg.in/src-d/go-git.v4/plumbing/transport/test" + + . "gopkg.in/check.v1" +) + +type ReceivePackSuite struct { + BaseSuite + test.ReceivePackSuite +} + +var _ = Suite(&ReceivePackSuite{}) + +func (s *ReceivePackSuite) SetUpSuite(c *C) { + s.BaseSuite.SetUpSuite(c) + s.ReceivePackSuite.Client = s.client +} + +func (s *ReceivePackSuite) SetUpTest(c *C) { + s.prepareRepositories(c, &s.Endpoint, &s.EmptyEndpoint, &s.NonExistentEndpoint) +} + +func (s *ReceivePackSuite) TearDownTest(c *C) { + s.Suite.TearDownSuite(c) +} + +// TODO +func (s *ReceivePackSuite) TestSendPackAddDeleteReference(c *C) { + c.Skip("delete reference not supported yet") +} + +// Overwritten, server returns error earlier. +func (s *ReceivePackSuite) TestAdvertisedReferencesNotExists(c *C) { + r, err := s.Client.NewReceivePackSession(s.NonExistentEndpoint) + c.Assert(err, Equals, transport.ErrRepositoryNotFound) + c.Assert(r, IsNil) +} diff --git a/plumbing/transport/server/server.go b/plumbing/transport/server/server.go new file mode 100644 index 0000000..6787e9d --- /dev/null +++ b/plumbing/transport/server/server.go @@ -0,0 +1,448 @@ +// Package server implements the git server protocol. For most use cases, the +// transport-specific implementations should be used. +package server + +import ( + "errors" + "fmt" + "io" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/format/packfile" + "gopkg.in/src-d/go-git.v4/plumbing/object" + "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp" + "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability" + "gopkg.in/src-d/go-git.v4/plumbing/revlist" + "gopkg.in/src-d/go-git.v4/plumbing/storer" + "gopkg.in/src-d/go-git.v4/plumbing/transport" +) + +var DefaultServer = NewServer(DefaultLoader) + +type server struct { + loader Loader + handler *handler +} + +// NewServer returns a transport.Transport implementing a git server, +// independent of transport. Each transport must wrap this. +func NewServer(loader Loader) transport.Transport { + return &server{loader, &handler{}} +} + +func (s *server) NewUploadPackSession(ep transport.Endpoint) (transport.UploadPackSession, error) { + sto, err := s.loader.Load(ep) + if err != nil { + return nil, err + } + + return s.handler.NewUploadPackSession(sto) +} + +func (s *server) NewReceivePackSession(ep transport.Endpoint) (transport.ReceivePackSession, error) { + sto, err := s.loader.Load(ep) + if err != nil { + return nil, err + } + + return s.handler.NewReceivePackSession(sto) +} + +type handler struct{} + +func (h *handler) NewUploadPackSession(s storer.Storer) (transport.UploadPackSession, error) { + return &upSession{ + session: session{storer: s}, + }, nil +} + +func (h *handler) NewReceivePackSession(s storer.Storer) (transport.ReceivePackSession, error) { + return &rpSession{ + session: session{storer: s}, + cmdStatus: map[plumbing.ReferenceName]error{}, + }, nil +} + +type session struct { + storer storer.Storer + caps *capability.List +} + +func (s *session) Close() error { + return nil +} + +//TODO: deprecate +func (s *session) SetAuth(transport.AuthMethod) error { + return nil +} + +func (s *session) checkSupportedCapabilities(cl *capability.List) error { + for _, c := range cl.All() { + if !s.caps.Supports(c) { + return fmt.Errorf("unsupported capability: %s", c) + } + } + + return nil +} + +type upSession struct { + session +} + +func (s *upSession) AdvertisedReferences() (*packp.AdvRefs, error) { + ar := packp.NewAdvRefs() + + if err := s.setSupportedCapabilities(ar.Capabilities); err != nil { + return nil, err + } + + s.caps = ar.Capabilities + + if err := setReferences(s.storer, ar); err != nil { + return nil, err + } + + if err := setHEAD(s.storer, ar); err != nil { + return nil, err + } + + return ar, nil +} + +func (s *upSession) UploadPack(req *packp.UploadPackRequest) (*packp.UploadPackResponse, error) { + if req.IsEmpty() { + return nil, transport.ErrEmptyUploadPackRequest + } + + if err := req.Validate(); err != nil { + return nil, err + } + + if s.caps == nil { + s.caps = capability.NewList() + if err := s.setSupportedCapabilities(s.caps); err != nil { + return nil, err + } + } + + if err := s.checkSupportedCapabilities(req.Capabilities); err != nil { + return nil, err + } + + s.caps = req.Capabilities + + if len(req.Shallows) > 0 { + return nil, fmt.Errorf("shallow not supported") + } + + objs, err := s.objectsToUpload(req) + if err != nil { + return nil, err + } + + pr, pw := io.Pipe() + e := packfile.NewEncoder(pw, s.storer, false) + go func() { + _, err := e.Encode(objs) + pw.CloseWithError(err) + }() + + return packp.NewUploadPackResponseWithPackfile(req, pr), nil +} + +func (s *upSession) objectsToUpload(req *packp.UploadPackRequest) ([]plumbing.Hash, error) { + commits, err := s.commitsToUpload(req.Wants) + if err != nil { + return nil, err + } + + return revlist.Objects(s.storer, commits, req.Haves) +} + +func (s *upSession) commitsToUpload(wants []plumbing.Hash) ([]*object.Commit, error) { + var commits []*object.Commit + for _, h := range wants { + c, err := object.GetCommit(s.storer, h) + if err != nil { + return nil, err + } + + commits = append(commits, c) + } + + return commits, nil +} + +func (*upSession) setSupportedCapabilities(c *capability.List) error { + if err := c.Set(capability.Agent, capability.DefaultAgent); err != nil { + return err + } + + if err := c.Set(capability.OFSDelta); err != nil { + return err + } + + return nil +} + +type rpSession struct { + session + cmdStatus map[plumbing.ReferenceName]error + firstErr error + unpackErr error +} + +func (s *rpSession) AdvertisedReferences() (*packp.AdvRefs, error) { + ar := packp.NewAdvRefs() + + if err := s.setSupportedCapabilities(ar.Capabilities); err != nil { + return nil, err + } + + s.caps = ar.Capabilities + + if err := setReferences(s.storer, ar); err != nil { + return nil, err + } + + if err := setHEAD(s.storer, ar); err != nil { + return nil, err + } + + return ar, nil +} + +var ( + ErrUpdateReference = errors.New("failed to update ref") +) + +func (s *rpSession) ReceivePack(req *packp.ReferenceUpdateRequest) (*packp.ReportStatus, error) { + if s.caps == nil { + s.caps = capability.NewList() + if err := s.setSupportedCapabilities(s.caps); err != nil { + return nil, err + } + } + + if err := s.checkSupportedCapabilities(req.Capabilities); err != nil { + return nil, err + } + + s.caps = req.Capabilities + + //TODO: Implement 'atomic' update of references. + + if err := s.writePackfile(req.Packfile); err != nil { + s.unpackErr = err + s.firstErr = err + return s.reportStatus(), err + } + + updatedRefs := s.updatedReferences(req) + + if s.caps.Supports(capability.Atomic) && s.firstErr != nil { + //TODO: add support for 'atomic' once we have reference + // transactions, currently we do not announce it. + rs := s.reportStatus() + for _, cs := range rs.CommandStatuses { + if cs.Error() == nil { + cs.Status = "" + } + } + } + + for name, ref := range updatedRefs { + //TODO: add support for 'delete-refs' once we can delete + // references, currently we do not announce it. + err := s.storer.SetReference(ref) + s.setStatus(name, err) + } + + return s.reportStatus(), s.firstErr +} + +func (s *rpSession) updatedReferences(req *packp.ReferenceUpdateRequest) map[plumbing.ReferenceName]*plumbing.Reference { + refs := map[plumbing.ReferenceName]*plumbing.Reference{} + for _, cmd := range req.Commands { + exists, err := referenceExists(s.storer, cmd.Name) + if err != nil { + s.setStatus(cmd.Name, err) + continue + } + + switch cmd.Action() { + case packp.Create: + if exists { + s.setStatus(cmd.Name, ErrUpdateReference) + continue + } + + ref := plumbing.NewHashReference(cmd.Name, cmd.New) + refs[ref.Name()] = ref + case packp.Delete: + if !exists { + s.setStatus(cmd.Name, ErrUpdateReference) + continue + } + + if !s.caps.Supports(capability.DeleteRefs) { + s.setStatus(cmd.Name, fmt.Errorf("delete not supported")) + continue + } + + refs[cmd.Name] = nil + case packp.Update: + if !exists { + s.setStatus(cmd.Name, ErrUpdateReference) + continue + } + + if err != nil { + s.setStatus(cmd.Name, err) + continue + } + + ref := plumbing.NewHashReference(cmd.Name, cmd.New) + refs[ref.Name()] = ref + } + } + + return refs +} + +func (s *rpSession) failAtomicUpdate() (*packp.ReportStatus, error) { + rs := s.reportStatus() + for _, cs := range rs.CommandStatuses { + if cs.Error() == nil { + cs.Status = "atomic updated" + } + } + + return rs, s.firstErr +} + +func (s *rpSession) writePackfile(r io.ReadCloser) error { + if r == nil { + return nil + } + + if err := packfile.UpdateObjectStorage(s.storer, r); err != nil { + _ = r.Close() + return err + } + + return r.Close() +} + +func (s *rpSession) setStatus(ref plumbing.ReferenceName, err error) { + s.cmdStatus[ref] = err + if s.firstErr == nil && err != nil { + s.firstErr = err + } +} + +func (s *rpSession) reportStatus() *packp.ReportStatus { + if !s.caps.Supports(capability.ReportStatus) { + return nil + } + + rs := packp.NewReportStatus() + rs.UnpackStatus = "ok" + + if s.unpackErr != nil { + rs.UnpackStatus = s.unpackErr.Error() + } + + if s.cmdStatus == nil { + return rs + } + + for ref, err := range s.cmdStatus { + msg := "ok" + if err != nil { + msg = err.Error() + } + status := &packp.CommandStatus{ + ReferenceName: ref, + Status: msg, + } + rs.CommandStatuses = append(rs.CommandStatuses, status) + } + + return rs +} + +func (*rpSession) setSupportedCapabilities(c *capability.List) error { + if err := c.Set(capability.Agent, capability.DefaultAgent); err != nil { + return err + } + + if err := c.Set(capability.OFSDelta); err != nil { + return err + } + + return c.Set(capability.ReportStatus) +} + +func setHEAD(s storer.Storer, ar *packp.AdvRefs) error { + ref, err := s.Reference(plumbing.HEAD) + if err == plumbing.ErrReferenceNotFound { + return nil + } + + if err != nil { + return err + } + + if ref.Type() == plumbing.SymbolicReference { + if err := ar.AddReference(ref); err != nil { + return nil + } + + ref, err = storer.ResolveReference(s, ref.Target()) + if err == plumbing.ErrReferenceNotFound { + return nil + } + + if err != nil { + return err + } + } + + if ref.Type() != plumbing.HashReference { + return plumbing.ErrInvalidType + } + + h := ref.Hash() + ar.Head = &h + + return nil +} + +//TODO: add peeled references. +func setReferences(s storer.Storer, ar *packp.AdvRefs) error { + iter, err := s.IterReferences() + if err != nil { + return err + } + + return iter.ForEach(func(ref *plumbing.Reference) error { + if ref.Type() != plumbing.HashReference { + return nil + } + + ar.References[ref.Name().String()] = ref.Hash() + return nil + }) +} + +func referenceExists(s storer.ReferenceStorer, n plumbing.ReferenceName) (bool, error) { + _, err := s.Reference(n) + if err == plumbing.ErrReferenceNotFound { + return false, nil + } + + return err == nil, err +} diff --git a/plumbing/transport/server/server_test.go b/plumbing/transport/server/server_test.go new file mode 100644 index 0000000..2020fe3 --- /dev/null +++ b/plumbing/transport/server/server_test.go @@ -0,0 +1,70 @@ +package server_test + +import ( + "fmt" + "testing" + + "gopkg.in/src-d/go-git.v4/fixtures" + "gopkg.in/src-d/go-git.v4/plumbing/transport" + "gopkg.in/src-d/go-git.v4/plumbing/transport/client" + "gopkg.in/src-d/go-git.v4/plumbing/transport/server" + "gopkg.in/src-d/go-git.v4/storage/filesystem" + "gopkg.in/src-d/go-git.v4/storage/memory" + + . "gopkg.in/check.v1" +) + +func Test(t *testing.T) { TestingT(t) } + +const inprocScheme = "inproc" + +type BaseSuite struct { + fixtures.Suite + loader server.MapLoader + client transport.Transport + clientBackup transport.Transport +} + +func (s *BaseSuite) SetUpSuite(c *C) { + s.Suite.SetUpSuite(c) + s.loader = server.MapLoader{} + s.client = server.NewServer(s.loader) + s.clientBackup = client.Protocols[inprocScheme] + client.Protocols[inprocScheme] = s.client +} + +func (s *BaseSuite) TearDownSuite(c *C) { + if s.clientBackup == nil { + delete(client.Protocols, inprocScheme) + } else { + client.Protocols[inprocScheme] = s.clientBackup + } +} + +func (s *BaseSuite) prepareRepositories(c *C, basic *transport.Endpoint, + empty *transport.Endpoint, nonExistent *transport.Endpoint) { + + f := fixtures.Basic().One() + fs := f.DotGit() + path := fs.Base() + url := fmt.Sprintf("%s://%s", inprocScheme, path) + ep, err := transport.NewEndpoint(url) + c.Assert(err, IsNil) + *basic = ep + sto, err := filesystem.NewStorage(fs) + c.Assert(err, IsNil) + s.loader[ep] = sto + + path = "/empty.git" + url = fmt.Sprintf("%s://%s", inprocScheme, path) + ep, err = transport.NewEndpoint(url) + c.Assert(err, IsNil) + *empty = ep + s.loader[ep] = memory.NewStorage() + + path = "/non-existent.git" + url = fmt.Sprintf("%s://%s", inprocScheme, path) + ep, err = transport.NewEndpoint(url) + c.Assert(err, IsNil) + *nonExistent = ep +} diff --git a/plumbing/transport/server/upload_pack_test.go b/plumbing/transport/server/upload_pack_test.go new file mode 100644 index 0000000..8919e8e --- /dev/null +++ b/plumbing/transport/server/upload_pack_test.go @@ -0,0 +1,40 @@ +package server_test + +import ( + "gopkg.in/src-d/go-git.v4/plumbing/transport" + "gopkg.in/src-d/go-git.v4/plumbing/transport/test" + + . "gopkg.in/check.v1" +) + +type UploadPackSuite struct { + BaseSuite + test.UploadPackSuite +} + +var _ = Suite(&UploadPackSuite{}) + +func (s *UploadPackSuite) SetUpSuite(c *C) { + s.BaseSuite.SetUpSuite(c) + s.UploadPackSuite.Client = s.client +} + +func (s *UploadPackSuite) SetUpTest(c *C) { + s.prepareRepositories(c, &s.Endpoint, &s.EmptyEndpoint, &s.NonExistentEndpoint) +} + +// Overwritten, it's not an error in server-side. +func (s *UploadPackSuite) TestAdvertisedReferencesEmpty(c *C) { + r, err := s.Client.NewUploadPackSession(s.EmptyEndpoint) + c.Assert(err, IsNil) + ar, err := r.AdvertisedReferences() + c.Assert(err, IsNil) + c.Assert(len(ar.References), Equals, 0) +} + +// Overwritten, server returns error earlier. +func (s *UploadPackSuite) TestAdvertisedReferencesNotExists(c *C) { + r, err := s.Client.NewUploadPackSession(s.NonExistentEndpoint) + c.Assert(err, Equals, transport.ErrRepositoryNotFound) + c.Assert(r, IsNil) +} diff --git a/plumbing/transport/ssh/fetch_pack_test.go b/plumbing/transport/ssh/upload_pack_test.go index 927e9a8..8194770 100644 --- a/plumbing/transport/ssh/fetch_pack_test.go +++ b/plumbing/transport/ssh/upload_pack_test.go @@ -9,29 +9,29 @@ import ( . "gopkg.in/check.v1" ) -type FetchPackSuite struct { - test.FetchPackSuite +type UploadPackSuite struct { + test.UploadPackSuite } -var _ = Suite(&FetchPackSuite{}) +var _ = Suite(&UploadPackSuite{}) -func (s *FetchPackSuite) SetUpSuite(c *C) { +func (s *UploadPackSuite) SetUpSuite(c *C) { if os.Getenv("SSH_AUTH_SOCK") == "" { c.Skip("SSH_AUTH_SOCK is not set") } - s.FetchPackSuite.Client = DefaultClient + s.UploadPackSuite.Client = DefaultClient ep, err := transport.NewEndpoint("git@github.com:git-fixtures/basic.git") c.Assert(err, IsNil) - s.FetchPackSuite.Endpoint = ep + s.UploadPackSuite.Endpoint = ep ep, err = transport.NewEndpoint("git@github.com:git-fixtures/empty.git") c.Assert(err, IsNil) - s.FetchPackSuite.EmptyEndpoint = ep + s.UploadPackSuite.EmptyEndpoint = ep ep, err = transport.NewEndpoint("git@github.com:git-fixtures/non-existent.git") c.Assert(err, IsNil) - s.FetchPackSuite.NonExistentEndpoint = ep + s.UploadPackSuite.NonExistentEndpoint = ep } diff --git a/plumbing/transport/test/send_pack.go b/plumbing/transport/test/receive_pack.go index f880588..e798154 100644 --- a/plumbing/transport/test/send_pack.go +++ b/plumbing/transport/test/receive_pack.go @@ -19,44 +19,44 @@ import ( . "gopkg.in/check.v1" ) -type SendPackSuite struct { +type ReceivePackSuite struct { Endpoint transport.Endpoint EmptyEndpoint transport.Endpoint NonExistentEndpoint transport.Endpoint - Client transport.Client + Client transport.Transport } -func (s *SendPackSuite) TestInfoEmpty(c *C) { - r, err := s.Client.NewSendPackSession(s.EmptyEndpoint) +func (s *ReceivePackSuite) TestAdvertisedReferencesEmpty(c *C) { + r, err := s.Client.NewReceivePackSession(s.EmptyEndpoint) c.Assert(err, IsNil) defer func() { c.Assert(r.Close(), IsNil) }() - info, err := r.AdvertisedReferences() + ar, err := r.AdvertisedReferences() c.Assert(err, IsNil) - c.Assert(info.Head, IsNil) + c.Assert(ar.Head, IsNil) } -func (s *SendPackSuite) TestInfoNotExists(c *C) { - r, err := s.Client.NewSendPackSession(s.NonExistentEndpoint) +func (s *ReceivePackSuite) TestAdvertisedReferencesNotExists(c *C) { + r, err := s.Client.NewReceivePackSession(s.NonExistentEndpoint) c.Assert(err, IsNil) defer func() { c.Assert(r.Close(), IsNil) }() - info, err := r.AdvertisedReferences() + ar, err := r.AdvertisedReferences() c.Assert(err, Equals, transport.ErrRepositoryNotFound) - c.Assert(info, IsNil) + c.Assert(ar, IsNil) - r, err = s.Client.NewSendPackSession(s.NonExistentEndpoint) + r, err = s.Client.NewReceivePackSession(s.NonExistentEndpoint) c.Assert(err, IsNil) req := packp.NewReferenceUpdateRequest() req.Commands = []*packp.Command{ {"master", plumbing.ZeroHash, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")}, } - writer, err := r.SendPack(req) + writer, err := r.ReceivePack(req) c.Assert(err, Equals, transport.ErrRepositoryNotFound) c.Assert(writer, IsNil) } -func (s *SendPackSuite) TestCallAdvertisedReferenceTwice(c *C) { - r, err := s.Client.NewSendPackSession(s.Endpoint) +func (s *ReceivePackSuite) TestCallAdvertisedReferenceTwice(c *C) { + r, err := s.Client.NewReceivePackSession(s.Endpoint) c.Assert(err, IsNil) ar1, err := r.AdvertisedReferences() c.Assert(err, IsNil) @@ -66,8 +66,8 @@ func (s *SendPackSuite) TestCallAdvertisedReferenceTwice(c *C) { c.Assert(ar2, DeepEquals, ar1) } -func (s *SendPackSuite) TestDefaultBranch(c *C) { - r, err := s.Client.NewSendPackSession(s.Endpoint) +func (s *ReceivePackSuite) TestDefaultBranch(c *C) { + r, err := s.Client.NewReceivePackSession(s.Endpoint) c.Assert(err, IsNil) defer func() { c.Assert(r.Close(), IsNil) }() @@ -78,8 +78,8 @@ func (s *SendPackSuite) TestDefaultBranch(c *C) { c.Assert(ref, Equals, fixtures.Basic().One().Head) } -func (s *SendPackSuite) TestCapabilities(c *C) { - r, err := s.Client.NewSendPackSession(s.Endpoint) +func (s *ReceivePackSuite) TestCapabilities(c *C) { + r, err := s.Client.NewReceivePackSession(s.Endpoint) c.Assert(err, IsNil) defer func() { c.Assert(r.Close(), IsNil) }() @@ -88,7 +88,7 @@ func (s *SendPackSuite) TestCapabilities(c *C) { c.Assert(info.Capabilities.Get("agent"), HasLen, 1) } -func (s *SendPackSuite) TestFullSendPackOnEmpty(c *C) { +func (s *ReceivePackSuite) TestFullSendPackOnEmpty(c *C) { endpoint := s.EmptyEndpoint full := true fixture := fixtures.Basic().ByTag("packfile").One() @@ -96,11 +96,11 @@ func (s *SendPackSuite) TestFullSendPackOnEmpty(c *C) { req.Commands = []*packp.Command{ {"refs/heads/master", plumbing.ZeroHash, fixture.Head}, } - s.sendPack(c, endpoint, req, fixture, full) + s.receivePack(c, endpoint, req, fixture, full) s.checkRemoteHead(c, endpoint, fixture.Head) } -func (s *SendPackSuite) TestSendPackOnEmpty(c *C) { +func (s *ReceivePackSuite) TestSendPackOnEmpty(c *C) { endpoint := s.EmptyEndpoint full := false fixture := fixtures.Basic().ByTag("packfile").One() @@ -108,11 +108,11 @@ func (s *SendPackSuite) TestSendPackOnEmpty(c *C) { req.Commands = []*packp.Command{ {"refs/heads/master", plumbing.ZeroHash, fixture.Head}, } - s.sendPack(c, endpoint, req, fixture, full) + s.receivePack(c, endpoint, req, fixture, full) s.checkRemoteHead(c, endpoint, fixture.Head) } -func (s *SendPackSuite) TestSendPackOnEmptyWithReportStatus(c *C) { +func (s *ReceivePackSuite) TestSendPackOnEmptyWithReportStatus(c *C) { endpoint := s.EmptyEndpoint full := false fixture := fixtures.Basic().ByTag("packfile").One() @@ -121,11 +121,11 @@ func (s *SendPackSuite) TestSendPackOnEmptyWithReportStatus(c *C) { {"refs/heads/master", plumbing.ZeroHash, fixture.Head}, } req.Capabilities.Set(capability.ReportStatus) - s.sendPack(c, endpoint, req, fixture, full) + s.receivePack(c, endpoint, req, fixture, full) s.checkRemoteHead(c, endpoint, fixture.Head) } -func (s *SendPackSuite) TestFullSendPackOnNonEmpty(c *C) { +func (s *ReceivePackSuite) TestFullSendPackOnNonEmpty(c *C) { endpoint := s.Endpoint full := true fixture := fixtures.Basic().ByTag("packfile").One() @@ -133,11 +133,11 @@ func (s *SendPackSuite) TestFullSendPackOnNonEmpty(c *C) { req.Commands = []*packp.Command{ {"refs/heads/master", fixture.Head, fixture.Head}, } - s.sendPack(c, endpoint, req, fixture, full) + s.receivePack(c, endpoint, req, fixture, full) s.checkRemoteHead(c, endpoint, fixture.Head) } -func (s *SendPackSuite) TestSendPackOnNonEmpty(c *C) { +func (s *ReceivePackSuite) TestSendPackOnNonEmpty(c *C) { endpoint := s.Endpoint full := false fixture := fixtures.Basic().ByTag("packfile").One() @@ -145,11 +145,11 @@ func (s *SendPackSuite) TestSendPackOnNonEmpty(c *C) { req.Commands = []*packp.Command{ {"refs/heads/master", fixture.Head, fixture.Head}, } - s.sendPack(c, endpoint, req, fixture, full) + s.receivePack(c, endpoint, req, fixture, full) s.checkRemoteHead(c, endpoint, fixture.Head) } -func (s *SendPackSuite) TestSendPackOnNonEmptyWithReportStatus(c *C) { +func (s *ReceivePackSuite) TestSendPackOnNonEmptyWithReportStatus(c *C) { endpoint := s.Endpoint full := false fixture := fixtures.Basic().ByTag("packfile").One() @@ -159,11 +159,11 @@ func (s *SendPackSuite) TestSendPackOnNonEmptyWithReportStatus(c *C) { } req.Capabilities.Set(capability.ReportStatus) - s.sendPack(c, endpoint, req, fixture, full) + s.receivePack(c, endpoint, req, fixture, full) s.checkRemoteHead(c, endpoint, fixture.Head) } -func (s *SendPackSuite) TestSendPackOnNonEmptyWithReportStatusWithError(c *C) { +func (s *ReceivePackSuite) TestSendPackOnNonEmptyWithReportStatusWithError(c *C) { endpoint := s.Endpoint full := false fixture := fixtures.Basic().ByTag("packfile").One() @@ -173,7 +173,7 @@ func (s *SendPackSuite) TestSendPackOnNonEmptyWithReportStatusWithError(c *C) { } req.Capabilities.Set(capability.ReportStatus) - report, err := s.sendPackNoCheck(c, endpoint, req, fixture, full) + report, err := s.receivePackNoCheck(c, endpoint, req, fixture, full) //XXX: Recent git versions return "failed to update ref", while older // (>=1.9) return "failed to lock". c.Assert(err, ErrorMatches, ".*(failed to update ref|failed to lock).*") @@ -184,7 +184,7 @@ func (s *SendPackSuite) TestSendPackOnNonEmptyWithReportStatusWithError(c *C) { s.checkRemoteHead(c, endpoint, fixture.Head) } -func (s *SendPackSuite) sendPackNoCheck(c *C, ep transport.Endpoint, +func (s *ReceivePackSuite) receivePackNoCheck(c *C, ep transport.Endpoint, req *packp.ReferenceUpdateRequest, fixture *fixtures.Fixture, callAdvertisedReferences bool) (*packp.ReportStatus, error) { url := "" @@ -196,7 +196,7 @@ func (s *SendPackSuite) sendPackNoCheck(c *C, ep transport.Endpoint, ep.String(), url, callAdvertisedReferences, ) - r, err := s.Client.NewSendPackSession(ep) + r, err := s.Client.NewReceivePackSession(ep) c.Assert(err, IsNil, comment) defer func() { c.Assert(r.Close(), IsNil, comment) }() @@ -213,10 +213,10 @@ func (s *SendPackSuite) sendPackNoCheck(c *C, ep transport.Endpoint, req.Packfile = s.emptyPackfile() } - return r.SendPack(req) + return r.ReceivePack(req) } -func (s *SendPackSuite) sendPack(c *C, ep transport.Endpoint, +func (s *ReceivePackSuite) receivePack(c *C, ep transport.Endpoint, req *packp.ReferenceUpdateRequest, fixture *fixtures.Fixture, callAdvertisedReferences bool) { @@ -229,7 +229,7 @@ func (s *SendPackSuite) sendPack(c *C, ep transport.Endpoint, "failed with ep=%s fixture=%s callAdvertisedReferences=%s", ep.String(), url, callAdvertisedReferences, ) - report, err := s.sendPackNoCheck(c, ep, req, fixture, callAdvertisedReferences) + report, err := s.receivePackNoCheck(c, ep, req, fixture, callAdvertisedReferences) c.Assert(err, IsNil, comment) if req.Capabilities.Supports(capability.ReportStatus) { @@ -240,14 +240,14 @@ func (s *SendPackSuite) sendPack(c *C, ep transport.Endpoint, } } -func (s *SendPackSuite) checkRemoteHead(c *C, ep transport.Endpoint, head plumbing.Hash) { +func (s *ReceivePackSuite) checkRemoteHead(c *C, ep transport.Endpoint, head plumbing.Hash) { s.checkRemoteReference(c, ep, "refs/heads/master", head) } -func (s *SendPackSuite) checkRemoteReference(c *C, ep transport.Endpoint, +func (s *ReceivePackSuite) checkRemoteReference(c *C, ep transport.Endpoint, refName string, head plumbing.Hash) { - r, err := s.Client.NewFetchPackSession(ep) + r, err := s.Client.NewUploadPackSession(ep) c.Assert(err, IsNil) defer func() { c.Assert(r.Close(), IsNil) }() ar, err := r.AdvertisedReferences() @@ -261,13 +261,13 @@ func (s *SendPackSuite) checkRemoteReference(c *C, ep transport.Endpoint, } } -func (s *SendPackSuite) TestSendPackAddDeleteReference(c *C) { +func (s *ReceivePackSuite) TestSendPackAddDeleteReference(c *C) { s.testSendPackAddReference(c) s.testSendPackDeleteReference(c) } -func (s *SendPackSuite) testSendPackAddReference(c *C) { - r, err := s.Client.NewSendPackSession(s.Endpoint) +func (s *ReceivePackSuite) testSendPackAddReference(c *C) { + r, err := s.Client.NewReceivePackSession(s.Endpoint) c.Assert(err, IsNil) defer func() { c.Assert(r.Close(), IsNil) }() @@ -284,12 +284,12 @@ func (s *SendPackSuite) testSendPackAddReference(c *C) { req.Capabilities.Set(capability.ReportStatus) } - s.sendPack(c, s.Endpoint, req, nil, false) + s.receivePack(c, s.Endpoint, req, nil, false) s.checkRemoteReference(c, s.Endpoint, "refs/heads/newbranch", fixture.Head) } -func (s *SendPackSuite) testSendPackDeleteReference(c *C) { - r, err := s.Client.NewSendPackSession(s.Endpoint) +func (s *ReceivePackSuite) testSendPackDeleteReference(c *C) { + r, err := s.Client.NewReceivePackSession(s.Endpoint) c.Assert(err, IsNil) defer func() { c.Assert(r.Close(), IsNil) }() @@ -306,11 +306,11 @@ func (s *SendPackSuite) testSendPackDeleteReference(c *C) { req.Capabilities.Set(capability.ReportStatus) } - s.sendPack(c, s.Endpoint, req, nil, false) + s.receivePack(c, s.Endpoint, req, nil, false) s.checkRemoteReference(c, s.Endpoint, "refs/heads/newbranch", plumbing.ZeroHash) } -func (s *SendPackSuite) emptyPackfile() io.ReadCloser { +func (s *ReceivePackSuite) emptyPackfile() io.ReadCloser { var buf bytes.Buffer e := packfile.NewEncoder(&buf, memory.NewStorage(), false) _, err := e.Encode(nil) diff --git a/plumbing/transport/test/fetch_pack.go b/plumbing/transport/test/upload_pack.go index 2984154..5af4b29 100644 --- a/plumbing/transport/test/fetch_pack.go +++ b/plumbing/transport/test/upload_pack.go @@ -18,39 +18,39 @@ import ( "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability" ) -type FetchPackSuite struct { +type UploadPackSuite struct { Endpoint transport.Endpoint EmptyEndpoint transport.Endpoint NonExistentEndpoint transport.Endpoint - Client transport.Client + Client transport.Transport } -func (s *FetchPackSuite) TestInfoEmpty(c *C) { - r, err := s.Client.NewFetchPackSession(s.EmptyEndpoint) +func (s *UploadPackSuite) TestAdvertisedReferencesEmpty(c *C) { + r, err := s.Client.NewUploadPackSession(s.EmptyEndpoint) c.Assert(err, IsNil) - info, err := r.AdvertisedReferences() + ar, err := r.AdvertisedReferences() c.Assert(err, Equals, transport.ErrEmptyRemoteRepository) - c.Assert(info, IsNil) + c.Assert(ar, IsNil) } -func (s *FetchPackSuite) TestInfoNotExists(c *C) { - r, err := s.Client.NewFetchPackSession(s.NonExistentEndpoint) +func (s *UploadPackSuite) TestAdvertisedReferencesNotExists(c *C) { + r, err := s.Client.NewUploadPackSession(s.NonExistentEndpoint) c.Assert(err, IsNil) - info, err := r.AdvertisedReferences() + ar, err := r.AdvertisedReferences() c.Assert(err, Equals, transport.ErrRepositoryNotFound) - c.Assert(info, IsNil) + c.Assert(ar, IsNil) - r, err = s.Client.NewFetchPackSession(s.NonExistentEndpoint) + r, err = s.Client.NewUploadPackSession(s.NonExistentEndpoint) c.Assert(err, IsNil) req := packp.NewUploadPackRequest() req.Wants = append(req.Wants, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) - reader, err := r.FetchPack(req) + reader, err := r.UploadPack(req) c.Assert(err, Equals, transport.ErrRepositoryNotFound) c.Assert(reader, IsNil) } -func (s *FetchPackSuite) TestCallAdvertisedReferenceTwice(c *C) { - r, err := s.Client.NewFetchPackSession(s.Endpoint) +func (s *UploadPackSuite) TestCallAdvertisedReferenceTwice(c *C) { + r, err := s.Client.NewUploadPackSession(s.Endpoint) c.Assert(err, IsNil) ar1, err := r.AdvertisedReferences() c.Assert(err, IsNil) @@ -60,8 +60,8 @@ func (s *FetchPackSuite) TestCallAdvertisedReferenceTwice(c *C) { c.Assert(ar2, DeepEquals, ar1) } -func (s *FetchPackSuite) TestDefaultBranch(c *C) { - r, err := s.Client.NewFetchPackSession(s.Endpoint) +func (s *UploadPackSuite) TestDefaultBranch(c *C) { + r, err := s.Client.NewUploadPackSession(s.Endpoint) c.Assert(err, IsNil) defer func() { c.Assert(r.Close(), IsNil) }() @@ -72,8 +72,8 @@ func (s *FetchPackSuite) TestDefaultBranch(c *C) { c.Assert(symrefs[0], Equals, "HEAD:refs/heads/master") } -func (s *FetchPackSuite) TestAdvertisedReferencesFilterUnsupported(c *C) { - r, err := s.Client.NewFetchPackSession(s.Endpoint) +func (s *UploadPackSuite) TestAdvertisedReferencesFilterUnsupported(c *C) { + r, err := s.Client.NewUploadPackSession(s.Endpoint) c.Assert(err, IsNil) defer func() { c.Assert(r.Close(), IsNil) }() @@ -82,8 +82,8 @@ func (s *FetchPackSuite) TestAdvertisedReferencesFilterUnsupported(c *C) { c.Assert(info.Capabilities.Supports(capability.MultiACK), Equals, false) } -func (s *FetchPackSuite) TestCapabilities(c *C) { - r, err := s.Client.NewFetchPackSession(s.Endpoint) +func (s *UploadPackSuite) TestCapabilities(c *C) { + r, err := s.Client.NewUploadPackSession(s.Endpoint) c.Assert(err, IsNil) defer func() { c.Assert(r.Close(), IsNil) }() @@ -92,8 +92,8 @@ func (s *FetchPackSuite) TestCapabilities(c *C) { c.Assert(info.Capabilities.Get(capability.Agent), HasLen, 1) } -func (s *FetchPackSuite) TestFullFetchPack(c *C) { - r, err := s.Client.NewFetchPackSession(s.Endpoint) +func (s *UploadPackSuite) TestFullUploadPack(c *C) { + r, err := s.Client.NewUploadPackSession(s.Endpoint) c.Assert(err, IsNil) defer func() { c.Assert(r.Close(), IsNil) }() @@ -104,28 +104,28 @@ func (s *FetchPackSuite) TestFullFetchPack(c *C) { req := packp.NewUploadPackRequest() req.Wants = append(req.Wants, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) - reader, err := r.FetchPack(req) + reader, err := r.UploadPack(req) c.Assert(err, IsNil) s.checkObjectNumber(c, reader, 28) } -func (s *FetchPackSuite) TestFetchPack(c *C) { - r, err := s.Client.NewFetchPackSession(s.Endpoint) +func (s *UploadPackSuite) TestUploadPack(c *C) { + r, err := s.Client.NewUploadPackSession(s.Endpoint) c.Assert(err, IsNil) defer func() { c.Assert(r.Close(), IsNil) }() req := packp.NewUploadPackRequest() req.Wants = append(req.Wants, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) - reader, err := r.FetchPack(req) + reader, err := r.UploadPack(req) c.Assert(err, IsNil) s.checkObjectNumber(c, reader, 28) } -func (s *FetchPackSuite) TestFetchPackInvalidReq(c *C) { - r, err := s.Client.NewFetchPackSession(s.Endpoint) +func (s *UploadPackSuite) TestUploadPackInvalidReq(c *C) { + r, err := s.Client.NewUploadPackSession(s.Endpoint) c.Assert(err, IsNil) defer func() { c.Assert(r.Close(), IsNil) }() @@ -134,12 +134,12 @@ func (s *FetchPackSuite) TestFetchPackInvalidReq(c *C) { req.Capabilities.Set(capability.Sideband) req.Capabilities.Set(capability.Sideband64k) - _, err = r.FetchPack(req) + _, err = r.UploadPack(req) c.Assert(err, NotNil) } -func (s *FetchPackSuite) TestFetchPackNoChanges(c *C) { - r, err := s.Client.NewFetchPackSession(s.Endpoint) +func (s *UploadPackSuite) TestUploadPackNoChanges(c *C) { + r, err := s.Client.NewUploadPackSession(s.Endpoint) c.Assert(err, IsNil) defer func() { c.Assert(r.Close(), IsNil) }() @@ -147,13 +147,13 @@ func (s *FetchPackSuite) TestFetchPackNoChanges(c *C) { req.Wants = append(req.Wants, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) req.Haves = append(req.Haves, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) - reader, err := r.FetchPack(req) + reader, err := r.UploadPack(req) c.Assert(err, Equals, transport.ErrEmptyUploadPackRequest) c.Assert(reader, IsNil) } -func (s *FetchPackSuite) TestFetchPackMulti(c *C) { - r, err := s.Client.NewFetchPackSession(s.Endpoint) +func (s *UploadPackSuite) TestUploadPackMulti(c *C) { + r, err := s.Client.NewUploadPackSession(s.Endpoint) c.Assert(err, IsNil) defer func() { c.Assert(r.Close(), IsNil) }() @@ -161,28 +161,28 @@ func (s *FetchPackSuite) TestFetchPackMulti(c *C) { req.Wants = append(req.Wants, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) req.Wants = append(req.Wants, plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881")) - reader, err := r.FetchPack(req) + reader, err := r.UploadPack(req) c.Assert(err, IsNil) s.checkObjectNumber(c, reader, 31) } -func (s *FetchPackSuite) TestFetchError(c *C) { - r, err := s.Client.NewFetchPackSession(s.Endpoint) +func (s *UploadPackSuite) TestFetchError(c *C) { + r, err := s.Client.NewUploadPackSession(s.Endpoint) c.Assert(err, IsNil) req := packp.NewUploadPackRequest() req.Wants = append(req.Wants, plumbing.NewHash("1111111111111111111111111111111111111111")) - reader, err := r.FetchPack(req) - c.Assert(err, Equals, transport.ErrEmptyUploadPackRequest) + reader, err := r.UploadPack(req) + c.Assert(err, NotNil) c.Assert(reader, IsNil) //XXX: We do not test Close error, since implementations might return // different errors if a previous error was found. } -func (s *FetchPackSuite) checkObjectNumber(c *C, r io.Reader, n int) { +func (s *UploadPackSuite) checkObjectNumber(c *C, r io.Reader, n int) { b, err := ioutil.ReadAll(r) c.Assert(err, IsNil) buf := bytes.NewBuffer(b) @@ -130,7 +130,7 @@ func (r *Remote) fetch(o *FetchOptions) (refs storer.ReferenceStorer, err error) o.RefSpecs = r.c.Fetch } - s, err := newFetchPackSession(r.c.URL) + s, err := newUploadPackSession(r.c.URL) if err != nil { return nil, err } @@ -173,25 +173,25 @@ func (r *Remote) fetch(o *FetchOptions) (refs storer.ReferenceStorer, err error) return remoteRefs, err } -func newFetchPackSession(url string) (transport.FetchPackSession, error) { +func newUploadPackSession(url string) (transport.UploadPackSession, error) { c, ep, err := newClient(url) if err != nil { return nil, err } - return c.NewFetchPackSession(ep) + return c.NewUploadPackSession(ep) } -func newSendPackSession(url string) (transport.SendPackSession, error) { +func newSendPackSession(url string) (transport.ReceivePackSession, error) { c, ep, err := newClient(url) if err != nil { return nil, err } - return c.NewSendPackSession(ep) + return c.NewReceivePackSession(ep) } -func newClient(url string) (transport.Client, transport.Endpoint, error) { +func newClient(url string) (transport.Transport, transport.Endpoint, error) { ep, err := transport.NewEndpoint(url) if err != nil { return nil, transport.Endpoint{}, err @@ -205,10 +205,10 @@ func newClient(url string) (transport.Client, transport.Endpoint, error) { return c, ep, err } -func (r *Remote) fetchPack(o *FetchOptions, s transport.FetchPackSession, +func (r *Remote) fetchPack(o *FetchOptions, s transport.UploadPackSession, req *packp.UploadPackRequest) (err error) { - reader, err := s.FetchPack(req) + reader, err := s.UploadPack(req) if err != nil { return err } @@ -219,7 +219,7 @@ func (r *Remote) fetchPack(o *FetchOptions, s transport.FetchPackSession, return err } - if err = r.updateObjectStorage( + if err = packfile.UpdateObjectStorage(r.s, buildSidebandIfSupported(req.Capabilities, reader, r.p), ); err != nil { return err @@ -406,28 +406,6 @@ func (r *Remote) newUploadPackRequest(o *FetchOptions, return req, nil } -func (r *Remote) updateObjectStorage(reader io.Reader) error { - if sw, ok := r.s.(storer.PackfileWriter); ok { - w, err := sw.PackfileWriter() - if err != nil { - return err - } - - defer w.Close() - _, err = io.Copy(w, reader) - return err - } - - stream := packfile.NewScanner(reader) - d, err := packfile.NewDecoder(stream, r.s) - if err != nil { - return err - } - - _, err = d.Decode() - return err -} - func buildSidebandIfSupported(l *capability.List, reader io.Reader, p sideband.Progress) io.Reader { var t sideband.Type @@ -532,7 +510,7 @@ func referencesToHashes(refs storer.ReferenceStorer) ([]plumbing.Hash, error) { return hs, nil } -func pushHashes(sess transport.SendPackSession, sto storer.EncodedObjectStorer, +func pushHashes(sess transport.ReceivePackSession, sto storer.EncodedObjectStorer, req *packp.ReferenceUpdateRequest, hs []plumbing.Hash) (*packp.ReportStatus, error) { rd, wr := io.Pipe() @@ -548,7 +526,7 @@ func pushHashes(sess transport.SendPackSession, sto storer.EncodedObjectStorer, done <- wr.Close() }() - rs, err := sess.SendPack(req) + rs, err := sess.ReceivePack(req) if err != nil { return nil, err } |