diff --git a/lib/src/bindings/graph.dart b/lib/src/bindings/graph.dart new file mode 100644 index 0000000..06d9f6a --- /dev/null +++ b/lib/src/bindings/graph.dart @@ -0,0 +1,53 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'libgit2_bindings.dart'; +import '../error.dart'; +import '../util.dart'; + +/// Determine if a commit is the descendant of another commit. +/// +/// Note that a commit is not considered a descendant of itself, in contrast to +/// `git merge-base --is-ancestor`. +/// +/// Throws a [LibGit2Error] if error occured. +bool descendantOf({ + required Pointer repoPointer, + required Pointer commitPointer, + required Pointer ancestorPointer, +}) { + final result = libgit2.git_graph_descendant_of( + repoPointer, + commitPointer, + ancestorPointer, + ); + + if (result < 0) { + throw LibGit2Error(libgit2.git_error_last()); + } else { + return result == 1 ? true : false; + } +} + +/// Count the number of unique commits between two commit objects. +/// +/// There is no need for branches containing the commits to have any upstream relationship, +/// but it helps to think of one as a branch and the other as its upstream, the ahead and +/// behind values will be what git would report for the branches. +List aheadBehind({ + required Pointer repoPointer, + required Pointer localPointer, + required Pointer upstreamPointer, +}) { + final ahead = calloc(); + final behind = calloc(); + + libgit2.git_graph_ahead_behind( + ahead, + behind, + repoPointer, + localPointer, + upstreamPointer, + ); + + return [ahead.value, behind.value]; +} diff --git a/lib/src/odb.dart b/lib/src/odb.dart index 1cba960..5d4fe46 100644 --- a/lib/src/odb.dart +++ b/lib/src/odb.dart @@ -1,7 +1,5 @@ import 'dart:ffi'; import 'package:libgit2dart/libgit2dart.dart'; -import 'package:libgit2dart/src/git_types.dart'; - import 'bindings/libgit2_bindings.dart'; import 'bindings/odb.dart' as bindings; import 'oid.dart'; diff --git a/lib/src/repository.dart b/lib/src/repository.dart index 145dead..35eb2f8 100644 --- a/lib/src/repository.dart +++ b/lib/src/repository.dart @@ -12,6 +12,7 @@ import 'bindings/reset.dart' as reset_bindings; import 'bindings/diff.dart' as diff_bindings; import 'bindings/stash.dart' as stash_bindings; import 'bindings/attr.dart' as attr_bindings; +import 'bindings/graph.dart' as graph_bindings; import 'branch.dart'; import 'commit.dart'; import 'config.dart'; @@ -1181,4 +1182,40 @@ class Repository { force: force, ); } + + /// Checks if a commit is the descendant of another commit. + /// + /// Note that a commit is not considered a descendant of itself, in contrast to + /// `git merge-base --is-ancestor`. + /// + /// Throws a [LibGit2Error] if error occured. + bool descendantOf({required String commitSHA, required String ancestorSHA}) { + final commit = Oid.fromSHA(repo: this, sha: commitSHA); + final ancestor = Oid.fromSHA(repo: this, sha: ancestorSHA); + + return graph_bindings.descendantOf( + repoPointer: _repoPointer, + commitPointer: commit.pointer, + ancestorPointer: ancestor.pointer, + ); + } + + /// Returns list with the `ahead` and `behind` number of unique commits respectively. + /// + /// There is no need for branches containing the commits to have any upstream relationship, + /// but it helps to think of one as a branch and the other as its upstream, the ahead and + /// behind values will be what git would report for the branches. + List aheadBehind({ + required String localSHA, + required String upstreamSHA, + }) { + final local = Oid.fromSHA(repo: this, sha: localSHA); + final upstream = Oid.fromSHA(repo: this, sha: upstreamSHA); + + return graph_bindings.aheadBehind( + repoPointer: _repoPointer, + localPointer: local.pointer, + upstreamPointer: upstream.pointer, + ); + } } diff --git a/test/repository_test.dart b/test/repository_test.dart index 0b1d82b..18606d0 100644 --- a/test/repository_test.dart +++ b/test/repository_test.dart @@ -251,5 +251,58 @@ void main() { expect(repo.getAttribute(path: 'file.jpg', name: 'text'), false); expect(repo.getAttribute(path: 'file.sh', name: 'eol'), 'lf'); }); + + test('checks if commit is a descendant of another commit', () { + final commit1 = repo['821ed6e8'] as Commit; + final commit2 = repo['78b8bf12'] as Commit; + + expect( + repo.descendantOf( + commitSHA: commit1.id.sha, + ancestorSHA: commit2.id.sha, + ), + true, + ); + + expect( + repo.descendantOf( + commitSHA: commit1.id.sha, + ancestorSHA: commit1.id.sha, + ), + false, + ); + + expect( + repo.descendantOf( + commitSHA: commit2.id.sha, + ancestorSHA: commit1.id.sha, + ), + false, + ); + + commit1.free(); + commit2.free(); + }); + + test('returns number of ahead behind commits', () { + final commit1 = repo['821ed6e8'] as Commit; + final commit2 = repo['c68ff54a'] as Commit; + + expect( + repo.aheadBehind(localSHA: commit1.id.sha, upstreamSHA: commit2.id.sha), + [4, 0], + ); + expect( + repo.aheadBehind(localSHA: commit2.id.sha, upstreamSHA: commit1.id.sha), + [0, 4], + ); + expect( + repo.aheadBehind(localSHA: commit1.id.sha, upstreamSHA: commit1.id.sha), + [0, 0], + ); + + commit1.free(); + commit2.free(); + }); }); }