diff --git a/example/reference_example.dart b/example/reference_example.dart index 8f87e8e..f9a71f7 100644 --- a/example/reference_example.dart +++ b/example/reference_example.dart @@ -4,11 +4,12 @@ import 'package:libgit2dart/libgit2dart.dart'; void main() { final repo = Repository.open(Directory.current.path); - final ref = Reference.lookup(repo, 'refs/heads/master'); + final ref = repo.getReference('refs/heads/master'); - print('Reference SHA hex: ${ref.target}'); + print('Reference SHA hex: ${ref.target.sha}'); print('Is reference a local branch: ${ref.isBranch}'); print('Reference full name: ${ref.name}'); ref.free(); + repo.close(); } diff --git a/lib/libgit2dart.dart b/lib/libgit2dart.dart index a87cafa..ab62a5a 100644 --- a/lib/libgit2dart.dart +++ b/lib/libgit2dart.dart @@ -1,4 +1,3 @@ export 'src/repository.dart'; export 'src/config.dart'; export 'src/error.dart'; -export 'src/reference.dart'; diff --git a/lib/src/bindings/odb.dart b/lib/src/bindings/odb.dart new file mode 100644 index 0000000..36393bf --- /dev/null +++ b/lib/src/bindings/odb.dart @@ -0,0 +1,26 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import '../error.dart'; +import 'libgit2_bindings.dart'; +import '../util.dart'; + +/// Determine if an object can be found in the object database by an abbreviated object ID. +/// +/// Throws a [LibGit2Error] if error occured. +Pointer existsPrefix( + Pointer db, + Pointer shortOid, + int len, +) { + final out = calloc(); + final error = libgit2.git_odb_exists_prefix(out, db, shortOid, len); + + if (error < 0) { + throw LibGit2Error(libgit2.git_error_last()); + } else { + return out; + } +} + +/// Close an open object database. +void free(Pointer db) => libgit2.git_odb_free(db); diff --git a/lib/src/bindings/oid.dart b/lib/src/bindings/oid.dart index 29ad2f8..1cd4216 100644 --- a/lib/src/bindings/oid.dart +++ b/lib/src/bindings/oid.dart @@ -4,6 +4,24 @@ import '../error.dart'; import 'libgit2_bindings.dart'; import '../util.dart'; +/// Parse N characters of a hex formatted object id into a git_oid. +/// +/// If N is odd, the last byte's high nibble will be read in and the low nibble set to zero. +/// +/// Throws a [LibGit2Error] if error occured. +Pointer fromStrN(String hex) { + final out = calloc(); + final str = hex.toNativeUtf8().cast(); + final error = libgit2.git_oid_fromstrn(out, str, hex.length); + calloc.free(str); + + if (error < 0) { + throw LibGit2Error(libgit2.git_error_last()); + } else { + return out; + } +} + /// Parse a hex formatted object id into a git_oid. /// /// Throws a [LibGit2Error] if error occured. diff --git a/lib/src/bindings/reference.dart b/lib/src/bindings/reference.dart index b179254..22e8d2f 100644 --- a/lib/src/bindings/reference.dart +++ b/lib/src/bindings/reference.dart @@ -11,8 +11,17 @@ int referenceType(Pointer ref) => /// Get the OID pointed to by a direct reference. /// /// Only available if the reference is direct (i.e. an object id reference, not a symbolic one). -Pointer? target(Pointer ref) => - libgit2.git_reference_target(ref); +/// +/// Throws an exception if error occured. +Pointer target(Pointer ref) { + final result = libgit2.git_reference_target(ref); + + if (result == nullptr) { + throw Exception('OID for reference isn\'t available'); + } else { + return result; + } +} /// Resolve a symbolic reference to a direct reference. /// @@ -127,6 +136,70 @@ bool isTag(Pointer ref) { return result == 1 ? true : false; } +/// Create a new direct reference. +/// +/// A direct reference (also called an object id reference) refers directly to a +/// specific object id (a.k.a. OID or SHA) in the repository. The id permanently refers to +/// the object (although the reference itself can be moved). For example, in libgit2 +/// the direct ref "refs/tags/v0.17.0" refers to OID 5b9fac39d8a76b9139667c26a63e6b3f204b3977. +/// +/// The direct reference will be created in the repository and written to the disk. +/// The generated reference object must be freed by the user. +/// +/// Valid reference names must follow one of two patterns: +/// +/// Top-level names must contain only capital letters and underscores, and must begin and end +/// with a letter. (e.g. "HEAD", "ORIG_HEAD"). +/// Names prefixed with "refs/" can be almost anything. You must avoid the characters +/// '~', '^', ':', '\', '?', '[', and '*', and the sequences ".." and "@{" which have +/// special meaning to revparse. +/// This function will throw a [LibGit2Error] if a reference already exists with the given name +/// unless force is true, in which case it will be overwritten. +/// +/// The message for the reflog will be ignored if the reference does not belong in the +/// standard set (HEAD, branches and remote-tracking branches) and it does not have a reflog. +Pointer createDirect( + Pointer repo, + String name, + Pointer oid, + bool force, + String logMessage, +) { + final out = calloc>(); + final nameC = name.toNativeUtf8().cast(); + final forceC = force == true ? 1 : 0; + final logMessageC = logMessage.toNativeUtf8().cast(); + final error = + libgit2.git_reference_create(out, repo, nameC, oid, forceC, logMessageC); + calloc.free(nameC); + calloc.free(logMessageC); + + if (error < 0) { + throw (LibGit2Error(libgit2.git_error_last())); + } else { + return out.value; + } +} + +/// Delete an existing reference. +/// +/// This method works for both direct and symbolic references. +/// The reference will be immediately removed on disk but the memory will not be freed. +/// +/// Throws a [LibGit2Error] if the reference has changed from the time it was looked up. +void delete(Pointer ref) { + final error = libgit2.git_reference_delete(ref); + + if (error < 0) { + throw LibGit2Error(libgit2.git_error_last()); + } +} + +/// Get the repository where a reference resides. +Pointer owner(Pointer ref) { + return libgit2.git_reference_owner(ref); +} + /// Ensure the reference name is well-formed. /// /// Valid reference names must follow one of two patterns: diff --git a/lib/src/bindings/reflog.dart b/lib/src/bindings/reflog.dart new file mode 100644 index 0000000..7be9963 --- /dev/null +++ b/lib/src/bindings/reflog.dart @@ -0,0 +1,56 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import '../error.dart'; +import 'libgit2_bindings.dart'; +import '../util.dart'; + +/// Read the reflog for the given reference. +/// +/// If there is no reflog file for the given reference yet, an empty reflog +/// object will be returned. +/// +/// The reflog must be freed manually. +/// +/// Throws a [LibGit2Error] if error occured. +Pointer read(Pointer repo, String name) { + final out = calloc>(); + final nameC = name.toNativeUtf8().cast(); + final error = libgit2.git_reflog_read(out, repo, nameC); + calloc.free(nameC); + + if (error < 0) { + throw LibGit2Error(libgit2.git_error_last()); + } else { + return out.value; + } +} + +/// Get the number of log entries in a reflog. +int entryCount(Pointer reflog) => + libgit2.git_reflog_entrycount(reflog); + +/// Lookup an entry by its index. +/// +/// Requesting the reflog entry with an index of 0 (zero) will return +/// the most recently created entry. +Pointer entryByIndex(Pointer reflog, int idx) { + return libgit2.git_reflog_entry_byindex(reflog, idx); +} + +/// Get the log message. +String entryMessage(Pointer entry) { + final result = libgit2.git_reflog_entry_message(entry); + return result.cast().toDartString(); +} + +/// Get the committer of this entry. +Map entryCommiter(Pointer entry) { + final result = libgit2.git_reflog_entry_committer(entry); + var committer = {}; + committer['name'] = result.ref.name.cast().toDartString(); + committer['email'] = result.ref.email.cast().toDartString(); + return committer; +} + +/// Free the reflog. +void free(Pointer reflog) => libgit2.git_reflog_free(reflog); diff --git a/lib/src/odb.dart b/lib/src/odb.dart new file mode 100644 index 0000000..44eaf10 --- /dev/null +++ b/lib/src/odb.dart @@ -0,0 +1,31 @@ +import 'dart:ffi'; +import 'bindings/libgit2_bindings.dart'; +import 'bindings/odb.dart' as bindings; +import 'util.dart'; + +class Odb { + /// Initializes a new instance of [Odb] class from provided + /// pointer to Odb object in memory. + Odb(this._odbPointer) { + libgit2.git_libgit2_init(); + } + + /// Pointer to memory address for allocated oid object. + late final Pointer _odbPointer; + + /// Determine if an object can be found in the object database by an abbreviated object ID. + /// + /// Throws a [LibGit2Error] if error occured. + Pointer existsPrefix( + Pointer shortOid, + int len, + ) { + return bindings.existsPrefix(_odbPointer, shortOid, len); + } + + /// Releases memory allocated for odb object. + void free() { + bindings.free(_odbPointer); + libgit2.git_libgit2_shutdown(); + } +} diff --git a/lib/src/oid.dart b/lib/src/oid.dart index 00c7be2..9e513b2 100644 --- a/lib/src/oid.dart +++ b/lib/src/oid.dart @@ -24,8 +24,19 @@ class Oid { } } + /// Initializes a new instance of [Oid] class from provided + /// hexadecimal [sha] string that is lesser than 40 characters long. + /// + /// Throws a [LibGit2Error] if error occured. + Oid.fromSHAn(String sha) { + libgit2.git_libgit2_init(); + _oidPointer = bindings.fromStrN(sha); + } + + late final Pointer _oidPointer; + /// Pointer to memory address for allocated oid object. - late Pointer _oidPointer; + Pointer get pointer => _oidPointer; /// Returns hexadecimal SHA-1 string. String get sha { diff --git a/lib/src/reference.dart b/lib/src/reference.dart index c04b7b1..de51b87 100644 --- a/lib/src/reference.dart +++ b/lib/src/reference.dart @@ -2,7 +2,6 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'bindings/libgit2_bindings.dart'; import 'bindings/reference.dart' as bindings; -import 'repository.dart'; import 'oid.dart'; import 'util.dart'; @@ -15,22 +14,45 @@ class Reference { libgit2.git_libgit2_init(); } + /// Initializes a new instance of the [Reference] class by creating a new direct reference. + /// + /// The direct reference will be created in the repository and written to the disk. + /// The generated [Reference] object must be freed by the user. + /// + /// Valid reference names must follow one of two patterns: + /// + /// Top-level names must contain only capital letters and underscores, and must begin and end + /// with a letter. (e.g. "HEAD", "ORIG_HEAD"). + /// Names prefixed with "refs/" can be almost anything. You must avoid the characters + /// '~', '^', ':', '\', '?', '[', and '*', and the sequences ".." and "@{" which have + /// special meaning to revparse. + /// + /// Throws a [LibGit2Error] if a reference already exists with the given name + /// unless force is true, in which case it will be overwritten. + /// + /// The message for the reflog will be ignored if the reference does not belong in the + /// standard set (HEAD, branches and remote-tracking branches) and it does not have a reflog. + Reference.createDirect({ + required Pointer repo, + required String name, + required Pointer oid, + required bool force, + required String logMessage, + }) { + _refPointer = bindings.createDirect(repo, name, oid, force, logMessage); + } + /// Initializes a new instance of the [Reference] class by - /// lookingup a reference by [name] in a [repository]. + /// lookingup a reference by [name] in a repository. /// /// Should be freed with `free()` to release allocated memory. /// /// The name will be checked for validity. /// /// Throws a [LibGit2Error] if error occured. - Reference.lookup(Repository repository, String name) { + Reference.lookup(Pointer repo, String name) { libgit2.git_libgit2_init(); - - try { - _refPointer = bindings.lookup(repository.pointer, name); - } catch (e) { - rethrow; - } + _refPointer = bindings.lookup(repo, name); } /// Pointer to memory address for allocated reference object. @@ -60,18 +82,18 @@ class Reference { : ReferenceType.symbolic; } - /// Returns the SHA hex of the OID pointed to by a reference. - String get target { - late final Pointer? oidPointer; - final sha = ''; + /// Returns the OID pointed to by a reference. + /// + /// Throws an exception if error occured. + Oid get target { + late final Pointer oidPointer; if (type == ReferenceType.direct) { oidPointer = bindings.target(_refPointer); } else { oidPointer = bindings.target(bindings.resolve(_refPointer)); } - - return oidPointer == nullptr ? sha : Oid(oidPointer!).sha; + return Oid(oidPointer); } /// Returns the full name of a reference. @@ -81,22 +103,14 @@ class Reference { /// /// Throws a [LibGit2Error] if error occured. static List list(Pointer repo) { - try { - return bindings.list(repo); - } catch (e) { - rethrow; - } + return bindings.list(repo); } /// Checks if a reflog exists for the specified reference [name]. /// /// Throws a [LibGit2Error] if error occured. - static bool hasLog(Repository repo, String name) { - try { - return bindings.hasLog(repo.pointer, name); - } catch (e) { - rethrow; - } + static bool hasLog(Pointer repo, String name) { + return bindings.hasLog(repo, name); } /// Checks if a reference is a local branch. @@ -111,6 +125,17 @@ class Reference { /// Check if a reference is a tag. bool get isTag => bindings.isTag(_refPointer); + /// Returns the repository where a reference resides. + Pointer get owner => bindings.owner(_refPointer); + + /// Delete an existing reference. + /// + /// This method works for both direct and symbolic references. + /// The reference will be immediately removed on disk but the memory will not be freed. + /// + /// Throws a [LibGit2Error] if the reference has changed from the time it was looked up. + void delete() => bindings.delete(_refPointer); + /// Releases memory allocated for reference object. void free() { calloc.free(_refPointer); diff --git a/lib/src/reflog.dart b/lib/src/reflog.dart new file mode 100644 index 0000000..ef97cd8 --- /dev/null +++ b/lib/src/reflog.dart @@ -0,0 +1,52 @@ +import 'dart:ffi'; +import 'bindings/libgit2_bindings.dart'; +import 'bindings/reflog.dart' as bindings; +import 'reference.dart'; +import 'util.dart'; + +class RefLog { + /// Initializes a new instance of [RefLog] class from provided [Reference]. + /// + /// Throws a [LibGit2Error] if error occured. + RefLog(Reference ref) { + libgit2.git_libgit2_init(); + + final repo = ref.owner; + final name = ref.name; + _reflogPointer = bindings.read(repo, name); + } + + /// Pointer to memory address for allocated reflog object. + late final Pointer _reflogPointer; + + /// Returns the number of log entries in a reflog. + int get count => bindings.entryCount(_reflogPointer); + + /// Lookup an entry by its index. + /// + /// Requesting the reflog entry with an index of 0 (zero) will return + /// the most recently created entry. + RefLogEntry entryAt(int index) { + return RefLogEntry(bindings.entryByIndex(_reflogPointer, index)); + } + + /// Releases memory allocated for reflog object. + void free() { + bindings.free(_reflogPointer); + libgit2.git_libgit2_shutdown(); + } +} + +class RefLogEntry { + /// Initializes a new instance of [RefLogEntry] class. + RefLogEntry(this._entryPointer); + + /// Pointer to memory address for allocated reflog entry object. + late final Pointer _entryPointer; + + /// Returns the log message. + String get message => bindings.entryMessage(_entryPointer); + + /// Returns the committer of this entry. + Map get committer => bindings.entryCommiter(_entryPointer); +} diff --git a/lib/src/repository.dart b/lib/src/repository.dart index f23474e..4f9876f 100644 --- a/lib/src/repository.dart +++ b/lib/src/repository.dart @@ -1,5 +1,7 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; +import 'odb.dart'; +import 'oid.dart'; import 'reference.dart'; import 'bindings/libgit2_bindings.dart'; import 'bindings/repository.dart' as bindings; @@ -25,10 +27,8 @@ class Repository { } } - late final Pointer _repoPointer; - /// Pointer to memory address for allocated repository object. - Pointer get pointer => _repoPointer; + late final Pointer _repoPointer; /// Returns path to the `.git` folder for normal repositories /// or path to the repository itself for bare repositories. @@ -89,17 +89,84 @@ class Repository { } } - /// Returns reference object pointing to repository head. + /// Creates a new reference. + /// + /// The reference will be created in the repository and written to the disk. + /// The generated [Reference] object must be freed by the user. + /// + /// Valid reference names must follow one of two patterns: + /// + /// Top-level names must contain only capital letters and underscores, and must begin and end + /// with a letter. (e.g. "HEAD", "ORIG_HEAD"). + /// Names prefixed with "refs/" can be almost anything. You must avoid the characters + /// '~', '^', ':', '\', '?', '[', and '*', and the sequences ".." and "@{" which have + /// special meaning to revparse. + /// Throws a [LibGit2Error] if a reference already exists with the given name + /// unless force is true, in which case it will be overwritten. + /// + /// The message for the reflog will be ignored if the reference does not belong in the + /// standard set (HEAD, branches and remote-tracking branches) and it does not have a reflog. + Reference createReference({ + required String name, + required Object target, + bool force = false, + String logMessage = '', + }) { + late final Oid oid; + late final bool isDirect; + + if (target.runtimeType == Oid) { + oid = target as Oid; + isDirect = true; + } else if (_isValidShaHex(target as String)) { + if (target.length == 40) { + oid = Oid.fromSHA(target); + } else { + final shortOid = Oid.fromSHAn(target); + final odb = this.odb; + oid = Oid(odb.existsPrefix(shortOid.pointer, target.length)); + odb.free(); + } + isDirect = true; + } else { + oid = getReference(target).target; + isDirect = false; + } + + if (isDirect) { + return Reference.createDirect( + repo: _repoPointer, + name: name, + oid: oid.pointer, + force: force, + logMessage: logMessage, + ); + } else { + throw UnimplementedError; + } + } + + /// Returns [Reference] object pointing to repository head. Reference get head => Reference(bindings.head(_repoPointer)); - /// Returns a map with all the references names and corresponding SHA hexes + /// Returns [Reference] object by lookingup a [name] in repository. + /// + /// Throws a [LibGit2Error] if error occured. + Reference getReference(String name) => Reference.lookup(_repoPointer, name); + + /// Checks if a reflog exists for the specified reference [name]. + /// + /// Throws a [LibGit2Error] if error occured. + bool referenceHasLog(String name) => Reference.hasLog(_repoPointer, name); + + /// Returns a map with all the references names and corresponding SHA hashes /// that can be found in a repository. Map get references { var refMap = {}; final refList = Reference.list(_repoPointer); for (var ref in refList) { - final r = Reference.lookup(this, ref); - refMap[ref] = r.target; + final r = getReference(ref); + refMap[ref] = r.target.sha; r.free(); } @@ -206,4 +273,17 @@ class Repository { calloc.free(_repoPointer); libgit2.git_libgit2_shutdown(); } + + /// Returns [Odb] for this repository. + /// + /// ODB Object must be freed once it's no longer being used. + /// + /// Throws a [LibGit2Error] if error occured. + Odb get odb => Odb(bindings.odb(_repoPointer)); + + bool _isValidShaHex(String str) { + final hexRegExp = RegExp(r'^[0-9a-fA-F]+$'); + return hexRegExp.hasMatch(str) && + (GIT_OID_MINPREFIXLEN <= str.length && GIT_OID_HEXSZ >= str.length); + } } diff --git a/test/odb_test.dart b/test/odb_test.dart new file mode 100644 index 0000000..c39826f --- /dev/null +++ b/test/odb_test.dart @@ -0,0 +1,46 @@ +import 'dart:io'; + +import 'package:test/test.dart'; +import 'package:libgit2dart/src/repository.dart'; +import 'package:libgit2dart/src/odb.dart'; +import 'package:libgit2dart/src/oid.dart'; + +import 'helpers/util.dart'; + +void main() { + const lastCommit = '78b8bf123e3952c970ae5c1ce0a3ea1d1336f6e8'; + + group('Odb', () { + late Repository repo; + final tmpDir = '${Directory.systemTemp.path}/odb_testrepo/'; + + setUpAll(() 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); + }); + + tearDownAll(() async { + repo.close(); + await Directory(tmpDir).delete(recursive: true); + }); + + test('successfully initializes', () { + expect(repo.odb, isA()); + repo.odb.free(); + }); + + test('finds object by short oid', () { + final shortSha = '78b8bf'; + final shortOid = Oid.fromSHAn(shortSha); + final oid = repo.odb.existsPrefix(shortOid.pointer, shortSha.length); + expect(Oid(oid).sha, lastCommit); + repo.odb.free(); + }); + }); +} diff --git a/test/oid_test.dart b/test/oid_test.dart index 6e6607e..36f8a40 100644 --- a/test/oid_test.dart +++ b/test/oid_test.dart @@ -1,6 +1,5 @@ import 'package:test/test.dart'; import 'package:libgit2dart/src/oid.dart'; -import 'package:libgit2dart/src/error.dart'; void main() { const sha = '9d81c715ff606057fa448e558c7458467a86c8c7'; @@ -10,9 +9,12 @@ void main() { test('initializes successfully', () { expect(Oid.fromSHA(sha), isA()); }); + }); - test('throws when hex string is lesser than 40 characters', () { - expect(() => Oid.fromSHA('9d8'), throwsA(isA())); + group('fromSHAn()', () { + test('initializes successfully from short hex string', () { + final oid = Oid.fromSHAn('9d81'); + expect(oid, isA()); }); }); diff --git a/test/reference_test.dart b/test/reference_test.dart index 05bf714..a911e8d 100644 --- a/test/reference_test.dart +++ b/test/reference_test.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:libgit2dart/src/reflog.dart'; import 'package:test/test.dart'; import 'package:libgit2dart/src/repository.dart'; import 'package:libgit2dart/src/reference.dart'; @@ -11,8 +12,8 @@ void main() { const lastCommit = '78b8bf123e3952c970ae5c1ce0a3ea1d1336f6e8'; group('Reference', () { - late Repository repo; - final tmpDir = '${Directory.systemTemp.path}/testrepo/'; + late final Repository repo; + final tmpDir = '${Directory.systemTemp.path}/ref_testrepo/'; setUpAll(() async { if (await Directory(tmpDir).exists()) { @@ -30,23 +31,150 @@ void main() { await Directory(tmpDir).delete(recursive: true); }); + group('.createDirect()', () { + test('successfully creates with Oid as target', () { + final ref = repo.getReference('refs/heads/master'); + final refFromOid = repo.createReference( + name: 'refs/tags/from.oid', + target: ref.target, + ); + + expect(repo.references, contains('refs/tags/from.oid')); + + refFromOid.delete(); + refFromOid.free(); + ref.free(); + }); + + test('successfully creates with SHA hash as target', () { + final refFromHash = repo.createReference( + name: 'refs/tags/from.hash', + target: lastCommit, + ); + + expect(repo.references, contains('refs/tags/from.hash')); + + refFromHash.delete(); + refFromHash.free(); + }); + + test('successfully creates with short SHA hash as target', () { + final refFromHash = repo.createReference( + name: 'refs/tags/from.short.hash', + target: '78b8bf', + ); + + expect(repo.references, contains('refs/tags/from.short.hash')); + + refFromHash.delete(); + refFromHash.free(); + }); + + test('successfully creates with log message', () { + repo.setIdentity(name: 'name', email: 'email'); + final ref = repo.createReference( + name: 'refs/heads/log.message', + target: lastCommit, + logMessage: 'log message', + ); + + final reflog = RefLog(ref); + final reflogEntry = reflog.entryAt(0); + + expect(reflogEntry.message, 'log message'); + expect(reflogEntry.committer, {'name': 'name', 'email': 'email'}); + + reflog.free(); + ref.delete(); + ref.free(); + }); + + test('throws if target is not valid', () { + expect( + () => repo.createReference( + name: 'refs/tags/invalid', + target: '78b', + ), + throwsA(isA()), + ); + }); + + test('throws if name is not valid', () { + expect( + () => repo.createReference( + name: 'refs/tags/invalid~', + target: lastCommit, + ), + throwsA(isA()), + ); + }); + + test('successfully creates with force flag if name already exists', () { + final ref = repo.createReference( + name: 'refs/tags/test', + target: lastCommit, + ); + + final forceRef = repo.createReference( + name: 'refs/tags/test', + target: lastCommit, + force: true, + ); + + expect(forceRef.target.sha, lastCommit); + + forceRef.delete(); + ref.free(); + forceRef.free(); + }); + + test('throws if name already exists', () { + final ref = repo.createReference( + name: 'refs/tags/test', + target: lastCommit, + ); + + expect( + () => repo.createReference( + name: 'refs/tags/test', + target: lastCommit, + ), + throwsA(isA()), + ); + + ref.delete(); + ref.free(); + }); + }); + + test('successfully deletes reference', () { + final ref = repo.createReference( + name: 'refs/tags/test', + target: lastCommit, + ); + expect(repo.references, contains('refs/tags/test')); + + ref.delete(); + expect(repo.references, isNot(contains('refs/tags/test'))); + }); + test('returns correct type of reference', () { expect(repo.head.type, ReferenceType.direct); repo.head.free(); - final ref = Reference.lookup(repo, 'HEAD'); + final ref = repo.getReference('HEAD'); expect(ref.type, ReferenceType.symbolic); ref.free(); }); test('returns SHA hex of direct reference', () { - expect(repo.head.target, lastCommit); + expect(repo.head.target.sha, lastCommit); repo.head.free(); }); test('returns SHA hex of symbolic reference', () { - final ref = Reference.lookup(repo, 'HEAD'); - expect(ref.target, lastCommit); + final ref = repo.getReference('HEAD'); + expect(ref.target.sha, lastCommit); ref.free(); }); @@ -67,44 +195,50 @@ void main() { }); test('checks if reflog exists for the reference', () { - expect(Reference.hasLog(repo, 'refs/heads/master'), true); - expect(Reference.hasLog(repo, 'refs/heads/not/there'), false); + expect(repo.referenceHasLog('refs/heads/master'), true); + expect(repo.referenceHasLog('refs/tags/v0.1'), false); }); test('checks if reference is a local branch', () { - final ref = Reference.lookup(repo, 'refs/heads/feature'); + final ref = repo.getReference('refs/heads/feature'); expect(ref.isBranch, true); ref.free(); }); test('checks if reference is a note', () { - final ref = Reference.lookup(repo, 'refs/heads/master'); + final ref = repo.getReference('refs/heads/master'); expect(ref.isNote, false); ref.free(); }); test('checks if reference is a remote branch', () { - final ref = Reference.lookup(repo, 'refs/heads/master'); - expect(ref.isRemote, false); + final ref = repo.createReference( + name: 'refs/remotes/origin/master', + target: lastCommit, + ); + + expect(ref.isRemote, true); + + ref.delete(); ref.free(); }); test('checks if reference is a tag', () { - final ref = Reference.lookup(repo, 'refs/tags/v0.1'); + final ref = repo.getReference('refs/tags/v0.1'); expect(ref.isTag, true); ref.free(); }); group('.lookup()', () { test('finds a reference with provided name', () { - final ref = Reference.lookup(repo, 'refs/heads/master'); - expect(ref.target, lastCommit); + final ref = repo.getReference('refs/heads/master'); + expect(ref.target.sha, lastCommit); ref.free(); }); test('throws when error occured', () { expect( - () => Reference.lookup(repo, 'refs/heads/not/there'), + () => repo.getReference('refs/heads/not/there'), throwsA(isA()), ); }); diff --git a/test/reflog_test.dart b/test/reflog_test.dart new file mode 100644 index 0000000..c1a7d38 --- /dev/null +++ b/test/reflog_test.dart @@ -0,0 +1,56 @@ +import 'dart:io'; + +import 'package:test/test.dart'; +import 'package:libgit2dart/src/repository.dart'; +import 'package:libgit2dart/src/reflog.dart'; + +import 'helpers/util.dart'; + +void main() { + group('RefLog', () { + late final Repository repo; + late final RefLog reflog; + final tmpDir = '${Directory.systemTemp.path}/reflog_testrepo/'; + + setUpAll(() 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); + reflog = RefLog(repo.head); + }); + + tearDownAll(() async { + repo.head.free(); + reflog.free(); + repo.close(); + await Directory(tmpDir).delete(recursive: true); + }); + + test('initializes successfully', () { + expect(reflog, isA()); + }); + + test('returns correct number of log entries', () { + expect(reflog.count, 3); + }); + + test('returns the log message', () { + final entry = reflog.entryAt(0); + expect( + entry.message, + "merge feature: Merge made by the 'recursive' strategy.", + ); + }); + + test('returns the committer of the entry', () { + final entry = reflog.entryAt(0); + expect(entry.committer['name'], 'Aleksey Kulikov'); + expect(entry.committer['email'], 'skinny.mind@gmail.com'); + }); + }); +} diff --git a/test/repository_test.dart b/test/repository_test.dart index b3ea7a4..e6c4963 100644 --- a/test/repository_test.dart +++ b/test/repository_test.dart @@ -6,9 +6,8 @@ import 'package:libgit2dart/src/error.dart'; import 'helpers/util.dart'; void main() { - late Repository repo; - group('Repository', () { + late Repository repo; test('throws when repository isn\'t found at provided path', () { expect( () => Repository.open(''),