mirror of
https://github.com/SkinnyMind/libgit2dart.git
synced 2025-05-04 20:29:08 -04:00
feat(merge): add bindings and api for merge analysis
This commit is contained in:
parent
1f2d00b177
commit
223cc7cc14
5 changed files with 231 additions and 13 deletions
|
@ -39,6 +39,35 @@ Pointer<git_commit> lookupPrefix(
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a git_annotated_commit from the given commit id. The resulting git_annotated_commit
|
||||
/// must be freed with git_annotated_commit_free.
|
||||
///
|
||||
/// An annotated commit contains information about how it was looked up, which may be useful
|
||||
/// for functions like merge or rebase to provide context to the operation. For example, conflict
|
||||
/// files will include the name of the source or target branches being merged. It is therefore
|
||||
/// preferable to use the most specific function (eg git_annotated_commit_from_ref) instead of
|
||||
/// this one when that data is known.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Pointer<Pointer<git_annotated_commit>> annotatedLookup(
|
||||
Pointer<git_repository> repo,
|
||||
Pointer<git_oid> id,
|
||||
) {
|
||||
final out = calloc<Pointer<git_annotated_commit>>();
|
||||
final error = libgit2.git_annotated_commit_lookup(out, repo, id);
|
||||
|
||||
if (error < 0) {
|
||||
throw LibGit2Error(libgit2.git_error_last());
|
||||
} else {
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
/// Frees a git_annotated_commit.
|
||||
void annotatedFree(Pointer<git_annotated_commit> commit) {
|
||||
libgit2.git_annotated_commit_free(commit);
|
||||
}
|
||||
|
||||
/// Create new commit in the repository.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
|
|
|
@ -21,3 +21,36 @@ Pointer<git_oid> mergeBase(
|
|||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
/// Analyzes the given branch(es) and determines the opportunities for merging them
|
||||
/// into a reference.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
List<int> analysis(
|
||||
Pointer<git_repository> repo,
|
||||
Pointer<git_reference> ourRef,
|
||||
Pointer<Pointer<git_annotated_commit>> theirHead,
|
||||
int theirHeadsLen,
|
||||
) {
|
||||
final analysisOut = calloc<Int32>();
|
||||
final preferenceOut = calloc<Int32>();
|
||||
final error = libgit2.git_merge_analysis_for_ref(
|
||||
analysisOut,
|
||||
preferenceOut,
|
||||
repo,
|
||||
ourRef,
|
||||
theirHead,
|
||||
theirHeadsLen,
|
||||
);
|
||||
var result = <int>[];
|
||||
|
||||
if (error < 0) {
|
||||
throw LibGit2Error(libgit2.git_error_last());
|
||||
} else {
|
||||
result.add(analysisOut.value);
|
||||
result.add(preferenceOut.value);
|
||||
calloc.free(analysisOut);
|
||||
calloc.free(preferenceOut);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -147,3 +147,51 @@ class GitStatus {
|
|||
|
||||
int get value => _value;
|
||||
}
|
||||
|
||||
/// The results of `mergeAnalysis` indicate the merge opportunities.
|
||||
class GitMergeAnalysis {
|
||||
const GitMergeAnalysis._(this._value);
|
||||
final int _value;
|
||||
|
||||
/// No merge is possible (unused).
|
||||
static const none = GitMergeAnalysis._(0);
|
||||
|
||||
/// A "normal" merge; both HEAD and the given merge input have diverged
|
||||
/// from their common ancestor. The divergent commits must be merged.
|
||||
static const normal = GitMergeAnalysis._(1);
|
||||
|
||||
/// All given merge inputs are reachable from HEAD, meaning the
|
||||
/// repository is up-to-date and no merge needs to be performed.
|
||||
static const upToDate = GitMergeAnalysis._(2);
|
||||
|
||||
/// The given merge input is a fast-forward from HEAD and no merge
|
||||
/// needs to be performed. Instead, the client can check out the
|
||||
/// given merge input.
|
||||
static const fastForward = GitMergeAnalysis._(4);
|
||||
|
||||
/// The HEAD of the current repository is "unborn" and does not point to
|
||||
/// a valid commit. No merge can be performed, but the caller may wish
|
||||
/// to simply set HEAD to the target commit(s).
|
||||
static const unborn = GitMergeAnalysis._(8);
|
||||
|
||||
int get value => _value;
|
||||
}
|
||||
|
||||
/// The user's stated preference for merges.
|
||||
class GitMergePreference {
|
||||
const GitMergePreference._(this._value);
|
||||
final int _value;
|
||||
|
||||
/// No configuration was found that suggests a preferred behavior for merge.
|
||||
static const none = GitMergePreference._(0);
|
||||
|
||||
/// There is a `merge.ff=false` configuration setting, suggesting that
|
||||
/// the user does not want to allow a fast-forward merge.
|
||||
static const noFastForward = GitMergePreference._(1);
|
||||
|
||||
/// There is a `merge.ff=only` configuration setting, suggesting that
|
||||
/// the user only wants fast-forward merges.
|
||||
static const fastForwardOnly = GitMergePreference._(2);
|
||||
|
||||
int get value => _value;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'bindings/repository.dart' as bindings;
|
|||
import 'bindings/merge.dart' as merge_bindings;
|
||||
import 'bindings/object.dart' as object_bindings;
|
||||
import 'bindings/status.dart' as status_bindings;
|
||||
import 'bindings/commit.dart' as commit_bindings;
|
||||
import 'branch.dart';
|
||||
import 'commit.dart';
|
||||
import 'config.dart';
|
||||
|
@ -399,19 +400,6 @@ class Repository {
|
|||
/// Throws a [LibGit2Error] if error occured.
|
||||
RevSpec revParse(String spec) => RevParse.range(this, spec);
|
||||
|
||||
/// Finds a merge base between two commits.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Oid mergeBase(String one, String two) {
|
||||
final oidOne = Oid.fromSHA(this, one);
|
||||
final oidTwo = Oid.fromSHA(this, two);
|
||||
return Oid(merge_bindings.mergeBase(
|
||||
_repoPointer,
|
||||
oidOne.pointer,
|
||||
oidTwo.pointer,
|
||||
));
|
||||
}
|
||||
|
||||
/// Creates a new blob from a [content] string and writes it to ODB.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
|
@ -494,4 +482,39 @@ class Repository {
|
|||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
int statusFile(String path) => status_bindings.file(_repoPointer, path);
|
||||
|
||||
/// Finds a merge base between two commits.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Oid mergeBase(String one, String two) {
|
||||
final oidOne = Oid.fromSHA(this, one);
|
||||
final oidTwo = Oid.fromSHA(this, two);
|
||||
return Oid(merge_bindings.mergeBase(
|
||||
_repoPointer,
|
||||
oidOne.pointer,
|
||||
oidTwo.pointer,
|
||||
));
|
||||
}
|
||||
|
||||
/// Analyzes the given branch(es) and determines the opportunities for merging them
|
||||
/// into a reference (default is 'HEAD').
|
||||
///
|
||||
/// Returns list with analysis result and preference for fast forward merge values
|
||||
/// respectively.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
List<int> mergeAnalysis(Oid theirHead, [String ourRef = 'HEAD']) {
|
||||
final ref = references[ourRef];
|
||||
final head = commit_bindings.annotatedLookup(
|
||||
_repoPointer,
|
||||
theirHead.pointer,
|
||||
);
|
||||
|
||||
final result = merge_bindings.analysis(_repoPointer, ref.pointer, head, 1);
|
||||
|
||||
commit_bindings.annotatedFree(head.value);
|
||||
ref.free();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
85
test/merge_test.dart
Normal file
85
test/merge_test.dart
Normal file
|
@ -0,0 +1,85 @@
|
|||
import 'dart:io';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:libgit2dart/libgit2dart.dart';
|
||||
import 'helpers/util.dart';
|
||||
|
||||
void main() {
|
||||
late Repository repo;
|
||||
final tmpDir = '${Directory.systemTemp.path}/merge_testrepo/';
|
||||
|
||||
setUp(() async {
|
||||
if (await Directory(tmpDir).exists()) {
|
||||
await Directory(tmpDir).delete(recursive: true);
|
||||
}
|
||||
await copyRepo(
|
||||
from: Directory('test/assets/testrepo/'),
|
||||
to: await Directory(tmpDir).create(),
|
||||
);
|
||||
repo = Repository.open(tmpDir);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
repo.free();
|
||||
await Directory(tmpDir).delete(recursive: true);
|
||||
});
|
||||
|
||||
group('Merge', () {
|
||||
group('analysis', () {
|
||||
test('is up to date when no reference is provided', () {
|
||||
final commit =
|
||||
repo['c68ff54aabf660fcdd9a2838d401583fe31249e3'] as Commit;
|
||||
final result = repo.mergeAnalysis(commit.id);
|
||||
|
||||
expect(result[0], GitMergeAnalysis.upToDate.value);
|
||||
expect(repo.status, isEmpty);
|
||||
|
||||
commit.free();
|
||||
});
|
||||
|
||||
test('is up to date for provided ref', () {
|
||||
final commit =
|
||||
repo['c68ff54aabf660fcdd9a2838d401583fe31249e3'] as Commit;
|
||||
final result = repo.mergeAnalysis(commit.id, 'refs/tags/v0.1');
|
||||
|
||||
expect(result[0], GitMergeAnalysis.upToDate.value);
|
||||
expect(repo.status, isEmpty);
|
||||
|
||||
commit.free();
|
||||
});
|
||||
|
||||
test('is fast forward', () {
|
||||
final theirHead =
|
||||
repo['6cbc22e509d72758ab4c8d9f287ea846b90c448b'] as Commit;
|
||||
final ffCommit =
|
||||
repo['f17d0d48eae3aa08cecf29128a35e310c97b3521'] as Commit;
|
||||
final ffBranch = repo.branches.create(
|
||||
name: 'ff-branch',
|
||||
target: ffCommit,
|
||||
);
|
||||
|
||||
final result = repo.mergeAnalysis(theirHead.id, ffBranch.name);
|
||||
|
||||
expect(
|
||||
result[0],
|
||||
GitMergeAnalysis.fastForward.value + GitMergeAnalysis.normal.value,
|
||||
);
|
||||
expect(repo.status, isEmpty);
|
||||
|
||||
ffBranch.free();
|
||||
ffCommit.free();
|
||||
theirHead.free();
|
||||
});
|
||||
|
||||
test('is not fast forward and there is no conflicts', () {
|
||||
final commit =
|
||||
repo['5aecfa0fb97eadaac050ccb99f03c3fb65460ad4'] as Commit;
|
||||
final result = repo.mergeAnalysis(commit.id);
|
||||
|
||||
expect(result[0], GitMergeAnalysis.normal.value);
|
||||
expect(repo.status, isEmpty);
|
||||
|
||||
commit.free();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue