mirror of
https://github.com/SkinnyMind/libgit2dart.git
synced 2025-05-05 04:39:07 -04:00
feat(patch): add bindings and api
This commit is contained in:
parent
f7f4a395c0
commit
344dba60e9
11 changed files with 1087 additions and 47 deletions
|
@ -15,16 +15,7 @@ Pointer<git_diff> indexToWorkdir(
|
|||
int interhunkLines,
|
||||
) {
|
||||
final out = calloc<Pointer<git_diff>>();
|
||||
final opts = calloc<git_diff_options>();
|
||||
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());
|
||||
}
|
||||
final opts = _diffOptionsInit(flags, contextLines, interhunkLines);
|
||||
|
||||
libgit2.git_diff_index_to_workdir(out, repo, index, opts);
|
||||
|
||||
|
@ -45,16 +36,7 @@ Pointer<git_diff> treeToIndex(
|
|||
int interhunkLines,
|
||||
) {
|
||||
final out = calloc<Pointer<git_diff>>();
|
||||
final opts = calloc<git_diff_options>();
|
||||
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());
|
||||
}
|
||||
final opts = _diffOptionsInit(flags, contextLines, interhunkLines);
|
||||
|
||||
libgit2.git_diff_tree_to_index(out, repo, oldTree, index, opts);
|
||||
|
||||
|
@ -74,16 +56,7 @@ Pointer<git_diff> treeToWorkdir(
|
|||
int interhunkLines,
|
||||
) {
|
||||
final out = calloc<Pointer<git_diff>>();
|
||||
final opts = calloc<git_diff_options>();
|
||||
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());
|
||||
}
|
||||
final opts = _diffOptionsInit(flags, contextLines, interhunkLines);
|
||||
|
||||
libgit2.git_diff_tree_to_workdir(out, repo, oldTree, opts);
|
||||
|
||||
|
@ -104,16 +77,7 @@ Pointer<git_diff> treeToTree(
|
|||
int interhunkLines,
|
||||
) {
|
||||
final out = calloc<Pointer<git_diff>>();
|
||||
final opts = calloc<git_diff_options>();
|
||||
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());
|
||||
}
|
||||
final opts = _diffOptionsInit(flags, contextLines, interhunkLines);
|
||||
|
||||
libgit2.git_diff_tree_to_tree(out, repo, oldTree, newTree, opts);
|
||||
|
||||
|
@ -290,9 +254,41 @@ String statsPrint(
|
|||
}
|
||||
}
|
||||
|
||||
/// Add patch to buffer.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Pointer<git_buf> addToBuf(Pointer<git_patch> patch, Pointer<git_buf> buffer) {
|
||||
final error = libgit2.git_patch_to_buf(buffer, patch);
|
||||
|
||||
if (error < 0) {
|
||||
throw LibGit2Error(libgit2.git_error_last());
|
||||
} else {
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
/// Free a previously allocated diff stats.
|
||||
void statsFree(Pointer<git_diff_stats> stats) =>
|
||||
libgit2.git_diff_stats_free(stats);
|
||||
|
||||
/// Free a previously allocated diff.
|
||||
void free(Pointer<git_diff> diff) => libgit2.git_diff_free(diff);
|
||||
|
||||
Pointer<git_diff_options> _diffOptionsInit(
|
||||
int flags,
|
||||
int contextLines,
|
||||
int interhunkLines,
|
||||
) {
|
||||
final opts = calloc<git_diff_options>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
289
lib/src/bindings/patch.dart
Normal file
289
lib/src/bindings/patch.dart
Normal file
|
@ -0,0 +1,289 @@
|
|||
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<String, dynamic> fromBuffers(
|
||||
String? oldBuffer,
|
||||
String? oldAsPath,
|
||||
String? newBuffer,
|
||||
String? newAsPath,
|
||||
int flags,
|
||||
int contextLines,
|
||||
int interhunkLines,
|
||||
) {
|
||||
final out = calloc<Pointer<git_patch>>();
|
||||
final oldBufferC = oldBuffer?.toNativeUtf8().cast<Int8>() ?? nullptr;
|
||||
final oldAsPathC = oldAsPath?.toNativeUtf8().cast<Int8>() ?? nullptr;
|
||||
final oldLen = oldBuffer?.length ?? 0;
|
||||
final newBufferC = newBuffer?.toNativeUtf8().cast<Int8>() ?? nullptr;
|
||||
final newAsPathC = oldAsPath?.toNativeUtf8().cast<Int8>() ?? nullptr;
|
||||
final newLen = newBuffer?.length ?? 0;
|
||||
final opts = _diffOptionsInit(flags, contextLines, interhunkLines);
|
||||
|
||||
final error = libgit2.git_patch_from_buffers(
|
||||
out,
|
||||
oldBufferC.cast(),
|
||||
oldLen,
|
||||
oldAsPathC,
|
||||
newBufferC.cast(),
|
||||
newLen,
|
||||
newAsPathC,
|
||||
opts,
|
||||
);
|
||||
|
||||
final result = <String, dynamic>{};
|
||||
|
||||
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<String, dynamic> fromBlobs(
|
||||
Pointer<git_blob>? oldBlob,
|
||||
String? oldAsPath,
|
||||
Pointer<git_blob>? newBlob,
|
||||
String? newAsPath,
|
||||
int flags,
|
||||
int contextLines,
|
||||
int interhunkLines,
|
||||
) {
|
||||
final out = calloc<Pointer<git_patch>>();
|
||||
final oldAsPathC = oldAsPath?.toNativeUtf8().cast<Int8>() ?? nullptr;
|
||||
final newAsPathC = oldAsPath?.toNativeUtf8().cast<Int8>() ?? nullptr;
|
||||
final opts = _diffOptionsInit(flags, contextLines, interhunkLines);
|
||||
|
||||
final error = libgit2.git_patch_from_blobs(
|
||||
out,
|
||||
oldBlob ?? nullptr,
|
||||
oldAsPathC,
|
||||
newBlob ?? nullptr,
|
||||
newAsPathC,
|
||||
opts,
|
||||
);
|
||||
|
||||
final result = <String, dynamic>{};
|
||||
|
||||
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 refenrece to underlying blobs. So if the blob is freed/removed the patch
|
||||
// text becomes corrupted.
|
||||
result['patch'] = out.value;
|
||||
result['a'] = oldBlob;
|
||||
result['b'] = newBlob;
|
||||
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<String, dynamic> fromBlobAndBuffer(
|
||||
Pointer<git_blob>? oldBlob,
|
||||
String? oldAsPath,
|
||||
String? buffer,
|
||||
String? bufferAsPath,
|
||||
int flags,
|
||||
int contextLines,
|
||||
int interhunkLines,
|
||||
) {
|
||||
final out = calloc<Pointer<git_patch>>();
|
||||
final oldAsPathC = oldAsPath?.toNativeUtf8().cast<Int8>() ?? nullptr;
|
||||
final bufferC = buffer?.toNativeUtf8().cast<Int8>() ?? nullptr;
|
||||
final bufferAsPathC = oldAsPath?.toNativeUtf8().cast<Int8>() ?? nullptr;
|
||||
final bufferLen = buffer?.length ?? 0;
|
||||
final opts = _diffOptionsInit(flags, contextLines, interhunkLines);
|
||||
|
||||
final error = libgit2.git_patch_from_blob_and_buffer(
|
||||
out,
|
||||
oldBlob ?? nullptr,
|
||||
oldAsPathC,
|
||||
bufferC.cast(),
|
||||
bufferLen,
|
||||
bufferAsPathC,
|
||||
opts,
|
||||
);
|
||||
|
||||
final result = <String, dynamic>{};
|
||||
|
||||
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 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'] = oldBlob;
|
||||
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<git_patch> fromDiff(Pointer<git_diff> diff, int idx) {
|
||||
final out = calloc<Pointer<git_patch>>();
|
||||
final error = libgit2.git_patch_from_diff(out, diff, idx);
|
||||
|
||||
if (error < 0) {
|
||||
throw LibGit2Error(libgit2.git_error_last());
|
||||
} else {
|
||||
return out.value;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the delta associated with a patch.
|
||||
Pointer<git_diff_delta> delta(Pointer<git_patch> patch) =>
|
||||
libgit2.git_patch_get_delta(patch);
|
||||
|
||||
/// Get the number of hunks in a patch.
|
||||
int numHunks(Pointer<git_patch> 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<String, dynamic> hunk(Pointer<git_patch> patch, int hunkIdx) {
|
||||
final out = calloc<Pointer<git_diff_hunk>>();
|
||||
final linesInHunk = calloc<Int32>();
|
||||
final error = libgit2.git_patch_get_hunk(out, linesInHunk, patch, hunkIdx);
|
||||
final result = <String, dynamic>{};
|
||||
|
||||
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<git_diff_line> lines(
|
||||
Pointer<git_patch> patch,
|
||||
int hunkIdx,
|
||||
int lineOfHunk,
|
||||
) {
|
||||
final out = calloc<Pointer<git_diff_line>>();
|
||||
final error =
|
||||
libgit2.git_patch_get_line_in_hunk(out, patch, hunkIdx, 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<git_patch> patch) {
|
||||
final out = calloc<git_buf>(sizeOf<git_buf>());
|
||||
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<Utf8>().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(
|
||||
Pointer<git_patch> patch,
|
||||
bool includeContext,
|
||||
bool includeHunkHeaders,
|
||||
bool includeFileHeaders,
|
||||
) {
|
||||
final includeContextC = includeContext ? 1 : 0;
|
||||
final includeHunkHeadersC = includeHunkHeaders ? 1 : 0;
|
||||
final includeFileHeadersC = includeFileHeaders ? 1 : 0;
|
||||
|
||||
return libgit2.git_patch_size(
|
||||
patch,
|
||||
includeContextC,
|
||||
includeHunkHeadersC,
|
||||
includeFileHeadersC,
|
||||
);
|
||||
}
|
||||
|
||||
/// Free a previously allocated patch object.
|
||||
void free(Pointer<git_patch> patch) => libgit2.git_patch_free(patch);
|
||||
|
||||
Pointer<git_diff_options> _diffOptionsInit(
|
||||
int flags,
|
||||
int contextLines,
|
||||
int interhunkLines,
|
||||
) {
|
||||
final opts = calloc<git_diff_options>();
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
import 'dart:ffi';
|
||||
import 'bindings/libgit2_bindings.dart';
|
||||
import 'bindings/blob.dart' as bindings;
|
||||
import 'bindings/patch.dart' as patch_bindings;
|
||||
import 'git_types.dart';
|
||||
import 'patch.dart';
|
||||
import 'oid.dart';
|
||||
import 'repository.dart';
|
||||
import 'util.dart';
|
||||
|
@ -66,6 +69,64 @@ class Blob {
|
|||
/// Returns the size in bytes of the contents of a blob.
|
||||
int get size => bindings.size(_blobPointer);
|
||||
|
||||
/// Directly generate a [Patch] from the difference between two blobs.
|
||||
///
|
||||
/// Should be freed with `free()` to release allocated memory.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Patch diff({
|
||||
required Blob? newBlob,
|
||||
String? oldAsPath,
|
||||
String? newAsPath,
|
||||
Set<GitDiff> flags = const {GitDiff.normal},
|
||||
int contextLines = 3,
|
||||
int interhunkLines = 0,
|
||||
}) {
|
||||
final int flagsInt =
|
||||
flags.fold(0, (previousValue, e) => previousValue | e.value);
|
||||
|
||||
final result = patch_bindings.fromBlobs(
|
||||
_blobPointer,
|
||||
oldAsPath,
|
||||
newBlob?.pointer,
|
||||
newAsPath,
|
||||
flagsInt,
|
||||
contextLines,
|
||||
interhunkLines,
|
||||
);
|
||||
|
||||
return Patch(result['patch'], result['a'], result['b']);
|
||||
}
|
||||
|
||||
/// Directly generate a [Patch] from the difference between the blob and a buffer.
|
||||
///
|
||||
/// Should be freed with `free()` to release allocated memory.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Patch diffToBuffer({
|
||||
required String? buffer,
|
||||
String? oldAsPath,
|
||||
String? bufferAsPath,
|
||||
Set<GitDiff> flags = const {GitDiff.normal},
|
||||
int contextLines = 3,
|
||||
int interhunkLines = 0,
|
||||
}) {
|
||||
final int flagsInt =
|
||||
flags.fold(0, (previousValue, e) => previousValue | e.value);
|
||||
|
||||
final result = patch_bindings.fromBlobAndBuffer(
|
||||
_blobPointer,
|
||||
oldAsPath,
|
||||
buffer,
|
||||
bufferAsPath,
|
||||
flagsInt,
|
||||
contextLines,
|
||||
interhunkLines,
|
||||
);
|
||||
|
||||
return Patch(result['patch'], result['a'], result['b']);
|
||||
}
|
||||
|
||||
/// Releases memory allocated for blob object.
|
||||
void free() => bindings.free(_blobPointer);
|
||||
}
|
||||
|
|
|
@ -2,8 +2,10 @@ import 'dart:ffi';
|
|||
import 'package:ffi/ffi.dart';
|
||||
import 'bindings/libgit2_bindings.dart';
|
||||
import 'bindings/diff.dart' as bindings;
|
||||
import 'bindings/patch.dart' as patch_bindings;
|
||||
import 'git_types.dart';
|
||||
import 'oid.dart';
|
||||
import 'patch.dart';
|
||||
import 'util.dart';
|
||||
|
||||
class Diff {
|
||||
|
@ -38,6 +40,22 @@ class Diff {
|
|||
return deltas;
|
||||
}
|
||||
|
||||
/// Returns a patch diff string.
|
||||
String get patch {
|
||||
final length = bindings.length(_diffPointer);
|
||||
var buffer = calloc<git_buf>(sizeOf<git_buf>());
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
final patch = Patch.fromDiff(this, i);
|
||||
buffer = bindings.addToBuf(patch.pointer, buffer);
|
||||
patch.free();
|
||||
}
|
||||
|
||||
final result = buffer.ref.ptr.cast<Utf8>().toDartString();
|
||||
calloc.free(buffer);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Accumulates diff statistics for all patches.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
|
@ -223,3 +241,95 @@ class DiffStats {
|
|||
/// Releases memory allocated for diff stats object.
|
||||
void free() => bindings.statsFree(_diffStatsPointer);
|
||||
}
|
||||
|
||||
class DiffHunk {
|
||||
/// Initializes a new instance of [DiffHunk] class from provided
|
||||
/// pointers to patch object and diff hunk object in memory and number of lines in hunk.
|
||||
DiffHunk(
|
||||
this._patchPointer,
|
||||
this._diffHunkPointer,
|
||||
this.linesCount,
|
||||
this.index,
|
||||
);
|
||||
|
||||
/// Pointer to memory address for allocated diff hunk object.
|
||||
final Pointer<git_diff_hunk> _diffHunkPointer;
|
||||
|
||||
/// Pointer to memory address for allocated patch object.
|
||||
final Pointer<git_patch> _patchPointer;
|
||||
|
||||
/// Returns count of total lines in this hunk.
|
||||
late final int linesCount;
|
||||
|
||||
/// Returns index of this hunk in the patch.
|
||||
late final int index;
|
||||
|
||||
/// Returns starting line number in 'old file'.
|
||||
int get oldStart => _diffHunkPointer.ref.old_start;
|
||||
|
||||
/// Returns number of lines in 'old file'.
|
||||
int get oldLines => _diffHunkPointer.ref.old_lines;
|
||||
|
||||
/// Returns starting line number in 'new file'.
|
||||
int get newStart => _diffHunkPointer.ref.new_start;
|
||||
|
||||
/// Returns number of lines in 'new file'.
|
||||
int get newLines => _diffHunkPointer.ref.new_lines;
|
||||
|
||||
/// Returns header of a hunk.
|
||||
String get header {
|
||||
var list = <int>[];
|
||||
for (var i = 0; i < _diffHunkPointer.ref.header_len; i++) {
|
||||
list.add(_diffHunkPointer.ref.header[i]);
|
||||
}
|
||||
return String.fromCharCodes(list);
|
||||
}
|
||||
|
||||
/// Returns list of lines in a hunk of a patch.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
List<DiffLine> get lines {
|
||||
var lines = <DiffLine>[];
|
||||
for (var i = 0; i < linesCount; i++) {
|
||||
lines.add(DiffLine(patch_bindings.lines(_patchPointer, index, i)));
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
|
||||
class DiffLine {
|
||||
/// Initializes a new instance of [DiffLine] class from provided
|
||||
/// pointer to diff line object in memory.
|
||||
DiffLine(this._diffLinePointer);
|
||||
|
||||
/// Pointer to memory address for allocated diff line object.
|
||||
final Pointer<git_diff_line> _diffLinePointer;
|
||||
|
||||
/// Returns type of the line.
|
||||
GitDiffLine get origin {
|
||||
final originInt = _diffLinePointer.ref.origin;
|
||||
late final GitDiffLine result;
|
||||
for (var flag in GitDiffLine.values) {
|
||||
if (originInt == flag.value) {
|
||||
result = flag;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Returns line number in old file or -1 for added line.
|
||||
int get oldLineNumber => _diffLinePointer.ref.old_lineno;
|
||||
|
||||
/// Returns line number in new file or -1 for deleted line.
|
||||
int get newLineNumber => _diffLinePointer.ref.new_lineno;
|
||||
|
||||
/// Returns number of newline characters in content.
|
||||
int get numLines => _diffLinePointer.ref.num_lines;
|
||||
|
||||
/// Returns offset in the original file to the content.
|
||||
int get contentOffset => _diffLinePointer.ref.content_offset;
|
||||
|
||||
/// Returns content of the diff line.
|
||||
String get content =>
|
||||
_diffLinePointer.ref.content.cast<Utf8>().toDartString();
|
||||
}
|
||||
|
|
|
@ -1003,3 +1003,46 @@ class GitDiffFind {
|
|||
@override
|
||||
String toString() => 'GitDiffFind.$_name';
|
||||
}
|
||||
|
||||
/// Line origin, describing where a line came from.
|
||||
class GitDiffLine {
|
||||
const GitDiffLine._(this._value, this._name);
|
||||
final int _value;
|
||||
final String _name;
|
||||
|
||||
static const context = GitDiffLine._(32, 'context');
|
||||
static const addition = GitDiffLine._(43, 'addition');
|
||||
static const deletion = GitDiffLine._(45, 'deletion');
|
||||
|
||||
/// Both files have no LF at end.
|
||||
static const contextEOFNL = GitDiffLine._(61, 'contextEOFNL');
|
||||
|
||||
/// Old has no LF at end, new does.
|
||||
static const addEOFNL = GitDiffLine._(62, 'addEOFNL');
|
||||
|
||||
/// Old has LF at end, new does not.
|
||||
static const delEOFNL = GitDiffLine._(60, 'delEOFNL');
|
||||
|
||||
static const fileHeader = GitDiffLine._(70, 'fileHeader');
|
||||
static const hunkHeader = GitDiffLine._(72, 'hunkHeader');
|
||||
|
||||
/// For "Binary files x and y differ"
|
||||
static const binary = GitDiffLine._(66, 'binary');
|
||||
|
||||
static const List<GitDiffLine> values = [
|
||||
context,
|
||||
addition,
|
||||
deletion,
|
||||
contextEOFNL,
|
||||
addEOFNL,
|
||||
delEOFNL,
|
||||
fileHeader,
|
||||
hunkHeader,
|
||||
binary,
|
||||
];
|
||||
|
||||
int get value => _value;
|
||||
|
||||
@override
|
||||
String toString() => 'GitDiffLine.$_name';
|
||||
}
|
||||
|
|
154
lib/src/patch.dart
Normal file
154
lib/src/patch.dart
Normal file
|
@ -0,0 +1,154 @@
|
|||
import 'dart:ffi';
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'bindings/libgit2_bindings.dart';
|
||||
import 'bindings/patch.dart' as bindings;
|
||||
import 'blob.dart';
|
||||
import 'diff.dart';
|
||||
import 'git_types.dart';
|
||||
import 'util.dart';
|
||||
|
||||
class Patch {
|
||||
/// Initializes a new instance of [Patch] class from provided
|
||||
/// pointer to patch object in memory and pointers to old and new blobs/buffers.
|
||||
///
|
||||
/// Should be freed with `free()` to release allocated memory.
|
||||
Patch(this._patchPointer, this._aPointer, this._bPointer) {
|
||||
libgit2.git_libgit2_init();
|
||||
}
|
||||
|
||||
/// Directly generates a patch from the difference between two blobs, buffers or
|
||||
/// blob and a buffer.
|
||||
///
|
||||
/// [a] and [b] can be [Blob], [String] or null.
|
||||
///
|
||||
/// Should be freed with `free()` to release allocated memory.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Patch.createFrom({
|
||||
required dynamic a,
|
||||
required dynamic b,
|
||||
String? aPath,
|
||||
String? bPath,
|
||||
Set<GitDiff> flags = const {GitDiff.normal},
|
||||
int contextLines = 3,
|
||||
int interhunkLines = 0,
|
||||
}) {
|
||||
final int flagsInt =
|
||||
flags.fold(0, (previousValue, e) => previousValue | e.value);
|
||||
var result = <String, dynamic>{};
|
||||
|
||||
if (a is Blob || a == null) {
|
||||
if (b is Blob) {
|
||||
result = bindings.fromBlobs(
|
||||
a?.pointer,
|
||||
aPath,
|
||||
b.pointer,
|
||||
bPath,
|
||||
flagsInt,
|
||||
contextLines,
|
||||
interhunkLines,
|
||||
);
|
||||
} else if (b is String || b == null) {
|
||||
result = bindings.fromBlobAndBuffer(
|
||||
a?.pointer,
|
||||
aPath,
|
||||
b,
|
||||
bPath,
|
||||
flagsInt,
|
||||
contextLines,
|
||||
interhunkLines,
|
||||
);
|
||||
} else {
|
||||
throw ArgumentError('Provided argument(s) is not Blob or String');
|
||||
}
|
||||
} else if ((a is String || a == null) && (b is String || b == null)) {
|
||||
result = bindings.fromBuffers(
|
||||
a,
|
||||
aPath,
|
||||
b,
|
||||
bPath,
|
||||
flagsInt,
|
||||
contextLines,
|
||||
interhunkLines,
|
||||
);
|
||||
} else {
|
||||
throw ArgumentError('Provided argument(s) is not Blob or String');
|
||||
}
|
||||
|
||||
_patchPointer = result['patch'];
|
||||
_aPointer = result['a'];
|
||||
_bPointer = result['b'];
|
||||
}
|
||||
|
||||
/// Returns a patch for an entry in the diff list.
|
||||
///
|
||||
/// Should be freed with `free()` to release allocated memory.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Patch.fromDiff(Diff diff, int index) {
|
||||
_patchPointer = bindings.fromDiff(diff.pointer, index);
|
||||
}
|
||||
|
||||
late final Pointer<git_patch> _patchPointer;
|
||||
|
||||
dynamic _aPointer;
|
||||
dynamic _bPointer;
|
||||
|
||||
/// Pointer to memory address for allocated patch object.
|
||||
Pointer<git_patch> get pointer => _patchPointer;
|
||||
|
||||
/// Returns the content of a patch as a single diff text.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
String get text => bindings.text(_patchPointer);
|
||||
|
||||
/// Looks 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({
|
||||
bool includeContext = false,
|
||||
bool includeHunkHeaders = false,
|
||||
bool includeFileHeaders = false,
|
||||
}) {
|
||||
return bindings.size(
|
||||
_patchPointer,
|
||||
includeContext,
|
||||
includeHunkHeaders,
|
||||
includeFileHeaders,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the delta associated with a patch.
|
||||
DiffDelta get delta => DiffDelta(bindings.delta(_patchPointer));
|
||||
|
||||
/// Returns the list of hunks in a patch.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
List<DiffHunk> get hunks {
|
||||
final length = bindings.numHunks(_patchPointer);
|
||||
final hunks = <DiffHunk>[];
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
final hunk = bindings.hunk(_patchPointer, i);
|
||||
hunks.add(DiffHunk(_patchPointer, hunk['hunk'], hunk['linesN'], i));
|
||||
}
|
||||
|
||||
return hunks;
|
||||
}
|
||||
|
||||
/// Releases memory allocated for patch object.
|
||||
void free() {
|
||||
if (_aPointer != null) {
|
||||
calloc.free(_aPointer);
|
||||
}
|
||||
if (_bPointer != null) {
|
||||
calloc.free(_bPointer);
|
||||
}
|
||||
bindings.free(_patchPointer);
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import 'bindings/status.dart' as status_bindings;
|
|||
import 'bindings/commit.dart' as commit_bindings;
|
||||
import 'bindings/checkout.dart' as checkout_bindings;
|
||||
import 'bindings/reset.dart' as reset_bindings;
|
||||
import 'bindings/diff.dart' as diff_bindings;
|
||||
import 'branch.dart';
|
||||
import 'commit.dart';
|
||||
import 'config.dart';
|
||||
|
@ -723,4 +724,78 @@ class Repository {
|
|||
|
||||
object_bindings.free(object);
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Diff diff({
|
||||
Tree? a,
|
||||
Tree? b,
|
||||
bool cached = false,
|
||||
Set<GitDiff> flags = const {GitDiff.normal},
|
||||
int contextLines = 3,
|
||||
int interhunkLines = 0,
|
||||
}) {
|
||||
final int flagsInt =
|
||||
flags.fold(0, (previousValue, e) => previousValue | e.value);
|
||||
|
||||
if (a is Tree && b is Tree) {
|
||||
return Diff(diff_bindings.treeToTree(
|
||||
_repoPointer,
|
||||
a.pointer,
|
||||
b.pointer,
|
||||
flagsInt,
|
||||
contextLines,
|
||||
interhunkLines,
|
||||
));
|
||||
} else if (a is Tree && b == null) {
|
||||
if (cached) {
|
||||
return Diff(diff_bindings.treeToIndex(
|
||||
_repoPointer,
|
||||
a.pointer,
|
||||
index.pointer,
|
||||
flagsInt,
|
||||
contextLines,
|
||||
interhunkLines,
|
||||
));
|
||||
} else {
|
||||
return Diff(diff_bindings.treeToWorkdir(
|
||||
_repoPointer,
|
||||
a.pointer,
|
||||
flagsInt,
|
||||
contextLines,
|
||||
interhunkLines,
|
||||
));
|
||||
}
|
||||
} else if (a == null && b == null) {
|
||||
return Diff(diff_bindings.indexToWorkdir(
|
||||
_repoPointer,
|
||||
index.pointer,
|
||||
flagsInt,
|
||||
contextLines,
|
||||
interhunkLines,
|
||||
));
|
||||
} else {
|
||||
throw ArgumentError.notNull('a');
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a [Patch] with changes between the blobs.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Patch diffBlobs({
|
||||
required Blob a,
|
||||
required Blob b,
|
||||
String? aPath,
|
||||
String? bPath,
|
||||
Set<GitDiff> flags = const {GitDiff.normal},
|
||||
int contextLines = 3,
|
||||
int interhunkLines = 0,
|
||||
}) {
|
||||
return a.diff(newBlob: b, oldAsPath: aPath, newAsPath: bPath);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue