feat(odb): add more bindings and api methods

This commit is contained in:
Aleksey Kulikov 2021-10-05 17:12:51 +03:00
parent 2ce419d7c4
commit 618b4e7f05
5 changed files with 416 additions and 14 deletions

View file

@ -1,9 +1,53 @@
import 'dart:ffi'; import 'dart:ffi';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
import 'oid.dart' as oid_bindings;
import '../error.dart'; import '../error.dart';
import '../oid.dart';
import 'libgit2_bindings.dart'; import 'libgit2_bindings.dart';
import '../util.dart'; import '../util.dart';
/// Create a new object database with no backends.
///
/// Before the ODB can be used for read/writing, a custom database backend must be
/// manually added.
///
/// Throws a [LibGit2Error] if error occured.
Pointer<git_odb> create() {
final out = calloc<Pointer<git_odb>>();
final error = libgit2.git_odb_new(out);
if (error < 0) {
throw LibGit2Error(libgit2.git_error_last());
} else {
return out.value;
}
}
/// Add an on-disk alternate to an existing Object DB.
///
/// Note that the added path must point to an `objects`, not to a full repository,
/// to use it as an alternate store.
///
/// Alternate backends are always checked for objects after all the main backends
/// have been exhausted.
///
/// Writing is disabled on alternate backends.
///
/// Throws a [LibGit2Error] if error occured.
void addDiskAlternate({
required Pointer<git_odb> odbPointer,
required String path,
}) {
final pathC = path.toNativeUtf8().cast<Int8>();
final error = libgit2.git_odb_add_disk_alternate(odbPointer, pathC);
calloc.free(pathC);
if (error < 0) {
throw LibGit2Error(libgit2.git_error_last());
}
}
/// Determine if an object can be found in the object database by an abbreviated object ID. /// Determine if an object can be found in the object database by an abbreviated object ID.
/// ///
/// Throws a [LibGit2Error] if error occured. /// Throws a [LibGit2Error] if error occured.
@ -27,5 +71,175 @@ Pointer<git_oid> existsPrefix({
} }
} }
/// Determine if the given object can be found in the object database.
bool exists({
required Pointer<git_odb> odbPointer,
required Pointer<git_oid> oidPointer,
}) {
final result = libgit2.git_odb_exists(odbPointer, oidPointer);
return result == 1 ? true : false;
}
/// List of objects in the database.
var _objects = <Oid>[];
/// List all objects available in the database.
///
/// Throws a [LibGit2Error] if error occured.
List<Oid> objects(Pointer<git_odb> odb) {
const except = -1;
final payload = calloc<Pointer<git_oid>>();
final cb =
Pointer.fromFunction<Int32 Function(Pointer<git_oid>, Pointer<Void>)>(
_forEachCb, except);
final error = libgit2.git_odb_foreach(odb, cb, payload.cast());
if (error < 0) {
_objects.clear();
throw LibGit2Error(libgit2.git_error_last());
}
final result = _objects.toList(growable: false);
_objects.clear();
return result;
}
/// The callback to call for each object.
int _forEachCb(
Pointer<git_oid> oid,
Pointer<Void> payload,
) {
final _oid = oid_bindings.copy(oid);
_objects.add(Oid(_oid));
return 0;
}
/// Read an object from the database.
///
/// This method queries all available ODB backends trying to read the given OID.
///
/// The returned object is reference counted and internally cached, so it should be
/// closed by the user once it's no longer in use.
///
/// Throws a [LibGit2Error] if error occured.
Pointer<git_odb_object> read({
required Pointer<git_odb> odbPointer,
required Pointer<git_oid> oidPointer,
}) {
final out = calloc<Pointer<git_odb_object>>();
final error = libgit2.git_odb_read(out, odbPointer, oidPointer);
if (error < 0) {
throw LibGit2Error(libgit2.git_error_last());
} else {
return out.value;
}
}
/// Return the OID of an ODB object.
///
/// This is the OID from which the object was read from.
Pointer<git_oid> objectId(Pointer<git_odb_object> object) {
return libgit2.git_odb_object_id(object);
}
/// Return the type of an ODB object.
int objectType(Pointer<git_odb_object> object) {
return libgit2.git_odb_object_type(object);
}
/// Return the data of an ODB object.
///
/// This is the uncompressed, raw data as read from the ODB, without the leading header.
String objectData(Pointer<git_odb_object> object) {
final out = libgit2.git_odb_object_data(object);
return out.cast<Utf8>().toDartString();
}
/// Return the size of an ODB object.
///
/// This is the real size of the `data` buffer, not the actual size of the object.
int objectSize(Pointer<git_odb_object> object) {
return libgit2.git_odb_object_size(object);
}
/// Close an ODB object.
///
/// This method must always be called once a odb object is no longer needed,
/// otherwise memory will leak.
void objectFree(Pointer<git_odb_object> object) {
libgit2.git_odb_object_free(object);
}
/// Write raw data to into the object database.
///
/// Throws a [LibGit2Error] if error occured.
Pointer<git_oid> write({
required Pointer<git_odb> odbPointer,
required int type,
required String data,
}) {
final stream = calloc<Pointer<git_odb_stream>>();
final streamError = libgit2.git_odb_open_wstream(
stream,
odbPointer,
data.length,
type,
);
if (streamError < 0) {
throw LibGit2Error(libgit2.git_error_last());
}
final buffer = data.toNativeUtf8().cast<Int8>();
final writeError = libgit2.git_odb_stream_write(
stream.value,
buffer,
data.length,
);
if (writeError < 0) {
libgit2.git_odb_stream_free(stream.value);
throw LibGit2Error(libgit2.git_error_last());
}
final out = calloc<git_oid>();
final finalizeError = libgit2.git_odb_stream_finalize_write(
out,
stream.value,
);
calloc.free(buffer);
libgit2.git_odb_stream_free(stream.value);
if (finalizeError < 0) {
throw LibGit2Error(libgit2.git_error_last());
} else {
return out;
}
}
/// Get the number of ODB backend objects.
int backendsCount(Pointer<git_odb> odb) => libgit2.git_odb_num_backends(odb);
/// Lookup an ODB backend object by index.
///
/// Throws a [LibGit2Error] if error occured.
Pointer<git_odb_backend> getBackend({
required Pointer<git_odb> odbPointer,
required int position,
}) {
final out = calloc<Pointer<git_odb_backend>>();
final error = libgit2.git_odb_get_backend(out, odbPointer, position);
if (error < 0) {
throw LibGit2Error(libgit2.git_error_last());
} else {
return out.value;
}
}
/// Close an open object database. /// Close an open object database.
void free(Pointer<git_odb> db) => libgit2.git_odb_free(db); void free(Pointer<git_odb> db) => libgit2.git_odb_free(db);

View file

@ -84,3 +84,17 @@ int compare({
}) { }) {
return libgit2.git_oid_cmp(aPointer, bPointer); return libgit2.git_oid_cmp(aPointer, bPointer);
} }
/// Copy an oid from one structure to another.
///
/// Throws a [LibGit2Error] if error occured.
Pointer<git_oid> copy(Pointer<git_oid> src) {
final out = calloc<git_oid>();
final error = libgit2.git_oid_cpy(out, src);
if (error < 0) {
throw LibGit2Error(libgit2.git_error_last());
} else {
return out;
}
}

View file

@ -1,31 +1,138 @@
import 'dart:ffi'; import 'dart:ffi';
import 'package:libgit2dart/libgit2dart.dart';
import 'package:libgit2dart/src/git_types.dart';
import 'bindings/libgit2_bindings.dart'; import 'bindings/libgit2_bindings.dart';
import 'bindings/odb.dart' as bindings; import 'bindings/odb.dart' as bindings;
import 'oid.dart';
import 'util.dart';
class Odb { class Odb {
/// Initializes a new instance of [Odb] class from provided /// Initializes a new instance of [Odb] class from provided
/// pointer to Odb object in memory. /// pointer to Odb object in memory.
const Odb(this._odbPointer); Odb(this._odbPointer);
final Pointer<git_odb> _odbPointer; /// Initializes a new instance of [Odb] class by creating a new object database with
/// no backends.
///
/// Before the ODB can be used for read/writing, a custom database backend must be
/// manually added.
///
/// Throws a [LibGit2Error] if error occured.
Odb.create() {
libgit2.git_libgit2_init();
_odbPointer = bindings.create();
}
late final Pointer<git_odb> _odbPointer;
/// Pointer to memory address for allocated oid object. /// Pointer to memory address for allocated oid object.
Pointer<git_odb> get pointer => _odbPointer; Pointer<git_odb> get pointer => _odbPointer;
/// Determine if an object can be found in the object database by an abbreviated object ID. /// Adds an on-disk alternate to an existing Object DB.
///
/// Note that the added [path] must point to an `objects`, not to a full repository,
/// to use it as an alternate store.
///
/// Alternate backends are always checked for objects after all the main backends
/// have been exhausted.
///
/// Writing is disabled on alternate backends.
/// ///
/// Throws a [LibGit2Error] if error occured. /// Throws a [LibGit2Error] if error occured.
Pointer<git_oid> existsPrefix({ void addDiskAlternate(String path) {
required Pointer<git_oid> shortOidPointer, bindings.addDiskAlternate(
required int length,
}) {
return bindings.existsPrefix(
odbPointer: _odbPointer, odbPointer: _odbPointer,
shortOidPointer: shortOidPointer, path: path,
length: length,
); );
} }
/// Returns list of all objects available in the database.
///
/// Throws a [LibGit2Error] if error occured.
List<Oid> get objects => bindings.objects(_odbPointer);
/// Checks if the given object can be found in the object database.
bool contains(Oid oid) {
return bindings.exists(odbPointer: _odbPointer, oidPointer: oid.pointer);
}
/// Reads an object from the database.
///
/// This method queries all available ODB backends trying to read the given [oid].
///
/// The returned object should be freed by the user once it's no longer in use.
///
/// Throws a [LibGit2Error] if error occured.
OdbObject read(Oid oid) {
return OdbObject(bindings.read(
odbPointer: _odbPointer,
oidPointer: oid.pointer,
));
}
/// Writes raw [data] to into the object database.
///
/// [type] should be one of [GitObject.blob], [GitObject.commit], [GitObject.tag],
/// [GitObject.tree].
///
/// Throws a [LibGit2Error] if error occured or [ArgumentError] if provided type is invalid.
Oid write({required GitObject type, required String data}) {
if (type == GitObject.any ||
type == GitObject.invalid ||
type == GitObject.offsetDelta ||
type == GitObject.refDelta) {
throw ArgumentError.value('$type is invalid type');
} else {
return Oid(bindings.write(
odbPointer: _odbPointer,
type: type.value,
data: data,
));
}
}
/// Releases memory allocated for odb object. /// Releases memory allocated for odb object.
void free() => bindings.free(_odbPointer); void free() => bindings.free(_odbPointer);
} }
class OdbObject {
/// Initializes a new instance of the [OdbObject] class from
/// provided pointer to odbObject object in memory.
const OdbObject(this._odbObjectPointer);
/// Pointer to memory address for allocated odbObject object.
final Pointer<git_odb_object> _odbObjectPointer;
/// Returns the OID of an ODB object.
///
/// This is the OID from which the object was read from.
Oid get id => Oid(bindings.objectId(_odbObjectPointer));
/// Returns the type of an ODB object.
GitObject get type {
late GitObject result;
final typeInt = bindings.objectType(_odbObjectPointer);
for (var type in GitObject.values) {
if (typeInt == type.value) {
result = type;
break;
}
}
return result;
}
/// Returns the data of an ODB object.
///
/// This is the uncompressed, raw data as read from the ODB, without the leading header.
String get data => bindings.objectData(_odbObjectPointer);
/// Returns the size of an ODB object.
///
/// This is the real size of the `data` buffer, not the actual size of the object.
int get size => bindings.objectSize(_odbObjectPointer);
/// Releases memory allocated for odbObject object.
void free() => bindings.objectFree(_odbObjectPointer);
}

View file

@ -1,6 +1,7 @@
import 'dart:ffi'; import 'dart:ffi';
import 'bindings/libgit2_bindings.dart'; import 'bindings/libgit2_bindings.dart';
import 'bindings/oid.dart' as bindings; import 'bindings/oid.dart' as bindings;
import 'bindings/odb.dart' as odb_bindings;
import 'repository.dart'; import 'repository.dart';
import 'util.dart'; import 'util.dart';
@ -22,7 +23,8 @@ class Oid {
_oidPointer = bindings.fromSHA(sha); _oidPointer = bindings.fromSHA(sha);
} else { } else {
final odb = repo.odb; final odb = repo.odb;
_oidPointer = odb.existsPrefix( _oidPointer = odb_bindings.existsPrefix(
odbPointer: odb.pointer,
shortOidPointer: bindings.fromStrN(sha), shortOidPointer: bindings.fromStrN(sha),
length: sha.length, length: sha.length,
); );

View file

@ -1,5 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:libgit2dart/src/git_types.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:libgit2dart/libgit2dart.dart'; import 'package:libgit2dart/libgit2dart.dart';
import 'helpers/util.dart'; import 'helpers/util.dart';
@ -7,7 +8,9 @@ import 'helpers/util.dart';
void main() { void main() {
late Repository repo; late Repository repo;
late Directory tmpDir; late Directory tmpDir;
const lastCommit = '78b8bf123e3952c970ae5c1ce0a3ea1d1336f6e8'; const commitSha = '78b8bf123e3952c970ae5c1ce0a3ea1d1336f6e8';
const blobSha = '9c78c21d6680a7ffebc76f7ac68cacc11d8f48bc';
const blobContent = 'Feature edit\n';
setUp(() async { setUp(() async {
tmpDir = await setupRepo(Directory('test/assets/testrepo/')); tmpDir = await setupRepo(Directory('test/assets/testrepo/'));
@ -26,12 +29,74 @@ void main() {
odb.free(); odb.free();
}); });
test('successfully creates new odb with no backends', () {
final odb = Odb.create();
expect(odb, isA<Odb>());
odb.free();
});
test('successfully adds disk alternate', () {
final oid = Oid.fromSHA(repo: repo, sha: blobSha);
final odb = Odb.create();
odb.addDiskAlternate('${repo.workdir}.git/objects/');
expect(odb.contains(oid), true);
odb.free();
});
test('successfully reads object', () {
final oid = Oid.fromSHA(repo: repo, sha: blobSha);
final odb = repo.odb;
final object = odb.read(oid);
expect(object.id, oid);
expect(object.type, GitObject.blob);
expect(object.data, blobContent);
expect(object.size, 13);
object.free();
odb.free();
});
test('returns list of all objects oid\'s in database', () {
final oid = Oid.fromSHA(repo: repo, sha: commitSha);
final odb = repo.odb;
expect(odb.objects, isNot(isEmpty));
expect(odb.objects.contains(oid), true);
odb.free();
});
test('finds object by short oid', () { test('finds object by short oid', () {
final oid = Oid.fromSHA( final oid = Oid.fromSHA(
repo: repo, repo: repo,
sha: lastCommit.substring(0, 5), sha: commitSha.substring(0, 5),
); );
expect(oid.sha, lastCommit); expect(oid.sha, commitSha);
});
test('successfully writes data', () {
final odb = repo.odb;
final oid = odb.write(type: GitObject.blob, data: 'testing');
final object = odb.read(oid);
expect(odb.contains(oid), true);
expect(object.data, 'testing');
object.free();
odb.free();
});
test('throws when trying to write with invalid object type', () {
final odb = repo.odb;
expect(
() => odb.write(type: GitObject.any, data: 'testing'),
throwsA(isA<ArgumentError>()),
);
odb.free();
}); });
}); });
} }