From 6d48ae742ce78dadc676ceac5734b70dc21018d2 Mon Sep 17 00:00:00 2001 From: Aleksey Kulikov Date: Mon, 27 Sep 2021 11:19:00 +0300 Subject: [PATCH] feat(repository): add ability to pass callbacks for remote and repository creation during clone --- lib/src/bindings/repository.dart | 64 +++++++++++++++++++++++++ lib/src/remote.dart | 4 +- lib/src/repository.dart | 17 ++++++- test/repository_clone_test.dart | 82 ++++++++++++++++++++++++++++++-- 4 files changed, 161 insertions(+), 6 deletions(-) diff --git a/lib/src/bindings/repository.dart b/lib/src/bindings/repository.dart index b2cb040..23cf210 100644 --- a/lib/src/bindings/repository.dart +++ b/lib/src/bindings/repository.dart @@ -1,6 +1,8 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; import '../error.dart'; +import '../remote.dart'; +import '../repository.dart'; import 'libgit2_bindings.dart'; import '../util.dart'; @@ -135,6 +137,8 @@ Pointer clone( String url, String localPath, bool bare, + Remote Function(Repository, String, String)? remote, + Repository Function(String, bool)? repository, String checkoutBranch, ) { final out = calloc>(); @@ -143,6 +147,7 @@ Pointer clone( final checkoutBranchC = checkoutBranch.isEmpty ? nullptr : checkoutBranch.toNativeUtf8().cast(); + final cloneOptions = calloc(); final cloneOptionsError = libgit2.git_clone_options_init(cloneOptions, GIT_CLONE_OPTIONS_VERSION); @@ -159,8 +164,24 @@ Pointer clone( throw LibGit2Error(libgit2.git_error_last()); } + const except = -1; + + git_remote_create_cb remoteCb = nullptr; + if (remote != null) { + _remoteFunction = remote; + remoteCb = Pointer.fromFunction(_remoteCb, except); + } + + git_repository_create_cb repositoryCb = nullptr; + if (repository != null) { + _repositoryFunction = repository; + repositoryCb = Pointer.fromFunction(_repositoryCb, except); + } + cloneOptions.ref.bare = bare ? 1 : 0; + cloneOptions.ref.remote_cb = remoteCb; cloneOptions.ref.checkout_branch = checkoutBranchC; + cloneOptions.ref.repository_cb = repositoryCb; cloneOptions.ref.fetch_opts = fetchOptions.ref; final error = libgit2.git_clone(out, urlC, localPathC, cloneOptions); @@ -170,6 +191,8 @@ Pointer clone( calloc.free(checkoutBranchC); calloc.free(cloneOptions); calloc.free(fetchOptions); + _remoteFunction = null; + _repositoryFunction = null; if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); @@ -178,6 +201,47 @@ Pointer clone( } } +/// A function matching the `Remote Function(Repository repo, String name, String url)` signature +/// to override the remote creation and customization process during a clone operation. +Remote Function(Repository, String, String)? _remoteFunction; + +/// A callback used to create the git remote, prior to its being used to perform +/// the clone operation. +int _remoteCb( + Pointer> remote, + Pointer repo, + Pointer name, + Pointer url, + Pointer payload, +) { + remote[0] = _remoteFunction!( + Repository(repo), + name.cast().toDartString(), + url.cast().toDartString(), + ).pointer; + + return 0; +} + +/// A function matching the `Repository Function(String path, bool bare)` signature to override +/// the repository creation and customization process during a clone operation. +Repository Function(String, bool)? _repositoryFunction; + +/// A callback used to create the new repository into which to clone. +int _repositoryCb( + Pointer> repo, + Pointer path, + int bare, + Pointer payload, +) { + repo[0] = _repositoryFunction!( + path.cast().toDartString(), + bare == 1 ? true : false, + ).pointer; + + return 0; +} + /// Returns the path to the `.git` folder for normal repositories or the /// repository itself for bare repositories. String path(Pointer repo) { diff --git a/lib/src/remote.dart b/lib/src/remote.dart index d040d28..080e1a2 100644 --- a/lib/src/remote.dart +++ b/lib/src/remote.dart @@ -119,9 +119,11 @@ class Remote { /// to remote object in memory. const Remote(this._remotePointer); - /// Pointer to memory address for allocated remote object. final Pointer _remotePointer; + /// Pointer to memory address for allocated remote object. + Pointer get pointer => _remotePointer; + /// Returns the remote's name. String get name => bindings.name(_remotePointer); diff --git a/lib/src/repository.dart b/lib/src/repository.dart index eb38d25..d444aa6 100644 --- a/lib/src/repository.dart +++ b/lib/src/repository.dart @@ -90,6 +90,12 @@ class Repository { /// Initializes a new instance of the [Repository] class by cloning a remote repository /// at provided [url] into [localPath]. /// + /// [remote] is the callback function with `Remote Function(Repository repo, String name, String url)` + /// signature. The [Remote] it returns will be used instead of default one. + /// + /// [repository] is the callback function matching the `Repository Function(String path, bool bare)` + /// signature. The [Repository] it returns will be used instead of creating a new one. + /// /// [checkoutBranch] is the name of the branch to checkout after the clone. Defaults /// to using the remote's default branch. /// @@ -98,11 +104,20 @@ class Repository { required String url, required String localPath, bool bare = false, + Remote Function(Repository, String, String)? remote, + Repository Function(String, bool)? repository, String checkoutBranch = '', }) { libgit2.git_libgit2_init(); - _repoPointer = bindings.clone(url, localPath, bare, checkoutBranch); + _repoPointer = bindings.clone( + url, + localPath, + bare, + remote, + repository, + checkoutBranch, + ); } late final Pointer _repoPointer; diff --git a/test/repository_clone_test.dart b/test/repository_clone_test.dart index e2129bf..b9dc713 100644 --- a/test/repository_clone_test.dart +++ b/test/repository_clone_test.dart @@ -20,11 +20,13 @@ void main() { tearDown(() async { repo.free(); await tmpDir.delete(recursive: true); - cloneDir.delete(recursive: true); + if (await cloneDir.exists()) { + cloneDir.delete(recursive: true); + } }); group('Repository.clone', () { - test('successfully clones repository', () async { + test('successfully clones repository', () { final clonedRepo = Repository.clone( url: tmpDir.path, localPath: cloneDir.path, @@ -36,7 +38,7 @@ void main() { clonedRepo.free(); }); - test('successfully clones repository as bare', () async { + test('successfully clones repository as bare', () { final clonedRepo = Repository.clone( url: tmpDir.path, localPath: cloneDir.path, @@ -50,7 +52,7 @@ void main() { }); test('successfully clones repository with provided checkout branch name', - () async { + () { final clonedRepo = Repository.clone( url: tmpDir.path, localPath: cloneDir.path, @@ -64,5 +66,77 @@ void main() { clonedRepo.free(); }); + + test('successfully clones repository with provided remote callback', () { + Remote remote(Repository repo, String name, String url) => + repo.remotes.create(name: 'test', url: tmpDir.path); + + final clonedRepo = Repository.clone( + url: tmpDir.path, + localPath: cloneDir.path, + remote: remote, + ); + + expect(clonedRepo.isEmpty, false); + expect(clonedRepo.isBare, false); + expect(clonedRepo.remotes.list, ['test']); + expect(clonedRepo.references.list, contains('refs/remotes/test/master')); + + clonedRepo.free(); + }); + + test('throws when cloning repository with invalid remote callback', () { + Remote remote(Repository repo, String name, String url) => + repo.remotes.create(name: '', url: ''); + + expect( + () => Repository.clone( + url: tmpDir.path, + localPath: cloneDir.path, + remote: remote, + ), + throwsA(isA()), + ); + }); + + test('successfully clones repository with provided repository callback', + () async { + final callbackPath = + Directory('${Directory.systemTemp.path}/callbackRepo'); + if (await callbackPath.exists()) { + callbackPath.delete(recursive: true); + } + callbackPath.create(); + + Repository repository(String path, bool bare) => + Repository.init(path: callbackPath.path); + + final clonedRepo = Repository.clone( + url: tmpDir.path, + localPath: cloneDir.path, + repository: repository, + ); + + expect(clonedRepo.isEmpty, false); + expect(clonedRepo.isBare, false); + expect(clonedRepo.path, '${callbackPath.path}/.git/'); + + clonedRepo.free(); + callbackPath.delete(recursive: true); + }); + + test('throws when cloning repository with invalid repository callback', () { + Repository repository(String path, bool bare) => + Repository.init(path: ''); + + expect( + () => Repository.clone( + url: tmpDir.path, + localPath: cloneDir.path, + repository: repository, + ), + throwsA(isA()), + ); + }); }); }