diff --git a/lib/src/bindings/reference.dart b/lib/src/bindings/reference.dart index 9729b60..5a3734d 100644 --- a/lib/src/bindings/reference.dart +++ b/lib/src/bindings/reference.dart @@ -82,6 +82,46 @@ String shorthand(Pointer ref) { return result.cast().toDartString(); } +/// Rename an existing reference. +/// +/// This method works for both direct and symbolic references. +/// +/// The new name will be checked for validity. +/// +/// If the force flag is not enabled, and there's already a reference with the given name, +/// the renaming will fail. +/// +/// IMPORTANT: The user needs to write a proper reflog entry if the reflog is enabled for +/// the repository. We only rename the reflog if it exists. +/// +/// Throws a [LibGit2Error] if error occured. +Pointer rename( + Pointer ref, + String newName, + bool force, + String? logMessage, +) { + final out = calloc>(); + final newNameC = newName.toNativeUtf8().cast(); + final forceC = force == true ? 1 : 0; + final logMessageC = logMessage?.toNativeUtf8().cast() ?? nullptr; + final error = libgit2.git_reference_rename( + out, + ref, + newNameC, + forceC, + logMessageC, + ); + calloc.free(newNameC); + calloc.free(logMessageC); + + if (error < 0) { + throw LibGit2Error(libgit2.git_error_last()); + } else { + return out.value; + } +} + /// Fill a list with all the references that can be found in a repository. /// /// The string array will be filled with the names of all references; diff --git a/lib/src/reference.dart b/lib/src/reference.dart index efbd718..5363be9 100644 --- a/lib/src/reference.dart +++ b/lib/src/reference.dart @@ -171,6 +171,23 @@ class Reference { /// If no shortname is appropriate, it will return the full name. String get shorthand => bindings.shorthand(_refPointer); + /// Renames an existing reference. + /// + /// This method works for both direct and symbolic references. + /// + /// The new name will be checked for validity. + /// + /// If the force flag is not enabled, and there's already a reference with the given name, + /// the renaming will fail. + /// + /// IMPORTANT: The user needs to write a proper reflog entry if the reflog is enabled for + /// the repository. We only rename the reflog if it exists. + /// + /// Throws a [LibGit2Error] if error occured. + void rename(String newName, {bool force = false, String? logMessage}) { + _refPointer = bindings.rename(_refPointer, newName, force, logMessage); + } + /// Returns a list with all the references that can be found in a repository. /// /// Throws a [LibGit2Error] if error occured. diff --git a/test/reference_test.dart b/test/reference_test.dart index 6dc0ec6..65a12cc 100644 --- a/test/reference_test.dart +++ b/test/reference_test.dart @@ -358,7 +358,7 @@ void main() { ref.free(); }); - group('setTarget()', () { + group('.setTarget()', () { test('successfully sets target with SHA hex', () { final ref = repo.getReference('refs/heads/master'); ref.setTarget(newCommit); @@ -418,6 +418,80 @@ void main() { }); }); + group('.rename()', () { + test('successfully renames reference', () { + final ref = repo.createReference( + name: 'refs/tags/v1', + target: lastCommit, + ); + expect(ref.name, 'refs/tags/v1'); + + ref.rename('refs/tags/v2'); + expect(ref.name, 'refs/tags/v2'); + + ref.delete(); + ref.free(); + }); + + test('throws on invalid name', () { + final ref = repo.createReference( + name: 'refs/tags/v1', + target: lastCommit, + ); + + expect( + () => ref.rename('refs/tags/invalid~'), + throwsA(isA()), + ); + + ref.delete(); + ref.free(); + }); + + test('throws if name already exists', () { + final ref1 = repo.createReference( + name: 'refs/tags/v1', + target: lastCommit, + ); + + final ref2 = repo.createReference( + name: 'refs/tags/v2', + target: lastCommit, + ); + + expect( + () => ref1.rename('refs/tags/v2'), + throwsA(isA()), + ); + + ref1.delete(); + ref2.delete(); + ref1.free(); + ref2.free(); + }); + + test('successfully renames with force flag set to true', () { + final ref1 = repo.createReference( + name: 'refs/tags/v1', + target: lastCommit, + ); + + final ref2 = repo.createReference( + name: 'refs/tags/v2', + target: newCommit, + ); + + expect(ref2.target.sha, newCommit); + + ref1.rename('refs/tags/v2', force: true); + expect(ref1.name, 'refs/tags/v2'); + + ref1.delete(); + ref1.free(); + ref2.free(); + }); + }); + group('isValidName()', () { test('returns true for valid names', () { expect(Reference.isValidName('HEAD'), true);