Merge branch 'master' into ttaylorr/migrate-warn-if-dirty

This commit is contained in:
Taylor Blau 2018-07-23 09:14:29 -05:00
commit 9763ac1623
16 changed files with 505 additions and 89 deletions

@ -179,13 +179,31 @@ func includeExcludeRefs(l *tasklog.Logger, args []string) (include, exclude []st
include = append(include, migrateIncludeRefs...) include = append(include, migrateIncludeRefs...)
exclude = append(exclude, migrateExcludeRefs...) exclude = append(exclude, migrateExcludeRefs...)
} else if migrateEverything { } else if migrateEverything {
localRefs, err := git.LocalRefs() refs, err := git.AllRefsIn("")
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
for _, ref := range localRefs { for _, ref := range refs {
include = append(include, ref.Refspec()) switch ref.Type {
case git.RefTypeLocalBranch, git.RefTypeLocalTag,
git.RefTypeRemoteBranch, git.RefTypeRemoteTag:
include = append(include, ref.Refspec())
case git.RefTypeOther:
parts := strings.SplitN(ref.Refspec(), "/", 3)
if len(parts) < 2 {
continue
}
switch parts[1] {
// The following are GitLab-, GitHub-, VSTS-,
// and BitBucket-specific reference naming
// conventions.
case "merge-requests", "pull", "pull-requests":
include = append(include, ref.Refspec())
}
}
} }
} else { } else {
bare, err := git.IsBare() bare, err := git.IsBare()

@ -311,8 +311,8 @@ func (c *Configuration) LocalGitStorageDir() string {
return c.Filesystem().GitStorageDir return c.Filesystem().GitStorageDir
} }
func (c *Configuration) LocalReferenceDir() string { func (c *Configuration) LocalReferenceDirs() []string {
return c.Filesystem().ReferenceDir return c.Filesystem().ReferenceDirs
} }
func (c *Configuration) LFSStorageDir() string { func (c *Configuration) LFSStorageDir() string {
@ -346,7 +346,12 @@ func (c *Configuration) Filesystem() *fs.Filesystem {
if c.fs == nil { if c.fs == nil {
lfsdir, _ := c.Git.Get("lfs.storage") lfsdir, _ := c.Git.Get("lfs.storage")
c.fs = fs.New(c.LocalGitDir(), c.LocalWorkingDir(), lfsdir) c.fs = fs.New(
c.Os,
c.LocalGitDir(),
c.LocalWorkingDir(),
lfsdir,
)
} }
return c.fs return c.fs

@ -158,6 +158,7 @@ func keyIsUnsafe(key string) bool {
} }
var safeKeys = []string{ var safeKeys = []string{
"lfs.allowincompletepush",
"lfs.fetchexclude", "lfs.fetchexclude",
"lfs.fetchinclude", "lfs.fetchinclude",
"lfs.gitprotocol", "lfs.gitprotocol",

@ -190,8 +190,8 @@ The following configuration:
Would, therefore, include commits: F, E, D, C, B, but exclude commit A. Would, therefore, include commits: F, E, D, C, B, but exclude commit A.
The presence of flag `--everything` indicates that all local references should be The presence of flag `--everything` indicates that all local and remote
migrated. references should be migrated.
## EXAMPLES ## EXAMPLES
@ -253,16 +253,20 @@ Note: This will require a force push to any existing Git remotes.
### Migrate without rewriting local history ### Migrate without rewriting local history
You can also migrate files without modifying the existing history of your respoitory: You can also migrate files without modifying the existing history of your
repository:
Without a specified commit message: Without a specified commit message:
``` ```
git lfs migrate import --no-rewrite test.zip *.mp3 *.psd $ git lfs migrate import --no-rewrite test.zip *.mp3 *.psd
``` ```
With a specified commit message: With a specified commit message:
``` ```
git lfs migrate import --no-rewrite -m "Import .zip, .mp3, .psd files" \ $ git lfs migrate import --no-rewrite -m "Import .zip, .mp3, .psd files" \
test.zip *.mpd *.psd test.zip *.mpd *.psd
``` ```
## SEE ALSO ## SEE ALSO

108
fs/fs.go

@ -1,6 +1,7 @@
package fs package fs
import ( import (
"bufio"
"bytes" "bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -12,10 +13,19 @@ import (
"sync" "sync"
"github.com/git-lfs/git-lfs/tools" "github.com/git-lfs/git-lfs/tools"
"github.com/rubyist/tracerx"
) )
var oidRE = regexp.MustCompile(`\A[[:alnum:]]{64}`) var oidRE = regexp.MustCompile(`\A[[:alnum:]]{64}`)
// Environment is a copy of a subset of the interface
// github.com/git-lfs/git-lfs/config.Environment.
//
// For more information, see config/environment.go.
type Environment interface {
Get(key string) (val string, ok bool)
}
// Object represents a locally stored LFS object. // Object represents a locally stored LFS object.
type Object struct { type Object struct {
Oid string Oid string
@ -23,9 +33,9 @@ type Object struct {
} }
type Filesystem struct { type Filesystem struct {
GitStorageDir string // parent of objects/lfs (may be same as GitDir but may not) GitStorageDir string // parent of objects/lfs (may be same as GitDir but may not)
LFSStorageDir string // parent of lfs objects and tmp dirs. Default: ".git/lfs" LFSStorageDir string // parent of lfs objects and tmp dirs. Default: ".git/lfs"
ReferenceDir string // alternative local media dir (relative to clone reference repo) ReferenceDirs []string // alternative local media dirs (relative to clone reference repo)
lfsobjdir string lfsobjdir string
tmpdir string tmpdir string
logdir string logdir string
@ -105,12 +115,16 @@ func (f *Filesystem) localObjectDir(oid string) string {
return filepath.Join(f.LFSObjectDir(), oid[0:2], oid[2:4]) return filepath.Join(f.LFSObjectDir(), oid[0:2], oid[2:4])
} }
func (f *Filesystem) ObjectReferencePath(oid string) string { func (f *Filesystem) ObjectReferencePaths(oid string) []string {
if len(f.ReferenceDir) == 0 { if len(f.ReferenceDirs) == 0 {
return f.ReferenceDir return nil
} }
return filepath.Join(f.ReferenceDir, oid[0:2], oid[2:4], oid) var paths []string
for _, ref := range f.ReferenceDirs {
paths = append(paths, filepath.Join(ref, oid[0:2], oid[2:4], oid))
}
return paths
} }
func (f *Filesystem) LFSObjectDir() string { func (f *Filesystem) LFSObjectDir() string {
@ -159,12 +173,12 @@ func (f *Filesystem) Cleanup() error {
// New initializes a new *Filesystem with the given directories. gitdir is the // New initializes a new *Filesystem with the given directories. gitdir is the
// path to the bare repo, workdir is the path to the repository working // path to the bare repo, workdir is the path to the repository working
// directory, and lfsdir is the optional path to the `.git/lfs` directory. // directory, and lfsdir is the optional path to the `.git/lfs` directory.
func New(gitdir, workdir, lfsdir string) *Filesystem { func New(env Environment, gitdir, workdir, lfsdir string) *Filesystem {
fs := &Filesystem{ fs := &Filesystem{
GitStorageDir: resolveGitStorageDir(gitdir), GitStorageDir: resolveGitStorageDir(gitdir),
} }
fs.ReferenceDir = resolveReferenceDir(fs.GitStorageDir) fs.ReferenceDirs = resolveReferenceDirs(env, fs.GitStorageDir)
if len(lfsdir) == 0 { if len(lfsdir) == 0 {
lfsdir = "lfs" lfsdir = "lfs"
@ -179,19 +193,75 @@ func New(gitdir, workdir, lfsdir string) *Filesystem {
return fs return fs
} }
func resolveReferenceDir(gitStorageDir string) string { func resolveReferenceDirs(env Environment, gitStorageDir string) []string {
cloneReferencePath := filepath.Join(gitStorageDir, "objects", "info", "alternates") var references []string
if tools.FileExists(cloneReferencePath) {
buffer, err := ioutil.ReadFile(cloneReferencePath) envAlternates, ok := env.Get("GIT_ALTERNATE_OBJECT_DIRECTORIES")
if err == nil { if ok {
path := strings.TrimSpace(string(buffer[:])) splits := strings.Split(envAlternates, string(os.PathListSeparator))
referenceLfsStoragePath := filepath.Join(filepath.Dir(path), "lfs", "objects") for _, split := range splits {
if tools.DirExists(referenceLfsStoragePath) { if dir, ok := existsAlternate(split); ok {
return referenceLfsStoragePath references = append(references, dir)
} }
} }
} }
return ""
cloneReferencePath := filepath.Join(gitStorageDir, "objects", "info", "alternates")
if tools.FileExists(cloneReferencePath) {
f, err := os.Open(cloneReferencePath)
if err != nil {
tracerx.Printf("could not open %s: %s",
cloneReferencePath, err)
return nil
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
text := strings.TrimSpace(scanner.Text())
if len(text) == 0 || strings.HasPrefix(text, "#") {
continue
}
if dir, ok := existsAlternate(text); ok {
references = append(references, dir)
}
}
if err := scanner.Err(); err != nil {
tracerx.Printf("could not scan %s: %s",
cloneReferencePath, err)
}
}
return references
}
// existsAlternate takes an object directory given in "objs" (read as a single,
// line from .git/objects/info/alternates). If that is a satisfiable alternates
// directory (i.e., it exists), the directory is returned along with "true". If
// not, the empty string and false is returned instead.
func existsAlternate(objs string) (string, bool) {
objs = strings.TrimSpace(objs)
if strings.HasPrefix(objs, "\"") {
var err error
unquote := strings.LastIndex(objs, "\"")
if unquote == 0 {
return "", false
}
objs, err = strconv.Unquote(objs[:unquote+1])
if err != nil {
return "", false
}
}
storage := filepath.Join(filepath.Dir(objs), "lfs", "objects")
if tools.DirExists(storage) {
return storage, true
}
return "", false
} }
// From a git dir, get the location that objects are to be stored (we will store lfs alongside) // From a git dir, get the location that objects are to be stored (we will store lfs alongside)

@ -16,6 +16,18 @@ import (
func (f *GitFilter) SmudgeToFile(filename string, ptr *Pointer, download bool, manifest *tq.Manifest, cb tools.CopyCallback) error { func (f *GitFilter) SmudgeToFile(filename string, ptr *Pointer, download bool, manifest *tq.Manifest, cb tools.CopyCallback) error {
os.MkdirAll(filepath.Dir(filename), 0755) os.MkdirAll(filepath.Dir(filename), 0755)
if stat, _ := os.Stat(filename); stat != nil && stat.Mode()&0200 == 0 {
if err := os.Chmod(filename, stat.Mode()|0200); err != nil {
return errors.Wrap(err,
"Could not restore write permission")
}
// When we're done, return the file back to its normal
// permission bits.
defer os.Chmod(filename, stat.Mode())
}
file, err := os.Create(filename) file, err := os.Create(filename)
if err != nil { if err != nil {
return fmt.Errorf("Could not create working directory file: %v", err) return fmt.Errorf("Could not create working directory file: %v", err)

@ -35,12 +35,14 @@ func Environ(cfg *config.Configuration, manifest *tq.Manifest) []string {
fetchPruneConfig := NewFetchPruneConfig(cfg.Git) fetchPruneConfig := NewFetchPruneConfig(cfg.Git)
references := strings.Join(cfg.LocalReferenceDirs(), ", ")
env = append(env, env = append(env,
fmt.Sprintf("LocalWorkingDir=%s", cfg.LocalWorkingDir()), fmt.Sprintf("LocalWorkingDir=%s", cfg.LocalWorkingDir()),
fmt.Sprintf("LocalGitDir=%s", cfg.LocalGitDir()), fmt.Sprintf("LocalGitDir=%s", cfg.LocalGitDir()),
fmt.Sprintf("LocalGitStorageDir=%s", cfg.LocalGitStorageDir()), fmt.Sprintf("LocalGitStorageDir=%s", cfg.LocalGitStorageDir()),
fmt.Sprintf("LocalMediaDir=%s", cfg.LFSObjectDir()), fmt.Sprintf("LocalMediaDir=%s", cfg.LFSObjectDir()),
fmt.Sprintf("LocalReferenceDir=%s", cfg.LocalReferenceDir()), fmt.Sprintf("LocalReferenceDirs=%s", references),
fmt.Sprintf("TempDir=%s", cfg.TempDir()), fmt.Sprintf("TempDir=%s", cfg.TempDir()),
fmt.Sprintf("ConcurrentTransfers=%d", api.ConcurrentTransfers), fmt.Sprintf("ConcurrentTransfers=%d", api.ConcurrentTransfers),
fmt.Sprintf("TusTransfers=%v", cfg.TusTransfersAllowed()), fmt.Sprintf("TusTransfers=%v", cfg.TusTransfersAllowed()),
@ -98,13 +100,19 @@ func LinkOrCopyFromReference(cfg *config.Configuration, oid string, size int64)
if cfg.LFSObjectExists(oid, size) { if cfg.LFSObjectExists(oid, size) {
return nil return nil
} }
altMediafile := cfg.Filesystem().ObjectReferencePath(oid) altMediafiles := cfg.Filesystem().ObjectReferencePaths(oid)
mediafile, err := cfg.Filesystem().ObjectPath(oid) mediafile, err := cfg.Filesystem().ObjectPath(oid)
if err != nil { if err != nil {
return err return err
} }
if altMediafile != "" && tools.FileExistsOfSize(altMediafile, size) { for _, altMediafile := range altMediafiles {
return LinkOrCopy(cfg, altMediafile, mediafile) tracerx.Printf("altMediafile: %s", altMediafile)
if altMediafile != "" && tools.FileExistsOfSize(altMediafile, size) {
err = LinkOrCopy(cfg, altMediafile, mediafile)
if err == nil {
break
}
}
} }
return nil return err
} }

@ -78,6 +78,14 @@ type AskPassCredentialHelper struct {
Program string Program string
} }
type credValueType int
const (
credValueTypeUnknown credValueType = iota
credValueTypeUsername
credValueTypePassword
)
// Fill implements fill by running the ASKPASS program and returning its output // Fill implements fill by running the ASKPASS program and returning its output
// as a password encoded in the Creds type given the key "password". // as a password encoded in the Creds type given the key "password".
// //
@ -86,60 +94,92 @@ type AskPassCredentialHelper struct {
// //
// If there was an error running the command, it is returned instead of a set of // If there was an error running the command, it is returned instead of a set of
// filled credentials. // filled credentials.
//
// The ASKPASS program is only queried if a credential was not already
// provided, i.e. through the git URL
func (a *AskPassCredentialHelper) Fill(what Creds) (Creds, error) { func (a *AskPassCredentialHelper) Fill(what Creds) (Creds, error) {
var user bytes.Buffer
var pass bytes.Buffer
var err bytes.Buffer
u := &url.URL{ u := &url.URL{
Scheme: what["protocol"], Scheme: what["protocol"],
Host: what["host"], Host: what["host"],
Path: what["path"], Path: what["path"],
} }
// 'ucmd' will run the GIT_ASKPASS (or core.askpass) command prompting creds := make(Creds)
// for a username.
ucmd := exec.Command(a.Program, a.args(fmt.Sprintf("Username for %q", u))...)
ucmd.Stderr = &err
ucmd.Stdout = &user
tracerx.Printf("creds: filling with GIT_ASKPASS: %s", strings.Join(ucmd.Args, " ")) username, err := a.getValue(what, credValueTypeUsername, u)
if err := ucmd.Run(); err != nil { if err != nil {
return nil, err return nil, err
} }
creds["username"] = username
if err.Len() > 0 { if len(username) > 0 {
return nil, errors.New(err.String())
}
if username := strings.TrimSpace(user.String()); len(username) > 0 {
// If a non-empty username was given, add it to the URL via func // If a non-empty username was given, add it to the URL via func
// 'net/url.User()'. // 'net/url.User()'.
u.User = url.User(username) u.User = url.User(creds["username"])
} }
// Regardless, create 'pcmd' to run the GIT_ASKPASS (or core.askpass) password, err := a.getValue(what, credValueTypePassword, u)
// command prompting for a password. if err != nil {
pcmd := exec.Command(a.Program, a.args(fmt.Sprintf("Password for %q", u))...)
pcmd.Stderr = &err
pcmd.Stdout = &pass
tracerx.Printf("creds: filling with GIT_ASKPASS: %s", strings.Join(pcmd.Args, " "))
if err := pcmd.Run(); err != nil {
return nil, err return nil, err
} }
creds["password"] = password
return creds, nil
}
func (a *AskPassCredentialHelper) getValue(what Creds, valueType credValueType, u *url.URL) (string, error) {
var valueString string
switch valueType {
case credValueTypeUsername:
valueString = "username"
case credValueTypePassword:
valueString = "password"
default:
return "", errors.Errorf("Invalid Credential type queried from AskPass")
}
// Return the existing credential if it was already provided, otherwise
// query AskPass for it
if given, ok := what[valueString]; ok {
return given, nil
}
return a.getFromProgram(valueType, u)
}
func (a *AskPassCredentialHelper) getFromProgram(valueType credValueType, u *url.URL) (string, error) {
var (
value bytes.Buffer
err bytes.Buffer
valueString string
)
switch valueType {
case credValueTypeUsername:
valueString = "Username"
case credValueTypePassword:
valueString = "Password"
default:
return "", errors.Errorf("Invalid Credential type queried from AskPass")
}
// 'cmd' will run the GIT_ASKPASS (or core.askpass) command prompting
// for the desired valueType (`Username` or `Password`)
cmd := exec.Command(a.Program, a.args(fmt.Sprintf("%s for %q", valueString, u))...)
cmd.Stderr = &err
cmd.Stdout = &value
tracerx.Printf("creds: filling with GIT_ASKPASS: %s", strings.Join(cmd.Args, " "))
if err := cmd.Run(); err != nil {
return "", err
}
if err.Len() > 0 { if err.Len() > 0 {
return nil, errors.New(err.String()) return "", errors.New(err.String())
} }
// Finally, now that we have the username and password information, return strings.TrimSpace(value.String()), nil
// store it in the creds instance that we will return to the caller.
creds := make(Creds)
creds["username"] = strings.TrimSpace(user.String())
creds["password"] = strings.TrimSpace(pass.String())
return creds, nil
} }
// Approve implements CredentialHelper.Approve, and returns nil. The ASKPASS // Approve implements CredentialHelper.Approve, and returns nil. The ASKPASS

143
test/test-alternates.sh Executable file

@ -0,0 +1,143 @@
#!/usr/bin/env bash
. "test/testlib.sh"
begin_test "alternates (single)"
(
set -e
reponame="alternates-single-alternate"
setup_remote_repo_with_file "$reponame" "a.txt"
pushd "$TRASHDIR" > /dev/null
clone_repo "$reponame" "${reponame}_alternate"
popd > /dev/null
rm -rf .git/lfs/objects
alternate="$TRASHDIR/${reponame}_alternate/.git/objects"
echo "$alternate" > .git/objects/info/alternates
GIT_TRACE=1 git lfs fetch origin master 2>&1 | tee fetch.log
[ "0" -eq "$(grep -c "sending batch of size 1" fetch.log)" ]
)
end_test
begin_test "alternates (multiple)"
(
set -e
reponame="alternates-multiple-alternates"
setup_remote_repo_with_file "$reponame" "a.txt"
pushd "$TRASHDIR" > /dev/null
clone_repo "$reponame" "${reponame}_alternate_stale"
rm -rf .git/lfs/objects
popd > /dev/null
pushd "$TRASHDIR" > /dev/null
clone_repo "$reponame" "${reponame}_alternate"
popd > /dev/null
rm -rf .git/lfs/objects
alternate_stale="$TRASHDIR/${reponame}_alternate_stale/.git/objects"
alternate="$TRASHDIR/${reponame}_alternate/.git/objects"
echo "$alternate" > .git/objects/info/alternates
echo "$alternate_stale" >> .git/objects/info/alternates
GIT_TRACE=1 git lfs fetch origin master 2>&1 | tee fetch.log
[ "0" -eq "$(grep -c "sending batch of size 1" fetch.log)" ]
)
end_test
begin_test "alternates (commented)"
(
set -e
reponame="alternates-commented-alternate"
setup_remote_repo_with_file "$reponame" "a.txt"
pushd "$TRASHDIR" > /dev/null
clone_repo "$reponame" "${reponame}_alternate"
popd > /dev/null
rm -rf .git/lfs/objects
alternate="$TRASHDIR/${reponame}_alternate/.git/objects"
echo "# $alternate" > .git/objects/info/alternates
GIT_TRACE=1 git lfs fetch origin master 2>&1 | tee fetch.log
[ "1" -eq "$(grep -c "sending batch of size 1" fetch.log)" ]
)
end_test
begin_test "alternates (quoted)"
(
set -e
reponame="alternates-quoted-alternate"
setup_remote_repo_with_file "$reponame" "a.txt"
pushd "$TRASHDIR" > /dev/null
clone_repo "$reponame" "${reponame}_alternate"
popd > /dev/null
rm -rf .git/lfs/objects
alternate="$TRASHDIR/${reponame}_alternate/.git/objects"
echo "\"$alternate\"" > .git/objects/info/alternates
GIT_TRACE=1 git lfs fetch origin master 2>&1 | tee fetch.log
[ "0" -eq "$(grep -c "sending batch of size 1" fetch.log)" ]
)
end_test
begin_test "alternates (OS environment, single)"
(
set -e
reponame="alternates-environment-single-alternate"
setup_remote_repo_with_file "$reponame" "a.txt"
pushd "$TRASHDIR" > /dev/null
clone_repo "$reponame" "${reponame}_alternate"
popd > /dev/null
rm -rf .git/lfs/objects
alternate="$TRASHDIR/${reponame}_alternate/.git/objects"
GIT_ALTERNATE_OBJECT_DIRECTORIES="$alternate" \
GIT_TRACE=1 \
git lfs fetch origin master 2>&1 | tee fetch.log
[ "0" -eq "$(grep -c "sending batch of size 1" fetch.log)" ]
)
end_test
begin_test "alternates (OS environment, multiple)"
(
set -e
reponame="alternates-environment-multiple-alternates"
setup_remote_repo_with_file "$reponame" "a.txt"
pushd "$TRASHDIR" > /dev/null
clone_repo "$reponame" "${reponame}_alternate_stale"
rm -rf .git/lfs/objects
popd > /dev/null
pushd "$TRASHDIR" > /dev/null
clone_repo "$reponame" "${reponame}_alternate"
popd > /dev/null
rm -rf .git/lfs/objects
alternate_stale="$TRASHDIR/${reponame}_alternate_stale/.git/objects"
alternate="$TRASHDIR/${reponame}_alternate/.git/objects"
sep="$(native_path_list_separator)"
GIT_ALTERNATE_OBJECT_DIRECTORIES="$alternate_stale$sep$alternate" \
GIT_TRACE=1 \
git lfs fetch origin master 2>&1 | tee fetch.log
[ "0" -eq "$(grep -c "sending batch of size 1" fetch.log)" ]
)
end_test

@ -102,3 +102,33 @@ begin_test "askpass: push with SSH_ASKPASS"
grep "master -> master" push.log grep "master -> master" push.log
) )
end_test end_test
begin_test "askpass: defaults to provided credentials"
(
set -e
reponame="askpass-provided-creds"
setup_remote_repo "$reponame"
clone_repo "$reponame" "$reponame"
git lfs track "*.dat"
echo "hello" > a.dat
git add .gitattributes a.dat
git commit -m "initial commit"
# $password is defined from test/cmd/lfstest-gitserver.go (see: skipIfBadAuth)
export LFS_ASKPASS_USERNAME="fakeuser"
export LFS_ASKPASS_PASSWORD="fakepass"
git config --local "credential.helper" ""
url=$(git config --get remote.origin.url)
newurl=${url/http:\/\//http:\/\/user\:pass@}
git remote set-url origin "$newurl"
GIT_ASKPASS="lfs-askpass" GIT_TRACE=1 GIT_CURL_VERBOSE=1 git push origin master 2>&1 | tee push.log
[ ! $(grep "filling with GIT_ASKPASS" push.log) ]
grep "master -> master" push.log
)
end_test

@ -156,3 +156,29 @@ begin_test "checkout: outside git repository"
grep "Not in a git repository" checkout.log grep "Not in a git repository" checkout.log
) )
end_test end_test
begin_test "checkout: write-only file"
(
set -e
reponame="checkout-locked"
filename="a.txt"
setup_remote_repo_with_file "$reponame" "$filename"
pushd "$TRASHDIR" > /dev/null
GIT_LFS_SKIP_SMUDGE=1 clone_repo "$reponame" "${reponame}_checkout"
chmod -w "$filename"
refute_file_writeable "$filename"
assert_pointer "refs/heads/master" "$filename" "$(calc_oid "$filename\n")" 6
git lfs fetch
git lfs checkout "$filename"
refute_file_writeable "$filename"
[ "$filename" = "$(cat "$filename")" ]
popd > /dev/null
)
end_test

@ -29,7 +29,7 @@ LocalWorkingDir=%s
LocalGitDir=%s LocalGitDir=%s
LocalGitStorageDir=%s LocalGitStorageDir=%s
LocalMediaDir=%s LocalMediaDir=%s
LocalReferenceDir= LocalReferenceDirs=
TempDir=%s TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
TusTransfers=false TusTransfers=false
@ -81,7 +81,7 @@ LocalWorkingDir=%s
LocalGitDir=%s LocalGitDir=%s
LocalGitStorageDir=%s LocalGitStorageDir=%s
LocalMediaDir=%s LocalMediaDir=%s
LocalReferenceDir= LocalReferenceDirs=
TempDir=%s TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
TusTransfers=false TusTransfers=false
@ -140,7 +140,7 @@ LocalWorkingDir=%s
LocalGitDir=%s LocalGitDir=%s
LocalGitStorageDir=%s LocalGitStorageDir=%s
LocalMediaDir=%s LocalMediaDir=%s
LocalReferenceDir= LocalReferenceDirs=
TempDir=%s TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
TusTransfers=false TusTransfers=false
@ -197,7 +197,7 @@ LocalWorkingDir=%s
LocalGitDir=%s LocalGitDir=%s
LocalGitStorageDir=%s LocalGitStorageDir=%s
LocalMediaDir=%s LocalMediaDir=%s
LocalReferenceDir= LocalReferenceDirs=
TempDir=%s TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
TusTransfers=false TusTransfers=false
@ -256,7 +256,7 @@ LocalWorkingDir=%s
LocalGitDir=%s LocalGitDir=%s
LocalGitStorageDir=%s LocalGitStorageDir=%s
LocalMediaDir=%s LocalMediaDir=%s
LocalReferenceDir= LocalReferenceDirs=
TempDir=%s TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
TusTransfers=false TusTransfers=false
@ -316,7 +316,7 @@ LocalWorkingDir=%s
LocalGitDir=%s LocalGitDir=%s
LocalGitStorageDir=%s LocalGitStorageDir=%s
LocalMediaDir=%s LocalMediaDir=%s
LocalReferenceDir= LocalReferenceDirs=
TempDir=%s TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
TusTransfers=false TusTransfers=false
@ -377,7 +377,7 @@ LocalWorkingDir=%s
LocalGitDir=%s LocalGitDir=%s
LocalGitStorageDir=%s LocalGitStorageDir=%s
LocalMediaDir=%s LocalMediaDir=%s
LocalReferenceDir= LocalReferenceDirs=
TempDir=%s TempDir=%s
ConcurrentTransfers=5 ConcurrentTransfers=5
TusTransfers=false TusTransfers=false
@ -445,7 +445,7 @@ LocalWorkingDir=%s
LocalGitDir=%s LocalGitDir=%s
LocalGitStorageDir=%s LocalGitStorageDir=%s
LocalMediaDir=%s LocalMediaDir=%s
LocalReferenceDir= LocalReferenceDirs=
TempDir=%s TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
TusTransfers=false TusTransfers=false
@ -500,7 +500,7 @@ LocalWorkingDir=%s
LocalGitDir=%s LocalGitDir=%s
LocalGitStorageDir=%s LocalGitStorageDir=%s
LocalMediaDir=%s LocalMediaDir=%s
LocalReferenceDir= LocalReferenceDirs=
TempDir=%s TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
TusTransfers=false TusTransfers=false
@ -549,7 +549,7 @@ LocalWorkingDir=%s
LocalGitDir=%s LocalGitDir=%s
LocalGitStorageDir=%s LocalGitStorageDir=%s
LocalMediaDir=%s LocalMediaDir=%s
LocalReferenceDir= LocalReferenceDirs=
TempDir=%s TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
TusTransfers=false TusTransfers=false
@ -582,7 +582,7 @@ LocalWorkingDir=%s
LocalGitDir=%s LocalGitDir=%s
LocalGitStorageDir=%s LocalGitStorageDir=%s
LocalMediaDir=%s LocalMediaDir=%s
LocalReferenceDir= LocalReferenceDirs=
TempDir=%s TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
TusTransfers=false TusTransfers=false
@ -615,7 +615,7 @@ LocalWorkingDir=%s
LocalGitDir=%s LocalGitDir=%s
LocalGitStorageDir=%s LocalGitStorageDir=%s
LocalMediaDir=%s LocalMediaDir=%s
LocalReferenceDir= LocalReferenceDirs=
TempDir=%s TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
TusTransfers=false TusTransfers=false
@ -660,7 +660,7 @@ LocalWorkingDir=
LocalGitDir=%s LocalGitDir=%s
LocalGitStorageDir=%s LocalGitStorageDir=%s
LocalMediaDir=%s LocalMediaDir=%s
LocalReferenceDir= LocalReferenceDirs=
TempDir=%s TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
TusTransfers=false TusTransfers=false
@ -739,7 +739,7 @@ LocalWorkingDir=%s
LocalGitDir=%s LocalGitDir=%s
LocalGitStorageDir=%s LocalGitStorageDir=%s
LocalMediaDir=%s LocalMediaDir=%s
LocalReferenceDir= LocalReferenceDirs=
TempDir=%s TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
TusTransfers=false TusTransfers=false
@ -772,7 +772,7 @@ LocalWorkingDir=%s
LocalGitDir=%s LocalGitDir=%s
LocalGitStorageDir=%s LocalGitStorageDir=%s
LocalMediaDir=%s LocalMediaDir=%s
LocalReferenceDir= LocalReferenceDirs=
TempDir=%s TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
TusTransfers=false TusTransfers=false
@ -838,7 +838,7 @@ LocalWorkingDir=%s
LocalGitDir=%s LocalGitDir=%s
LocalGitStorageDir=%s LocalGitStorageDir=%s
LocalMediaDir=%s LocalMediaDir=%s
LocalReferenceDir= LocalReferenceDirs=
TempDir=%s TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
TusTransfers=true TusTransfers=true

@ -178,7 +178,7 @@ setup_single_local_branch_tracked_corrupt() {
# #
# - Commit 'A' has 120, 140 bytes of data in a.txt, and a.md, respectively. # - Commit 'A' has 120, 140 bytes of data in a.txt, and a.md, respectively.
# #
# - Commit 'B' has 30 bytes of data in a.txt, and includes commit 'A' as a # - Commit 'B' has 30 bytes of data in a.md, and includes commit 'A' as a
# parent. # parent.
setup_multiple_local_branches() { setup_multiple_local_branches() {
set -e set -e
@ -218,6 +218,29 @@ setup_multiple_local_branches_with_gitattrs() {
git commit -m "add .gitattributes" git commit -m "add .gitattributes"
} }
# setup_multiple_local_branches_non_standard creates a repository as follows:
#
# refs/pull/1/head
# /
# |
# B
# / \
# A refs/heads/my-feature
# |\
# | refs/heads/master
# \
# refs/pull/1/base
#
# With the same contents in 'A' and 'B' as setup_multiple_local_branches.
setup_multiple_local_branches_non_standard() {
set -e
setup_multiple_local_branches
git update-ref refs/pull/1/head "$(git rev-parse my-feature)"
git update-ref refs/pull/1/base "$(git rev-parse master)"
}
# setup_multiple_local_branches_tracked creates a repo with exactly the same # setup_multiple_local_branches_tracked creates a repo with exactly the same
# structure as in setup_multiple_local_branches, but with all files tracked by # structure as in setup_multiple_local_branches, but with all files tracked by
# Git LFS # Git LFS

@ -763,3 +763,29 @@ begin_test "migrate import (dirty copy, positive answer)"
assert_local_object "$oid" "5" assert_local_object "$oid" "5"
) )
end_test end_test
begin_test "migrate import (non-standard refs)"
(
set -e
setup_multiple_local_branches_non_standard
md_oid="$(calc_oid "$(git cat-file -p :a.md)")"
txt_oid="$(calc_oid "$(git cat-file -p :a.txt)")"
md_feature_oid="$(calc_oid "$(git cat-file -p my-feature:a.md)")"
git lfs migrate import --everything
assert_pointer "refs/heads/master" "a.md" "$md_oid" "140"
assert_pointer "refs/heads/master" "a.txt" "$txt_oid" "120"
assert_pointer "refs/pull/1/base" "a.md" "$md_oid" "140"
assert_pointer "refs/pull/1/base" "a.txt" "$txt_oid" "120"
assert_pointer "refs/heads/my-feature" "a.txt" "$txt_oid" "120"
assert_pointer "refs/pull/1/head" "a.txt" "$txt_oid" "120"
assert_local_object "$md_oid" "140"
assert_local_object "$txt_oid" "120"
assert_local_object "$md_feature_oid" "30"
)
end_test

@ -25,7 +25,7 @@ LocalWorkingDir=$(native_path_escaped "$TRASHDIR/$reponame")
LocalGitDir=$(native_path_escaped "$TRASHDIR/$reponame/.git") LocalGitDir=$(native_path_escaped "$TRASHDIR/$reponame/.git")
LocalGitStorageDir=$(native_path_escaped "$TRASHDIR/$reponame/.git") LocalGitStorageDir=$(native_path_escaped "$TRASHDIR/$reponame/.git")
LocalMediaDir=$(native_path_escaped "$TRASHDIR/$reponame/.git/lfs/objects") LocalMediaDir=$(native_path_escaped "$TRASHDIR/$reponame/.git/lfs/objects")
LocalReferenceDir= LocalReferenceDirs=
TempDir=$(native_path_escaped "$TRASHDIR/$reponame/.git/lfs/tmp") TempDir=$(native_path_escaped "$TRASHDIR/$reponame/.git/lfs/tmp")
ConcurrentTransfers=3 ConcurrentTransfers=3
TusTransfers=false TusTransfers=false
@ -61,7 +61,7 @@ LocalWorkingDir=$(native_path_escaped "$TRASHDIR/$worktreename")
LocalGitDir=$(native_path_escaped "$TRASHDIR/$reponame/.git/worktrees/$worktreename") LocalGitDir=$(native_path_escaped "$TRASHDIR/$reponame/.git/worktrees/$worktreename")
LocalGitStorageDir=$(native_path_escaped "$TRASHDIR/$reponame/.git") LocalGitStorageDir=$(native_path_escaped "$TRASHDIR/$reponame/.git")
LocalMediaDir=$(native_path_escaped "$TRASHDIR/$reponame/.git/lfs/objects") LocalMediaDir=$(native_path_escaped "$TRASHDIR/$reponame/.git/lfs/objects")
LocalReferenceDir= LocalReferenceDirs=
TempDir=$(native_path_escaped "$TRASHDIR/$reponame/.git/worktrees/$worktreename/lfs/tmp") TempDir=$(native_path_escaped "$TRASHDIR/$reponame/.git/worktrees/$worktreename/lfs/tmp")
ConcurrentTransfers=3 ConcurrentTransfers=3
TusTransfers=false TusTransfers=false

@ -40,7 +40,7 @@ refute_pointer() {
file=$(git cat-file -p $gitblob) file=$(git cat-file -p $gitblob)
version="version https://git-lfs.github.com/spec/v[0-9]" version="version https://git-lfs.github.com/spec/v[0-9]"
oid="oid sha256:[0-9a-f]\{32\}" oid="oid sha256:[0-9a-f]\{64\}"
size="size [0-9]*" size="size [0-9]*"
regex="$version.*$oid.*$size" regex="$version.*$oid.*$size"
@ -730,6 +730,16 @@ native_path_escaped() {
escape_path "$unescaped" escape_path "$unescaped"
} }
# native_path_list_separator prints the operating system-specific path list
# separator.
native_path_list_separator() {
if [ "$IS_WINDOWS" -eq 1 ]; then
printf ";";
else
printf ":";
fi
}
cat_end() { cat_end() {
if [ $IS_WINDOWS -eq 1 ]; then if [ $IS_WINDOWS -eq 1 ]; then
printf '^M$' printf '^M$'