import 'dart:ffi'; import 'package:ffi/ffi.dart'; import '../callbacks.dart'; import '../error.dart'; import '../remote.dart'; import '../repository.dart'; import 'libgit2_bindings.dart'; import '../util.dart'; import 'remote_callbacks.dart'; /// Attempt to open an already-existing repository at [path]. /// /// The [path] can point to either a normal or bare repository. /// /// Throws a [LibGit2Error] if error occured. Pointer open(String path) { final out = calloc>(); final pathC = path.toNativeUtf8().cast(); final error = libgit2.git_repository_open(out, pathC); calloc.free(pathC); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } else { return out.value; } } /// Attempt to open an already-existing bare repository at [bare_path]. /// /// The [bare_path] can point to only a bare repository. /// /// Throws a [LibGit2Error] if error occured. Pointer openBare(String barePath) { final out = calloc>(); final barePathC = barePath.toNativeUtf8().cast(); final error = libgit2.git_repository_open_bare(out, barePathC); calloc.free(barePathC); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } else { return out.value; } } /// Look for a git repository and return its path. The lookup start from [startPath] /// and walk across parent directories if nothing has been found. The lookup ends when /// the first repository is found, or when reaching a directory referenced in [ceilingDirs]. /// /// The method will automatically detect if the repository is bare (if there is a repository). /// /// Throws a [LibGit2Error] if error occured. String discover({ required String startPath, String? ceilingDirs, }) { final out = calloc(sizeOf()); final startPathC = startPath.toNativeUtf8().cast(); final ceilingDirsC = ceilingDirs?.toNativeUtf8().cast() ?? nullptr; final error = libgit2.git_repository_discover( out, startPathC, 0, ceilingDirsC, ); calloc.free(startPathC); calloc.free(ceilingDirsC); if (error == git_error_code.GIT_ENOTFOUND) { return ''; } else if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } else { return out.ref.ptr.cast().toDartString(); } } /// Creates a new Git repository in the given folder. /// /// Throws a [LibGit2Error] if error occured. Pointer init({ required String path, required int flags, required int mode, String? workdirPath, String? description, String? templatePath, String? initialHead, String? originUrl, }) { final out = calloc>(); final pathC = path.toNativeUtf8().cast(); final workdirPathC = workdirPath?.toNativeUtf8().cast() ?? nullptr; final descriptionC = description?.toNativeUtf8().cast() ?? nullptr; final templatePathC = templatePath?.toNativeUtf8().cast() ?? nullptr; final initialHeadC = initialHead?.toNativeUtf8().cast() ?? nullptr; final originUrlC = originUrl?.toNativeUtf8().cast() ?? nullptr; final opts = calloc(); final optsError = libgit2.git_repository_init_options_init( opts, GIT_REPOSITORY_INIT_OPTIONS_VERSION, ); if (optsError < 0) { throw LibGit2Error(libgit2.git_error_last()); } opts.ref.flags = flags; opts.ref.mode = mode; opts.ref.workdir_path = workdirPathC; opts.ref.description = descriptionC; opts.ref.template_path = templatePathC; opts.ref.initial_head = initialHeadC; opts.ref.origin_url = originUrlC; final error = libgit2.git_repository_init_ext(out, pathC, opts); calloc.free(pathC); calloc.free(workdirPathC); calloc.free(descriptionC); calloc.free(templatePathC); calloc.free(initialHeadC); calloc.free(originUrlC); calloc.free(opts); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } else { return out.value; } } /// Clone a remote repository. /// /// Throws a [LibGit2Error] if error occured. Pointer clone({ required String url, required String localPath, required bool bare, Remote Function(Repository, String, String)? remote, Repository Function(String, bool)? repository, String? checkoutBranch, required Callbacks callbacks, }) { final out = calloc>(); final urlC = url.toNativeUtf8().cast(); final localPathC = localPath.toNativeUtf8().cast(); final checkoutBranchC = checkoutBranch?.toNativeUtf8().cast() ?? nullptr; final cloneOptions = calloc(); final cloneOptionsError = libgit2.git_clone_options_init(cloneOptions, GIT_CLONE_OPTIONS_VERSION); if (cloneOptionsError < 0) { throw LibGit2Error(libgit2.git_error_last()); } final fetchOptions = calloc(); final fetchOptionsError = libgit2.git_fetch_options_init(fetchOptions, GIT_FETCH_OPTIONS_VERSION); if (fetchOptionsError < 0) { throw LibGit2Error(libgit2.git_error_last()); } RemoteCallbacks.plug( callbacksOptions: fetchOptions.ref.callbacks, callbacks: callbacks, ); const except = -1; git_remote_create_cb remoteCb = nullptr; if (remote != null) { RemoteCallbacks.remoteFunction = remote; remoteCb = Pointer.fromFunction(RemoteCallbacks.remoteCb, except); } git_repository_create_cb repositoryCb = nullptr; if (repository != null) { RemoteCallbacks.repositoryFunction = repository; repositoryCb = Pointer.fromFunction(RemoteCallbacks.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); calloc.free(urlC); calloc.free(localPathC); calloc.free(checkoutBranchC); calloc.free(cloneOptions); calloc.free(fetchOptions); RemoteCallbacks.reset(); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } else { return out.value; } } /// Returns the path to the `.git` folder for normal repositories or the /// repository itself for bare repositories. String path(Pointer repo) { final result = libgit2.git_repository_path(repo); return result.cast().toDartString(); } /// Get the path of the shared common directory for this repository. /// /// If the repository is bare, it is the root directory for the repository. /// If the repository is a worktree, it is the parent repo's `.git` folder. /// Otherwise, it is the `.git` folder. String commonDir(Pointer repo) { final result = libgit2.git_repository_commondir(repo); return result.cast().toDartString(); } /// Get the currently active namespace for this repository. /// /// If there is no namespace, or the namespace is not a valid utf8 string, /// empty string is returned. String getNamespace(Pointer repo) { final result = libgit2.git_repository_get_namespace(repo); if (result == nullptr) { return ''; } else { return result.cast().toDartString(); } } /// Sets the active namespace for this repository. /// /// This namespace affects all reference operations for the repo. See `man gitnamespaces` /// /// The [namespace] should not include the refs folder, e.g. to namespace all references /// under refs/namespaces/foo/, use foo as the namespace. /// /// Throws a [LibGit2Error] if error occured. void setNamespace({ required Pointer repoPointer, String? namespace, }) { final nmspace = namespace?.toNativeUtf8().cast() ?? nullptr; final error = libgit2.git_repository_set_namespace(repoPointer, nmspace); calloc.free(nmspace); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Check if a repository is bare or not. bool isBare(Pointer repo) { final result = libgit2.git_repository_is_bare(repo); return result == 1 ? true : false; } /// Check if a repository is empty. /// /// An empty repository has just been initialized and contains no references /// apart from HEAD, which must be pointing to the unborn master branch. /// /// Throws a [LibGit2Error] if repository is corrupted. bool isEmpty(Pointer repo) { final error = libgit2.git_repository_is_empty(repo); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } else { return error == 1 ? true : false; } } /// Retrieve and resolve the reference pointed at by HEAD. /// /// The returned `git_reference` will be owned by caller and must be freed /// to release the allocated memory and prevent a leak. /// /// Throws a [LibGit2Error] if error occured. Pointer head(Pointer repo) { final out = calloc>(); final error = libgit2.git_repository_head(out, repo); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } else { return out.value; } } /// Check if a repository's HEAD is detached. /// /// A repository's HEAD is detached when it points directly to a commit instead of a branch. /// /// Throws a [LibGit2Error] if error occured. bool isHeadDetached(Pointer repo) { final error = libgit2.git_repository_head_detached(repo); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } else { return error == 1 ? true : false; } } /// Check if the current branch is unborn. /// /// An unborn branch is one named from HEAD but which doesn't exist in the refs namespace, /// because it doesn't have any commit to point to. /// /// Throws a [LibGit2Error] if error occured. bool isBranchUnborn(Pointer repo) { final error = libgit2.git_repository_head_unborn(repo); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } else { return error == 1 ? true : false; } } /// Set the identity to be used for writing reflogs. /// /// If both are set, this name and email will be used to write to the reflog. /// Pass NULL to unset. When unset, the identity will be taken from the repository's configuration. void setIdentity({ required Pointer repoPointer, String? name, String? email, }) { final nameC = name?.toNativeUtf8().cast() ?? nullptr; final emailC = email?.toNativeUtf8().cast() ?? nullptr; libgit2.git_repository_set_ident(repoPointer, nameC, emailC); calloc.free(nameC); calloc.free(emailC); } /// Retrieve the configured identity to use for reflogs. Map identity(Pointer repo) { final name = calloc>(); final email = calloc>(); libgit2.git_repository_ident(name, email, repo); var identity = {}; if (name.value == nullptr && email.value == nullptr) { return identity; } else { identity[name.value.cast().toDartString()] = email.value.cast().toDartString(); } calloc.free(name); calloc.free(email); return identity; } /// Get the configuration file for this repository. /// /// If a configuration file has not been set, the default config set for the repository /// will be returned, including global and system configurations (if they are available). /// /// The configuration file must be freed once it's no longer being used by the user. /// /// Throws a [LibGit2Error] if error occured. Pointer config(Pointer repo) { final out = calloc>(); final error = libgit2.git_repository_config(out, repo); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } else { return out.value; } } /// Get a snapshot of the repository's configuration. /// /// Convenience function to take a snapshot from the repository's configuration. /// The contents of this snapshot will not change, even if the underlying config files are modified. /// /// The configuration file must be freed once it's no longer being used by the user. /// /// Throws a [LibGit2Error] if error occured. Pointer configSnapshot(Pointer repo) { final out = calloc>(); final error = libgit2.git_repository_config_snapshot(out, repo); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } else { return out.value; } } /// Get the Index file for this repository. /// /// If a custom index has not been set, the default index for the repository /// will be returned (the one located in `.git/index`). /// /// The index must be freed once it's no longer being used. /// /// Throws a [LibGit2Error] if error occured. Pointer index(Pointer repo) { final out = calloc>(); final error = libgit2.git_repository_index(out, repo); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } else { return out.value; } } /// Determine if the repository was a shallow clone. bool isShallow(Pointer repo) { final result = libgit2.git_repository_is_shallow(repo); return result == 1 ? true : false; } /// Check if a repository is a linked work tree. bool isWorktree(Pointer repo) { final result = libgit2.git_repository_is_worktree(repo); return result == 1 ? true : false; } /// Retrieve git's prepared message. /// /// Operations such as git revert/cherry-pick/merge with the -n option /// stop just short of creating a commit with the changes and save their /// prepared message in .git/MERGE_MSG so the next git-commit execution /// can present it to the user for them to amend if they wish. /// /// Use this function to get the contents of this file. /// Don't forget to remove the file with [removeMessage] after you create the commit. /// /// Throws a [LibGit2Error] if error occured. String message(Pointer repo) { final out = calloc(sizeOf()); final error = libgit2.git_repository_message(out, repo); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } else { return out.ref.ptr.cast().toDartString(); } } /// Remove git's prepared message. void removeMessage(Pointer repo) { libgit2.git_repository_message_remove(repo); } /// Get the Object Database for this repository. /// /// If a custom ODB has not been set, the default database for the repository /// will be returned (the one located in `.git/objects`). /// /// The ODB must be freed once it's no longer being used. /// /// Throws a [LibGit2Error] if error occured. Pointer odb(Pointer repo) { final out = calloc>(); final error = libgit2.git_repository_odb(out, repo); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } else { return out.value; } } /// Get the Reference Database Backend for this repository. /// /// If a custom refsdb has not been set, the default database for the repository /// will be returned (the one that manipulates loose and packed references in /// the `.git` directory). /// /// The refdb must be freed once it's no longer being used. /// /// Throws a [LibGit2Error] if error occured. Pointer refdb(Pointer repo) { final out = calloc>(); final error = libgit2.git_repository_refdb(out, repo); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } else { return out.value; } } /// Make the repository HEAD point to the specified reference. /// /// If the provided reference points to a Tree or a Blob, the HEAD is unaltered. /// /// If the provided reference points to a branch, the HEAD will point to that branch, /// staying attached, or become attached if it isn't yet. /// /// If the branch doesn't exist yet, the HEAD will be attached to an unborn branch. /// /// Otherwise, the HEAD will be detached and will directly point to the Commit. /// /// Throws a [LibGit2Error] if error occured. void setHead({ required Pointer repoPointer, required String refname, }) { final refnameC = refname.toNativeUtf8().cast(); final error = libgit2.git_repository_set_head(repoPointer, refnameC); calloc.free(refnameC); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Make the repository HEAD directly point to the commit. /// /// If the provided committish cannot be found in the repository, the HEAD is unaltered. /// /// If the provided commitish cannot be peeled into a commit, the HEAD is unaltered. /// /// Otherwise, the HEAD will eventually be detached and will directly point to the peeled commit. /// /// Throws a [LibGit2Error] if error occured. void setHeadDetached({ required Pointer repoPointer, required Pointer commitishPointer, }) { final error = libgit2.git_repository_set_head_detached( repoPointer, commitishPointer, ); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Make the repository HEAD directly point to the commit. /// /// This behaves like [setHeadDetached] but takes an annotated commit, /// which lets you specify which extended sha syntax string was specified /// by a user, allowing for more exact reflog messages. /// /// See the documentation for [setHeadDetached]. void setHeadDetachedFromAnnotated({ required Pointer repoPointer, required Pointer commitishPointer, }) { final error = libgit2.git_repository_set_head_detached_from_annotated( repoPointer, commitishPointer, ); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Set the path to the working directory for this repository. /// /// The working directory doesn't need to be the same one that contains the /// `.git` folder for this repository. /// /// If this repository is bare, setting its working directory will turn it into a /// normal repository, capable of performing all the common workdir operations /// (checkout, status, index manipulation, etc). /// /// Throws a [LibGit2Error] if error occured. void setWorkdir({ required Pointer repoPointer, required String path, required bool updateGitlink, }) { final workdir = path.toNativeUtf8().cast(); final updateGitlinkC = updateGitlink ? 1 : 0; final error = libgit2.git_repository_set_workdir( repoPointer, workdir, updateGitlinkC, ); calloc.free(workdir); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Determines the status of a git repository - ie, whether an operation /// (merge, cherry-pick, etc) is in progress. int state(Pointer repo) => libgit2.git_repository_state(repo); /// Remove all the metadata associated with an ongoing command like /// merge, revert, cherry-pick, etc. For example: MERGE_HEAD, MERGE_MSG, etc. /// /// Throws a [LibGit2Error] if error occured. void stateCleanup(Pointer repo) { final error = libgit2.git_repository_state_cleanup(repo); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Get the path of the working directory for this repository. /// /// If the repository is bare, this function will always return empty string. String workdir(Pointer repo) { final result = libgit2.git_repository_workdir(repo); if (result == nullptr) { return ''; } else { return result.cast().toDartString(); } } /// Create a "fake" repository to wrap an object database /// /// Create a repository object to wrap an object database to be used with the API /// when all you have is an object database. This doesn't have any paths associated /// with it, so use with care. /// /// Throws a [LibGit2Error] if error occured. Pointer wrapODB(Pointer odb) { final out = calloc>(); final error = libgit2.git_repository_wrap_odb(out, odb); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } else { return out.value; } } /// Free a previously allocated repository. void free(Pointer repo) => libgit2.git_repository_free(repo);