client

package
v0.12.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 3, 2024 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package client provides a Git packfile protocol client for sending and receiving Git objects. The packfile protocol is used in the `git fetch` and `git push` commands. See https://git-scm.com/docs/pack-protocol for more detailed information.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func ParseURL

func ParseURL(urlstr string) (*url.URL, error)

ParseURL parses a Git remote URL, including the alternative SCP syntax. See git-fetch(1) for details.

func URLFromPath

func URLFromPath(path string) *url.URL

URLFromPath converts a filesystem path into a URL. If it's a relative path, then it returns a path-only URL.

Types

type Options

type Options struct {
	HTTPClient        *http.Client // defaults to http.DefaultClient
	HTTPAuthorization string
	UserAgent         string
}

Options holds optional arguments for creating a Remote.

type PullCapabilities

type PullCapabilities uint64

PullCapabilities is a bitset of capabilities that a remote supports for pulling.

const (
	PullCapShallow        PullCapabilities = 1 << iota // shallow
	PullCapDepthRelative                               // deepen-relative
	PullCapSince                                       // deepen-since
	PullCapShallowExclude                              // deepen-not
	PullCapFilter                                      // filter
	PullCapIncludeTag                                  // include-tag
	PullCapThinPack                                    // thin-pack

)

Pull capabilities. See https://git-scm.com/docs/protocol-capabilities for descriptions.

func (PullCapabilities) Has

func (caps PullCapabilities) Has(mask PullCapabilities) bool

Has reports whether caps includes all of the capabilities in mask.

func (PullCapabilities) String

func (caps PullCapabilities) String() string

String returns a |-separated list of the capability constant names present in caps.

type PullRequest

type PullRequest struct {
	// Want is the set of commits to send. At least one must be specified,
	// or SendRequest will return an error.
	Want []githash.SHA1
	// Have is a set of commits that the remote can exclude from the packfile.
	// It may be empty for a full clone. The remote will also avoid sending any
	// trees and blobs used in the Have commits and any of their ancestors, even
	// if they're used in returned commits.
	Have []githash.SHA1
	// If HaveMore is true, then the response will not return a packfile if the
	// remote hasn't found a suitable base.
	HaveMore bool

	// Progress will receive progress messages from the remote while the caller
	// reads the packfile. It may be nil.
	Progress io.Writer

	// Shallow is the set of object IDs that the client does not have the parent
	// commits of. This is only supported by the remote if it has PullCapShallow.
	Shallow []githash.SHA1

	// If Depth is greater than zero, it limits the depth of the commits pulled.
	// It is mutually exclusive with Since. This is only supported by the remote
	// if it has PullCapShallow.
	Depth int
	// If DepthRelative is true, Depth is interpreted as relative to the client's
	// shallow boundary. Otherwise, it is interpreted as relative to the commits
	// in Want. This is only supported by the remote if it has PullCapDepthRelative.
	DepthRelative bool
	// Since requests that the shallow clone/pull should be cut at a specific
	// time. It is mutually exclusive with Depth.  This is only supported by the
	// remote if it has PullCapSince.
	Since time.Time
	// ShallowExclude is a set of revisions that the remote will exclude from
	// the packfile. Unlike Have, the remote will send any needed trees and
	// blobs even if they are shared with the revisions in ShallowExclude.
	// It is mutually exclusive with Depth, but not Since. This is only supported
	// by the remote if it has PullCapShallowExclude.
	ShallowExclude []string

	// Filter filters objects in the packfile based on a filter-spec as defined
	// in git-rev-list(1). This is only supported by the remote if it has PullCapFilter.
	Filter string

	// IncludeTag indicates whether annotated tags should be sent if the objects
	// they point to are being sent. This is only supported by the remote if it
	// has PullCapIncludeTag.
	IncludeTag bool

	// ThinPack requests that a thin pack be sent, which is a pack with deltas
	// which reference base objects not contained within the pack (but are known
	// to exist at the receiving end). This can reduce the network traffic
	// significantly, but it requires the receiving end to know how to "thicken"
	// these packs by adding the missing bases to the pack. This is only supported
	// by the remote if it has PullCapThinPack.
	ThinPack bool
}

A PullRequest informs the remote which objects to include in the packfile.

type PullResponse

type PullResponse struct {
	// Packfile is a packfile stream. It may be nil if the request set HaveMore
	// and the remote didn't find a suitable base.
	//
	// Any progress messages sent by the remote will be written to the Progress
	// writer specified in the request while reading from Packfile.
	Packfile io.ReadCloser
	// Acks indicates which of the Have objects from the request that the remote
	// shares. It may not be populated if Packfile is not nil.
	Acks map[githash.SHA1]struct{}
	// Shallow indicates each commit sent whose parents will not be in the
	// packfile. If a commit hash is in the Shallow map but its value is false,
	// it means that the request indicated the commit was shallow, but its parents
	// are present in the packfile.
	Shallow map[githash.SHA1]bool
}

A PullResponse holds the remote response to a round of negotiation.

type PullStream

type PullStream struct {
	// contains filtered or unexported fields
}

PullStream represents a git-upload-pack session.

Example (Clone)

This example connects to a remote, lists all the refs, and requests for them all to be downloaded. This is suitable for performing the initial clone.

package main

import (
	"bufio"
	"context"
	"crypto/sha1"
	"errors"
	"fmt"
	"io"
	"os"

	"gg-scm.io/pkg/git/githash"
	"gg-scm.io/pkg/git/object"
	"gg-scm.io/pkg/git/packfile"
	"gg-scm.io/pkg/git/packfile/client"
)

func main() {
	// Create a remote for the URL.
	ctx := context.Background()
	u, err := client.ParseURL("https://example.com/my/repo.git")
	if err != nil {
		// handle error
	}
	remote, err := client.NewRemote(u, nil)
	if err != nil {
		// handle error
	}

	// Open a connection for pulling objects.
	stream, err := remote.StartPull(ctx)
	if err != nil {
		// handle error
	}
	defer stream.Close()

	// Gather object IDs to pull.
	refs, err := stream.ListRefs()
	if err != nil {
		// handle error
	}
	var want []githash.SHA1
	for _, r := range refs {
		want = append(want, r.ObjectID)
	}

	// Start pulling from remote.
	response, err := stream.Negotiate(&client.PullRequest{
		Want:     want,
		Progress: os.Stdout,
	})
	if err != nil {
		// handle error
	}
	defer response.Packfile.Close()

	// Read the packfile and print commit IDs.
	packReader := packfile.NewReader(bufio.NewReader(response.Packfile))
	for {
		hdr, err := packReader.Next()
		if errors.Is(err, io.EOF) {
			break
		}
		if err != nil {
			// handle error
		}
		if hdr.Type == packfile.Commit {
			// Hash the object to get the ID.
			h := sha1.New()
			h.Write(object.AppendPrefix(nil, object.TypeCommit, hdr.Size))
			if _, err := io.Copy(h, packReader); err != nil {
				// handle error
			}
			var commitID githash.SHA1
			h.Sum(commitID[:0])
			fmt.Println(commitID)
		}
	}
}
Output:

Example (SingleCommit)

This example connects to a remote and requests only the objects for a single commit.

package main

import (
	"bufio"
	"context"
	"crypto/sha1"
	"errors"
	"fmt"
	"io"
	"os"

	"gg-scm.io/pkg/git/githash"
	"gg-scm.io/pkg/git/object"
	"gg-scm.io/pkg/git/packfile"
	"gg-scm.io/pkg/git/packfile/client"
)

func main() {
	// Create a remote for the URL.
	ctx := context.Background()
	u, err := client.ParseURL("https://github.com/gg-scm/gg-git.git")
	if err != nil {
		// handle error
	}
	remote, err := client.NewRemote(u, nil)
	if err != nil {
		// handle error
	}

	// Open a connection for pulling objects.
	stream, err := remote.StartPull(ctx)
	if err != nil {
		// handle error
	}
	defer stream.Close()
	if !stream.Capabilities().Has(client.PullCapShallow) {
		fmt.Fprintln(os.Stderr, "Remote does not support shallow clones!")
		return
	}

	// Start pulling from remote.
	want, err := githash.ParseSHA1("c8ede9119a7188f2564d3b7257fa526c9285c23f")
	if err != nil {
		// handle error
	}
	response, err := stream.Negotiate(&client.PullRequest{
		Want:     []githash.SHA1{want},
		Depth:    1,
		Progress: os.Stderr,
	})
	if err != nil {
		// handle error
	}
	defer response.Packfile.Close()

	// Read the packfile and print commit IDs.
	packReader := packfile.NewReader(bufio.NewReader(response.Packfile))
	for {
		hdr, err := packReader.Next()
		if errors.Is(err, io.EOF) {
			break
		}
		if err != nil {
			// handle error
		}
		if hdr.Type == packfile.Commit {
			// Hash the object to get the ID.
			h := sha1.New()
			h.Write(object.AppendPrefix(nil, object.TypeCommit, hdr.Size))
			if _, err := io.Copy(h, packReader); err != nil {
				// handle error
			}
			var commitID githash.SHA1
			h.Sum(commitID[:0])
			fmt.Println(commitID)
		}
	}

}
Output:

c8ede9119a7188f2564d3b7257fa526c9285c23f
Example (SingleRef)

This example connects to a remote, finds a single desired ref, and requests only objects for that ref to be downloaded.

package main

import (
	"bufio"
	"context"
	"crypto/sha1"
	"errors"
	"fmt"
	"io"
	"os"

	"gg-scm.io/pkg/git/githash"
	"gg-scm.io/pkg/git/object"
	"gg-scm.io/pkg/git/packfile"
	"gg-scm.io/pkg/git/packfile/client"
)

func main() {
	// Create a remote for the URL.
	ctx := context.Background()
	u, err := client.ParseURL("https://example.com/my/repo.git")
	if err != nil {
		// handle error
	}
	remote, err := client.NewRemote(u, nil)
	if err != nil {
		// handle error
	}

	// Open a connection for pulling objects.
	stream, err := remote.StartPull(ctx)
	if err != nil {
		// handle error
	}
	defer stream.Close()

	// Find the HEAD ref to pull.
	refs, err := stream.ListRefs()
	if err != nil {
		// handle error
	}
	headRef := refs[githash.Head]
	if headRef == nil {
		fmt.Fprintln(os.Stderr, "No HEAD found!")
		return
	}

	// Start pulling from remote.
	response, err := stream.Negotiate(&client.PullRequest{
		Want:     []githash.SHA1{headRef.ObjectID},
		Progress: os.Stdout,
	})
	if err != nil {
		// handle error
	}
	defer response.Packfile.Close()

	// Read the packfile and print commit IDs.
	packReader := packfile.NewReader(bufio.NewReader(response.Packfile))
	for {
		hdr, err := packReader.Next()
		if errors.Is(err, io.EOF) {
			break
		}
		if err != nil {
			// handle error
		}
		if hdr.Type == packfile.Commit {
			// Hash the object to get the ID.
			h := sha1.New()
			h.Write(object.AppendPrefix(nil, object.TypeCommit, hdr.Size))
			if _, err := io.Copy(h, packReader); err != nil {
				// handle error
			}
			var commitID githash.SHA1
			h.Sum(commitID[:0])
			fmt.Println(commitID)
		}
	}
}
Output:

func (*PullStream) Capabilities

func (p *PullStream) Capabilities() PullCapabilities

Capabilities returns the set of pull request fields the remote supports.

func (*PullStream) Close

func (p *PullStream) Close() error

Close releases any resources used by the stream.

func (*PullStream) ListRefs

func (p *PullStream) ListRefs(refPrefixes ...string) (map[githash.Ref]*Ref, error)

ListRefs lists the remote's references. If refPrefixes is given, then only refs that start with one of the given strings are returned.

If you need to call both ListRefs and Negotiate on a stream, you should call ListRefs first. Older Git servers send their refs upfront

func (*PullStream) Negotiate

func (p *PullStream) Negotiate(req *PullRequest) (*PullResponse, error)

Negotiate requests a packfile from the remote. It must be called before calling the Read method.

type PushCommand

type PushCommand struct {
	RefName githash.Ref
	Old     githash.SHA1 // if not set, then create the ref
	New     githash.SHA1 // if not set, then delete the ref
}

A PushCommand is an instruction to update a remote ref. At least one of Old or New must be set.

func (*PushCommand) String

func (cmd *PushCommand) String() string

String returns the wire representation of the push command.

type PushStream

type PushStream struct {
	// contains filtered or unexported fields
}

PushStream represents a git-receive-pack session.

Example
package main

import (
	"context"
	"fmt"
	"os"
	"time"

	"gg-scm.io/pkg/git/githash"
	"gg-scm.io/pkg/git/object"
	"gg-scm.io/pkg/git/packfile"
	"gg-scm.io/pkg/git/packfile/client"
)

func main() {
	// Create a remote for the URL.
	ctx := context.Background()
	u, err := client.ParseURL("https://example.com/my/repo.git")
	if err != nil {
		// handle error
	}
	remote, err := client.NewRemote(u, nil)
	if err != nil {
		// handle error
	}

	// Open a connection for sending objects.
	stream, err := remote.StartPush(ctx)
	if err != nil {
		// handle error
	}
	defer func() {
		if err := stream.Close(); err != nil {
			// handle error
		}
	}()

	// Find the current commit for the main branch.
	// You should check that the ref is currently pointing to an ancestor of the
	// commit you want to push to that ref. Otherwise, you are performing a
	// force push.
	curr := stream.Refs()[githash.BranchRef("main")]
	if curr == nil {
		fmt.Fprintln(os.Stderr, "main branch not found!")
		return
	}

	// Create a new commit that deletes the entire tree.
	const author = "<foo@example.com>"
	now := time.Now()
	newTree := object.Tree(nil)
	newCommit := &object.Commit{
		Tree:       newTree.SHA1(),
		Parents:    []githash.SHA1{curr.ObjectID},
		Author:     author,
		AuthorTime: now,
		Committer:  author,
		CommitTime: now,
		Message:    "Delete ALL THE THINGS!",
	}

	// Start the push. First inform the remote of refs that we intend to change.
	err = stream.WriteCommands(&client.PushCommand{
		RefName: curr.Name,
		Old:     curr.ObjectID,
		New:     newCommit.SHA1(),
	})
	if err != nil {
		// handle error
	}

	// Write a packfile with the new objects to the stream.
	// 1. Write the tree.
	packWriter := packfile.NewWriter(stream, 2)
	treeData, err := newTree.MarshalBinary()
	if err != nil {
		// handle error
	}
	_, err = packWriter.WriteHeader(&packfile.Header{
		Type: packfile.Tree,
		Size: int64(len(treeData)),
	})
	if err != nil {
		// handle error
	}
	if _, err := packWriter.Write(treeData); err != nil {
		// handle error
	}

	// 2. Write the commit.
	commitData, err := newCommit.MarshalBinary()
	if err != nil {
		// handle error
	}
	_, err = packWriter.WriteHeader(&packfile.Header{
		Type: packfile.Commit,
		Size: int64(len(commitData)),
	})
	if err != nil {
		// handle error
	}
	if _, err := packWriter.Write(commitData); err != nil {
		// handle error
	}
}
Output:

func (*PushStream) Close

func (p *PushStream) Close() error

Close completes the stream and releases any resources associated with the stream.

func (*PushStream) Refs

func (p *PushStream) Refs() map[githash.Ref]*Ref

Refs returns the refs the remote sent when the stream started. The caller must not modify the returned map.

func (*PushStream) Write

func (p *PushStream) Write(data []byte) (int, error)

Write writes packfile data. It returns an error if it is called before WriteCommands.

func (*PushStream) WriteCommands

func (p *PushStream) WriteCommands(commands ...*PushCommand) error

WriteCommands informs the remote what ref changes to make once the stream is complete. This must be called at most once, and must be called before any calls to Write.

type Ref

type Ref struct {
	ObjectID     githash.SHA1
	Name         githash.Ref
	SymrefTarget githash.Ref
}

Ref describes a single reference to a Git object.

type Remote

type Remote struct {
	// contains filtered or unexported fields
}

Remote represents a Git repository that can be pulled from or pushed to.

func NewRemote

func NewRemote(u *url.URL, opts *Options) (*Remote, error)

NewRemote returns a new Remote or returns an error if the transport specified in the URL scheme is unsupported.

The supported schemes are:

file: Local git-upload-pack/git-receive-pack subprocesses
http/https: Smart Git HTTP protocol
Example
package main

import (
	"gg-scm.io/pkg/git/packfile/client"
)

func main() {
	u, err := client.ParseURL("https://example.com/my/repo.git")
	if err != nil {
		// handle error
	}
	remote, err := client.NewRemote(u, nil)
	if err != nil {
		// handle error
	}
	_ = remote
}
Output:

func (*Remote) StartPull

func (r *Remote) StartPull(ctx context.Context) (_ *PullStream, err error)

StartPull starts a git-upload-pack session on the remote. The Context is used for the entire pull stream. The caller is responsible for calling Close on the returned PullStream.

func (*Remote) StartPush

func (r *Remote) StartPush(ctx context.Context) (_ *PushStream, err error)

StartPush starts a git-receive-pack session, reading the ref advertisements. The Context is used for the entire push stream.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL