From 75687c469c0821bbcf3dce1e2f82e1537dd78059 Mon Sep 17 00:00:00 2001 From: Aleksey Kulikov Date: Fri, 17 Jun 2022 10:16:43 +0300 Subject: [PATCH 1/2] chore: upgrade dependencies --- pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 5d564d5..d28b846 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,13 +13,13 @@ environment: dependencies: args: ^2.3.0 equatable: ^2.0.3 - ffi: ^1.1.2 + ffi: ^2.0.0 meta: ^1.7.0 path: ^1.8.1 pub_cache: ^0.3.1 dev_dependencies: - ffigen: ^5.0.0 + ffigen: ^6.0.1 lints: ^2.0.0 test: ^1.20.0 From d113af44b5fe4110a689245578f952e3755ddc5a Mon Sep 17 00:00:00 2001 From: Aleksey Kulikov Date: Fri, 17 Jun 2022 12:43:54 +0300 Subject: [PATCH 2/2] feat(commit): add ability to pass options to `revert(...)` and `revertTo(...)` API methods (#67) - select parent to revert to for merge commits - merge options - checkout options --- lib/src/bindings/commit.dart | 49 +++++++++++++- lib/src/commit.dart | 48 +++++++++++++- test/commit_test.dart | 122 +++++++++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+), 3 deletions(-) diff --git a/lib/src/bindings/commit.dart b/lib/src/bindings/commit.dart index f4a97c6..70a66ac 100644 --- a/lib/src/bindings/commit.dart +++ b/lib/src/bindings/commit.dart @@ -384,8 +384,48 @@ Pointer tree(Pointer commit) { void revert({ required Pointer repoPointer, required Pointer commitPointer, + required int mainline, + int? mergeFavor, + int? mergeFlags, + int? mergeFileFlags, + int? checkoutStrategy, + String? checkoutDirectory, + List? checkoutPaths, }) { - final error = libgit2.git_revert(repoPointer, commitPointer, nullptr); + final opts = calloc(); + libgit2.git_revert_options_init(opts, GIT_REVERT_OPTIONS_VERSION); + + opts.ref.mainline = mainline; + + if (mergeFavor != null) opts.ref.merge_opts.file_favor = mergeFavor; + if (mergeFlags != null) opts.ref.merge_opts.flags = mergeFlags; + if (mergeFileFlags != null) opts.ref.merge_opts.file_flags = mergeFileFlags; + + if (checkoutStrategy != null) { + opts.ref.checkout_opts.checkout_strategy = checkoutStrategy; + } + if (checkoutDirectory != null) { + opts.ref.checkout_opts.target_directory = checkoutDirectory.toChar(); + } + var pathPointers = >[]; + Pointer> strArray = nullptr; + if (checkoutPaths != null) { + pathPointers = checkoutPaths.map((e) => e.toChar()).toList(); + strArray = calloc(checkoutPaths.length); + for (var i = 0; i < checkoutPaths.length; i++) { + strArray[i] = pathPointers[i]; + } + opts.ref.checkout_opts.paths.strings = strArray; + opts.ref.checkout_opts.paths.count = checkoutPaths.length; + } + + final error = libgit2.git_revert(repoPointer, commitPointer, opts); + + for (final p in pathPointers) { + calloc.free(p); + } + calloc.free(strArray); + calloc.free(opts); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); @@ -403,11 +443,18 @@ Pointer revertCommit({ required Pointer revertCommitPointer, required Pointer ourCommitPointer, required int mainline, + int? mergeFavor, + int? mergeFlags, + int? mergeFileFlags, }) { final out = calloc>(); final opts = calloc(); libgit2.git_merge_options_init(opts, GIT_MERGE_OPTIONS_VERSION); + if (mergeFavor != null) opts.ref.file_favor = mergeFavor; + if (mergeFlags != null) opts.ref.flags = mergeFlags; + if (mergeFileFlags != null) opts.ref.file_flags = mergeFileFlags; + final error = libgit2.git_revert_commit( out, repoPointer, diff --git a/lib/src/commit.dart b/lib/src/commit.dart index 9fbb59e..0e399ff 100644 --- a/lib/src/commit.dart +++ b/lib/src/commit.dart @@ -189,23 +189,64 @@ class Commit extends Equatable { /// Reverts commit, producing changes in the index and working directory. /// + /// [mainline] is parent of the commit if it is a merge (i.e. 1, 2, etc.). + /// + /// [mergeFavor] is one of the optional [GitMergeFileFavor] flags for + /// handling conflicting content. + /// + /// [mergeFlags] is optional combination of [GitMergeFlag] flags. + /// + /// [mergeFileFlags] is optional combination of [GitMergeFileFlag] flags. + /// + /// [checkoutStrategy] is optional combination of [GitCheckout] flags. + /// + /// [checkoutDirectory] is optional alternative checkout path to workdir. + /// + /// [checkoutPaths] is optional list of files to checkout (by default all + /// paths are processed). + /// /// Throws a [LibGit2Error] if error occured. - void revert() { + void revert({ + int mainline = 0, + GitMergeFileFavor? mergeFavor, + Set? mergeFlags, + Set? mergeFileFlags, + Set? checkoutStrategy, + String? checkoutDirectory, + List? checkoutPaths, + }) { bindings.revert( repoPointer: bindings.owner(_commitPointer), commitPointer: _commitPointer, + mainline: mainline, + mergeFavor: mergeFavor?.value, + mergeFlags: mergeFlags?.fold(0, (acc, e) => acc! | e.value), + mergeFileFlags: mergeFileFlags?.fold(0, (acc, e) => acc! | e.value), + checkoutStrategy: checkoutStrategy?.fold(0, (acc, e) => acc! | e.value), + checkoutDirectory: checkoutDirectory, + checkoutPaths: checkoutPaths, ); } /// Reverts commit against provided [commit], producing an index that /// reflects the result of the revert. /// - /// [mainline] is parent of the commit if it is a merge (i.e. 1, 2). + /// [mainline] is parent of the commit if it is a merge (i.e. 1, 2, etc.). + /// + /// [mergeFavor] is one of the optional [GitMergeFileFavor] flags for + /// handling conflicting content. + /// + /// [mergeFlags] is optional combination of [GitMergeFlag] flags. + /// + /// [mergeFileFlags] is optional combination of [GitMergeFileFlag] flags. /// /// Throws a [LibGit2Error] if error occured. Index revertTo({ required Commit commit, int mainline = 0, + GitMergeFileFavor? mergeFavor, + Set? mergeFlags, + Set? mergeFileFlags, }) { return Index( bindings.revertCommit( @@ -213,6 +254,9 @@ class Commit extends Equatable { revertCommitPointer: _commitPointer, ourCommitPointer: commit.pointer, mainline: mainline, + mergeFavor: mergeFavor?.value, + mergeFlags: mergeFlags?.fold(0, (acc, e) => acc! | e.value), + mergeFileFlags: mergeFileFlags?.fold(0, (acc, e) => acc! | e.value), ), ); } diff --git a/test/commit_test.dart b/test/commit_test.dart index 7e95471..a68f9c0 100644 --- a/test/commit_test.dart +++ b/test/commit_test.dart @@ -67,6 +67,109 @@ void main() { expect(file.existsSync(), false); }); + test('reverts merge commit to provided parent', () { + const masterContents = 'master contents'; + final file = File(p.join(repo.workdir, 'another_feature_file')) + ..createSync() + ..writeAsStringSync(masterContents); + + repo.index.add('another_feature_file'); + repo.index.write(); + + // Creating commit on 'master' branch with file contents conflicting to + // 'feature' branch. + final masterTip = Commit.create( + repo: repo, + updateRef: 'HEAD', + message: 'master commit\n', + author: author, + committer: committer, + tree: Tree.lookup(repo: repo, oid: repo.index.writeTree()), + parents: [Commit.lookup(repo: repo, oid: repo.head.target)], + ); + + // Switching to 'feature' branch. + Checkout.reference(repo: repo, name: 'refs/heads/feature'); + repo.setHead('refs/heads/feature'); + + file.writeAsStringSync('feature contents'); + + repo.index.add('another_feature_file'); + repo.index.write(); + + // Creating commit on 'feature' branch with file contents conflicting to + // 'master' branch. + final featureTip = Commit.create( + repo: repo, + updateRef: 'HEAD', + message: 'feature commit\n', + author: author, + committer: committer, + tree: Tree.lookup(repo: repo, oid: repo.index.writeTree()), + parents: [Commit.lookup(repo: repo, oid: repo.head.target)], + ); + + // Merging master branch. + Merge.commit( + repo: repo, + commit: AnnotatedCommit.lookup( + repo: repo, + oid: Oid.fromSHA(repo: repo, sha: masterTip.sha), + ), + ); + + expect(repo.index.hasConflicts, true); + + // "Resolving" conflict. + repo.index.updateAll(['another_feature_file']); + repo.index.write(); + repo.stateCleanup(); + + // Creating merge commit. + final mergeOid = Commit.create( + repo: repo, + updateRef: 'HEAD', + message: 'merge commit\n', + author: author, + committer: committer, + tree: Tree.lookup(repo: repo, oid: repo.index.writeTree()), + parents: [ + Commit.lookup(repo: repo, oid: featureTip), + Commit.lookup(repo: repo, oid: masterTip), + ], + ); + + final mergeCommit = Commit.lookup(repo: repo, oid: mergeOid); + mergeCommit.revert(mainline: 2); + + expect(file.readAsStringSync(), masterContents); + }); + + test('reverts commit with provided merge options and checkout options', () { + final commit = Commit.lookup(repo: repo, oid: repo['821ed6e']); + final file = File(p.join(repo.workdir, 'dir', 'dir_file.txt')); + expect(repo.index.find('dir/dir_file.txt'), true); + expect(file.existsSync(), true); + + commit.revert( + mergeFavor: GitMergeFileFavor.ours, + mergeFlags: {GitMergeFlag.noRecursive, GitMergeFlag.skipREUC}, + mergeFileFlags: { + GitMergeFileFlag.ignoreWhitespace, + GitMergeFileFlag.styleZdiff3 + }, + checkoutStrategy: { + GitCheckout.force, + GitCheckout.conflictStyleMerge, + }, + checkoutDirectory: repo.workdir, + checkoutPaths: ['dir/dir_file.txt'], + ); + + expect(repo.index.find('dir/dir_file.txt'), false); + expect(file.existsSync(), false); + }); + test('throws when trying to revert and error occurs', () { expect(() => Commit(nullptr).revert(), throwsA(isA())); }); @@ -84,6 +187,25 @@ void main() { expect(file.existsSync(), true); }); + test('reverts commit to provided commit with provided merge options', () { + final file = File(p.join(repo.workdir, 'dir', 'dir_file.txt')); + expect(repo.index.find('dir/dir_file.txt'), true); + expect(file.existsSync(), true); + + final from = Commit.lookup(repo: repo, oid: repo['821ed6e']); + final revertIndex = from.revertTo( + commit: Commit.lookup(repo: repo, oid: repo['78b8bf1']), + mergeFavor: GitMergeFileFavor.ours, + mergeFlags: {GitMergeFlag.noRecursive, GitMergeFlag.skipREUC}, + mergeFileFlags: { + GitMergeFileFlag.ignoreWhitespace, + GitMergeFileFlag.styleZdiff3 + }, + ); + expect(revertIndex.find('dir/dir_file.txt'), false); + expect(file.existsSync(), true); + }); + test('throws when trying to revert commit and error occurs', () { final nullCommit = Commit(nullptr); expect(