import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'package:libgit2dart/src/bindings/libgit2_bindings.dart'; import 'package:libgit2dart/src/bindings/remote_callbacks.dart'; import 'package:libgit2dart/src/callbacks.dart'; import 'package:libgit2dart/src/error.dart'; import 'package:libgit2dart/src/oid.dart'; import 'package:libgit2dart/src/util.dart'; /// Get a list of the configured remotes for a repo. /// /// Throws a [LibGit2Error] if error occured. List list(Pointer repo) { final out = calloc(); libgit2.git_remote_list(out, repo); final result = []; for (var i = 0; i < out.ref.count; i++) { result.add(out.ref.strings[i].cast().toDartString()); } calloc.free(out); return result; } /// Get the information for a particular remote. /// /// The name will be checked for validity. /// /// Throws a [LibGit2Error] if error occured. Pointer lookup({ required Pointer repoPointer, required String name, }) { final out = calloc>(); final nameC = name.toNativeUtf8().cast(); final error = libgit2.git_remote_lookup(out, repoPointer, nameC); calloc.free(nameC); if (error < 0) { calloc.free(out); throw LibGit2Error(libgit2.git_error_last()); } else { return out.value; } } /// Add a remote with the default fetch refspec to the repository's /// configuration. /// /// Throws a [LibGit2Error] if error occured. Pointer create({ required Pointer repoPointer, required String name, required String url, }) { final out = calloc>(); final nameC = name.toNativeUtf8().cast(); final urlC = url.toNativeUtf8().cast(); final error = libgit2.git_remote_create(out, repoPointer, nameC, urlC); calloc.free(nameC); calloc.free(urlC); if (error < 0) { calloc.free(out); throw LibGit2Error(libgit2.git_error_last()); } else { return out.value; } } /// Add a remote with the provided fetch refspec to the repository's /// configuration. /// /// Throws a [LibGit2Error] if error occured. Pointer createWithFetchSpec({ required Pointer repoPointer, required String name, required String url, required String fetch, }) { final out = calloc>(); final nameC = name.toNativeUtf8().cast(); final urlC = url.toNativeUtf8().cast(); final fetchC = fetch.toNativeUtf8().cast(); final error = libgit2.git_remote_create_with_fetchspec( out, repoPointer, nameC, urlC, fetchC, ); calloc.free(nameC); calloc.free(urlC); calloc.free(fetchC); if (error < 0) { calloc.free(out); throw LibGit2Error(libgit2.git_error_last()); } else { return out.value; } } /// Delete an existing persisted remote. /// /// All remote-tracking branches and configuration settings for the remote will /// be removed. /// /// Throws a [LibGit2Error] if error occured. void delete({ required Pointer repoPointer, required String name, }) { final nameC = name.toNativeUtf8().cast(); final error = libgit2.git_remote_delete(repoPointer, nameC); calloc.free(nameC); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Give the remote a new name. /// /// Returns list of non-default refspecs that cannot be renamed. /// /// All remote-tracking branches and configuration settings for the remote are /// updated. /// /// The new name will be checked for validity. /// /// No loaded instances of a the remote with the old name will change their /// name or their list of refspecs. /// /// Throws a [LibGit2Error] if error occured. List rename({ required Pointer repoPointer, required String name, required String newName, }) { final out = calloc(); final nameC = name.toNativeUtf8().cast(); final newNameC = newName.toNativeUtf8().cast(); final error = libgit2.git_remote_rename(out, repoPointer, nameC, newNameC); calloc.free(nameC); calloc.free(newNameC); if (error < 0) { calloc.free(out); throw LibGit2Error(libgit2.git_error_last()); } else { final result = []; for (var i = 0; i < out.ref.count; i++) { result.add(out.ref.strings[i].cast().toDartString()); } calloc.free(out); return result; } } /// Set the remote's url in the configuration. /// /// Remote objects already in memory will not be affected. This assumes the /// common case of a single-url remote and will otherwise return an error. /// /// Throws a [LibGit2Error] if error occured. void setUrl({ required Pointer repoPointer, required String remote, required String url, }) { final remoteC = remote.toNativeUtf8().cast(); final urlC = url.toNativeUtf8().cast(); final error = libgit2.git_remote_set_url(repoPointer, remoteC, urlC); calloc.free(remoteC); calloc.free(urlC); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Set the remote's url for pushing in the configuration. /// /// Remote objects already in memory will not be affected. This assumes the /// common case of a single-url remote and will otherwise return an error. /// /// Throws a [LibGit2Error] if error occured. void setPushUrl({ required Pointer repoPointer, required String remote, required String url, }) { final remoteC = remote.toNativeUtf8().cast(); final urlC = url.toNativeUtf8().cast(); final error = libgit2.git_remote_set_pushurl(repoPointer, remoteC, urlC); calloc.free(remoteC); calloc.free(urlC); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Get the remote's name. String name(Pointer remote) { final result = libgit2.git_remote_name(remote); return result == nullptr ? '' : result.cast().toDartString(); } /// Get the remote's url. String url(Pointer remote) { return libgit2.git_remote_url(remote).cast().toDartString(); } /// Get the remote's url for pushing. /// /// Returns empty string if no special url for pushing is set. String pushUrl(Pointer remote) { final result = libgit2.git_remote_pushurl(remote); return result == nullptr ? '' : result.cast().toDartString(); } /// Get the number of refspecs for a remote. int refspecCount(Pointer remote) => libgit2.git_remote_refspec_count(remote); /// Get a refspec from the remote at provided position. Pointer getRefspec({ required Pointer remotePointer, required int position, }) { return libgit2.git_remote_get_refspec(remotePointer, position); } /// Get the remote's list of fetch refspecs. List fetchRefspecs(Pointer remote) { final out = calloc(); libgit2.git_remote_get_fetch_refspecs(out, remote); final result = []; for (var i = 0; i < out.ref.count; i++) { result.add(out.ref.strings[i].cast().toDartString()); } calloc.free(out); return result; } /// Get the remote's list of push refspecs. List pushRefspecs(Pointer remote) { final out = calloc(); libgit2.git_remote_get_push_refspecs(out, remote); final result = []; for (var i = 0; i < out.ref.count; i++) { result.add(out.ref.strings[i].cast().toDartString()); } calloc.free(out); return result; } /// Add a fetch refspec to the remote's configuration. /// /// Add the given refspec to the fetch list in the configuration. No loaded /// remote instances will be affected. /// /// Throws a [LibGit2Error] if error occured. void addFetch({ required Pointer repoPointer, required String remote, required String refspec, }) { final remoteC = remote.toNativeUtf8().cast(); final refspecC = refspec.toNativeUtf8().cast(); final error = libgit2.git_remote_add_fetch(repoPointer, remoteC, refspecC); calloc.free(remoteC); calloc.free(refspecC); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Add a push refspec to the remote's configuration. /// /// Add the given refspec to the push list in the configuration. No loaded /// remote instances will be affected. /// /// Throws a [LibGit2Error] if error occured. void addPush({ required Pointer repoPointer, required String remote, required String refspec, }) { final remoteC = remote.toNativeUtf8().cast(); final refspecC = refspec.toNativeUtf8().cast(); final error = libgit2.git_remote_add_push(repoPointer, remoteC, refspecC); calloc.free(remoteC); calloc.free(refspecC); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Open a connection to a remote. /// /// The transport is selected based on the URL. The direction argument is due /// to a limitation of the git protocol (over TCP or SSH) which starts up a /// specific binary which can only do the one or the other. /// /// Throws a [LibGit2Error] if error occured. void connect({ required Pointer remotePointer, required int direction, required Callbacks callbacks, String? proxyOption, }) { final callbacksOptions = calloc(); libgit2.git_remote_init_callbacks( callbacksOptions, GIT_REMOTE_CALLBACKS_VERSION, ); RemoteCallbacks.plug( callbacksOptions: callbacksOptions.ref, callbacks: callbacks, ); final proxyOptions = _proxyOptionsInit(proxyOption); final error = libgit2.git_remote_connect( remotePointer, direction, callbacksOptions, proxyOptions, nullptr, ); calloc.free(callbacksOptions); calloc.free(proxyOptions); RemoteCallbacks.reset(); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Get the remote repository's reference advertisement list. /// /// Get the list of references with which the server responds to a new /// connection. /// /// The remote (or more exactly its transport) must have connected to the /// remote repository. This list is available as soon as the connection to the /// remote is initiated and it remains available after disconnecting. /// /// Throws a [LibGit2Error] if error occured. List> lsRemotes(Pointer remote) { final out = calloc>>(); final size = calloc(); libgit2.git_remote_ls(out, size, remote); final result = >[]; for (var i = 0; i < size.value; i++) { final remote = {}; final local = out[0][i].ref.local == 1 || false; remote['local'] = local; remote['loid'] = local ? Oid.fromRaw(out[0][i].ref.loid) : null; remote['name'] = out[0][i].ref.name == nullptr ? '' : out[0][i].ref.name.cast().toDartString(); remote['symref'] = out[0][i].ref.symref_target == nullptr ? '' : out[0][i].ref.symref_target.cast().toDartString(); remote['oid'] = Oid.fromRaw(out[0][i].ref.oid); result.add(remote); } calloc.free(out); calloc.free(size); return result; } /// Download new data and update tips. /// /// Convenience function to connect to a remote, download the data, disconnect /// and update the remote-tracking branches. /// /// Throws a [LibGit2Error] if error occured. void fetch({ required Pointer remotePointer, required List refspecs, required int prune, required Callbacks callbacks, String? reflogMessage, String? proxyOption, }) { final refspecsC = calloc(); final refspecsPointers = refspecs.map((e) => e.toNativeUtf8().cast()).toList(); final strArray = calloc>(refspecs.length); for (var i = 0; i < refspecs.length; i++) { strArray[i] = refspecsPointers[i]; } refspecsC.ref.count = refspecs.length; refspecsC.ref.strings = strArray; final reflogMessageC = reflogMessage?.toNativeUtf8().cast() ?? nullptr; final proxyOptions = _proxyOptionsInit(proxyOption); final opts = calloc(); libgit2.git_fetch_options_init(opts, GIT_FETCH_OPTIONS_VERSION); RemoteCallbacks.plug( callbacksOptions: opts.ref.callbacks, callbacks: callbacks, ); opts.ref.prune = prune; opts.ref.proxy_opts = proxyOptions.ref; final error = libgit2.git_remote_fetch( remotePointer, refspecsC, opts, reflogMessageC, ); for (final p in refspecsPointers) { calloc.free(p); } calloc.free(strArray); calloc.free(refspecsC); calloc.free(proxyOptions); calloc.free(reflogMessageC); calloc.free(opts); RemoteCallbacks.reset(); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Perform a push. /// /// Throws a [LibGit2Error] if error occured. void push({ required Pointer remotePointer, required List refspecs, required Callbacks callbacks, String? proxyOption, }) { final refspecsC = calloc(); final refspecsPointers = refspecs.map((e) => e.toNativeUtf8().cast()).toList(); final strArray = calloc>(refspecs.length); for (var i = 0; i < refspecs.length; i++) { strArray[i] = refspecsPointers[i]; } refspecsC.ref.count = refspecs.length; refspecsC.ref.strings = strArray; final proxyOptions = _proxyOptionsInit(proxyOption); final opts = calloc(); libgit2.git_push_options_init(opts, GIT_PUSH_OPTIONS_VERSION); RemoteCallbacks.plug( callbacksOptions: opts.ref.callbacks, callbacks: callbacks, ); opts.ref.proxy_opts = proxyOptions.ref; final error = libgit2.git_remote_push(remotePointer, refspecsC, opts); for (final p in refspecsPointers) { calloc.free(p); } calloc.free(strArray); calloc.free(refspecsC); calloc.free(proxyOptions); calloc.free(opts); RemoteCallbacks.reset(); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Get the statistics structure that is filled in by the fetch operation. Pointer stats(Pointer remote) => libgit2.git_remote_stats(remote); /// Close the connection to the remote. void disconnect(Pointer remote) => libgit2.git_remote_disconnect(remote); /// Prune tracking refs that are no longer present on remote. /// /// Throws a [LibGit2Error] if error occured. void prune({ required Pointer remotePointer, required Callbacks callbacks, }) { final callbacksOptions = calloc(); libgit2.git_remote_init_callbacks( callbacksOptions, GIT_REMOTE_CALLBACKS_VERSION, ); RemoteCallbacks.plug( callbacksOptions: callbacksOptions.ref, callbacks: callbacks, ); final error = libgit2.git_remote_prune(remotePointer, callbacksOptions); calloc.free(callbacksOptions); RemoteCallbacks.reset(); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Free the memory associated with a remote. /// /// This also disconnects from the remote, if the connection has not been closed /// yet (using `disconnect()`). void free(Pointer remote) => libgit2.git_remote_free(remote); /// Initializes git_proxy_options structure. Pointer _proxyOptionsInit(String? proxyOption) { final proxyOptions = calloc(); libgit2.git_proxy_options_init(proxyOptions, GIT_PROXY_OPTIONS_VERSION); if (proxyOption == null) { proxyOptions.ref.type = git_proxy_t.GIT_PROXY_NONE; } else if (proxyOption == 'auto') { proxyOptions.ref.type = git_proxy_t.GIT_PROXY_AUTO; } else { proxyOptions.ref.type = git_proxy_t.GIT_PROXY_SPECIFIED; proxyOptions.ref.url = proxyOption.toNativeUtf8().cast(); } return proxyOptions; }