diff --git a/lib/src/bindings/rebase.dart b/lib/src/bindings/rebase.dart index b41a771..853459c 100644 --- a/lib/src/bindings/rebase.dart +++ b/lib/src/bindings/rebase.dart @@ -50,11 +50,45 @@ Pointer init({ } } +/// Opens an existing rebase that was previously started by either an +/// invocation of [init] or by another client. +/// +/// Throws a [LibGit2Error] if error occured. +Pointer open(Pointer repo) { + final out = calloc>(); + final opts = calloc(); + libgit2.git_rebase_options_init(opts, GIT_REBASE_OPTIONS_VERSION); + + final error = libgit2.git_rebase_open(out, repo, opts); + + if (error < 0) { + calloc.free(out); + throw LibGit2Error(libgit2.git_error_last()); + } else { + return out.value; + } +} + /// Gets the count of rebase operations that are to be applied. int operationsCount(Pointer rebase) { return libgit2.git_rebase_operation_entrycount(rebase); } +/// Gets the rebase operation specified by the given index. +Pointer getOperationByIndex({ + required Pointer rebase, + required int index, +}) { + return libgit2.git_rebase_operation_byindex(rebase, index); +} + +/// Gets the index of the rebase operation that is currently being applied. If +/// the first operation has not yet been applied (because you have called [init] +/// but not yet [next]) then this returns `-1`. +int currentOperation(Pointer rebase) { + return libgit2.git_rebase_operation_current(rebase); +} + /// Performs the next rebase operation and returns the information about it. /// If the operation is one that applies a patch (which is any operation except /// GIT_REBASE_OPERATION_EXEC) then the patch will be applied and the index and @@ -75,7 +109,7 @@ Pointer next(Pointer rebase) { } /// Commits the current patch. You must have resolved any conflicts that were -/// introduced during the patch application from the `next()` invocation. +/// introduced during the patch application from the [next] invocation. /// /// Throws a [LibGit2Error] if error occured. void commit({ @@ -113,5 +147,24 @@ void finish(Pointer rebase) => /// working directory to their state before rebase began. void abort(Pointer rebase) => libgit2.git_rebase_abort(rebase); +/// Gets the original HEAD id for merge rebases. +Pointer origHeadOid(Pointer rebase) => + libgit2.git_rebase_orig_head_id(rebase); + +/// Gets the original HEAD ref name for merge rebases. +String origHeadName(Pointer rebase) { + final result = libgit2.git_rebase_orig_head_name(rebase); + return result == nullptr ? '' : result.cast().toDartString(); +} + +/// Gets the onto id for merge rebases. +Pointer ontoOid(Pointer rebase) => + libgit2.git_rebase_onto_id(rebase); + +/// Gets the onto ref name for merge rebases. +String ontoName(Pointer rebase) { + return libgit2.git_rebase_onto_name(rebase).cast().toDartString(); +} + /// Free memory allocated for rebase object. void free(Pointer rebase) => libgit2.git_rebase_free(rebase); diff --git a/lib/src/rebase.dart b/lib/src/rebase.dart index f5280f6..f9fda60 100644 --- a/lib/src/rebase.dart +++ b/lib/src/rebase.dart @@ -34,12 +34,62 @@ class Rebase { ); } + /// Opens an existing rebase that was previously started by either an + /// invocation of [Rebase.init] or by another client. + /// + /// **IMPORTANT**: Should be freed to release allocated memory. + /// + /// Throws a [LibGit2Error] if error occured. + Rebase.open(Repository repo) { + _rebasePointer = bindings.open(repo.pointer); + } + /// Pointer to memory address for allocated rebase object. late final Pointer _rebasePointer; - /// Number of rebase operations that are to be applied. - int get operationsCount { - return bindings.operationsCount(_rebasePointer); + /// List of operations that are to be applied. + List get operations { + final result = []; + final operationsCount = bindings.operationsCount(_rebasePointer); + + for (var i = 0; i < operationsCount; i++) { + final operation = bindings.getOperationByIndex( + rebase: _rebasePointer, + index: i, + ); + result.add(RebaseOperation(operation)); + } + + return result; + } + + /// Index of the rebase operation that is currently being applied. If the + /// first operation has not yet been applied (because you have called + /// [Rebase.init] but not yet [next]) then this returns `-1`. + int get currentOperation { + return bindings.currentOperation(_rebasePointer); + } + + /// Original HEAD oid for merge rebases. + Oid get origHeadOid { + return Oid.fromRaw(bindings.origHeadOid(_rebasePointer).ref); + } + + /// Original HEAD ref name for merge rebases. + /// + /// Returns empty string if no information available. + String get origHeadName { + return bindings.origHeadName(_rebasePointer); + } + + /// Onto oid for merge rebases. + Oid get ontoOid { + return Oid.fromRaw(bindings.ontoOid(_rebasePointer).ref); + } + + /// Onto ref name for merge rebases. + String get ontoName { + return bindings.ontoName(_rebasePointer); } /// Performs the next rebase operation and returns the information about it. diff --git a/test/rebase_test.dart b/test/rebase_test.dart index c683800..91c2ecc 100644 --- a/test/rebase_test.dart +++ b/test/rebase_test.dart @@ -32,9 +32,15 @@ void main() { time: 1234, ); final master = repo.lookupReference('refs/heads/master'); - final branchHead = AnnotatedCommit.lookup(repo: repo, oid: master.target); + final branchHead = AnnotatedCommit.fromReference( + repo: repo, + reference: master, + ); final feature = repo.lookupReference('refs/heads/feature'); - final ontoHead = AnnotatedCommit.lookup(repo: repo, oid: feature.target); + final ontoHead = AnnotatedCommit.fromReference( + repo: repo, + reference: feature, + ); repo.checkout(refName: feature.name); expect(() => repo.index['.gitignore'], throwsA(isA())); @@ -45,10 +51,15 @@ void main() { onto: ontoHead, ); - final operationsCount = rebase.operationsCount; - expect(operationsCount, 3); + expect(rebase.origHeadOid, master.target); + expect(rebase.origHeadName, 'refs/heads/master'); + expect(rebase.ontoOid, feature.target); + expect(rebase.ontoName, 'feature'); - for (var i = 0; i < operationsCount; i++) { + final operations = rebase.operations; + expect(operations.length, 3); + + for (var i = 0; i < operations.length; i++) { final operation = rebase.next(); expect(operation.type, GitRebaseOperation.pick); expect(operation.oid.sha, shas[i]); @@ -86,10 +97,18 @@ void main() { onto: ontoHead, ); - final operationsCount = rebase.operationsCount; - expect(operationsCount, 3); + expect( + rebase.origHeadOid.sha, + '14905459d775f3f56a39ebc2ff081163f7da3529', + ); + expect(rebase.origHeadName, 'refs/heads/master'); + expect(rebase.ontoOid, feature.target); + expect(rebase.ontoName, '2ee89b2f7124b8e4632bc6a20774a90b795245e4'); - for (var i = 0; i < operationsCount; i++) { + final operations = rebase.operations; + expect(operations.length, 3); + + for (var i = 0; i < operations.length; i++) { final operation = rebase.next(); expect(operation.type, GitRebaseOperation.pick); expect(operation.oid.sha, shas[i]); @@ -130,11 +149,19 @@ void main() { upstream: upstream, ); - final operationsCount = rebase.operationsCount; - expect(operationsCount, 1); + expect(rebase.origHeadOid, master.target); + expect(rebase.origHeadName, ''); + expect(rebase.ontoOid.sha, shas[1]); + expect(rebase.ontoName, '821ed6e80627b8769d170a293862f9fc60825226'); - for (var i = 0; i < operationsCount; i++) { + final operations = rebase.operations; + expect(operations.length, 1); + + for (final operation in operations) { + expect(rebase.currentOperation, -1); + expect(operation.type, GitRebaseOperation.pick); rebase.next(); + expect(rebase.currentOperation, 0); rebase.commit(committer: signature); } @@ -173,7 +200,7 @@ void main() { branch: branchHead, onto: ontoHead, ); - expect(rebase.operationsCount, 1); + expect(rebase.operations.length, 1); rebase.next(); expect(repo.status['conflict_file'], {GitStatus.conflicted}); @@ -210,7 +237,7 @@ void main() { branch: branchHead, onto: ontoHead, ); - expect(rebase.operationsCount, 1); + expect(rebase.operations.length, 1); rebase.next(); // repo now have conflicts expect(() => rebase.next(), throwsA(isA())); @@ -236,7 +263,7 @@ void main() { branch: branchHead, onto: ontoHead, ); - expect(rebase.operationsCount, 1); + expect(rebase.operations.length, 1); rebase.next(); expect(repo.status['conflict_file'], {GitStatus.conflicted}); @@ -252,5 +279,28 @@ void main() { conflict.free(); master.free(); }); + + test('opens an existing rebase', () { + final feature = repo.lookupReference('refs/heads/feature'); + final ontoHead = AnnotatedCommit.lookup(repo: repo, oid: feature.target); + + final rebase = Rebase.init( + repo: repo, + onto: ontoHead, + ); + expect(rebase.operations.length, 3); + + final openRebase = Rebase.open(repo); + expect(openRebase.operations.length, 3); + + openRebase.free(); + rebase.free(); + ontoHead.free(); + feature.free(); + }); + + test('throws when trying to open an existing rebase but there is none', () { + expect(() => Rebase.open(repo), throwsA(isA())); + }); }); }