2015-08-18 15:26:38 +00:00
|
|
|
package test
|
|
|
|
|
|
|
|
// Utility functions for more complex go tests
|
|
|
|
// Need to be in a separate test package so they can be imported anywhere
|
|
|
|
// Also can't add _test.go suffix to exclude from main build (import doesn't work)
|
|
|
|
|
|
|
|
// To avoid import cycles, append "_test" to the package statement of any test using
|
|
|
|
// this package and use "import . original/package/name" to get the same visibility
|
|
|
|
// as if the test was in the same package (as usual)
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"math/rand"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2016-08-22 21:41:43 +00:00
|
|
|
"sync"
|
2015-08-18 15:26:38 +00:00
|
|
|
"time"
|
|
|
|
|
2016-11-16 17:17:14 +00:00
|
|
|
"github.com/git-lfs/git-lfs/errors"
|
2016-11-15 17:01:18 +00:00
|
|
|
"github.com/git-lfs/git-lfs/git"
|
|
|
|
"github.com/git-lfs/git-lfs/lfs"
|
|
|
|
"github.com/git-lfs/git-lfs/localstorage"
|
2015-08-18 15:26:38 +00:00
|
|
|
)
|
|
|
|
|
2015-08-19 16:33:13 +00:00
|
|
|
type RepoType int
|
2015-08-18 15:26:38 +00:00
|
|
|
|
|
|
|
const (
|
|
|
|
// Normal repo with working copy
|
2015-08-19 16:33:13 +00:00
|
|
|
RepoTypeNormal = RepoType(iota)
|
2015-08-18 15:26:38 +00:00
|
|
|
// Bare repo (no working copy)
|
2015-08-19 16:33:13 +00:00
|
|
|
RepoTypeBare = RepoType(iota)
|
2015-08-18 15:26:38 +00:00
|
|
|
// Repo with working copy but git dir is separate
|
2015-08-19 16:33:13 +00:00
|
|
|
RepoTypeSeparateDir = RepoType(iota)
|
2015-08-18 15:26:38 +00:00
|
|
|
)
|
|
|
|
|
2016-11-16 15:50:10 +00:00
|
|
|
var (
|
|
|
|
// Deterministic sequence of seeds for file data
|
|
|
|
fileInputSeed = rand.NewSource(0)
|
|
|
|
storageOnce sync.Once
|
|
|
|
)
|
|
|
|
|
2015-08-19 16:33:13 +00:00
|
|
|
type RepoCreateSettings struct {
|
|
|
|
RepoType RepoType
|
2015-08-18 15:26:38 +00:00
|
|
|
}
|
|
|
|
|
2015-08-24 16:45:18 +00:00
|
|
|
// Callback interface (testing.T compatible)
|
|
|
|
type RepoCallback interface {
|
|
|
|
// Fatalf reports error and fails
|
|
|
|
Fatalf(format string, args ...interface{})
|
|
|
|
// Errorf reports error and continues
|
|
|
|
Errorf(format string, args ...interface{})
|
|
|
|
}
|
2015-08-19 16:33:13 +00:00
|
|
|
type Repo struct {
|
2015-08-18 15:26:38 +00:00
|
|
|
// Path to the repo, working copy if non-bare
|
|
|
|
Path string
|
|
|
|
// Path to the git dir
|
|
|
|
GitDir string
|
2015-08-20 10:58:01 +00:00
|
|
|
// Paths to remotes
|
|
|
|
Remotes map[string]*Repo
|
2015-08-18 15:26:38 +00:00
|
|
|
// Settings used to create this repo
|
2015-08-19 16:33:13 +00:00
|
|
|
Settings *RepoCreateSettings
|
2015-08-19 15:45:21 +00:00
|
|
|
// Previous dir for pushd
|
|
|
|
popDir string
|
2015-08-24 16:45:18 +00:00
|
|
|
// Test callback
|
|
|
|
callback RepoCallback
|
2015-08-18 15:26:38 +00:00
|
|
|
}
|
|
|
|
|
2015-08-19 15:45:21 +00:00
|
|
|
// Change to repo dir but save current dir
|
2015-08-20 09:46:08 +00:00
|
|
|
func (r *Repo) Pushd() {
|
2015-08-19 15:45:21 +00:00
|
|
|
if r.popDir != "" {
|
2015-08-24 16:45:18 +00:00
|
|
|
r.callback.Fatalf("Cannot Pushd twice")
|
2015-08-19 15:45:21 +00:00
|
|
|
}
|
|
|
|
oldwd, err := os.Getwd()
|
|
|
|
if err != nil {
|
2015-08-24 16:45:18 +00:00
|
|
|
r.callback.Fatalf("Can't get cwd %v", err)
|
2015-08-19 15:45:21 +00:00
|
|
|
}
|
|
|
|
err = os.Chdir(r.Path)
|
|
|
|
if err != nil {
|
2015-08-24 16:45:18 +00:00
|
|
|
r.callback.Fatalf("Can't chdir %v", err)
|
2015-08-19 15:45:21 +00:00
|
|
|
}
|
|
|
|
r.popDir = oldwd
|
2016-05-17 09:28:42 +00:00
|
|
|
localstorage.ResolveDirs()
|
2015-08-19 15:45:21 +00:00
|
|
|
}
|
|
|
|
|
2015-08-20 09:46:08 +00:00
|
|
|
func (r *Repo) Popd() {
|
2015-08-19 15:45:21 +00:00
|
|
|
if r.popDir != "" {
|
2015-08-21 15:41:33 +00:00
|
|
|
err := os.Chdir(r.popDir)
|
2015-08-19 15:45:21 +00:00
|
|
|
if err != nil {
|
2015-08-24 16:45:18 +00:00
|
|
|
r.callback.Fatalf("Can't chdir %v", err)
|
2015-08-19 15:45:21 +00:00
|
|
|
}
|
|
|
|
r.popDir = ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-20 09:46:08 +00:00
|
|
|
func (r *Repo) Cleanup() {
|
2015-08-19 15:45:21 +00:00
|
|
|
|
|
|
|
// pop out if necessary
|
2015-08-20 09:46:08 +00:00
|
|
|
r.Popd()
|
2015-08-18 17:07:14 +00:00
|
|
|
|
|
|
|
// Make sure cwd isn't inside a path we're going to delete
|
|
|
|
oldwd, err := os.Getwd()
|
|
|
|
if err == nil {
|
|
|
|
if strings.HasPrefix(oldwd, r.Path) ||
|
|
|
|
strings.HasPrefix(oldwd, r.GitDir) {
|
|
|
|
os.Chdir(os.TempDir())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-18 15:26:38 +00:00
|
|
|
if r.GitDir != "" {
|
|
|
|
os.RemoveAll(r.GitDir)
|
2015-08-20 10:58:01 +00:00
|
|
|
r.GitDir = ""
|
2015-08-18 15:26:38 +00:00
|
|
|
}
|
|
|
|
if r.Path != "" {
|
|
|
|
os.RemoveAll(r.Path)
|
2015-08-20 10:58:01 +00:00
|
|
|
r.Path = ""
|
2015-08-18 15:26:38 +00:00
|
|
|
}
|
2015-08-20 10:58:01 +00:00
|
|
|
for _, remote := range r.Remotes {
|
|
|
|
remote.Cleanup()
|
|
|
|
}
|
|
|
|
r.Remotes = nil
|
2015-08-18 15:26:38 +00:00
|
|
|
}
|
|
|
|
|
2015-08-24 16:45:18 +00:00
|
|
|
// NewRepo creates a new git repo in a new temp dir
|
|
|
|
func NewRepo(callback RepoCallback) *Repo {
|
|
|
|
return NewCustomRepo(callback, &RepoCreateSettings{RepoType: RepoTypeNormal})
|
2015-08-18 15:26:38 +00:00
|
|
|
}
|
2015-08-24 16:45:18 +00:00
|
|
|
|
|
|
|
// NewCustomRepo creates a new git repo in a new temp dir with more control over settings
|
|
|
|
func NewCustomRepo(callback RepoCallback, settings *RepoCreateSettings) *Repo {
|
2015-08-20 10:58:01 +00:00
|
|
|
ret := &Repo{
|
|
|
|
Settings: settings,
|
|
|
|
Remotes: make(map[string]*Repo),
|
2015-08-24 16:45:18 +00:00
|
|
|
callback: callback}
|
2015-08-18 15:26:38 +00:00
|
|
|
|
2015-08-19 16:33:13 +00:00
|
|
|
path, err := ioutil.TempDir("", "lfsRepo")
|
2015-08-18 15:26:38 +00:00
|
|
|
if err != nil {
|
2015-08-24 16:45:18 +00:00
|
|
|
callback.Fatalf("Can't create temp dir for git repo: %v", err)
|
2015-08-18 15:26:38 +00:00
|
|
|
}
|
|
|
|
ret.Path = path
|
|
|
|
args := []string{"init"}
|
|
|
|
switch settings.RepoType {
|
2015-08-19 16:33:13 +00:00
|
|
|
case RepoTypeBare:
|
2015-08-18 15:26:38 +00:00
|
|
|
args = append(args, "--bare")
|
|
|
|
ret.GitDir = ret.Path
|
2015-08-19 16:33:13 +00:00
|
|
|
case RepoTypeSeparateDir:
|
2015-08-18 15:26:38 +00:00
|
|
|
gitdir, err := ioutil.TempDir("", "lfstestgitdir")
|
|
|
|
if err != nil {
|
2015-08-20 09:46:08 +00:00
|
|
|
ret.Cleanup()
|
2015-08-24 16:45:18 +00:00
|
|
|
callback.Fatalf("Can't create temp dir for git repo: %v", err)
|
2015-08-18 15:26:38 +00:00
|
|
|
}
|
|
|
|
args = append(args, "--separate-dir", gitdir)
|
|
|
|
ret.GitDir = gitdir
|
|
|
|
default:
|
|
|
|
ret.GitDir = filepath.Join(ret.Path, ".git")
|
|
|
|
}
|
|
|
|
args = append(args, path)
|
|
|
|
cmd := exec.Command("git", args...)
|
|
|
|
err = cmd.Run()
|
|
|
|
if err != nil {
|
2015-08-20 09:46:08 +00:00
|
|
|
ret.Cleanup()
|
2015-08-24 16:45:18 +00:00
|
|
|
callback.Fatalf("Unable to create git repo at %v: %v", path, err)
|
2015-08-18 15:26:38 +00:00
|
|
|
}
|
2015-08-21 15:13:46 +00:00
|
|
|
|
|
|
|
// Configure default user/email so not reliant on env
|
|
|
|
ret.Pushd()
|
2015-08-24 16:45:18 +00:00
|
|
|
RunGitCommand(callback, true, "config", "user.name", "Git LFS Tests")
|
|
|
|
RunGitCommand(callback, true, "config", "user.email", "git-lfs@example.com")
|
2015-08-21 15:13:46 +00:00
|
|
|
ret.Popd()
|
|
|
|
|
2015-08-18 15:26:38 +00:00
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2015-08-24 16:54:56 +00:00
|
|
|
// WrapRepo creates a new Repo instance for an existing git repo
|
|
|
|
func WrapRepo(c RepoCallback, path string) *Repo {
|
|
|
|
return &Repo{Path: path, callback: c, Settings: &RepoCreateSettings{RepoType: RepoTypeNormal}}
|
|
|
|
}
|
|
|
|
|
2015-08-18 15:26:38 +00:00
|
|
|
// Simplistic fire & forget running of git command - returns combined output
|
2015-08-24 16:45:18 +00:00
|
|
|
func RunGitCommand(callback RepoCallback, failureCheck bool, args ...string) string {
|
2015-08-18 15:26:38 +00:00
|
|
|
outp, err := exec.Command("git", args...).CombinedOutput()
|
|
|
|
if failureCheck && err != nil {
|
2015-08-24 16:45:18 +00:00
|
|
|
callback.Fatalf("Error running git command 'git %v': %v %v", strings.Join(args, " "), err, string(outp))
|
2015-08-18 15:26:38 +00:00
|
|
|
}
|
|
|
|
return string(outp)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Input data for a single file in a commit
|
2015-08-19 16:33:13 +00:00
|
|
|
type FileInput struct {
|
2015-08-18 15:26:38 +00:00
|
|
|
// Name of file (required)
|
|
|
|
Filename string
|
|
|
|
// Size of file (required)
|
|
|
|
Size int64
|
2015-08-25 14:08:11 +00:00
|
|
|
// Input data (optional, if provided will be source of data)
|
|
|
|
DataReader io.Reader
|
|
|
|
// Input data (optional, if provided will be source of data)
|
2015-08-25 15:56:21 +00:00
|
|
|
Data string
|
2015-08-18 15:26:38 +00:00
|
|
|
}
|
|
|
|
|
2016-11-16 15:50:10 +00:00
|
|
|
func (infile *FileInput) AddToIndex(output *CommitOutput, repo *Repo) {
|
|
|
|
inputData := infile.getFileInputReader()
|
|
|
|
pointer, err := infile.writeLFSPointer(inputData)
|
|
|
|
if err != nil {
|
|
|
|
repo.callback.Errorf("%+v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
output.Files = append(output.Files, pointer)
|
|
|
|
RunGitCommand(repo.callback, true, "add", infile.Filename)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (infile *FileInput) writeLFSPointer(inputData io.Reader) (*lfs.Pointer, error) {
|
|
|
|
cleaned, err := lfs.PointerClean(inputData, infile.Filename, infile.Size, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "creating pointer file")
|
|
|
|
}
|
|
|
|
|
|
|
|
// this only created the temp file, move to final location
|
|
|
|
tmpfile := cleaned.Filename
|
|
|
|
storageOnce.Do(localstorage.ResolveDirs)
|
|
|
|
mediafile, err := lfs.LocalMediaPath(cleaned.Oid)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "local media path")
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := os.Stat(mediafile); err != nil {
|
|
|
|
if err := os.Rename(tmpfile, mediafile); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write pointer to local filename for adding (not using clean filter)
|
|
|
|
os.MkdirAll(filepath.Dir(infile.Filename), 0755)
|
|
|
|
f, err := os.Create(infile.Filename)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "creating pointer file")
|
|
|
|
}
|
|
|
|
_, err = cleaned.Pointer.Encode(f)
|
|
|
|
f.Close()
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "encoding pointer file")
|
|
|
|
}
|
|
|
|
|
|
|
|
return cleaned.Pointer, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (infile *FileInput) getFileInputReader() io.Reader {
|
|
|
|
if infile.DataReader != nil {
|
|
|
|
return infile.DataReader
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(infile.Data) > 0 {
|
|
|
|
return strings.NewReader(infile.Data)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Different data for each file but deterministic
|
|
|
|
return NewPlaceholderDataReader(fileInputSeed.Int63(), infile.Size)
|
|
|
|
}
|
|
|
|
|
2015-08-18 15:26:38 +00:00
|
|
|
// Input for defining commits for test repo
|
2015-08-19 16:33:13 +00:00
|
|
|
type CommitInput struct {
|
2015-08-18 15:26:38 +00:00
|
|
|
// Date that we should commit on (optional, leave blank for 'now')
|
|
|
|
CommitDate time.Time
|
|
|
|
// List of files to include in this commit
|
2015-08-19 16:33:13 +00:00
|
|
|
Files []*FileInput
|
2015-08-19 12:02:58 +00:00
|
|
|
// List of parent branches (all branches must have been created in a previous NewBranch or be master)
|
2015-08-18 15:26:38 +00:00
|
|
|
// Can be omitted to just use the parent of the previous commit
|
|
|
|
ParentBranches []string
|
|
|
|
// Name of a new branch we should create at this commit (optional - master not required)
|
|
|
|
NewBranch string
|
2015-08-19 12:02:58 +00:00
|
|
|
// Names of any tags we should create at this commit (optional)
|
|
|
|
Tags []string
|
2015-08-18 15:26:38 +00:00
|
|
|
// Name of committer
|
|
|
|
CommitterName string
|
|
|
|
// Email of committer
|
|
|
|
CommitterEmail string
|
|
|
|
}
|
|
|
|
|
|
|
|
// Output struct with details of commits created for test
|
2015-08-19 16:33:13 +00:00
|
|
|
type CommitOutput struct {
|
2015-08-18 15:26:38 +00:00
|
|
|
Sha string
|
|
|
|
Parents []string
|
|
|
|
Files []*lfs.Pointer
|
|
|
|
}
|
|
|
|
|
2015-08-18 17:01:26 +00:00
|
|
|
func commitAtDate(atDate time.Time, committerName, committerEmail, msg string) error {
|
2015-08-18 15:26:38 +00:00
|
|
|
var args []string
|
|
|
|
if committerName != "" && committerEmail != "" {
|
|
|
|
args = append(args, "-c", fmt.Sprintf("user.name=%v", committerName))
|
|
|
|
args = append(args, "-c", fmt.Sprintf("user.email=%v", committerEmail))
|
|
|
|
}
|
|
|
|
args = append(args, "commit", "--allow-empty", "-m", msg)
|
|
|
|
cmd := exec.Command("git", args...)
|
|
|
|
env := os.Environ()
|
|
|
|
// set GIT_COMMITTER_DATE environment var e.g. "Fri Jun 21 20:26:41 2013 +0900"
|
|
|
|
if atDate.IsZero() {
|
|
|
|
env = append(env, "GIT_COMMITTER_DATE=")
|
2017-01-17 04:38:31 +00:00
|
|
|
env = append(env, "GIT_AUTHOR_DATE=")
|
2015-08-18 15:26:38 +00:00
|
|
|
} else {
|
2015-08-21 14:19:16 +00:00
|
|
|
env = append(env, fmt.Sprintf("GIT_COMMITTER_DATE=%v", git.FormatGitDate(atDate)))
|
2017-01-17 04:38:31 +00:00
|
|
|
env = append(env, fmt.Sprintf("GIT_AUTHOR_DATE=%v", git.FormatGitDate(atDate)))
|
2015-08-18 15:26:38 +00:00
|
|
|
}
|
|
|
|
cmd.Env = env
|
2015-08-21 14:59:18 +00:00
|
|
|
out, err := cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
2015-08-21 15:13:46 +00:00
|
|
|
return fmt.Errorf("%v %v", err, string(out))
|
2015-08-21 14:59:18 +00:00
|
|
|
}
|
|
|
|
return nil
|
2015-08-18 15:26:38 +00:00
|
|
|
}
|
|
|
|
|
2015-08-20 09:46:08 +00:00
|
|
|
func (repo *Repo) AddCommits(inputs []*CommitInput) []*CommitOutput {
|
2015-08-19 16:33:13 +00:00
|
|
|
if repo.Settings.RepoType == RepoTypeBare {
|
2015-08-24 16:45:18 +00:00
|
|
|
repo.callback.Fatalf("Cannot use AddCommits on a bare repo; clone it & push changes instead")
|
2015-08-18 17:01:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Change to repo working dir
|
|
|
|
oldwd, err := os.Getwd()
|
|
|
|
if err != nil {
|
2015-08-24 16:45:18 +00:00
|
|
|
repo.callback.Fatalf("Can't get cwd %v", err)
|
2015-08-18 17:01:26 +00:00
|
|
|
}
|
|
|
|
err = os.Chdir(repo.Path)
|
|
|
|
if err != nil {
|
2015-08-24 16:45:18 +00:00
|
|
|
repo.callback.Fatalf("Can't chdir to repo %v", err)
|
2015-08-18 17:01:26 +00:00
|
|
|
}
|
2015-08-18 15:26:38 +00:00
|
|
|
// Used to check whether we need to checkout another commit before
|
|
|
|
lastBranch := "master"
|
2015-08-19 16:33:13 +00:00
|
|
|
outputs := make([]*CommitOutput, 0, len(inputs))
|
2016-11-16 15:50:10 +00:00
|
|
|
|
2015-08-18 15:26:38 +00:00
|
|
|
for i, input := range inputs {
|
2015-08-19 16:33:13 +00:00
|
|
|
output := &CommitOutput{}
|
2015-08-18 15:26:38 +00:00
|
|
|
// first, are we on the correct branch
|
|
|
|
if len(input.ParentBranches) > 0 {
|
|
|
|
if input.ParentBranches[0] != lastBranch {
|
2015-08-24 16:45:18 +00:00
|
|
|
RunGitCommand(repo.callback, true, "checkout", input.ParentBranches[0])
|
2015-08-18 15:26:38 +00:00
|
|
|
lastBranch = input.ParentBranches[0]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Is this a merge?
|
|
|
|
if len(input.ParentBranches) > 1 {
|
|
|
|
// Always take the *other* side in a merge so we adopt changes
|
|
|
|
// also don't automatically commit, we'll do that below
|
|
|
|
args := []string{"merge", "--no-ff", "--no-commit", "--strategy-option=theirs"}
|
|
|
|
args = append(args, input.ParentBranches[1:]...)
|
2015-08-24 16:45:18 +00:00
|
|
|
RunGitCommand(repo.callback, false, args...)
|
2015-08-18 15:26:38 +00:00
|
|
|
} else if input.NewBranch != "" {
|
2015-08-24 16:45:18 +00:00
|
|
|
RunGitCommand(repo.callback, true, "checkout", "-b", input.NewBranch)
|
2015-08-18 15:26:38 +00:00
|
|
|
lastBranch = input.NewBranch
|
|
|
|
}
|
|
|
|
// Any files to write?
|
2015-09-11 15:41:58 +00:00
|
|
|
for _, infile := range input.Files {
|
2016-11-16 15:50:10 +00:00
|
|
|
infile.AddToIndex(output, repo)
|
2015-08-18 15:26:38 +00:00
|
|
|
}
|
|
|
|
// Now commit
|
2015-08-21 15:13:46 +00:00
|
|
|
err = commitAtDate(input.CommitDate, input.CommitterName, input.CommitterEmail,
|
2015-08-18 15:26:38 +00:00
|
|
|
fmt.Sprintf("Test commit %d", i))
|
2015-08-21 15:13:46 +00:00
|
|
|
if err != nil {
|
2015-08-24 16:45:18 +00:00
|
|
|
repo.callback.Fatalf("Error committing: %v", err)
|
2015-08-21 15:13:46 +00:00
|
|
|
}
|
|
|
|
|
2015-08-18 15:26:38 +00:00
|
|
|
commit, err := git.GetCommitSummary("HEAD")
|
|
|
|
if err != nil {
|
2015-08-24 16:45:18 +00:00
|
|
|
repo.callback.Fatalf("Error determining commit SHA: %v", err)
|
2015-08-18 15:26:38 +00:00
|
|
|
}
|
2015-08-19 12:02:58 +00:00
|
|
|
|
|
|
|
// tags
|
|
|
|
for _, tag := range input.Tags {
|
|
|
|
// Use annotated tags, assume full release tags (also tag objects have edge cases)
|
2015-08-24 16:45:18 +00:00
|
|
|
RunGitCommand(repo.callback, true, "tag", "-a", "-m", "Added tag", tag)
|
2015-08-19 12:02:58 +00:00
|
|
|
}
|
|
|
|
|
2015-08-18 15:26:38 +00:00
|
|
|
output.Sha = commit.Sha
|
|
|
|
output.Parents = commit.Parents
|
|
|
|
outputs = append(outputs, output)
|
|
|
|
}
|
2015-08-18 17:01:26 +00:00
|
|
|
|
|
|
|
// Restore cwd
|
|
|
|
err = os.Chdir(oldwd)
|
|
|
|
if err != nil {
|
2015-08-24 16:45:18 +00:00
|
|
|
repo.callback.Fatalf("Can't restore old cwd %v", err)
|
2015-08-18 17:01:26 +00:00
|
|
|
}
|
|
|
|
|
2015-08-18 15:26:38 +00:00
|
|
|
return outputs
|
|
|
|
}
|
|
|
|
|
2015-08-20 10:58:01 +00:00
|
|
|
// Add a new remote (generate a path for it to live in, will be cleaned up)
|
|
|
|
func (r *Repo) AddRemote(name string) *Repo {
|
|
|
|
if _, exists := r.Remotes[name]; exists {
|
2015-08-24 16:45:18 +00:00
|
|
|
r.callback.Fatalf("Remote %v already exists", name)
|
2015-08-20 10:58:01 +00:00
|
|
|
}
|
2015-08-24 16:45:18 +00:00
|
|
|
remote := NewCustomRepo(r.callback, &RepoCreateSettings{RepoTypeBare})
|
2015-08-20 10:58:01 +00:00
|
|
|
r.Remotes[name] = remote
|
2015-08-24 16:45:18 +00:00
|
|
|
RunGitCommand(r.callback, true, "remote", "add", name, remote.Path)
|
2015-08-20 10:58:01 +00:00
|
|
|
return remote
|
|
|
|
}
|
|
|
|
|
2015-08-18 15:26:38 +00:00
|
|
|
// Just a psuedo-random stream of bytes (not cryptographic)
|
|
|
|
// Calls RNG a bit less often than using rand.Source directly
|
|
|
|
type PlaceholderDataReader struct {
|
2015-08-19 12:02:37 +00:00
|
|
|
source rand.Source
|
|
|
|
bytesLeft int64
|
2015-08-18 15:26:38 +00:00
|
|
|
}
|
|
|
|
|
2015-08-19 12:02:37 +00:00
|
|
|
func NewPlaceholderDataReader(seed, size int64) *PlaceholderDataReader {
|
|
|
|
return &PlaceholderDataReader{rand.NewSource(seed), size}
|
2015-08-18 15:26:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *PlaceholderDataReader) Read(p []byte) (int, error) {
|
|
|
|
c := len(p)
|
2015-08-19 12:02:37 +00:00
|
|
|
i := 0
|
|
|
|
for i < c && r.bytesLeft > 0 {
|
2015-08-18 15:26:38 +00:00
|
|
|
// Use all 8 bytes of the 64-bit random number
|
|
|
|
val64 := r.source.Int63()
|
2015-08-19 12:02:37 +00:00
|
|
|
for j := 0; j < 8 && i < c && r.bytesLeft > 0; j++ {
|
2015-08-18 15:26:38 +00:00
|
|
|
// Duplicate this byte 16 times (faster)
|
2015-08-19 12:02:37 +00:00
|
|
|
for k := 0; k < 16 && r.bytesLeft > 0; k++ {
|
2015-08-18 15:26:38 +00:00
|
|
|
p[i] = byte(val64)
|
|
|
|
i++
|
2015-08-19 12:02:37 +00:00
|
|
|
r.bytesLeft--
|
2015-08-18 15:26:38 +00:00
|
|
|
}
|
|
|
|
// Next byte from the 8-byte number
|
|
|
|
val64 = val64 >> 8
|
|
|
|
}
|
|
|
|
}
|
2015-08-19 12:02:37 +00:00
|
|
|
var err error
|
|
|
|
if r.bytesLeft == 0 {
|
|
|
|
err = io.EOF
|
|
|
|
}
|
|
|
|
return i, err
|
2015-08-18 15:26:38 +00:00
|
|
|
}
|
2015-08-21 15:53:16 +00:00
|
|
|
|
|
|
|
// RefsByName implements sort.Interface for []*git.Ref based on name
|
|
|
|
type RefsByName []*git.Ref
|
|
|
|
|
|
|
|
func (a RefsByName) Len() int { return len(a) }
|
|
|
|
func (a RefsByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
func (a RefsByName) Less(i, j int) bool { return a[i].Name < a[j].Name }
|
2015-08-24 14:15:16 +00:00
|
|
|
|
|
|
|
// WrappedPointersByOid implements sort.Interface for []*lfs.WrappedPointer based on oid
|
|
|
|
type WrappedPointersByOid []*lfs.WrappedPointer
|
|
|
|
|
|
|
|
func (a WrappedPointersByOid) Len() int { return len(a) }
|
|
|
|
func (a WrappedPointersByOid) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
func (a WrappedPointersByOid) Less(i, j int) bool { return a[i].Pointer.Oid < a[j].Pointer.Oid }
|
2015-09-11 11:54:41 +00:00
|
|
|
|
|
|
|
// PointersByOid implements sort.Interface for []*lfs.Pointer based on oid
|
|
|
|
type PointersByOid []*lfs.Pointer
|
|
|
|
|
|
|
|
func (a PointersByOid) Len() int { return len(a) }
|
|
|
|
func (a PointersByOid) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
func (a PointersByOid) Less(i, j int) bool { return a[i].Oid < a[j].Oid }
|