import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'package:libgit2dart/libgit2dart.dart'; import 'package:libgit2dart/src/bindings/attr.dart' as attr_bindings; import 'package:libgit2dart/src/bindings/checkout.dart' as checkout_bindings; import 'package:libgit2dart/src/bindings/commit.dart' as commit_bindings; import 'package:libgit2dart/src/bindings/describe.dart' as describe_bindings; import 'package:libgit2dart/src/bindings/diff.dart' as diff_bindings; import 'package:libgit2dart/src/bindings/graph.dart' as graph_bindings; import 'package:libgit2dart/src/bindings/libgit2_bindings.dart'; import 'package:libgit2dart/src/bindings/merge.dart' as merge_bindings; import 'package:libgit2dart/src/bindings/object.dart' as object_bindings; import 'package:libgit2dart/src/bindings/repository.dart' as bindings; import 'package:libgit2dart/src/bindings/reset.dart' as reset_bindings; import 'package:libgit2dart/src/bindings/status.dart' as status_bindings; import 'package:libgit2dart/src/util.dart'; class Repository { /// Initializes a new instance of the [Repository] class from provided /// pointer to repository object in memory. /// /// **IMPORTANT**: Should be freed to release allocated memory. Repository(this._repoPointer); /// Creates new git repository at the provided [path]. /// /// [path] is the path to the repository. /// /// [bare] whether new repository should be bare. /// /// [flags] is a combination of [GitRepositoryInit] flags. Defaults to /// [GitRepositoryInit.mkdir]. /// /// [mode] is the permissions for the folder. Default to 0 (permissions /// configured by umask). /// /// [workdirPath] is the path to the working directory. Can be null for /// default path. /// /// [description] if set will be used to initialize the "description" file in /// the repository, instead of using the template content. /// /// [templatePath] is the the path to use for the template directory if /// [GitRepositoryInit.externalTemplate] is set. Defaults to the config or /// default directory options. /// /// [initialHead] is the name of the head to point HEAD at. If null, then /// this will be treated as "master" and the HEAD ref will be set to /// "refs/heads/master". If this begins with "refs/" it will be used /// verbatim, otherwise "refs/heads/" will be prefixed. /// /// [originUrl] if set, then after the rest of the repository initialization /// is completed, an "origin" remote will be added pointing to this URL. /// /// **IMPORTANT**: Should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. Repository.init({ required String path, bool bare = false, Set flags = const {GitRepositoryInit.mkpath}, int mode = 0, String? workdirPath, String? description, String? templatePath, String? initialHead, String? originUrl, }) { libgit2.git_libgit2_init(); var flagsInt = flags.fold(0, (int acc, e) => acc | e.value); if (bare) { flagsInt |= GitRepositoryInit.bare.value; } _repoPointer = bindings.init( path: path, flags: flagsInt, mode: mode, workdirPath: workdirPath, description: description, templatePath: templatePath, initialHead: initialHead, originUrl: originUrl, ); } /// Opens repository at provided [path]. /// /// For a standard repository, [path] should either point to the ".git" folder /// or to the working directory. For a bare repository, [path] should directly /// point to the repository folder. /// /// **IMPORTANT**: Should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. Repository.open(String path) { libgit2.git_libgit2_init(); _repoPointer = bindings.open(path); } /// Clones a remote repository at provided [url] into [localPath]. /// /// By default this creates its repository and initial remote to match git's /// defaults. You can use the [remote] and [repository] options to customize /// how these are created. /// /// [url] is the remote repository to clone. /// /// [localPath] is the local directory to clone to. /// /// [bare] whether cloned repo should be bare. /// /// [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. /// /// [callbacks] is the combination of callback functions from [Callbacks] /// object. /// /// **IMPORTANT**: Should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. Repository.clone({ required String url, required String localPath, bool bare = false, Remote Function(Repository, String, String)? remote, Repository Function(String, bool)? repository, String? checkoutBranch, Callbacks callbacks = const Callbacks(), }) { libgit2.git_libgit2_init(); _repoPointer = bindings.clone( url: url, localPath: localPath, bare: bare, remote: remote, repository: repository, checkoutBranch: checkoutBranch, callbacks: callbacks, ); } late final Pointer _repoPointer; /// Pointer to memory address for allocated repository object. Pointer get pointer => _repoPointer; /// Looks 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). static String discover({required String startPath, String? ceilingDirs}) { return bindings.discover( startPath: startPath, ceilingDirs: ceilingDirs, ); } /// Returns [Oid] object if it can be found in the ODB of repository with /// provided hexadecimal [sha] string that is 40 characters long or shorter. /// /// Throws [ArgumentError] if provided [sha] hex string is not valid or /// [LibGit2Error] if error occured. Oid operator [](String sha) { return Oid.fromSHA(repo: this, sha: sha); } /// Path to the ".git" folder for normal repositories or path to the /// repository itself for bare repositories. String get path => bindings.path(_repoPointer); /// 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 get commonDir => bindings.commonDir(_repoPointer); /// 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 get namespace => bindings.getNamespace(_repoPointer); /// 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. /// /// Pass null to unset. void setNamespace(String? namespace) { bindings.setNamespace( repoPointer: _repoPointer, namespace: namespace, ); } /// Whether repository is a bare repository. bool get isBare => bindings.isBare(_repoPointer); /// Whether 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 get isEmpty => bindings.isEmpty(_repoPointer); /// Whether 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 get isHeadDetached { return bindings.isHeadDetached(_repoPointer); } /// Makes the repository HEAD point to the specified reference or commit. /// /// If the provided [target] points to a Tree or a Blob, the HEAD is /// unaltered. /// /// If the provided [target] 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 or [ArgumentError] if provided /// [target] is not [Oid] or string. void setHead(Object target) { if (target is Oid) { bindings.setHeadDetached( repoPointer: _repoPointer, commitishPointer: target.pointer, ); } else if (target is String) { bindings.setHead(repoPointer: _repoPointer, refname: target); } else { throw ArgumentError.value( '$target must be either Oid or String reference name', ); } } /// Whether 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 get isBranchUnborn { return bindings.isBranchUnborn(_repoPointer); } /// Sets 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 String? name, required String? email}) { bindings.setIdentity( repoPointer: _repoPointer, name: name, email: email, ); } /// Configured identity to use for reflogs. /// /// Returns map with name as key and email as value. Map get identity => bindings.identity(_repoPointer); /// Whether repository was a shallow clone. bool get isShallow => bindings.isShallow(_repoPointer); /// Whether repository is a linked work tree. bool get isWorktree => bindings.isWorktree(_repoPointer); /// 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. /// /// **IMPORTANT**: remove the file with [removeMessage] after you create the /// commit. /// /// Throws a [LibGit2Error] if error occured. String get message => bindings.message(_repoPointer); /// Removes git's prepared message. void removeMessage() => bindings.removeMessage(_repoPointer); /// Status of a git repository - ie, whether an operation (merge, /// cherry-pick, etc) is in progress. GitRepositoryState get state { final stateInt = bindings.state(_repoPointer); return GitRepositoryState.values.singleWhere( (state) => stateInt == state.value, ); } /// Removes 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() => bindings.stateCleanup(_repoPointer); /// Path of the working directory for this repository. /// /// If the repository is bare, this method will always return empty string. String get workdir => bindings.workdir(_repoPointer); /// Sets 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). /// /// [updateGitLink] if set creates/updates gitlink in workdir and sets config /// "core.worktree" (if workdir is not the parent of the ".git" directory) /// /// Throws a [LibGit2Error] if error occured. void setWorkdir({required String path, bool updateGitlink = false}) { bindings.setWorkdir( repoPointer: _repoPointer, path: path, updateGitlink: updateGitlink, ); } /// Releases memory allocated for repository object. void free() => bindings.free(_repoPointer); @override String toString() { return 'Repository{path: $path, commonDir: $commonDir, ' 'namespace: $namespace, isBare: $isBare, isEmpty: $isEmpty, ' 'isHeadDetached: $isHeadDetached, isBranchUnborn: $isBranchUnborn, ' 'isShallow: $isShallow, isWorktree: $isWorktree, state: $state, ' 'workdir: $workdir}'; } /// 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). /// /// **IMPORTANT**: Should be freed to release allocated memory. Config get config => Config(bindings.config(_repoPointer)); /// 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. /// /// **IMPORTANT**: Should be freed to release allocated memory. Config get configSnapshot => Config(bindings.configSnapshot(_repoPointer)); /// Repository's head. /// /// **IMPORTANT**: Should be freed to release allocated memory. Reference get head => Reference(bindings.head(_repoPointer)); /// List of all the references names that can be found in a repository. /// /// Throws a [LibGit2Error] if error occured. List get references => Reference.list(this); /// Lookups reference [name] in a repository. /// /// The [name] will be checked for validity. /// /// **IMPORTANT**: Should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. Reference lookupReference(String name) { return Reference.lookup(repo: this, name: name); } /// Creates a new reference for provided [target]. /// /// The reference will be created in the repository and written to the disk. /// /// **IMPORTANT**: The generated [Reference] object should be freed to release /// allocated memory. /// /// Valid reference [name]s 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 [logMessage] 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. /// /// Throws a [LibGit2Error] if error occured or [ArgumentError] if provided /// [target] is not Oid or String reference name. Reference createReference({ required String name, required Object target, bool force = false, String? logMessage, }) { return Reference.create( repo: this, name: name, target: target, force: force, logMessage: logMessage, ); } /// Deletes an existing reference with provided [name]. /// /// This method works for both direct and symbolic references. /// /// Throws a [LibGit2Error] if error occured. void deleteReference(String name) => Reference.delete(repo: this, name: name); /// Renames an existing reference with provided [oldName]. /// /// This method works for both direct and symbolic references. /// /// The [newName] will be checked for validity. /// /// If the [force] flag is set to false, and there's already a reference with /// the given name, the renaming will fail. /// /// IMPORTANT: The user needs to write a proper reflog entry [logMessage] if /// the reflog is enabled for the repository. We only rename the reflog if it /// exists. /// /// Throws a [LibGit2Error] if error occured. void renameReference({ required String oldName, required String newName, bool force = false, String? logMessage, }) { Reference.rename( repo: this, oldName: oldName, newName: newName, force: force, logMessage: logMessage, ); } /// Index file for this repository. /// /// **IMPORTANT**: Should be freed to release allocated memory. Index get index => Index(bindings.index(_repoPointer)); /// ODB for this repository. /// /// **IMPORTANT**: Should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. Odb get odb => Odb(bindings.odb(_repoPointer)); /// Lookups a tree object for provided [oid]. /// /// **IMPORTANT**: Should be freed to release allocated memory. Tree lookupTree(Oid oid) { return Tree.lookup(repo: this, oid: oid); } /// Creates a new action signature with default user and now timestamp. /// /// This looks up the "user.name" and "user.email" from the configuration and /// uses the current time as the timestamp, and creates a new signature based /// on that information. Signature get defaultSignature => Signature.defaultSignature(this); /// Returns the list of commits starting from provided commit [oid]. /// /// If [sorting] isn't provided default will be used (reverse chronological /// order, like in git). /// /// **IMPORTANT**: Commits should be freed to release allocated memory. List log({ required Oid oid, Set sorting = const {GitSort.none}, }) { final walker = RevWalk(this); walker.sorting(sorting); walker.push(oid); final result = walker.walk(); walker.free(); return result; } /// Finds a single object, as specified by a [spec] revision string. /// See `man gitrevisions`, or https://git-scm.com/docs/git-rev-parse.html#_specifying_revisions /// for information on the syntax accepted. /// /// **IMPORTANT**: Should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. Commit revParseSingle(String spec) { return RevParse.single(repo: this, spec: spec); } /// Lookups commit object for provided [oid]. /// /// **IMPORTANT**: Should be freed to release allocated memory. Commit lookupCommit(Oid oid) { return Commit.lookup(repo: this, oid: oid); } /// Creates new commit in the repository. /// /// [updateRef] is the name of the reference that will be updated to point to /// this commit. If the reference is not direct, it will be resolved to a /// direct reference. Use "HEAD" to update the HEAD of the current branch and /// make it point to this commit. If the reference doesn't exist yet, it will /// be created. If it does exist, the first parent must be the tip of this /// branch. /// /// [author] is the signature with author and author time of commit. /// /// [committer] is the signature with committer and commit time of commit. /// /// [messageEncoding] is the encoding for the message in the commit, /// represented with a standard encoding name. E.g. "UTF-8". If null, no /// encoding header is written and UTF-8 is assumed. /// /// [message] is the full message for this commit. /// /// [tree] is an instance of a [Tree] object that will be used as the tree /// for the commit. /// /// [parents] is a list of [Commit] objects that will be used as the parents /// for this commit. This list may be empty if parent count is 0 (root /// commit). All the given commits must be owned by the repo. /// /// Throws a [LibGit2Error] if error occured. Oid createCommit({ required String updateRef, required String message, required Signature author, required Signature committer, required Tree tree, required List parents, String? messageEncoding, }) { return Commit.create( repo: this, updateRef: updateRef, message: message, author: author, committer: committer, tree: tree, parents: parents, messageEncoding: messageEncoding, ); } /// Amends an existing commit by replacing only non-null values. /// /// This creates a new commit that is exactly the same as the old commit, /// except that any non-null values will be updated. The new commit has the /// same parents as the old commit. /// /// The [updateRef] value works as in the regular [create], updating the ref /// to point to the newly rewritten commit. If you want to amend a commit /// that is not currently the tip of the branch and then rewrite the /// following commits to reach a ref, pass this as null and update the rest /// of the commit chain and ref separately. /// /// Unlike [create], the [author], [committer], [message], [messageEncoding], /// and [tree] arguments can be null in which case this will use the values /// from the original [commit]. /// /// All arguments have the same meanings as in [create]. /// /// Throws a [LibGit2Error] if error occured. Oid amendCommit({ required Commit commit, required String? updateRef, Signature? author, Signature? committer, Tree? tree, String? message, String? messageEncoding, }) { return Commit.amend( repo: this, commit: commit, updateRef: updateRef, author: author, committer: committer, tree: tree, message: message, messageEncoding: messageEncoding, ); } /// Reverts provided [revertCommit] against provided [ourCommit], producing /// an index that reflects the result of the revert. /// /// [mainline] is parent of the [revertCommit] if it is a merge (i.e. 1, 2). /// /// **IMPORTANT**: produced index should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. Index revertCommit({ required Commit revertCommit, required Commit ourCommit, int mainline = 0, }) { return Index( commit_bindings.revertCommit( repoPointer: _repoPointer, revertCommitPointer: revertCommit.pointer, ourCommitPointer: ourCommit.pointer, mainline: mainline, ), ); } /// Reverts the given [commit], producing changes in the index and working /// directory. /// /// Throws a [LibGit2Error] if error occured. void revert(Commit commit) { commit_bindings.revert( repoPointer: _repoPointer, commitPointer: commit.pointer, ); } /// Finds a single object and intermediate reference (if there is one) by a /// [spec] revision string. /// /// See `man gitrevisions`, or https://git-scm.com/docs/git-rev-parse.html#_specifying_revisions /// for information on the syntax accepted. /// /// In some cases (@{<-n>} or @{upstream}), the expression may /// point to an intermediate reference. When such expressions are being /// passed in, reference_out will be valued as well. /// /// **IMPORTANT**: the returned object and reference should be released when /// no longer needed. /// /// Throws a [LibGit2Error] if error occured. RevParse revParseExt(String spec) { return RevParse.ext(repo: this, spec: spec); } /// Parses a revision string for from, to, and intent. /// /// See `man gitrevisions` or https://git-scm.com/docs/git-rev-parse.html#_specifying_revisions /// for information on the syntax accepted. /// /// Throws a [LibGit2Error] if error occured. RevSpec revParse(String spec) { return RevParse.range(repo: this, spec: spec); } /// Lookups a blob object for provided [oid]. /// /// Should be freed to release allocated memory. Blob lookupBlob(Oid oid) { return Blob.lookup(repo: this, oid: oid); } /// Creates a new blob from a [content] string and writes it to ODB. /// /// Throws a [LibGit2Error] if error occured. Oid createBlob(String content) => Blob.create(repo: this, content: content); /// Creates a new blob from the file in working directory of a repository and /// writes it to the ODB. Provided [relativePath] should be relative to the /// working directory. /// /// Throws a [LibGit2Error] if error occured. Oid createBlobFromWorkdir(String relativePath) { return Blob.createFromWorkdir( repo: this, relativePath: relativePath, ); } /// Creates a new blob from the file at provided [path] in filesystem and /// writes it to the ODB. /// /// Throws a [LibGit2Error] if error occured. Oid createBlobFromDisk(String path) { return Blob.createFromDisk(repo: this, path: path); } /// List with all the tags names in the repository. /// /// Throws a [LibGit2Error] if error occured. List get tags => Tag.list(this); /// Lookups tag object for provided [oid]. /// /// **IMPORTANT**: Should be freed to release allocated memory. Tag lookupTag(Oid oid) => Tag.lookup(repo: this, oid: oid); /// Creates a new annotated tag in the repository for provided [target] /// object. /// /// A new reference will also be created in the `/refs/tags` folder pointing /// to this tag object. If [force] is true and a reference already exists /// with the given name, it'll be replaced. /// /// The [tagName] will be checked for validity. You must avoid the characters /// '~', '^', ':', '\', '?', '[', and '*', and the sequences ".." and "@{" which have /// special meaning to revparse. /// /// [tagName] is the name for the tag. This name is validated for /// consistency. It should also not conflict with an already existing tag /// name. /// /// [target] is the object to which this tag points. This object must belong /// to the given [repo]. /// /// [targetType] is one of the [GitObject] basic types: commit, tree, blob or /// tag. /// /// [tagger] is the signature of the tagger for this tag, and of the tagging /// time. /// /// [message] is the full message for this tag. /// /// [force] determines whether existing reference with the same [tagName] /// should be replaced. /// /// Throws a [LibGit2Error] if error occured. Oid createAnnotatedTag({ required String tagName, required Oid target, required GitObject targetType, required Signature tagger, required String message, bool force = false, }) { return Tag.createAnnotated( repo: this, tagName: tagName, target: target, targetType: targetType, tagger: tagger, message: message, force: force, ); } /// Creates a new lightweight tag in the repository for provided [target] /// object. /// /// A new reference will also be created in the `/refs/tags` folder pointing /// to this tag object. If [force] is true and a reference already exists /// with the given name, it'll be replaced. /// /// The [tagName] will be checked for validity. You must avoid the characters /// '~', '^', ':', '\', '?', '[', and '*', and the sequences ".." and "@{" which have /// special meaning to revparse. /// /// [tagName] is the name for the tag. This name is validated for /// consistency. It should also not conflict with an already existing tag /// name. /// /// [target] is the object to which this tag points. This object must belong /// to the given [repo]. /// /// [targetType] is one of the [GitObject] basic types: commit, tree, blob or /// tag. /// /// [force] determines whether existing reference with the same [tagName] /// should be replaced. /// /// Throws a [LibGit2Error] if error occured. void createLightweightTag({ required String tagName, required Oid target, required GitObject targetType, bool force = false, }) { Tag.createLightweight( repo: this, tagName: tagName, target: target, targetType: targetType, force: force, ); } /// Deletes an existing tag reference with provided [name]. /// /// The tag [name] will be checked for validity. /// /// Throws a [LibGit2Error] if error occured. void deleteTag(String name) => Tag.delete(repo: this, name: name); /// List of all branches that can be found in a repository. /// /// **IMPORTANT**: Branches should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. List get branches => Branch.list(repo: this); /// List of local branches that can be found in a repository. /// /// **IMPORTANT**: Branches should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. List get branchesLocal => Branch.list(repo: this, type: GitBranch.local); /// List of remote branches that can be found in a repository. /// /// **IMPORTANT**: Branches should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. List get branchesRemote => Branch.list(repo: this, type: GitBranch.remote); /// Lookups a branch by its [name] and [type] in a repository. /// /// The branch [name] will be checked for validity. /// /// If branch [type] is [GitBranch.remote] you must include the remote name /// in the [name] (e.g. "origin/master"). /// /// **IMPORTANT**: Branches should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. Branch lookupBranch({ required String name, GitBranch type = GitBranch.local, }) { return Branch.lookup(repo: this, name: name, type: type); } /// Creates a new branch pointing at a [target] commit. /// /// A new direct reference will be created pointing to this target commit. /// If [force] is true and a reference already exists with the given name, /// it'll be replaced. /// /// [name] is the name for the branch, this name is validated for consistency. /// It should also not conflict with an already existing branch name. /// /// [target] is the commit to which this branch should point. This object must /// belong to the repo. /// /// **IMPORTANT**: Should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. Branch createBranch({ required String name, required Commit target, bool force = false, }) { return Branch.create( repo: this, name: name, target: target, force: force, ); } /// Deletes an existing branch reference with provided [name]. /// /// Throws a [LibGit2Error] if error occured. void deleteBranch(String name) => Branch.delete(repo: this, name: name); /// Renames an existing local branch reference with provided [oldName]. /// /// The new branch name [newName] will be checked for validity. /// /// If [force] is true, existing branch will be overwritten. /// /// Throws a [LibGit2Error] if error occured. void renameBranch({ required String oldName, required String newName, bool force = false, }) { Branch.rename(repo: this, oldName: oldName, newName: newName, force: force); } /// Status of the repository. /// /// Returns map of file paths and their statuses. /// /// Returns empty map if there are no changes in statuses. /// /// Throws a [LibGit2Error] if error occured. Map> get status { final result = >{}; final list = status_bindings.listNew(_repoPointer); final count = status_bindings.listEntryCount(list); for (var i = 0; i < count; i++) { late String path; final entry = status_bindings.getByIndex( statuslistPointer: list, index: i, ); if (entry.ref.head_to_index != nullptr) { path = entry.ref.head_to_index.ref.old_file.path .cast() .toDartString(); } else { path = entry.ref.index_to_workdir.ref.old_file.path .cast() .toDartString(); } // Skipping GitStatus.current because entry that is in the list can't be // without changes but `&` on `0` value falsly adds it to the set of flags result[path] = GitStatus.values .skip(1) .where((e) => entry.ref.status & e.value == e.value) .toSet(); } status_bindings.listFree(list); return result; } /// Returns file status for a single file at provided [path]. /// /// This does not do any sort of rename detection. Renames require a set of /// targets and because of the path filtering, there is not enough /// information to check renames correctly. To check file status with rename /// detection, there is no choice but to do a full [status] and scan through /// looking for the path that you are interested in. /// /// Throws a [LibGit2Error] if error occured. Set statusFile(String path) { final statusInt = status_bindings.file( repoPointer: _repoPointer, path: path, ); if (statusInt == GitStatus.current.value) { return {GitStatus.current}; } else { // Skipping GitStatus.current because `&` on `0` value falsly adds it to // the set of flags return GitStatus.values .skip(1) .where((e) => statusInt & e.value == e.value) .toSet(); } } /// Finds a merge base between [commits]. /// /// Throws a [LibGit2Error] if error occured. Oid mergeBase(List commits) { return commits.length == 2 ? Oid( merge_bindings.mergeBase( repoPointer: _repoPointer, aPointer: commits[0].pointer, bPointer: commits[1].pointer, ), ) : Oid( merge_bindings.mergeBaseMany( repoPointer: _repoPointer, commits: commits.map((e) => e.pointer.ref).toList(), ), ); } /// Finds a merge base in preparation for an octopus merge. /// /// Throws a [LibGit2Error] if error occured. Oid mergeBaseOctopus(List commits) { return Oid( merge_bindings.mergeBaseOctopus( repoPointer: _repoPointer, commits: commits.map((e) => e.pointer.ref).toList(), ), ); } /// Analyzes the given branch's [theirHead] oid and determines the /// opportunities for merging them into [ourRef] reference (default is /// 'HEAD'). /// /// Returns list with analysis result and preference for fast forward merge /// values respectively. /// /// Throws a [LibGit2Error] if error occured. List mergeAnalysis({ required Oid theirHead, String ourRef = 'HEAD', }) { final ref = lookupReference(ourRef); final head = AnnotatedCommit.lookup( repo: this, oid: theirHead, ); final analysisInt = merge_bindings.analysis( repoPointer: _repoPointer, ourRefPointer: ref.pointer, theirHeadPointer: head.pointer, theirHeadsLen: 1, ); final analysisSet = GitMergeAnalysis.values .where((e) => analysisInt[0] & e.value == e.value) .toSet(); final mergePreference = GitMergePreference.values.singleWhere( (e) => analysisInt[1] == e.value, ); head.free(); ref.free(); return [analysisSet, mergePreference]; } /// Merges the given [commit] into HEAD, writing the results into the /// working directory. Any changes are staged for commit and any conflicts /// are written to the index. Callers should inspect the repository's index /// after this completes, resolve any conflicts and prepare a commit. /// /// [favor] is one of the [GitMergeFileFavor] flags for handling conflicting /// content. Defaults to [GitMergeFileFavor.normal], recording conflict to t /// he index. /// /// [mergeFlags] is a combination of [GitMergeFlag] flags. Defaults to /// [GitMergeFlag.findRenames] enabling the ability to merge between a /// modified and renamed file. /// /// [fileFlags] is a combination of [GitMergeFileFlag] flags. Defaults to /// [GitMergeFileFlag.defaults]. /// /// Throws a [LibGit2Error] if error occured. void merge({ required AnnotatedCommit commit, GitMergeFileFavor favor = GitMergeFileFavor.normal, Set mergeFlags = const {GitMergeFlag.findRenames}, Set fileFlags = const {GitMergeFileFlag.defaults}, }) { merge_bindings.merge( repoPointer: _repoPointer, theirHeadPointer: commit.pointer, theirHeadsLen: 1, favor: favor.value, mergeFlags: mergeFlags.fold(0, (acc, e) => acc | e.value), fileFlags: fileFlags.fold(0, (acc, e) => acc | e.value), ); } /// Merges two files as they exist in the in-memory data structures, using the /// given common ancestor as the baseline, producing a string that reflects /// the merge result. /// /// Note that this function does not reference a repository and configuration /// must be passed as [favor] and [flags]. /// /// [ancestor] is the contents of the ancestor file. /// /// [ancestorLabel] is optional label for the ancestor file side of the /// conflict which will be prepended to labels in diff3-format merge files. /// Defaults to "file.txt". /// /// [ours] is the contents of the file in "our" side. /// /// [oursLabel] is optional label for our file side of the conflict which /// will be prepended to labels in merge files. Defaults to "file.txt". /// /// [theirs] is the contents of the file in "their" side. /// /// [theirsLabel] is optional label for their file side of the conflict which /// will be prepended to labels in merge files. Defaults to "file.txt". /// /// [favor] is one of the [GitMergeFileFavor] flags for handling conflicting /// content. Defaults to [GitMergeFileFavor.normal]. /// /// [flags] is a combination of [GitMergeFileFlag] flags. Defaults to /// [GitMergeFileFlag.defaults]. String mergeFile({ required String ancestor, String ancestorLabel = '', required String ours, String oursLabel = '', required String theirs, String theirsLabel = '', GitMergeFileFavor favor = GitMergeFileFavor.normal, Set flags = const {GitMergeFileFlag.defaults}, }) { return merge_bindings.mergeFile( ancestor: ancestor, ancestorLabel: ancestorLabel, ours: ours, oursLabel: oursLabel, theirs: theirs, theirsLabel: theirsLabel, favor: favor.value, flags: flags.fold(0, (acc, e) => acc | e.value), ); } /// Merges two files [ours] and [theirs] as they exist in the index, using the /// given common [ancestor] as the baseline, producing a string that reflects /// the merge result containing possible conflicts. /// /// Throws a [LibGit2Error] if error occured. String mergeFileFromIndex({ required IndexEntry? ancestor, required IndexEntry ours, required IndexEntry theirs, }) { return merge_bindings.mergeFileFromIndex( repoPointer: _repoPointer, ancestorPointer: ancestor?.pointer, oursPointer: ours.pointer, theirsPointer: theirs.pointer, ); } /// Merges two commits, producing an index that reflects the result of the /// merge. The index may be written as-is to the working directory or checked /// out. If the index is to be converted to a tree, the caller should resolve /// any conflicts that arose as part of the merge. /// /// [ourCommit] is the commit that reflects the destination tree. /// /// [theirCommit] is the commit to merge into [ourCommit]. /// /// [favor] is one of the [GitMergeFileFavor] flags for handling conflicting /// content. Defaults to [GitMergeFileFavor.normal], recording conflict to t /// he index. /// /// [mergeFlags] is a combination of [GitMergeFlag] flags. Defaults to /// [GitMergeFlag.findRenames] enabling the ability to merge between a /// modified and renamed file. /// /// [fileFlags] is a combination of [GitMergeFileFlag] flags. Defaults to /// [GitMergeFileFlag.defaults]. /// /// **IMPORTANT**: returned index should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. Index mergeCommits({ required Commit ourCommit, required Commit theirCommit, GitMergeFileFavor favor = GitMergeFileFavor.normal, Set mergeFlags = const {GitMergeFlag.findRenames}, Set fileFlags = const {GitMergeFileFlag.defaults}, }) { return Index( merge_bindings.mergeCommits( repoPointer: _repoPointer, ourCommitPointer: ourCommit.pointer, theirCommitPointer: theirCommit.pointer, favor: favor.value, mergeFlags: mergeFlags.fold(0, (acc, e) => acc | e.value), fileFlags: fileFlags.fold(0, (acc, e) => acc | e.value), ), ); } /// Merges two trees, producing an index that reflects the result of the /// merge. The index may be written as-is to the working directory or checked /// out. If the index is to be converted to a tree, the caller should resolve /// any conflicts that arose as part of the merge. /// /// [ancestorTree] is the common ancestor between the trees, or null if none. /// /// [ourTree] is the tree that reflects the destination tree. /// /// [theirTree] is the tree to merge into [ourTree]. /// /// [favor] is one of the [GitMergeFileFavor] flags for handling conflicting /// content. Defaults to [GitMergeFileFavor.normal], recording conflict to /// the index. /// /// [mergeFlags] is a combination of [GitMergeFlag] flags. Defaults to /// [GitMergeFlag.findRenames] enabling the ability to merge between a /// modified and renamed file. /// /// [fileFlags] is a combination of [GitMergeFileFlag] flags. Defaults to /// [GitMergeFileFlag.defaults]. /// /// **IMPORTANT**: returned index should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. Index mergeTrees({ Tree? ancestorTree, required Tree ourTree, required Tree theirTree, GitMergeFileFavor favor = GitMergeFileFavor.normal, List mergeFlags = const [GitMergeFlag.findRenames], List fileFlags = const [GitMergeFileFlag.defaults], }) { return Index( merge_bindings.mergeTrees( repoPointer: _repoPointer, ancestorTreePointer: ancestorTree?.pointer ?? nullptr, ourTreePointer: ourTree.pointer, theirTreePointer: theirTree.pointer, favor: favor.value, mergeFlags: mergeFlags.fold(0, (acc, element) => acc | element.value), fileFlags: fileFlags.fold(0, (acc, element) => acc | element.value), ), ); } /// Cherry-picks the provided [commit], producing changes in the index and /// working directory. /// /// Any changes are staged for commit and any conflicts are written to the /// index. Callers should inspect the repository's index after this /// completes, resolve any conflicts and prepare a commit. /// /// Throws a [LibGit2Error] if error occured. void cherryPick(Commit commit) { merge_bindings.cherryPick( repoPointer: _repoPointer, commitPointer: commit.pointer, ); } /// Checkouts the provided [target] using the given strategy, and updates the /// HEAD. /// /// [target] can be null, 'HEAD', reference name or commit [Oid]. /// /// If no [target] is given, updates files in the working tree to match the /// content of the index. /// /// If [target] is 'HEAD' string, updates files in the index and the working /// tree to match the content of the commit pointed at by HEAD. /// /// If [target] is reference name, updates files in the index and working /// tree to match the content of the tree pointed at by the reference. /// /// If [target] is commit oid, updates files in the index and working /// tree to match the content of the tree pointed at by the commit. /// /// Default checkout strategy is combination of [GitCheckout.safe] and /// [GitCheckout.recreateMissing]. /// /// [directory] is alternative checkout path to workdir. /// /// [paths] is list of files to checkout from provided [target]. /// If paths are provided, HEAD will not be set to the [target]. /// /// Throws [ArgumentError] if provided [target] is not String or [Oid]. void checkout({ Object? target, Set strategy = const { GitCheckout.safe, GitCheckout.recreateMissing }, String? directory, List? paths, }) { final strat = strategy.fold(0, (int acc, e) => acc | e.value); if (target == null) { checkout_bindings.index( repoPointer: _repoPointer, strategy: strat, directory: directory, paths: paths, ); } else if (target == 'HEAD') { checkout_bindings.head( repoPointer: _repoPointer, strategy: strat, directory: directory, paths: paths, ); } else if (target is String) { final ref = lookupReference(target); final treeish = object_bindings.lookup( repoPointer: _repoPointer, oidPointer: ref.target.pointer, type: GitObject.any.value, ); checkout_bindings.tree( repoPointer: _repoPointer, treeishPointer: treeish, strategy: strat, directory: directory, paths: paths, ); if (paths == null) { setHead(target); } ref.free(); } else if (target is Oid) { final treeish = object_bindings.lookup( repoPointer: _repoPointer, oidPointer: target.pointer, type: GitObject.any.value, ); checkout_bindings.tree( repoPointer: _repoPointer, treeishPointer: treeish, strategy: strat, directory: directory, paths: paths, ); if (paths == null) { setHead(target); } } else { throw ArgumentError.value('$target must be either String or Oid'); } } /// Sets the current head to the specified commit [oid] and optionally resets /// the index and working tree to match. /// /// [oid] is the committish to which the HEAD should be moved to. This object /// can either be a commit or a tag. When a tag oid is being passed, it /// should be dereferencable to a commit which oid will be used as the target /// of the branch. /// /// [resetType] is one of the [GitReset] flags. /// /// Throws a [LibGit2Error] if error occured. void reset({required Oid oid, required GitReset resetType}) { final object = object_bindings.lookup( repoPointer: _repoPointer, oidPointer: oid.pointer, type: GitObject.any.value, ); reset_bindings.reset( repoPointer: _repoPointer, targetPointer: object, resetType: resetType.value, checkoutOptsPointer: nullptr, ); object_bindings.free(object); } /// Updates some entries in the index from the [oid] commit tree. /// /// The scope of the updated entries is determined by the paths being passed /// in the [pathspec]. /// /// Throws a [LibGit2Error] if error occured. void resetDefault({required Oid oid, required List pathspec}) { final object = object_bindings.lookup( repoPointer: _repoPointer, oidPointer: oid.pointer, type: GitObject.commit.value, ); reset_bindings.resetDefault( repoPointer: _repoPointer, targetPointer: object, pathspec: pathspec, ); } /// Returns a [Diff] with changes between the trees, tree and index, tree and /// workdir or index and workdir. /// /// If [b] is null, by default the [a] tree compared to working directory. If /// [cached] is set to true the [a] tree compared to index/staging area. /// /// [flags] is a combination of [GitDiff] flags. Defaults to [GitDiff.normal]. /// /// [contextLines] is the number of unchanged lines that define the boundary /// of a hunk (and to display before and after). Defaults to 3. /// /// [interhunkLines] is the maximum number of unchanged lines between hunk /// boundaries before the hunks will be merged into one. Defaults to 0. /// /// **IMPORTANT**: Should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. Diff diff({ Tree? a, Tree? b, bool cached = false, Set flags = const {GitDiff.normal}, int contextLines = 3, int interhunkLines = 0, }) { final flagsInt = flags.fold(0, (int acc, e) => acc | e.value); if (a is Tree && b is Tree) { return Diff( diff_bindings.treeToTree( repoPointer: _repoPointer, oldTreePointer: a.pointer, newTreePointer: b.pointer, flags: flagsInt, contextLines: contextLines, interhunkLines: interhunkLines, ), ); } else if (a is Tree && b == null) { if (cached) { return Diff( diff_bindings.treeToIndex( repoPointer: _repoPointer, treePointer: a.pointer, indexPointer: index.pointer, flags: flagsInt, contextLines: contextLines, interhunkLines: interhunkLines, ), ); } else { return Diff( diff_bindings.treeToWorkdir( repoPointer: _repoPointer, treePointer: a.pointer, flags: flagsInt, contextLines: contextLines, interhunkLines: interhunkLines, ), ); } } else if (a == null && b == null) { return Diff( diff_bindings.indexToWorkdir( repoPointer: _repoPointer, indexPointer: index.pointer, flags: flagsInt, contextLines: contextLines, interhunkLines: interhunkLines, ), ); } else { throw ArgumentError.notNull('a'); } } /// Returns a [Patch] with changes between the blobs. /// /// [a] is the blob for old side of diff. /// /// [b] is the blob for new side of diff. /// /// [aPath] treat [a] blob as if it had this filename, can be null. /// /// [bPath] treat [b] blob as if it had this filename, can be null. /// /// [flags] is a combination of [GitDiff] flags. Defaults to [GitDiff.normal]. /// /// [contextLines] is the number of unchanged lines that define the boundary /// of a hunk (and to display before and after). Defaults to 3. /// /// [interhunkLines] is the maximum number of unchanged lines between hunk /// boundaries before the hunks will be merged into one. Defaults to 0. /// /// **IMPORTANT**: Should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. Patch diffBlobs({ required Blob a, required Blob b, String? aPath, String? bPath, Set flags = const {GitDiff.normal}, int contextLines = 3, int interhunkLines = 0, }) { return a.diff(newBlob: b, oldAsPath: aPath, newAsPath: bPath); } /// Applies the [diff] to the repository, making changes in the provided /// [location] (directly in the working directory (default), the index or /// both). /// /// [hunkIndex] is optional index of the hunk to apply. /// /// Throws a [LibGit2Error] if error occured. void apply({ required Diff diff, int? hunkIndex, GitApplyLocation location = GitApplyLocation.workdir, }) { diff_bindings.apply( repoPointer: _repoPointer, diffPointer: diff.pointer, hunkIndex: hunkIndex, location: location.value, ); } /// Applies the [diff] to the [tree], and returns the resulting image as an /// index. /// /// [hunkIndex] is optional index of the hunk to apply. /// /// Throws a [LibGit2Error] if error occured. Index applyToTree({ required Diff diff, required Tree tree, int? hunkIndex, }) { return Index( diff_bindings.applyToTree( repoPointer: _repoPointer, diffPointer: diff.pointer, treePointer: tree.pointer, hunkIndex: hunkIndex, ), ); } /// Checks if the [diff] will apply to provided [location] (the working /// directory (default), the index or both). /// /// [hunkIndex] is optional index of the hunk to apply. bool applies({ required Diff diff, int? hunkIndex, GitApplyLocation location = GitApplyLocation.workdir, }) { return diff_bindings.apply( repoPointer: _repoPointer, diffPointer: diff.pointer, hunkIndex: hunkIndex, location: location.value, check: true, ); } /// Returns list of all the stashed states, first being the most recent. List get stashes => Stash.list(this); /// Saves the local modifications to a new stash. /// /// [stasher] is the identity of the person performing the stashing. /// /// [message] is optional description along with the stashed state. /// /// [flags] is a combination of [GitStash] flags. Defaults to /// [GitStash.defaults]. /// /// Throws a [LibGit2Error] if error occured. Oid createStash({ required Signature stasher, String? message, Set flags = const {GitStash.defaults}, }) { return Stash.create( repo: this, stasher: stasher, message: message, flags: flags, ); } /// Applies a single stashed state from the stash list. /// /// [index] is the position of the stashed state in the list. Defaults to /// last saved. /// /// [reinstateIndex] whether to try to reinstate not only the working tree's /// changes, but also the index's changes. /// /// [strategy] is a combination of [GitCheckout] flags. Defaults to /// [GitCheckout.safe] with [GitCheckout.recreateMissing]. /// /// [directory] is the alternative checkout path to workdir, can be null. /// /// [paths] is a list of wildmatch patterns or paths. By default, all paths /// are processed. If you pass a list of wildmatch patterns, those will be /// used to filter which paths should be taken into account. /// /// Throws a [LibGit2Error] if error occured. void applyStash({ int index = 0, bool reinstateIndex = false, Set strategy = const { GitCheckout.safe, GitCheckout.recreateMissing }, String? directory, List? paths, }) { Stash.apply( repo: this, index: index, reinstateIndex: reinstateIndex, strategy: strategy, directory: directory, paths: paths, ); } /// Removes a single stashed state from the stash list at provided [index]. /// Defaults to the last saved stash. /// /// Throws a [LibGit2Error] if error occured. void dropStash({int index = 0}) { Stash.drop(repo: this, index: index); } /// Applies a single stashed state from the stash list and remove it from /// the list if successful. /// /// [index] is the position of the stashed state in the list. Defaults to /// last saved. /// /// [reinstateIndex] whether to try to reinstate not only the working tree's /// changes, but also the index's changes. /// /// [strategy] is a combination of [GitCheckout] flags. Defaults to /// [GitCheckout.safe] with [GitCheckout.recreateMissing]. /// /// [directory] is the alternative checkout path to workdir, can be null. /// /// [paths] is a list of wildmatch patterns or paths. By default, all paths /// are processed. If you pass a list of wildmatch patterns, those will be /// used to filter which paths should be taken into account. /// /// Throws a [LibGit2Error] if error occured. void popStash({ int index = 0, bool reinstateIndex = false, Set strategy = const { GitCheckout.safe, GitCheckout.recreateMissing }, String? directory, List? paths, }) { Stash.pop( repo: this, index: index, reinstateIndex: reinstateIndex, strategy: strategy, directory: directory, paths: paths, ); } /// List of the configured remotes names for a repository. List get remotes => Remote.list(this); /// Lookups remote with provided [name]. /// /// The [name] will be checked for validity. /// /// **IMPORTANT**: Should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. Remote lookupRemote(String name) { return Remote.lookup(repo: this, name: name); } /// Adds remote with provided [name] and [url] to the repository's /// configuration with the default [fetch] refspec if none provided. /// /// **IMPORTANT**: Should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. Remote createRemote({ required String name, required String url, String? fetch, }) { return Remote.create(repo: this, name: name, url: url, fetch: fetch); } /// Deletes an existing persisted remote with provided [name]. /// /// All remote-tracking branches and configuration settings for the remote /// will be removed. /// /// Throws a [LibGit2Error] if error occured. void deleteRemote(String name) => Remote.delete(repo: this, name: name); /// Renames remote with provided [oldName]. /// /// Returns list of non-default refspecs that cannot be renamed. /// /// All remote-tracking branches and configuration settings for the remote /// are updated. /// /// The [newName] 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 renameRemote({ required String oldName, required String newName, }) { return Remote.rename(repo: this, oldName: oldName, newName: newName); } /// Lookups the value of one git attribute with provided [name] for [path]. /// /// Returned value can be either `true`, `false`, `null` (if the attribute /// was not set at all), or a [String] value, if the attribute was set to an /// actual string. Object? getAttribute({ required String path, required String name, Set flags = const {GitAttributeCheck.fileThenIndex}, }) { return attr_bindings.getAttribute( repoPointer: _repoPointer, flags: flags.fold(0, (acc, e) => acc | e.value), path: path, name: name, ); } /// Returns the blame for a single file. /// /// [path] is the path to file to consider. /// /// [flags] is a combination of [GitBlameFlag]s. Defaults to /// [GitBlameFlag.normal]. /// /// [minMatchCharacters] is the lower bound on the number of alphanumeric /// characters that must be detected as moving/copying within a file for /// it to associate those lines with the parent commit. The default value is /// 20. This value only takes effect if any of the [GitBlameFlag.trackCopies*] /// flags are specified. /// /// [newestCommit] is the id of the newest commit to consider. The default is /// HEAD. /// /// [oldestCommit] is the id of the oldest commit to consider. The default is /// the first commit encountered with no parent. /// /// [minLine] is the first line in the file to blame. The default is 1 /// (line numbers start with 1). /// /// [maxLine] is the last line in the file to blame. The default is the last /// line of the file. /// /// **IMPORTANT**: Should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. Blame blame({ required String path, Set flags = const {GitBlameFlag.normal}, int? minMatchCharacters, Oid? newestCommit, Oid? oldestCommit, int? minLine, int? maxLine, }) { return Blame.file( repo: this, path: path, flags: flags, minMatchCharacters: minMatchCharacters, newestCommit: newestCommit, oldestCommit: oldestCommit, minLine: minLine, maxLine: maxLine, ); } /// List of notes for repository. /// /// **IMPORTANT**: Notes Should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. List get notes => Note.list(this); /// Lookups the note for an [annotatedOid]. /// /// [annotatedOid] is the [Oid] of the git object to read the note from. /// /// [notesRef] is the canonical name of the reference to use. Defaults to "refs/notes/commits". /// /// **IMPORTANT**: Should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. Note lookupNote({ required Oid annotatedOid, String notesRef = 'refs/notes/commits', }) { return Note.lookup( repo: this, annotatedOid: annotatedOid, notesRef: notesRef, ); } /// Creates a note for an [annotatedOid]. /// /// [author] is the signature of the note's commit author. /// /// [committer] is the signature of the note's commit committer. /// /// [annotatedOid] is the [Oid] of the git object to decorate. /// /// [note] is the content of the note to add. /// /// [notesRef] is the canonical name of the reference to use. Defaults to "refs/notes/commits". /// /// [force] determines whether existing note should be overwritten. /// /// Throws a [LibGit2Error] if error occured. Oid createNote({ required Signature author, required Signature committer, required Oid annotatedOid, required String note, String notesRef = 'refs/notes/commits', bool force = false, }) { return Note.create( repo: this, author: author, committer: committer, annotatedOid: annotatedOid, note: note, notesRef: notesRef, force: force, ); } /// Deletes the note for an [annotatedOid]. /// /// [annotatedOid] is the [Oid] of the git object to remove the note from. /// /// [author] is the signature of the note's commit author. /// /// [committer] is the signature of the note's commit committer. /// /// [notesRef] is the canonical name of the reference to use. Defaults to "refs/notes/commits". /// /// Throws a [LibGit2Error] if error occured. void deleteNote({ required Oid annotatedOid, required Signature author, required Signature committer, String notesRef = 'refs/notes/commits', }) { Note.delete( repo: this, annotatedOid: annotatedOid, author: author, committer: committer, notesRef: notesRef, ); } /// Checks if a provided [commit] is the descendant of another [ancestor] /// commit. /// /// Note that a commit is not considered a descendant of itself, in contrast /// to `git merge-base --is-ancestor`. /// /// Throws a [LibGit2Error] if error occured. bool descendantOf({required Oid commit, required Oid ancestor}) { return graph_bindings.descendantOf( repoPointer: _repoPointer, commitPointer: commit.pointer, ancestorPointer: ancestor.pointer, ); } /// Returns list with the `ahead` and `behind` number of unique commits /// respectively. /// /// There is no need for branches containing the commits to have any upstream /// relationship, but it helps to think of one as a branch and the other as /// its upstream, the ahead and behind values will be what git would report /// for the branches. /// /// [local] is the commit oid for local. /// /// [upstream] is the commit oid for upstream. List aheadBehind({ required Oid local, required Oid upstream, }) { return graph_bindings.aheadBehind( repoPointer: _repoPointer, localPointer: local.pointer, upstreamPointer: upstream.pointer, ); } /// Describes a [commit] if provided or the current worktree. /// /// [maxCandidatesTags] is the number of candidate tags to consider. /// Increasing above 10 will take slightly longer but may produce a more /// accurate result. A value of 0 will cause only exact matches to be output. /// Default is 10. /// /// [describeStrategy] is reference lookup strategy that is one of /// [GitDescribeStrategy]. Default matches only annotated tags. /// /// [pattern] is pattern to use for tags matching, excluding the "refs/tags/" prefix. /// /// [onlyFollowFirstParent] checks whether or not to follow only the first /// parent commit upon seeing a merge commit. /// /// [showCommitOidAsFallback] determines if full id of the commit should be /// shown if no matching tag or reference is found. /// /// [abbreviatedSize] is the minimum number of hexadecimal digits to show for /// abbreviated object names. A value of 0 will suppress long format, only /// showing the closest tag. Default is 7. /// /// [alwaysUseLongFormat] determines if he long format (the nearest tag, the /// number of commits, and the abbrevated commit name) should be used even /// when the commit matches the tag. /// /// [dirtySuffix] is a string to append if the working tree is dirty. /// /// Throws a [LibGit2Error] if error occured. String describe({ Commit? commit, int? maxCandidatesTags, GitDescribeStrategy? describeStrategy, String? pattern, bool? onlyFollowFirstParent, bool? showCommitOidAsFallback, int? abbreviatedSize, bool? alwaysUseLongFormat, String? dirtySuffix, }) { Pointer describeResult = nullptr; if (commit != null) { describeResult = describe_bindings.commit( commitPointer: commit.pointer, maxCandidatesTags: maxCandidatesTags, describeStrategy: describeStrategy?.value, pattern: pattern, onlyFollowFirstParent: onlyFollowFirstParent, showCommitOidAsFallback: showCommitOidAsFallback, ); } else { describeResult = describe_bindings.workdir( repo: _repoPointer, maxCandidatesTags: maxCandidatesTags, describeStrategy: describeStrategy?.value, pattern: pattern, onlyFollowFirstParent: onlyFollowFirstParent, showCommitOidAsFallback: showCommitOidAsFallback, ); } final result = describe_bindings.format( describeResultPointer: describeResult, abbreviatedSize: abbreviatedSize, alwaysUseLongFormat: alwaysUseLongFormat, dirtySuffix: dirtySuffix, ); describe_bindings.describeResultFree(describeResult); return result; } /// Packs the objects in the odb chosen by the [packDelegate] function and /// writes ".pack" and ".idx" files for them into provided [path] or default /// location. /// /// Returns the number of objects written to the pack. /// /// [packDelegate] is a function that will provide what objects should be /// added to the pack builder. Default is add all objects. /// /// [threads] is number of threads the PackBuilder will spawn. Default is /// none. 0 will let libgit2 to autodetect number of CPUs. /// /// Throws a [LibGit2Error] if error occured. int pack({ String? path, void Function(PackBuilder)? packDelegate, int? threads, }) { void packAll(PackBuilder packbuilder) { for (final object in odb.objects) { packbuilder.add(object); } } final _packDelegate = packDelegate ?? packAll; final packbuilder = PackBuilder(this); if (threads != null) { packbuilder.setThreads(threads); } _packDelegate(packbuilder); packbuilder.write(path); final result = packbuilder.writtenLength; packbuilder.free(); return result; } /// List with all tracked submodules paths of a repository. List get submodules => Submodule.list(this); /// Lookups submodule information by [name] or path (they are usually the /// same). /// /// **IMPORTANT**: Should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. Submodule lookupSubmodule(String name) { return Submodule.lookup(repo: this, name: name); } /// Copies submodule info into ".git/config" file. /// /// Just like `git submodule init`, this copies information about the /// submodule into ".git/config". /// /// By default, existing entries will not be overwritten, but setting /// [overwrite] to true forces them to be updated. /// /// Throws a [LibGit2Error] if error occured. void initSubmodule({ required String submodule, bool overwrite = false, }) { Submodule.init(repo: this, submodule: submodule, overwrite: overwrite); } /// Updates a submodule. This will clone a missing submodule and checkout the /// subrepository to the commit specified in the index of the containing /// repository. If the submodule repository doesn't contain the target commit /// (e.g. because fetchRecurseSubmodules isn't set), then the submodule is /// fetched using the fetch options supplied in [callbacks]. /// /// If the submodule is not initialized, setting [init] to true will /// initialize the submodule before updating. Otherwise, this will return an /// error if attempting to update an uninitialzed repository. /// /// Throws a [LibGit2Error] if error occured. void updateSubmodule({ required String submodule, bool init = false, Callbacks callbacks = const Callbacks(), }) { Submodule.update( repo: this, submodule: submodule, init: init, callbacks: callbacks, ); } /// Adds a submodule to the index. /// /// [url] is the URL for the submodule's remote. /// /// [path] is the path at which the submodule should be created. /// /// [useGitLink] determines if workdir should contain a gitlink to the repo in `.git/modules` /// vs. repo directly in workdir. Default is true. /// /// [callbacks] is the combination of callback functions from [Callbacks] /// object. /// /// **IMPORTANT**: Should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. Submodule addSubmodule({ required String url, required String path, bool useGitlink = true, Callbacks callbacks = const Callbacks(), }) { return Submodule.add( repo: this, url: url, path: path, useGitlink: useGitlink, callbacks: callbacks, ); } /// List of linked working trees names. /// /// Throws a [LibGit2Error] if error occured. List get worktrees => Worktree.list(this); /// Lookups existing worktree with provided [name]. /// /// **IMPORTANT**: Should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. Worktree lookupWorktree(String name) { return Worktree.lookup(repo: this, name: name); } /// Creates new worktree. /// /// If [ref] is provided, no new branch will be created but specified [ref] /// will be used instead. /// /// [name] is the name of the working tree. /// /// [path] is the path to create working tree at. /// /// **IMPORTANT**: Should be freed to release allocated memory. /// /// Throws a [LibGit2Error] if error occured. Worktree createWorktree({ required String name, required String path, Reference? ref, }) { return Worktree.create(repo: this, name: name, path: path, ref: ref); } }