feat(merge): add bindings and api for merge analysis

This commit is contained in:
Aleksey Kulikov 2021-09-08 16:03:35 +03:00
parent 1f2d00b177
commit 223cc7cc14
5 changed files with 231 additions and 13 deletions

View file

@ -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.

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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;
}
}