From 371d52b7f8f064b9c227969524482694d9fb8116 Mon Sep 17 00:00:00 2001 From: Aleksey Kulikov Date: Fri, 6 Aug 2021 16:44:50 +0300 Subject: [PATCH] feat(reference): add ability to set target --- lib/src/bindings/reference.dart | 62 ++++++++++++++++++++++++++++++--- lib/src/reference.dart | 39 +++++++++++++++++++-- lib/src/repository.dart | 10 ++---- lib/src/util.dart | 6 ++++ test/reference_test.dart | 45 ++++++++++++++++++++++++ 5 files changed, 147 insertions(+), 15 deletions(-) diff --git a/lib/src/bindings/reference.dart b/lib/src/bindings/reference.dart index 1330576..af1d3cb 100644 --- a/lib/src/bindings/reference.dart +++ b/lib/src/bindings/reference.dart @@ -163,12 +163,12 @@ Pointer createDirect( String name, Pointer oid, bool force, - String logMessage, + String? logMessage, ) { final out = calloc>(); final nameC = name.toNativeUtf8().cast(); final forceC = force == true ? 1 : 0; - final logMessageC = logMessage.toNativeUtf8().cast(); + final logMessageC = logMessage?.toNativeUtf8().cast() ?? nullptr; final error = libgit2.git_reference_create( out, repo, @@ -214,13 +214,13 @@ Pointer createSymbolic( String name, String target, bool force, - String logMessage, + String? logMessage, ) { final out = calloc>(); final nameC = name.toNativeUtf8().cast(); final targetC = target.toNativeUtf8().cast(); final forceC = force == true ? 1 : 0; - final logMessageC = logMessage.toNativeUtf8().cast(); + final logMessageC = logMessage?.toNativeUtf8().cast() ?? nullptr; final error = libgit2.git_reference_symbolic_create( out, repo, @@ -259,6 +259,60 @@ Pointer owner(Pointer ref) { return libgit2.git_reference_owner(ref); } +/// Conditionally create a new reference with the same name as the given reference +/// but a different OID target. The reference must be a direct reference, otherwise this will fail. +/// +/// The new reference will be written to disk, overwriting the given reference. +/// +/// Throws a [LibGit2Error] if error occured. +Pointer setTarget( + Pointer ref, + Pointer oid, + String? logMessage, +) { + final out = calloc>(); + final logMessageC = logMessage?.toNativeUtf8().cast() ?? nullptr; + final error = libgit2.git_reference_set_target(out, ref, oid, logMessageC); + calloc.free(logMessageC); + + if (error < 0) { + throw LibGit2Error(libgit2.git_error_last()); + } else { + return out.value; + } +} + +/// Create a new reference with the same name as the given reference but a different +/// symbolic target. The reference must be a symbolic reference, otherwise this will fail. +/// +/// The new reference will be written to disk, overwriting the given reference. +/// +/// The target name will be checked for validity. +/// +/// 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 and it does not have a reflog. +/// +/// Throws a [LibGit2Error] if error occured. +Pointer setTargetSymbolic( + Pointer ref, + String target, + String? logMessage, +) { + final out = calloc>(); + final targetC = target.toNativeUtf8().cast(); + final logMessageC = logMessage?.toNativeUtf8().cast() ?? nullptr; + final error = + libgit2.git_reference_symbolic_set_target(out, ref, targetC, logMessageC); + calloc.free(targetC); + calloc.free(logMessageC); + + if (error < 0) { + throw LibGit2Error(libgit2.git_error_last()); + } else { + return out.value; + } +} + /// Ensure the reference name is well-formed. /// /// Valid reference names must follow one of two patterns: diff --git a/lib/src/reference.dart b/lib/src/reference.dart index 37c2a63..b521d13 100644 --- a/lib/src/reference.dart +++ b/lib/src/reference.dart @@ -1,6 +1,8 @@ import 'dart:ffi'; import 'bindings/libgit2_bindings.dart'; import 'bindings/reference.dart' as bindings; +import 'bindings/repository.dart' as repo_bindings; +import 'odb.dart'; import 'oid.dart'; import 'reflog.dart'; import 'util.dart'; @@ -37,7 +39,7 @@ class Reference { required String name, required Pointer oid, required bool force, - required String logMessage, + String? logMessage, }) { _refPointer = bindings.createDirect(repo, name, oid, force, logMessage); } @@ -69,7 +71,7 @@ class Reference { required String name, required String target, required bool force, - required String logMessage, + String? logMessage, }) { _refPointer = bindings.createSymbolic(repo, name, target, force, logMessage); @@ -89,7 +91,7 @@ class Reference { } /// Pointer to memory address for allocated reference object. - late final Pointer _refPointer; + late Pointer _refPointer; /// Checks if the reference [name] is well-formed. /// @@ -129,6 +131,37 @@ class Reference { return Oid(oidPointer); } + /// Conditionally creates a new reference with the same name as the given reference + /// but a different OID target. + /// + /// The new reference will be written to disk, overwriting the given reference. + /// + /// Throws a [LibGit2Error] if error occured. + void setTarget(String target, [String? logMessage]) { + late final Oid oid; + + if (isValidShaHex(target)) { + if (target.length == 40) { + oid = Oid.fromSHA(target); + } else { + final shortOid = Oid.fromSHAn(target); + final odb = Odb(repo_bindings.odb(owner)); + oid = Oid(odb.existsPrefix(shortOid.pointer, target.length)); + odb.free(); + } + } else { + final ref = Reference(bindings.lookup(owner, target)); + oid = ref.target; + ref.free(); + } + + if (type == ReferenceType.direct) { + _refPointer = bindings.setTarget(_refPointer, oid.pointer, logMessage); + } else { + _refPointer = bindings.setTargetSymbolic(_refPointer, target, logMessage); + } + } + /// Returns the full name of a reference. String get name => bindings.name(_refPointer); diff --git a/lib/src/repository.dart b/lib/src/repository.dart index 7dd7b8e..573b549 100644 --- a/lib/src/repository.dart +++ b/lib/src/repository.dart @@ -178,7 +178,7 @@ class Repository { required String name, required Object target, bool force = false, - String logMessage = '', + String? logMessage, }) { late final Oid oid; late final bool isDirect; @@ -186,7 +186,7 @@ class Repository { if (target.runtimeType == Oid) { oid = target as Oid; isDirect = true; - } else if (_isValidShaHex(target as String)) { + } else if (isValidShaHex(target as String)) { if (target.length == 40) { oid = Oid.fromSHA(target); } else { @@ -252,10 +252,4 @@ class Repository { /// /// 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/lib/src/util.dart b/lib/src/util.dart index 94fd18b..a8a2897 100644 --- a/lib/src/util.dart +++ b/lib/src/util.dart @@ -19,3 +19,9 @@ DynamicLibrary loadLibrary() { } final libgit2 = Libgit2(loadLibrary()); + +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/reference_test.dart b/test/reference_test.dart index 553f791..7e1c794 100644 --- a/test/reference_test.dart +++ b/test/reference_test.dart @@ -10,6 +10,7 @@ import 'helpers/util.dart'; void main() { const lastCommit = '78b8bf123e3952c970ae5c1ce0a3ea1d1336f6e8'; + const newCommit = 'c68ff54aabf660fcdd9a2838d401583fe31249e3'; group('Reference', () { late final Repository repo; @@ -343,6 +344,50 @@ void main() { ref.free(); }); + group('setTarget()', () { + test('successfully sets target with SHA hex', () { + final ref = repo.getReference('refs/heads/master'); + ref.setTarget(newCommit); + expect(ref.target.sha, newCommit); + + // change back for tests purpose + ref.setTarget(lastCommit); + ref.free(); + }); + + test('successfully sets target with short SHA hex', () { + final ref = repo.getReference('refs/heads/master'); + ref.setTarget(newCommit.substring(0, 5)); + expect(ref.target.sha, newCommit); + + // change back for tests purpose + ref.setTarget(lastCommit); + ref.free(); + }); + + test('successfully sets symbolic target', () { + final ref = repo.getReference('HEAD'); + expect(ref.target.sha, lastCommit); + + ref.setTarget('refs/heads/feature'); + expect(ref.target.sha, '5aecfa0fb97eadaac050ccb99f03c3fb65460ad4'); + + // change back for tests purpose + ref.setTarget('refs/heads/master'); + ref.free(); + }); + + test('throws on invalid target', () { + final ref = repo.getReference('HEAD'); + expect( + () => ref.setTarget('refs/heads/invalid~'), + throwsA(isA()), + ); + + ref.free(); + }); + }); + group('isValidName()', () { test('returns true for valid names', () { expect(Reference.isValidName('HEAD'), true);