From 0cdaa6f8f416eeca725eb9634a4ee1b6c0801b8a Mon Sep 17 00:00:00 2001 From: Aleksey Kulikov Date: Fri, 3 Sep 2021 12:13:23 +0300 Subject: [PATCH] feat(tag): add bindings and api --- lib/libgit2dart.dart | 1 + lib/src/bindings/object.dart | 35 +++++++ lib/src/bindings/tag.dart | 97 ++++++++++++++++++ lib/src/repository.dart | 32 ++++++ lib/src/tag.dart | 94 +++++++++++++++++ .../f0/fdbf506397e9f58c59b88dfdd72778ec06cc0c | Bin 0 -> 142 bytes test/assets/testrepo/.gitdir/refs/tags/v0.2 | 1 + test/reference_test.dart | 7 +- test/repository_test.dart | 35 +++++++ test/tag_test.dart | 91 ++++++++++++++++ 10 files changed, 392 insertions(+), 1 deletion(-) create mode 100644 lib/src/bindings/tag.dart create mode 100644 lib/src/tag.dart create mode 100644 test/assets/testrepo/.gitdir/objects/f0/fdbf506397e9f58c59b88dfdd72778ec06cc0c create mode 100644 test/assets/testrepo/.gitdir/refs/tags/v0.2 create mode 100644 test/tag_test.dart diff --git a/lib/libgit2dart.dart b/lib/libgit2dart.dart index 2ff75fa..96601d9 100644 --- a/lib/libgit2dart.dart +++ b/lib/libgit2dart.dart @@ -10,5 +10,6 @@ export 'src/tree.dart'; export 'src/signature.dart'; export 'src/revwalk.dart'; export 'src/blob.dart'; +export 'src/tag.dart'; export 'src/error.dart'; export 'src/enums.dart'; diff --git a/lib/src/bindings/object.dart b/lib/src/bindings/object.dart index f29c00c..8a8b720 100644 --- a/lib/src/bindings/object.dart +++ b/lib/src/bindings/object.dart @@ -1,6 +1,41 @@ import 'dart:ffi'; +import 'package:ffi/ffi.dart'; import 'libgit2_bindings.dart'; +import '../error.dart'; import '../util.dart'; /// Get the object type of an object. int type(Pointer obj) => libgit2.git_object_type(obj); + +/// Lookup a reference to one of the objects in a repository. +/// +/// The generated reference is owned by the repository and should be closed with +/// the `free()` method instead of free'd manually. +/// +/// The 'type' parameter must match the type of the object in the odb; the method will +/// fail otherwise. The special value 'GIT_OBJECT_ANY' may be passed to let the method +/// guess the object's type. +/// +/// Throws a [LibGit2Error] if error occured. +Pointer lookup( + Pointer repo, + Pointer id, + int type, +) { + final out = calloc>(); + final error = libgit2.git_object_lookup(out, repo, id, type); + + if (error < 0) { + throw LibGit2Error(libgit2.git_error_last()); + } else { + return out.value; + } +} + +/// Close an open object to release memory. +/// +/// This method instructs the library to close an existing object; note that git_objects +/// are owned and cached by the repository so the object may or may not be freed after +/// this library call, depending on how aggressive is the caching mechanism used by +/// the repository. +void free(Pointer object) => libgit2.git_object_free(object); diff --git a/lib/src/bindings/tag.dart b/lib/src/bindings/tag.dart new file mode 100644 index 0000000..63bdc57 --- /dev/null +++ b/lib/src/bindings/tag.dart @@ -0,0 +1,97 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import '../error.dart'; +import 'libgit2_bindings.dart'; +import '../util.dart'; + +/// Lookup a tag object from the repository. +/// +/// Throws a [LibGit2Error] if error occured. +Pointer lookup(Pointer repo, Pointer id) { + final out = calloc>(); + final error = libgit2.git_tag_lookup(out, repo, id); + + if (error < 0) { + throw LibGit2Error(libgit2.git_error_last()); + } else { + return out.value; + } +} + +/// Get the tagged object of a tag. +/// +/// This method performs a repository lookup for the given object and returns it. +/// +/// Throws a [LibGit2Error] if error occured. +Pointer target(Pointer tag) { + final out = calloc>(); + final error = libgit2.git_tag_target(out, tag); + + if (error < 0) { + throw LibGit2Error(libgit2.git_error_last()); + } else { + return out.value; + } +} + +/// Get the id of a tag. +Pointer id(Pointer tag) => libgit2.git_tag_id(tag); + +/// Get the name of a tag. +String name(Pointer tag) => + libgit2.git_tag_name(tag).cast().toDartString(); + +/// Get the message of a tag. +String message(Pointer tag) => + libgit2.git_tag_message(tag).cast().toDartString(); + +/// Get the tagger (author) of a tag. +Pointer tagger(Pointer tag) => + libgit2.git_tag_tagger(tag); + +/// Create a new tag in the repository from an object. +/// +/// A new reference will also be created pointing to this tag object. If force is true +/// and a reference already exists with the given name, it'll be replaced. +/// +/// The message will not be cleaned up. +/// +/// The tag name will be checked for validity. You must avoid the characters +/// '~', '^', ':', '\', '?', '[', and '*', and the sequences ".." and "@{" which have +/// special meaning to revparse. +/// +/// Throws a [LibGit2Error] if error occured. +Pointer create( + Pointer repo, + String tagName, + Pointer target, + Pointer tagger, + String message, + bool force, +) { + final out = calloc(); + final tagNameC = tagName.toNativeUtf8().cast(); + final messageC = message.toNativeUtf8().cast(); + final forceC = force ? 1 : 0; + final error = libgit2.git_tag_create( + out, + repo, + tagNameC, + target, + tagger, + messageC, + forceC, + ); + + calloc.free(tagNameC); + calloc.free(messageC); + + if (error < 0) { + throw LibGit2Error(libgit2.git_error_last()); + } else { + return out; + } +} + +/// Close an open tag to release memory. +void free(Pointer tag) => libgit2.git_tag_free(tag); diff --git a/lib/src/repository.dart b/lib/src/repository.dart index dc21fb6..e8338f9 100644 --- a/lib/src/repository.dart +++ b/lib/src/repository.dart @@ -12,6 +12,8 @@ import 'revwalk.dart'; import 'revparse.dart'; import 'blob.dart'; import 'enums.dart'; +import 'signature.dart'; +import 'tag.dart'; import 'util.dart'; class Repository { @@ -397,4 +399,34 @@ class Repository { /// /// Throws a [LibGit2Error] if error occured. Oid createBlobFromDisk(String path) => Blob.createFromDisk(this, path); + + /// Creates a new tag in the repository from provided Oid object. + /// + /// A new reference will also be created pointing to this tag object. If force is true + /// and a reference already exists with the given name, it'll be replaced. + /// + /// The message will not be cleaned up. + /// + /// The tag name will be checked for validity. You must avoid the characters + /// '~', '^', ':', '\', '?', '[', and '*', and the sequences ".." and "@{" which have + /// special meaning to revparse. + /// + /// Throws a [LibGit2Error] if error occured. + Oid createTag({ + required String tagName, + required Oid target, + required GitObject targetType, + required Signature tagger, + required String message, + bool force = false, + }) { + return Tag.create( + repository: this, + tagName: tagName, + target: target, + targetType: targetType, + tagger: tagger, + message: message, + force: force); + } } diff --git a/lib/src/tag.dart b/lib/src/tag.dart new file mode 100644 index 0000000..b0c438d --- /dev/null +++ b/lib/src/tag.dart @@ -0,0 +1,94 @@ +import 'dart:ffi'; + +import 'bindings/libgit2_bindings.dart'; +import 'bindings/tag.dart' as bindings; +import 'bindings/object.dart' as object_bindings; +import 'commit.dart'; +import 'oid.dart'; +import 'repository.dart'; +import 'signature.dart'; +import 'enums.dart'; + +class Tag { + /// Initializes a new instance of [Tag] class from provided + /// [Repository] and [Oid] objects. + /// + /// Should be freed with `free()` to release allocated memory. + Tag.lookup(Repository repo, Oid oid) { + _tagPointer = bindings.lookup(repo.pointer, oid.pointer); + } + + late final Pointer _tagPointer; + + /// Pointer to memory address for allocated tag object. + Pointer get pointer => _tagPointer; + + /// Creates a new tag in the repository from provided Oid object. + /// + /// A new reference will also be created pointing to this tag object. If force is true + /// and a reference already exists with the given name, it'll be replaced. + /// + /// The message will not be cleaned up. + /// + /// The tag name will be checked for validity. You must avoid the characters + /// '~', '^', ':', '\', '?', '[', and '*', and the sequences ".." and "@{" which have + /// special meaning to revparse. + /// + /// Throws a [LibGit2Error] if error occured. + static Oid create({ + required Repository repository, + required String tagName, + required Oid target, + required GitObject targetType, + required Signature tagger, + required String message, + bool force = false, + }) { + // add 1 to GitObject enum index to match libgit2 + final object = object_bindings.lookup( + repository.pointer, + target.pointer, + targetType.index + 1, + ); + final result = bindings.create( + repository.pointer, + tagName, + object, + tagger.pointer, + message, + force, + ); + + object_bindings.free(object); + return Oid(result); + } + + /// Get the tagged object of a tag. + /// + /// This method performs a repository lookup for the given object and returns it. + /// + /// Throws a [LibGit2Error] if error occured. + Commit get target => Commit(bindings.target(_tagPointer).cast()); + + /// Get the id of a tag. + Oid get id => Oid(bindings.id(_tagPointer)); + + /// Returns the name of a tag. + String get name => bindings.name(_tagPointer); + + /// Returns the message of a tag. + String get message => bindings.message(_tagPointer); + + /// Returns the tagger (author) of a tag if there is one. + Signature? get tagger { + final sigPointer = bindings.tagger(_tagPointer); + if (sigPointer != nullptr) { + return Signature(sigPointer); + } else { + return null; + } + } + + /// Releases memory allocated for tag object. + void free() => bindings.free(_tagPointer); +} diff --git a/test/assets/testrepo/.gitdir/objects/f0/fdbf506397e9f58c59b88dfdd72778ec06cc0c b/test/assets/testrepo/.gitdir/objects/f0/fdbf506397e9f58c59b88dfdd72778ec06cc0c new file mode 100644 index 0000000000000000000000000000000000000000..88a7b3e3605ae9f665946e25a53e2cb7992fa52e GIT binary patch literal 142 zcmV;90CE3#0Ts+m4#FT1Kw;OMV)iD@Fj#&PV|48)Xqh(H0>)w!Zg1Va&HJjKL0;^s z3@;ulSdttxThwF*YqSf}peLuSrpZmCMoT@B7PY+@#*$L3q8`SM9sOhG4?I?0Hg53z wu5l?lT(>3W+`1HVxX;PQwL8umNUgAU&X9sLDnucC&ZYXw0S-$103$vx2ldTCM*si- literal 0 HcmV?d00001 diff --git a/test/assets/testrepo/.gitdir/refs/tags/v0.2 b/test/assets/testrepo/.gitdir/refs/tags/v0.2 new file mode 100644 index 0000000..3e0b1fd --- /dev/null +++ b/test/assets/testrepo/.gitdir/refs/tags/v0.2 @@ -0,0 +1 @@ +f0fdbf506397e9f58c59b88dfdd72778ec06cc0c diff --git a/test/reference_test.dart b/test/reference_test.dart index e260c6b..34f8bf0 100644 --- a/test/reference_test.dart +++ b/test/reference_test.dart @@ -30,7 +30,12 @@ void main() { test('returns a list', () { expect( repo.references.list(), - ['refs/heads/feature', 'refs/heads/master', 'refs/tags/v0.1'], + [ + 'refs/heads/feature', + 'refs/heads/master', + 'refs/tags/v0.1', + 'refs/tags/v0.2', + ], ); }); diff --git a/test/repository_test.dart b/test/repository_test.dart index 45fefee..ba61899 100644 --- a/test/repository_test.dart +++ b/test/repository_test.dart @@ -304,6 +304,41 @@ void main() { newBlob.free(); }); }); + + test('successfully creates tag with provided Oid', () { + final signature = Signature.create( + name: 'Author', + email: 'author@email.com', + time: 1234, + ); + const tagName = 'tag'; + final target = + Oid.fromSHA(repo, 'f17d0d48eae3aa08cecf29128a35e310c97b3521'); + const message = 'init tag\n'; + + final oid = Tag.create( + repository: repo, + tagName: tagName, + target: target, + targetType: GitObject.commit, + tagger: signature, + message: message, + ); + + final newTag = Tag.lookup(repo, oid); + final tagger = newTag.tagger; + final newTagTarget = newTag.target; + + expect(newTag.id.sha, '131a5eb6b7a880b5096c550ee7351aeae7b95a42'); + expect(newTag.name, tagName); + expect(newTag.message, message); + expect(tagger, signature); + expect(newTagTarget.id, target); + + newTag.free(); + newTagTarget.free(); + signature.free(); + }); }); }); } diff --git a/test/tag_test.dart b/test/tag_test.dart new file mode 100644 index 0000000..e949381 --- /dev/null +++ b/test/tag_test.dart @@ -0,0 +1,91 @@ +import 'dart:io'; + +import 'package:test/test.dart'; +import 'package:libgit2dart/libgit2dart.dart'; +import 'helpers/util.dart'; + +void main() { + late Repository repo; + late Tag tag; + final tmpDir = '${Directory.systemTemp.path}/tag_testrepo/'; + const tagSHA = 'f0fdbf506397e9f58c59b88dfdd72778ec06cc0c'; + + setUp(() async { + if (await Directory(tmpDir).exists()) { + await Directory(tmpDir).delete(recursive: true); + } + await copyRepo( + from: Directory('test/assets/testrepo/'), + to: await Directory(tmpDir).create(), + ); + repo = Repository.open(tmpDir); + tag = Tag.lookup(repo, Oid.fromSHA(repo, tagSHA)); + }); + + tearDown(() async { + tag.free(); + repo.free(); + await Directory(tmpDir).delete(recursive: true); + }); + + group('Tag', () { + test('successfully initializes tag from provided Oid', () { + expect(tag, isA()); + }); + + test('returns correct values', () { + final signature = Signature.create( + name: 'Aleksey Kulikov', + email: 'skinny.mind@gmail.com', + time: 1630599723, + offset: 180, + ); + final target = tag.target; + final tagger = tag.tagger; + + expect(tag.id.sha, tagSHA); + expect(tag.name, 'v0.2'); + expect(tag.message, 'annotated tag\n'); + expect(target.message, 'add subdirectory file\n'); + expect(tagger, signature); + + signature.free(); + target.free(); + }); + + test('successfully creates new tag', () { + final signature = Signature.create( + name: 'Author', + email: 'author@email.com', + time: 1234, + ); + const tagName = 'tag'; + final target = + Oid.fromSHA(repo, 'f17d0d48eae3aa08cecf29128a35e310c97b3521'); + const message = 'init tag\n'; + + final oid = Tag.create( + repository: repo, + tagName: tagName, + target: target, + targetType: GitObject.commit, + tagger: signature, + message: message, + ); + + final newTag = Tag.lookup(repo, oid); + final tagger = newTag.tagger; + final newTagTarget = newTag.target; + + expect(newTag.id.sha, '131a5eb6b7a880b5096c550ee7351aeae7b95a42'); + expect(newTag.name, tagName); + expect(newTag.message, message); + expect(tagger, signature); + expect(newTagTarget.id, target); + + newTag.free(); + newTagTarget.free(); + signature.free(); + }); + }); +}