feat(diff): add more bindings and api methods (#18)

This commit is contained in:
Aleksey Kulikov 2021-12-14 12:53:17 +03:00 committed by GitHub
parent 6c1735d67d
commit b3e9f6dd1a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 238 additions and 29 deletions

View file

@ -298,6 +298,24 @@ Pointer<git_buf> addToBuf({
return bufferPointer; return bufferPointer;
} }
/// Counter for hunk number being applied.
///
/// **IMPORTANT**: make sure to reset it to 0 before using since it's a global
/// variable.
int _counter = 0;
/// When applying a patch, callback that will be made per hunk.
int _hunkCb(Pointer<git_diff_hunk> hunk, Pointer<Void> payload) {
final index = payload.cast<Int32>().value;
if (_counter == index) {
_counter++;
return 0;
} else {
_counter++;
return 1;
}
}
/// Apply a diff to the given repository, making changes directly in the /// Apply a diff to the given repository, making changes directly in the
/// working directory, the index, or both. /// working directory, the index, or both.
/// ///
@ -305,6 +323,7 @@ Pointer<git_buf> addToBuf({
bool apply({ bool apply({
required Pointer<git_repository> repoPointer, required Pointer<git_repository> repoPointer,
required Pointer<git_diff> diffPointer, required Pointer<git_diff> diffPointer,
int? hunkIndex,
required int location, required int location,
bool check = false, bool check = false,
}) { }) {
@ -313,8 +332,18 @@ bool apply({
if (check) { if (check) {
opts.ref.flags |= git_apply_flags_t.GIT_APPLY_CHECK; opts.ref.flags |= git_apply_flags_t.GIT_APPLY_CHECK;
} }
Pointer<Int32> payload = nullptr;
if (hunkIndex != null) {
_counter = 0;
const except = -1;
final git_apply_hunk_cb callback = Pointer.fromFunction(_hunkCb, except);
payload = calloc<Int32>()..value = hunkIndex;
opts.ref.payload = payload.cast();
opts.ref.hunk_cb = callback;
}
final error = libgit2.git_apply(repoPointer, diffPointer, location, opts); final error = libgit2.git_apply(repoPointer, diffPointer, location, opts);
calloc.free(payload);
calloc.free(opts); calloc.free(opts);
if (error < 0) { if (error < 0) {
@ -324,6 +353,46 @@ bool apply({
} }
} }
/// Apply a diff to a tree, and return the resulting image as an index.
///
/// Throws a [LibGit2Error] if error occured.
Pointer<git_index> applyToTree({
required Pointer<git_repository> repoPointer,
required Pointer<git_tree> treePointer,
required Pointer<git_diff> diffPointer,
int? hunkIndex,
}) {
final out = calloc<Pointer<git_index>>();
final opts = calloc<git_apply_options>();
libgit2.git_apply_options_init(opts, GIT_APPLY_OPTIONS_VERSION);
Pointer<Int32> payload = nullptr;
if (hunkIndex != null) {
_counter = 0;
const except = -1;
final git_apply_hunk_cb callback = Pointer.fromFunction(_hunkCb, except);
payload = calloc<Int32>()..value = hunkIndex;
opts.ref.payload = payload.cast();
opts.ref.hunk_cb = callback;
}
final error = libgit2.git_apply_to_tree(
out,
repoPointer,
treePointer,
diffPointer,
opts,
);
calloc.free(payload);
calloc.free(opts);
if (error < 0) {
calloc.free(out);
throw LibGit2Error(libgit2.git_error_last());
} else {
return out.value;
}
}
/// 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);

View file

@ -1362,27 +1362,56 @@ class Repository {
/// [location] (directly in the working directory (default), the index or /// [location] (directly in the working directory (default), the index or
/// both). /// both).
/// ///
/// [hunkIndex] is optional index of the hunk to apply.
///
/// Throws a [LibGit2Error] if error occured. /// Throws a [LibGit2Error] if error occured.
void apply({ void apply({
required Diff diff, required Diff diff,
int? hunkIndex,
GitApplyLocation location = GitApplyLocation.workdir, GitApplyLocation location = GitApplyLocation.workdir,
}) { }) {
diff_bindings.apply( diff_bindings.apply(
repoPointer: _repoPointer, repoPointer: _repoPointer,
diffPointer: diff.pointer, diffPointer: diff.pointer,
hunkIndex: hunkIndex,
location: location.value, location: location.value,
); );
} }
/// Applies the [diff] to the [tree], and returns the resulting image as an
/// index.
///
/// [hunkIndex] is optional index of the hunk to apply.
///
/// Throws a [LibGit2Error] if error occured.
Index applyToTree({
required Diff diff,
required Tree tree,
int? hunkIndex,
}) {
return Index(
diff_bindings.applyToTree(
repoPointer: _repoPointer,
diffPointer: diff.pointer,
treePointer: tree.pointer,
hunkIndex: hunkIndex,
),
);
}
/// Checks if the [diff] will apply to provided [location] (the working /// Checks if the [diff] will apply to provided [location] (the working
/// directory (default), the index or both). /// directory (default), the index or both).
///
/// [hunkIndex] is optional index of the hunk to apply.
bool applies({ bool applies({
required Diff diff, required Diff diff,
int? hunkIndex,
GitApplyLocation location = GitApplyLocation.workdir, GitApplyLocation location = GitApplyLocation.workdir,
}) { }) {
return diff_bindings.apply( return diff_bindings.apply(
repoPointer: _repoPointer, repoPointer: _repoPointer,
diffPointer: diff.pointer, diffPointer: diff.pointer,
hunkIndex: hunkIndex,
location: location.value, location: location.value,
check: true, check: true,
); );

View file

@ -242,13 +242,44 @@ index e69de29..c217c63 100644
diff.free(); diff.free();
}); });
group('apply', () {
test('checks if diff can be applied to repository', () { test('checks if diff can be applied to repository', () {
final diff1 = repo.diff(); final diff1 = repo.diff();
expect(repo.applies(diff: diff1, location: GitApplyLocation.both), false); expect(
repo.applies(diff: diff1, location: GitApplyLocation.both),
false,
);
final diff2 = Diff.parse(patchText); final diff2 = Diff.parse(patchText);
repo.checkout(refName: 'HEAD', strategy: {GitCheckout.force}); repo.checkout(refName: 'HEAD', strategy: {GitCheckout.force});
expect(repo.applies(diff: diff2, location: GitApplyLocation.both), true); expect(
repo.applies(diff: diff2, location: GitApplyLocation.both),
true,
);
diff1.free();
diff2.free();
});
test('checks if hunk with provided index can be applied to repository',
() {
final diff1 = repo.diff();
expect(
repo.applies(diff: diff1, location: GitApplyLocation.both),
false,
);
final diff2 = Diff.parse(patchText);
final hunk = diff2.patches.first.hunks.first;
repo.checkout(refName: 'HEAD', strategy: {GitCheckout.force});
expect(
repo.applies(
diff: diff2,
hunkIndex: hunk.index,
location: GitApplyLocation.both,
),
true,
);
diff1.free(); diff1.free();
diff2.free(); diff2.free();
@ -283,6 +314,86 @@ index e69de29..c217c63 100644
diff.free(); diff.free();
}); });
test('successfully applies hunk with provided index to repository', () {
final diff = Diff.parse(patchText);
final hunk = diff.patches.first.hunks.first;
final file = File('${tmpDir.path}/subdir/modified_file');
repo.checkout(refName: 'HEAD', strategy: {GitCheckout.force});
expect(file.readAsStringSync(), '');
repo.apply(diff: diff, hunkIndex: hunk.index);
expect(file.readAsStringSync(), 'Modified content\n');
diff.free();
});
test('successfully applies diff to tree', () {
final diff = Diff.parse(patchText);
repo.checkout(refName: 'HEAD', strategy: {GitCheckout.force});
final head = repo.head;
final commit = repo.lookupCommit(head.target);
final tree = commit.tree;
final oldIndex = repo.index;
final oldBlob = repo.lookupBlob(oldIndex['subdir/modified_file'].oid);
expect(oldBlob.content, '');
final newIndex = repo.applyToTree(diff: diff, tree: tree);
final newBlob = repo.lookupBlob(newIndex['subdir/modified_file'].oid);
expect(newBlob.content, 'Modified content\n');
oldBlob.free();
newBlob.free();
oldIndex.free();
newIndex.free();
tree.free();
commit.free();
head.free();
diff.free();
});
test('successfully applies hunk with provided index to tree', () {
final diff = Diff.parse(patchText);
final hunk = diff.patches.first.hunks.first;
repo.checkout(refName: 'HEAD', strategy: {GitCheckout.force});
final head = repo.head;
final commit = repo.lookupCommit(head.target);
final tree = commit.tree;
final oldIndex = repo.index;
final oldBlob = repo.lookupBlob(oldIndex['subdir/modified_file'].oid);
expect(oldBlob.content, '');
final newIndex = repo.applyToTree(
diff: diff,
tree: tree,
hunkIndex: hunk.index,
);
final newBlob = repo.lookupBlob(newIndex['subdir/modified_file'].oid);
expect(newBlob.content, 'Modified content\n');
oldBlob.free();
newBlob.free();
oldIndex.free();
newIndex.free();
tree.free();
commit.free();
head.free();
diff.free();
});
test('throws when trying to apply diff to tree and error occurs', () {
final diff = Diff.parse(patchText);
expect(
() => repo.applyToTree(diff: diff, tree: Tree(nullptr)),
throwsA(isA<LibGit2Error>()),
);
});
});
test('successfully finds similar entries', () { test('successfully finds similar entries', () {
final index = repo.index; final index = repo.index;
final head = repo.head; final head = repo.head;