import 'dart:ffi'; import 'package:ffi/ffi.dart'; import '../error.dart'; import '../util.dart'; import 'libgit2_bindings.dart'; /// Create a diff between the repository index and the workdir directory. Pointer indexToWorkdir({ required Pointer repoPointer, required Pointer indexPointer, required int flags, required int contextLines, required int interhunkLines, }) { final out = calloc>(); final opts = _diffOptionsInit( flags: flags, contextLines: contextLines, interhunkLines: interhunkLines, ); libgit2.git_diff_index_to_workdir(out, repoPointer, indexPointer, opts); calloc.free(opts); return out.value; } /// Create a diff between a tree and repository index. Pointer treeToIndex({ required Pointer repoPointer, required Pointer treePointer, required Pointer indexPointer, required int flags, required int contextLines, required int interhunkLines, }) { final out = calloc>(); final opts = _diffOptionsInit( flags: flags, contextLines: contextLines, interhunkLines: interhunkLines, ); libgit2.git_diff_tree_to_index( out, repoPointer, treePointer, indexPointer, opts, ); calloc.free(opts); return out.value; } /// Create a diff between a tree and the working directory. /// /// Throws a [LibGit2Error] if error occured. Pointer treeToWorkdir({ required Pointer repoPointer, required Pointer treePointer, required int flags, required int contextLines, required int interhunkLines, }) { final out = calloc>(); final opts = _diffOptionsInit( flags: flags, contextLines: contextLines, interhunkLines: interhunkLines, ); final error = libgit2.git_diff_tree_to_workdir( out, repoPointer, treePointer, opts, ); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } calloc.free(opts); return out.value; } /// Create a diff with the difference between two tree objects. /// /// Throws a [LibGit2Error] if error occured. Pointer treeToTree({ required Pointer repoPointer, required Pointer oldTreePointer, required Pointer newTreePointer, required int flags, required int contextLines, required int interhunkLines, }) { final out = calloc>(); final opts = _diffOptionsInit( flags: flags, contextLines: contextLines, interhunkLines: interhunkLines, ); final error = libgit2.git_diff_tree_to_tree( out, repoPointer, oldTreePointer, newTreePointer, opts, ); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } calloc.free(opts); return out.value; } /// Query how many diff records are there in a diff. int length(Pointer diff) => libgit2.git_diff_num_deltas(diff); /// Merge one diff into another. /// /// This merges items from the "from" list into the "onto" list. The resulting /// diff will have all items that appear in either list. If an item appears in /// both lists, then it will be "merged" to appear as if the old version was /// from the "onto" list and the new version is from the "from" list (with the /// exception that if the item has a pending DELETE in the middle, then it will /// show as deleted). void merge({ required Pointer ontoPointer, required Pointer fromPointer, }) { libgit2.git_diff_merge(ontoPointer, fromPointer); } /// Read the contents of a git patch file into a git diff object. /// /// The diff object produced is similar to the one that would be produced if /// you actually produced it computationally by comparing two trees, however /// there may be subtle differences. For example, a patch file likely contains /// abbreviated object IDs, so the object IDs in a diff delta produced by this /// function will also be abbreviated. /// /// This function will only read patch files created by a git implementation, /// it will not read unified diffs produced by the `diff` program, nor any /// other types of patch files. Pointer parse(String content) { final out = calloc>(); final contentC = content.toNativeUtf8().cast(); libgit2.git_diff_from_buffer(out, contentC, content.length); calloc.free(contentC); return out.value; } /// Transform a diff marking file renames, copies, etc. /// /// This modifies a diff in place, replacing old entries that look like renames /// or copies with new entries reflecting those changes. This also will, if /// requested, break modified files into add/remove pairs if the amount of /// change is above a threshold. /// /// Throws a [LibGit2Error] if error occured. void findSimilar({ required Pointer diffPointer, required int flags, required int renameThreshold, required int copyThreshold, required int renameFromRewriteThreshold, required int breakRewriteThreshold, required int renameLimit, }) { final opts = calloc(); libgit2.git_diff_find_options_init(opts, GIT_DIFF_FIND_OPTIONS_VERSION); opts.ref.flags = flags; opts.ref.rename_threshold = renameThreshold; opts.ref.copy_threshold = copyThreshold; opts.ref.rename_from_rewrite_threshold = renameFromRewriteThreshold; opts.ref.break_rewrite_threshold = breakRewriteThreshold; opts.ref.rename_limit = renameLimit; final error = libgit2.git_diff_find_similar(diffPointer, opts); calloc.free(opts); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } } /// Calculate the patch ID for the given patch. /// /// Calculate a stable patch ID for the given patch by summing the hash of the /// file diffs, ignoring whitespace and line numbers. This can be used to /// derive whether two diffs are the same with a high probability. /// /// Currently, this function only calculates stable patch IDs, as defined in /// `git-patch-id(1)`, and should in fact generate the same IDs as the upstream /// git project does. /// /// Throws a [LibGit2Error] if error occured. Pointer patchOid(Pointer diff) { final out = calloc(); final error = libgit2.git_diff_patchid(out, diff, nullptr); if (error < 0) { calloc.free(out); throw LibGit2Error(libgit2.git_error_last()); } else { return out; } } /// Return the diff delta for an entry in the diff list. Pointer getDeltaByIndex({ required Pointer diffPointer, required int index, }) { return libgit2.git_diff_get_delta(diffPointer, index); } /// Look up the single character abbreviation for a delta status code. /// /// When you run `git diff --name-status` it uses single letter codes in the /// output such as 'A' for added, 'D' for deleted, 'M' for modified, etc. This /// function converts a [GitDelta] value into these letters for your own /// purposes. [GitDelta.untracked] will return a space (i.e. ' '). String statusChar(int status) { return String.fromCharCode(libgit2.git_diff_status_char(status)); } /// Accumulate diff statistics for all patches. /// /// Throws a [LibGit2Error] if error occured. Pointer stats(Pointer diff) { final out = calloc>(); final error = libgit2.git_diff_get_stats(out, diff); if (error < 0) { calloc.free(out); throw LibGit2Error(libgit2.git_error_last()); } else { return out.value; } } /// Get the total number of insertions in a diff. int statsInsertions(Pointer stats) => libgit2.git_diff_stats_insertions(stats); /// Get the total number of deletions in a diff. int statsDeletions(Pointer stats) => libgit2.git_diff_stats_deletions(stats); /// Get the total number of files changed in a diff. int statsFilesChanged(Pointer stats) => libgit2.git_diff_stats_files_changed(stats); /// Print diff statistics. /// /// Throws a [LibGit2Error] if error occured. String statsPrint({ required Pointer statsPointer, required int format, required int width, }) { final out = calloc(sizeOf()); final error = libgit2.git_diff_stats_to_buf(out, statsPointer, format, width); if (error < 0) { calloc.free(out); throw LibGit2Error(libgit2.git_error_last()); } else { final result = out.ref.ptr.cast().toDartString(); calloc.free(out); return result; } } /// Add patch to buffer. Pointer addToBuf({ required Pointer patchPointer, required Pointer bufferPointer, }) { libgit2.git_patch_to_buf(bufferPointer, patchPointer); return bufferPointer; } /// Apply a diff to the given repository, making changes directly in the /// working directory, the index, or both. /// /// Throws a [LibGit2Error] if error occured. bool apply({ required Pointer repoPointer, required Pointer diffPointer, required int location, bool check = false, }) { final opts = calloc(); libgit2.git_apply_options_init(opts, GIT_APPLY_OPTIONS_VERSION); if (check) { opts.ref.flags |= git_apply_flags_t.GIT_APPLY_CHECK; } final error = libgit2.git_apply(repoPointer, diffPointer, location, opts); calloc.free(opts); if (error < 0) { return check ? false : throw LibGit2Error(libgit2.git_error_last()); } else { return true; } } /// Free a previously allocated diff stats. void statsFree(Pointer stats) => libgit2.git_diff_stats_free(stats); /// Free a previously allocated diff. void free(Pointer diff) => libgit2.git_diff_free(diff); Pointer _diffOptionsInit({ required int flags, required int contextLines, required int interhunkLines, }) { final opts = calloc(); libgit2.git_diff_options_init(opts, GIT_DIFF_OPTIONS_VERSION); opts.ref.flags = flags; opts.ref.context_lines = contextLines; opts.ref.interhunk_lines = interhunkLines; return opts; }