import 'dart:ffi'; import 'package:ffi/ffi.dart'; import '../error.dart'; import 'libgit2_bindings.dart'; import '../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 call `free()` on the patch when done. /// /// Throws a [LibGit2Error] if error occured. Map 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, ); final error = libgit2.git_patch_from_buffers( out, oldBufferC.cast(), oldLen, oldAsPathC, newBufferC.cast(), newLen, newAsPathC, opts, ); final result = {}; calloc.free(oldAsPathC); calloc.free(newAsPathC); calloc.free(opts); if (error < 0) { calloc.free(oldBufferC); calloc.free(newBufferC); throw LibGit2Error(libgit2.git_error_last()); } else { // Returning map with pointers to patch and buffers because patch object does not // have refenrece to underlying buffers or blobs. So if the buffer or blob is freed/removed // the patch text becomes corrupted. result['patch'] = out.value; result['a'] = oldBufferC; result['b'] = newBufferC; return result; } } /// 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 call `free()` on the patch when done. /// /// Throws a [LibGit2Error] if error occured. Map fromBlobs({ Pointer? oldBlobPointer, String? oldAsPath, 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, ); final error = libgit2.git_patch_from_blobs( out, oldBlobPointer ?? nullptr, oldAsPathC, newBlobPointer ?? nullptr, newAsPathC, opts, ); final result = {}; calloc.free(oldAsPathC); calloc.free(newAsPathC); calloc.free(opts); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } else { // Returning map with pointers to patch and blobs because patch object does not // have reference to underlying blobs. So if the blob is freed/removed the patch // text becomes corrupted. result['patch'] = out.value; result['a'] = oldBlobPointer; result['b'] = newBlobPointer; return result; } } /// 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 /// call `free()` on the patch when done. /// /// Throws a [LibGit2Error] if error occured. Map 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, ); final error = libgit2.git_patch_from_blob_and_buffer( out, oldBlobPointer ?? nullptr, oldAsPathC, bufferC.cast(), bufferLen, bufferAsPathC, opts, ); final result = {}; calloc.free(oldAsPathC); calloc.free(bufferAsPathC); calloc.free(opts); if (error < 0) { calloc.free(bufferC); throw LibGit2Error(libgit2.git_error_last()); } else { // Returning map with pointers to patch and buffers because patch object does not // have reference to underlying buffers or blobs. So if the buffer or blob is freed/removed // the patch text becomes corrupted. result['patch'] = out.value; result['a'] = oldBlobPointer; result['b'] = bufferC; return result; } } /// 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) { 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. /// /// Throws a [LibGit2Error] if error occured. Map hunk({ required Pointer patchPointer, required int hunkIndex, }) { final out = calloc>(); final linesInHunk = calloc(); final error = libgit2.git_patch_get_hunk( out, linesInHunk.cast(), patchPointer, hunkIndex, ); final result = {}; if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } else { result['hunk'] = out.value; result['linesN'] = linesInHunk.value; return result; } } /// Get data about a line in a hunk of a patch. /// /// Throws a [LibGit2Error] if error occured. Pointer lines({ required Pointer patchPointer, required int hunkIndex, required int lineOfHunk, }) { final out = calloc>(); final error = libgit2.git_patch_get_line_in_hunk( out, patchPointer, hunkIndex, lineOfHunk, ); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); } else { 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) { 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(); final optsError = libgit2.git_diff_options_init(opts, GIT_DIFF_OPTIONS_VERSION); opts.ref.flags = flags; opts.ref.context_lines = contextLines; opts.ref.interhunk_lines = interhunkLines; if (optsError < 0) { throw LibGit2Error(libgit2.git_error_last()); } else { return opts; } }