diff --git a/lib/src/bindings/reference.dart b/lib/src/bindings/reference.dart index 484a4ce..1330576 100644 --- a/lib/src/bindings/reference.dart +++ b/lib/src/bindings/reference.dart @@ -169,8 +169,14 @@ Pointer createDirect( 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); + final error = libgit2.git_reference_create( + out, + repo, + nameC, + oid, + forceC, + logMessageC, + ); calloc.free(nameC); calloc.free(logMessageC); @@ -181,6 +187,59 @@ Pointer createDirect( } } +/// Create a new symbolic reference. +/// +/// A symbolic reference is a reference name that refers to another reference name. +/// If the other name moves, the symbolic name will move, too. As a simple example, +/// the "HEAD" reference might refer to "refs/heads/master" while on the "master" branch +/// of a repository. +/// +/// The symbolic 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 an [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 createSymbolic( + Pointer repo, + String name, + String target, + bool force, + 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 error = libgit2.git_reference_symbolic_create( + out, + repo, + nameC, + targetC, + forceC, + logMessageC, + ); + calloc.free(nameC); + calloc.free(targetC); + 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. diff --git a/lib/src/config.dart b/lib/src/config.dart index 23918c1..5f5973f 100644 --- a/lib/src/config.dart +++ b/lib/src/config.dart @@ -1,6 +1,5 @@ import 'dart:ffi'; import 'dart:io'; -import 'package:ffi/ffi.dart'; import 'bindings/libgit2_bindings.dart'; import 'bindings/config.dart' as bindings; import 'util.dart'; diff --git a/lib/src/reference.dart b/lib/src/reference.dart index 349296c..c4bbdf6 100644 --- a/lib/src/reference.dart +++ b/lib/src/reference.dart @@ -1,5 +1,4 @@ import 'dart:ffi'; -import 'package:ffi/ffi.dart'; import 'bindings/libgit2_bindings.dart'; import 'bindings/reference.dart' as bindings; import 'oid.dart'; @@ -42,6 +41,39 @@ class Reference { _refPointer = bindings.createDirect(repo, name, oid, force, logMessage); } + /// Initializes a new instance of the [Reference] class by creating a new symbolic reference. + /// + /// A symbolic reference is a reference name that refers to another reference name. + /// If the other name moves, the symbolic name will move, too. As a simple example, + /// the "HEAD" reference might refer to "refs/heads/master" while on the "master" branch + /// of a repository. + /// + /// The symbolic 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 an [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.createSymbolic({ + required Pointer repo, + required String name, + required String target, + required bool force, + required String logMessage, + }) { + _refPointer = + bindings.createSymbolic(repo, name, target, force, logMessage); + } + /// Initializes a new instance of the [Reference] class by /// lookingup a reference by [name] in a repository. /// diff --git a/lib/src/repository.dart b/lib/src/repository.dart index 2c995ca..7dd7b8e 100644 --- a/lib/src/repository.dart +++ b/lib/src/repository.dart @@ -1,5 +1,4 @@ import 'dart:ffi'; -import 'package:ffi/ffi.dart'; import 'odb.dart'; import 'oid.dart'; import 'reference.dart'; @@ -77,90 +76,6 @@ class Repository { return bindings.isHeadDetached(_repoPointer); } - /// 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 [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 = getReference(ref); - refMap[ref] = r.target.sha; - r.free(); - } - - return refMap; - } - /// Makes the repository HEAD point to the specified reference. /// /// If the provided [reference] points to a Tree or a Blob, the HEAD is unaltered. @@ -242,6 +157,95 @@ class Repository { libgit2.git_libgit2_shutdown(); } + /// 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 { + isDirect = false; + } + + if (isDirect) { + return Reference.createDirect( + repo: _repoPointer, + name: name, + oid: oid.pointer, + force: force, + logMessage: logMessage, + ); + } else { + return Reference.createSymbolic( + repo: _repoPointer, + name: name, + target: target as String, + force: force, + logMessage: logMessage, + ); + } + } + + /// Returns [Reference] object pointing to repository head. + Reference get head => Reference(bindings.head(_repoPointer)); + + /// 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 = getReference(ref); + refMap[ref] = r.target.sha; + r.free(); + } + + return refMap; + } + /// Returns [Odb] for this repository. /// /// ODB Object must be freed once it's no longer being used. diff --git a/test/reference_test.dart b/test/reference_test.dart index 2e2abfd..0f3c73b 100644 --- a/test/reference_test.dart +++ b/test/reference_test.dart @@ -147,6 +147,97 @@ void main() { }); }); + group('.createSymbolic()', () { + test('successfully creates with valid target', () { + final ref = repo.createReference( + name: 'refs/tags/symbolic', + target: 'refs/heads/master', + ); + + expect(repo.references, contains('refs/tags/symbolic')); + expect(ref.type, ReferenceType.symbolic); + + ref.delete(); + ref.free(); + }); + + test('successfully creates with force flag if name already exists', () { + final ref = repo.createReference( + name: 'refs/tags/test', + target: 'refs/heads/master', + ); + + final forceRef = repo.createReference( + name: 'refs/tags/test', + target: 'refs/heads/master', + force: true, + ); + + expect(forceRef.target.sha, lastCommit); + expect(forceRef.type, ReferenceType.symbolic); + + forceRef.delete(); + ref.free(); + forceRef.free(); + }); + + test('throws if name already exists', () { + final ref = repo.createReference( + name: 'refs/tags/exists', + target: 'refs/heads/master', + ); + + expect( + () => repo.createReference( + name: 'refs/tags/exists', + target: 'refs/heads/master', + ), + throwsA(isA()), + ); + + ref.delete(); + ref.free(); + }); + + test('throws if name is not valid', () { + expect( + () => repo.createReference( + name: 'refs/tags/invalid~', + target: 'refs/heads/master', + ), + throwsA(isA()), + ); + }); + + test('successfully creates with log message', () { + repo.setIdentity(name: 'name', email: 'email'); + final ref = repo.createReference( + name: 'HEAD', + target: 'refs/heads/feature', + force: true, + logMessage: 'log message', + ); + + final reflog = RefLog(ref); + final reflogEntry = reflog.entryAt(0); + + expect(reflogEntry.message, 'log message'); + expect(reflogEntry.committer, {'name': 'name', 'email': 'email'}); + + // set HEAD back to master + repo + .createReference( + name: 'HEAD', + target: 'refs/heads/master', + force: true, + ) + .free(); + + reflog.free(); + ref.free(); + }); + }); + test('successfully deletes reference', () { final ref = repo.createReference( name: 'refs/tags/test',