diff --git a/lib/src/bindings/commit.dart b/lib/src/bindings/commit.dart index 0df462a..e90bb8f 100644 --- a/lib/src/bindings/commit.dart +++ b/lib/src/bindings/commit.dart @@ -3,6 +3,7 @@ import 'package:ffi/ffi.dart'; import '../error.dart'; import 'libgit2_bindings.dart'; import '../util.dart'; +import 'oid.dart' as oid_bindings; /// Lookup a commit object from a repository. /// @@ -20,6 +21,83 @@ Pointer lookup(Pointer repo, Pointer id) { } } +/// Lookup a commit object from a repository, given a prefix of its identifier (short id). +/// +/// Throws a [LibGit2Error] if error occured. +Pointer lookupPrefix( + Pointer repo, + Pointer id, + int len, +) { + final out = calloc>(); + final error = libgit2.git_commit_lookup_prefix(out, repo, id, len); + + if (error < 0) { + throw LibGit2Error(libgit2.git_error_last()); + } else { + return out.value; + } +} + +/// Create new commit in the repository. +/// +/// Throws a [LibGit2Error] if error occured. +Pointer create( + Pointer repo, + String? updateRef, + Pointer author, + Pointer committer, + String? messageEncoding, + String message, + Pointer tree, + int parentCount, + List parents, +) { + final out = calloc(); + final updateRefC = updateRef?.toNativeUtf8().cast() ?? nullptr; + final messageEncodingC = + messageEncoding?.toNativeUtf8().cast() ?? nullptr; + final messageC = message.toNativeUtf8().cast(); + late final Pointer> parentsC; + + if (parents.isNotEmpty) { + parentsC = calloc(parentCount); + + for (var i = 0; i < parentCount; i++) { + final oid = oid_bindings.fromSHA(parents[i]); + final commit = lookup(repo, oid); + parentsC[i] = commit; + } + } else { + throw UnimplementedError( + 'Writing commit without parents is not implemented'); + } + + final error = libgit2.git_commit_create( + out, + repo, + updateRefC, + author, + committer, + messageEncodingC, + messageC, + tree, + parentCount, + parentsC, + ); + + calloc.free(updateRefC); + calloc.free(messageEncodingC); + calloc.free(messageC); + calloc.free(parentsC); + + if (error < 0) { + throw LibGit2Error(libgit2.git_error_last()); + } else { + return out; + } +} + /// Get the encoding for the message of a commit, as a string representing a standard encoding name. /// /// The encoding may be NULL if the encoding header in the commit is missing; diff --git a/lib/src/commit.dart b/lib/src/commit.dart index 22ed274..f5365ac 100644 --- a/lib/src/commit.dart +++ b/lib/src/commit.dart @@ -1,8 +1,11 @@ import 'dart:ffi'; + import 'bindings/libgit2_bindings.dart'; import 'bindings/commit.dart' as bindings; +import 'repository.dart'; import 'oid.dart'; import 'signature.dart'; +import 'tree.dart'; import 'util.dart'; class Commit { @@ -26,6 +29,58 @@ class Commit { /// Pointer to memory address for allocated commit object. late final Pointer _commitPointer; + /// Creates new commit in the repository. + /// + /// Throws a [LibGit2Error] if error occured. + static Oid create({ + required Repository repo, + required String message, + required Signature author, + required Signature commiter, + required String treeSHA, + required List parentsSHA, + String? updateRef, + String? messageEncoding, + }) { + libgit2.git_libgit2_init(); + + final parentCount = parentsSHA.length; + late final Tree tree; + + if (treeSHA.length == 40) { + final treeOid = Oid.fromSHA(treeSHA); + tree = Tree.lookup( + repo.pointer, + treeOid.pointer, + ); + } else { + final odb = repo.odb; + final treeOid = Oid.fromShortSHA(treeSHA, odb); + tree = Tree.lookup( + repo.pointer, + treeOid.pointer, + ); + odb.free(); + } + + final result = Oid(bindings.create( + repo.pointer, + updateRef, + author.pointer, + commiter.pointer, + messageEncoding, + message, + tree.pointer, + parentCount, + parentsSHA, + )); + + tree.free(); + libgit2.git_libgit2_init(); + + return result; + } + /// Returns the encoding for the message of a commit, as a string /// representing a standard encoding name. String get messageEncoding => bindings.messageEncoding(_commitPointer); diff --git a/lib/src/signature.dart b/lib/src/signature.dart index 8b9ed44..1aefb3d 100644 --- a/lib/src/signature.dart +++ b/lib/src/signature.dart @@ -34,9 +34,11 @@ class Signature { } } - /// Pointer to memory address for allocated signature object. late final Pointer _signaturePointer; + /// Pointer to memory address for allocated signature object. + Pointer get pointer => _signaturePointer; + /// Returns full name of the author. String get name => _signaturePointer.ref.name.cast().toDartString(); diff --git a/test/commit_test.dart b/test/commit_test.dart index a694967..9e3bebf 100644 --- a/test/commit_test.dart +++ b/test/commit_test.dart @@ -9,7 +9,7 @@ void main() { group('Commit', () { late Repository repo; - final tmpDir = '${Directory.systemTemp.path}/ref_testrepo/'; + final tmpDir = '${Directory.systemTemp.path}/commit_testrepo/'; setUp(() async { if (await Directory(tmpDir).exists()) { @@ -55,14 +55,16 @@ void main() { time: 1626091184, offset: 180, ); + final commit = repo[mergeCommit]; + final parents = commit.parents; expect(commit.messageEncoding, 'utf-8'); expect(commit.message, 'Merge branch \'feature\'\n'); expect(commit.id.sha, mergeCommit); - expect(commit.parents.length, 2); + expect(parents.length, 2); expect( - commit.parents[0].id.sha, + parents[0].id.sha, 'c68ff54aabf660fcdd9a2838d401583fe31249e3', ); expect(commit.time, 1626091184); @@ -70,9 +72,100 @@ void main() { expect(commit.author, signature); expect(commit.tree.sha, '7796359a96eb722939c24bafdb1afe9f07f2f628'); + for (var p in parents) { + p.free(); + } signature.free(); commit.free(); }); }); + + group('.create()', () { + test('successfuly creates commit', () { + const message = "Commit message.\n\nSome description.\n"; + const tree = '7796359a96eb722939c24bafdb1afe9f07f2f628'; + final author = Signature.create( + name: 'Author Name', + email: 'author@email.com', + time: 123, + ); + final commiter = Signature.create( + name: 'Commiter', + email: 'commiter@email.com', + time: 124, + ); + + final oid = Commit.create( + repo: repo, + message: message, + author: author, + commiter: commiter, + treeSHA: tree, + parentsSHA: [mergeCommit], + ); + + final commit = repo[oid.sha]; + final parents = commit.parents; + + expect(commit.id.sha, oid.sha); + expect(commit.message, message); + expect(commit.author, author); + expect(commit.committer, commiter); + expect(commit.time, 124); + expect(commit.tree.sha, tree); + expect(parents.length, 1); + expect(parents[0].id.sha, mergeCommit); + + for (var p in parents) { + p.free(); + } + author.free(); + commiter.free(); + commit.free(); + }); + + test('successfuly creates commit with short sha of tree', () { + const message = "Commit message.\n\nSome description.\n"; + const tree = '7796359a96eb722939c24bafdb1afe9f07f2f628'; + final author = Signature.create( + name: 'Author Name', + email: 'author@email.com', + time: 123, + ); + final commiter = Signature.create( + name: 'Commiter', + email: 'commiter@email.com', + time: 124, + ); + + final oid = Commit.create( + repo: repo, + message: message, + author: author, + commiter: commiter, + treeSHA: tree.substring(0, 5), + parentsSHA: [mergeCommit], + ); + + final commit = repo[oid.sha]; + final parents = commit.parents; + + expect(commit.id.sha, oid.sha); + expect(commit.message, message); + expect(commit.author, author); + expect(commit.committer, commiter); + expect(commit.time, 124); + expect(commit.tree.sha, tree); + expect(parents.length, 1); + expect(parents[0].id.sha, mergeCommit); + + for (var p in parents) { + p.free(); + } + author.free(); + commiter.free(); + commit.free(); + }); + }); }); } diff --git a/test/oid_test.dart b/test/oid_test.dart index b53f784..f6b6e7d 100644 --- a/test/oid_test.dart +++ b/test/oid_test.dart @@ -39,7 +39,7 @@ void main() { group('fromShortSHA()', () { test('initializes successfully from short hex string', () { final odb = repo.odb; - final oid = Oid.fromShortSHA(sha.substring(0, 4), odb); + final oid = Oid.fromShortSHA(sha.substring(0, 5), odb); expect(oid, isA()); expect(oid.sha, sha);