import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'package:libgit2dart/src/bindings/libgit2_bindings.dart'; import 'package:libgit2dart/src/error.dart'; import 'package:libgit2dart/src/util.dart'; /// Create an in-memory index object. /// /// This index object cannot be read/written to the filesystem, but may be /// used to perform in-memory index operations. /// /// The returned index must be freed with [free]. Pointer newInMemory() { final out = calloc>(); libgit2.git_index_new(out); final result = out.value; calloc.free(out); return result; } /// Read index capabilities flags. int capabilities(Pointer index) => libgit2.git_index_caps(index); /// Set index capabilities flags. /// /// If you pass [GitIndexCapability.fromOwner] for the caps, then capabilities /// will be read from the config of the owner object, looking at /// core.ignorecase, core.filemode, core.symlinks. /// /// Throws a [LibGit2Error] if error occured. void setCapabilities({ required Pointer indexPointer, required int caps, }) { final error = libgit2.git_index_set_caps(indexPointer, caps); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Get the full path to the index file on disk. String path(Pointer index) { return libgit2.git_index_path(index).cast().toDartString(); } /// Update the contents of an existing index object in memory by reading from /// the hard disk. /// /// If [force] is true, this performs a "hard" read that discards in-memory /// changes and always reloads the on-disk index data. If there is no on-disk /// version, the index will be cleared. /// /// If [force] is false, this does a "soft" read that reloads the index data /// from disk only if it has changed since the last time it was loaded. Purely /// in-memory index data will be untouched. Be aware: if there are changes on /// disk, unwritten in-memory changes are discarded. void read({required Pointer indexPointer, required bool force}) { final forceC = force == true ? 1 : 0; libgit2.git_index_read(indexPointer, forceC); } /// Read a tree into the index file with stats. /// /// The current index contents will be replaced by the specified tree. void readTree({ required Pointer indexPointer, required Pointer treePointer, }) { libgit2.git_index_read_tree(indexPointer, treePointer); } /// Write the index as a tree. /// /// This method will scan the index and write a representation of its current /// state back to disk; it recursively creates tree objects for each of the /// subtrees stored in the index, but only returns the OID of the root tree. /// This is the OID that can be used e.g. to create a commit. /// /// The index instance cannot be bare, and needs to be associated to an /// existing repository. /// /// The index must not contain any file in conflict. /// /// Throws a [LibGit2Error] if error occured. Pointer writeTree(Pointer index) { final out = calloc(); final error = libgit2.git_index_write_tree(out, index); if (error < 0) { calloc.free(out); throw LibGit2Error(libgit2.git_error_last()); } else { return out; } } /// Write the index as a tree to the given repository. /// /// This method will do the same as [writeTree], but letting the user choose /// the repository where the tree will be written. /// /// The index must not contain any file in conflict. /// /// Throws a [LibGit2Error] if error occured. Pointer writeTreeTo({ required Pointer indexPointer, required Pointer repoPointer, }) { final out = calloc(); final error = libgit2.git_index_write_tree_to(out, indexPointer, repoPointer); if (error < 0) { calloc.free(out); throw LibGit2Error(libgit2.git_error_last()); } else { return out; } } /// Find the first position of any entries which point to given path in the Git /// index. bool find({required Pointer indexPointer, required String path}) { final pathC = path.toNativeUtf8().cast(); final result = libgit2.git_index_find(nullptr, indexPointer, pathC); calloc.free(pathC); return result != git_error_code.GIT_ENOTFOUND || false; } /// Get the count of entries currently in the index. int entryCount(Pointer index) => libgit2.git_index_entrycount(index); /// Get a pointer to one of the entries in the index based on position. /// /// The entry is not modifiable and should not be freed. /// /// Throws [RangeError] when provided index is outside of valid range. Pointer getByIndex({ required Pointer indexPointer, required int position, }) { final result = libgit2.git_index_get_byindex(indexPointer, position); if (result == nullptr) { throw RangeError('Out of bounds'); } else { return result; } } /// Get a pointer to one of the entries in the index based on path. /// /// The entry is not modifiable and should not be freed. /// /// Throws [ArgumentError] if nothing found for provided path. Pointer getByPath({ required Pointer indexPointer, required String path, required int stage, }) { final pathC = path.toNativeUtf8().cast(); final result = libgit2.git_index_get_bypath(indexPointer, pathC, stage); calloc.free(pathC); if (result == nullptr) { throw ArgumentError.value('$path was not found'); } else { return result; } } /// Return the stage number from a git index entry. int entryStage(Pointer entry) => libgit2.git_index_entry_stage(entry); /// Clear the contents (all the entries) of an index object. /// /// This clears the index object in memory; changes must be explicitly written /// to disk for them to take effect persistently. /// /// Throws a [LibGit2Error] if error occured. void clear(Pointer index) { final error = libgit2.git_index_clear(index); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Add or update an index entry from an in-memory struct. /// /// If a previous index entry exists that has the same path and stage as the /// given `sourceEntry`, it will be replaced. Otherwise, the `sourceEntry` will /// be added. /// /// Throws a [LibGit2Error] if error occured. void add({ required Pointer indexPointer, required Pointer sourceEntryPointer, }) { final error = libgit2.git_index_add(indexPointer, sourceEntryPointer); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Add or update an index entry from a file on disk. /// /// The file [path] must be relative to the repository's working folder and must /// be readable. /// /// This method will fail in bare index instances. /// /// This forces the file to be added to the index, not looking at gitignore /// rules. /// /// If this file currently is the result of a merge conflict, this file will no /// longer be marked as conflicting. The data about the conflict will be moved /// to the "resolve undo" (REUC) section. /// /// Throws a [LibGit2Error] if error occured. void addByPath({ required Pointer indexPointer, required String path, }) { final pathC = path.toNativeUtf8().cast(); final error = libgit2.git_index_add_bypath(indexPointer, pathC); calloc.free(pathC); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Add or update an index entry from a buffer in memory. /// /// This method will create a blob in the repository that owns the index and /// then add the index entry to the index. The path of the entry represents the /// position of the blob relative to the repository's root folder. /// /// If a previous index entry exists that has the same path as the given /// 'entry', it will be replaced. Otherwise, the 'entry' will be added. /// /// This forces the file to be added to the index, not looking at gitignore /// rules. /// /// If this file currently is the result of a merge conflict, this file will no /// longer be marked as conflicting. The data about the conflict will be moved /// to the "resolve undo" (REUC) section. /// /// Throws a [LibGit2Error] if error occured. void addFromBuffer({ required Pointer indexPointer, required Pointer entryPointer, required String buffer, }) { final bufferC = buffer.toNativeUtf8().cast(); final error = libgit2.git_index_add_from_buffer( indexPointer, entryPointer, bufferC.cast(), buffer.length, ); calloc.free(bufferC); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Add or update index entries matching files in the working directory. /// /// This method will fail in bare index instances. /// /// The [pathspec] is a list of file names or shell glob patterns that will be /// matched against files in the repository's working directory. Each file that /// matches will be added to the index (either updating an existing entry or /// adding a new entry). /// /// Throws a [LibGit2Error] if error occured. void addAll({ required Pointer indexPointer, required List pathspec, }) { final pathspecC = calloc(); final pathPointers = pathspec.map((e) => e.toNativeUtf8().cast()).toList(); final strArray = calloc>(pathspec.length); for (var i = 0; i < pathspec.length; i++) { strArray[i] = pathPointers[i]; } pathspecC.ref.strings = strArray; pathspecC.ref.count = pathspec.length; final error = libgit2.git_index_add_all( indexPointer, pathspecC, 0, nullptr, nullptr, ); calloc.free(pathspecC); for (final p in pathPointers) { calloc.free(p); } calloc.free(strArray); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Update all index entries to match the working directory. /// /// This method will fail in bare index instances. /// /// This scans the existing index entries and synchronizes them with the /// working directory, deleting them if the corresponding working directory /// file no longer exists otherwise updating the information (including adding /// the latest version of file to the ODB if needed). /// /// Throws a [LibGit2Error] if error occured. void updateAll({ required Pointer indexPointer, required List pathspec, }) { final pathspecC = calloc(); final pathPointers = pathspec.map((e) => e.toNativeUtf8().cast()).toList(); final strArray = calloc>(pathspec.length); for (var i = 0; i < pathspec.length; i++) { strArray[i] = pathPointers[i]; } pathspecC.ref.strings = strArray; pathspecC.ref.count = pathspec.length; final error = libgit2.git_index_update_all( indexPointer, pathspecC, nullptr, nullptr, ); calloc.free(pathspecC); for (final p in pathPointers) { calloc.free(p); } calloc.free(strArray); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Write an existing index object from memory back to disk using an atomic /// file lock. void write(Pointer index) => libgit2.git_index_write(index); /// Remove an entry from the index. /// /// Throws a [LibGit2Error] if error occured. void remove({ required Pointer indexPointer, required String path, required int stage, }) { final pathC = path.toNativeUtf8().cast(); final error = libgit2.git_index_remove(indexPointer, pathC, stage); calloc.free(pathC); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Remove all entries from the index under a given directory. void removeDirectory({ required Pointer indexPointer, required String dir, required int stage, }) { final dirC = dir.toNativeUtf8().cast(); libgit2.git_index_remove_directory(indexPointer, dirC, stage); calloc.free(dirC); } /// Remove all matching index entries. void removeAll({ required Pointer indexPointer, required List pathspec, }) { final pathspecC = calloc(); final pathPointers = pathspec.map((e) => e.toNativeUtf8().cast()).toList(); final strArray = calloc>(pathspec.length); for (var i = 0; i < pathspec.length; i++) { strArray[i] = pathPointers[i]; } pathspecC.ref.strings = strArray; pathspecC.ref.count = pathspec.length; libgit2.git_index_remove_all(indexPointer, pathspecC, nullptr, nullptr); calloc.free(pathspecC); for (final p in pathPointers) { calloc.free(p); } calloc.free(strArray); } /// Determine if the index contains entries representing file conflicts. bool hasConflicts(Pointer index) { return libgit2.git_index_has_conflicts(index) == 1 || false; } /// Return list of conflicts in the index. /// /// Throws a [LibGit2Error] if error occured. List>> conflictList( Pointer index, ) { final iterator = calloc>(); libgit2.git_index_conflict_iterator_new(iterator, index); final result = >>[]; var error = 0; while (error >= 0) { final ancestorOut = calloc>(); final ourOut = calloc>(); final theirOut = calloc>(); error = libgit2.git_index_conflict_next( ancestorOut, ourOut, theirOut, iterator.value, ); if (error >= 0) { result.add({ 'ancestor': ancestorOut.value, 'our': ourOut.value, 'their': theirOut.value, }); } else { break; } calloc.free(ancestorOut); calloc.free(ourOut); calloc.free(theirOut); } libgit2.git_index_conflict_iterator_free(iterator.value); return result; } /// Return whether the given index entry is a conflict (has a high stage entry). /// This is simply shorthand for [entryStage] > 0. bool entryIsConflict(Pointer entry) { return libgit2.git_index_entry_is_conflict(entry) == 1 || false; } /// Add or update index entries to represent a conflict. Any staged entries /// that exist at the given paths will be removed. /// /// The entries are the entries from the tree included in the merge. Any entry /// may be null to indicate that that file was not present in the trees during /// the merge. For example, [ancestorEntryPointer] may be null to indicate that /// a file was added in both branches and must be resolved. /// /// Throws a [LibGit2Error] if error occured. void conflictAdd({ required Pointer indexPointer, Pointer? ancestorEntryPointer, Pointer? ourEntryPointer, Pointer? theirEntryPointer, }) { final error = libgit2.git_index_conflict_add( indexPointer, ancestorEntryPointer ?? nullptr, ourEntryPointer ?? nullptr, theirEntryPointer ?? nullptr, ); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Removes the index entries that represent a conflict of a single file. /// /// Throws a [LibGit2Error] if error occured. void conflictRemove({ required Pointer indexPointer, required String path, }) { final pathC = path.toNativeUtf8().cast(); final error = libgit2.git_index_conflict_remove(indexPointer, pathC); calloc.free(pathC); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Remove all conflicts in the index (entries with a stage greater than 0). /// /// Throws a [LibGit2Error] if error occured. void conflictCleanup(Pointer index) { final error = libgit2.git_index_conflict_cleanup(index); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Free an existing index object. void free(Pointer index) => libgit2.git_index_free(index);