diff --git a/.gitignore b/.gitignore index 525c4e01..e7d06a73 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,9 @@ man/* *.test tmp -test/remote +t/remote +t/test_count +t/test_count.lock debian/git-lfs/ debian/*.log diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4174cf6b..1b4739e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -112,7 +112,7 @@ submitting changes, be sure to run the Go tests and the shell integration tests: $ make test # runs just the Go tests - $ script/integration # runs the shell tests in ./test + $ cd t && make test # runs the shell tests in ./test $ script/cibuild # runs everything, with verbose debug output ## Updating 3rd party packages diff --git a/README.md b/README.md index 07716375..7810c163 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,9 @@ setup and preferences. [PackageCloud](https://packagecloud.io/github/git-lfs/install). * **macOS users**. [Homebrew](https://brew.sh) bottles are distributed, and can be installed via `brew install git-lfs`. -* **Windows users**. Chocolatey packages are distributed, and can be installed - via `choco install git-lfs`. +* **Windows users**. Git LFS is included in the distribution of + [Git for Windows](https://gitforwindows.org/). Alternatively, you can + install a recent version of Git LFS from the Chocolatey package manager. In addition, [binary packages](https://github.com/git-lfs/git-lfs/releases) are available for Linux, macOS, Windows, and FreeBSD. This repository can also be diff --git a/appveyor.yml b/appveyor.yml index 7e3612a7..ad46437b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,8 +1,12 @@ +image: Visual Studio 2017 + skip_branch_with_pr: true environment: CYGWIN: "$(CYGWIN) winsymlinks:nativestrict" MSYS: "$(MSYS) winsymlinks:nativestrict" + GIT_LFS_NO_TEST_COUNT: 1 + GIT_LFS_LOCK_ACQUIRE_DISABLED: 1 GOPATH: $(HOMEDRIVE)$(HOMEPATH)\go MSYSTEM: MINGW64 @@ -10,8 +14,13 @@ clone_folder: $(GOPATH)\src\github.com\git-lfs\git-lfs install: - rd C:\Go /s /q + - rd C:\Perl /s /q + - refreshenv - cinst golang --version 1.8.3 -y - cinst InnoSetup -y + - cinst strawberryperl -y + - refreshenv + - cinst make - refreshenv - ps: | echo "Go directories in machine PATH environment:" diff --git a/commands/command_migrate.go b/commands/command_migrate.go index 5e8f57d7..2f4f476e 100644 --- a/commands/command_migrate.go +++ b/commands/command_migrate.go @@ -1,7 +1,9 @@ package commands import ( + "bufio" "fmt" + "io" "path/filepath" "strings" @@ -21,6 +23,10 @@ var ( // in the migration. migrateExcludeRefs []string + // migrateYes indicates that an answer of 'yes' should be presumed + // whenever 'git lfs migrate' asks for user input. + migrateYes bool + // migrateSkipFetch assumes that the client has the latest copy of // remote references, and thus should not contact the remote for a set // of updated references. @@ -173,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() @@ -294,6 +318,56 @@ func getHistoryRewriter(cmd *cobra.Command, db *gitobj.ObjectDatabase, l *tasklo githistory.WithFilter(filter), githistory.WithLogger(l)) } +func ensureWorkingCopyClean(in io.Reader, out io.Writer) { + dirty, err := git.IsWorkingCopyDirty() + if err != nil { + ExitWithError(errors.Wrap(err, + "fatal: could not determine if working copy is dirty")) + } + + if !dirty { + return + } + + var proceed bool + if migrateYes { + proceed = true + } else { + answer := bufio.NewReader(in) + L: + for { + fmt.Fprintf(out, "migrate: override changes in your working copy? [Y/n] ") + s, err := answer.ReadString('\n') + if err != nil { + if err == io.EOF { + break L + } + ExitWithError(errors.Wrap(err, + "fatal: could not read answer")) + } + + switch strings.TrimSpace(s) { + case "n", "N": + proceed = false + break L + case "y", "Y": + proceed = true + break L + } + + if !strings.HasSuffix(s, "\n") { + fmt.Fprintf(out, "\n") + } + } + } + + if proceed { + fmt.Fprintf(out, "migrate: changes in your working copy will be overridden ...\n") + } else { + Exit("migrate: working copy must not be dirty") + } +} + func init() { info := NewCommand("info", migrateInfoCommand) info.Flags().IntVar(&migrateInfoTopN, "top", 5, "--top=") @@ -321,6 +395,8 @@ func init() { cmd.PersistentFlags().BoolVar(&migrateEverything, "everything", false, "Migrate all local references") cmd.PersistentFlags().BoolVar(&migrateSkipFetch, "skip-fetch", false, "Assume up-to-date remote references.") + cmd.PersistentFlags().BoolVarP(&migrateYes, "yes", "y", false, "Don't prompt for answers.") + cmd.AddCommand(exportCmd, importCmd, info) }) } diff --git a/commands/command_migrate_export.go b/commands/command_migrate_export.go index ae6b6554..aba64c64 100644 --- a/commands/command_migrate_export.go +++ b/commands/command_migrate_export.go @@ -17,6 +17,8 @@ import ( ) func migrateExportCommand(cmd *cobra.Command, args []string) { + ensureWorkingCopyClean(os.Stdin, os.Stderr) + l := tasklog.NewLogger(os.Stderr) defer l.Close() diff --git a/commands/command_migrate_import.go b/commands/command_migrate_import.go index 8fa53c01..b979ba77 100644 --- a/commands/command_migrate_import.go +++ b/commands/command_migrate_import.go @@ -22,6 +22,8 @@ import ( ) func migrateImportCommand(cmd *cobra.Command, args []string) { + ensureWorkingCopyClean(os.Stdin, os.Stderr) + l := tasklog.NewLogger(os.Stderr) defer l.Close() diff --git a/commands/command_push.go b/commands/command_push.go index 6584fd8f..48de5b98 100644 --- a/commands/command_push.go +++ b/commands/command_push.go @@ -38,7 +38,7 @@ func pushCommand(cmd *cobra.Command, args []string) { requireGitVersion() // Remote is first arg - if err := cfg.SetValidRemote(args[0]); err != nil { + if err := cfg.SetValidPushRemote(args[0]); err != nil { Exit("Invalid remote name %q: %s", args[0], err) } diff --git a/config/config.go b/config/config.go index 005d83f0..0f0c5a21 100644 --- a/config/config.go +++ b/config/config.go @@ -232,10 +232,22 @@ func (c *Configuration) SetValidRemote(name string) error { return nil } +func (c *Configuration) SetValidPushRemote(name string) error { + if err := git.ValidateRemote(name); err != nil { + return err + } + c.SetPushRemote(name) + return nil +} + func (c *Configuration) SetRemote(name string) { c.currentRemote = &name } +func (c *Configuration) SetPushRemote(name string) { + c.pushRemote = &name +} + func (c *Configuration) Remotes() []string { c.loadGitConfig() return c.remotes @@ -311,8 +323,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 +358,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/git/git.go b/git/git.go index 51507ba6..7b7970d0 100644 --- a/git/git.go +++ b/git/git.go @@ -1192,3 +1192,21 @@ func IsFileModified(filepath string) (bool, error) { return matched, nil } + +// IsWorkingCopyDirty returns true if and only if the working copy in which the +// command was executed is dirty as compared to the index. +// +// If the status of the working copy could not be determined, an error will be +// returned instead. +func IsWorkingCopyDirty() (bool, error) { + bare, err := IsBare() + if bare || err != nil { + return false, err + } + + out, err := gitSimple("status", "--porcelain") + if err != nil { + return false, err + } + return len(out) != 0, nil +} diff --git a/git/git_test.go b/git/git_test.go index 6b0228b2..0a94f2b1 100644 --- a/git/git_test.go +++ b/git/git_test.go @@ -9,7 +9,7 @@ import ( "time" . "github.com/git-lfs/git-lfs/git" - "github.com/git-lfs/git-lfs/test" + test "github.com/git-lfs/git-lfs/t/cmd/util" "github.com/stretchr/testify/assert" ) 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/lfs/lfs_test.go b/lfs/lfs_test.go index 156a4479..e3eb9461 100644 --- a/lfs/lfs_test.go +++ b/lfs/lfs_test.go @@ -7,7 +7,7 @@ import ( "github.com/git-lfs/git-lfs/fs" "github.com/git-lfs/git-lfs/lfs" - "github.com/git-lfs/git-lfs/test" + test "github.com/git-lfs/git-lfs/t/cmd/util" "github.com/stretchr/testify/assert" ) diff --git a/lfs/scanner_git_test.go b/lfs/scanner_git_test.go index a5cc1eb3..e70c95a4 100644 --- a/lfs/scanner_git_test.go +++ b/lfs/scanner_git_test.go @@ -11,7 +11,7 @@ import ( "time" . "github.com/git-lfs/git-lfs/lfs" - "github.com/git-lfs/git-lfs/test" + test "github.com/git-lfs/git-lfs/t/cmd/util" "github.com/stretchr/testify/assert" ) 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/lfsapi/proxy_test.go b/lfsapi/proxy_test.go index e8e7e76a..ad98419b 100644 --- a/lfsapi/proxy_test.go +++ b/lfsapi/proxy_test.go @@ -20,7 +20,7 @@ func TestHttpsProxyFromGitConfig(t *testing.T) { require.Nil(t, err) proxyURL, err := proxyFromClient(c)(req) - assert.Equal(t, "proxy-from-env:8080", proxyURL.Host) + assert.Equal(t, "proxy-from-git-config:8080", proxyURL.Host) assert.Nil(t, err) } diff --git a/rpm/SPECS/git-lfs.spec b/rpm/SPECS/git-lfs.spec index 93157b0b..e8224642 100644 --- a/rpm/SPECS/git-lfs.spec +++ b/rpm/SPECS/git-lfs.spec @@ -68,7 +68,7 @@ export SKIPAPITESTCOMPILE=1 pushd src/github.com/git-lfs/%{name} make test go get github.com/ThomsonReutersEikon/go-ntlm/ntlm - ./script/integration + cd t && make test popd rmdir ${GIT_LFS_TEST_DIR} diff --git a/script/cibuild b/script/cibuild index 818b6676..0c48ca24 100755 --- a/script/cibuild +++ b/script/cibuild @@ -1,8 +1,26 @@ #!/usr/bin/env bash set -e +GOCACHE=off script/bootstrap GOCACHE=off make test # re-run test to ensure GIT_TRACE output doesn't leak into the git package GIT_TRACE=1 GOCACHE=off make PKGS=git test -VERBOSE_LOGS=1 script/integration +pushd t >/dev/null + UNAME=$(uname -s) + X="" + if [[ $UNAME == MINGW* || $UNAME == MSYS* || $UNAME == CYGWIN* ]]; then + X=".exe" + fi + + PROVE="prove" + PROVE_EXTRA_ARGS="-j9" + if [ -n "$APPVEYOR" ]; then + export PATH="/c/Strawberry/perl/bin:.:$PATH" + PROVE="prove.bat" + PROVE_EXTRA_ARGS="$PROVE_EXTRA_ARGS --exec bash" + fi + + VERBOSE_LOGS=1 make X="$X" clean + VERBOSE_LOGS=1 make X="$X" PROVE="$PROVE" PROVE_EXTRA_ARGS="$PROVE_EXTRA_ARGS" +popd >/dev/null diff --git a/script/integration b/script/integration deleted file mode 100755 index ab92785d..00000000 --- a/script/integration +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash - -. "test/testenv.sh" -set -e - -SHUTDOWN_LFS=no -SHOW_LOGS=yes - -atexit() { - res=${1:-$?} - SHUTDOWN_LFS=yes - if [ "$res" = "0" ]; then - SHOW_LOGS=no - fi - - if [ "$SHOW_LOGS" = "yes" ] && [ "$VERBOSE_LOGS" = "1" ]; then - if [ -s "$REMOTEDIR/gitserver.log" ]; then - echo "" - echo "gitserver.log:" - cat "$REMOTEDIR/gitserver.log" - fi - - echo "" - echo "env:" - env - fi - - shutdown - exit $res -} - -trap "atexit" EXIT - -if [ -s "$LFS_URL_FILE" ]; then - SHOW_LOGS=no - echo "$LFS_URL_FILE still exists!" - echo "Confirm other tests are done, and run:" - echo " $ curl $(cat "$LFS_URL_FILE")/shutdown" - exit 1 -fi - -setup - -GO15VENDOREXPERIMENT=1 GIT_LFS_TEST_MAXPROCS=$GIT_LFS_TEST_MAXPROCS GIT_LFS_TEST_DIR="$GIT_LFS_TEST_DIR" SHUTDOWN_LFS="no" go run script/*.go -cmd integration "$@" diff --git a/script/integration.go b/script/integration.go deleted file mode 100644 index e67b30a9..00000000 --- a/script/integration.go +++ /dev/null @@ -1,209 +0,0 @@ -package main - -import ( - "bytes" - "errors" - "fmt" - "os" - "os/exec" - "path/filepath" - "regexp" - "runtime" - "strconv" - "strings" - "sync" - "time" -) - -var ( - bashPath string - debugging = false - erroring = false - maxprocs = 4 - testPattern = regexp.MustCompile(`test[/\\]test-([a-z\-]+)\.sh$`) -) - -func mainIntegration() { - if len(os.Getenv("DEBUG")) > 0 { - debugging = true - } - - setBash() - - if max, _ := strconv.Atoi(os.Getenv("GIT_LFS_TEST_MAXPROCS")); max > 0 { - maxprocs = max - } - - fmt.Println("Running this maxprocs", maxprocs) - - files := testFiles() - - if len(files) == 0 { - fmt.Println("no tests to run") - os.Exit(1) - } - - var wg sync.WaitGroup - tests := make(chan string, len(files)) - output := make(chan string, len(files)) - - for _, file := range files { - tests <- file - } - close(tests) - - outputDone := make(chan bool) - go func() { - for out := range output { - fmt.Println(out) - } - outputDone <- true - }() - - for i := 0; i < maxprocs; i++ { - wg.Add(1) - go worker(tests, output, &wg) - } - - wg.Wait() - close(output) - <-outputDone - - if erroring { - os.Exit(1) - } -} - -func runTest(output chan string, testname string) { - buf := &bytes.Buffer{} - cmd := exec.Command(bashPath, testname) - cmd.Stdout = buf - cmd.Stderr = buf - - err := cmd.Start() - if err != nil { - sendTestOutput(output, testname, buf, err) - return - } - - done := make(chan error) - go func() { - if err := cmd.Wait(); err != nil { - done <- err - } - close(done) - }() - - select { - case err = <-done: - sendTestOutput(output, testname, buf, err) - return - case <-time.After(3 * time.Minute): - sendTestOutput(output, testname, buf, errors.New("Timed out")) - cmd.Process.Kill() - return - } -} - -func sendTestOutput(output chan string, testname string, buf *bytes.Buffer, err error) { - cli := strings.TrimSpace(buf.String()) - if len(cli) == 0 { - cli = fmt.Sprintf("", testname) - } - - if err == nil { - output <- cli - } else { - basetestname := filepath.Base(testname) - if debugging { - fmt.Printf("Error on %s: %s\n", basetestname, err) - } - erroring = true - output <- fmt.Sprintf("error: %s => %s\n%s", basetestname, err, cli) - } -} - -func worker(tests <-chan string, output chan string, wg *sync.WaitGroup) { - defer wg.Done() - for { - select { - case testname, ok := <-tests: - if !ok { - return - } - runTest(output, testname) - } - } -} - -func testFiles() []string { - if len(os.Args) < 4 { - return allTestFiles() - } - - fileMap := make(map[string]bool) - for _, file := range allTestFiles() { - fileMap[file] = true - } - - files := make([]string, 0, len(os.Args)-3) - for _, arg := range os.Args { - fullname := "test/test-" + arg + ".sh" - if fileMap[fullname] { - files = append(files, fullname) - } - } - - return files -} - -func allTestFiles() []string { - files := make([]string, 0, 100) - filepath.Walk("test", func(path string, info os.FileInfo, err error) error { - if debugging { - fmt.Println("FOUND:", path) - } - if err != nil || info.IsDir() || !testPattern.MatchString(path) { - return nil - } - - if debugging { - fmt.Println("MATCHING:", path) - } - files = append(files, path) - return nil - }) - return files -} - -func setBash() { - findcmd := "which" - if runtime.GOOS == "windows" { - // Can't use paths returned from which even if it's on PATH in Windows - // Because our Go binary is a separate Windows app & not MinGW, it - // can't understand paths like '/usr/bin/bash', needs Windows version - findcmd = "where" - } - - out, err := exec.Command(findcmd, "bash").Output() - if err != nil { - fmt.Println("Unable to find bash:", err) - os.Exit(1) - } - if len(out) == 0 { - fmt.Printf("No output from '%s bash'\n", findcmd) - os.Exit(1) - } - - bashPath = strings.TrimSpace(strings.Split(string(out), "\n")[0]) - if debugging { - fmt.Println("Using", bashPath) - } - - // Test - _, err = exec.Command(bashPath, "--version").CombinedOutput() - if err != nil { - fmt.Println("Error calling bash:", err) - os.Exit(1) - } -} diff --git a/script/script.go b/script/script.go index 4ed198ce..8dd02def 100644 --- a/script/script.go +++ b/script/script.go @@ -18,8 +18,6 @@ func main() { switch *SubCommand { case "release": mainRelease() - case "integration": - mainIntegration() default: log.Fatalln("Unknown command:", *SubCommand) } diff --git a/t/Makefile b/t/Makefile new file mode 100644 index 00000000..846063df --- /dev/null +++ b/t/Makefile @@ -0,0 +1,40 @@ +RM ?= rm -f +PROVE ?= prove +PROVE_EXTRA_ARGS = +DEFAULT_TEST_TARGET ?= test + +GO ?= go +X = + +TEST_CMDS = + +TEST_CMDS += ../bin/git-credential-lfsnoop$X +TEST_CMDS += ../bin/git-credential-lfstest$X +TEST_CMDS += ../bin/lfs-askpass$X +TEST_CMDS += ../bin/lfs-ssh-echo$X +TEST_CMDS += ../bin/lfs-ssh-proxy-test$X +TEST_CMDS += ../bin/lfstest-count-tests$X +TEST_CMDS += ../bin/lfstest-customadapter$X +TEST_CMDS += ../bin/lfstest-gitserver$X +TEST_CMDS += ../bin/lfstest-standalonecustomadapter$X +TEST_CMDS += ../bin/lfstest-testutils$X + +all : $(DEFAULT_TEST_TARGET) + +test : $(TEST_CMDS) + $(RM) -r remote test_count{,.lock} + @GIT_LFS_NO_TEST_COUNT= bash -c '. ./testenv.sh && setup' + $(PROVE) $(PROVE_EXTRA_ARGS) ./t-*.sh + @GIT_LFS_NO_TEST_COUNT= bash -c '. ./testenv.sh && shutdown' + +./t-%.sh : $(TEST_CMDS) + $(RM) -r remote test_count{,.lock} + $(PROVE) -v $(PROVE_EXTRA_ARGS) $@ + +.PHONY : clean +clean : + $(RM) -r remote + $(RM) $(TEST_CMDS) + +../bin/%$X : cmd/%.go + go build -o $@ $^ diff --git a/t/README.md b/t/README.md new file mode 100644 index 00000000..a433f986 --- /dev/null +++ b/t/README.md @@ -0,0 +1,117 @@ +# `t` + +This directory contains one of the two types of tests that the Git LFS project +uses to protect against regression. The first, scattered in `*_test.go` files +throughout the repository are _unit tests_, and written in Go, designed to +uncover failures at the unit level. + +The second kind--and the one contained in this directory--are _integration +tests_, which are designed to exercise Git LFS in an end-to-end fashion, +running the `git`, and `git-lfs` binaries, along with a mock Git server. + +You can run all tests in this directory with any of the following: + +```ShellSession +$ make +$ make test +$ make PROVE_EXTRA_ARGS=-j9 test +``` + +Or run a single test (for example, `t-checkout.sh`) by any of the following: + +```ShellSession +$ make ./t-checkout.sh +$ make PROVE_EXTRA_ARGS=-v ./t-checkout.sh +$ ./t-checkout.sh +``` + +Alternatively, one can run a selection of tests (via explicitly listing them or +making use of the built-in shell globbing) by any of the following: + +```ShellSession +$ make ./t-*.sh +$ make PROVE_EXTRA_ARGS=-j9 ./t-*.sh +$ ./t-*.sh +``` + +## Test File(s) + +There are a few important kinds of files to know about in the `t` directory: + +- `cmd/`: contains the source code of binaries that are useful during test + time, like the mocked Git server, or the test counting binary. For more about + the contents of this directory, see [test lifecycle](#test-lifecycle) below. + + The file `t/cmd/testutils.go` is automatically linked and included during the + build process of each file in `cmd`. + +- `fixtures/`: contains shell scripts that load fixture repositories useful for + testing against. + +- `t-*.sh`: file(s) containing zero or more tests, typically related to + a similar topic (c.f,. `t/t-push.sh`, `t/t-pull.sh`, etc.) + +- `testenv.sh`: loads environment variables useful during tests. This file is + sourced by `testlib.sh`. + +- `testhelpers.sh`: loads shell functions useful during tests, like + `setup_remote_repo`, and `clone_repo`. + +- `testlib.sh`: loads the `begin_test`, `end_test`, and similar functions + useful for instantiating a particular test. + +## Test Lifecycle + +When a test is run, the following occurs, in order: + +1. Missing test binaries are compiled into the `bin` directory in the + repository root. Note: this does _not_ include the `git-lfs` binary, which + is re-compiled via `script/boostrap`. + +2. An integration server is started by either (1) the `Makefile` or (2) the + `cmd/lfstest-count-test.go` program, which keeps track of the number of + running tests and starts an integration server any time the number of active + tests goes from `0` to `1`, and stops the server when it goes from `n` to + `0`. + +3. After sourcing `t/testlib.sh` (& loading `t/testenv.sh`), each test is run + in sequence per file. (In other words, multiple test files can be run in + parallel, but the tests in a single file are run in sequence.) + +4. An individual test will finish, and (if running under `prove`) another will + be started in its place. Once all tests are done, `t/test_count` will go to + `0`, and the test server will be torn down. + +## Test Environment + +There are a few environment variables that you can set to change the test suite +behavior: + +* `GIT_LFS_TEST_DIR=path` - This sets the directory that is used as the current +working directory of the tests. By default, this will be in your temp dir. It's +recommended that this is set to a directory outside of any Git repository. + +* `KEEPTRASH=1` - This will leave the local repository data in a `tmp` directory +and the remote repository data in `test/remote`. + +Also ensure that your `noproxy` environment variable contains `127.0.0.1` host, +to allow git commands to reach the local Git server `lfstest-gitserver`. + +## Writing new tests + +A new test file should be named `t/t-*.sh`, where `*` is the topic of Git LFS +being tested. It should look as follows: + +```bash +#!/usr/bin/env bash + +. "$(dirname "$0")/testlib.sh" + +begin_test "my test" +( + set -e + + # ... +) +end_test +``` diff --git a/test/cmd/git-credential-lfsnoop.go b/t/cmd/git-credential-lfsnoop.go similarity index 100% rename from test/cmd/git-credential-lfsnoop.go rename to t/cmd/git-credential-lfsnoop.go diff --git a/test/cmd/git-credential-lfstest.go b/t/cmd/git-credential-lfstest.go similarity index 100% rename from test/cmd/git-credential-lfstest.go rename to t/cmd/git-credential-lfstest.go diff --git a/test/cmd/lfs-askpass.go b/t/cmd/lfs-askpass.go similarity index 100% rename from test/cmd/lfs-askpass.go rename to t/cmd/lfs-askpass.go diff --git a/test/cmd/lfs-ssh-echo.go b/t/cmd/lfs-ssh-echo.go similarity index 100% rename from test/cmd/lfs-ssh-echo.go rename to t/cmd/lfs-ssh-echo.go diff --git a/test/cmd/lfs-ssh-proxy-test.go b/t/cmd/lfs-ssh-proxy-test.go similarity index 100% rename from test/cmd/lfs-ssh-proxy-test.go rename to t/cmd/lfs-ssh-proxy-test.go diff --git a/t/cmd/lfstest-count-tests.go b/t/cmd/lfstest-count-tests.go new file mode 100644 index 00000000..75874bae --- /dev/null +++ b/t/cmd/lfstest-count-tests.go @@ -0,0 +1,296 @@ +package main + +import ( + "context" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "strings" + "time" +) + +var ( + // countFile is the path to a file (relative to the $LFSTEST_DIR) who's + // contents is the number of actively-running integration tests. + countFile = "test_count" + // lockFile is the path to a file (relative to the $LFSTEST_DIR) who's + // presence indicates that another invocation of the lfstest-count-tests + // program is modifying the test_count. + lockFile = "test_count.lock" + + // lockAcquireTimeout is the maximum amount of time that we will wait + // for lockFile to become available (and thus the amount of time that we + // will wait in order to acquire the lock). + lockAcquireTimeout = 5 * time.Second + + // errCouldNotAcquire indicates that the program could not acquire the + // lock needed to modify the test_count. It is a fatal error. + errCouldNotAcquire = fmt.Errorf("could not acquire lock, dying") + // errNegativeCount indicates that the count in test_count was negative, + // which is unexpected and makes this script behave in an undefined + // fashion + errNegativeCount = fmt.Errorf("unexpected negative count") +) + +// countFn is a type signature that all functions who wish to modify the +// test_count must inhabit. +// +// The first and only formal parameter is the current number of running tests +// found in test_count after acquiring the lock. +// +// The returned tuple indicates (1) the new number that should be written to +// test_count, and (2) if there was an error in computing that value. If err is +// non-nil, the program will exit and test_count will not be updated. +type countFn func(int) (int, error) + +func main() { + if len(os.Args) > 2 { + fmt.Fprintf(os.Stderr, + "usage: %s [increment|decrement]\n", os.Args[0]) + os.Exit(1) + } + + ctx, cancel := context.WithTimeout( + context.Background(), lockAcquireTimeout) + defer cancel() + + if err := acquire(ctx); err != nil { + fatal(err) + } + defer release() + + if len(os.Args) == 1 { + // Calling with no arguments indicates that we simply want to + // read the contents of test_count. + callWithCount(func(n int) (int, error) { + fmt.Fprintf(os.Stdout, "%d\n", n) + return n, nil + }) + return + } + + var err error + + switch strings.ToLower(os.Args[1]) { + case "increment": + err = callWithCount(func(n int) (int, error) { + if n > 0 { + // If n>1, it is therefore true that a + // lfstest-gitserver invocation is already + // running. + // + // Hence, let's do nothing here other than + // increase the count. + return n + 1, nil + } + + // The lfstest-gitserver invocation (see: below) does + // not itself create a gitserver.log in the appropriate + // directory. Thus, let's create it ourselves instead. + log, err := os.Create(fmt.Sprintf( + "%s/gitserver.log", os.Getenv("LFSTEST_DIR"))) + if err != nil { + return n, err + } + + // The executable name depends on the X environment + // variable, which is set in script/cibuild. + var cmd *exec.Cmd + if runtime.GOOS == "windows" { + cmd = exec.Command("lfstest-gitserver.exe") + } else { + cmd = exec.Command("lfstest-gitserver") + } + + // The following are ported from the old + // test/testhelpers.sh, and comprise the requisite + // environment variables needed to run + // lfstest-gitserver. + cmd.Env = append(os.Environ(), + fmt.Sprintf("LFSTEST_URL=%s", os.Getenv("LFSTEST_URL")), + fmt.Sprintf("LFSTEST_SSL_URL=%s", os.Getenv("LFSTEST_SSL_URL")), + fmt.Sprintf("LFSTEST_CLIENT_CERT_URL=%s", os.Getenv("LFSTEST_CLIENT_CERT_URL")), + fmt.Sprintf("LFSTEST_DIR=%s", os.Getenv("LFSTEST_DIR")), + fmt.Sprintf("LFSTEST_CERT=%s", os.Getenv("LFSTEST_CERT")), + fmt.Sprintf("LFSTEST_CLIENT_CERT=%s", os.Getenv("LFSTEST_CLIENT_CERT")), + fmt.Sprintf("LFSTEST_CLIENT_KEY=%s", os.Getenv("LFSTEST_CLIENT_KEY")), + ) + cmd.Stdout = log + + // Start performs a fork/execve, hence we can abandon + // this process once it has started. + if err := cmd.Start(); err != nil { + return n, err + } + return 1, nil + }) + case "decrement": + err = callWithCount(func(n int) (int, error) { + if n > 1 { + // If there is at least two tests running, we + // need not shutdown a lfstest-gitserver + // instance. + return n - 1, nil + } + + // Otherwise, we need to POST to /shutdown, which will + // cause the lfstest-gitserver to abort itself. + url, err := ioutil.ReadFile(os.Getenv("LFS_URL_FILE")) + if err == nil { + _, err = http.Post(string(url)+"/shutdown", + "application/text", + strings.NewReader(time.Now().String())) + } + + return 0, nil + }) + } + + if err != nil { + fatal(err) + } +} + +var ( + // acquireTick is the constant time that one tick (i.e., one attempt at + // acquiring the lock) should last. + acquireTick = 10 * time.Millisecond +) + +// acquire acquires the lock file necessary to perform updates to test_count, +// and returns an error if that lock cannot be acquired. +func acquire(ctx context.Context) error { + if disabled() { + return nil + } + + path, err := path(lockFile) + if err != nil { + return err + } + + tick := time.NewTicker(acquireTick) + defer tick.Stop() + + for { + select { + case <-tick.C: + // Try every tick of the above ticker before giving up + // and trying again. + _, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL, 0666) + if err == nil || !os.IsExist(err) { + return err + } + case <-ctx.Done(): + // If the context.Context above has reached its + // deadline, we must give up. + return errCouldNotAcquire + } + } +} + +// release releases the lock file so that another process can take over, or +// returns an error. +func release() error { + if disabled() { + return nil + } + + path, err := path(lockFile) + if err != nil { + return err + } + return os.Remove(path) +} + +// callWithCount calls the given countFn with the current count in test_count, +// and updates it with what the function returns. +// +// If the function produced an error, that will be returned instead. +func callWithCount(fn countFn) error { + path, err := path(countFile) + if err != nil { + return err + } + + f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0666) + if err != nil { + return err + } + + contents, err := ioutil.ReadAll(f) + if err != nil { + return err + } + + var n int = 0 + if len(contents) != 0 { + n, err = strconv.Atoi(string(contents)) + if err != nil { + return err + } + + if n < 0 { + return errNegativeCount + } + } + + after, err := fn(n) + if err != nil { + return err + } + + // We want to write over the contents in the file, so "truncate" the + // file to a length of 0, and then seek to the beginning of the file to + // update the write head. + if err := f.Truncate(0); err != nil { + return err + } + if _, err := f.Seek(0, io.SeekStart); err != nil { + return err + } + + if _, err := fmt.Fprintf(f, "%d", after); err != nil { + return err + } + return nil +} + +// path returns an absolute path corresponding to any given path relative to the +// 't' directory of the current checkout of Git LFS. +func path(s string) (string, error) { + p := filepath.Join(filepath.Dir(os.Getenv("LFSTEST_DIR")), s) + if err := os.MkdirAll(filepath.Dir(p), 0666); err != nil { + return "", err + } + return p, nil +} + +// disabled returns true if and only if the lock acquisition phase is disabled. +func disabled() bool { + s := os.Getenv("GIT_LFS_LOCK_ACQUIRE_DISABLED") + b, err := strconv.ParseBool(s) + if err != nil { + return false + } + return b +} + +// fatal reports the given error (if non-nil), and then dies. If the error was +// nil, nothing happens. +func fatal(err error) { + if err == nil { + return + } + if err := release(); err != nil { + fmt.Fprintf(os.Stderr, "fatal: while dying, got: %s\n", err) + } + fmt.Fprintf(os.Stderr, "fatal: %s\n", err) + os.Exit(1) +} diff --git a/test/cmd/lfstest-customadapter.go b/t/cmd/lfstest-customadapter.go similarity index 100% rename from test/cmd/lfstest-customadapter.go rename to t/cmd/lfstest-customadapter.go diff --git a/test/cmd/lfstest-gitserver.go b/t/cmd/lfstest-gitserver.go similarity index 99% rename from test/cmd/lfstest-gitserver.go rename to t/cmd/lfstest-gitserver.go index 97732995..7af7f160 100644 --- a/test/cmd/lfstest-gitserver.go +++ b/t/cmd/lfstest-gitserver.go @@ -101,6 +101,9 @@ func main() { mux.HandleFunc("/storage/", storageHandler) mux.HandleFunc("/verify", verifyHandler) mux.HandleFunc("/redirect307/", redirect307Handler) + mux.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "%s\n", time.Now().String()) + }) mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { id, ok := reqId(w) if !ok { diff --git a/test/cmd/lfstest-standalonecustomadapter.go b/t/cmd/lfstest-standalonecustomadapter.go similarity index 100% rename from test/cmd/lfstest-standalonecustomadapter.go rename to t/cmd/lfstest-standalonecustomadapter.go diff --git a/test/cmd/lfstest-testutils.go b/t/cmd/lfstest-testutils.go similarity index 88% rename from test/cmd/lfstest-testutils.go rename to t/cmd/lfstest-testutils.go index beb5c92b..370aa507 100644 --- a/test/cmd/lfstest-testutils.go +++ b/t/cmd/lfstest-testutils.go @@ -9,7 +9,7 @@ import ( "os" "path/filepath" - "github.com/git-lfs/git-lfs/test" + . "github.com/git-lfs/git-lfs/t/cmd/util" ) type TestUtilRepoCallback struct{} @@ -23,7 +23,7 @@ func (*TestUtilRepoCallback) Errorf(format string, args ...interface{}) { } func main() { - commandMap := map[string]func(*test.Repo){ + commandMap := map[string]func(*Repo){ "addcommits": AddCommits, } if len(os.Args) < 2 { @@ -51,18 +51,18 @@ func main() { os.Exit(2) } - repo := test.WrapRepo(&TestUtilRepoCallback{}, wd) + repo := WrapRepo(&TestUtilRepoCallback{}, wd) f(repo) } -func AddCommits(repo *test.Repo) { - // Read stdin as JSON []*test.CommitInput +func AddCommits(repo *Repo) { + // Read stdin as JSON []*CommitInput in, err := ioutil.ReadAll(os.Stdin) if err != nil { fmt.Fprintf(os.Stderr, "addcommits: Unable to read input data: %v\n", err) os.Exit(3) } - inputs := make([]*test.CommitInput, 0) + inputs := make([]*CommitInput, 0) err = json.Unmarshal(in, &inputs) if err != nil { fmt.Fprintf(os.Stderr, "addcommits: Unable to unmarshal JSON: %v\n%v\n", string(in), err) diff --git a/test/testutils.go b/t/cmd/util/testutils.go similarity index 99% rename from test/testutils.go rename to t/cmd/util/testutils.go index c01446db..454af139 100644 --- a/test/testutils.go +++ b/t/cmd/util/testutils.go @@ -1,4 +1,4 @@ -package test +package util // Utility functions for more complex go tests // Need to be in a separate test package so they can be imported anywhere diff --git a/test/test-migrate-fixtures.sh b/t/fixtures/migrate.sh similarity index 92% rename from test/test-migrate-fixtures.sh rename to t/fixtures/migrate.sh index e628f0af..bdcd5dce 100755 --- a/test/test-migrate-fixtures.sh +++ b/t/fixtures/migrate.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 @@ -553,6 +576,28 @@ setup_local_branch_with_symlink() { git commit -m "add symlink" } +# setup_local_branch_with_dirty_copy creates a repository as follows: +# +# A +# \ +# refs/heads/master +# +# - Commit 'A' has the contents "a.txt in a.txt, and marks a.txt as unclean +# in the working copy. +setup_local_branch_with_dirty_copy() { + set -e + + reponame="migrate-single-local-branch-with-dirty-copy" + remove_and_create_local_repo "$reponame" + + printf "a.txt" > a.txt + + git add a.txt + git commit -m "initial commit" + + printf "2" >> a.txt +} + # make_bare converts the existing full checkout of a repository into a bare one, # and then `cd`'s into it. make_bare() { @@ -587,4 +632,6 @@ remove_and_create_remote_repo() { setup_remote_repo "$reponame" clone_repo "$reponame" "$reponame" + + rm clone.log } diff --git a/t/git-lfs-test-server-api/.gitignore b/t/git-lfs-test-server-api/.gitignore new file mode 100644 index 00000000..ad2a7476 --- /dev/null +++ b/t/git-lfs-test-server-api/.gitignore @@ -0,0 +1 @@ +git-lfs-test-server-api* diff --git a/test/git-lfs-test-server-api/README.md b/t/git-lfs-test-server-api/README.md similarity index 100% rename from test/git-lfs-test-server-api/README.md rename to t/git-lfs-test-server-api/README.md diff --git a/test/git-lfs-test-server-api/main.go b/t/git-lfs-test-server-api/main.go similarity index 94% rename from test/git-lfs-test-server-api/main.go rename to t/git-lfs-test-server-api/main.go index 295536f6..3635df76 100644 --- a/test/git-lfs-test-server-api/main.go +++ b/t/git-lfs-test-server-api/main.go @@ -13,8 +13,8 @@ import ( "github.com/git-lfs/git-lfs/errors" "github.com/git-lfs/git-lfs/fs" "github.com/git-lfs/git-lfs/lfsapi" + t "github.com/git-lfs/git-lfs/t/cmd/util" "github.com/git-lfs/git-lfs/tasklog" - "github.com/git-lfs/git-lfs/test" "github.com/git-lfs/git-lfs/tq" "github.com/spf13/cobra" ) @@ -64,7 +64,7 @@ func testServerApi(cmd *cobra.Command, args []string) { // Use test repo for this to simplify the process of making sure data matches oid // We're not performing a real test at this point (although an upload fail will break it) var callback testDataCallback - repo := test.NewRepo(&callback) + repo := t.NewRepo(&callback) // Force loading of config before we alter it repo.GitEnv().All() @@ -137,7 +137,7 @@ func (*testDataCallback) Errorf(format string, args ...interface{}) { fmt.Printf(format, args...) } -func buildManifest(r *test.Repo) (*tq.Manifest, error) { +func buildManifest(r *t.Repo) (*tq.Manifest, error) { // Configure the endpoint manually finder := lfsapi.NewEndpointFinder(r) @@ -173,7 +173,7 @@ func (c *constantEndpoint) Endpoint(operation, remote string) lfsapi.Endpoint { func (c *constantEndpoint) RemoteEndpoint(operation, remote string) lfsapi.Endpoint { return c.e } -func buildTestData(repo *test.Repo, manifest *tq.Manifest) (oidsExist, oidsMissing []TestObject, err error) { +func buildTestData(repo *t.Repo, manifest *tq.Manifest) (oidsExist, oidsMissing []TestObject, err error) { const oidCount = 50 oidsExist = make([]TestObject, 0, oidCount) oidsMissing = make([]TestObject, 0, oidCount) @@ -183,14 +183,14 @@ func buildTestData(repo *test.Repo, manifest *tq.Manifest) (oidsExist, oidsMissi meter := tq.NewMeter() meter.Logger = meter.LoggerFromEnv(repo.OSEnv()) logger.Enqueue(meter) - commit := test.CommitInput{CommitterName: "A N Other", CommitterEmail: "noone@somewhere.com"} + commit := t.CommitInput{CommitterName: "A N Other", CommitterEmail: "noone@somewhere.com"} for i := 0; i < oidCount; i++ { filename := fmt.Sprintf("file%d.dat", i) sz := int64(rand.Intn(200)) + 50 - commit.Files = append(commit.Files, &test.FileInput{Filename: filename, Size: sz}) + commit.Files = append(commit.Files, &t.FileInput{Filename: filename, Size: sz}) meter.Add(sz) } - outputs := repo.AddCommits([]*test.CommitInput{&commit}) + outputs := repo.AddCommits([]*t.CommitInput{&commit}) // now upload uploadQueue := tq.NewTransferQueue(tq.Upload, manifest, "origin", tq.WithProgress(meter)) diff --git a/test/git-lfs-test-server-api/testdownload.go b/t/git-lfs-test-server-api/testdownload.go similarity index 100% rename from test/git-lfs-test-server-api/testdownload.go rename to t/git-lfs-test-server-api/testdownload.go diff --git a/test/git-lfs-test-server-api/testupload.go b/t/git-lfs-test-server-api/testupload.go similarity index 100% rename from test/git-lfs-test-server-api/testupload.go rename to t/git-lfs-test-server-api/testupload.go diff --git a/test/test-askpass.sh b/t/t-askpass.sh similarity index 78% rename from test/test-askpass.sh rename to t/t-askpass.sh index 29d3d52d..ef5f6c6f 100755 --- a/test/test-askpass.sh +++ b/t/t-askpass.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "askpass: push with GIT_ASKPASS" ( @@ -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-batch-error-handling.sh b/t/t-batch-error-handling.sh similarity index 98% rename from test/test-batch-error-handling.sh rename to t/t-batch-error-handling.sh index cb087e11..b1192ac7 100755 --- a/test/test-batch-error-handling.sh +++ b/t/t-batch-error-handling.sh @@ -2,7 +2,7 @@ # This is a sample Git LFS test. See test/README.md and testhelpers.sh for # more documentation. -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "batch error handling" ( diff --git a/test/test-batch-retries.sh b/t/t-batch-retries.sh similarity index 98% rename from test/test-batch-retries.sh rename to t/t-batch-retries.sh index 13735dc9..f10b590b 100755 --- a/test/test-batch-retries.sh +++ b/t/t-batch-retries.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "batch storage upload causes retries" ( diff --git a/test/test-batch-transfer.sh b/t/t-batch-transfer.sh similarity index 99% rename from test/test-batch-transfer.sh rename to t/t-batch-transfer.sh index 1c7b779c..2a5ba7ba 100755 --- a/test/test-batch-transfer.sh +++ b/t/t-batch-transfer.sh @@ -2,7 +2,7 @@ # This is a sample Git LFS test. See test/README.md and testhelpers.sh for # more documentation. -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "batch transfer" ( diff --git a/test/test-batch-unknown-oids.sh b/t/t-batch-unknown-oids.sh similarity index 95% rename from test/test-batch-unknown-oids.sh rename to t/t-batch-unknown-oids.sh index b96721b9..a2eb5c84 100755 --- a/test/test-batch-unknown-oids.sh +++ b/t/t-batch-unknown-oids.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "transfer queue rejects unknown OIDs" ( diff --git a/test/test-checkout.sh b/t/t-checkout.sh similarity index 88% rename from test/test-checkout.sh rename to t/t-checkout.sh index cd2633c8..9271f3f8 100755 --- a/test/test-checkout.sh +++ b/t/t-checkout.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "checkout" ( @@ -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-chunked-transfer-encoding.sh b/t/t-chunked-transfer-encoding.sh similarity index 98% rename from test/test-chunked-transfer-encoding.sh rename to t/t-chunked-transfer-encoding.sh index a545ca39..8ddc50e6 100755 --- a/test/test-chunked-transfer-encoding.sh +++ b/t/t-chunked-transfer-encoding.sh @@ -2,7 +2,7 @@ # This is a sample Git LFS test. See test/README.md and testhelpers.sh for # more documentation. -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "chunked transfer encoding" ( diff --git a/test/test-clean.sh b/t/t-clean.sh similarity index 99% rename from test/test-clean.sh rename to t/t-clean.sh index 3b21c071..806778e6 100755 --- a/test/test-clean.sh +++ b/t/t-clean.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" clean_setup () { mkdir "$1" diff --git a/test/test-clone-deprecated.sh b/t/t-clone-deprecated.sh similarity index 93% rename from test/test-clone-deprecated.sh rename to t/t-clone-deprecated.sh index 7686093e..f75c2576 100755 --- a/test/test-clone-deprecated.sh +++ b/t/t-clone-deprecated.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" ensure_git_version_isnt $VERSION_LOWER "2.15.0" diff --git a/test/test-clone.sh b/t/t-clone.sh similarity index 99% rename from test/test-clone.sh rename to t/t-clone.sh index ec51cf35..7fcb7180 100755 --- a/test/test-clone.sh +++ b/t/t-clone.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" ensure_git_version_isnt $VERSION_LOWER "2.2.0" diff --git a/test/test-commit-delete-push.sh b/t/t-commit-delete-push.sh similarity index 97% rename from test/test-commit-delete-push.sh rename to t/t-commit-delete-push.sh index aac5c497..057f6072 100755 --- a/test/test-commit-delete-push.sh +++ b/t/t-commit-delete-push.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "commit, delete, then push" ( diff --git a/test/test-config.sh b/t/t-config.sh similarity index 99% rename from test/test-config.sh rename to t/t-config.sh index 7127a81e..b9c43567 100755 --- a/test/test-config.sh +++ b/t/t-config.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "default config" ( diff --git a/test/test-credentials-no-prompt.sh b/t/t-credentials-no-prompt.sh similarity index 97% rename from test/test-credentials-no-prompt.sh rename to t/t-credentials-no-prompt.sh index a9fd287c..31e6f9ed 100755 --- a/test/test-credentials-no-prompt.sh +++ b/t/t-credentials-no-prompt.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" # these tests rely on GIT_TERMINAL_PROMPT to test properly ensure_git_version_isnt $VERSION_LOWER "2.3.0" diff --git a/test/test-credentials.sh b/t/t-credentials.sh similarity index 99% rename from test/test-credentials.sh rename to t/t-credentials.sh index 845d484b..b0809993 100755 --- a/test/test-credentials.sh +++ b/t/t-credentials.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" ensure_git_version_isnt $VERSION_LOWER "2.3.0" @@ -164,7 +164,7 @@ begin_test "credentials with useHttpPath, with correct password" credcalls="$(grep "creds: git credential" push.log)" [ "0" -eq "$(echo "$credcalls" | grep '", "")' | wc -l)" ] expected="$(echo "$credcalls" | wc -l)" - [ "$expected" -eq "$(printf "$credcalls" | grep "test-credentials" | wc -l)" ] + [ "$expected" -eq "$(printf "$credcalls" | grep "t-credentials" | wc -l)" ] ) end_test diff --git a/test/test-custom-transfers.sh b/t/t-custom-transfers.sh similarity index 99% rename from test/test-custom-transfers.sh rename to t/t-custom-transfers.sh index b8898bf9..27539669 100755 --- a/test/test-custom-transfers.sh +++ b/t/t-custom-transfers.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "custom-transfer-wrong-path" ( diff --git a/test/test-duplicate-oids.sh b/t/t-duplicate-oids.sh similarity index 97% rename from test/test-duplicate-oids.sh rename to t/t-duplicate-oids.sh index 9b9c6fb5..9508e8a3 100755 --- a/test/test-duplicate-oids.sh +++ b/t/t-duplicate-oids.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "multiple revs with same OID get pushed once" ( diff --git a/test/test-env.sh b/t/t-env.sh similarity index 98% rename from test/test-env.sh rename to t/t-env.sh index 2a3047e2..9c72fab8 100755 --- a/test/test-env.sh +++ b/t/t-env.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" envInitConfig='git config filter.lfs.process = "git-lfs filter-process" git config filter.lfs.smudge = "git-lfs smudge -- %f" @@ -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-expired.sh b/t/t-expired.sh similarity index 97% rename from test/test-expired.sh rename to t/t-expired.sh index 5639442c..2e2647c9 100755 --- a/test/test-expired.sh +++ b/t/t-expired.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" declare -a expiration_types=("absolute" "relative" "both") diff --git a/test/test-ext.sh b/t/t-ext.sh similarity index 97% rename from test/test-ext.sh rename to t/t-ext.sh index 9cae8d8f..4fb3e8ba 100755 --- a/test/test-ext.sh +++ b/t/t-ext.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "ext" ( diff --git a/test/test-extra-header.sh b/t/t-extra-header.sh similarity index 98% rename from test/test-extra-header.sh rename to t/t-extra-header.sh index b3a5f919..995d22be 100755 --- a/test/test-extra-header.sh +++ b/t/t-extra-header.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "http..extraHeader" ( diff --git a/test/test-fetch-include.sh b/t/t-fetch-include.sh similarity index 98% rename from test/test-fetch-include.sh rename to t/t-fetch-include.sh index a5f183a5..85fbe9ce 100755 --- a/test/test-fetch-include.sh +++ b/t/t-fetch-include.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" reponame="$(basename "$0" ".sh")" contents="big file" diff --git a/test/test-fetch-paths.sh b/t/t-fetch-paths.sh similarity index 98% rename from test/test-fetch-paths.sh rename to t/t-fetch-paths.sh index a4f63dc0..6d4a1f74 100755 --- a/test/test-fetch-paths.sh +++ b/t/t-fetch-paths.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" reponame="$(basename "$0" ".sh")" contents="a" diff --git a/test/test-fetch-recent.sh b/t/t-fetch-recent.sh similarity index 99% rename from test/test-fetch-recent.sh rename to t/t-fetch-recent.sh index 5ade93c7..391a643b 100755 --- a/test/test-fetch-recent.sh +++ b/t/t-fetch-recent.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" reponame="fetch-recent" diff --git a/test/test-fetch-refspec.sh b/t/t-fetch-refspec.sh similarity index 98% rename from test/test-fetch-refspec.sh rename to t/t-fetch-refspec.sh index 38529a12..3e94f02d 100755 --- a/test/test-fetch-refspec.sh +++ b/t/t-fetch-refspec.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "fetch with good ref" ( diff --git a/test/test-fetch.sh b/t/t-fetch.sh similarity index 99% rename from test/test-fetch.sh rename to t/t-fetch.sh index bc3410bc..69403174 100755 --- a/test/test-fetch.sh +++ b/t/t-fetch.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" contents="a" contents_oid=$(calc_oid "$contents") @@ -526,14 +526,14 @@ begin_test "fetch raw remote url" git init git lfs install --local --skip-smudge - git remote add origin $GITSERVER/test-fetch + git remote add origin "$GITSERVER/$reponame" git pull origin master # LFS object not downloaded, pointer in working directory refute_local_object "$contents_oid" grep "$content_oid" a.dat - git lfs fetch "$GITSERVER/test-fetch" + git lfs fetch "$GITSERVER/$reponame" # LFS object downloaded, pointer still in working directory assert_local_object "$contents_oid" 1 diff --git a/test/test-filter-branch.sh b/t/t-filter-branch.sh similarity index 96% rename from test/test-filter-branch.sh rename to t/t-filter-branch.sh index 6377812a..9a312481 100755 --- a/test/test-filter-branch.sh +++ b/t/t-filter-branch.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "filter-branch (git-lfs/git-lfs#1773)" ( diff --git a/test/test-filter-process.sh b/t/t-filter-process.sh similarity index 98% rename from test/test-filter-process.sh rename to t/t-filter-process.sh index 32b83860..21ef709f 100755 --- a/test/test-filter-process.sh +++ b/t/t-filter-process.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" # HACK(taylor): git uses ".g" in the version name to signal that it is # from the "next" branch, which is the only (current) version of Git that has diff --git a/test/test-fsck.sh b/t/t-fsck.sh similarity index 98% rename from test/test-fsck.sh rename to t/t-fsck.sh index 6d6ccfa8..170492eb 100755 --- a/test/test-fsck.sh +++ b/t/t-fsck.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "fsck default" ( diff --git a/test/test-happy-path.sh b/t/t-happy-path.sh similarity index 99% rename from test/test-happy-path.sh rename to t/t-happy-path.sh index d3aea38f..38c57819 100755 --- a/test/test-happy-path.sh +++ b/t/t-happy-path.sh @@ -2,7 +2,7 @@ # This is a sample Git LFS test. See test/README.md and testhelpers.sh for # more documentation. -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "happy path" ( diff --git a/test/test-install-custom-hooks-path-unsupported.sh b/t/t-install-custom-hooks-path-unsupported.sh similarity index 95% rename from test/test-install-custom-hooks-path-unsupported.sh rename to t/t-install-custom-hooks-path-unsupported.sh index e5c624eb..7cf9f2f0 100755 --- a/test/test-install-custom-hooks-path-unsupported.sh +++ b/t/t-install-custom-hooks-path-unsupported.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" # These tests rely on behavior found in Git versions less than 2.9.0 to perform # themselves, specifically: diff --git a/test/test-install-custom-hooks-path.sh b/t/t-install-custom-hooks-path.sh similarity index 96% rename from test/test-install-custom-hooks-path.sh rename to t/t-install-custom-hooks-path.sh index 26b9d168..4a403ca9 100755 --- a/test/test-install-custom-hooks-path.sh +++ b/t/t-install-custom-hooks-path.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" # These tests rely on behavior found in 2.9.0 to perform themselves, # specifically: diff --git a/test/test-install.sh b/t/t-install.sh similarity index 99% rename from test/test-install.sh rename to t/t-install.sh index abc8f641..05753b31 100755 --- a/test/test-install.sh +++ b/t/t-install.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "install again" ( diff --git a/test/test-lock.sh b/t/t-lock.sh similarity index 99% rename from test/test-lock.sh rename to t/t-lock.sh index bd82b4f0..9d3b8f82 100755 --- a/test/test-lock.sh +++ b/t/t-lock.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "lock with good ref" ( diff --git a/test/test-locks.sh b/t/t-locks.sh similarity index 99% rename from test/test-locks.sh rename to t/t-locks.sh index e167750e..d822ff00 100755 --- a/test/test-locks.sh +++ b/t/t-locks.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "list a single lock with bad ref" ( diff --git a/test/test-logs.sh b/t/t-logs.sh similarity index 92% rename from test/test-logs.sh rename to t/t-logs.sh index a4176da0..8ebb1940 100755 --- a/test/test-logs.sh +++ b/t/t-logs.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "logs" ( diff --git a/test/test-ls-files.sh b/t/t-ls-files.sh similarity index 97% rename from test/test-ls-files.sh rename to t/t-ls-files.sh index 2e4490f4..947bbd3b 100755 --- a/test/test-ls-files.sh +++ b/t/t-ls-files.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "ls-files" ( @@ -334,17 +334,12 @@ begin_test "ls-files: list/stat files with escaped runes in path before commit" ( set -e - if [ -n "$CIRCLECI" ]; then - echo >&2 "info: skipping due to known failure on CircleCI" - exit 0 - fi - reponame=runes-in-path content="zero" checksum="d3eb539a55" - pathWithGermanRunes="german/äöü" - fileWithGermanRunes="schüüch.bin" - + pathWithGermanRunes="german/äöü" + fileWithGermanRunes="schüüch.bin" + mkdir $reponame git init "$reponame" cd $reponame diff --git a/test/test-malformed-pointers.sh b/t/t-malformed-pointers.sh similarity index 98% rename from test/test-malformed-pointers.sh rename to t/t-malformed-pointers.sh index e2f7b261..a384e3b9 100755 --- a/test/test-malformed-pointers.sh +++ b/t/t-malformed-pointers.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "malformed pointers" ( diff --git a/test/test-mergetool.sh b/t/t-mergetool.sh similarity index 96% rename from test/test-mergetool.sh rename to t/t-mergetool.sh index baac2e81..3fd77654 100755 --- a/test/test-mergetool.sh +++ b/t/t-mergetool.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "mergetool works with large files" ( diff --git a/test/test-migrate-export.sh b/t/t-migrate-export.sh similarity index 98% rename from test/test-migrate-export.sh rename to t/t-migrate-export.sh index 37431bbf..fc114d39 100755 --- a/test/test-migrate-export.sh +++ b/t/t-migrate-export.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -. "test/test-migrate-fixtures.sh" -. "test/testlib.sh" +. "$(dirname "$0")/fixtures/migrate.sh" +. "$(dirname "$0")/testlib.sh" begin_test "migrate export (default branch)" ( @@ -132,7 +132,7 @@ begin_test "migrate export (bare repository)" md_oid="$(calc_oid "$(cat a.md)")" txt_oid="$(calc_oid "$(cat a.txt)")" - + make_bare assert_pointer "refs/heads/master" "a.txt" "$txt_oid" "30" @@ -198,7 +198,7 @@ begin_test "migrate export (no filter)" setup_multiple_local_branches_tracked - git lfs migrate export 2>&1 | tee migrate.log + git lfs migrate export --yes 2>&1 | tee migrate.log if [ ${PIPESTATUS[0]} -eq 0 ]; then echo >&2 "fatal: expected git lfs migrate export to fail, didn't" exit 1 @@ -330,7 +330,7 @@ begin_test "migrate export (include/exclude ref)" --include="*.txt" \ --include-ref=refs/heads/my-feature \ --exclude-ref=refs/heads/master - + assert_pointer "refs/heads/master" "a.md" "$md_master_oid" "21" assert_pointer "refs/heads/master" "a.txt" "$txt_master_oid" "20" @@ -432,7 +432,8 @@ begin_test "migrate export (invalid --remote)" setup_single_remote_branch_tracked - git lfs migrate export --include="*" --remote="zz" 2>&1 | tee migrate.log + git lfs migrate export --include="*" --remote="zz" --yes 2>&1 \ + | tee migrate.log if [ ${PIPESTATUS[0]} -eq 0 ]; then echo >&2 "fatal: expected git lfs migrate export to fail, didn't" exit 1 diff --git a/test/test-migrate-fixup.sh b/t/t-migrate-fixup.sh similarity index 84% rename from test/test-migrate-fixup.sh rename to t/t-migrate-fixup.sh index be20396a..17f50398 100755 --- a/test/test-migrate-fixup.sh +++ b/t/t-migrate-fixup.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -. "test/test-migrate-fixtures.sh" -. "test/testlib.sh" +. "$(dirname "$0")/fixtures/migrate.sh" +. "$(dirname "$0")/testlib.sh" begin_test "migrate import (--fixup)" ( @@ -11,7 +11,7 @@ begin_test "migrate import (--fixup)" txt_oid="$(calc_oid "$(git cat-file -p :a.txt)")" - git lfs migrate import --everything --fixup + git lfs migrate import --everything --fixup --yes assert_pointer "refs/heads/master" "a.txt" "$txt_oid" "120" assert_local_object "$txt_oid" "120" @@ -31,7 +31,7 @@ begin_test "migrate import (--fixup, complex nested)" a_oid="$(calc_oid "$(git cat-file -p :a.txt)")" b_oid="$(calc_oid "$(git cat-file -p :dir/b.txt)")" - git lfs migrate import --everything --fixup + git lfs migrate import --everything --fixup --yes assert_pointer "refs/heads/master" "a.txt" "$a_oid" "1" refute_pointer "refs/heads/master" "b.txt" @@ -53,7 +53,7 @@ begin_test "migrate import (--fixup, --include)" setup_single_local_branch_tracked_corrupt - git lfs migrate import --everything --fixup --include="*.txt" 2>&1 \ + git lfs migrate import --everything --fixup --yes --include="*.txt" 2>&1 \ | tee migrate.log if [ "${PIPESTATUS[0]}" -eq 0 ]; then @@ -71,7 +71,7 @@ begin_test "migrate import (--fixup, --exclude)" setup_single_local_branch_tracked_corrupt - git lfs migrate import --everything --fixup --exclude="*.txt" 2>&1 \ + git lfs migrate import --everything --fixup --yes --exclude="*.txt" 2>&1 \ | tee migrate.log if [ "${PIPESTATUS[0]}" -eq 0 ]; then @@ -89,7 +89,7 @@ begin_test "migrate import (--fixup, --no-rewrite)" setup_single_local_branch_tracked_corrupt - git lfs migrate import --everything --fixup --no-rewrite 2>&1 \ + git lfs migrate import --everything --fixup --yes --no-rewrite 2>&1 \ | tee migrate.log if [ "${PIPESTATUS[0]}" -eq 0 ]; then diff --git a/test/test-migrate-import-no-rewrite.sh b/t/t-migrate-import-no-rewrite.sh similarity index 91% rename from test/test-migrate-import-no-rewrite.sh rename to t/t-migrate-import-no-rewrite.sh index 2bae29f8..d59883c0 100755 --- a/test/test-migrate-import-no-rewrite.sh +++ b/t/t-migrate-import-no-rewrite.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -. "test/test-migrate-fixtures.sh" -. "test/testlib.sh" +. "$(dirname "$0")/fixtures/migrate.sh" +. "$(dirname "$0")/testlib.sh" begin_test "migrate import --no-rewrite (default branch)" ( @@ -12,7 +12,7 @@ begin_test "migrate import --no-rewrite (default branch)" txt_oid="$(calc_oid "$(git cat-file -p :a.txt)")" prev_commit_oid="$(git rev-parse HEAD)" - git lfs migrate import --no-rewrite *.txt + git lfs migrate import --no-rewrite --yes *.txt # Ensure our desired files were imported into git-lfs assert_pointer "refs/heads/master" "a.txt" "$txt_oid" "120" @@ -46,7 +46,7 @@ begin_test "migrate import --no-rewrite (bare repository)" txt_oid="$(calc_oid "$(git cat-file -p :a.txt)")" md_oid="$(calc_oid "$(git cat-file -p :a.md)")" - git lfs migrate import --no-rewrite a.txt a.md + git lfs migrate import --no-rewrite --yes a.txt a.md # Ensure our desired files were imported assert_pointer "refs/heads/master" "a.txt" "$txt_oid" "30" @@ -78,7 +78,7 @@ begin_test "migrate import --no-rewrite (multiple branches)" 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 --no-rewrite *.txt *.md + git lfs migrate import --no-rewrite --yes *.txt *.md # Ensure our desired files were imported assert_pointer "refs/heads/master" "a.md" "$md_oid" "140" @@ -111,7 +111,7 @@ begin_test "migrate import --no-rewrite (no .gitattributes)" setup_multiple_local_branches # Ensure command fails if no .gitattributes files are present - git lfs migrate import --no-rewrite *.txt *.md 2>&1 | tee migrate.log + git lfs migrate import --no-rewrite --yes *.txt *.md 2>&1 | tee migrate.log if [ ${PIPESTATUS[0]} -eq 0 ]; then echo >&2 "fatal: expected git lfs migrate import --no-rewrite to fail, didn't" exit 1 @@ -139,7 +139,7 @@ begin_test "migrate import --no-rewrite (nested .gitattributes)" nested_md_oid="$(calc_oid "$(git cat-file -p :b/a.md)")" txt_oid="$(calc_oid "$(git cat-file -p :a.txt)")" - git lfs migrate import --no-rewrite a.txt b/a.md + git lfs migrate import --no-rewrite --yes a.txt b/a.md # Ensure a.txt and subtree/a.md were imported, even though *.md only exists in the # nested subtree/.gitattributes file @@ -152,7 +152,7 @@ begin_test "migrate import --no-rewrite (nested .gitattributes)" # Failure should occur when trying to import a.md as no entry exists in # top-level .gitattributes file - git lfs migrate import --no-rewrite a.md 2>&1 | tee migrate.log + git lfs migrate import --no-rewrite --yes a.md 2>&1 | tee migrate.log if [ ${PIPESTATUS[0]} -eq 0 ]; then echo >&2 "fatal: expected git lfs migrate import --no-rewrite to fail, didn't" exit 1 @@ -171,7 +171,7 @@ begin_test "migrate import --no-rewrite (with commit message)" prev_commit_oid="$(git rev-parse HEAD)" expected_commit_msg="run git-lfs migrate import --no-rewrite" - git lfs migrate import --message "$expected_commit_msg" --no-rewrite *.txt + git lfs migrate import --message "$expected_commit_msg" --no-rewrite --yes *.txt # Ensure the git history remained the same new_commit_oid="$(git rev-parse HEAD~1)" @@ -201,7 +201,7 @@ begin_test "migrate import --no-rewrite (with empty commit message)" prev_commit_oid="$(git rev-parse HEAD)" - git lfs migrate import -m "" --no-rewrite *.txt + git lfs migrate import -m "" --no-rewrite --yes *.txt # Ensure the git history remained the same new_commit_oid="$(git rev-parse HEAD~1)" diff --git a/test/test-migrate-import.sh b/t/t-migrate-import.sh similarity index 90% rename from test/test-migrate-import.sh rename to t/t-migrate-import.sh index 22517d0b..6e79f3c4 100755 --- a/test/test-migrate-import.sh +++ b/t/t-migrate-import.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -. "test/test-migrate-fixtures.sh" -. "test/testlib.sh" +. "$(dirname "$0")/fixtures/migrate.sh" +. "$(dirname "$0")/testlib.sh" begin_test "migrate import (default branch)" ( @@ -417,7 +417,7 @@ begin_test "migrate import (existing .gitattributes)" txt_master_oid="$(calc_oid "$(git cat-file -p "$master:a.txt")")" - git lfs migrate import --include-ref=refs/heads/master --include="*.txt" + git lfs migrate import --yes --include-ref=refs/heads/master --include="*.txt" assert_local_object "$txt_master_oid" "120" @@ -707,3 +707,85 @@ begin_test "migrate import (multiple remotes)" assert_ref_unmoved "master" "$original_master" "$migrated_master" ) end_test + +begin_test "migrate import (dirty copy, negative answer)" +( + set -e + + setup_local_branch_with_dirty_copy + + original_master="$(git rev-parse master)" + + echo "n" | git lfs migrate import --everything 2>&1 | tee migrate.log + grep "migrate: working copy must not be dirty" migrate.log + + migrated_master="$(git rev-parse master)" + + assert_ref_unmoved "master" "$original_master" "$migrated_master" +) +end_test + +begin_test "migrate import (dirty copy, unknown then negative answer)" +( + set -e + + setup_local_branch_with_dirty_copy + + original_master="$(git rev-parse master)" + + echo "x\nn" | git lfs migrate import --everything 2>&1 | tee migrate.log + + cat migrate.log + + [ "2" -eq "$(grep -o "override changes in your working copy" migrate.log \ + | wc -l | awk '{ print $1 }')" ] + grep "migrate: working copy must not be dirty" migrate.log + + migrated_master="$(git rev-parse master)" + + assert_ref_unmoved "master" "$original_master" "$migrated_master" +) +end_test + +begin_test "migrate import (dirty copy, positive answer)" +( + set -e + + setup_local_branch_with_dirty_copy + + oid="$(calc_oid "$(git cat-file -p :a.txt)")" + + echo "y" | git lfs migrate import --everything 2>&1 | tee migrate.log + grep "migrate: changes in your working copy will be overridden ..." \ + migrate.log + + assert_pointer "refs/heads/master" "a.txt" "$oid" "5" + 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-migrate-info.sh b/t/t-migrate-info.sh similarity index 99% rename from test/test-migrate-info.sh rename to t/t-migrate-info.sh index 62a8de07..a4630895 100755 --- a/test/test-migrate-info.sh +++ b/t/t-migrate-info.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -. "test/test-migrate-fixtures.sh" -. "test/testlib.sh" +. "$(dirname "$0")/fixtures/migrate.sh" +. "$(dirname "$0")/testlib.sh" begin_test "migrate info (default branch)" ( diff --git a/test/test-object-authenticated.sh b/t/t-object-authenticated.sh similarity index 94% rename from test/test-object-authenticated.sh rename to t/t-object-authenticated.sh index 4f16247e..228550cd 100755 --- a/test/test-object-authenticated.sh +++ b/t/t-object-authenticated.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" # these tests rely on GIT_TERMINAL_PROMPT to test properly ensure_git_version_isnt $VERSION_LOWER "2.3.0" diff --git a/test/test-pointer.sh b/t/t-pointer.sh similarity index 98% rename from test/test-pointer.sh rename to t/t-pointer.sh index 8f93da10..dc49366a 100755 --- a/test/test-pointer.sh +++ b/t/t-pointer.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "pointer --file --stdin" @@ -110,6 +110,7 @@ begin_test "pointer --stdin without stdin" [ "1" = "$status" ] ) +end_test begin_test "pointer --stdin with bad pointer" ( @@ -126,6 +127,7 @@ Pointer file error: invalid header" [ "1" = "$status" ] ) +end_test begin_test "pointer --file --pointer mismatch" ( @@ -239,7 +241,7 @@ begin_test "pointer invalid --pointer" expected="Pointer from some-pointer -Pointer file error: invalid header +Pointer file error: invalid header" diff -u <(printf "$expected") <(printf "$output") diff --git a/test/test-post-checkout.sh b/t/t-post-checkout.sh similarity index 99% rename from test/test-post-checkout.sh rename to t/t-post-checkout.sh index 9ef0b89c..0808c697 100755 --- a/test/test-post-checkout.sh +++ b/t/t-post-checkout.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "post-checkout" ( diff --git a/test/test-post-commit.sh b/t/t-post-commit.sh similarity index 98% rename from test/test-post-commit.sh rename to t/t-post-commit.sh index ba489e1f..3751d718 100755 --- a/test/test-post-commit.sh +++ b/t/t-post-commit.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "post-commit" ( diff --git a/test/test-post-merge.sh b/t/t-post-merge.sh similarity index 98% rename from test/test-post-merge.sh rename to t/t-post-merge.sh index 8592da5c..2bf37529 100755 --- a/test/test-post-merge.sh +++ b/t/t-post-merge.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "post-merge" ( diff --git a/test/test-pre-push.sh b/t/t-pre-push.sh similarity index 99% rename from test/test-pre-push.sh rename to t/t-pre-push.sh index a287dc31..f2dae3ea 100755 --- a/test/test-pre-push.sh +++ b/t/t-pre-push.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "pre-push with good ref" ( diff --git a/test/test-progress-meter.sh b/t/t-progress-meter.sh similarity index 94% rename from test/test-progress-meter.sh rename to t/t-progress-meter.sh index 5c86f11f..1f7b9e8f 100755 --- a/test/test-progress-meter.sh +++ b/t/t-progress-meter.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "progress meter displays positive progress" ( diff --git a/test/test-progress.sh b/t/t-progress.sh similarity index 97% rename from test/test-progress.sh rename to t/t-progress.sh index 38a077f0..b8303fec 100755 --- a/test/test-progress.sh +++ b/t/t-progress.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" reponame="$(basename "$0" ".sh")" diff --git a/test/test-prune-worktree.sh b/t/t-prune-worktree.sh similarity index 99% rename from test/test-prune-worktree.sh rename to t/t-prune-worktree.sh index 582cc822..e443b065 100755 --- a/test/test-prune-worktree.sh +++ b/t/t-prune-worktree.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" ensure_git_version_isnt $VERSION_LOWER "2.5.0" diff --git a/test/test-prune.sh b/t/t-prune.sh similarity index 99% rename from test/test-prune.sh rename to t/t-prune.sh index abe6feaa..df37cd86 100755 --- a/test/test-prune.sh +++ b/t/t-prune.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "prune unreferenced and old" ( diff --git a/test/test-pull.sh b/t/t-pull.sh similarity index 97% rename from test/test-pull.sh rename to t/t-pull.sh index ca10758b..b46eaa35 100755 --- a/test/test-pull.sh +++ b/t/t-pull.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "pull" ( @@ -149,7 +149,7 @@ begin_test "pull without clean filter" ( set -e - GIT_LFS_SKIP_SMUDGE=1 git clone $GITSERVER/test-pull no-clean + GIT_LFS_SKIP_SMUDGE=1 git clone $GITSERVER/t-pull no-clean cd no-clean git lfs uninstall git config --list > config.txt @@ -198,7 +198,7 @@ begin_test "pull with raw remote url" git init git lfs install --local --skip-smudge - git remote add origin $GITSERVER/test-pull + git remote add origin $GITSERVER/t-pull git pull origin master contents="a" @@ -208,7 +208,7 @@ begin_test "pull with raw remote url" refute_local_object "$contents_oid" grep "$contents_oid" a.dat - git lfs pull "$GITSERVER/test-pull" + git lfs pull "$GITSERVER/t-pull" echo "pulled!" # LFS object downloaded and in working directory diff --git a/test/test-push-bad-dns.sh b/t/t-push-bad-dns.sh similarity index 95% rename from test/test-push-bad-dns.sh rename to t/t-push-bad-dns.sh index ca0e4010..99f8b9dc 100755 --- a/test/test-push-bad-dns.sh +++ b/t/t-push-bad-dns.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" ensure_git_version_isnt $VERSION_LOWER "2.3.0" diff --git a/test/test-push-failures-local.sh b/t/t-push-failures-local.sh similarity index 99% rename from test/test-push-failures-local.sh rename to t/t-push-failures-local.sh index c3aa8470..ef2d7559 100755 --- a/test/test-push-failures-local.sh +++ b/t/t-push-failures-local.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "push with missing objects (lfs.allowincompletepush true)" ( diff --git a/test/test-push-failures-remote.sh b/t/t-push-failures-remote.sh similarity index 98% rename from test/test-push-failures-remote.sh rename to t/t-push-failures-remote.sh index 30b20501..b5b6c8a0 100755 --- a/test/test-push-failures-remote.sh +++ b/t/t-push-failures-remote.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" # push_fail_test preforms a test expecting a `git lfs push` to fail given the # contents of a particular file contained within that push. The Git server used diff --git a/test/test-push-file-with-branch-name.sh b/t/t-push-file-with-branch-name.sh similarity index 93% rename from test/test-push-file-with-branch-name.sh rename to t/t-push-file-with-branch-name.sh index 0655d050..7ba4f427 100755 --- a/test/test-push-file-with-branch-name.sh +++ b/t/t-push-file-with-branch-name.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "push a file with the same name as a branch" ( diff --git a/test/test-push.sh b/t/t-push.sh similarity index 96% rename from test/test-push.sh rename to t/t-push.sh index b6cabfd0..48ee3eea 100755 --- a/test/test-push.sh +++ b/t/t-push.sh @@ -1,11 +1,12 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" -begin_test "push with good ref" -( - set -e - reponame="push-master-branch-required" +# sets up the repos for the first few push tests. The passed argument is the +# name of the repo to setup. The resuling repo will have a local file tracked +# with LFS and committed, but not yet pushed to the remote +push_repo_setup() { + reponame="$1" setup_remote_repo "$reponame" clone_repo "$reponame" "$reponame" @@ -14,6 +15,12 @@ begin_test "push with good ref" echo "push a" > a.dat git add .gitattributes a.dat git commit -m "add a.dat" +} + +begin_test "push with good ref" +( + set -e + push_repo_setup "push-master-branch-required" git lfs push origin master ) @@ -22,15 +29,8 @@ end_test begin_test "push with tracked ref" ( set -e - reponame="push-tracked-branch-required" - setup_remote_repo "$reponame" - clone_repo "$reponame" "$reponame" - git config "lfs.$(repo_endpoint "$GITSERVER" "$reponame").locksverify" false - git lfs track "*.dat" - echo "push a" > a.dat - git add .gitattributes a.dat - git commit -m "add a.dat" + push_repo_setup "push-tracked-branch-required" git config push.default upstream git config branch.master.merge refs/heads/tracked @@ -41,15 +41,7 @@ end_test begin_test "push with bad ref" ( set -e - reponame="push-other-branch-required" - setup_remote_repo "$reponame" - clone_repo "$reponame" "$reponame" - - git config "lfs.$(repo_endpoint "$GITSERVER" "$reponame").locksverify" false - git lfs track "*.dat" - echo "push a" > a.dat - git add .gitattributes a.dat - git commit -m "add a.dat" + push_repo_setup "push-other-branch-required" git lfs push origin master 2>&1 | tee push.log if [ "0" -eq "${PIPESTATUS[0]}" ]; then @@ -61,6 +53,19 @@ begin_test "push with bad ref" ) end_test +begin_test "push with given remote, configured pushRemote" +( + set -e + push_repo_setup "push-given-and-config" + + git remote add bad-remote "invalid-url" + + git config branch.master.pushRemote bad-remote + + git lfs push --all origin +) +end_test + begin_test "push" ( set -e diff --git a/test/test-reference-clone.sh b/t/t-reference-clone.sh similarity index 98% rename from test/test-reference-clone.sh rename to t/t-reference-clone.sh index 80f4127a..039ce45a 100755 --- a/test/test-reference-clone.sh +++ b/t/t-reference-clone.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" assert_same_inode() { local repo1=$1 diff --git a/test/test-resume-http-range.sh b/t/t-resume-http-range.sh similarity index 98% rename from test/test-resume-http-range.sh rename to t/t-resume-http-range.sh index 011a3022..4751fec6 100755 --- a/test/test-resume-http-range.sh +++ b/t/t-resume-http-range.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "resume-http-range" ( diff --git a/test/test-resume-tus.sh b/t/t-resume-tus.sh similarity index 98% rename from test/test-resume-tus.sh rename to t/t-resume-tus.sh index 02a986bb..3bc5b446 100755 --- a/test/test-resume-tus.sh +++ b/t/t-resume-tus.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "tus-upload-uninterrupted" ( diff --git a/test/test-smudge.sh b/t/t-smudge.sh similarity index 99% rename from test/test-smudge.sh rename to t/t-smudge.sh index ea4a5e8d..5eb01c42 100755 --- a/test/test-smudge.sh +++ b/t/t-smudge.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "smudge" ( diff --git a/test/test-ssh.sh b/t/t-ssh.sh similarity index 95% rename from test/test-ssh.sh rename to t/t-ssh.sh index 7394d802..6d65543a 100755 --- a/test/test-ssh.sh +++ b/t/t-ssh.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "ssh with proxy command in lfs.url" ( diff --git a/test/test-status.sh b/t/t-status.sh similarity index 99% rename from test/test-status.sh rename to t/t-status.sh index 3c6ab029..ea83e45b 100755 --- a/test/test-status.sh +++ b/t/t-status.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "status" ( diff --git a/test/test-submodule-lfsconfig.sh b/t/t-submodule-lfsconfig.sh similarity index 98% rename from test/test-submodule-lfsconfig.sh rename to t/t-submodule-lfsconfig.sh index cfe1adec..cc15bfe0 100755 --- a/test/test-submodule-lfsconfig.sh +++ b/t/t-submodule-lfsconfig.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" lfsname="submodule-config-test-lfs" reponame="submodule-config-test-repo" submodname="submodule-config-test-submodule" diff --git a/test/test-submodule.sh b/t/t-submodule.sh similarity index 98% rename from test/test-submodule.sh rename to t/t-submodule.sh index 828ad1f2..c2f71ee2 100755 --- a/test/test-submodule.sh +++ b/t/t-submodule.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" reponame="submodule-test-repo" submodname="submodule-test-submodule" diff --git a/test/test-track-attrs.sh b/t/t-track-attrs.sh similarity index 95% rename from test/test-track-attrs.sh rename to t/t-track-attrs.sh index 31735c59..ad9d40f9 100755 --- a/test/test-track-attrs.sh +++ b/t/t-track-attrs.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" ensure_git_version_isnt $VERSION_LOWER "2.1.0" diff --git a/test/test-track-wildcards.sh b/t/t-track-wildcards.sh old mode 100644 new mode 100755 similarity index 98% rename from test/test-track-wildcards.sh rename to t/t-track-wildcards.sh index fcdce3b0..8a6ee70e --- a/test/test-track-wildcards.sh +++ b/t/t-track-wildcards.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "track files using wildcard pattern with leading slash" ( diff --git a/test/test-track.sh b/t/t-track.sh similarity index 99% rename from test/test-track.sh rename to t/t-track.sh index 3823a1c7..021ad492 100755 --- a/test/test-track.sh +++ b/t/t-track.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "track" ( diff --git a/test/test-uninstall.sh b/t/t-uninstall.sh similarity index 99% rename from test/test-uninstall.sh rename to t/t-uninstall.sh index 82b07346..4dad6408 100755 --- a/test/test-uninstall.sh +++ b/t/t-uninstall.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "uninstall outside repository" ( diff --git a/test/test-unlock.sh b/t/t-unlock.sh similarity index 99% rename from test/test-unlock.sh rename to t/t-unlock.sh index 75195cb8..c5d65ea9 100755 --- a/test/test-unlock.sh +++ b/t/t-unlock.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "unlocking a lock by path with good ref" ( diff --git a/test/test-untrack.sh b/t/t-untrack.sh similarity index 98% rename from test/test-untrack.sh rename to t/t-untrack.sh index 2e5005ea..60b285f1 100755 --- a/test/test-untrack.sh +++ b/t/t-untrack.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "untrack" ( diff --git a/test/test-unusual-filenames.sh b/t/t-unusual-filenames.sh similarity index 94% rename from test/test-unusual-filenames.sh rename to t/t-unusual-filenames.sh index 2f356f52..a3ef40ee 100755 --- a/test/test-unusual-filenames.sh +++ b/t/t-unusual-filenames.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" reponame="$(basename "$0" ".sh")" diff --git a/test/test-update.sh b/t/t-update.sh similarity index 99% rename from test/test-update.sh rename to t/t-update.sh index 8bde2104..9af4195d 100755 --- a/test/test-update.sh +++ b/t/t-update.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "update" ( diff --git a/test/test-verify.sh b/t/t-verify.sh similarity index 98% rename from test/test-verify.sh rename to t/t-verify.sh index d1613819..399868cf 100755 --- a/test/test-verify.sh +++ b/t/t-verify.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "verify with retries" ( diff --git a/test/test-version.sh b/t/t-version.sh similarity index 93% rename from test/test-version.sh rename to t/t-version.sh index 31dfdb7f..a0218ba4 100755 --- a/test/test-version.sh +++ b/t/t-version.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" begin_test "git lfs --version is a synonym of git lfs version" ( diff --git a/test/test-worktree.sh b/t/t-worktree.sh similarity index 97% rename from test/test-worktree.sh rename to t/t-worktree.sh index d49f99a0..d88a8e4a 100755 --- a/test/test-worktree.sh +++ b/t/t-worktree.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" ensure_git_version_isnt $VERSION_LOWER "2.5.0" envInitConfig='git config filter.lfs.process = "git-lfs filter-process" @@ -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/test-zero-len-file.sh b/t/t-zero-len-file.sh similarity index 97% rename from test/test-zero-len-file.sh rename to t/t-zero-len-file.sh index ff536e7c..6141f32b 100755 --- a/test/test-zero-len-file.sh +++ b/t/t-zero-len-file.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -. "test/testlib.sh" +. "$(dirname "$0")/testlib.sh" reponame="$(basename "$0" ".sh")" diff --git a/t/test-alternates.sh b/t/test-alternates.sh new file mode 100755 index 00000000..d60a84e5 --- /dev/null +++ b/t/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/testenv.sh b/t/testenv.sh similarity index 92% rename from test/testenv.sh rename to t/testenv.sh index 97f53041..34ab6489 100644 --- a/test/testenv.sh +++ b/t/testenv.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Including in script/integration and every test/test-*.sh file. +# Including in script/integration and every t/t-*.sh file. set -e @@ -53,7 +53,7 @@ resolve_symlink() { # The root directory for the git-lfs repository by default. if [ -z "$ROOTDIR" ]; then - ROOTDIR=$(cd $(dirname "$0")/.. && pwd -P) + ROOTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" fi # Where Git LFS outputs the compiled binaries @@ -78,7 +78,7 @@ TRASHDIR="$TMPDIR/$(basename "$0")-$$" # The directory that the test Git server works from. This cleared at the # beginning of every test run. -REMOTEDIR="$ROOTDIR/test/remote" +REMOTEDIR="$ROOTDIR/t/remote" # The directory that stores credentials. Credentials are stored in files with # the username:password with filenames identifying the host (port numbers are @@ -93,19 +93,19 @@ REMOTEDIR="$ROOTDIR/test/remote" CREDSDIR="$REMOTEDIR/creds" # This is the prefix for Git config files. See the "Test Suite" section in -# test/README.md +# t/README.md LFS_CONFIG="$REMOTEDIR/config" # This file contains the URL of the test Git server. See the "Test Suite" -# section in test/README.md +# section in t/README.md LFS_URL_FILE="$REMOTEDIR/url" # This file contains the SSL URL of the test Git server. See the "Test Suite" -# section in test/README.md +# section in t/README.md LFS_SSL_URL_FILE="$REMOTEDIR/sslurl" # This file contains the client cert SSL URL of the test Git server. See the "Test Suite" -# section in test/README.md +# section in t/README.md LFS_CLIENT_CERT_URL_FILE="$REMOTEDIR/clientcerturl" # This file contains the self-signed SSL cert of the TLS endpoint of the test Git server. @@ -137,4 +137,5 @@ if [ $IS_WINDOWS -eq 1 ]; then # prevent Windows OpenSSH from opening GUI prompts SSH_ASKPASS="" fi -. "test/testhelpers.sh" + +. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/testhelpers.sh" diff --git a/test/testhelpers.sh b/t/testhelpers.sh similarity index 85% rename from test/testhelpers.sh rename to t/testhelpers.sh index 92fdc246..f9e1e6e5 100644 --- a/test/testhelpers.sh +++ b/t/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" @@ -313,9 +313,9 @@ wait_for_file() { # setup_remote_repo initializes a bare Git repository that is accessible through # the test Git server. The `pwd` is set to the repository's directory, in case -# further commands need to be run. This server is running for every test in a -# script/integration run, so every test file should setup its own remote -# repository to avoid conflicts. +# further commands need to be run. This server is running for every test in an +# integration run, so every test file should setup its own remote repository to +# avoid conflicts. # # $ setup_remote_repo "some-name" # @@ -490,25 +490,24 @@ repo_endpoint() { setup() { cd "$ROOTDIR" - rm -rf "$REMOTEDIR" - mkdir "$REMOTEDIR" - - echo "Git LFS: ${LFS_BIN:-$(which git-lfs)}" - git lfs version - git version - - if [ -z "$SKIPCOMPILE" ]; then - [ $IS_WINDOWS -eq 1 ] && EXT=".exe" - for go in test/cmd/*.go; do - GO15VENDOREXPERIMENT=1 go build -o "$BINPATH/$(basename $go .go)$EXT" "$go" - done - if [ -z "$SKIPAPITESTCOMPILE" ]; then - # Ensure API test util is built during tests to ensure it stays in sync - GO15VENDOREXPERIMENT=1 go build -o "$BINPATH/git-lfs-test-server-api$EXT" "test/git-lfs-test-server-api/main.go" "test/git-lfs-test-server-api/testdownload.go" "test/git-lfs-test-server-api/testupload.go" - fi + if [ ! -d "$REMOTEDIR" ]; then + mkdir "$REMOTEDIR" fi - LFSTEST_URL="$LFS_URL_FILE" LFSTEST_SSL_URL="$LFS_SSL_URL_FILE" LFSTEST_CLIENT_CERT_URL="$LFS_CLIENT_CERT_URL_FILE" LFSTEST_DIR="$REMOTEDIR" LFSTEST_CERT="$LFS_CERT_FILE" LFSTEST_CLIENT_CERT="$LFS_CLIENT_CERT_FILE" LFSTEST_CLIENT_KEY="$LFS_CLIENT_KEY_FILE" lfstest-gitserver > "$REMOTEDIR/gitserver.log" 2>&1 & + echo "# Git LFS: ${LFS_BIN:-$(which git-lfs)}" + git lfs version | sed -e 's/^/# /g' + git version | sed -e 's/^/# /g' + + if [ -z "$GIT_LFS_NO_TEST_COUNT" ]; then + LFSTEST_URL="$LFS_URL_FILE" \ + LFSTEST_SSL_URL="$LFS_SSL_URL_FILE" \ + LFSTEST_CLIENT_CERT_URL="$LFS_CLIENT_CERT_URL_FILE" \ + LFSTEST_DIR="$REMOTEDIR" \ + LFSTEST_CERT="$LFS_CERT_FILE" \ + LFSTEST_CLIENT_CERT="$LFS_CLIENT_CERT_FILE" \ + LFSTEST_CLIENT_KEY="$LFS_CLIENT_KEY_FILE" \ + lfstest-count-tests increment + fi wait_for_file "$LFS_URL_FILE" wait_for_file "$LFS_SSL_URL_FILE" @@ -521,64 +520,56 @@ setup() { # Set up the initial git config and osx keychain if applicable HOME="$TESTHOME" - mkdir "$HOME" - git lfs install --skip-repo - git config --global credential.usehttppath true - git config --global credential.helper lfstest - git config --global user.name "Git LFS Tests" - git config --global user.email "git-lfs@example.com" - git config --global http.sslcainfo "$LFS_CERT_FILE" - git config --global http.$LFS_CLIENT_CERT_URL/.sslKey "$LFS_CLIENT_KEY_FILE" - git config --global http.$LFS_CLIENT_CERT_URL/.sslCert "$LFS_CLIENT_CERT_FILE" - git config --global http.$LFS_CLIENT_CERT_URL/.sslVerify "false" + if [ ! -d "$HOME" ]; then + mkdir "$HOME" + fi - ( grep "git-lfs clean" "$REMOTEDIR/home/.gitconfig" > /dev/null && grep "git-lfs filter-process" "$REMOTEDIR/home/.gitconfig" > /dev/null ) || { - echo "global git config should be set in $REMOTEDIR/home" - ls -al "$REMOTEDIR/home" - exit 1 - } + if [ ! -f $HOME/.gitconfig ]; then + git lfs install --skip-repo + git config --global credential.usehttppath true + git config --global credential.helper lfstest + git config --global user.name "Git LFS Tests" + git config --global user.email "git-lfs@example.com" + git config --global http.sslcainfo "$LFS_CERT_FILE" + git config --global http.$LFS_CLIENT_CERT_URL/.sslKey "$LFS_CLIENT_KEY_FILE" + git config --global http.$LFS_CLIENT_CERT_URL/.sslCert "$LFS_CLIENT_CERT_FILE" + git config --global http.$LFS_CLIENT_CERT_URL/.sslVerify "false" + fi | sed -e 's/^/# /g' # setup the git credential password storage mkdir -p "$CREDSDIR" printf "user:pass" > "$CREDSDIR/127.0.0.1" - echo - echo "HOME: $HOME" - echo "TMP: $TMPDIR" - echo "CREDS: $CREDSDIR" - echo "lfstest-gitserver:" - echo " LFSTEST_URL=$LFS_URL_FILE" - echo " LFSTEST_SSL_URL=$LFS_SSL_URL_FILE" - echo " LFSTEST_CLIENT_CERT_URL=$LFS_CLIENT_CERT_URL_FILE ($LFS_CLIENT_CERT_URL)" - echo " LFSTEST_CERT=$LFS_CERT_FILE" - echo " LFSTEST_CLIENT_CERT=$LFS_CLIENT_CERT_FILE" - echo " LFSTEST_CLIENT_KEY=$LFS_CLIENT_KEY_FILE" - echo " LFSTEST_DIR=$REMOTEDIR" - echo "GIT:" - git config --global --get-regexp "lfs|credential|user" - - echo + echo "#" + echo "# HOME: $HOME" + echo "# TMP: $TMPDIR" + echo "# CREDS: $CREDSDIR" + echo "# lfstest-gitserver:" + echo "# LFSTEST_URL=$LFS_URL_FILE" + echo "# LFSTEST_SSL_URL=$LFS_SSL_URL_FILE" + echo "# LFSTEST_CLIENT_CERT_URL=$LFS_CLIENT_CERT_URL_FILE ($LFS_CLIENT_CERT_URL)" + echo "# LFSTEST_CERT=$LFS_CERT_FILE" + echo "# LFSTEST_CLIENT_CERT=$LFS_CLIENT_CERT_FILE" + echo "# LFSTEST_CLIENT_KEY=$LFS_CLIENT_KEY_FILE" + echo "# LFSTEST_DIR=$REMOTEDIR" } # shutdown cleans the $TRASHDIR and shuts the test Git server down. shutdown() { - # every test/test-*.sh file should cleanup its trashdir + # every t/t-*.sh file should cleanup its trashdir [ -z "$KEEPTRASH" ] && rm -rf "$TRASHDIR" - if [ "$SHUTDOWN_LFS" != "no" ]; then - # only cleanup test/remote after script/integration done OR a single - # test/test-*.sh file is run manually. - if [ -s "$LFS_URL_FILE" ]; then - curl -s "$(cat "$LFS_URL_FILE")/shutdown" - fi + if [ -z "$GIT_LFS_NO_TEST_COUNT" ]; then + LFSTEST_DIR="$REMOTEDIR" \ + LFS_URL_FILE="$LFS_URL_FILE" \ + lfstest-count-tests decrement + fi +} - [ -z "$KEEPTRASH" ] && rm -rf "$REMOTEDIR" +tap_show_plan() { + local tests="$1" - # delete entire lfs test root if we created it (double check pattern) - if [ -z "$KEEPTRASH" ] && [ "$RM_GIT_LFS_TEST_DIR" = "yes" ] && [[ $GIT_LFS_TEST_DIR == *"$TEMPDIR_PREFIX"* ]]; then - rm -rf "$GIT_LFS_TEST_DIR" - fi -fi + printf "1..%i\n" "$tests" } ensure_git_version_isnt() { @@ -723,6 +714,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$' diff --git a/test/testlib.sh b/t/testlib.sh similarity index 79% rename from test/testlib.sh rename to t/testlib.sh index ea42fdfb..c0391b02 100644 --- a/test/testlib.sh +++ b/t/testlib.sh @@ -22,7 +22,7 @@ fullfile="$(pwd)/$0" -. "test/testenv.sh" +. "$(dirname "$0")/testenv.sh" set -e # keep track of num tests and failures @@ -31,6 +31,7 @@ failures=0 # this runs at process exit atexit () { + tap_show_plan "$tests" shutdown if [ $failures -gt 0 ]; then @@ -41,21 +42,12 @@ atexit () { } # create the trash dir -trap "atexit" EXIT +trap "atexit" SIGKILL SIGINT SIGTERM EXIT SHUTDOWN_LFS=yes GITSERVER=undefined -# if the file exists, assume another process started it, and will clean it up -# when it's done -if [ -s $LFS_URL_FILE ]; then - SHUTDOWN_LFS=no -else - setup || { - failures=$(( failures + 1 )) - exit $? - } -fi +setup GITSERVER=$(cat "$LFS_URL_FILE") SSLGITSERVER=$(cat "$LFS_SSL_URL_FILE") @@ -109,19 +101,19 @@ end_test () { exec 5>&- if [ "$test_status" -eq 0 ]; then - printf "test: %-60s OK\n" "$test_description ..." + printf "ok %d - %-60s\n" "$tests" "$test_description ..." else failures=$(( failures + 1 )) - printf "test: %-60s FAILED\n" "$test_description ..." + printf "not ok %d - %-60s\n" "$tests" "$test_description ..." ( - echo "-- stdout --" - sed 's/^/ /' <"$TRASHDIR/out" - echo "-- stderr --" + echo "# -- stdout --" + sed 's/^/# /' <"$TRASHDIR/out" + echo "# -- stderr --" grep -v -e '^\+ end_test' -e '^+ set +x' <"$TRASHDIR/err" | - sed 's/^/ /' + sed 's/^/# /' if [ $IS_WINDOWS -eq 0 ]; then - echo "-- git trace --" - sed 's/^/ /' <"$TRASHDIR/trace" + echo "# -- git trace --" + sed 's/^/# /' <"$TRASHDIR/trace" fi ) 1>&2 echo diff --git a/test/README.md b/test/README.md deleted file mode 100644 index b289421f..00000000 --- a/test/README.md +++ /dev/null @@ -1,137 +0,0 @@ -# Git LFS Tests - -Git LFS uses two form of tests: unit tests for the internals written in Go, and -integration tests that run `git` and `git-lfs` in a real shell environment. -You can run them separately: - -``` -$ script/test # Tests the Go packages. -$ script/integration # Tests the commands in shell scripts. -``` - -CI servers should always run both: - -``` -$ script/cibuild -``` - -## Internal Package Tests - -The internal tests use Go's builtin [testing][t] package. - -You can run individual tests by passing arguments to `script/test`: - -``` -# test a specific Go package -$ script/test lfs - -# pass other `go test` arguments -$ script/test lfs -run TestSuccessStatus -v -github.com/rubyist/tracerx -github.com/git-lfs/git-lfs/git -github.com/technoweenie/assert -=== RUN TestSuccessStatus ---- PASS: TestSuccessStatus (0.00 seconds) -PASS -ok _/Users/rick/git-lfs/git-lfs/lfs 0.011s -``` - -[t]: http://golang.org/pkg/testing/ - -## Integration Tests - -Git LFS integration tests are shell scripts that test the `git-lfs` command from -the shell. Each test file can be run individually, or in parallel through -`script/integration`. Some tests will change the `pwd`, so it's important that -they run in separate OS processes. - -``` -$ test/test-happy-path.sh -compile git-lfs for test/test-happy-path.sh -LFSTEST_URL=/Users/rick/git-lfs/git-lfs/test/remote/url LFSTEST_DIR=/Users/rick/git-lfs/git-lfs/test/remote lfstest-gitserver -test: happy path ... OK -``` - -1. The integration tests should not rely on global system or git config. -2. The tests should be cross platform (Linux, Mac, Windows). -3. Tests should bootstrap an isolated, clean environment. See the Test Suite -section. -4. Successful test runs should have minimal output. -5. Failing test runs should dump enough information to diagnose the bug. This -includes stdout, stderr, any log files, and even the OS environment. - -There are a few environment variables that you can set to change the test suite -behavior: - -* `GIT_LFS_TEST_DIR=path` - This sets the directory that is used as the current -working directory of the tests. By default, this will be in your temp dir. It's -recommended that this is set to a directory outside of any Git repository. -* `GIT_LFS_TEST_MAXPROCS=N` - This tells `script/integration` how many tests to -run in parallel. Default: 4. -* `KEEPTRASH=1` - This will leave the local repository data in a `tmp` directory -and the remote repository data in `test/remote`. -* `SKIPCOMPILE=1` - This skips the Git LFS compilation step. Speeds up the -tests when you're running the same test script multiple times without changing -any Go code. - -Also ensure that your `noproxy` environment variable contains `127.0.0.1` host, -to allow git commands to reach the local Git server `lfstest-gitserver`. - -### Test Suite - -The `testenv.sh` script includes some global variables used in tests. This -should be automatically included in every `test/test-*.sh` script and -`script/integration`. - -`testhelpers.sh` defines some shell functions. Most are only used in the test -scripts themselves. `script/integration` uses the `setup()` and `shutdown()` -functions. - -`testlib.sh` is a [fork of a lightweight shell testing lib][testlib] that is -used internally at GitHub. Only the `test/test-*.sh` scripts should include -this. - -Tests live in this `./test` directory, and must have a unique name like: -`test-{name}.sh`. All tests should start with a basic template. See -`test/test-happy-path.sh` for an example. - -``` -#!/usr/bin/env bash - -. "test/testlib.sh" - -begin_test "template" -( - set -e - - echo "your tests go here" -) -end_test -``` - -The `set -e` command will bail on the test at the first command that returns a -non zero exit status. Use simple shell commands like `grep` as assertions. - -The test suite has standard `setup` and `shutdown` functions that should be -run only once, before/after running the tests. The `setup` and `shutdown` -functions are run by `script/integration` and also by individual test scripts -when they are executed directly. Setup does the following: - -* Resets temporary test directories. -* Compiles git-lfs with the latest code changes. -* Compiles Go files in `test/cmd` to `bin`, and adds them the PATH. -* Spins up a test Git and Git LFS server so the entire push/pull flow can be -exercised. -* Sets up a git credential helper that always returns a set username and -password. - -The test Git server writes a `test/remote/url` file when it's complete. This -file is how individual test scripts detect if `script/integration` is being -run. You can fake this by manually spinning up the Git server using the -`lfstest-gitserver` line that is output after Git LFS is compiled. - -By default, temporary directories in `tmp` and the `test/remote` directory are -cleared after test runs. Send the "KEEPTRASH" if you want to keep these files -around for debugging failed tests. - -[testlib]: https://gist3.github.com/rtomayko/3877539 diff --git a/test/git-lfs-test-server-api/.gitignore b/test/git-lfs-test-server-api/.gitignore deleted file mode 100644 index 6b3d7527..00000000 --- a/test/git-lfs-test-server-api/.gitignore +++ /dev/null @@ -1 +0,0 @@ -git-lfs-test-server-api* \ No newline at end of file diff --git a/tq/basic_upload.go b/tq/basic_upload.go index 00ced92e..8e7a3d37 100644 --- a/tq/basic_upload.go +++ b/tq/basic_upload.go @@ -3,6 +3,7 @@ package tq import ( "io" "io/ioutil" + "net/http" "os" "path/filepath" "strconv" @@ -14,7 +15,8 @@ import ( ) const ( - BasicAdapterName = "basic" + BasicAdapterName = "basic" + defaultContentType = "application/octet-stream" ) // Adapter for basic uploads (non resumable) @@ -56,10 +58,6 @@ func (a *basicUploadAdapter) DoTransfer(ctx interface{}, t *Transfer, cb Progres return err } - if len(req.Header.Get("Content-Type")) == 0 { - req.Header.Set("Content-Type", "application/octet-stream") - } - if req.Header.Get("Transfer-Encoding") == "chunked" { req.TransferEncoding = []string{"chunked"} } else { @@ -74,6 +72,10 @@ func (a *basicUploadAdapter) DoTransfer(ctx interface{}, t *Transfer, cb Progres } defer f.Close() + if err := setContentTypeFor(req, f); err != nil { + return err + } + // Ensure progress callbacks made while uploading // Wrap callback to give name context ccb := func(totalSize int64, readSoFar int64, readSinceLast int) error { @@ -171,3 +173,27 @@ func configureBasicUploadAdapter(m *Manifest) { return nil }) } + +func setContentTypeFor(req *http.Request, r io.ReadSeeker) error { + if len(req.Header.Get("Content-Type")) != 0 { + return nil + } + + buffer := make([]byte, 512) + n, err := r.Read(buffer) + if err != nil && err != io.EOF { + return errors.Wrap(err, "content type detect") + } + + contentType := http.DetectContentType(buffer[:n]) + if _, err := r.Seek(0, 0); err != nil { + return errors.Wrap(err, "content type rewind") + } + + if contentType == "" { + contentType = defaultContentType + } + + req.Header.Set("Content-Type", contentType) + return nil +}