mirror of
https://github.com/SkinnyMind/libgit2dart.git
synced 2025-05-05 04:39:07 -04:00
feat(describe): add bindings and api
This commit is contained in:
parent
c88b75b0fd
commit
caac6b2fd2
6 changed files with 409 additions and 4 deletions
158
lib/src/bindings/describe.dart
Normal file
158
lib/src/bindings/describe.dart
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
import 'dart:ffi';
|
||||||
|
import 'package:ffi/ffi.dart';
|
||||||
|
import 'libgit2_bindings.dart';
|
||||||
|
import '../error.dart';
|
||||||
|
import '../util.dart';
|
||||||
|
|
||||||
|
/// Describe a commit.
|
||||||
|
///
|
||||||
|
/// Perform the describe operation on the given committish object.
|
||||||
|
///
|
||||||
|
/// Returned object should be freed with `describeResultFree()` once no longer needed.
|
||||||
|
///
|
||||||
|
/// Throws a [LibGit2Error] if error occured.
|
||||||
|
Pointer<git_describe_result> commit({
|
||||||
|
required Pointer<git_commit> commitPointer,
|
||||||
|
int? maxCandidatesTags,
|
||||||
|
int? describeStrategy,
|
||||||
|
String? pattern,
|
||||||
|
bool? onlyFollowFirstParent,
|
||||||
|
bool? showCommitOidAsFallback,
|
||||||
|
}) {
|
||||||
|
final out = calloc<Pointer<git_describe_result>>();
|
||||||
|
final opts = _initOpts(
|
||||||
|
maxCandidatesTags: maxCandidatesTags,
|
||||||
|
describeStrategy: describeStrategy,
|
||||||
|
pattern: pattern,
|
||||||
|
onlyFollowFirstParent: onlyFollowFirstParent,
|
||||||
|
showCommitOidAsFallback: showCommitOidAsFallback,
|
||||||
|
);
|
||||||
|
|
||||||
|
final error = libgit2.git_describe_commit(out, commitPointer.cast(), opts);
|
||||||
|
|
||||||
|
calloc.free(opts);
|
||||||
|
|
||||||
|
if (error < 0) {
|
||||||
|
throw LibGit2Error(libgit2.git_error_last());
|
||||||
|
} else {
|
||||||
|
return out.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describe a commit.
|
||||||
|
///
|
||||||
|
/// Perform the describe operation on the current commit and the worktree.
|
||||||
|
/// After peforming describe on HEAD, a status is run and the description is
|
||||||
|
/// considered to be dirty if there are.
|
||||||
|
///
|
||||||
|
/// Throws a [LibGit2Error] if error occured.
|
||||||
|
Pointer<git_describe_result> workdir({
|
||||||
|
required Pointer<git_repository> repo,
|
||||||
|
int? maxCandidatesTags,
|
||||||
|
int? describeStrategy,
|
||||||
|
String? pattern,
|
||||||
|
bool? onlyFollowFirstParent,
|
||||||
|
bool? showCommitOidAsFallback,
|
||||||
|
}) {
|
||||||
|
final out = calloc<Pointer<git_describe_result>>();
|
||||||
|
final opts = _initOpts(
|
||||||
|
maxCandidatesTags: maxCandidatesTags,
|
||||||
|
describeStrategy: describeStrategy,
|
||||||
|
pattern: pattern,
|
||||||
|
onlyFollowFirstParent: onlyFollowFirstParent,
|
||||||
|
showCommitOidAsFallback: showCommitOidAsFallback,
|
||||||
|
);
|
||||||
|
|
||||||
|
final error = libgit2.git_describe_workdir(out, repo, opts);
|
||||||
|
|
||||||
|
calloc.free(opts);
|
||||||
|
|
||||||
|
if (error < 0) {
|
||||||
|
throw LibGit2Error(libgit2.git_error_last());
|
||||||
|
} else {
|
||||||
|
return out.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Print the describe result to a buffer.
|
||||||
|
///
|
||||||
|
/// Throws a [LibGit2Error] if error occured.
|
||||||
|
String format({
|
||||||
|
required Pointer<git_describe_result> describeResultPointer,
|
||||||
|
int? abbreviatedSize,
|
||||||
|
bool? alwaysUseLongFormat,
|
||||||
|
String? dirtySuffix,
|
||||||
|
}) {
|
||||||
|
final out = calloc<git_buf>(sizeOf<git_buf>());
|
||||||
|
final opts = calloc<git_describe_format_options>();
|
||||||
|
final optsError = libgit2.git_describe_format_options_init(
|
||||||
|
opts,
|
||||||
|
GIT_DESCRIBE_FORMAT_OPTIONS_VERSION,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (optsError < 0) {
|
||||||
|
throw LibGit2Error(libgit2.git_error_last());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (abbreviatedSize != null) {
|
||||||
|
opts.ref.abbreviated_size = abbreviatedSize;
|
||||||
|
}
|
||||||
|
if (alwaysUseLongFormat != null) {
|
||||||
|
opts.ref.always_use_long_format = alwaysUseLongFormat ? 1 : 0;
|
||||||
|
}
|
||||||
|
if (dirtySuffix != null) {
|
||||||
|
opts.ref.dirty_suffix = dirtySuffix.toNativeUtf8().cast<Int8>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final error = libgit2.git_describe_format(out, describeResultPointer, opts);
|
||||||
|
|
||||||
|
if (error < 0) {
|
||||||
|
throw LibGit2Error(libgit2.git_error_last());
|
||||||
|
} else {
|
||||||
|
final result = out.ref.ptr.cast<Utf8>().toDartString();
|
||||||
|
calloc.free(out);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Free the describe result.
|
||||||
|
void describeResultFree(Pointer<git_describe_result> result) {
|
||||||
|
libgit2.git_describe_result_free(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize git_describe_options structure.
|
||||||
|
Pointer<git_describe_options> _initOpts({
|
||||||
|
int? maxCandidatesTags,
|
||||||
|
int? describeStrategy,
|
||||||
|
String? pattern,
|
||||||
|
bool? onlyFollowFirstParent,
|
||||||
|
bool? showCommitOidAsFallback,
|
||||||
|
}) {
|
||||||
|
final opts = calloc<git_describe_options>();
|
||||||
|
final error = libgit2.git_describe_options_init(
|
||||||
|
opts,
|
||||||
|
GIT_DESCRIBE_OPTIONS_VERSION,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error < 0) {
|
||||||
|
throw LibGit2Error(libgit2.git_error_last());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxCandidatesTags != null) {
|
||||||
|
opts.ref.max_candidates_tags = maxCandidatesTags;
|
||||||
|
}
|
||||||
|
if (describeStrategy != null) {
|
||||||
|
opts.ref.describe_strategy = describeStrategy;
|
||||||
|
}
|
||||||
|
if (pattern != null) {
|
||||||
|
opts.ref.pattern = pattern.toNativeUtf8().cast<Int8>();
|
||||||
|
}
|
||||||
|
if (onlyFollowFirstParent != null) {
|
||||||
|
opts.ref.only_follow_first_parent = onlyFollowFirstParent ? 1 : 0;
|
||||||
|
}
|
||||||
|
if (showCommitOidAsFallback != null) {
|
||||||
|
opts.ref.show_commit_oid_as_fallback = showCommitOidAsFallback ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts;
|
||||||
|
}
|
|
@ -1498,3 +1498,30 @@ class GitRebaseOperation {
|
||||||
@override
|
@override
|
||||||
String toString() => 'GitRebaseOperation.$_name';
|
String toString() => 'GitRebaseOperation.$_name';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reference lookup strategy.
|
||||||
|
///
|
||||||
|
/// These behave like the --tags and --all options to git-describe,
|
||||||
|
/// namely they say to look for any reference in either refs/tags/ or
|
||||||
|
/// refs/ respectively.
|
||||||
|
class GitDescribeStrategy {
|
||||||
|
const GitDescribeStrategy._(this._value, this._name);
|
||||||
|
final int _value;
|
||||||
|
final String _name;
|
||||||
|
|
||||||
|
/// Only match annotated tags.
|
||||||
|
static const defaultStrategy = GitDescribeStrategy._(0, 'defaultStrategy');
|
||||||
|
|
||||||
|
/// Match everything under `refs/tags/` (includes lightweight tags).
|
||||||
|
static const tags = GitDescribeStrategy._(1, 'tags');
|
||||||
|
|
||||||
|
/// Match everything under `refs/` (includes branches).
|
||||||
|
static const all = GitDescribeStrategy._(2, 'all');
|
||||||
|
|
||||||
|
static const List<GitDescribeStrategy> values = [defaultStrategy, tags, all];
|
||||||
|
|
||||||
|
int get value => _value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'GitDescribeStrategy.$_name';
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import 'bindings/diff.dart' as diff_bindings;
|
||||||
import 'bindings/stash.dart' as stash_bindings;
|
import 'bindings/stash.dart' as stash_bindings;
|
||||||
import 'bindings/attr.dart' as attr_bindings;
|
import 'bindings/attr.dart' as attr_bindings;
|
||||||
import 'bindings/graph.dart' as graph_bindings;
|
import 'bindings/graph.dart' as graph_bindings;
|
||||||
|
import 'bindings/describe.dart' as describe_bindings;
|
||||||
import 'branch.dart';
|
import 'branch.dart';
|
||||||
import 'commit.dart';
|
import 'commit.dart';
|
||||||
import 'config.dart';
|
import 'config.dart';
|
||||||
|
@ -530,6 +531,11 @@ class Repository {
|
||||||
force: force);
|
force: force);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a list with all the tags in the repository.
|
||||||
|
///
|
||||||
|
/// Throws a [LibGit2Error] if error occured.
|
||||||
|
List<String> get tags => Tag.list(this);
|
||||||
|
|
||||||
/// Returns a [Branches] object.
|
/// Returns a [Branches] object.
|
||||||
Branches get branches => Branches(this);
|
Branches get branches => Branches(this);
|
||||||
|
|
||||||
|
@ -1218,4 +1224,77 @@ class Repository {
|
||||||
upstreamPointer: upstream.pointer,
|
upstreamPointer: upstream.pointer,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Describes a commit or the current worktree.
|
||||||
|
///
|
||||||
|
/// [maxCandidatesTags] is the number of candidate tags to consider. Increasing above 10 will
|
||||||
|
/// take slightly longer but may produce a more accurate result. A value of 0 will cause
|
||||||
|
/// only exact matches to be output. Default is 10.
|
||||||
|
///
|
||||||
|
/// [describeStrategy] is reference lookup strategy that is one of [GitDescribeStrategy].
|
||||||
|
/// Default matches only annotated tags.
|
||||||
|
///
|
||||||
|
/// [pattern] is pattern to use for tags matching, excluding the "refs/tags/" prefix.
|
||||||
|
///
|
||||||
|
/// [onlyFollowFirstParent] checks whether or not to follow only the first parent
|
||||||
|
/// commit upon seeing a merge commit.
|
||||||
|
///
|
||||||
|
/// [showCommitOidAsFallback] determines if full id of the commit should be shown
|
||||||
|
/// if no matching tag or reference is found.
|
||||||
|
///
|
||||||
|
/// [abbreviatedSize] is the minimum number of hexadecimal digits to show for abbreviated
|
||||||
|
/// object names. A value of 0 will suppress long format, only showing the closest tag.
|
||||||
|
/// Default is 7.
|
||||||
|
///
|
||||||
|
/// [alwaysUseLongFormat] determines if he long format (the nearest tag, the number of
|
||||||
|
/// commits, and the abbrevated commit name) should be used even when the commit matches
|
||||||
|
/// the tag.
|
||||||
|
///
|
||||||
|
/// [dirtySuffix] is a string to append if the working tree is dirty.
|
||||||
|
///
|
||||||
|
/// Throws a [LibGit2Error] if error occured.
|
||||||
|
String describe({
|
||||||
|
Commit? commit,
|
||||||
|
int? maxCandidatesTags,
|
||||||
|
GitDescribeStrategy? describeStrategy,
|
||||||
|
String? pattern,
|
||||||
|
bool? onlyFollowFirstParent,
|
||||||
|
bool? showCommitOidAsFallback,
|
||||||
|
int? abbreviatedSize,
|
||||||
|
bool? alwaysUseLongFormat,
|
||||||
|
String? dirtySuffix,
|
||||||
|
}) {
|
||||||
|
late final Pointer<git_describe_result> describeResult;
|
||||||
|
|
||||||
|
if (commit != null) {
|
||||||
|
describeResult = describe_bindings.commit(
|
||||||
|
commitPointer: commit.pointer,
|
||||||
|
maxCandidatesTags: maxCandidatesTags,
|
||||||
|
describeStrategy: describeStrategy?.value,
|
||||||
|
pattern: pattern,
|
||||||
|
onlyFollowFirstParent: onlyFollowFirstParent,
|
||||||
|
showCommitOidAsFallback: showCommitOidAsFallback,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
describeResult = describe_bindings.workdir(
|
||||||
|
repo: _repoPointer,
|
||||||
|
maxCandidatesTags: maxCandidatesTags,
|
||||||
|
describeStrategy: describeStrategy?.value,
|
||||||
|
pattern: pattern,
|
||||||
|
onlyFollowFirstParent: onlyFollowFirstParent,
|
||||||
|
showCommitOidAsFallback: showCommitOidAsFallback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final result = describe_bindings.format(
|
||||||
|
describeResultPointer: describeResult,
|
||||||
|
abbreviatedSize: abbreviatedSize,
|
||||||
|
alwaysUseLongFormat: alwaysUseLongFormat,
|
||||||
|
dirtySuffix: dirtySuffix,
|
||||||
|
);
|
||||||
|
|
||||||
|
describe_bindings.describeResultFree(describeResult);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
143
test/describe_test.dart
Normal file
143
test/describe_test.dart
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:libgit2dart/src/git_types.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:libgit2dart/libgit2dart.dart';
|
||||||
|
import 'helpers/util.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late Repository repo;
|
||||||
|
late Directory tmpDir;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
tmpDir = await setupRepo(Directory('test/assets/testrepo/'));
|
||||||
|
repo = Repository.open(tmpDir.path);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
repo.free();
|
||||||
|
await tmpDir.delete(recursive: true);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Describe', () {
|
||||||
|
test('successfully describes with default arguments', () {
|
||||||
|
expect(repo.describe(), 'v0.2');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully describes commit', () {
|
||||||
|
final tag = Tag.lookup(repo: repo, sha: 'f0fdbf5');
|
||||||
|
tag.delete();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
repo.describe(describeStrategy: GitDescribeStrategy.tags),
|
||||||
|
'v0.1-1-g821ed6e',
|
||||||
|
);
|
||||||
|
|
||||||
|
tag.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throws when trying to describe and no reference found', () {
|
||||||
|
final commit = repo['f17d0d48'] as Commit;
|
||||||
|
expect(() => repo.describe(commit: commit), throwsA(isA<LibGit2Error>()));
|
||||||
|
commit.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns oid when fallback argument is provided', () {
|
||||||
|
final commit = repo['f17d0d48'] as Commit;
|
||||||
|
expect(
|
||||||
|
repo.describe(commit: commit, showCommitOidAsFallback: true),
|
||||||
|
'f17d0d4',
|
||||||
|
);
|
||||||
|
commit.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully describes with provided strategy', () {
|
||||||
|
final commit = repo['5aecfa0'] as Commit;
|
||||||
|
expect(
|
||||||
|
repo.describe(
|
||||||
|
commit: commit,
|
||||||
|
describeStrategy: GitDescribeStrategy.all,
|
||||||
|
),
|
||||||
|
'heads/feature',
|
||||||
|
);
|
||||||
|
commit.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully describes with provided pattern', () {
|
||||||
|
final signature = repo.defaultSignature;
|
||||||
|
final commit = repo['fc38877'] as Commit;
|
||||||
|
repo.createTag(
|
||||||
|
tagName: 'test/tag1',
|
||||||
|
target: 'f17d0d48',
|
||||||
|
targetType: GitObject.commit,
|
||||||
|
tagger: signature,
|
||||||
|
message: '',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
repo.describe(commit: commit, pattern: 'test/*'),
|
||||||
|
'test/tag1-2-gfc38877',
|
||||||
|
);
|
||||||
|
|
||||||
|
commit.free();
|
||||||
|
signature.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully describes and follows first parent only', () {
|
||||||
|
final tag = Tag.lookup(repo: repo, sha: 'f0fdbf5');
|
||||||
|
tag.delete();
|
||||||
|
|
||||||
|
final commit = repo['821ed6e'] as Commit;
|
||||||
|
expect(
|
||||||
|
repo.describe(
|
||||||
|
commit: commit,
|
||||||
|
onlyFollowFirstParent: true,
|
||||||
|
describeStrategy: GitDescribeStrategy.tags,
|
||||||
|
),
|
||||||
|
'v0.1-1-g821ed6e',
|
||||||
|
);
|
||||||
|
|
||||||
|
tag.free();
|
||||||
|
commit.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully describes with abbreviated size provided', () {
|
||||||
|
final tag = Tag.lookup(repo: repo, sha: 'f0fdbf5');
|
||||||
|
tag.delete();
|
||||||
|
|
||||||
|
final commit = repo['821ed6e'] as Commit;
|
||||||
|
expect(
|
||||||
|
repo.describe(
|
||||||
|
commit: commit,
|
||||||
|
describeStrategy: GitDescribeStrategy.tags,
|
||||||
|
abbreviatedSize: 20,
|
||||||
|
),
|
||||||
|
'v0.1-1-g821ed6e80627b8769d17',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
repo.describe(
|
||||||
|
commit: commit,
|
||||||
|
describeStrategy: GitDescribeStrategy.tags,
|
||||||
|
abbreviatedSize: 0,
|
||||||
|
),
|
||||||
|
'v0.1',
|
||||||
|
);
|
||||||
|
|
||||||
|
tag.free();
|
||||||
|
commit.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully describes with long format', () {
|
||||||
|
expect(repo.describe(alwaysUseLongFormat: true), 'v0.2-0-g821ed6e');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully describes and appends dirty suffix', () {
|
||||||
|
final index = repo.index;
|
||||||
|
index.clear();
|
||||||
|
|
||||||
|
expect(repo.describe(dirtySuffix: '-dirty'), 'v0.2-dirty');
|
||||||
|
|
||||||
|
index.free();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,6 +1,4 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:libgit2dart/src/git_types.dart';
|
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'package:libgit2dart/libgit2dart.dart';
|
import 'package:libgit2dart/libgit2dart.dart';
|
||||||
import 'helpers/util.dart';
|
import 'helpers/util.dart';
|
||||||
|
|
|
@ -86,10 +86,10 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('successfully deletes tag', () {
|
test('successfully deletes tag', () {
|
||||||
expect(Tag.list(repo), ['v0.1', 'v0.2']);
|
expect(repo.tags, ['v0.1', 'v0.2']);
|
||||||
|
|
||||||
tag.delete();
|
tag.delete();
|
||||||
expect(Tag.list(repo), ['v0.1']);
|
expect(repo.tags, ['v0.1']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue