Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit eb74b0e

Browse files
darkowlzzmcuadros
authored andcommitted
storage: filesystem, add support for git alternates (#663)
This change adds a new method Alternates() in DotGit to check and query alternate source.
1 parent f96d46d commit eb74b0e

File tree

4 files changed

+148
-1
lines changed

4 files changed

+148
-1
lines changed

storage/filesystem/internal/dotgit/dotgit.go

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import (
88
"io"
99
stdioutil "io/ioutil"
1010
"os"
11+
"path/filepath"
1112
"strings"
1213
"time"
1314

15+
"gopkg.in/src-d/go-billy.v4/osfs"
1416
"gopkg.in/src-d/go-git.v4/plumbing"
1517
"gopkg.in/src-d/go-git.v4/utils/ioutil"
1618

@@ -756,11 +758,52 @@ func (d *DotGit) PackRefs() (err error) {
756758
return nil
757759
}
758760

759-
// Module return a billy.Filesystem poiting to the module folder
761+
// Module return a billy.Filesystem pointing to the module folder
760762
func (d *DotGit) Module(name string) (billy.Filesystem, error) {
761763
return d.fs.Chroot(d.fs.Join(modulePath, name))
762764
}
763765

766+
// Alternates returns DotGit(s) based off paths in objects/info/alternates if
767+
// available. This can be used to checks if it's a shared repository.
768+
func (d *DotGit) Alternates() ([]*DotGit, error) {
769+
altpath := d.fs.Join("objects", "info", "alternates")
770+
f, err := d.fs.Open(altpath)
771+
if err != nil {
772+
return nil, err
773+
}
774+
defer f.Close()
775+
776+
var alternates []*DotGit
777+
778+
// Read alternate paths line-by-line and create DotGit objects.
779+
scanner := bufio.NewScanner(f)
780+
for scanner.Scan() {
781+
path := scanner.Text()
782+
if !filepath.IsAbs(path) {
783+
// For relative paths, we can perform an internal conversion to
784+
// slash so that they work cross-platform.
785+
slashPath := filepath.ToSlash(path)
786+
// If the path is not absolute, it must be relative to object
787+
// database (.git/objects/info).
788+
// https://www.kernel.org/pub/software/scm/git/docs/gitrepository-layout.html
789+
// Hence, derive a path relative to DotGit's root.
790+
// "../../../reponame/.git/" -> "../../reponame/.git"
791+
// Remove the first ../
792+
relpath := filepath.Join(strings.Split(slashPath, "/")[1:]...)
793+
normalPath := filepath.FromSlash(relpath)
794+
path = filepath.Join(d.fs.Root(), normalPath)
795+
}
796+
fs := osfs.New(filepath.Dir(path))
797+
alternates = append(alternates, New(fs))
798+
}
799+
800+
if err = scanner.Err(); err != nil {
801+
return nil, err
802+
}
803+
804+
return alternates, nil
805+
}
806+
764807
func isHex(s string) bool {
765808
for _, b := range []byte(s) {
766809
if isNum(b) {

storage/filesystem/internal/dotgit/dotgit_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"io/ioutil"
66
"os"
77
"path/filepath"
8+
"runtime"
89
"strings"
910
"testing"
1011

@@ -615,3 +616,55 @@ func (s *SuiteDotGit) TestPackRefs(c *C) {
615616
c.Assert(ref, NotNil)
616617
c.Assert(ref.Hash().String(), Equals, "b8d3ffab552895c19b9fcf7aa264d277cde33881")
617618
}
619+
620+
func (s *SuiteDotGit) TestAlternates(c *C) {
621+
tmp, err := ioutil.TempDir("", "dot-git")
622+
c.Assert(err, IsNil)
623+
defer os.RemoveAll(tmp)
624+
625+
// Create a new billy fs.
626+
fs := osfs.New(tmp)
627+
628+
// Create a new dotgit object and initialize.
629+
dir := New(fs)
630+
err = dir.Initialize()
631+
c.Assert(err, IsNil)
632+
633+
// Create alternates file.
634+
altpath := filepath.Join("objects", "info", "alternates")
635+
f, err := fs.Create(altpath)
636+
c.Assert(err, IsNil)
637+
638+
// Multiple alternates.
639+
var strContent string
640+
if runtime.GOOS == "windows" {
641+
strContent = "C:\\Users\\username\\repo1\\.git\\objects\r\n..\\..\\..\\rep2\\.git\\objects"
642+
} else {
643+
strContent = "/Users/username/rep1//.git/objects\n../../../rep2//.git/objects"
644+
}
645+
content := []byte(strContent)
646+
f.Write(content)
647+
f.Close()
648+
649+
dotgits, err := dir.Alternates()
650+
c.Assert(err, IsNil)
651+
if runtime.GOOS == "windows" {
652+
c.Assert(dotgits[0].fs.Root(), Equals, "C:\\Users\\username\\repo1\\.git")
653+
} else {
654+
c.Assert(dotgits[0].fs.Root(), Equals, "/Users/username/rep1/.git")
655+
}
656+
657+
// For relative path:
658+
// /some/absolute/path/to/dot-git -> /some/absolute/path
659+
pathx := strings.Split(tmp, string(filepath.Separator))
660+
pathx = pathx[:len(pathx)-2]
661+
// Use string.Join() to avoid malformed absolutepath on windows
662+
// C:Users\\User\\... instead of C:\\Users\\appveyor\\... .
663+
resolvedPath := strings.Join(pathx, string(filepath.Separator))
664+
// Append the alternate path to the resolvedPath
665+
expectedPath := filepath.Join(string(filepath.Separator), resolvedPath, "rep2", ".git")
666+
if runtime.GOOS == "windows" {
667+
expectedPath = filepath.Join(resolvedPath, "rep2", ".git")
668+
}
669+
c.Assert(dotgits[1].fs.Root(), Equals, expectedPath)
670+
}

storage/filesystem/object.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,27 @@ func (s *ObjectStorage) EncodedObject(t plumbing.ObjectType, h plumbing.Hash) (p
160160
obj, err = s.getFromPackfile(h, false)
161161
}
162162

163+
// If the error is still object not found, check if it's a shared object
164+
// repository.
165+
if err == plumbing.ErrObjectNotFound {
166+
dotgits, e := s.dir.Alternates()
167+
if e == nil {
168+
// Create a new object storage with the DotGit(s) and check for the
169+
// required hash object. Skip when not found.
170+
for _, dg := range dotgits {
171+
o, oe := newObjectStorage(dg)
172+
if oe != nil {
173+
continue
174+
}
175+
enobj, enerr := o.EncodedObject(t, h)
176+
if enerr != nil {
177+
continue
178+
}
179+
return enobj, nil
180+
}
181+
}
182+
}
183+
163184
if err != nil {
164185
return nil, err
165186
}

worktree_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,3 +1287,33 @@ func (s *WorktreeSuite) TestClean(c *C) {
12871287

12881288
c.Assert(len(status), Equals, 0)
12891289
}
1290+
1291+
func (s *WorktreeSuite) TestAlternatesRepo(c *C) {
1292+
fs := fixtures.ByTag("alternates").One().Worktree()
1293+
1294+
// Open 1st repo.
1295+
rep1fs, err := fs.Chroot("rep1")
1296+
c.Assert(err, IsNil)
1297+
rep1, err := PlainOpen(rep1fs.Root())
1298+
c.Assert(err, IsNil)
1299+
1300+
// Open 2nd repo.
1301+
rep2fs, err := fs.Chroot("rep2")
1302+
c.Assert(err, IsNil)
1303+
rep2, err := PlainOpen(rep2fs.Root())
1304+
c.Assert(err, IsNil)
1305+
1306+
// Get the HEAD commit from the main repo.
1307+
h, err := rep1.Head()
1308+
c.Assert(err, IsNil)
1309+
commit1, err := rep1.CommitObject(h.Hash())
1310+
c.Assert(err, IsNil)
1311+
1312+
// Get the HEAD commit from the shared repo.
1313+
h, err = rep2.Head()
1314+
c.Assert(err, IsNil)
1315+
commit2, err := rep2.CommitObject(h.Hash())
1316+
c.Assert(err, IsNil)
1317+
1318+
c.Assert(commit1.String(), Equals, commit2.String())
1319+
}

0 commit comments

Comments
 (0)