feat(reflog): add more bindings and API methods (#29)

This commit is contained in:
Aleksey Kulikov 2021-12-23 10:58:44 +03:00 committed by GitHub
parent ff2dd8b408
commit fda5173e7f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 305 additions and 12 deletions

View file

@ -2,6 +2,7 @@ import 'dart:ffi';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
import 'package:libgit2dart/src/bindings/libgit2_bindings.dart'; import 'package:libgit2dart/src/bindings/libgit2_bindings.dart';
import 'package:libgit2dart/src/error.dart';
import 'package:libgit2dart/src/util.dart'; import 'package:libgit2dart/src/util.dart';
/// Read the reflog for the given reference. /// Read the reflog for the given reference.
@ -23,6 +24,90 @@ Pointer<git_reflog> read({
return out.value; return out.value;
} }
/// Write an existing in-memory reflog object back to disk using an atomic file
/// lock.
void write(Pointer<git_reflog> reflog) {
final error = libgit2.git_reflog_write(reflog);
if (error < 0) {
throw LibGit2Error(libgit2.git_error_last());
}
}
/// Delete the reflog for the given reference.
void delete({
required Pointer<git_repository> repoPointer,
required String name,
}) {
final nameC = name.toNativeUtf8().cast<Int8>();
libgit2.git_reflog_delete(repoPointer, nameC);
calloc.free(nameC);
}
/// Rename a reflog.
///
/// The reflog to be renamed is expected to already exist.
///
/// The new name will be checked for validity.
///
/// Throws a [LibGit2Error] if error occured.
void rename({
required Pointer<git_repository> repoPointer,
required String oldName,
required String newName,
}) {
final oldNameC = oldName.toNativeUtf8().cast<Int8>();
final newNameC = newName.toNativeUtf8().cast<Int8>();
final error = libgit2.git_reflog_rename(repoPointer, oldNameC, newNameC);
calloc.free(oldNameC);
calloc.free(newNameC);
if (error < 0) {
throw LibGit2Error(libgit2.git_error_last());
}
}
/// Add a new entry to the in-memory reflog.
///
/// Throws a [LibGit2Error] if error occured.
void add({
required Pointer<git_reflog> reflogPointer,
required Pointer<git_oid> oidPointer,
required Pointer<git_signature> committerPointer,
required String message,
}) {
final messageC =
message.isEmpty ? nullptr : message.toNativeUtf8().cast<Int8>();
final error = libgit2.git_reflog_append(
reflogPointer,
oidPointer,
committerPointer,
messageC,
);
calloc.free(messageC);
if (error < 0) {
throw LibGit2Error(libgit2.git_error_last());
}
}
/// Remove an entry from the reflog by its index.
///
/// Throws a [LibGit2Error] if error occured.
void remove({
required Pointer<git_reflog> reflogPointer,
required int index,
}) {
final error = libgit2.git_reflog_drop(reflogPointer, index, 1);
if (error < 0) {
throw LibGit2Error(libgit2.git_error_last());
}
}
/// Get the number of log entries in a reflog. /// Get the number of log entries in a reflog.
int entryCount(Pointer<git_reflog> reflog) => int entryCount(Pointer<git_reflog> reflog) =>
libgit2.git_reflog_entrycount(reflog); libgit2.git_reflog_entrycount(reflog);
@ -40,7 +125,8 @@ Pointer<git_reflog_entry> getByIndex({
/// Get the log message. /// Get the log message.
String entryMessage(Pointer<git_reflog_entry> entry) { String entryMessage(Pointer<git_reflog_entry> entry) {
return libgit2.git_reflog_entry_message(entry).cast<Utf8>().toDartString(); final result = libgit2.git_reflog_entry_message(entry);
return result == nullptr ? '' : result.cast<Utf8>().toDartString();
} }
/// Get the committer of this entry. /// Get the committer of this entry.
@ -48,5 +134,15 @@ Pointer<git_signature> entryCommiter(Pointer<git_reflog_entry> entry) {
return libgit2.git_reflog_entry_committer(entry); return libgit2.git_reflog_entry_committer(entry);
} }
/// Get the new oid.
Pointer<git_oid> entryOidNew(Pointer<git_reflog_entry> entry) {
return libgit2.git_reflog_entry_id_new(entry);
}
/// Get the old oid.
Pointer<git_oid> entryOidOld(Pointer<git_reflog_entry> entry) {
return libgit2.git_reflog_entry_id_old(entry);
}
/// Free the reflog. /// Free the reflog.
void free(Pointer<git_reflog> reflog) => libgit2.git_reflog_free(reflog); void free(Pointer<git_reflog> reflog) => libgit2.git_reflog_free(reflog);

View file

@ -139,6 +139,19 @@ class Reference {
refdb_bindings.free(refdb); refdb_bindings.free(refdb);
} }
/// Ensures there is a reflog for a particular reference.
///
/// Makes sure that successive updates to the reference will append to its
/// log.
///
/// Throws a [LibGit2Error] if error occured.
static void ensureLog({
required Repository repo,
required String refName,
}) {
bindings.ensureLog(repoPointer: repo.pointer, refName: refName);
}
/// Creates a copy of an existing reference. /// Creates a copy of an existing reference.
/// ///
/// **IMPORTANT**: Should be freed to release allocated memory. /// **IMPORTANT**: Should be freed to release allocated memory.
@ -247,17 +260,6 @@ class Reference {
); );
} }
/// Ensures there is a reflog for a particular reference.
///
/// Makes sure that successive updates to the reference will append to its
/// log.
///
/// Throws a [LibGit2Error] if error occured.
void ensureLog() {
final owner = bindings.owner(_refPointer);
bindings.ensureLog(repoPointer: owner, refName: name);
}
/// [RefLog] object. /// [RefLog] object.
/// ///
/// **IMPORTANT**: Should be freed to release allocated memory. /// **IMPORTANT**: Should be freed to release allocated memory.

View file

@ -18,6 +18,30 @@ class RefLog with IterableMixin<RefLogEntry> {
/// Pointer to memory address for allocated reflog object. /// Pointer to memory address for allocated reflog object.
late final Pointer<git_reflog> _reflogPointer; late final Pointer<git_reflog> _reflogPointer;
/// Deletes the reflog for the given reference.
static void delete(Reference ref) {
bindings.delete(repoPointer: ref.owner.pointer, name: ref.name);
}
/// Renames a reflog.
///
/// The reflog to be renamed is expected to already exist.
///
/// The new name will be checked for validity.
///
/// Throws a [LibGit2Error] if error occured.
static void rename({
required Repository repo,
required String oldName,
required String newName,
}) {
bindings.rename(
repoPointer: repo.pointer,
oldName: oldName,
newName: newName,
);
}
/// Lookups an entry by its index. /// Lookups an entry by its index.
/// ///
/// Requesting the reflog entry with an index of 0 will return the most /// Requesting the reflog entry with an index of 0 will return the most
@ -31,6 +55,39 @@ class RefLog with IterableMixin<RefLogEntry> {
); );
} }
/// Adds a new entry to the in-memory reflog.
///
/// [oid] is the OID the reference is now pointing to.
///
/// [committer] is the signature of the committer.
///
/// [message] is optional reflog message.
///
/// Throws a [LibGit2Error] if error occured.
void add({
required Oid oid,
required Signature committer,
String message = '',
}) {
bindings.add(
reflogPointer: _reflogPointer,
oidPointer: oid.pointer,
committerPointer: committer.pointer,
message: message,
);
}
/// Removes an entry from the reflog by its [index].
///
/// Throws a [LibGit2Error] if error occured.
void remove(int index) {
bindings.remove(reflogPointer: _reflogPointer, index: index);
}
/// Writes an existing in-memory reflog object back to disk using an atomic
/// file lock.
void write() => bindings.write(_reflogPointer);
/// Releases memory allocated for reflog object. /// Releases memory allocated for reflog object.
void free() => bindings.free(_reflogPointer); void free() => bindings.free(_reflogPointer);
@ -47,11 +104,19 @@ class RefLogEntry {
final Pointer<git_reflog_entry> _entryPointer; final Pointer<git_reflog_entry> _entryPointer;
/// Log message. /// Log message.
///
/// Returns empty string if there is no message.
String get message => bindings.entryMessage(_entryPointer); String get message => bindings.entryMessage(_entryPointer);
/// Committer of this entry. /// Committer of this entry.
Signature get committer => Signature(bindings.entryCommiter(_entryPointer)); Signature get committer => Signature(bindings.entryCommiter(_entryPointer));
/// New oid of entry at this time.
Oid get newOid => Oid(bindings.entryOidNew(_entryPointer));
/// Old oid of entry.
Oid get oldOid => Oid(bindings.entryOidOld(_entryPointer));
@override @override
String toString() => 'RefLogEntry{message: $message, committer: $committer}'; String toString() => 'RefLogEntry{message: $message, committer: $committer}';
} }

View file

@ -128,6 +128,31 @@ void main() {
ref.free(); ref.free();
}); });
test('ensures updates to the reference will append to its log', () {
Reference.ensureLog(repo: repo, refName: 'refs/tags/tag');
final ref = repo.createReference(
name: 'refs/tags/tag',
target: repo[lastCommit],
);
final reflog = ref.log;
expect(reflog.length, 1);
reflog.free();
ref.free();
});
test('throws when trying to ensure there is a reflog and error occurs', () {
expect(
() => Reference.ensureLog(
repo: Repository(nullptr),
refName: 'refs/tags/tag',
),
throwsA(isA<LibGit2Error>()),
);
});
test('duplicates existing reference', () { test('duplicates existing reference', () {
expect(repo.references.length, 6); expect(repo.references.length, 6);

View file

@ -1,3 +1,4 @@
import 'dart:ffi';
import 'dart:io'; import 'dart:io';
import 'package:libgit2dart/libgit2dart.dart'; import 'package:libgit2dart/libgit2dart.dart';
@ -44,6 +45,110 @@ void main() {
expect(reflog[0].committer.time, 1630568461); expect(reflog[0].committer.time, 1630568461);
}); });
test('returns new and old oids of entry', () {
expect(reflog[0].newOid.sha, '821ed6e80627b8769d170a293862f9fc60825226');
expect(reflog.last.oldOid.sha, '0' * 40);
});
test('deletes the reflog of provided reference', () {
expect(head.hasLog, true);
RefLog.delete(head);
expect(head.hasLog, false);
});
test('renames existing reflog', () {
expect(
File('${repo.workdir}.git/logs/refs/heads/master').existsSync(),
true,
);
expect(
File('${repo.workdir}.git/logs/refs/heads/renamed').existsSync(),
false,
);
RefLog.rename(
repo: repo,
oldName: 'refs/heads/master',
newName: 'refs/heads/renamed',
);
expect(
File('${repo.workdir}.git/logs/refs/heads/master').existsSync(),
false,
);
expect(
File('${repo.workdir}.git/logs/refs/heads/renamed').existsSync(),
true,
);
});
test('throws when trying to rename reflog and provided new name is invalid',
() {
expect(
() => RefLog.rename(
repo: repo,
oldName: 'refs/heads/master',
newName: '',
),
throwsA(isA<LibGit2Error>()),
);
});
test('adds a new entry to the in-memory reflog', () {
final committer = Signature.create(
name: 'Commiter',
email: 'commiter@email.com',
time: 124,
);
expect(reflog.length, 4);
reflog.add(oid: head.target, committer: committer);
expect(reflog.length, 5);
reflog.add(oid: head.target, committer: committer, message: 'new entry');
expect(reflog.length, 6);
expect(reflog[0].message, 'new entry');
committer.free();
});
test('throws when trying to add new entry', () {
expect(
() => reflog.add(oid: head.target, committer: Signature(nullptr)),
throwsA(isA<LibGit2Error>()),
);
});
test('removes entry from reflog with provided index', () {
expect(reflog.length, 4);
expect(reflog[0].message, 'commit: add subdirectory file');
reflog.remove(0);
expect(reflog.length, 3);
expect(
reflog[0].message,
"merge feature: Merge made by the 'recursive' strategy.",
);
});
test('throws when trying to remove entry from reflog at invalid index', () {
expect(() => reflog.remove(-1), throwsA(isA<LibGit2Error>()));
});
test('writes in-memory reflog to disk', () {
expect(reflog.length, 4);
reflog.remove(0);
// making sure change is only in memory
final oldReflog = RefLog(head);
expect(oldReflog.length, 4);
reflog.write();
final newReflog = RefLog(head);
expect(newReflog.length, 3);
});
test('returns string representation of RefLogEntry object', () { test('returns string representation of RefLogEntry object', () {
expect(reflog[0].toString(), contains('RefLogEntry{')); expect(reflog[0].toString(), contains('RefLogEntry{'));
}); });