merge latest master

This commit is contained in:
risk danger olson 2015-10-26 13:59:02 -06:00
commit 4e4dac25c5
27 changed files with 643 additions and 640 deletions

@ -19,4 +19,3 @@ authors = [
"github.com/spf13/cobra" = "c55cdf33856a08e4822738728b41783292812889" "github.com/spf13/cobra" = "c55cdf33856a08e4822738728b41783292812889"
"github.com/spf13/pflag" = "580b9be06c33d8ba9dcc8757ea56b7642472c2f5" "github.com/spf13/pflag" = "580b9be06c33d8ba9dcc8757ea56b7642472c2f5"
"github.com/technoweenie/assert" = "b25ea301d127043ffacf3b2545726e79b6632139" "github.com/technoweenie/assert" = "b25ea301d127043ffacf3b2545726e79b6632139"
"github.com/technoweenie/go-contentaddressable" = "38171def3cd15e3b76eb156219b3d48704643899"

@ -1,11 +1,37 @@
package main package main
import ( import (
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"github.com/github/git-lfs/commands" "github.com/github/git-lfs/commands"
"github.com/github/git-lfs/lfs" "github.com/github/git-lfs/lfs"
) )
func main() { func main() {
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, os.Kill)
var once sync.Once
go func() {
for {
sig := <-c
once.Do(lfs.ClearTempObjects)
fmt.Fprintf(os.Stderr, "\nExiting because of %q signal.\n", sig)
exitCode := 1
if sysSig, ok := sig.(syscall.Signal); ok {
exitCode = int(sysSig)
}
os.Exit(exitCode + 128)
}
}()
commands.Run() commands.Run()
lfs.LogHttpStats() lfs.LogHttpStats()
once.Do(lfs.ClearTempObjects)
} }

@ -425,7 +425,7 @@ func doApiBatchRequest(req *http.Request) (*http.Response, []*objectResource, er
res, err := doAPIRequest(req, Config.PrivateAccess()) res, err := doAPIRequest(req, Config.PrivateAccess())
if err != nil { if err != nil {
if res.StatusCode == 401 { if res != nil && res.StatusCode == 401 {
return res, nil, newAuthError(err) return res, nil, newAuthError(err)
} }
return res, nil, err return res, nil, err

@ -337,7 +337,7 @@ func (c *Configuration) loadGitConfig() bool {
return true return true
} }
func (c *Configuration) readGitConfig(output string, uniqRemotes map[string]bool, whitelisted bool) { func (c *Configuration) readGitConfig(output string, uniqRemotes map[string]bool, onlySafe bool) {
lines := strings.Split(output, "\n") lines := strings.Split(output, "\n")
for _, line := range lines { for _, line := range lines {
pieces := strings.SplitN(line, "=", 2) pieces := strings.SplitN(line, "=", 2)
@ -345,7 +345,7 @@ func (c *Configuration) readGitConfig(output string, uniqRemotes map[string]bool
continue continue
} }
allowed := !whitelisted allowed := !onlySafe
key := strings.ToLower(pieces[0]) key := strings.ToLower(pieces[0])
value := pieces[1] value := pieces[1]
@ -355,12 +355,12 @@ func (c *Configuration) readGitConfig(output string, uniqRemotes map[string]bool
ext := c.extensions[name] ext := c.extensions[name]
switch keyParts[3] { switch keyParts[3] {
case "clean": case "clean":
if whitelisted { if onlySafe {
continue continue
} }
ext.Clean = value ext.Clean = value
case "smudge": case "smudge":
if whitelisted { if onlySafe {
continue continue
} }
ext.Smudge = value ext.Smudge = value
@ -375,7 +375,7 @@ func (c *Configuration) readGitConfig(output string, uniqRemotes map[string]bool
ext.Name = name ext.Name = name
c.extensions[name] = ext c.extensions[name] = ext
} else if len(keyParts) > 1 && keyParts[0] == "remote" { } else if len(keyParts) > 1 && keyParts[0] == "remote" {
if whitelisted && (len(keyParts) < 3 || keyParts[2] != "lfsurl") { if onlySafe && (len(keyParts) == 3 && keyParts[2] != "lfsurl") {
continue continue
} }
@ -384,7 +384,7 @@ func (c *Configuration) readGitConfig(output string, uniqRemotes map[string]bool
uniqRemotes[remote] = remote == "origin" uniqRemotes[remote] = remote == "origin"
} }
if !allowed && key != "lfs.url" { if !allowed && keyIsUnsafe(key) {
continue continue
} }
@ -403,3 +403,18 @@ func (c *Configuration) readGitConfig(output string, uniqRemotes map[string]bool
} }
} }
} }
func keyIsUnsafe(key string) bool {
for _, safe := range safeKeys {
if safe == key {
return false
}
}
return true
}
var safeKeys = []string{
"lfs.url",
"lfs.fetchinclude",
"lfs.fetchexclude",
}

@ -84,7 +84,9 @@ func TestSuccessfulDownload(t *testing.T) {
}) })
defer Config.ResetConfig() defer Config.ResetConfig()
Config.SetConfig("lfs.batch", "false")
Config.SetConfig("lfs.url", server.URL+"/media") Config.SetConfig("lfs.url", server.URL+"/media")
reader, size, err := Download("oid") reader, size, err := Download("oid")
if err != nil { if err != nil {
t.Fatalf("unexpected error: %s", err) t.Fatalf("unexpected error: %s", err)
@ -213,6 +215,7 @@ func TestSuccessfulDownloadWithRedirects(t *testing.T) {
}) })
defer Config.ResetConfig() defer Config.ResetConfig()
Config.SetConfig("lfs.batch", "false")
Config.SetConfig("lfs.url", server.URL+"/redirect") Config.SetConfig("lfs.url", server.URL+"/redirect")
for _, redirect := range redirectCodes { for _, redirect := range redirectCodes {
@ -319,6 +322,7 @@ func TestSuccessfulDownloadWithAuthorization(t *testing.T) {
}) })
defer Config.ResetConfig() defer Config.ResetConfig()
Config.SetConfig("lfs.batch", "false")
Config.SetConfig("lfs.url", server.URL+"/media") Config.SetConfig("lfs.url", server.URL+"/media")
reader, size, err := Download("oid") reader, size, err := Download("oid")
if err != nil { if err != nil {
@ -419,6 +423,7 @@ func TestSuccessfulDownloadFromSeparateHost(t *testing.T) {
}) })
defer Config.ResetConfig() defer Config.ResetConfig()
Config.SetConfig("lfs.batch", "false")
Config.SetConfig("lfs.url", server.URL+"/media") Config.SetConfig("lfs.url", server.URL+"/media")
reader, size, err := Download("oid") reader, size, err := Download("oid")
if err != nil { if err != nil {
@ -550,6 +555,7 @@ func TestSuccessfulDownloadFromSeparateRedirectedHost(t *testing.T) {
}) })
defer Config.ResetConfig() defer Config.ResetConfig()
Config.SetConfig("lfs.batch", "false")
Config.SetConfig("lfs.url", server.URL+"/media") Config.SetConfig("lfs.url", server.URL+"/media")
for _, redirect := range redirectCodes { for _, redirect := range redirectCodes {
@ -587,6 +593,7 @@ func TestDownloadAPIError(t *testing.T) {
}) })
defer Config.ResetConfig() defer Config.ResetConfig()
Config.SetConfig("lfs.batch", "false")
Config.SetConfig("lfs.url", server.URL+"/media") Config.SetConfig("lfs.url", server.URL+"/media")
_, _, err := Download("oid") _, _, err := Download("oid")
if err == nil { if err == nil {
@ -655,6 +662,7 @@ func TestDownloadStorageError(t *testing.T) {
}) })
defer Config.ResetConfig() defer Config.ResetConfig()
Config.SetConfig("lfs.batch", "false")
Config.SetConfig("lfs.url", server.URL+"/media") Config.SetConfig("lfs.url", server.URL+"/media")
_, _, err := Download("oid") _, _, err := Download("oid")
if err == nil { if err == nil {

@ -28,6 +28,7 @@ var (
LocalGitDir string // parent of index / config / hooks etc LocalGitDir string // parent of index / config / hooks etc
LocalGitStorageDir string // parent of objects/lfs (may be same as LocalGitDir but may not) LocalGitStorageDir string // parent of objects/lfs (may be same as LocalGitDir but may not)
LocalMediaDir string // root of lfs objects LocalMediaDir string // root of lfs objects
LocalObjectTempDir string // where temporarily downloading objects are stored
LocalLogDir string LocalLogDir string
checkedTempDir string checkedTempDir string
) )
@ -112,7 +113,8 @@ func ResolveDirs() {
panic(fmt.Errorf("Error trying to create log directory in '%s': %s", LocalLogDir, err)) panic(fmt.Errorf("Error trying to create log directory in '%s': %s", LocalLogDir, err))
} }
if err := os.MkdirAll(TempDir, tempDirPerms); err != nil { LocalObjectTempDir = filepath.Join(TempDir, "objects")
if err := os.MkdirAll(LocalObjectTempDir, tempDirPerms); err != nil {
panic(fmt.Errorf("Error trying to create temp directory in '%s': %s", TempDir, err)) panic(fmt.Errorf("Error trying to create temp directory in '%s': %s", TempDir, err))
} }
} }

62
lfs/localstorage.go Normal file

@ -0,0 +1,62 @@
package lfs
import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
)
func ClearTempObjects() {
if len(LocalObjectTempDir) == 0 {
return
}
d, err := os.Open(LocalObjectTempDir)
if err != nil {
fmt.Fprintf(os.Stderr, "Error opening %q to clear old temp files: %s\n", LocalObjectTempDir, err)
return
}
filenames, _ := d.Readdirnames(-1)
for _, filename := range filenames {
path := filepath.Join(LocalObjectTempDir, filename)
if shouldDeleteTempObject(path) {
os.RemoveAll(path)
}
}
}
func shouldDeleteTempObject(path string) bool {
info, err := os.Stat(path)
if err != nil {
return false
}
if info.IsDir() {
return false
}
base := filepath.Base(path)
parts := strings.SplitN(base, "-", 2)
oid := parts[0]
if len(parts) < 2 || len(oid) != 64 {
tracerx.Printf("Removing invalid tmp object file: %s", path)
return true
}
if FileExists(localMediaPathNoCreate(oid)) {
tracerx.Printf("Removing existing tmp object file: %s", path)
return true
}
if time.Since(info.ModTime()) > time.Hour {
tracerx.Printf("Removing old tmp object file: %s", path)
return true
}
return false
}

@ -1,14 +1,17 @@
package lfs package lfs
import ( import (
"crypto/sha256"
"encoding/hex"
"fmt" "fmt"
"hash"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"github.com/github/git-lfs/vendor/_nuts/github.com/cheggaaa/pb" "github.com/github/git-lfs/vendor/_nuts/github.com/cheggaaa/pb"
"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx" "github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
contentaddressable "github.com/github/git-lfs/vendor/_nuts/github.com/technoweenie/go-contentaddressable"
) )
func PointerSmudgeToFile(filename string, ptr *Pointer, download bool, cb CopyCallback) error { func PointerSmudgeToFile(filename string, ptr *Pointer, download bool, cb CopyCallback) error {
@ -99,7 +102,6 @@ func downloadObject(ptr *Pointer, obj *objectResource, mediafile string, cb Copy
defer reader.Close() defer reader.Close()
} }
// TODO this can be unified with the same code in downloadFile
if err != nil { if err != nil {
return Errorf(err, "Error downloading %s", mediafile) return Errorf(err, "Error downloading %s", mediafile)
} }
@ -108,18 +110,7 @@ func downloadObject(ptr *Pointer, obj *objectResource, mediafile string, cb Copy
ptr.Size = size ptr.Size = size
} }
mediaFile, err := contentaddressable.NewFile(mediafile) if err := bufferDownloadedFile(mediafile, reader, ptr.Size, cb); err != nil {
if err != nil {
return Errorf(err, "Error opening media file buffer.")
}
_, err = CopyWithCallback(mediaFile, reader, ptr.Size, cb)
if err == nil {
err = mediaFile.Accept()
}
mediaFile.Close()
if err != nil {
return Errorf(err, "Error buffering media file.") return Errorf(err, "Error buffering media file.")
} }
@ -134,31 +125,84 @@ func downloadFile(writer io.Writer, ptr *Pointer, workingfile, mediafile string,
} }
if err != nil { if err != nil {
return Errorf(err, "Error downloading %s.", mediafile) return Errorf(err, "Error downloading %s: %s", filepath.Base(mediafile), err)
} }
if ptr.Size == 0 { if ptr.Size == 0 {
ptr.Size = size ptr.Size = size
} }
mediaFile, err := contentaddressable.NewFile(mediafile) if err := bufferDownloadedFile(mediafile, reader, ptr.Size, cb); err != nil {
if err != nil { return Errorf(err, "Error buffering media file: %s", err)
return Errorf(err, "Error opening media file buffer.")
}
_, err = CopyWithCallback(mediaFile, reader, ptr.Size, cb)
if err == nil {
err = mediaFile.Accept()
}
mediaFile.Close()
if err != nil {
return Errorf(err, "Error buffering media file.")
} }
return readLocalFile(writer, ptr, mediafile, workingfile, nil) return readLocalFile(writer, ptr, mediafile, workingfile, nil)
} }
// Writes the content of reader to filename atomically by writing to a temp file
// first, and confirming the content SHA-256 is valid. This is basically a copy
// of atomic.WriteFile() at:
//
// https://github.com/natefinch/atomic/blob/a62ce929ffcc871a51e98c6eba7b20321e3ed62d/atomic.go#L12-L17
//
// filename - Absolute path to a file to write, with the filename a 64 character
// SHA-256 hex signature.
// reader - Any io.Reader
// size - Expected byte size of the content. Used for the progress bar in
// the optional CopyCallback.
// cb - Optional CopyCallback object for providing download progress to
// external Git LFS tools.
func bufferDownloadedFile(filename string, reader io.Reader, size int64, cb CopyCallback) error {
oid := filepath.Base(filename)
f, err := ioutil.TempFile(LocalObjectTempDir, oid+"-")
if err != nil {
return fmt.Errorf("cannot create temp file: %v", err)
}
defer func() {
if err != nil {
// Don't leave the temp file lying around on error.
_ = os.Remove(f.Name()) // yes, ignore the error, not much we can do about it.
}
}()
hasher := newHashingReader(reader)
// ensure we always close f. Note that this does not conflict with the
// close below, as close is idempotent.
defer f.Close()
name := f.Name()
written, err := CopyWithCallback(f, hasher, size, cb)
if err != nil {
return fmt.Errorf("cannot write data to tempfile %q: %v", name, err)
}
if err := f.Close(); err != nil {
return fmt.Errorf("can't close tempfile %q: %v", name, err)
}
if actual := hasher.Hash(); actual != oid {
return fmt.Errorf("Expected OID %s, got %s after %d bytes written", oid, actual, written)
}
// get the file mode from the original file and use that for the replacement
// file, too.
info, err := os.Stat(filename)
if os.IsNotExist(err) {
// no original file
} else if err != nil {
return err
} else {
if err := os.Chmod(name, info.Mode()); err != nil {
return fmt.Errorf("can't set filemode on tempfile %q: %v", name, err)
}
}
if err := os.Rename(name, filename); err != nil {
return fmt.Errorf("cannot replace %q with tempfile %q: %v", filename, name, err)
}
return nil
}
func readLocalFile(writer io.Writer, ptr *Pointer, mediafile string, workingfile string, cb CopyCallback) error { func readLocalFile(writer io.Writer, ptr *Pointer, mediafile string, workingfile string, cb CopyCallback) error {
reader, err := os.Open(mediafile) reader, err := os.Open(mediafile)
if err != nil { if err != nil {
@ -191,7 +235,7 @@ func readLocalFile(writer io.Writer, ptr *Pointer, mediafile string, workingfile
// pipe extensions in reverse order // pipe extensions in reverse order
var extsR []Extension var extsR []Extension
for i, _ := range exts { for i := range exts {
ext := exts[len(exts)-1-i] ext := exts[len(exts)-1-i]
extsR = append(extsR, ext) extsR = append(extsR, ext)
} }
@ -230,15 +274,41 @@ func readLocalFile(writer io.Writer, ptr *Pointer, mediafile string, workingfile
// setup reader // setup reader
reader, err = os.Open(response.file.Name()) reader, err = os.Open(response.file.Name())
if err != nil { if err != nil {
return Errorf(err, "Error opening smudged file.") return Errorf(err, "Error opening smudged file: %s", err)
} }
defer reader.Close() defer reader.Close()
} }
_, err = CopyWithCallback(writer, reader, ptr.Size, cb) _, err = CopyWithCallback(writer, reader, ptr.Size, cb)
if err != nil { if err != nil {
return Errorf(err, "Error reading from media file.") return Errorf(err, "Error reading from media file: %s", err)
} }
return nil return nil
} }
type hashingReader struct {
reader io.Reader
hasher hash.Hash
}
func newHashingReader(r io.Reader) *hashingReader {
tracerx.Printf("NEW HASHING READER")
return &hashingReader{r, sha256.New()}
}
func (r *hashingReader) Hash() string {
return hex.EncodeToString(r.hasher.Sum(nil))
}
func (r *hashingReader) Read(b []byte) (int, error) {
w, err := r.reader.Read(b)
if err == nil || err == io.EOF {
_, e := r.hasher.Write(b[0:w])
if e != nil && err == nil {
return w, e
}
}
return w, err
}

@ -30,7 +30,6 @@ $ script/test lfs -run TestSuccessStatus -v
github.com/kr/text github.com/kr/text
github.com/cheggaaa/pb github.com/cheggaaa/pb
github.com/rubyist/tracerx github.com/rubyist/tracerx
github.com/technoweenie/go-contentaddressable
github.com/kr/pretty github.com/kr/pretty
github.com/github/git-lfs/git github.com/github/git-lfs/git
github.com/technoweenie/assert github.com/technoweenie/assert

@ -18,7 +18,7 @@ begin_test "checkout"
contentsize=19 contentsize=19
contents_oid=$(calc_oid "$contents") contents_oid=$(calc_oid "$contents")
# Same content everywhere is ok, just one object in lfs db echo "Same content everywhere is ok, just one object in lfs db"
printf "$contents" > file1.dat printf "$contents" > file1.dat
printf "$contents" > file2.dat printf "$contents" > file2.dat
printf "$contents" > file3.dat printf "$contents" > file3.dat
@ -40,7 +40,7 @@ begin_test "checkout"
# Remove the working directory # Remove the working directory
rm -rf file1.dat file2.dat file3.dat folder1/nested.dat folder2/nested.dat rm -rf file1.dat file2.dat file3.dat folder1/nested.dat folder2/nested.dat
# checkout should replace all echo "checkout should replace all"
git lfs checkout git lfs checkout
[ "$contents" = "$(cat file1.dat)" ] [ "$contents" = "$(cat file1.dat)" ]
[ "$contents" = "$(cat file2.dat)" ] [ "$contents" = "$(cat file2.dat)" ]
@ -51,7 +51,7 @@ begin_test "checkout"
# Remove again # Remove again
rm -rf file1.dat file2.dat file3.dat folder1/nested.dat folder2/nested.dat rm -rf file1.dat file2.dat file3.dat folder1/nested.dat folder2/nested.dat
# checkout with filters echo "checkout with filters"
git lfs checkout file2.dat git lfs checkout file2.dat
[ "$contents" = "$(cat file2.dat)" ] [ "$contents" = "$(cat file2.dat)" ]
[ ! -f file1.dat ] [ ! -f file1.dat ]
@ -59,14 +59,14 @@ begin_test "checkout"
[ ! -f folder1/nested.dat ] [ ! -f folder1/nested.dat ]
[ ! -f folder2/nested.dat ] [ ! -f folder2/nested.dat ]
# quotes to avoid shell globbing echo "quotes to avoid shell globbing"
git lfs checkout "file*.dat" git lfs checkout "file*.dat"
[ "$contents" = "$(cat file1.dat)" ] [ "$contents" = "$(cat file1.dat)" ]
[ "$contents" = "$(cat file3.dat)" ] [ "$contents" = "$(cat file3.dat)" ]
[ ! -f folder1/nested.dat ] [ ! -f folder1/nested.dat ]
[ ! -f folder2/nested.dat ] [ ! -f folder2/nested.dat ]
# test subdir context echo "test subdir context"
pushd folder1 pushd folder1
git lfs checkout nested.dat git lfs checkout nested.dat
[ "$contents" = "$(cat nested.dat)" ] [ "$contents" = "$(cat nested.dat)" ]
@ -77,11 +77,11 @@ begin_test "checkout"
[ "$contents" = "$(cat nested.dat)" ] [ "$contents" = "$(cat nested.dat)" ]
popd popd
# test folder param echo "test folder param"
git lfs checkout folder2 git lfs checkout folder2
[ "$contents" = "$(cat folder2/nested.dat)" ] [ "$contents" = "$(cat folder2/nested.dat)" ]
# test '.' in current dir echo "test '.' in current dir"
rm -rf file1.dat file2.dat file3.dat folder1/nested.dat folder2/nested.dat rm -rf file1.dat file2.dat file3.dat folder1/nested.dat folder2/nested.dat
git lfs checkout . git lfs checkout .
[ "$contents" = "$(cat file1.dat)" ] [ "$contents" = "$(cat file1.dat)" ]
@ -90,7 +90,7 @@ begin_test "checkout"
[ "$contents" = "$(cat folder1/nested.dat)" ] [ "$contents" = "$(cat folder1/nested.dat)" ]
[ "$contents" = "$(cat folder2/nested.dat)" ] [ "$contents" = "$(cat folder2/nested.dat)" ]
# test checkout with missing data doesn't fail echo "test checkout with missing data doesn't fail"
git push origin master git push origin master
rm -rf .git/lfs/objects rm -rf .git/lfs/objects
rm file*.dat rm file*.dat

@ -13,19 +13,29 @@ begin_test "env with no remote"
cd $reponame cd $reponame
git init git init
expected=$(printf "%s\n%s\n localwd=$(native_path "$TRASHDIR/$reponame")
LocalWorkingDir=$TRASHDIR/$reponame localgit=$(native_path "$TRASHDIR/$reponame/.git")
LocalGitDir=$TRASHDIR/$reponame/.git localgitstore=$(native_path "$TRASHDIR/$reponame/.git")
LocalGitStorageDir=$TRASHDIR/$reponame/.git localmedia=$(native_path "$TRASHDIR/$reponame/.git/lfs/objects")
LocalMediaDir=$TRASHDIR/$reponame/.git/lfs/objects tempdir=$(native_path "$TRASHDIR/$reponame/.git/lfs/tmp")
TempDir=$TRASHDIR/$reponame/.git/lfs/tmp envVars=$(printf "%s" "$(env | grep "^GIT")")
expected=$(printf '%s
%s
LocalWorkingDir=%s
LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
BatchTransfer=true BatchTransfer=true
$(env | grep "^GIT")
%s %s
" "$(git lfs version)" "$(git version)" "$envInitConfig") %s
' "$(git lfs version)" "$(git version)" "$localwd" "$localgit" "$localgitstore" "$localmedia" "$tempdir" "$envVars" "$envInitConfig")
actual=$(git lfs env) actual=$(git lfs env)
[ "$expected" = "$actual" ]
contains_same_elements "$expected" "$actual"
) )
end_test end_test
@ -38,25 +48,34 @@ begin_test "env with origin remote"
git init git init
git remote add origin "$GITSERVER/env-origin-remote" git remote add origin "$GITSERVER/env-origin-remote"
expected=$(printf "%s\n%s\n endpoint="$GITSERVER/$reponame.git/info/lfs (auth=none)"
Endpoint=$GITSERVER/$reponame.git/info/lfs (auth=none) localwd=$(native_path "$TRASHDIR/$reponame")
LocalWorkingDir=$TRASHDIR/$reponame localgit=$(native_path "$TRASHDIR/$reponame/.git")
LocalGitDir=$TRASHDIR/$reponame/.git localgitstore=$(native_path "$TRASHDIR/$reponame/.git")
LocalGitStorageDir=$TRASHDIR/$reponame/.git localmedia=$(native_path "$TRASHDIR/$reponame/.git/lfs/objects")
LocalMediaDir=$TRASHDIR/$reponame/.git/lfs/objects tempdir=$(native_path "$TRASHDIR/$reponame/.git/lfs/tmp")
TempDir=$TRASHDIR/$reponame/.git/lfs/tmp envVars=$(printf "%s" "$(env | grep "^GIT")")
expected=$(printf '%s
%s
Endpoint=%s
LocalWorkingDir=%s
LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
BatchTransfer=true BatchTransfer=true
$(env | grep "^GIT")
%s %s
" "$(git lfs version)" "$(git version)" "$envInitConfig") %s
' "$(git lfs version)" "$(git version)" "$endpoint" "$localwd" "$localgit" "$localgitstore" "$localmedia" "$tempdir" "$envVars" "$envInitConfig")
actual=$(git lfs env) actual=$(git lfs env)
[ "$expected" = "$actual" ] contains_same_elements "$expected" "$actual"
cd .git cd .git
expected2=$(echo "$expected" | sed -e 's/LocalWorkingDir=.*/LocalWorkingDir=/') expected2=$(echo "$expected" | sed -e 's/LocalWorkingDir=.*/LocalWorkingDir=/')
actual2=$(git lfs env) actual2=$(git lfs env)
[ "$expected2" = "$actual2" ] contains_same_elements "$expected2" "$actual2"
) )
end_test end_test
@ -70,26 +89,36 @@ begin_test "env with multiple remotes"
git remote add origin "$GITSERVER/env-origin-remote" git remote add origin "$GITSERVER/env-origin-remote"
git remote add other "$GITSERVER/env-other-remote" git remote add other "$GITSERVER/env-other-remote"
expected=$(printf "%s\n%s\n endpoint="$GITSERVER/env-origin-remote.git/info/lfs (auth=none)"
Endpoint=$GITSERVER/env-origin-remote.git/info/lfs (auth=none) endpoint2="$GITSERVER/env-other-remote.git/info/lfs (auth=none)"
Endpoint (other)=$GITSERVER/env-other-remote.git/info/lfs (auth=none) localwd=$(native_path "$TRASHDIR/$reponame")
LocalWorkingDir=$TRASHDIR/$reponame localgit=$(native_path "$TRASHDIR/$reponame/.git")
LocalGitDir=$TRASHDIR/$reponame/.git localgitstore=$(native_path "$TRASHDIR/$reponame/.git")
LocalGitStorageDir=$TRASHDIR/$reponame/.git localmedia=$(native_path "$TRASHDIR/$reponame/.git/lfs/objects")
LocalMediaDir=$TRASHDIR/$reponame/.git/lfs/objects tempdir=$(native_path "$TRASHDIR/$reponame/.git/lfs/tmp")
TempDir=$TRASHDIR/$reponame/.git/lfs/tmp envVars=$(printf "%s" "$(env | grep "^GIT")")
expected=$(printf '%s
%s
Endpoint=%s
Endpoint (other)=%s
LocalWorkingDir=%s
LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
BatchTransfer=true BatchTransfer=true
$(env | grep "^GIT")
%s %s
" "$(git lfs version)" "$(git version)" "$envInitConfig") %s
' "$(git lfs version)" "$(git version)" "$endpoint" "$endpoint2" "$localwd" "$localgit" "$localgitstore" "$localmedia" "$tempdir" "$envVars" "$envInitConfig")
actual=$(git lfs env) actual=$(git lfs env)
[ "$expected" = "$actual" ] contains_same_elements "$expected" "$actual"
cd .git cd .git
expected2=$(echo "$expected" | sed -e 's/LocalWorkingDir=.*/LocalWorkingDir=/') expected2=$(echo "$expected" | sed -e 's/LocalWorkingDir=.*/LocalWorkingDir=/')
actual2=$(git lfs env) actual2=$(git lfs env)
[ "$expected2" = "$actual2" ] contains_same_elements "$expected2" "$actual2"
) )
end_test end_test
@ -102,25 +131,35 @@ begin_test "env with other remote"
git init git init
git remote add other "$GITSERVER/env-other-remote" git remote add other "$GITSERVER/env-other-remote"
expected=$(printf "%s\n%s\n endpoint="$GITSERVER/env-other-remote.git/info/lfs (auth=none)"
Endpoint (other)=$GITSERVER/env-other-remote.git/info/lfs (auth=none) localwd=$(native_path "$TRASHDIR/$reponame")
LocalWorkingDir=$TRASHDIR/$reponame localgit=$(native_path "$TRASHDIR/$reponame/.git")
LocalGitDir=$TRASHDIR/$reponame/.git localgitstore=$(native_path "$TRASHDIR/$reponame/.git")
LocalGitStorageDir=$TRASHDIR/$reponame/.git localmedia=$(native_path "$TRASHDIR/$reponame/.git/lfs/objects")
LocalMediaDir=$TRASHDIR/$reponame/.git/lfs/objects tempdir=$(native_path "$TRASHDIR/$reponame/.git/lfs/tmp")
TempDir=$TRASHDIR/$reponame/.git/lfs/tmp envVars=$(printf "%s" "$(env | grep "^GIT")")
expected=$(printf '%s
%s
Endpoint (other)=%s
LocalWorkingDir=%s
LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
BatchTransfer=true BatchTransfer=true
$(env | grep "^GIT")
%s %s
" "$(git lfs version)" "$(git version)" "$envInitConfig") %s
' "$(git lfs version)" "$(git version)" "$endpoint" "$localwd" "$localgit" "$localgitstore" "$localmedia" "$tempdir" "$envVars" "$envInitConfig")
actual=$(git lfs env) actual=$(git lfs env)
[ "$expected" = "$actual" ] contains_same_elements "$expected" "$actual"
cd .git cd .git
expected2=$(echo "$expected" | sed -e 's/LocalWorkingDir=.*/LocalWorkingDir=/') expected2=$(echo "$expected" | sed -e 's/LocalWorkingDir=.*/LocalWorkingDir=/')
actual2=$(git lfs env) actual2=$(git lfs env)
[ "$expected2" = "$actual2" ] contains_same_elements "$expected2" "$actual2"
) )
end_test end_test
@ -135,26 +174,35 @@ begin_test "env with multiple remotes and lfs.url config"
git remote add other "$GITSERVER/env-other-remote" git remote add other "$GITSERVER/env-other-remote"
git config lfs.url "http://foo/bar" git config lfs.url "http://foo/bar"
expected=$(printf "%s\n%s\n endpoint="$GITSERVER/env-other-remote.git/info/lfs (auth=none)"
localwd=$(native_path "$TRASHDIR/$reponame")
localgit=$(native_path "$TRASHDIR/$reponame/.git")
localgitstore=$(native_path "$TRASHDIR/$reponame/.git")
localmedia=$(native_path "$TRASHDIR/$reponame/.git/lfs/objects")
tempdir=$(native_path "$TRASHDIR/$reponame/.git/lfs/tmp")
envVars=$(printf "%s" "$(env | grep "^GIT")")
expected=$(printf '%s
%s
Endpoint=http://foo/bar (auth=none) Endpoint=http://foo/bar (auth=none)
Endpoint (other)=$GITSERVER/env-other-remote.git/info/lfs (auth=none) Endpoint (other)=%s
LocalWorkingDir=$TRASHDIR/$reponame LocalWorkingDir=%s
LocalGitDir=$TRASHDIR/$reponame/.git LocalGitDir=%s
LocalGitStorageDir=$TRASHDIR/$reponame/.git LocalGitStorageDir=%s
LocalMediaDir=$TRASHDIR/$reponame/.git/lfs/objects LocalMediaDir=%s
TempDir=$TRASHDIR/$reponame/.git/lfs/tmp TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
BatchTransfer=true BatchTransfer=true
$(env | grep "^GIT")
%s %s
" "$(git lfs version)" "$(git version)" "$envInitConfig") %s
' "$(git lfs version)" "$(git version)" "$endpoint" "$localwd" "$localgit" "$localgitstore" "$localmedia" "$tempdir" "$envVars" "$envInitConfig")
actual=$(git lfs env) actual=$(git lfs env)
[ "$expected" = "$actual" ] contains_same_elements "$expected" "$actual"
cd .git cd .git
expected2=$(echo "$expected" | sed -e 's/LocalWorkingDir=.*/LocalWorkingDir=/') expected2=$(echo "$expected" | sed -e 's/LocalWorkingDir=.*/LocalWorkingDir=/')
actual2=$(git lfs env) actual2=$(git lfs env)
[ "$expected2" = "$actual2" ] contains_same_elements "$expected2" "$actual2"
) )
end_test end_test
@ -171,26 +219,34 @@ begin_test "env with multiple remotes and lfs configs"
git config remote.origin.lfsurl "http://custom/origin" git config remote.origin.lfsurl "http://custom/origin"
git config remote.other.lfsurl "http://custom/other" git config remote.other.lfsurl "http://custom/other"
expected=$(printf "%s\n%s\n localwd=$(native_path "$TRASHDIR/$reponame")
localgit=$(native_path "$TRASHDIR/$reponame/.git")
localgitstore=$(native_path "$TRASHDIR/$reponame/.git")
localmedia=$(native_path "$TRASHDIR/$reponame/.git/lfs/objects")
tempdir=$(native_path "$TRASHDIR/$reponame/.git/lfs/tmp")
envVars=$(printf "%s" "$(env | grep "^GIT")")
expected=$(printf '%s
%s
Endpoint=http://foo/bar (auth=none) Endpoint=http://foo/bar (auth=none)
Endpoint (other)=http://custom/other (auth=none) Endpoint (other)=http://custom/other (auth=none)
LocalWorkingDir=$TRASHDIR/$reponame LocalWorkingDir=%s
LocalGitDir=$TRASHDIR/$reponame/.git LocalGitDir=%s
LocalGitStorageDir=$TRASHDIR/$reponame/.git LocalGitStorageDir=%s
LocalMediaDir=$TRASHDIR/$reponame/.git/lfs/objects LocalMediaDir=%s
TempDir=$TRASHDIR/$reponame/.git/lfs/tmp TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
BatchTransfer=true BatchTransfer=true
$(env | grep "^GIT")
%s %s
" "$(git lfs version)" "$(git version)" "$envInitConfig") %s
' "$(git lfs version)" "$(git version)" "$localwd" "$localgit" "$localgitstore" "$localmedia" "$tempdir" "$envVars" "$envInitConfig")
actual=$(git lfs env) actual=$(git lfs env)
[ "$expected" = "$actual" ] contains_same_elements "$expected" "$actual"
cd .git cd .git
expected2=$(echo "$expected" | sed -e 's/LocalWorkingDir=.*/LocalWorkingDir=/') expected2=$(echo "$expected" | sed -e 's/LocalWorkingDir=.*/LocalWorkingDir=/')
actual2=$(git lfs env) actual2=$(git lfs env)
[ "$expected2" = "$actual2" ] contains_same_elements "$expected2" "$actual2"
) )
end_test end_test
@ -209,26 +265,34 @@ begin_test "env with multiple remotes and lfs url and batch configs"
git config remote.origin.lfsurl "http://custom/origin" git config remote.origin.lfsurl "http://custom/origin"
git config remote.other.lfsurl "http://custom/other" git config remote.other.lfsurl "http://custom/other"
expected=$(printf "%s\n%s\n localwd=$(native_path "$TRASHDIR/$reponame")
localgit=$(native_path "$TRASHDIR/$reponame/.git")
localgitstore=$(native_path "$TRASHDIR/$reponame/.git")
localmedia=$(native_path "$TRASHDIR/$reponame/.git/lfs/objects")
tempdir=$(native_path "$TRASHDIR/$reponame/.git/lfs/tmp")
envVars=$(printf "%s" "$(env | grep "^GIT")")
expected=$(printf '%s
%s
Endpoint=http://foo/bar (auth=none) Endpoint=http://foo/bar (auth=none)
Endpoint (other)=http://custom/other (auth=none) Endpoint (other)=http://custom/other (auth=none)
LocalWorkingDir=$TRASHDIR/$reponame LocalWorkingDir=%s
LocalGitDir=$TRASHDIR/$reponame/.git LocalGitDir=%s
LocalGitStorageDir=$TRASHDIR/$reponame/.git LocalGitStorageDir=%s
LocalMediaDir=$TRASHDIR/$reponame/.git/lfs/objects LocalMediaDir=%s
TempDir=$TRASHDIR/$reponame/.git/lfs/tmp TempDir=%s
ConcurrentTransfers=5 ConcurrentTransfers=5
BatchTransfer=false BatchTransfer=false
$(env | grep "^GIT")
%s %s
" "$(git lfs version)" "$(git version)" "$envInitConfig") %s
' "$(git lfs version)" "$(git version)" "$localwd" "$localgit" "$localgitstore" "$localmedia" "$tempdir" "$envVars" "$envInitConfig")
actual=$(git lfs env) actual=$(git lfs env)
[ "$expected" = "$actual" ] contains_same_elements "$expected" "$actual"
cd .git cd .git
expected2=$(echo "$expected" | sed -e 's/LocalWorkingDir=.*/LocalWorkingDir=/') expected2=$(echo "$expected" | sed -e 's/LocalWorkingDir=.*/LocalWorkingDir=/')
actual2=$(git lfs env) actual2=$(git lfs env)
[ "$expected2" = "$actual2" ] contains_same_elements "$expected2" "$actual2"
) )
end_test end_test
@ -248,26 +312,33 @@ begin_test "env with .gitconfig"
concurrenttransfers = 5 concurrenttransfers = 5
' > .gitconfig ' > .gitconfig
expected=$(printf "%s\n%s\n localwd=$(native_path "$TRASHDIR/$reponame")
localgit=$(native_path "$TRASHDIR/$reponame/.git")
localgitstore=$(native_path "$TRASHDIR/$reponame/.git")
localmedia=$(native_path "$TRASHDIR/$reponame/.git/lfs/objects")
tempdir=$(native_path "$TRASHDIR/$reponame/.git/lfs/tmp")
envVars=$(printf "%s" "$(env | grep "^GIT")")
expected=$(printf '%s
%s
Endpoint=http://foobar:8080/ (auth=none) Endpoint=http://foobar:8080/ (auth=none)
LocalWorkingDir=$TRASHDIR/$reponame LocalWorkingDir=%s
LocalGitDir=$TRASHDIR/$reponame/.git LocalGitDir=%s
LocalGitStorageDir=$TRASHDIR/$reponame/.git LocalGitStorageDir=%s
LocalMediaDir=$TRASHDIR/$reponame/.git/lfs/objects LocalMediaDir=%s
TempDir=$TRASHDIR/$reponame/.git/lfs/tmp TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
BatchTransfer=true BatchTransfer=true
$(env | grep "^GIT")
%s %s
" "$(git lfs version)" "$(git version)" "$envInitConfig") %s
' "$(git lfs version)" "$(git version)" "$localwd" "$localgit" "$localgitstore" "$localmedia" "$tempdir" "$envVars" "$envInitConfig")
actual=$(git lfs env) actual=$(git lfs env)
[ "$expected" = "$actual" ] contains_same_elements "$expected" "$actual"
mkdir a mkdir a
cd a cd a
actual2=$(git lfs env) actual2=$(git lfs env)
[ "$expected" = "$actual2" ] contains_same_elements "$expected" "$actual2"
) )
end_test end_test
@ -278,80 +349,97 @@ begin_test "env with environment variables"
git init $reponame git init $reponame
mkdir -p $reponame/a/b/c mkdir -p $reponame/a/b/c
gitDir=$TRASHDIR/$reponame/.git gitDir=$(native_path "$TRASHDIR/$reponame/.git")
workTree=$TRASHDIR/$reponame/a/b workTree=$(native_path "$TRASHDIR/$reponame/a/b")
expected=$(printf "%s\n%s\n localwd=$(native_path "$TRASHDIR/$reponame/a/b")
LocalWorkingDir=$TRASHDIR/$reponame/a/b localgit=$(native_path "$TRASHDIR/$reponame/.git")
LocalGitDir=$TRASHDIR/$reponame/.git localgitstore=$(native_path "$TRASHDIR/$reponame/.git")
LocalGitStorageDir=$TRASHDIR/$reponame/.git localmedia=$(native_path "$TRASHDIR/$reponame/.git/lfs/objects")
LocalMediaDir=$TRASHDIR/$reponame/.git/lfs/objects tempdir=$(native_path "$TRASHDIR/$reponame/.git/lfs/tmp")
TempDir=$TRASHDIR/$reponame/.git/lfs/tmp envVars="$(GIT_DIR=$gitDir GIT_WORK_TREE=$workTree env | grep "^GIT" | sort)"
expected=$(printf '%s
%s
LocalWorkingDir=%s
LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
BatchTransfer=true BatchTransfer=true
$(GIT_DIR=$gitDir GIT_WORK_TREE=$workTree env | grep "^GIT")
%s %s
" "$(git lfs version)" "$(git version)" "$envInitConfig") %s
' "$(git lfs version)" "$(git version)" "$localwd" "$localgit" "$localgitstore" "$localmedia" "$tempdir" "$envVars" "$envInitConfig")
actual=$(GIT_DIR=$gitDir GIT_WORK_TREE=$workTree git lfs env) actual=$(GIT_DIR=$gitDir GIT_WORK_TREE=$workTree git lfs env)
[ "$expected" = "$actual" ] contains_same_elements "$expected" "$actual"
cd $TRASHDIR/$reponame cd $TRASHDIR/$reponame
actual2=$(GIT_DIR=$gitDir GIT_WORK_TREE=$workTree git lfs env) actual2=$(GIT_DIR=$gitDir GIT_WORK_TREE=$workTree git lfs env)
[ "$expected" = "$actual2" ] contains_same_elements "$expected" "$actual2"
cd $TRASHDIR/$reponame/.git cd $TRASHDIR/$reponame/.git
actual3=$(GIT_DIR=$gitDir GIT_WORK_TREE=$workTree git lfs env) actual3=$(GIT_DIR=$gitDir GIT_WORK_TREE=$workTree git lfs env)
[ "$expected" = "$actual3" ] contains_same_elements "$expected" "$actual3"
cd $TRASHDIR/$reponame/a/b/c cd $TRASHDIR/$reponame/a/b/c
actual4=$(GIT_DIR=$gitDir GIT_WORK_TREE=$workTree git lfs env) actual4=$(GIT_DIR=$gitDir GIT_WORK_TREE=$workTree git lfs env)
[ "$expected" = "$actual4" ] contains_same_elements "$expected" "$actual4"
expected5=$(printf "%s\n%s\n envVars="$(GIT_DIR=$gitDir GIT_WORK_TREE=a/b env | grep "^GIT" | sort)"
LocalWorkingDir=$TRASHDIR/$reponame/a/b expected5=$(printf '%s
LocalGitDir=$TRASHDIR/$reponame/.git %s
LocalGitStorageDir=$TRASHDIR/$reponame/.git
LocalMediaDir=$TRASHDIR/$reponame/.git/lfs/objects LocalWorkingDir=%s
TempDir=$TRASHDIR/$reponame/.git/lfs/tmp LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
BatchTransfer=true BatchTransfer=true
$(GIT_DIR=$gitDir GIT_WORK_TREE=a/b env | grep "^GIT") %s
git config filter.lfs.smudge = \"\" git config filter.lfs.smudge = \"\"
git config filter.lfs.clean = \"\" git config filter.lfs.clean = \"\"
" "$(git lfs version)" "$(git version)") ' "$(git lfs version)" "$(git version)" "$localwd" "$localgit" "$localgitstore" "$localmedia" "$tempdir" "$envVars")
actual5=$(GIT_DIR=$gitDir GIT_WORK_TREE=a/b git lfs env) actual5=$(GIT_DIR=$gitDir GIT_WORK_TREE=a/b git lfs env)
[ "$expected5" = "$actual5" ] contains_same_elements "$expected5" "$actual5"
cd $TRASHDIR/$reponame/a/b cd $TRASHDIR/$reponame/a/b
expected7=$(printf "%s\n%s\n envVars="$(GIT_DIR=$gitDir env | grep "^GIT" | sort)"
LocalWorkingDir=$TRASHDIR/$reponame/a/b expected7=$(printf '%s
LocalGitDir=$TRASHDIR/$reponame/.git %s
LocalGitStorageDir=$TRASHDIR/$reponame/.git
LocalMediaDir=$TRASHDIR/$reponame/.git/lfs/objects LocalWorkingDir=%s
TempDir=$TRASHDIR/$reponame/.git/lfs/tmp LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
BatchTransfer=true BatchTransfer=true
$(GIT_DIR=$gitDir env | grep "^GIT")
%s %s
" "$(git lfs version)" "$(git version)" "$envInitConfig") %s
' "$(git lfs version)" "$(git version)" "$localwd" "$localgit" "$localgitstore" "$localmedia" "$tempdir" "$envVars" "$envInitConfig")
actual7=$(GIT_DIR=$gitDir git lfs env) actual7=$(GIT_DIR=$gitDir git lfs env)
[ "$expected7" = "$actual7" ] contains_same_elements "$expected7" "$actual7"
cd $TRASHDIR/$reponame/a cd $TRASHDIR/$reponame/a
expected8=$(printf "%s\n%s\n envVars="$(GIT_WORK_TREE=$workTree env | grep "^GIT" | sort)"
LocalWorkingDir=$TRASHDIR/$reponame/a/b expected8=$(printf '%s
LocalGitDir=$TRASHDIR/$reponame/.git %s
LocalGitStorageDir=$TRASHDIR/$reponame/.git
LocalMediaDir=$TRASHDIR/$reponame/.git/lfs/objects LocalWorkingDir=%s
TempDir=$TRASHDIR/$reponame/.git/lfs/tmp LocalGitDir=%s
LocalGitStorageDir=%s
LocalMediaDir=%s
TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
BatchTransfer=true BatchTransfer=true
$(GIT_WORK_TREE=$workTree env | grep "^GIT")
%s %s
" "$(git lfs version)" "$(git version)" "$envInitConfig") %s
' "$(git lfs version)" "$(git version)" "$localwd" "$localgit" "$localgitstore" "$localmedia" "$tempdir" "$envVars" "$envInitConfig")
actual8=$(GIT_WORK_TREE=$workTree git lfs env) actual8=$(GIT_WORK_TREE=$workTree git lfs env)
[ "$expected8" = "$actual8" ] contains_same_elements "$expected8" "$actual8"
) )
end_test end_test
@ -363,19 +451,25 @@ begin_test "env with bare repo"
git init --bare $reponame git init --bare $reponame
cd $reponame cd $reponame
localgit=$(native_path "$TRASHDIR/$reponame")
localgitstore=$(native_path "$TRASHDIR/$reponame")
localmedia=$(native_path "$TRASHDIR/$reponame/lfs/objects")
tempdir=$(native_path "$TRASHDIR/$reponame/lfs/tmp")
envVars=$(printf "%s" "$(env | grep "^GIT")")
expected=$(printf "%s\n%s\n expected=$(printf "%s\n%s\n
LocalWorkingDir= LocalWorkingDir=
LocalGitDir=$TRASHDIR/$reponame LocalGitDir=%s
LocalGitStorageDir=$TRASHDIR/$reponame LocalGitStorageDir=%s
LocalMediaDir=$TRASHDIR/$reponame/lfs/objects LocalMediaDir=%s
TempDir=$TRASHDIR/$reponame/lfs/tmp TempDir=%s
ConcurrentTransfers=3 ConcurrentTransfers=3
BatchTransfer=true BatchTransfer=true
$(env | grep "^GIT")
%s %s
" "$(git lfs version)" "$(git version)" "$envInitConfig") %s
" "$(git lfs version)" "$(git version)" "$localgit" "$localgitstore" "$localmedia" "$tempdir" "$envVars" "$envInitConfig")
actual=$(git lfs env) actual=$(git lfs env)
[ "$expected" = "$actual" ] contains_same_elements "$expected" "$actual"
) )
end_test end_test

@ -22,7 +22,7 @@ begin_test "fsck default"
aOid=$(git log --patch a.dat | grep "^+oid" | cut -d ":" -f 2) aOid=$(git log --patch a.dat | grep "^+oid" | cut -d ":" -f 2)
aOid12=$(echo $aOid | cut -b 1-2) aOid12=$(echo $aOid | cut -b 1-2)
aOid34=$(echo $aOid | cut -b 3-4) aOid34=$(echo $aOid | cut -b 3-4)
if [ "$aOid .git/lfs/objects/$aOid12/$aOid34/$aOid" != "$(shasum -a 256 .git/lfs/objects/$aOid12/$aOid34/$aOid)" ]; then if [ "$aOid" != "$(shasum -a 256 .git/lfs/objects/$aOid12/$aOid34/$aOid | cut -d " " -f 1)" ]; then
echo "oid for a.dat does not match" echo "oid for a.dat does not match"
exit 1 exit 1
fi fi
@ -30,7 +30,7 @@ begin_test "fsck default"
bOid=$(git log --patch b.dat | grep "^+oid" | cut -d ":" -f 2) bOid=$(git log --patch b.dat | grep "^+oid" | cut -d ":" -f 2)
bOid12=$(echo $bOid | cut -b 1-2) bOid12=$(echo $bOid | cut -b 1-2)
bOid34=$(echo $bOid | cut -b 3-4) bOid34=$(echo $bOid | cut -b 3-4)
if [ "$bOid ".git/lfs/objects/$bOid12/$bOid34/$bOid != "$(shasum -a 256 .git/lfs/objects/$bOid12/$bOid34/$bOid)" ]; then if [ "$bOid" != "$(shasum -a 256 .git/lfs/objects/$bOid12/$bOid34/$bOid | cut -d " " -f 1)" ]; then
echo "oid for b.dat does not match" echo "oid for b.dat does not match"
exit 1 exit 1
fi fi
@ -38,8 +38,9 @@ begin_test "fsck default"
echo "CORRUPTION" >> .git/lfs/objects/$aOid12/$aOid34/$aOid echo "CORRUPTION" >> .git/lfs/objects/$aOid12/$aOid34/$aOid
expected="Object a.dat ($aOid) is corrupt moved=$(native_path "$TRASHDIR/$reponame/.git/lfs/bad/$aOid")
moved to $TRASHDIR/$reponame/.git/lfs/bad/$aOid" expected="$(printf 'Object a.dat (%s) is corrupt
moved to %s' "$aOid" "$moved")"
[ "$expected" = "$(git lfs fsck)" ] [ "$expected" = "$(git lfs fsck)" ]
if [ -e .git/lfs/objects/$aOid12/$aOid34/$aOid ]; then if [ -e .git/lfs/objects/$aOid12/$aOid34/$aOid ]; then
@ -47,7 +48,7 @@ begin_test "fsck default"
exit 1 exit 1
fi fi
if [ "$bOid ".git/lfs/objects/$bOid12/$bOid34/$bOid != "$(shasum -a 256 .git/lfs/objects/$bOid12/$bOid34/$bOid)" ]; then if [ "$bOid" != "$(shasum -a 256 .git/lfs/objects/$bOid12/$bOid34/$bOid | cut -d " " -f 1)" ]; then
echo "oid for b.dat does not match" echo "oid for b.dat does not match"
exit 1 exit 1
fi fi
@ -74,7 +75,7 @@ begin_test "fsck dry run"
aOid=$(git log --patch a.dat | grep "^+oid" | cut -d ":" -f 2) aOid=$(git log --patch a.dat | grep "^+oid" | cut -d ":" -f 2)
aOid12=$(echo $aOid | cut -b 1-2) aOid12=$(echo $aOid | cut -b 1-2)
aOid34=$(echo $aOid | cut -b 3-4) aOid34=$(echo $aOid | cut -b 3-4)
if [ "$aOid .git/lfs/objects/$aOid12/$aOid34/$aOid" != "$(shasum -a 256 .git/lfs/objects/$aOid12/$aOid34/$aOid)" ]; then if [ "$aOid" != "$(shasum -a 256 .git/lfs/objects/$aOid12/$aOid34/$aOid | cut -d " " -f 1)" ]; then
echo "oid for a.dat does not match" echo "oid for a.dat does not match"
exit 1 exit 1
fi fi
@ -82,7 +83,7 @@ begin_test "fsck dry run"
bOid=$(git log --patch b.dat | grep "^+oid" | cut -d ":" -f 2) bOid=$(git log --patch b.dat | grep "^+oid" | cut -d ":" -f 2)
bOid12=$(echo $bOid | cut -b 1-2) bOid12=$(echo $bOid | cut -b 1-2)
bOid34=$(echo $bOid | cut -b 3-4) bOid34=$(echo $bOid | cut -b 3-4)
if [ "$bOid ".git/lfs/objects/$bOid12/$bOid34/$bOid != "$(shasum -a 256 .git/lfs/objects/$bOid12/$bOid34/$bOid)" ]; then if [ "$bOid" != "$(shasum -a 256 .git/lfs/objects/$bOid12/$bOid34/$bOid | cut -d " " -f 1)" ]; then
echo "oid for b.dat does not match" echo "oid for b.dat does not match"
exit 1 exit 1
fi fi
@ -91,12 +92,12 @@ begin_test "fsck dry run"
[ "Object a.dat ($aOid) is corrupt" = "$(git lfs fsck --dry-run)" ] [ "Object a.dat ($aOid) is corrupt" = "$(git lfs fsck --dry-run)" ]
if [ "$aOid .git/lfs/objects/$aOid12/$aOid34/$aOid" = "$(shasum -a 256 .git/lfs/objects/$aOid12/$aOid34/$aOid)" ]; then if [ "$aOid" = "$(shasum -a 256 .git/lfs/objects/$aOid12/$aOid34/$aOid | cut -d " " -f 1)" ]; then
echo "oid for a.dat still matches match" echo "oid for a.dat still matches match"
exit 1 exit 1
fi fi
if [ "$bOid ".git/lfs/objects/$bOid12/$bOid34/$bOid != "$(shasum -a 256 .git/lfs/objects/$bOid12/$bOid34/$bOid)" ]; then if [ "$bOid" != "$(shasum -a 256 .git/lfs/objects/$bOid12/$bOid34/$bOid | cut -d " " -f 1)" ]; then
echo "oid for b.dat does not match" echo "oid for b.dat does not match"
exit 1 exit 1
fi fi

@ -4,6 +4,41 @@
. "test/testlib.sh" . "test/testlib.sh"
begin_test "clears local temp objects"
(
set -e
mkdir repo-temp-objects
cd repo-temp-objects
git init
# abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01
mkdir -p .git/lfs/objects/go/od
mkdir -p .git/lfs/tmp/objects
touch .git/lfs/objects/go/od/goodabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwx
touch .git/lfs/tmp/objects/goodabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwx-rand123
touch .git/lfs/tmp/objects/goodabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwx-rand456
touch .git/lfs/tmp/objects/badabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxy-rand123
touch .git/lfs/tmp/objects/badabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxy-rand456
GIT_TRACE=5 git lfs env
# object file exists
[ -e ".git/lfs/objects/go/od/goodabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwx" ]
# newer tmp files exist
[ -e ".git/lfs/tmp/objects/badabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxy-rand123" ]
[ -e ".git/lfs/tmp/objects/badabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxy-rand456" ]
# existing tmp files were cleaned up
[ ! -e ".git/lfs/tmp/objects/goodabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwx-rand123" ]
[ ! -e ".git/lfs/tmp/objects/goodabcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwx-rand456" ]
)
end_test
exit 0
begin_test "happy path" begin_test "happy path"
( (
set -e set -e

@ -161,6 +161,12 @@ end_test
begin_test "init --local outside repository" begin_test "init --local outside repository"
( (
# If run inside the git-lfs source dir this will update its .git/config & cause issues
if [ "$GIT_LFS_TEST_DIR" == "" ]; then
echo "Skipping init --local because GIT_LFS_TEST_DIR is not set"
exit 0
fi
set +e set +e
[ -n "$LFS_DOCKER" ] && exit 0 [ -n "$LFS_DOCKER" ] && exit 0

@ -93,7 +93,13 @@ end_test
begin_test "pointer --stdin without stdin" begin_test "pointer --stdin without stdin"
( (
output=$(git lfs pointer --stdin 2>&1) # this test doesn't work on Windows, it just operates like 'bad pointer' case
# stdin isn't detectable as detached, it just times out with no content
if [[ "$(is_stdin_attached)" == "0" ]]; then
echo "Skipping pointer without stdin because STDIN attached"
exit 0
fi
output=$(echo "" | git lfs pointer --stdin 2>&1)
status=$? status=$?
set -e set -e
@ -212,7 +218,7 @@ begin_test "pointer missing --pointer"
set -e set -e
[ "1" = "$status" ] [ "1" = "$status" ]
[ "open missing-pointer: no such file or directory" = "$output" ] [[ "$output" == open missing-pointer:* ]]
) )
end_test end_test

@ -38,6 +38,24 @@ begin_test "smudge --info"
) )
end_test end_test
begin_test "smudge with temp file"
(
set -e
cd repo
rm -rf .git/lfs/objects
mkdir -p .git/lfs/tmp/objects
touch .git/lfs/tmp/objects/fcf5015df7a9089a7aa7fe74139d4b8f7d62e52d5a34f9a87aeffc8e8c668254-1
pointer fcf5015df7a9089a7aa7fe74139d4b8f7d62e52d5a34f9a87aeffc8e8c668254 9 | GIT_TRACE=5 git lfs smudge | tee smudge.log
[ "smudge a" = "$(cat smudge.log)" ] || {
rm -rf .git/lfs/tmp
git lfs logs last
exit 1
}
)
end_test
begin_test "smudge with invalid pointer" begin_test "smudge with invalid pointer"
( (
set -e set -e

@ -41,43 +41,44 @@ begin_test "submodule env"
git lfs env | tee env.log git lfs env | tee env.log
grep "Endpoint=$GITSERVER/$reponame.git/info/lfs (auth=none)$" env.log grep "Endpoint=$GITSERVER/$reponame.git/info/lfs (auth=none)$" env.log
grep "LocalWorkingDir=$TRASHDIR/repo$" env.log grep "LocalWorkingDir=$(native_path_escaped "$TRASHDIR/repo$")" env.log
grep "LocalGitDir=$TRASHDIR/repo/.git$" env.log grep "LocalGitDir=$(native_path_escaped "$TRASHDIR/repo/.git$")" env.log
grep "LocalGitStorageDir=$TRASHDIR/repo/.git$" env.log grep "LocalGitStorageDir=$(native_path_escaped "$TRASHDIR/repo/.git$")" env.log
grep "LocalMediaDir=$TRASHDIR/repo/.git/lfs/objects$" env.log grep "LocalMediaDir=$(native_path_escaped "$TRASHDIR/repo/.git/lfs/objects$")" env.log
grep "TempDir=$TRASHDIR/repo/.git/lfs/tmp$" env.log grep "TempDir=$(native_path_escaped "$TRASHDIR/repo/.git/lfs/tmp$")" env.log
cd .git cd .git
echo "./.git" echo "./.git"
git lfs env | tee env.log git lfs env | tee env.log
cat env.log
grep "Endpoint=$GITSERVER/$reponame.git/info/lfs (auth=none)$" env.log grep "Endpoint=$GITSERVER/$reponame.git/info/lfs (auth=none)$" env.log
grep "LocalWorkingDir=$" env.log grep "LocalWorkingDir=$" env.log
grep "LocalGitDir=$TRASHDIR/repo/.git$" env.log grep "LocalGitDir=$(native_path_escaped "$TRASHDIR/repo/.git$")" env.log
grep "LocalGitStorageDir=$TRASHDIR/repo/.git$" env.log grep "LocalGitStorageDir=$(native_path_escaped "$TRASHDIR/repo/.git$")" env.log
grep "LocalMediaDir=$TRASHDIR/repo/.git/lfs/objects$" env.log grep "LocalMediaDir=$(native_path_escaped "$TRASHDIR/repo/.git/lfs/objects$")" env.log
grep "TempDir=$TRASHDIR/repo/.git/lfs/tmp$" env.log grep "TempDir=$(native_path_escaped "$TRASHDIR/repo/.git/lfs/tmp$")" env.log
cd ../sub cd ../sub
echo "./sub" echo "./sub"
git lfs env | tee env.log git lfs env | tee env.log
grep "Endpoint=$GITSERVER/$submodname.git/info/lfs (auth=none)$" env.log grep "Endpoint=$GITSERVER/$submodname.git/info/lfs (auth=none)$" env.log
grep "LocalWorkingDir=$TRASHDIR/repo/sub$" env.log grep "LocalWorkingDir=$(native_path_escaped "$TRASHDIR/repo/sub$")" env.log
grep "LocalGitDir=$TRASHDIR/repo/.git/modules/sub$" env.log grep "LocalGitDir=$(native_path_escaped "$TRASHDIR/repo/.git/modules/sub$")" env.log
grep "LocalGitStorageDir=$TRASHDIR/repo/.git/modules/sub$" env.log grep "LocalGitStorageDir=$(native_path_escaped "$TRASHDIR/repo/.git/modules/sub$")" env.log
grep "LocalMediaDir=$TRASHDIR/repo/.git/modules/sub/lfs/objects$" env.log grep "LocalMediaDir=$(native_path_escaped "$TRASHDIR/repo/.git/modules/sub/lfs/objects$")" env.log
grep "TempDir=$TRASHDIR/repo/.git/modules/sub/lfs/tmp$" env.log grep "TempDir=$(native_path_escaped "$TRASHDIR/repo/.git/modules/sub/lfs/tmp$")" env.log
cd dir cd dir
echo "./sub/dir" echo "./sub/dir"
git lfs env | tee env.log git lfs env | tee env.log
grep "Endpoint=$GITSERVER/$submodname.git/info/lfs (auth=none)$" env.log grep "Endpoint=$GITSERVER/$submodname.git/info/lfs (auth=none)$" env.log
grep "LocalWorkingDir=$TRASHDIR/repo/sub$" env.log grep "LocalWorkingDir=$(native_path_escaped "$TRASHDIR/repo/sub$")" env.log
grep "LocalGitDir=$TRASHDIR/repo/.git/modules/sub$" env.log grep "LocalGitDir=$(native_path_escaped "$TRASHDIR/repo/.git/modules/sub$")" env.log
grep "LocalGitStorageDir=$TRASHDIR/repo/.git/modules/sub$" env.log grep "LocalGitStorageDir=$(native_path_escaped "$TRASHDIR/repo/.git/modules/sub$")" env.log
grep "LocalMediaDir=$TRASHDIR/repo/.git/modules/sub/lfs/objects$" env.log grep "LocalMediaDir=$(native_path_escaped "$TRASHDIR/repo/.git/modules/sub/lfs/objects$")" env.log
grep "TempDir=$TRASHDIR/repo/.git/modules/sub/lfs/tmp$" env.log grep "TempDir=$(native_path_escaped "$TRASHDIR/repo/.git/modules/sub/lfs/tmp$")" env.log
) )
end_test end_test

@ -38,10 +38,10 @@ begin_test "track"
out=$(git lfs track) out=$(git lfs track)
echo "$out" | grep "Listing tracked paths" echo "$out" | grep "Listing tracked paths"
echo "$out" | grep "*.mov (.git/info/attributes)" echo "$out" | grep "*.mov ($(native_path_escaped ".git/info/attributes"))"
echo "$out" | grep "*.jpg (.gitattributes)" echo "$out" | grep "*.jpg (.gitattributes)"
echo "$out" | grep "*.gif (a/.gitattributes)" echo "$out" | grep "*.gif ($(native_path_escaped "a/.gitattributes"))"
echo "$out" | grep "*.png (a/b/.gitattributes)" echo "$out" | grep "*.png ($(native_path_escaped "a/b/.gitattributes"))"
) )
end_test end_test
@ -134,9 +134,9 @@ begin_test "track representation"
cd track-representation cd track-representation
git lfs track "*.jpg" git lfs track "*.jpg"
out=$(git lfs track "$PWD/*.jpg") out=$(git lfs track "$(native_path "$PWD/")*.jpg")
if [ "$out" != "$PWD/*.jpg already supported" ]; then if [ "$out" != "$(native_path "$PWD/")*.jpg already supported" ]; then
echo "Track didn't recognize duplicate path" echo "Track didn't recognize duplicate path"
cat .gitattributes cat .gitattributes
exit 1 exit 1
@ -179,7 +179,7 @@ begin_test "track absolute"
git init track-absolute git init track-absolute
cd track-absolute cd track-absolute
git lfs track "$PWD/*.jpg" git lfs track "$(native_path "$PWD/")*.jpg"
grep "^*.jpg" .gitattributes || { grep "^*.jpg" .gitattributes || {
echo ".gitattributes doesn't contain the expected relative path *.jpg:" echo ".gitattributes doesn't contain the expected relative path *.jpg:"
cat .gitattributes cat .gitattributes

@ -20,18 +20,18 @@ begin_test "git worktree"
git commit -m "Initial commit" git commit -m "Initial commit"
expected=$(printf "%s\n%s\n expected=$(printf "%s\n%s\n
LocalWorkingDir=$TRASHDIR/$reponame LocalWorkingDir=$(native_path_escaped "$TRASHDIR/$reponame")
LocalGitDir=$TRASHDIR/$reponame/.git LocalGitDir=$(native_path_escaped "$TRASHDIR/$reponame/.git")
LocalGitStorageDir=$TRASHDIR/$reponame/.git LocalGitStorageDir=$(native_path_escaped "$TRASHDIR/$reponame/.git")
LocalMediaDir=$TRASHDIR/$reponame/.git/lfs/objects LocalMediaDir=$(native_path_escaped "$TRASHDIR/$reponame/.git/lfs/objects")
TempDir=$TRASHDIR/$reponame/.git/lfs/tmp TempDir=$(native_path_escaped "$TRASHDIR/$reponame/.git/lfs/tmp")
ConcurrentTransfers=3 ConcurrentTransfers=3
BatchTransfer=true BatchTransfer=true
$(env | grep "^GIT") $(escape_path "$(env | grep "^GIT")")
%s %s
" "$(git lfs version)" "$(git version)" "$envInitConfig") " "$(git lfs version)" "$(git version)" "$envInitConfig")
actual=$(git lfs env) actual=$(git lfs env)
[ "$expected" = "$actual" ] contains_same_elements "$expected" "$actual"
worktreename="worktree-2" worktreename="worktree-2"
git worktree add "$TRASHDIR/$worktreename" git worktree add "$TRASHDIR/$worktreename"
@ -41,17 +41,17 @@ $(env | grep "^GIT")
# is only for index, temp etc # is only for index, temp etc
# storage of git objects and lfs objects is in the original .git # storage of git objects and lfs objects is in the original .git
expected=$(printf "%s\n%s\n expected=$(printf "%s\n%s\n
LocalWorkingDir=$TRASHDIR/$worktreename LocalWorkingDir=$(native_path_escaped "$TRASHDIR/$worktreename")
LocalGitDir=$TRASHDIR/$reponame/.git/worktrees/$worktreename LocalGitDir=$(native_path_escaped "$TRASHDIR/$reponame/.git/worktrees/$worktreename")
LocalGitStorageDir=$TRASHDIR/$reponame/.git LocalGitStorageDir=$(native_path_escaped "$TRASHDIR/$reponame/.git")
LocalMediaDir=$TRASHDIR/$reponame/.git/lfs/objects LocalMediaDir=$(native_path_escaped "$TRASHDIR/$reponame/.git/lfs/objects")
TempDir=$TRASHDIR/$reponame/.git/worktrees/$worktreename/lfs/tmp TempDir=$(native_path_escaped "$TRASHDIR/$reponame/.git/worktrees/$worktreename/lfs/tmp")
ConcurrentTransfers=3 ConcurrentTransfers=3
BatchTransfer=true BatchTransfer=true
$(env | grep "^GIT") $(escape_path "$(env | grep "^GIT")")
%s %s
" "$(git lfs version)" "$(git version)" "$envInitConfig") " "$(git lfs version)" "$(git version)" "$envInitConfig")
actual=$(git lfs env) actual=$(git lfs env)
[ "$expected" = "$actual" ] contains_same_elements "$expected" "$actual"
) )
end_test end_test

@ -49,6 +49,13 @@ TESTHOME="$REMOTEDIR/home"
GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_NOSYSTEM=1
UNAME=$(uname -s)
IS_WINDOWS=0
if [[ $UNAME == MINGW* || $UNAME == CYGWIN* ]]
then
IS_WINDOWS=1
fi
export CREDSDIR export CREDSDIR
if [[ `git config --system credential.helper | grep osxkeychain` == "osxkeychain" ]] if [[ `git config --system credential.helper | grep osxkeychain` == "osxkeychain" ]]

@ -382,3 +382,51 @@ get_date() {
date $ARGS -u +%Y-%m-%dT%TZ date $ARGS -u +%Y-%m-%dT%TZ
fi fi
} }
# Convert potentially MinGW bash paths to native Windows paths
# Needed to match generic built paths in test scripts to native paths generated from Go
native_path() {
local arg=$1
if [ $IS_WINDOWS == "1" ]; then
# Use params form to avoid interpreting any '\' characters
printf '%s' "$(cygpath -w $arg)"
else
printf '%s' "$arg"
fi
}
# escape any instance of '\' with '\\' on Windows
escape_path() {
local unescaped="$1"
if [ $IS_WINDOWS == "1" ]; then
printf '%s' "${unescaped//\\/\\\\}"
else
printf '%s' "$unescaped"
fi
}
# As native_path but escape all backslash characters to "\\"
native_path_escaped() {
local unescaped=$(native_path "$1")
escape_path "$unescaped"
}
# Compare 2 lists which are newline-delimited in a string, ignoring ordering and blank lines
contains_same_elements() {
# Remove blank lines then sort
printf '%s' "$1" | grep -v '^$' | sort > a.txt
printf '%s' "$2" | grep -v '^$' | sort > b.txt
set +e
diff -u a.txt b.txt 1>&2
res=$?
set -e
rm a.txt b.txt
exit $res
}
is_stdin_attached() {
test -t0
echo $?
}

@ -75,11 +75,17 @@ begin_test () {
err="$TRASHDIR/err" err="$TRASHDIR/err"
trace="$TRASHDIR/trace" trace="$TRASHDIR/trace"
exec 1>"$out" 2>"$err" 5>"$trace" exec 1>"$out" 2>"$err"
# enabling GIT_TRACE can cause Windows git to stall, esp with fd 5
# other fd numbers like 8/9 don't stall but still don't work, so disable
if [ $IS_WINDOWS == "0" ]; then
exec 5>"$trace"
export GIT_TRACE=5
fi
# reset global git config # reset global git config
HOME="$TRASHDIR/home" HOME="$TRASHDIR/home"
export GIT_TRACE=5
rm -rf "$TRASHDIR/home" rm -rf "$TRASHDIR/home"
mkdir "$HOME" mkdir "$HOME"
cp "$TESTHOME/.gitconfig" "$HOME/.gitconfig" cp "$TESTHOME/.gitconfig" "$HOME/.gitconfig"
@ -97,6 +103,8 @@ end_test () {
test_status="${1:-$?}" test_status="${1:-$?}"
set +x -e set +x -e
exec 1>&3 2>&4 exec 1>&3 2>&4
# close fd 5 (GIT_TRACE)
exec 5>&-
if [ "$test_status" -eq 0 ]; then if [ "$test_status" -eq 0 ]; then
printf "test: %-60s OK\n" "$test_description ..." printf "test: %-60s OK\n" "$test_description ..."
@ -109,8 +117,10 @@ end_test () {
echo "-- stderr --" echo "-- stderr --"
grep -v -e '^\+ end_test' -e '^+ set +x' <"$TRASHDIR/err" | grep -v -e '^\+ end_test' -e '^+ set +x' <"$TRASHDIR/err" |
sed 's/^/ /' sed 's/^/ /'
echo "-- git trace --" if [ "$IS_WINDOWS" == "0" ]; then
sed 's/^/ /' <"$TRASHDIR/trace" echo "-- git trace --"
sed 's/^/ /' <"$TRASHDIR/trace"
fi
) 1>&2 ) 1>&2
echo echo
fi fi

@ -1,20 +0,0 @@
Copyright (c) 2014 rick olson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -1,49 +0,0 @@
# Content Addressable
Package contentaddressable contains tools for writing content addressable files.
Files are written to a temporary location, and only renamed to the final
location after the file's OID (Object ID) has been verified.
```go
filename := "path/to/01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"
file, err := contentaddressable.NewFile(filename)
if err != nil {
panic(err)
}
defer file.Close()
file.Oid // 01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b
written, err := io.Copy(file, someReader)
if err == nil {
// Move file to final location if OID is verified.
err = file.Accept()
}
if err != nil {
panic(err)
}
```
See the [godocs](http://godoc.org/github.com/technoweenie/go-contentaddressable)
for details.
## Installation
$ go get github.com/technoweenie/go-contentaddressable
Then import it:
import "github.com/technoweenie/go-contentaddressable"
## Note on Patches/Pull Requests
1. Fork the project on GitHub.
2. Make your feature addition or bug fix.
3. Add tests for it. This is important so I don't break it in a future version
unintentionally.
4. Commit, do not mess with version or history. (if you want to have
your own version, that is fine but bump version in a commit by itself I can
ignore when I pull)
5. Send me a pull request. Bonus points for topic branches.

@ -1,28 +0,0 @@
/*
Package contentaddressable contains tools for writing content addressable files.
Files are written to a temporary location, and only renamed to the final
location after the file's OID (Object ID) has been verified.
filename := "path/to/01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"
file, err := contentaddressable.NewFile(filename)
if err != nil {
panic(err)
}
defer file.Close()
file.Oid // 01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b
written, err := io.Copy(file, someReader)
if err == nil {
// Move file to final location if OID is verified.
err = file.Accept()
}
if err != nil {
panic(err)
}
Currently SHA-256 is used for a file's OID.
*/
package contentaddressable

@ -1,131 +0,0 @@
package contentaddressable
import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"hash"
"os"
"path/filepath"
)
var (
AlreadyClosed = errors.New("Already closed.")
HasData = errors.New("Destination file already has data.")
)
// File handles the atomic writing of a content addressable file. It writes to
// a temp file, and then renames to the final location after Accept().
type File struct {
Oid string
filename string
tempFilename string
file *os.File
tempFile *os.File
hasher hash.Hash
}
// NewFile initializes a content addressable file for writing. It opens both
// the given filename, and a temp filename in exclusive mode. The *File OID
// is taken from the base name of the given filename.
func NewFile(filename string) (*File, error) {
oid := filepath.Base(filename)
dir := filepath.Dir(filename)
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, err
}
tempFilename := filename + "-temp"
tempFile, err := os.OpenFile(tempFilename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
if err != nil {
return nil, err
}
file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
if err != nil {
cleanupFile(tempFile)
return nil, err
}
caw := &File{
Oid: oid,
filename: filename,
tempFilename: tempFilename,
file: file,
tempFile: tempFile,
hasher: sha256.New(),
}
return caw, nil
}
// Write sends data to the temporary file.
func (w *File) Write(p []byte) (int, error) {
if w.Closed() {
return 0, AlreadyClosed
}
w.hasher.Write(p)
return w.tempFile.Write(p)
}
// Accept verifies the written content SHA-256 signature matches the given OID.
// If it matches, the temp file is renamed to the original filename. If not,
// an error is returned.
func (w *File) Accept() error {
if w.Closed() {
return AlreadyClosed
}
sig := hex.EncodeToString(w.hasher.Sum(nil))
if sig != w.Oid {
return fmt.Errorf("Content mismatch. Expected OID %s, got %s", w.Oid, sig)
}
if err := cleanupFile(w.file); err != nil {
return err
}
w.file = nil
w.tempFile.Close()
err := os.Rename(w.tempFilename, w.filename)
w.Close()
return err
}
// Close cleans up the internal file objects.
func (w *File) Close() error {
if w.tempFile != nil {
if err := cleanupFile(w.tempFile); err != nil {
return err
}
w.tempFile = nil
}
if w.file != nil {
if err := cleanupFile(w.file); err != nil {
return err
}
w.file = nil
}
return nil
}
// Closed reports whether this file object has been closed.
func (w *File) Closed() bool {
if w.tempFile == nil || w.file == nil {
return true
}
return false
}
func cleanupFile(f *os.File) error {
err := f.Close()
if err := os.RemoveAll(f.Name()); err != nil {
return err
}
return err
}

@ -1,176 +0,0 @@
package contentaddressable
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"reflect"
"runtime"
)
var supOid = "a2b71d6ee8997eb87b25ab42d566c44f6a32871752c7c73eb5578cb1182f7be0"
func TestFile(t *testing.T) {
test := SetupFile(t)
defer test.Teardown()
filename := filepath.Join(test.Path, supOid)
aw, err := NewFile(filename)
assertEqual(t, nil, err)
n, err := aw.Write([]byte("SUP"))
assertEqual(t, nil, err)
assertEqual(t, 3, n)
by, err := ioutil.ReadFile(filename)
assertEqual(t, nil, err)
assertEqual(t, 0, len(by))
assertEqual(t, nil, aw.Accept())
by, err = ioutil.ReadFile(filename)
assertEqual(t, nil, err)
assertEqual(t, "SUP", string(by))
assertEqual(t, nil, aw.Close())
}
func TestFileMismatch(t *testing.T) {
test := SetupFile(t)
defer test.Teardown()
filename := filepath.Join(test.Path, "b2b71d6ee8997eb87b25ab42d566c44f6a32871752c7c73eb5578cb1182f7be0")
aw, err := NewFile(filename)
assertEqual(t, nil, err)
n, err := aw.Write([]byte("SUP"))
assertEqual(t, nil, err)
assertEqual(t, 3, n)
by, err := ioutil.ReadFile(filename)
assertEqual(t, nil, err)
assertEqual(t, 0, len(by))
err = aw.Accept()
if err == nil || !strings.Contains(err.Error(), "Content mismatch") {
t.Errorf("Expected mismatch error: %s", err)
}
by, err = ioutil.ReadFile(filename)
assertEqual(t, nil, err)
assertEqual(t, "", string(by))
assertEqual(t, nil, aw.Close())
_, err = ioutil.ReadFile(filename)
assertEqual(t, true, os.IsNotExist(err))
}
func TestFileCancel(t *testing.T) {
test := SetupFile(t)
defer test.Teardown()
filename := filepath.Join(test.Path, supOid)
aw, err := NewFile(filename)
assertEqual(t, nil, err)
n, err := aw.Write([]byte("SUP"))
assertEqual(t, nil, err)
assertEqual(t, 3, n)
assertEqual(t, nil, aw.Close())
for _, name := range []string{aw.filename, aw.tempFilename} {
if _, err := os.Stat(name); err == nil {
t.Errorf("%s exists?", name)
}
}
}
func TestFileLocks(t *testing.T) {
test := SetupFile(t)
defer test.Teardown()
filename := filepath.Join(test.Path, supOid)
aw, err := NewFile(filename)
assertEqual(t, nil, err)
assertEqual(t, filename, aw.filename)
assertEqual(t, filename+"-temp", aw.tempFilename)
files := []string{aw.filename, aw.tempFilename}
for _, name := range files {
if _, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0665); err == nil {
t.Errorf("Able to open %s!", name)
}
}
assertEqual(t, nil, aw.Close())
for _, name := range files {
f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0665)
assertEqualf(t, nil, err, "unable to open %s: %s", name, err)
cleanupFile(f)
}
}
func TestFileDuel(t *testing.T) {
test := SetupFile(t)
defer test.Teardown()
filename := filepath.Join(test.Path, supOid)
aw, err := NewFile(filename)
assertEqual(t, nil, err)
defer aw.Close()
if _, err := NewFile(filename); err == nil {
t.Errorf("Expected a file open conflict!")
}
}
func SetupFile(t *testing.T) *FileTest {
wd, err := os.Getwd()
if err != nil {
t.Fatalf("Error getting wd: %s", err)
}
return &FileTest{filepath.Join(wd, "File"), t}
}
type FileTest struct {
Path string
*testing.T
}
func (t *FileTest) Teardown() {
if err := os.RemoveAll(t.Path); err != nil {
t.Fatalf("Error removing %s: %s", t.Path, err)
}
}
func assertEqual(t *testing.T, expected, actual interface{}) {
checkAssertion(t, expected, actual, "")
}
func assertEqualf(t *testing.T, expected, actual interface{}, format string, args ...interface{}) {
checkAssertion(t, expected, actual, format, args...)
}
func checkAssertion(t *testing.T, expected, actual interface{}, format string, args ...interface{}) {
if expected == nil {
if actual == nil {
return
}
} else if reflect.DeepEqual(expected, actual) {
return
}
_, file, line, _ := runtime.Caller(2) // assertEqual + checkAssertion
t.Logf("%s:%d\nExpected: %v\nActual: %v", file, line, expected, actual)
if len(args) > 0 {
t.Logf("! - "+format, args...)
}
t.FailNow()
}