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,5 +15,6 @@ export 'src/treebuilder.dart';
|
||||||
export 'src/branch.dart';
|
export 'src/branch.dart';
|
||||||
export 'src/worktree.dart';
|
export 'src/worktree.dart';
|
||||||
export 'src/diff.dart';
|
export 'src/diff.dart';
|
||||||
|
export 'src/patch.dart';
|
||||||
export 'src/error.dart';
|
export 'src/error.dart';
|
||||||
export 'src/git_types.dart';
|
export 'src/git_types.dart';
|
||||||
|
|
|
@ -15,16 +15,7 @@ Pointer<git_diff> indexToWorkdir(
|
||||||
int interhunkLines,
|
int interhunkLines,
|
||||||
) {
|
) {
|
||||||
final out = calloc<Pointer<git_diff>>();
|
final out = calloc<Pointer<git_diff>>();
|
||||||
final opts = calloc<git_diff_options>();
|
final opts = _diffOptionsInit(flags, contextLines, interhunkLines);
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
libgit2.git_diff_index_to_workdir(out, repo, index, opts);
|
libgit2.git_diff_index_to_workdir(out, repo, index, opts);
|
||||||
|
|
||||||
|
@ -45,16 +36,7 @@ Pointer<git_diff> treeToIndex(
|
||||||
int interhunkLines,
|
int interhunkLines,
|
||||||
) {
|
) {
|
||||||
final out = calloc<Pointer<git_diff>>();
|
final out = calloc<Pointer<git_diff>>();
|
||||||
final opts = calloc<git_diff_options>();
|
final opts = _diffOptionsInit(flags, contextLines, interhunkLines);
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
libgit2.git_diff_tree_to_index(out, repo, oldTree, index, opts);
|
libgit2.git_diff_tree_to_index(out, repo, oldTree, index, opts);
|
||||||
|
|
||||||
|
@ -74,16 +56,7 @@ Pointer<git_diff> treeToWorkdir(
|
||||||
int interhunkLines,
|
int interhunkLines,
|
||||||
) {
|
) {
|
||||||
final out = calloc<Pointer<git_diff>>();
|
final out = calloc<Pointer<git_diff>>();
|
||||||
final opts = calloc<git_diff_options>();
|
final opts = _diffOptionsInit(flags, contextLines, interhunkLines);
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
libgit2.git_diff_tree_to_workdir(out, repo, oldTree, opts);
|
libgit2.git_diff_tree_to_workdir(out, repo, oldTree, opts);
|
||||||
|
|
||||||
|
@ -104,16 +77,7 @@ Pointer<git_diff> treeToTree(
|
||||||
int interhunkLines,
|
int interhunkLines,
|
||||||
) {
|
) {
|
||||||
final out = calloc<Pointer<git_diff>>();
|
final out = calloc<Pointer<git_diff>>();
|
||||||
final opts = calloc<git_diff_options>();
|
final opts = _diffOptionsInit(flags, contextLines, interhunkLines);
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
libgit2.git_diff_tree_to_tree(out, repo, oldTree, newTree, opts);
|
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.
|
/// Free a previously allocated diff stats.
|
||||||
void statsFree(Pointer<git_diff_stats> stats) =>
|
void statsFree(Pointer<git_diff_stats> stats) =>
|
||||||
libgit2.git_diff_stats_free(stats);
|
libgit2.git_diff_stats_free(stats);
|
||||||
|
|
||||||
/// Free a previously allocated diff.
|
/// Free a previously allocated diff.
|
||||||
void free(Pointer<git_diff> diff) => libgit2.git_diff_free(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 'dart:ffi';
|
||||||
import 'bindings/libgit2_bindings.dart';
|
import 'bindings/libgit2_bindings.dart';
|
||||||
import 'bindings/blob.dart' as bindings;
|
import 'bindings/blob.dart' as bindings;
|
||||||
|
import 'bindings/patch.dart' as patch_bindings;
|
||||||
|
import 'git_types.dart';
|
||||||
|
import 'patch.dart';
|
||||||
import 'oid.dart';
|
import 'oid.dart';
|
||||||
import 'repository.dart';
|
import 'repository.dart';
|
||||||
import 'util.dart';
|
import 'util.dart';
|
||||||
|
@ -66,6 +69,64 @@ class Blob {
|
||||||
/// Returns the size in bytes of the contents of a blob.
|
/// Returns the size in bytes of the contents of a blob.
|
||||||
int get size => bindings.size(_blobPointer);
|
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.
|
/// Releases memory allocated for blob object.
|
||||||
void free() => bindings.free(_blobPointer);
|
void free() => bindings.free(_blobPointer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,10 @@ import 'dart:ffi';
|
||||||
import 'package:ffi/ffi.dart';
|
import 'package:ffi/ffi.dart';
|
||||||
import 'bindings/libgit2_bindings.dart';
|
import 'bindings/libgit2_bindings.dart';
|
||||||
import 'bindings/diff.dart' as bindings;
|
import 'bindings/diff.dart' as bindings;
|
||||||
|
import 'bindings/patch.dart' as patch_bindings;
|
||||||
import 'git_types.dart';
|
import 'git_types.dart';
|
||||||
import 'oid.dart';
|
import 'oid.dart';
|
||||||
|
import 'patch.dart';
|
||||||
import 'util.dart';
|
import 'util.dart';
|
||||||
|
|
||||||
class Diff {
|
class Diff {
|
||||||
|
@ -38,6 +40,22 @@ class Diff {
|
||||||
return deltas;
|
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.
|
/// Accumulates diff statistics for all patches.
|
||||||
///
|
///
|
||||||
/// Throws a [LibGit2Error] if error occured.
|
/// Throws a [LibGit2Error] if error occured.
|
||||||
|
@ -223,3 +241,95 @@ class DiffStats {
|
||||||
/// Releases memory allocated for diff stats object.
|
/// Releases memory allocated for diff stats object.
|
||||||
void free() => bindings.statsFree(_diffStatsPointer);
|
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
|
@override
|
||||||
String toString() => 'GitDiffFind.$_name';
|
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/commit.dart' as commit_bindings;
|
||||||
import 'bindings/checkout.dart' as checkout_bindings;
|
import 'bindings/checkout.dart' as checkout_bindings;
|
||||||
import 'bindings/reset.dart' as reset_bindings;
|
import 'bindings/reset.dart' as reset_bindings;
|
||||||
|
import 'bindings/diff.dart' as diff_bindings;
|
||||||
import 'branch.dart';
|
import 'branch.dart';
|
||||||
import 'commit.dart';
|
import 'commit.dart';
|
||||||
import 'config.dart';
|
import 'config.dart';
|
||||||
|
@ -723,4 +724,78 @@ class Repository {
|
||||||
|
|
||||||
object_bindings.free(object);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,5 +96,82 @@ void main() {
|
||||||
|
|
||||||
newBlob.free();
|
newBlob.free();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('diff', () {
|
||||||
|
const path = 'feature_file';
|
||||||
|
const oldBlobSha = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391';
|
||||||
|
const newBlobSha = '9c78c21d6680a7ffebc76f7ac68cacc11d8f48bc';
|
||||||
|
const blobPatch = """
|
||||||
|
diff --git a/feature_file b/feature_file
|
||||||
|
index e69de29..9c78c21 100644
|
||||||
|
--- a/feature_file
|
||||||
|
+++ b/feature_file
|
||||||
|
@@ -0,0 +1 @@
|
||||||
|
+Feature edit
|
||||||
|
""";
|
||||||
|
|
||||||
|
const blobPatchDelete = """
|
||||||
|
diff --git a/feature_file b/feature_file
|
||||||
|
deleted file mode 100644
|
||||||
|
index e69de29..0000000
|
||||||
|
--- a/feature_file
|
||||||
|
+++ /dev/null
|
||||||
|
""";
|
||||||
|
test('successfully creates from blobs', () {
|
||||||
|
final a = repo[oldBlobSha] as Blob;
|
||||||
|
final b = repo[newBlobSha] as Blob;
|
||||||
|
final patch = repo.diffBlobs(
|
||||||
|
a: a,
|
||||||
|
b: b,
|
||||||
|
aPath: path,
|
||||||
|
bPath: path,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(patch.text, blobPatch);
|
||||||
|
|
||||||
|
patch.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully creates from one blob (delete)', () {
|
||||||
|
final a = repo[oldBlobSha] as Blob;
|
||||||
|
final patch = a.diff(
|
||||||
|
newBlob: null,
|
||||||
|
oldAsPath: path,
|
||||||
|
newAsPath: path,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(patch.text, blobPatchDelete);
|
||||||
|
|
||||||
|
patch.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully creates from blob and buffer', () {
|
||||||
|
final a = repo[oldBlobSha] as Blob;
|
||||||
|
final patch = Patch.createFrom(
|
||||||
|
a: a,
|
||||||
|
b: 'Feature edit\n',
|
||||||
|
aPath: path,
|
||||||
|
bPath: path,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(patch.text, blobPatch);
|
||||||
|
|
||||||
|
patch.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully creates from blob and buffer (delete)', () {
|
||||||
|
final a = repo[oldBlobSha] as Blob;
|
||||||
|
final patch = Patch.createFrom(
|
||||||
|
a: a,
|
||||||
|
b: null,
|
||||||
|
aPath: path,
|
||||||
|
bPath: path,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(patch.text, blobPatchDelete);
|
||||||
|
|
||||||
|
patch.free();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ void main() {
|
||||||
'subdir/modified_file',
|
'subdir/modified_file',
|
||||||
];
|
];
|
||||||
|
|
||||||
const patch = """
|
const patchText = """
|
||||||
diff --git a/subdir/modified_file b/subdir/modified_file
|
diff --git a/subdir/modified_file b/subdir/modified_file
|
||||||
index e69de29..c217c63 100644
|
index e69de29..c217c63 100644
|
||||||
--- a/subdir/modified_file
|
--- a/subdir/modified_file
|
||||||
|
@ -94,7 +94,7 @@ index e69de29..c217c63 100644
|
||||||
group('Diff', () {
|
group('Diff', () {
|
||||||
test('successfully returns diff between index and workdir', () {
|
test('successfully returns diff between index and workdir', () {
|
||||||
final index = repo.index;
|
final index = repo.index;
|
||||||
final diff = index.diffToWorkdir();
|
final diff = repo.diff();
|
||||||
|
|
||||||
expect(diff.length, 8);
|
expect(diff.length, 8);
|
||||||
for (var i = 0; i < diff.deltas.length; i++) {
|
for (var i = 0; i < diff.deltas.length; i++) {
|
||||||
|
@ -122,7 +122,7 @@ index e69de29..c217c63 100644
|
||||||
|
|
||||||
test('successfully returns diff between tree and workdir', () {
|
test('successfully returns diff between tree and workdir', () {
|
||||||
final tree = (repo[repo.head.target.sha] as Commit).tree;
|
final tree = (repo[repo.head.target.sha] as Commit).tree;
|
||||||
final diff = tree.diffToWorkdir();
|
final diff = repo.diff(a: tree);
|
||||||
|
|
||||||
expect(diff.length, 9);
|
expect(diff.length, 9);
|
||||||
for (var i = 0; i < diff.deltas.length; i++) {
|
for (var i = 0; i < diff.deltas.length; i++) {
|
||||||
|
@ -136,7 +136,7 @@ index e69de29..c217c63 100644
|
||||||
test('successfully returns diff between tree and index', () {
|
test('successfully returns diff between tree and index', () {
|
||||||
final index = repo.index;
|
final index = repo.index;
|
||||||
final tree = (repo[repo.head.target.sha] as Commit).tree;
|
final tree = (repo[repo.head.target.sha] as Commit).tree;
|
||||||
final diff = tree.diffToIndex(index: index);
|
final diff = repo.diff(a: tree, cached: true);
|
||||||
|
|
||||||
expect(diff.length, 8);
|
expect(diff.length, 8);
|
||||||
for (var i = 0; i < diff.deltas.length; i++) {
|
for (var i = 0; i < diff.deltas.length; i++) {
|
||||||
|
@ -151,7 +151,7 @@ index e69de29..c217c63 100644
|
||||||
test('successfully returns diff between tree and tree', () {
|
test('successfully returns diff between tree and tree', () {
|
||||||
final tree1 = (repo[repo.head.target.sha] as Commit).tree;
|
final tree1 = (repo[repo.head.target.sha] as Commit).tree;
|
||||||
final tree2 = repo['b85d53c9236e89aff2b62558adaa885fd1d6ff1c'] as Tree;
|
final tree2 = repo['b85d53c9236e89aff2b62558adaa885fd1d6ff1c'] as Tree;
|
||||||
final diff = tree1.diffToTree(tree: tree2);
|
final diff = repo.diff(a: tree1, b: tree2);
|
||||||
|
|
||||||
expect(diff.length, 10);
|
expect(diff.length, 10);
|
||||||
for (var i = 0; i < diff.deltas.length; i++) {
|
for (var i = 0; i < diff.deltas.length; i++) {
|
||||||
|
@ -182,7 +182,7 @@ index e69de29..c217c63 100644
|
||||||
});
|
});
|
||||||
|
|
||||||
test('successfully parses provided diff', () {
|
test('successfully parses provided diff', () {
|
||||||
final diff = Diff.parse(patch);
|
final diff = Diff.parse(patchText);
|
||||||
final stats = diff.stats;
|
final stats = diff.stats;
|
||||||
|
|
||||||
expect(diff.length, 1);
|
expect(diff.length, 1);
|
||||||
|
@ -194,6 +194,17 @@ index e69de29..c217c63 100644
|
||||||
diff.free();
|
diff.free();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('successfully creates patch from entry index in diff', () {
|
||||||
|
final diff = Diff.parse(patchText);
|
||||||
|
final patch = Patch.fromDiff(diff, 0);
|
||||||
|
|
||||||
|
expect(diff.length, 1);
|
||||||
|
expect(patch.text, patchText);
|
||||||
|
|
||||||
|
patch.free();
|
||||||
|
diff.free();
|
||||||
|
});
|
||||||
|
|
||||||
test('successfully finds similar entries', () {
|
test('successfully finds similar entries', () {
|
||||||
final index = repo.index;
|
final index = repo.index;
|
||||||
final oldTree = (repo[repo.head.target.sha] as Commit).tree;
|
final oldTree = (repo[repo.head.target.sha] as Commit).tree;
|
||||||
|
@ -217,7 +228,7 @@ index e69de29..c217c63 100644
|
||||||
newTree.free();
|
newTree.free();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns deltas and patches', () {
|
test('returns deltas', () {
|
||||||
final index = repo.index;
|
final index = repo.index;
|
||||||
final diff = index.diffToWorkdir();
|
final diff = index.diffToWorkdir();
|
||||||
|
|
||||||
|
@ -268,5 +279,48 @@ index e69de29..c217c63 100644
|
||||||
diff.free();
|
diff.free();
|
||||||
index.free();
|
index.free();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('returns patch diff string', () {
|
||||||
|
final diff = Diff.parse(patchText);
|
||||||
|
|
||||||
|
expect(diff.patch, patchText);
|
||||||
|
|
||||||
|
diff.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns hunks in a patch', () {
|
||||||
|
final diff = Diff.parse(patchText);
|
||||||
|
final patch = Patch.fromDiff(diff, 0);
|
||||||
|
final hunk = patch.hunks[0];
|
||||||
|
|
||||||
|
expect(patch.hunks.length, 1);
|
||||||
|
expect(hunk.linesCount, 1);
|
||||||
|
expect(hunk.oldStart, 0);
|
||||||
|
expect(hunk.oldLines, 0);
|
||||||
|
expect(hunk.newStart, 1);
|
||||||
|
expect(hunk.newLines, 1);
|
||||||
|
expect(hunk.header, '\x00\x00\x00\x00@@ -0,0 +1');
|
||||||
|
|
||||||
|
patch.free();
|
||||||
|
diff.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns lines in a hunk', () {
|
||||||
|
final diff = Diff.parse(patchText);
|
||||||
|
final patch = Patch.fromDiff(diff, 0);
|
||||||
|
final hunk = patch.hunks[0];
|
||||||
|
final line = hunk.lines[0];
|
||||||
|
|
||||||
|
expect(hunk.lines.length, 1);
|
||||||
|
expect(line.origin, GitDiffLine.addition);
|
||||||
|
expect(line.oldLineNumber, -1);
|
||||||
|
expect(line.newLineNumber, 1);
|
||||||
|
expect(line.numLines, 1);
|
||||||
|
expect(line.contentOffset, 155);
|
||||||
|
expect(line.content, 'Modified content\n');
|
||||||
|
|
||||||
|
patch.free();
|
||||||
|
diff.free();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
180
test/patch_test.dart
Normal file
180
test/patch_test.dart
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:libgit2dart/libgit2dart.dart';
|
||||||
|
import 'helpers/util.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late Repository repo;
|
||||||
|
final tmpDir = '${Directory.systemTemp.path}/patch_testrepo/';
|
||||||
|
const oldBlob = '';
|
||||||
|
const newBlob = 'Feature edit\n';
|
||||||
|
const path = 'feature_file';
|
||||||
|
const oldBlobSha = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391';
|
||||||
|
const newBlobSha = '9c78c21d6680a7ffebc76f7ac68cacc11d8f48bc';
|
||||||
|
const blobPatch = """
|
||||||
|
diff --git a/feature_file b/feature_file
|
||||||
|
index e69de29..9c78c21 100644
|
||||||
|
--- a/feature_file
|
||||||
|
+++ b/feature_file
|
||||||
|
@@ -0,0 +1 @@
|
||||||
|
+Feature edit
|
||||||
|
""";
|
||||||
|
|
||||||
|
const blobPatchAdd = """
|
||||||
|
diff --git a/feature_file b/feature_file
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..9c78c21
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/feature_file
|
||||||
|
@@ -0,0 +1 @@
|
||||||
|
+Feature edit
|
||||||
|
""";
|
||||||
|
|
||||||
|
const blobPatchDelete = """
|
||||||
|
diff --git a/feature_file b/feature_file
|
||||||
|
deleted file mode 100644
|
||||||
|
index e69de29..0000000
|
||||||
|
--- a/feature_file
|
||||||
|
+++ /dev/null
|
||||||
|
""";
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
if (await Directory(tmpDir).exists()) {
|
||||||
|
await Directory(tmpDir).delete(recursive: true);
|
||||||
|
}
|
||||||
|
await copyRepo(
|
||||||
|
from: Directory('test/assets/testrepo/'),
|
||||||
|
to: await Directory(tmpDir).create(),
|
||||||
|
);
|
||||||
|
repo = Repository.open(tmpDir);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
repo.free();
|
||||||
|
await Directory(tmpDir).delete(recursive: true);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Patch', () {
|
||||||
|
test('successfully creates from buffers', () {
|
||||||
|
final patch = Patch.createFrom(
|
||||||
|
a: oldBlob,
|
||||||
|
b: newBlob,
|
||||||
|
aPath: path,
|
||||||
|
bPath: path,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(patch.size(), 14);
|
||||||
|
expect(patch.text, blobPatch);
|
||||||
|
|
||||||
|
patch.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully creates from one buffer (add)', () {
|
||||||
|
final patch = Patch.createFrom(
|
||||||
|
a: null,
|
||||||
|
b: newBlob,
|
||||||
|
aPath: path,
|
||||||
|
bPath: path,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(patch.text, blobPatchAdd);
|
||||||
|
|
||||||
|
patch.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully creates from one buffer (delete)', () {
|
||||||
|
final patch = Patch.createFrom(
|
||||||
|
a: oldBlob,
|
||||||
|
b: null,
|
||||||
|
aPath: path,
|
||||||
|
bPath: path,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(patch.text, blobPatchDelete);
|
||||||
|
|
||||||
|
patch.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully creates from blobs', () {
|
||||||
|
final a = repo[oldBlobSha] as Blob;
|
||||||
|
final b = repo[newBlobSha] as Blob;
|
||||||
|
final patch = Patch.createFrom(
|
||||||
|
a: a,
|
||||||
|
b: b,
|
||||||
|
aPath: path,
|
||||||
|
bPath: path,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(patch.text, blobPatch);
|
||||||
|
|
||||||
|
patch.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully creates from one blob (add)', () {
|
||||||
|
final b = repo[newBlobSha] as Blob;
|
||||||
|
final patch = Patch.createFrom(
|
||||||
|
a: null,
|
||||||
|
b: b,
|
||||||
|
aPath: path,
|
||||||
|
bPath: path,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(patch.text, blobPatchAdd);
|
||||||
|
|
||||||
|
patch.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully creates from one blob (delete)', () {
|
||||||
|
final a = repo[oldBlobSha] as Blob;
|
||||||
|
final patch = Patch.createFrom(
|
||||||
|
a: a,
|
||||||
|
b: null,
|
||||||
|
aPath: path,
|
||||||
|
bPath: path,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(patch.text, blobPatchDelete);
|
||||||
|
|
||||||
|
patch.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully creates from blob and buffer', () {
|
||||||
|
final a = repo[oldBlobSha] as Blob;
|
||||||
|
final patch = Patch.createFrom(
|
||||||
|
a: a,
|
||||||
|
b: newBlob,
|
||||||
|
aPath: path,
|
||||||
|
bPath: path,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(patch.text, blobPatch);
|
||||||
|
|
||||||
|
patch.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throws when argument is not Blob or String', () {
|
||||||
|
final commit = repo['fc38877b2552ab554752d9a77e1f48f738cca79b'] as Commit;
|
||||||
|
expect(
|
||||||
|
() => Patch.createFrom(
|
||||||
|
a: commit,
|
||||||
|
b: null,
|
||||||
|
aPath: 'file',
|
||||||
|
bPath: 'file',
|
||||||
|
),
|
||||||
|
throwsA(isA<ArgumentError>()),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() => Patch.createFrom(
|
||||||
|
a: null,
|
||||||
|
b: commit,
|
||||||
|
aPath: 'file',
|
||||||
|
bPath: 'file',
|
||||||
|
),
|
||||||
|
throwsA(isA<ArgumentError>()),
|
||||||
|
);
|
||||||
|
|
||||||
|
commit.free();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue