From ec80ad3dd46427a7a7e29624b4832be4e5aa8b53 Mon Sep 17 00:00:00 2001 From: Aleksey Kulikov Date: Thu, 30 Sep 2021 13:53:58 +0300 Subject: [PATCH] feat(attr): add bindings and api --- lib/src/bindings/attr.dart | 44 ++++++++++++++++++++++++++++++++++++++ lib/src/git_types.dart | 28 ++++++++++++++++++++++++ lib/src/repository.dart | 23 ++++++++++++++++++++ test/credentials_test.dart | 12 +++++------ test/repository_test.dart | 15 +++++++++++++ 5 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 lib/src/bindings/attr.dart diff --git a/lib/src/bindings/attr.dart b/lib/src/bindings/attr.dart new file mode 100644 index 0000000..189875b --- /dev/null +++ b/lib/src/bindings/attr.dart @@ -0,0 +1,44 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import '../error.dart'; +import '../util.dart'; +import 'libgit2_bindings.dart'; + +/// Look up the value of one git attribute for path. +/// +/// Returned value can be either `true`, `false`, `null` (if the attribute was not set at all), +/// or a [String] value, if the attribute was set to an actual string. +/// +/// Throws a [LibGit2Error] if error occured. +dynamic getAttribute({ + required Pointer repoPointer, + required int flags, + required String path, + required String name, +}) { + final out = calloc>(); + final pathC = path.toNativeUtf8().cast(); + final nameC = name.toNativeUtf8().cast(); + final error = libgit2.git_attr_get(out, repoPointer, flags, pathC, nameC); + + calloc.free(pathC); + calloc.free(nameC); + + if (error < 0) { + throw LibGit2Error(libgit2.git_error_last()); + } + + final attributeValue = libgit2.git_attr_value(out.value); + + if (attributeValue == git_attr_value_t.GIT_ATTR_VALUE_UNSPECIFIED) { + return null; + } else if (attributeValue == git_attr_value_t.GIT_ATTR_VALUE_TRUE) { + return true; + } else if (attributeValue == git_attr_value_t.GIT_ATTR_VALUE_FALSE) { + return false; + } else if (attributeValue == git_attr_value_t.GIT_ATTR_VALUE_STRING) { + return out.value.cast().toDartString(); + } else { + throw Exception('The attribute value from libgit2 is invalid'); + } +} diff --git a/lib/src/git_types.dart b/lib/src/git_types.dart index 99200f2..f6d6bc1 100644 --- a/lib/src/git_types.dart +++ b/lib/src/git_types.dart @@ -1356,3 +1356,31 @@ class GitFeature { @override String toString() => 'GitFeature.$_name'; } + +/// Combinations of these values determine the lookup order for attribute. +class GitAttributeCheck { + const GitAttributeCheck._(this._value, this._name); + final int _value; + final String _name; + + static const fileThenIndex = GitAttributeCheck._(0, 'fileThenIndex'); + static const indexThenFile = GitAttributeCheck._(1, 'indexThenFile'); + static const indexOnly = GitAttributeCheck._(2, 'indexOnly'); + static const noSystem = GitAttributeCheck._(4, 'noSystem'); + static const includeHead = GitAttributeCheck._(8, 'includeHead'); + static const includeCommit = GitAttributeCheck._(16, 'includeCommit'); + + static const List values = [ + fileThenIndex, + indexThenFile, + indexOnly, + noSystem, + includeHead, + includeCommit, + ]; + + int get value => _value; + + @override + String toString() => 'GitAttributeCheck.$_name'; +} diff --git a/lib/src/repository.dart b/lib/src/repository.dart index d820167..e8cfdb6 100644 --- a/lib/src/repository.dart +++ b/lib/src/repository.dart @@ -11,6 +11,7 @@ import 'bindings/checkout.dart' as checkout_bindings; import 'bindings/reset.dart' as reset_bindings; import 'bindings/diff.dart' as diff_bindings; import 'bindings/stash.dart' as stash_bindings; +import 'bindings/attr.dart' as attr_bindings; import 'branch.dart'; import 'commit.dart'; import 'config.dart'; @@ -955,4 +956,26 @@ class Repository { /// Returns [Remotes] object. Remotes get remotes => Remotes(this); + + /// Looks up the value of one git attribute for path. + /// + /// Returned value can be either `true`, `false`, `null` (if the attribute was not set at all), + /// or a [String] value, if the attribute was set to an actual string. + /// + /// Throws a [LibGit2Error] if error occured. + dynamic getAttribute({ + required String path, + required String name, + Set flags = const {GitAttributeCheck.fileThenIndex}, + }) { + final int flagsInt = + flags.fold(0, (previousValue, e) => previousValue | e.value); + + return attr_bindings.getAttribute( + repoPointer: _repoPointer, + flags: flagsInt, + path: path, + name: name, + ); + } } diff --git a/test/credentials_test.dart b/test/credentials_test.dart index c61073a..5388f92 100644 --- a/test/credentials_test.dart +++ b/test/credentials_test.dart @@ -5,15 +5,15 @@ import 'package:libgit2dart/libgit2dart.dart'; void main() { final cloneDir = Directory('${Directory.systemTemp.path}/credentials_cloned'); - setUp(() async { - if (await cloneDir.exists()) { - cloneDir.delete(recursive: true); + setUp(() { + if (cloneDir.existsSync()) { + cloneDir.deleteSync(recursive: true); } }); - tearDown(() async { - if (await cloneDir.exists()) { - cloneDir.delete(recursive: true); + tearDown(() { + if (cloneDir.existsSync()) { + cloneDir.deleteSync(recursive: true); } }); group('Credentials', () { diff --git a/test/repository_test.dart b/test/repository_test.dart index fb966ef..45ed7fa 100644 --- a/test/repository_test.dart +++ b/test/repository_test.dart @@ -236,5 +236,20 @@ void main() { signature.free(); config.free(); }); + + test('returns attribute value', () async { + expect(repo.getAttribute(path: 'invalid', name: 'not-there'), null); + + final attrFile = await File('${repo.workdir}.gitattributes').create(); + attrFile.writeAsString('*.dart text\n*.jpg -text\n*.sh eol=lf\n'); + + await File('${repo.workdir}file.dart').create(); + await File('${repo.workdir}file.sh').create(); + + expect(repo.getAttribute(path: 'file.dart', name: 'not-there'), null); + expect(repo.getAttribute(path: 'file.dart', name: 'text'), true); + expect(repo.getAttribute(path: 'file.jpg', name: 'text'), false); + expect(repo.getAttribute(path: 'file.sh', name: 'eol'), 'lf'); + }); }); }