2017-06-05 15:45:23 +00:00
|
|
|
package githistory
|
2017-06-02 22:29:00 +00:00
|
|
|
|
|
|
|
import (
|
2017-06-02 22:29:37 +00:00
|
|
|
"encoding/hex"
|
2017-06-02 22:29:00 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
2017-06-19 19:53:11 +00:00
|
|
|
"os/exec"
|
2017-06-02 22:29:00 +00:00
|
|
|
"path/filepath"
|
2017-06-02 22:30:13 +00:00
|
|
|
"strings"
|
2017-06-02 22:29:00 +00:00
|
|
|
"testing"
|
2017-06-02 22:30:13 +00:00
|
|
|
|
2017-06-05 15:45:23 +00:00
|
|
|
"github.com/git-lfs/git-lfs/git/odb"
|
2017-06-02 22:30:13 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
2017-06-02 22:29:00 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// DatabaseFromFixture returns a *git/odb.ObjectDatabase instance that is safely
|
|
|
|
// mutable and created from a template equivelant to the fixture that you
|
|
|
|
// provided it.
|
|
|
|
//
|
|
|
|
// If any error was encountered, it will call t.Fatalf() immediately.
|
2017-06-05 15:45:23 +00:00
|
|
|
func DatabaseFromFixture(t *testing.T, name string) *odb.ObjectDatabase {
|
2017-06-02 22:29:00 +00:00
|
|
|
path, err := copyToTmp(filepath.Join("fixtures", name))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("git/odb: could not copy fixture %s: %v", name, err)
|
|
|
|
}
|
|
|
|
|
2017-10-25 00:59:36 +00:00
|
|
|
db, err := odb.FromFilesystem(filepath.Join(path, "objects"), "")
|
2017-06-02 22:29:00 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("git/odb: could not create object database: %v", err)
|
|
|
|
}
|
|
|
|
return db
|
|
|
|
}
|
|
|
|
|
2017-06-02 22:30:13 +00:00
|
|
|
// AssertBlobContents asserts that the blob contents given by loading the path
|
|
|
|
// starting from the root tree "tree" has the given "contents".
|
2017-06-05 15:45:23 +00:00
|
|
|
func AssertBlobContents(t *testing.T, db *odb.ObjectDatabase, tree, path, contents string) {
|
2017-06-02 22:30:13 +00:00
|
|
|
// First, load the root tree.
|
|
|
|
root, err := db.Tree(HexDecode(t, tree))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("git/odb: cannot load tree: %s: %s", tree, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Then, iterating through each part of the filepath (i.e., a/b/c.txt ->
|
|
|
|
// []string{"a", "b", "c.txt"}).
|
2018-02-15 01:38:59 +00:00
|
|
|
parts := strings.Split(path, "/")
|
2017-06-02 22:30:13 +00:00
|
|
|
for i := 0; i < len(parts)-1; i++ {
|
|
|
|
part := parts[i]
|
|
|
|
|
|
|
|
// Load the subtree given by that name.
|
2017-06-05 15:45:23 +00:00
|
|
|
var subtree *odb.Tree
|
2017-06-02 22:30:13 +00:00
|
|
|
for _, entry := range root.Entries {
|
|
|
|
if entry.Name != part {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
subtree, err = db.Tree(entry.Oid)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("git/odb: cannot load subtree %s: %s", filepath.Join(parts[:i]...), err)
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if subtree == nil {
|
|
|
|
t.Fatalf("git/odb: subtree %s does not exist", path)
|
|
|
|
}
|
|
|
|
|
|
|
|
// And re-assign it to root, creating a sort of pseudo-recursion.
|
|
|
|
root = subtree
|
|
|
|
}
|
|
|
|
|
|
|
|
filename := parts[len(parts)-1]
|
|
|
|
|
|
|
|
// Find the blob given by the last entry in parts (the filename).
|
2017-06-05 15:45:23 +00:00
|
|
|
var blob *odb.Blob
|
2017-06-02 22:30:13 +00:00
|
|
|
for _, entry := range root.Entries {
|
|
|
|
if entry.Name == filename {
|
|
|
|
blob, err = db.Blob(entry.Oid)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("git/odb: cannot load blob %x: %s", entry.Oid, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we couldn't find the blob, fail immediately.
|
|
|
|
if blob == nil {
|
|
|
|
t.Fatalf("git/odb: blob at %s in %s does not exist", path, tree)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Perform an assertion on the blob's contents.
|
|
|
|
got, err := ioutil.ReadAll(blob.Contents)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("git/odb: cannot read contents from blob %s: %s", path, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.Equal(t, contents, string(got))
|
|
|
|
}
|
|
|
|
|
2017-06-02 22:30:34 +00:00
|
|
|
// AssertCommitParent asserts that the given commit has a parent equivalent to
|
|
|
|
// the one provided.
|
2017-06-05 15:45:23 +00:00
|
|
|
func AssertCommitParent(t *testing.T, db *odb.ObjectDatabase, sha, parent string) {
|
2017-06-02 22:30:34 +00:00
|
|
|
commit, err := db.Commit(HexDecode(t, sha))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("git/odb: expected to read commit: %s, couldn't: %v", sha, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
decoded, err := hex.DecodeString(parent)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("git/odb: expected to decode parent SHA: %s, couldn't: %v", parent, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.Contains(t, commit.ParentIDs, decoded,
|
|
|
|
"git/odb: expected parents of commit: %s to contain: %s", sha, parent)
|
|
|
|
}
|
|
|
|
|
2017-06-02 22:30:47 +00:00
|
|
|
// AssertCommitTree asserts that the given commit has a tree equivelant to the
|
|
|
|
// one provided.
|
2017-06-05 15:45:23 +00:00
|
|
|
func AssertCommitTree(t *testing.T, db *odb.ObjectDatabase, sha, tree string) {
|
2017-06-02 22:30:47 +00:00
|
|
|
commit, err := db.Commit(HexDecode(t, sha))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("git/odb: expected to read commit: %s, couldn't: %v", sha, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
decoded, err := hex.DecodeString(tree)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("git/odb: expected to decode tree SHA: %s, couldn't: %v", tree, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.Equal(t, decoded, commit.TreeID, "git/odb: expected tree ID: %s (got: %x)", tree, commit.TreeID)
|
|
|
|
}
|
|
|
|
|
2017-06-19 19:53:11 +00:00
|
|
|
// AssertRef asserts that a given refname points at the expected commit.
|
|
|
|
func AssertRef(t *testing.T, db *odb.ObjectDatabase, ref string, expected []byte) {
|
|
|
|
root, ok := db.Root()
|
|
|
|
assert.True(t, ok, "git/odb: expected *odb.ObjectDatabase to have Root()")
|
|
|
|
|
|
|
|
cmd := exec.Command("git", "rev-parse", ref)
|
|
|
|
cmd.Dir = root
|
|
|
|
out, err := cmd.Output()
|
|
|
|
|
|
|
|
assert.Nil(t, err)
|
|
|
|
|
|
|
|
assert.Equal(t, hex.EncodeToString(expected), strings.TrimSpace(string(out)))
|
|
|
|
}
|
|
|
|
|
2017-06-02 22:29:37 +00:00
|
|
|
// HexDecode decodes the given ASCII hex-encoded string into []byte's, or fails
|
|
|
|
// the test immediately if the given "sha" wasn't a valid hex-encoded sequence.
|
|
|
|
func HexDecode(t *testing.T, sha string) []byte {
|
|
|
|
b, err := hex.DecodeString(sha)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("git/odb: could not decode string: %q, %v", sha, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
2017-06-02 22:29:00 +00:00
|
|
|
// copyToTmp copies the given fixutre to a folder in /tmp.
|
|
|
|
func copyToTmp(fixture string) (string, error) {
|
|
|
|
p, err := ioutil.TempDir("", fmt.Sprintf("git-lfs-fixture-%s", filepath.Dir(fixture)))
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = copyDir(fixture, p); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return p, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// copyDir copies a directory (and recursively all files and subdirectories)
|
|
|
|
// from "from" to "to" preserving permissions and ownership.
|
|
|
|
func copyDir(from, to string) error {
|
|
|
|
stat, err := os.Stat(from)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := os.MkdirAll(to, stat.Mode()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
entries, err := ioutil.ReadDir(from)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, entry := range entries {
|
|
|
|
sp := filepath.Join(from, entry.Name())
|
|
|
|
dp := filepath.Join(to, entry.Name())
|
|
|
|
|
|
|
|
if entry.IsDir() {
|
|
|
|
err = copyDir(sp, dp)
|
|
|
|
} else {
|
|
|
|
err = copyFile(sp, dp)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// copyFile copies a file from "from" to "to" preserving permissions and
|
|
|
|
// ownership.
|
|
|
|
func copyFile(from, to string) error {
|
|
|
|
src, err := os.Open(from)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer src.Close()
|
|
|
|
|
|
|
|
dst, err := os.Create(to)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer dst.Close()
|
|
|
|
|
|
|
|
if _, err = io.Copy(dst, src); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
stat, err := os.Stat(from)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return os.Chmod(to, stat.Mode())
|
|
|
|
}
|