diff --git a/commands/command_migrate.go b/commands/command_migrate.go index 04f47e6a..2f4f476e 100644 --- a/commands/command_migrate.go +++ b/commands/command_migrate.go @@ -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() diff --git a/config/config.go b/config/config.go index 005d83f0..db261908 100644 --- a/config/config.go +++ b/config/config.go @@ -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 diff --git a/config/git_fetcher.go b/config/git_fetcher.go index f38d468f..3554fc64 100644 --- a/config/git_fetcher.go +++ b/config/git_fetcher.go @@ -158,6 +158,7 @@ func keyIsUnsafe(key string) bool { } var safeKeys = []string{ + "lfs.allowincompletepush", "lfs.fetchexclude", "lfs.fetchinclude", "lfs.gitprotocol", diff --git a/docs/man/git-lfs-migrate.1.ronn b/docs/man/git-lfs-migrate.1.ronn index 3af4fe54..9b06b135 100644 --- a/docs/man/git-lfs-migrate.1.ronn +++ b/docs/man/git-lfs-migrate.1.ronn @@ -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 diff --git a/fs/fs.go b/fs/fs.go index 8118e2b1..c189ead9 100644 --- a/fs/fs.go +++ b/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) diff --git a/lfs/gitfilter_smudge.go b/lfs/gitfilter_smudge.go index 0d1c0d1b..b6dc99fc 100644 --- a/lfs/gitfilter_smudge.go +++ b/lfs/gitfilter_smudge.go @@ -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) diff --git a/lfs/lfs.go b/lfs/lfs.go index eae67ca0..9b4b43dd 100644 --- a/lfs/lfs.go +++ b/lfs/lfs.go @@ -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 } diff --git a/lfsapi/creds.go b/lfsapi/creds.go index 4c113b49..2cd3aa0f 100644 --- a/lfsapi/creds.go +++ b/lfsapi/creds.go @@ -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 diff --git a/test/test-alternates.sh b/test/test-alternates.sh new file mode 100755 index 00000000..d60a84e5 --- /dev/null +++ b/test/test-alternates.sh @@ -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 diff --git a/test/test-askpass.sh b/test/test-askpass.sh index 29d3d52d..b41c5451 100755 --- a/test/test-askpass.sh +++ b/test/test-askpass.sh @@ -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 diff --git a/test/test-checkout.sh b/test/test-checkout.sh index cd2633c8..974127b6 100755 --- a/test/test-checkout.sh +++ b/test/test-checkout.sh @@ -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 diff --git a/test/test-env.sh b/test/test-env.sh index 2a3047e2..cc40e801 100755 --- a/test/test-env.sh +++ b/test/test-env.sh @@ -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 diff --git a/test/test-migrate-fixtures.sh b/test/test-migrate-fixtures.sh index 627f60f1..bdcd5dce 100755 --- a/test/test-migrate-fixtures.sh +++ b/test/test-migrate-fixtures.sh @@ -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 diff --git a/test/test-migrate-import.sh b/test/test-migrate-import.sh index 8794b63d..993ad116 100755 --- a/test/test-migrate-import.sh +++ b/test/test-migrate-import.sh @@ -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 diff --git a/test/test-worktree.sh b/test/test-worktree.sh index d49f99a0..d44f1dfb 100755 --- a/test/test-worktree.sh +++ b/test/test-worktree.sh @@ -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 diff --git a/test/testhelpers.sh b/test/testhelpers.sh index b8cdb1e8..71c74afb 100644 --- a/test/testhelpers.sh +++ b/test/testhelpers.sh @@ -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$'