Skip to content

Commit

Permalink
git: clone --shared implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
enverbisevac committed Oct 6, 2023
1 parent ced662e commit 2c4b22c
Show file tree
Hide file tree
Showing 11 changed files with 335 additions and 173 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ coverage.txt
profile.out
.tmp/
.git-dist/
.vscode
344 changes: 172 additions & 172 deletions COMPATIBILITY.md

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ type CloneOptions struct {
CABundle []byte
// ProxyOptions provides info required for connecting to a proxy.
ProxyOptions transport.ProxyOptions
// When the repository to clone is on the local machine, instead of
// using hard links, automatically setup .git/objects/info/alternates
// to share the objects with the source repository.
// The resulting repository starts out without any object of its own.
// NOTE: this is a possibly dangerous operation; do not use it unless
// you understand what it does.
//
// [Reference]: https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---shared
Shared bool
}

// Validate validates the fields and sets the default values.
Expand Down
1 change: 1 addition & 0 deletions plumbing/storer/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type EncodedObjectStorer interface {
HasEncodedObject(plumbing.Hash) error
// EncodedObjectSize returns the plaintext size of the encoded object.
EncodedObjectSize(plumbing.Hash) (int64, error)
AddAlternate(remote string) error
}

// DeltaObjectStorer is an EncodedObjectStorer that can return delta
Expand Down
4 changes: 4 additions & 0 deletions plumbing/storer/object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,7 @@ func (o *MockObjectStorage) IterEncodedObjects(t plumbing.ObjectType) (EncodedOb
func (o *MockObjectStorage) Begin() Transaction {
return nil
}

func (o *MockObjectStorage) AddAlternate(remote string) error {
return nil
}
26 changes: 26 additions & 0 deletions repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/internal/path_util"
"github.com/go-git/go-git/v5/internal/revision"
"github.com/go-git/go-git/v5/internal/url"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/cache"
formatcfg "github.com/go-git/go-git/v5/plumbing/format/config"
Expand Down Expand Up @@ -62,6 +63,7 @@ var (
ErrUnableToResolveCommit = errors.New("unable to resolve commit")
ErrPackedObjectsNotSupported = errors.New("packed objects not supported")
ErrSHA256NotSupported = errors.New("go-git was not compiled with SHA256 support")
ErrAlternatePath = errors.New("alternate path cannot start with http or https")
)

// Repository represents a git repository
Expand Down Expand Up @@ -887,6 +889,30 @@ func (r *Repository) clone(ctx context.Context, o *CloneOptions) error {
return err
}

// When the repository to clone is on the local machine,
// instead of using hard links, automatically setup .git/objects/info/alternates
// to share the objects with the source repository
if o.Shared {
if !url.IsLocalEndpoint(o.URL) {
return ErrAlternatePath
}
altpath := o.URL
remoteRepo, err := PlainOpen(o.URL)
if err != nil {
return fmt.Errorf("failed to open remote repository: %w", err)
}
conf, err := remoteRepo.Config()
if err != nil {
return fmt.Errorf("failed to read remote repository configuration: %w", err)
}
if !conf.Core.IsBare {
altpath = path.Join(altpath, GitDirName)
}
if err := r.Storer.AddAlternate(altpath); err != nil {
return fmt.Errorf("failed to add alternate file to git objects dir: %w", err)
}
}

ref, err := r.fetchAndUpdateReferences(ctx, &FetchOptions{
RefSpecs: c.Fetch,
Depth: o.Depth,
Expand Down
96 changes: 96 additions & 0 deletions repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os"
"os/exec"
"os/user"
"path"
"path/filepath"
"regexp"
"strings"
Expand Down Expand Up @@ -791,6 +792,101 @@ func (s *RepositorySuite) TestPlainClone(c *C) {
c.Assert(cfg.Branches["master"].Name, Equals, "master")
}

func (s *RepositorySuite) TestPlainCloneBareAndShared(c *C) {
dir, clean := s.TemporalDir()
defer clean()

remote := s.GetBasicLocalRepositoryURL()

r, err := PlainClone(dir, true, &CloneOptions{
URL: remote,
Shared: true,
})
c.Assert(err, IsNil)

altpath := path.Join(dir, "objects", "info", "alternates")
_, err = os.Stat(altpath)
c.Assert(err, IsNil)

data, err := os.ReadFile(altpath)
c.Assert(err, IsNil)

line := path.Join(remote, GitDirName, "objects") + "\n"
c.Assert(string(data), Equals, line)

cfg, err := r.Config()
c.Assert(err, IsNil)
c.Assert(cfg.Branches, HasLen, 1)
c.Assert(cfg.Branches["master"].Name, Equals, "master")
}

func (s *RepositorySuite) TestPlainCloneShared(c *C) {
dir, clean := s.TemporalDir()
defer clean()

remote := s.GetBasicLocalRepositoryURL()

r, err := PlainClone(dir, false, &CloneOptions{
URL: remote,
Shared: true,
})
c.Assert(err, IsNil)

altpath := path.Join(dir, GitDirName, "objects", "info", "alternates")
_, err = os.Stat(altpath)
c.Assert(err, IsNil)

data, err := os.ReadFile(altpath)
c.Assert(err, IsNil)

line := path.Join(remote, GitDirName, "objects") + "\n"
c.Assert(string(data), Equals, line)

cfg, err := r.Config()
c.Assert(err, IsNil)
c.Assert(cfg.Branches, HasLen, 1)
c.Assert(cfg.Branches["master"].Name, Equals, "master")
}

func (s *RepositorySuite) TestPlainCloneSharedHttpShouldReturnError(c *C) {
dir, clean := s.TemporalDir()
defer clean()

remote := "http://somerepo"

_, err := PlainClone(dir, false, &CloneOptions{
URL: remote,
Shared: true,
})
c.Assert(err, Equals, ErrAlternatePath)
}

func (s *RepositorySuite) TestPlainCloneSharedHttpsShouldReturnError(c *C) {
dir, clean := s.TemporalDir()
defer clean()

remote := "https://somerepo"

_, err := PlainClone(dir, false, &CloneOptions{
URL: remote,
Shared: true,
})
c.Assert(err, Equals, ErrAlternatePath)
}

func (s *RepositorySuite) TestPlainCloneSharedSSHShouldReturnError(c *C) {
dir, clean := s.TemporalDir()
defer clean()

remote := "ssh://somerepo"

_, err := PlainClone(dir, false, &CloneOptions{
URL: remote,
Shared: true,
})
c.Assert(err, Equals, ErrAlternatePath)
}

func (s *RepositorySuite) TestPlainCloneWithRemoteName(c *C) {
dir, clean := s.TemporalDir()
defer clean()
Expand Down
15 changes: 14 additions & 1 deletion storage/filesystem/dotgit/dotgit.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"sort"
"strings"
Expand Down Expand Up @@ -38,6 +39,7 @@ const (
remotesPath = "remotes"
logsPath = "logs"
worktreesPath = "worktrees"
alternatesPath = "alternates"

tmpPackedRefsPrefix = "._packed-refs"

Expand Down Expand Up @@ -1105,10 +1107,21 @@ func (d *DotGit) Module(name string) (billy.Filesystem, error) {
return d.fs.Chroot(d.fs.Join(modulePath, name))
}

func (d *DotGit) AddAlternate(remote string) error {
altpath := d.fs.Join(objectsPath, infoPath, alternatesPath)
f, err := d.fs.OpenFile(altpath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0640)
if err != nil {
return err
}
line := path.Join(remote, objectsPath) + "\n"
f.Write([]byte(line))
return f.Close()
}

// Alternates returns DotGit(s) based off paths in objects/info/alternates if
// available. This can be used to checks if it's a shared repository.
func (d *DotGit) Alternates() ([]*DotGit, error) {
altpath := d.fs.Join("objects", "info", "alternates")
altpath := d.fs.Join(objectsPath, infoPath, alternatesPath)
f, err := d.fs.Open(altpath)
if err != nil {
return nil, err
Expand Down
4 changes: 4 additions & 0 deletions storage/filesystem/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,7 @@ func (s *Storage) Filesystem() billy.Filesystem {
func (s *Storage) Init() error {
return s.dir.Initialize()
}

func (s *Storage) AddAlternate(remote string) error {
return s.dir.AddAlternate(remote)
}
4 changes: 4 additions & 0 deletions storage/memory/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ func (o *ObjectStorage) DeleteLooseObject(plumbing.Hash) error {
return errNotSupported
}

func (o *ObjectStorage) AddAlternate(remote string) error {
return errNotSupported
}

type TxObjectStorage struct {
Storage *ObjectStorage
Objects map[plumbing.Hash]plumbing.EncodedObject
Expand Down
4 changes: 4 additions & 0 deletions storage/transactional/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,7 @@ func (o *ObjectStorage) Commit() error {
return err
})
}

func (o *ObjectStorage) AddAlternate(remote string) error {
return o.temporal.AddAlternate(remote)
}

0 comments on commit 2c4b22c

Please sign in to comment.