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...)
exclude = append(exclude, migrateExcludeRefs...)
} else if migrateEverything {
localRefs, err := git.LocalRefs()
refs, err := git.AllRefsIn("")
if err != nil {
return nil, nil, err
}
for _, ref := range localRefs {
include = append(include, ref.Refspec())
for _, ref := range refs {
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 {
bare, err := git.IsBare()

@ -311,8 +311,8 @@ func (c *Configuration) LocalGitStorageDir() string {
return c.Filesystem().GitStorageDir
}
func (c *Configuration) LocalReferenceDir() string {
return c.Filesystem().ReferenceDir
func (c *Configuration) LocalReferenceDirs() []string {
return c.Filesystem().ReferenceDirs
}
func (c *Configuration) LFSStorageDir() string {
@ -346,7 +346,12 @@ func (c *Configuration) Filesystem() *fs.Filesystem {
if c.fs == nil {
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

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

@ -190,8 +190,8 @@ The following configuration:
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
migrated.
The presence of flag `--everything` indicates that all local and remote
references should be migrated.
## EXAMPLES
@ -253,16 +253,20 @@ Note: This will require a force push to any existing Git remotes.
### 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:
```
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:
```
git lfs migrate import --no-rewrite -m "Import .zip, .mp3, .psd files" \
test.zip *.mpd *.psd
$ git lfs migrate import --no-rewrite -m "Import .zip, .mp3, .psd files" \
test.zip *.mpd *.psd
```
## SEE ALSO

108
fs/fs.go

@ -1,6 +1,7 @@
package fs
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
@ -12,10 +13,19 @@ import (
"sync"
"github.com/git-lfs/git-lfs/tools"
"github.com/rubyist/tracerx"
)
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.
type Object struct {
Oid string
@ -23,9 +33,9 @@ type Object struct {
}
type Filesystem struct {
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"
ReferenceDir string // alternative local media dir (relative to clone reference repo)
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"
ReferenceDirs []string // alternative local media dirs (relative to clone reference repo)
lfsobjdir string
tmpdir 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])
}
func (f *Filesystem) ObjectReferencePath(oid string) string {
if len(f.ReferenceDir) == 0 {
return f.ReferenceDir
func (f *Filesystem) ObjectReferencePaths(oid string) []string {
if len(f.ReferenceDirs) == 0 {
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 {
@ -159,12 +173,12 @@ func (f *Filesystem) Cleanup() error {
// 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
// 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{
GitStorageDir: resolveGitStorageDir(gitdir),
}
fs.ReferenceDir = resolveReferenceDir(fs.GitStorageDir)
fs.ReferenceDirs = resolveReferenceDirs(env, fs.GitStorageDir)
if len(lfsdir) == 0 {
lfsdir = "lfs"
@ -179,19 +193,75 @@ func New(gitdir, workdir, lfsdir string) *Filesystem {
return fs
}
func resolveReferenceDir(gitStorageDir string) string {
cloneReferencePath := filepath.Join(gitStorageDir, "objects", "info", "alternates")
if tools.FileExists(cloneReferencePath) {
buffer, err := ioutil.ReadFile(cloneReferencePath)
if err == nil {
path := strings.TrimSpace(string(buffer[:]))
referenceLfsStoragePath := filepath.Join(filepath.Dir(path), "lfs", "objects")
if tools.DirExists(referenceLfsStoragePath) {
return referenceLfsStoragePath
func resolveReferenceDirs(env Environment, gitStorageDir string) []string {
var references []string
envAlternates, ok := env.Get("GIT_ALTERNATE_OBJECT_DIRECTORIES")
if ok {
splits := strings.Split(envAlternates, string(os.PathListSeparator))
for _, split := range splits {
if dir, ok := existsAlternate(split); ok {
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)

@ -16,6 +16,18 @@ import (
func (f *GitFilter) SmudgeToFile(filename string, ptr *Pointer, download bool, manifest *tq.Manifest, cb tools.CopyCallback) error {
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)
if err != nil {
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)
references := strings.Join(cfg.LocalReferenceDirs(), ", ")
env = append(env,
fmt.Sprintf("LocalWorkingDir=%s", cfg.LocalWorkingDir()),
fmt.Sprintf("LocalGitDir=%s", cfg.LocalGitDir()),
fmt.Sprintf("LocalGitStorageDir=%s", cfg.LocalGitStorageDir()),
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("ConcurrentTransfers=%d", api.ConcurrentTransfers),
fmt.Sprintf("TusTransfers=%v", cfg.TusTransfersAllowed()),
@ -98,13 +100,19 @@ func LinkOrCopyFromReference(cfg *config.Configuration, oid string, size int64)
if cfg.LFSObjectExists(oid, size) {
return nil
}
altMediafile := cfg.Filesystem().ObjectReferencePath(oid)
altMediafiles := cfg.Filesystem().ObjectReferencePaths(oid)
mediafile, err := cfg.Filesystem().ObjectPath(oid)
if err != nil {
return err
}
if altMediafile != "" && tools.FileExistsOfSize(altMediafile, size) {
return LinkOrCopy(cfg, altMediafile, mediafile)
for _, altMediafile := range altMediafiles {
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
}
type credValueType int
const (
credValueTypeUnknown credValueType = iota
credValueTypeUsername
credValueTypePassword
)
// Fill implements fill by running the ASKPASS program and returning its output
// 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
// 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) {
var user bytes.Buffer
var pass bytes.Buffer
var err bytes.Buffer
u := &url.URL{
Scheme: what["protocol"],
Host: what["host"],
Path: what["path"],
}
// 'ucmd' will run the GIT_ASKPASS (or core.askpass) command prompting
// for a username.
ucmd := exec.Command(a.Program, a.args(fmt.Sprintf("Username for %q", u))...)
ucmd.Stderr = &err
ucmd.Stdout = &user
creds := make(Creds)
tracerx.Printf("creds: filling with GIT_ASKPASS: %s", strings.Join(ucmd.Args, " "))
if err := ucmd.Run(); err != nil {
username, err := a.getValue(what, credValueTypeUsername, u)
if err != nil {
return nil, err
}
creds["username"] = username
if err.Len() > 0 {
return nil, errors.New(err.String())
}
if username := strings.TrimSpace(user.String()); len(username) > 0 {
if len(username) > 0 {
// If a non-empty username was given, add it to the URL via func
// '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)
// command prompting for a password.
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 {
password, err := a.getValue(what, credValueTypePassword, u)
if err != nil {
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 {
return nil, errors.New(err.String())
return "", errors.New(err.String())
}
// Finally, now that we have the username and password information,
// 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
return strings.TrimSpace(value.String()), nil
}
// 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
)
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
)
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
LocalGitStorageDir=%s
LocalMediaDir=%s
LocalReferenceDir=
LocalReferenceDirs=
TempDir=%s
ConcurrentTransfers=3
TusTransfers=false
@ -81,7 +81,7 @@ LocalWorkingDir=%s
LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
LocalReferenceDir=
LocalReferenceDirs=
TempDir=%s
ConcurrentTransfers=3
TusTransfers=false
@ -140,7 +140,7 @@ LocalWorkingDir=%s
LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
LocalReferenceDir=
LocalReferenceDirs=
TempDir=%s
ConcurrentTransfers=3
TusTransfers=false
@ -197,7 +197,7 @@ LocalWorkingDir=%s
LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
LocalReferenceDir=
LocalReferenceDirs=
TempDir=%s
ConcurrentTransfers=3
TusTransfers=false
@ -256,7 +256,7 @@ LocalWorkingDir=%s
LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
LocalReferenceDir=
LocalReferenceDirs=
TempDir=%s
ConcurrentTransfers=3
TusTransfers=false
@ -316,7 +316,7 @@ LocalWorkingDir=%s
LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
LocalReferenceDir=
LocalReferenceDirs=
TempDir=%s
ConcurrentTransfers=3
TusTransfers=false
@ -377,7 +377,7 @@ LocalWorkingDir=%s
LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
LocalReferenceDir=
LocalReferenceDirs=
TempDir=%s
ConcurrentTransfers=5
TusTransfers=false
@ -445,7 +445,7 @@ LocalWorkingDir=%s
LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
LocalReferenceDir=
LocalReferenceDirs=
TempDir=%s
ConcurrentTransfers=3
TusTransfers=false
@ -500,7 +500,7 @@ LocalWorkingDir=%s
LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
LocalReferenceDir=
LocalReferenceDirs=
TempDir=%s
ConcurrentTransfers=3
TusTransfers=false
@ -549,7 +549,7 @@ LocalWorkingDir=%s
LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
LocalReferenceDir=
LocalReferenceDirs=
TempDir=%s
ConcurrentTransfers=3
TusTransfers=false
@ -582,7 +582,7 @@ LocalWorkingDir=%s
LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
LocalReferenceDir=
LocalReferenceDirs=
TempDir=%s
ConcurrentTransfers=3
TusTransfers=false
@ -615,7 +615,7 @@ LocalWorkingDir=%s
LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
LocalReferenceDir=
LocalReferenceDirs=
TempDir=%s
ConcurrentTransfers=3
TusTransfers=false
@ -660,7 +660,7 @@ LocalWorkingDir=
LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
LocalReferenceDir=
LocalReferenceDirs=
TempDir=%s
ConcurrentTransfers=3
TusTransfers=false
@ -739,7 +739,7 @@ LocalWorkingDir=%s
LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
LocalReferenceDir=
LocalReferenceDirs=
TempDir=%s
ConcurrentTransfers=3
TusTransfers=false
@ -772,7 +772,7 @@ LocalWorkingDir=%s
LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
LocalReferenceDir=
LocalReferenceDirs=
TempDir=%s
ConcurrentTransfers=3
TusTransfers=false
@ -838,7 +838,7 @@ LocalWorkingDir=%s
LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
LocalReferenceDir=
LocalReferenceDirs=
TempDir=%s
ConcurrentTransfers=3
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 '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.
setup_multiple_local_branches() {
set -e
@ -218,6 +218,29 @@ setup_multiple_local_branches_with_gitattrs() {
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
# structure as in setup_multiple_local_branches, but with all files tracked by
# Git LFS

@ -763,3 +763,29 @@ begin_test "migrate import (dirty copy, positive answer)"
assert_local_object "$oid" "5"
)
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")
LocalGitStorageDir=$(native_path_escaped "$TRASHDIR/$reponame/.git")
LocalMediaDir=$(native_path_escaped "$TRASHDIR/$reponame/.git/lfs/objects")
LocalReferenceDir=
LocalReferenceDirs=
TempDir=$(native_path_escaped "$TRASHDIR/$reponame/.git/lfs/tmp")
ConcurrentTransfers=3
TusTransfers=false
@ -61,7 +61,7 @@ LocalWorkingDir=$(native_path_escaped "$TRASHDIR/$worktreename")
LocalGitDir=$(native_path_escaped "$TRASHDIR/$reponame/.git/worktrees/$worktreename")
LocalGitStorageDir=$(native_path_escaped "$TRASHDIR/$reponame/.git")
LocalMediaDir=$(native_path_escaped "$TRASHDIR/$reponame/.git/lfs/objects")
LocalReferenceDir=
LocalReferenceDirs=
TempDir=$(native_path_escaped "$TRASHDIR/$reponame/.git/worktrees/$worktreename/lfs/tmp")
ConcurrentTransfers=3
TusTransfers=false

@ -40,7 +40,7 @@ refute_pointer() {
file=$(git cat-file -p $gitblob)
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]*"
regex="$version.*$oid.*$size"
@ -730,6 +730,16 @@ native_path_escaped() {
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() {
if [ $IS_WINDOWS -eq 1 ]; then
printf '^M$'