Repository: libgit2/git2go
Pull request #485 (2019-02-16 17:40:02)

Add odb.NewOdbBackendLoose()
This change adds support for odb.NewOdbBackendLoose(). This, together
with the git.Packbuilder, can do what Mempack does with a lot less
memory.


Patch:
diff --git a/odb.go b/odb.go
index 994ff2a..d313e3b 100644
--- a/odb.go
+++ b/odb.go
@@ -4,6 +4,7 @@ package git
 #include <git2.h>
 
 extern int git_odb_backend_one_pack(git_odb_backend **out, const char *index_file);
+extern int git_odb_backend_loose(git_odb_backend **out, const char *objects_dir, int compression_level, int do_fsync, unsigned int dir_mode, unsigned int file_mode);
 extern int _go_git_odb_foreach(git_odb *db, void *payload);
 extern void _go_git_odb_backend_free(git_odb_backend *backend);
 extern int _go_git_odb_write_pack(git_odb_writepack **out, git_odb *db, void *progress_payload);
@@ -14,6 +15,7 @@ extern void _go_git_odb_writepack_free(git_odb_writepack *writepack);
 import "C"
 import (
 	"io"
+	"os"
 	"reflect"
 	"runtime"
 	"unsafe"
@@ -88,6 +90,27 @@ func NewOdbBackendOnePack(packfileIndexPath string) (backend *OdbBackend, err er
 	return NewOdbBackendFromC(unsafe.Pointer(odbOnePack)), nil
 }
 
+// NewOdbBackendLoose creates a backend for loose objects.
+func NewOdbBackendLoose(objectsDir string, compressionLevel int, doFsync bool, dirMode os.FileMode, fileMode os.FileMode) (backend *OdbBackend, err error) {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	var odbLoose *C.git_odb_backend = nil
+	var doFsyncInt C.int
+	if doFsync {
+		doFsyncInt = C.int(1)
+	}
+
+	cstr := C.CString(objectsDir)
+	defer C.free(unsafe.Pointer(cstr))
+
+	ret := C.git_odb_backend_loose(&odbLoose, cstr, C.int(compressionLevel), doFsyncInt, C.uint(dirMode), C.uint(fileMode))
+	if ret < 0 {
+		return nil, MakeGitError(ret)
+	}
+	return NewOdbBackendFromC(unsafe.Pointer(odbLoose)), nil
+}
+
 func (v *Odb) ReadHeader(oid *Oid) (uint64, ObjectType, error) {
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()


Test patch:
diff --git a/odb_test.go b/odb_test.go
index e44c927..d79afa1 100644
--- a/odb_test.go
+++ b/odb_test.go
@@ -3,8 +3,11 @@ package git
 import (
 	"bytes"
 	"errors"
+	"fmt"
 	"io"
 	"io/ioutil"
+	"os"
+	"path"
 	"testing"
 )
 
@@ -186,3 +189,47 @@ func TestOdbWritepack(t *testing.T) {
 		t.Errorf("mismatched indexed objects, expected 3, got %v", finalStats.IndexedObjects)
 	}
 }
+
+func TestOdbBackendLoose(t *testing.T) {
+	t.Parallel()
+	repo := createTestRepo(t)
+	defer cleanupTestRepo(t, repo)
+
+	_, _ = seedTestRepo(t, repo)
+
+	odb, err := repo.Odb()
+	checkFatal(t, err)
+
+	looseObjectsDir, err := ioutil.TempDir("", fmt.Sprintf("loose_objects_%s", path.Base(repo.Path())))
+	checkFatal(t, err)
+	defer os.RemoveAll(looseObjectsDir)
+
+	looseObjectsBackend, err := NewOdbBackendLoose(looseObjectsDir, -1, false, 0, 0)
+	checkFatal(t, err)
+	if err := odb.AddBackend(looseObjectsBackend, 999); err != nil {
+		looseObjectsBackend.Free()
+		checkFatal(t, err)
+	}
+
+	str := "hello, world!"
+
+	writeStream, err := odb.NewWriteStream(int64(len(str)), ObjectBlob)
+	checkFatal(t, err)
+	n, err := io.WriteString(writeStream, str)
+	checkFatal(t, err)
+	if n != len(str) {
+		t.Fatalf("Bad write length %v != %v", n, len(str))
+	}
+
+	err = writeStream.Close()
+	checkFatal(t, err)
+
+	expectedId, err := NewOid("30f51a3fba5274d53522d0f19748456974647b4f")
+	checkFatal(t, err)
+	if !writeStream.Id.Equal(expectedId) {
+		t.Fatalf("writeStream.id = %v; want %v", writeStream.Id, expectedId)
+	}
+
+	_, err = os.Stat(path.Join(looseObjectsDir, expectedId.String()[:2], expectedId.String()[2:]))
+	checkFatal(t, err)
+}