feat(reference): add ability to create direct reference

This commit is contained in:
Aleksey Kulikov 2021-08-05 19:48:58 +03:00
parent 6643527f2d
commit 9190ed2e0f
16 changed files with 669 additions and 60 deletions

26
lib/src/bindings/odb.dart Normal file
View file

@ -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<git_oid> existsPrefix(
Pointer<git_odb> db,
Pointer<git_oid> shortOid,
int len,
) {
final out = calloc<git_oid>();
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<git_odb> db) => libgit2.git_odb_free(db);

View file

@ -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<git_oid> fromStrN(String hex) {
final out = calloc<git_oid>();
final str = hex.toNativeUtf8().cast<Int8>();
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.

View file

@ -11,8 +11,17 @@ int referenceType(Pointer<git_reference> 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<git_oid>? target(Pointer<git_reference> ref) =>
libgit2.git_reference_target(ref);
///
/// Throws an exception if error occured.
Pointer<git_oid> target(Pointer<git_reference> 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<git_reference> 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<git_reference> createDirect(
Pointer<git_repository> repo,
String name,
Pointer<git_oid> oid,
bool force,
String logMessage,
) {
final out = calloc<Pointer<git_reference>>();
final nameC = name.toNativeUtf8().cast<Int8>();
final forceC = force == true ? 1 : 0;
final logMessageC = logMessage.toNativeUtf8().cast<Int8>();
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<git_reference> 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<git_repository> owner(Pointer<git_reference> ref) {
return libgit2.git_reference_owner(ref);
}
/// Ensure the reference name is well-formed.
///
/// Valid reference names must follow one of two patterns:

View file

@ -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<git_reflog> read(Pointer<git_repository> repo, String name) {
final out = calloc<Pointer<git_reflog>>();
final nameC = name.toNativeUtf8().cast<Int8>();
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<git_reflog> 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<git_reflog_entry> entryByIndex(Pointer<git_reflog> reflog, int idx) {
return libgit2.git_reflog_entry_byindex(reflog, idx);
}
/// Get the log message.
String entryMessage(Pointer<git_reflog_entry> entry) {
final result = libgit2.git_reflog_entry_message(entry);
return result.cast<Utf8>().toDartString();
}
/// Get the committer of this entry.
Map<String, String> entryCommiter(Pointer<git_reflog_entry> entry) {
final result = libgit2.git_reflog_entry_committer(entry);
var committer = <String, String>{};
committer['name'] = result.ref.name.cast<Utf8>().toDartString();
committer['email'] = result.ref.email.cast<Utf8>().toDartString();
return committer;
}
/// Free the reflog.
void free(Pointer<git_reflog> reflog) => libgit2.git_reflog_free(reflog);

31
lib/src/odb.dart Normal file
View file

@ -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<git_odb> _odbPointer;
/// Determine if an object can be found in the object database by an abbreviated object ID.
///
/// Throws a [LibGit2Error] if error occured.
Pointer<git_oid> existsPrefix(
Pointer<git_oid> shortOid,
int len,
) {
return bindings.existsPrefix(_odbPointer, shortOid, len);
}
/// Releases memory allocated for odb object.
void free() {
bindings.free(_odbPointer);
libgit2.git_libgit2_shutdown();
}
}

View file

@ -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<git_oid> _oidPointer;
/// Pointer to memory address for allocated oid object.
late Pointer<git_oid> _oidPointer;
Pointer<git_oid> get pointer => _oidPointer;
/// Returns hexadecimal SHA-1 string.
String get sha {

View file

@ -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<git_repository> repo,
required String name,
required Pointer<git_oid> 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<git_repository> 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<git_oid>? oidPointer;
final sha = '';
/// Returns the OID pointed to by a reference.
///
/// Throws an exception if error occured.
Oid get target {
late final Pointer<git_oid> 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<String> list(Pointer<git_repository> 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<git_repository> 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<git_repository> 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);

52
lib/src/reflog.dart Normal file
View file

@ -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<git_reflog> _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<git_reflog_entry> _entryPointer;
/// Returns the log message.
String get message => bindings.entryMessage(_entryPointer);
/// Returns the committer of this entry.
Map<String, String> get committer => bindings.entryCommiter(_entryPointer);
}

View file

@ -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<git_repository> _repoPointer;
/// Pointer to memory address for allocated repository object.
Pointer<git_repository> get pointer => _repoPointer;
late final Pointer<git_repository> _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<String, String> get references {
var refMap = <String, String>{};
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);
}
}