diff --git a/lib/src/bindings/merge.dart b/lib/src/bindings/merge.dart index 88229bf..2e16d11 100644 --- a/lib/src/bindings/merge.dart +++ b/lib/src/bindings/merge.dart @@ -186,30 +186,39 @@ String mergeFile({ libgit2.git_merge_file_input_init(theirsC, GIT_MERGE_FILE_INPUT_VERSION); ancestorC.ref.ptr = ancestor.toChar(); ancestorC.ref.size = ancestor.length; + Pointer ancestorLabelC = nullptr; oursC.ref.ptr = ours.toChar(); oursC.ref.size = ours.length; + Pointer oursLabelC = nullptr; theirsC.ref.ptr = theirs.toChar(); theirsC.ref.size = theirs.length; + Pointer theirsLabelC = nullptr; final opts = calloc(); libgit2.git_merge_file_options_init(opts, GIT_MERGE_FILE_OPTIONS_VERSION); opts.ref.favor = favor; opts.ref.flags = flags; if (ancestorLabel.isNotEmpty) { - opts.ref.ancestor_label = ancestorLabel.toChar(); + ancestorLabelC = ancestorLabel.toChar(); + opts.ref.ancestor_label = ancestorLabelC; } if (oursLabel.isNotEmpty) { - opts.ref.our_label = oursLabel.toChar(); + oursLabelC = oursLabel.toChar(); + opts.ref.our_label = oursLabelC; } if (theirsLabel.isNotEmpty) { - opts.ref.their_label = theirsLabel.toChar(); + theirsLabelC = theirsLabel.toChar(); + opts.ref.their_label = theirsLabelC; } libgit2.git_merge_file(out, ancestorC, oursC, theirsC, opts); calloc.free(ancestorC); + calloc.free(ancestorLabelC); calloc.free(oursC); + calloc.free(oursLabelC); calloc.free(theirsC); + calloc.free(theirsLabelC); calloc.free(opts); final result = out.ref.ptr.toDartString(length: out.ref.len); @@ -226,17 +235,43 @@ String mergeFile({ String mergeFileFromIndex({ required Pointer repoPointer, required Pointer? ancestorPointer, + required String ancestorLabel, required Pointer oursPointer, + required String oursLabel, required Pointer theirsPointer, + required String theirsLabel, + required int favor, + required int flags, }) { final out = calloc(); + final opts = calloc(); + Pointer ancestorLabelC = nullptr; + Pointer oursLabelC = nullptr; + Pointer theirsLabelC = nullptr; + + libgit2.git_merge_file_options_init(opts, GIT_MERGE_FILE_OPTIONS_VERSION); + opts.ref.favor = favor; + opts.ref.flags = flags; + if (ancestorLabel.isNotEmpty) { + ancestorLabelC = ancestorLabel.toChar(); + opts.ref.ancestor_label = ancestorLabelC; + } + if (oursLabel.isNotEmpty) { + oursLabelC = oursLabel.toChar(); + opts.ref.our_label = oursLabelC; + } + if (theirsLabel.isNotEmpty) { + theirsLabelC = theirsLabel.toChar(); + opts.ref.their_label = theirsLabelC; + } + final error = libgit2.git_merge_file_from_index( out, repoPointer, ancestorPointer ?? nullptr, oursPointer, theirsPointer, - nullptr, + opts, ); late final String result; @@ -244,6 +279,10 @@ String mergeFileFromIndex({ result = out.ref.ptr.toDartString(length: out.ref.len); } + calloc.free(ancestorLabelC); + calloc.free(oursLabelC); + calloc.free(theirsLabelC); + calloc.free(opts); calloc.free(out); if (error < 0) { diff --git a/lib/src/merge.dart b/lib/src/merge.dart index 015724b..da080e0 100644 --- a/lib/src/merge.dart +++ b/lib/src/merge.dart @@ -265,18 +265,43 @@ class Merge { /// given common [ancestor] as the baseline, producing a string that reflects /// the merge result containing possible conflicts. /// + /// [ancestorLabel] is optional label for the ancestor file side of the + /// conflict which will be prepended to labels in diff3-format merge files. + /// + /// [oursLabel] is optional label for our file side of the conflict which + /// will be prepended to labels in merge files. + /// + /// [theirsLabel] is optional label for their file side of the conflict which + /// will be prepended to labels in merge files. + /// + /// [favor] is one of the [GitMergeFileFavor] flags for handling conflicting + /// content. Defaults to [GitMergeFileFavor.normal]. + /// + /// [flags] is a combination of [GitMergeFileFlag] flags. Defaults to + /// [GitMergeFileFlag.defaults]. + /// /// Throws a [LibGit2Error] if error occured. static String fileFromIndex({ required Repository repo, required IndexEntry? ancestor, + String ancestorLabel = '', required IndexEntry ours, + String oursLabel = '', required IndexEntry theirs, + String theirsLabel = '', + GitMergeFileFavor favor = GitMergeFileFavor.normal, + Set flags = const {GitMergeFileFlag.defaults}, }) { return bindings.mergeFileFromIndex( repoPointer: repo.pointer, ancestorPointer: ancestor?.pointer, + ancestorLabel: ancestorLabel, oursPointer: ours.pointer, + oursLabel: oursLabel, theirsPointer: theirs.pointer, + theirsLabel: theirsLabel, + favor: favor.value, + flags: flags.fold(0, (acc, e) => acc | e.value), ); } diff --git a/test/merge_test.dart b/test/merge_test.dart index 6f3a276..4b35d29 100644 --- a/test/merge_test.dart +++ b/test/merge_test.dart @@ -168,31 +168,38 @@ Another feature edit expect(diff, diffExpected); }); - test('merges with provided merge flags and file flags', () { + test('merges with provided options', () { const diffExpected = """ -\<<<<<<< conflict_file -master conflict edit +\<<<<<<< ours +Feature edit on feature branch +||||||| ancestor +Feature edit ======= -conflict branch edit ->>>>>>> conflict_file +Another feature edit +>>>>>>> theirs """; + Checkout.reference(repo: repo, name: 'refs/heads/feature'); + repo.setHead('refs/heads/feature'); + Merge.commit( repo: repo, commit: AnnotatedCommit.lookup( repo: repo, - oid: Branch.lookup(repo: repo, name: 'conflict-branch').target, + oid: Branch.lookup(repo: repo, name: 'ancestor-conflict').target, ), - mergeFlags: {GitMergeFlag.noRecursive}, - fileFlags: {GitMergeFileFlag.ignoreWhitespaceEOL}, ); - final conflictedFile = repo.index.conflicts['conflict_file']!; + final conflictedFile = repo.index.conflicts['feature_file']!; final diff = Merge.fileFromIndex( repo: repo, - ancestor: null, + ancestor: conflictedFile.ancestor, + ancestorLabel: 'ancestor', ours: conflictedFile.our!, + oursLabel: 'ours', theirs: conflictedFile.their!, + theirsLabel: 'theirs', + flags: {GitMergeFileFlag.styleDiff3}, ); expect(diff, diffExpected);