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/pflag" = "580b9be06c33d8ba9dcc8757ea56b7642472c2f5"
"github.com/technoweenie/assert" = "b25ea301d127043ffacf3b2545726e79b6632139"
"github.com/technoweenie/go-contentaddressable" = "38171def3cd15e3b76eb156219b3d48704643899"

@ -1,11 +1,37 @@
package main
import (
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"github.com/github/git-lfs/commands"
"github.com/github/git-lfs/lfs"
)
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()
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())
if err != nil {
if res.StatusCode == 401 {
if res != nil && res.StatusCode == 401 {
return res, nil, newAuthError(err)
}
return res, nil, err

@ -337,7 +337,7 @@ func (c *Configuration) loadGitConfig() bool {
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")
for _, line := range lines {
pieces := strings.SplitN(line, "=", 2)
@ -345,7 +345,7 @@ func (c *Configuration) readGitConfig(output string, uniqRemotes map[string]bool
continue
}
allowed := !whitelisted
allowed := !onlySafe
key := strings.ToLower(pieces[0])
value := pieces[1]
@ -355,12 +355,12 @@ func (c *Configuration) readGitConfig(output string, uniqRemotes map[string]bool
ext := c.extensions[name]
switch keyParts[3] {
case "clean":
if whitelisted {
if onlySafe {
continue
}
ext.Clean = value
case "smudge":
if whitelisted {
if onlySafe {
continue
}
ext.Smudge = value
@ -375,7 +375,7 @@ func (c *Configuration) readGitConfig(output string, uniqRemotes map[string]bool
ext.Name = name
c.extensions[name] = ext
} 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
}
@ -384,7 +384,7 @@ func (c *Configuration) readGitConfig(output string, uniqRemotes map[string]bool
uniqRemotes[remote] = remote == "origin"
}
if !allowed && key != "lfs.url" {
if !allowed && keyIsUnsafe(key) {
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()
Config.SetConfig("lfs.batch", "false")
Config.SetConfig("lfs.url", server.URL+"/media")
reader, size, err := Download("oid")
if err != nil {
t.Fatalf("unexpected error: %s", err)
@ -213,6 +215,7 @@ func TestSuccessfulDownloadWithRedirects(t *testing.T) {
})
defer Config.ResetConfig()
Config.SetConfig("lfs.batch", "false")
Config.SetConfig("lfs.url", server.URL+"/redirect")
for _, redirect := range redirectCodes {
@ -319,6 +322,7 @@ func TestSuccessfulDownloadWithAuthorization(t *testing.T) {
})
defer Config.ResetConfig()
Config.SetConfig("lfs.batch", "false")
Config.SetConfig("lfs.url", server.URL+"/media")
reader, size, err := Download("oid")
if err != nil {
@ -419,6 +423,7 @@ func TestSuccessfulDownloadFromSeparateHost(t *testing.T) {
})
defer Config.ResetConfig()
Config.SetConfig("lfs.batch", "false")
Config.SetConfig("lfs.url", server.URL+"/media")
reader, size, err := Download("oid")
if err != nil {
@ -550,6 +555,7 @@ func TestSuccessfulDownloadFromSeparateRedirectedHost(t *testing.T) {
})
defer Config.ResetConfig()
Config.SetConfig("lfs.batch", "false")
Config.SetConfig("lfs.url", server.URL+"/media")
for _, redirect := range redirectCodes {
@ -587,6 +593,7 @@ func TestDownloadAPIError(t *testing.T) {
})
defer Config.ResetConfig()
Config.SetConfig("lfs.batch", "false")
Config.SetConfig("lfs.url", server.URL+"/media")
_, _, err := Download("oid")
if err == nil {
@ -655,6 +662,7 @@ func TestDownloadStorageError(t *testing.T) {
})
defer Config.ResetConfig()
Config.SetConfig("lfs.batch", "false")
Config.SetConfig("lfs.url", server.URL+"/media")
_, _, err := Download("oid")
if err == nil {

@ -28,6 +28,7 @@ var (
LocalGitDir string // parent of index / config / hooks etc
LocalGitStorageDir string // parent of objects/lfs (may be same as LocalGitDir but may not)
LocalMediaDir string // root of lfs objects
LocalObjectTempDir string // where temporarily downloading objects are stored
LocalLogDir string
checkedTempDir string
)
@ -112,7 +113,8 @@ func ResolveDirs() {
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))
}
}

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
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"hash"
"io"
"io/ioutil"
"os"
"path/filepath"
"github.com/github/git-lfs/vendor/_nuts/github.com/cheggaaa/pb"
"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 {
@ -99,7 +102,6 @@ func downloadObject(ptr *Pointer, obj *objectResource, mediafile string, cb Copy
defer reader.Close()
}
// TODO this can be unified with the same code in downloadFile
if err != nil {
return Errorf(err, "Error downloading %s", mediafile)
}
@ -108,18 +110,7 @@ func downloadObject(ptr *Pointer, obj *objectResource, mediafile string, cb Copy
ptr.Size = size
}
mediaFile, err := contentaddressable.NewFile(mediafile)
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 {
if err := bufferDownloadedFile(mediafile, reader, ptr.Size, cb); err != nil {
return Errorf(err, "Error buffering media file.")
}
@ -134,31 +125,84 @@ func downloadFile(writer io.Writer, ptr *Pointer, workingfile, mediafile string,
}
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 {
ptr.Size = size
}
mediaFile, err := contentaddressable.NewFile(mediafile)
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.")
if err := bufferDownloadedFile(mediafile, reader, ptr.Size, cb); err != nil {
return Errorf(err, "Error buffering media file: %s", err)
}
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 {
reader, err := os.Open(mediafile)
if err != nil {
@ -191,7 +235,7 @@ func readLocalFile(writer io.Writer, ptr *Pointer, mediafile string, workingfile
// pipe extensions in reverse order
var extsR []Extension
for i, _ := range exts {
for i := range exts {
ext := exts[len(exts)-1-i]
extsR = append(extsR, ext)
}
@ -230,15 +274,41 @@ func readLocalFile(writer io.Writer, ptr *Pointer, mediafile string, workingfile
// setup reader
reader, err = os.Open(response.file.Name())
if err != nil {
return Errorf(err, "Error opening smudged file.")
return Errorf(err, "Error opening smudged file: %s", err)
}
defer reader.Close()
}
_, err = CopyWithCallback(writer, reader, ptr.Size, cb)
if err != nil {
return Errorf(err, "Error reading from media file.")
return Errorf(err, "Error reading from media file: %s", err)
}
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/cheggaaa/pb
github.com/rubyist/tracerx
github.com/technoweenie/go-contentaddressable
github.com/kr/pretty
github.com/github/git-lfs/git
github.com/technoweenie/assert

@ -18,7 +18,7 @@ begin_test "checkout"
contentsize=19
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" > file2.dat
printf "$contents" > file3.dat
@ -40,7 +40,7 @@ begin_test "checkout"
# Remove the working directory
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
[ "$contents" = "$(cat file1.dat)" ]
[ "$contents" = "$(cat file2.dat)" ]
@ -51,7 +51,7 @@ begin_test "checkout"
# Remove again
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
[ "$contents" = "$(cat file2.dat)" ]
[ ! -f file1.dat ]
@ -59,14 +59,14 @@ begin_test "checkout"
[ ! -f folder1/nested.dat ]
[ ! -f folder2/nested.dat ]
# quotes to avoid shell globbing
echo "quotes to avoid shell globbing"
git lfs checkout "file*.dat"
[ "$contents" = "$(cat file1.dat)" ]
[ "$contents" = "$(cat file3.dat)" ]
[ ! -f folder1/nested.dat ]
[ ! -f folder2/nested.dat ]
# test subdir context
echo "test subdir context"
pushd folder1
git lfs checkout nested.dat
[ "$contents" = "$(cat nested.dat)" ]
@ -77,11 +77,11 @@ begin_test "checkout"
[ "$contents" = "$(cat nested.dat)" ]
popd
# test folder param
echo "test folder param"
git lfs checkout folder2
[ "$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
git lfs checkout .
[ "$contents" = "$(cat file1.dat)" ]
@ -90,7 +90,7 @@ begin_test "checkout"
[ "$contents" = "$(cat folder1/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
rm -rf .git/lfs/objects
rm file*.dat

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

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

@ -4,6 +4,41 @@
. "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"
(
set -e

@ -161,6 +161,12 @@ end_test
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
[ -n "$LFS_DOCKER" ] && exit 0

@ -93,7 +93,13 @@ end_test
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=$?
set -e
@ -212,7 +218,7 @@ begin_test "pointer missing --pointer"
set -e
[ "1" = "$status" ]
[ "open missing-pointer: no such file or directory" = "$output" ]
[[ "$output" == open missing-pointer:* ]]
)
end_test

@ -38,6 +38,24 @@ begin_test "smudge --info"
)
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"
(
set -e

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

@ -38,10 +38,10 @@ begin_test "track"
out=$(git lfs track)
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 "*.gif (a/.gitattributes)"
echo "$out" | grep "*.png (a/b/.gitattributes)"
echo "$out" | grep "*.gif ($(native_path_escaped "a/.gitattributes"))"
echo "$out" | grep "*.png ($(native_path_escaped "a/b/.gitattributes"))"
)
end_test
@ -134,9 +134,9 @@ begin_test "track representation"
cd track-representation
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"
cat .gitattributes
exit 1
@ -179,7 +179,7 @@ begin_test "track absolute"
git init track-absolute
cd track-absolute
git lfs track "$PWD/*.jpg"
git lfs track "$(native_path "$PWD/")*.jpg"
grep "^*.jpg" .gitattributes || {
echo ".gitattributes doesn't contain the expected relative path *.jpg:"
cat .gitattributes

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

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

@ -382,3 +382,51 @@ get_date() {
date $ARGS -u +%Y-%m-%dT%TZ
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"
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
HOME="$TRASHDIR/home"
export GIT_TRACE=5
rm -rf "$TRASHDIR/home"
mkdir "$HOME"
cp "$TESTHOME/.gitconfig" "$HOME/.gitconfig"
@ -97,6 +103,8 @@ end_test () {
test_status="${1:-$?}"
set +x -e
exec 1>&3 2>&4
# close fd 5 (GIT_TRACE)
exec 5>&-
if [ "$test_status" -eq 0 ]; then
printf "test: %-60s OK\n" "$test_description ..."
@ -109,8 +117,10 @@ end_test () {
echo "-- stderr --"
grep -v -e '^\+ end_test' -e '^+ set +x' <"$TRASHDIR/err" |
sed 's/^/ /'
if [ "$IS_WINDOWS" == "0" ]; then
echo "-- git trace --"
sed 's/^/ /' <"$TRASHDIR/trace"
fi
) 1>&2
echo
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()
}