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'; /// Directly generate a patch from the difference between two buffers. /// /// You can use the standard patch accessor functions to read the patch data, /// and you must free the patch when done. Pointer fromBuffers({ String? oldBuffer, String? oldAsPath, String? newBuffer, String? newAsPath, required int flags, required int contextLines, required int interhunkLines, }) { final out = calloc>(); final oldBufferC = oldBuffer?.toNativeUtf8().cast() ?? nullptr; final oldAsPathC = oldAsPath?.toNativeUtf8().cast() ?? nullptr; final oldLen = oldBuffer?.length ?? 0; final newBufferC = newBuffer?.toNativeUtf8().cast() ?? nullptr; final newAsPathC = oldAsPath?.toNativeUtf8().cast() ?? nullptr; final newLen = newBuffer?.length ?? 0; final opts = _diffOptionsInit( flags: flags, contextLines: contextLines, interhunkLines: interhunkLines, ); libgit2.git_patch_from_buffers( out, oldBufferC.cast(), oldLen, oldAsPathC, newBufferC.cast(), newLen, newAsPathC, opts, ); calloc.free(oldAsPathC); calloc.free(newAsPathC); calloc.free(opts); // We are not freeing buffers because patch object does not have reference to // underlying buffers. So if the buffer is freed the patch text becomes // corrupted. return out.value; } /// Directly generate a patch from the difference between two blobs. /// /// You can use the standard patch accessor functions to read the patch data, /// and you must free the patch when done. Pointer fromBlobs({ required Pointer? oldBlobPointer, String? oldAsPath, required Pointer? newBlobPointer, String? newAsPath, required int flags, required int contextLines, required int interhunkLines, }) { final out = calloc>(); final oldAsPathC = oldAsPath?.toNativeUtf8().cast() ?? nullptr; final newAsPathC = oldAsPath?.toNativeUtf8().cast() ?? nullptr; final opts = _diffOptionsInit( flags: flags, contextLines: contextLines, interhunkLines: interhunkLines, ); libgit2.git_patch_from_blobs( out, oldBlobPointer ?? nullptr, oldAsPathC, newBlobPointer ?? nullptr, newAsPathC, opts, ); calloc.free(oldAsPathC); calloc.free(newAsPathC); calloc.free(opts); return out.value; } /// Directly generate a patch from the difference between a blob and a buffer. /// /// You can use the standard patch accessor functions to read the patch data, /// and you must free the patch when done. Pointer fromBlobAndBuffer({ Pointer? oldBlobPointer, String? oldAsPath, String? buffer, String? bufferAsPath, required int flags, required int contextLines, required int interhunkLines, }) { final out = calloc>(); final oldAsPathC = oldAsPath?.toNativeUtf8().cast() ?? nullptr; final bufferC = buffer?.toNativeUtf8().cast() ?? nullptr; final bufferAsPathC = oldAsPath?.toNativeUtf8().cast() ?? nullptr; final bufferLen = buffer?.length ?? 0; final opts = _diffOptionsInit( flags: flags, contextLines: contextLines, interhunkLines: interhunkLines, ); libgit2.git_patch_from_blob_and_buffer( out, oldBlobPointer ?? nullptr, oldAsPathC, bufferC.cast(), bufferLen, bufferAsPathC, opts, ); calloc.free(oldAsPathC); calloc.free(bufferAsPathC); calloc.free(opts); return out.value; } /// Return a patch for an entry in the diff list. /// /// The newly created patch object contains the text diffs for the delta. You /// have to call [free] when you are done with it. You can use the patch object /// to loop over all the hunks and lines in the diff of the one delta. /// /// Throws a [LibGit2Error] if error occured. Pointer fromDiff({ required Pointer diffPointer, required int index, }) { final out = calloc>(); final error = libgit2.git_patch_from_diff(out, diffPointer, index); if (error < 0) { calloc.free(out); throw LibGit2Error(libgit2.git_error_last()); } else { return out.value; } } /// Get the delta associated with a patch. Pointer delta(Pointer patch) => libgit2.git_patch_get_delta(patch); /// Get the number of hunks in a patch. int numHunks(Pointer patch) => libgit2.git_patch_num_hunks(patch); /// Get the information about a hunk in a patch. /// /// Given a patch and a hunk index into the patch, this returns detailed /// information about that hunk. Map hunk({ required Pointer patchPointer, required int hunkIndex, }) { final out = calloc>(); final linesInHunk = calloc(); libgit2.git_patch_get_hunk(out, linesInHunk.cast(), patchPointer, hunkIndex); final linesN = linesInHunk.value; calloc.free(linesInHunk); return {'hunk': out.value, 'linesN': linesN}; } /// Get line counts of each type in a patch. Map lineStats(Pointer patch) { final context = calloc(); final insertions = calloc(); final deletions = calloc(); libgit2.git_patch_line_stats( context, insertions, deletions, patch, ); final result = { 'context': context.value, 'insertions': insertions.value, 'deletions': deletions.value, }; calloc.free(context); calloc.free(insertions); calloc.free(deletions); return result; } /// Get data about a line in a hunk of a patch. Pointer lines({ required Pointer patchPointer, required int hunkIndex, required int lineOfHunk, }) { final out = calloc>(); libgit2.git_patch_get_line_in_hunk(out, patchPointer, hunkIndex, lineOfHunk); return out.value; } /// Get the content of a patch as a single diff text. /// /// Throws a [LibGit2Error] if error occured. String text(Pointer patch) { final out = calloc(sizeOf()); final error = libgit2.git_patch_to_buf(out, patch); 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; } } /// Look up size of patch diff data in bytes. /// /// This returns the raw size of the patch data. This only includes the actual /// data from the lines of the diff, not the file or hunk headers. /// /// If you pass `includeContext` as true, this will be the size of all of the /// diff output; if you pass it as false, this will only include the actual /// changed lines (as if contextLines was 0). int size({ required Pointer patchPointer, required bool includeContext, required bool includeHunkHeaders, required bool includeFileHeaders, }) { final includeContextC = includeContext ? 1 : 0; final includeHunkHeadersC = includeHunkHeaders ? 1 : 0; final includeFileHeadersC = includeFileHeaders ? 1 : 0; return libgit2.git_patch_size( patchPointer, includeContextC, includeHunkHeadersC, includeFileHeadersC, ); } /// Free a previously allocated patch object. void free(Pointer patch) => libgit2.git_patch_free(patch); 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; }