diff --git a/lib/src/bindings/index.dart b/lib/src/bindings/index.dart index 4741233..bc1e1ea 100644 --- a/lib/src/bindings/index.dart +++ b/lib/src/bindings/index.dart @@ -77,7 +77,7 @@ int entryCount(Pointer index) => libgit2.git_index_entrycount(index); /// /// The entry is not modifiable and should not be freed. /// -/// Throws error if position is out of bounds. +/// Throws [RangeError] when provided index is outside of valid range. Pointer getByIndex(Pointer index, int n) { final result = libgit2.git_index_get_byindex(index, n); @@ -92,7 +92,7 @@ Pointer getByIndex(Pointer index, int n) { /// ///The entry is not modifiable and should not be freed. /// -/// Throws error if entry isn't found. +/// Throws [ArgumentError] if nothing found for provided path. Pointer getByPath( Pointer index, String path, diff --git a/lib/src/bindings/tree.dart b/lib/src/bindings/tree.dart index 5eb8672..c0c07fb 100644 --- a/lib/src/bindings/tree.dart +++ b/lib/src/bindings/tree.dart @@ -21,16 +21,57 @@ Pointer lookup(Pointer repo, Pointer id) { } } -/// Lookup a tree object from the repository, given a prefix of its identifier (short id). +/// Get the repository that contains the tree. +Pointer owner(Pointer tree) => + libgit2.git_tree_owner(tree); + +/// Lookup a tree entry by its position in the tree. +/// +/// This returns a tree entry that is owned by the tree. You don't have to free it, +/// but you must not use it after the tree is released. +/// +/// Throws [RangeError] when provided index is outside of valid range. +Pointer getByIndex(Pointer tree, int index) { + final result = libgit2.git_tree_entry_byindex(tree, index); + + if (result == nullptr) { + throw RangeError('Out of bounds'); + } else { + return result; + } +} + +/// Lookup a tree entry by its filename. +/// +/// This returns a tree entry that is owned by the tree. You don't have to free it, +/// but you must not use it after the tree is released. +/// +/// Throws [ArgumentError] if nothing found for provided filename. +Pointer getByName(Pointer tree, String filename) { + final filenameC = filename.toNativeUtf8().cast(); + final result = libgit2.git_tree_entry_byname(tree, filenameC); + + calloc.free(filenameC); + + if (result == nullptr) { + throw ArgumentError.value('$filename was not found'); + } else { + return result; + } +} + +/// Retrieve a tree entry contained in a tree or in any of its subtrees, given its relative path. +/// +/// Unlike the other lookup functions, the returned tree entry is owned by the user and must be +/// freed explicitly with `entryFree()`. /// /// Throws a [LibGit2Error] if error occured. -Pointer lookupPrefix( - Pointer repo, - Pointer id, - int len, -) { - final out = calloc>(); - final error = libgit2.git_tree_lookup_prefix(out, repo, id, len); +Pointer getByPath(Pointer root, String path) { + final out = calloc>(); + final pathC = path.toNativeUtf8().cast(); + final error = libgit2.git_tree_entry_bypath(out, root, pathC); + + calloc.free(pathC); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); @@ -39,5 +80,34 @@ Pointer lookupPrefix( } } +/// Get the number of entries listed in a tree. +int entryCount(Pointer tree) => libgit2.git_tree_entrycount(tree); + +/// Get the id of the object pointed by the entry. +Pointer entryId(Pointer entry) => + libgit2.git_tree_entry_id(entry); + +/// Get the filename of a tree entry. +String entryName(Pointer entry) => + libgit2.git_tree_entry_name(entry).cast().toDartString(); + +/// Get the UNIX file attributes of a tree entry. +int entryFilemode(Pointer entry) => + libgit2.git_tree_entry_filemode(entry); + +/// Compare two tree entries. +/// +/// Returns <0 if e1 is before e2, 0 if e1 == e2, >0 if e1 is after e2. +int compare(Pointer e1, Pointer e2) { + return libgit2.git_tree_entry_cmp(e1, e2); +} + +/// Free a user-owned tree entry. +/// +/// IMPORTANT: This function is only needed for tree entries owned by the user, +/// such as `getByPath()`. +void entryFree(Pointer entry) => + libgit2.git_tree_entry_free(entry); + /// Close an open tree to release memory. void free(Pointer tree) => libgit2.git_tree_free(tree); diff --git a/lib/src/commit.dart b/lib/src/commit.dart index 52ac032..cbeb8f2 100644 --- a/lib/src/commit.dart +++ b/lib/src/commit.dart @@ -1,8 +1,6 @@ import 'dart:ffi'; import 'bindings/libgit2_bindings.dart'; import 'bindings/commit.dart' as bindings; -import 'bindings/oid.dart' as oid_bindings; -import 'bindings/tree.dart' as tree_bindings; import 'repository.dart'; import 'oid.dart'; import 'signature.dart'; @@ -42,9 +40,8 @@ class Commit { String? updateRef, String? messageEncoding, }) { - final treeOid = oid_bindings.fromStrN(treeSHA); - final tree = - Tree(tree_bindings.lookupPrefix(repo.pointer, treeOid, treeSHA.length)); + final treeOid = Oid.fromSHA(repo, treeSHA); + final tree = Tree.lookup(repo, treeOid); final result = Oid(bindings.create( repo.pointer, diff --git a/lib/src/index.dart b/lib/src/index.dart index bd96dbb..0adea12 100644 --- a/lib/src/index.dart +++ b/lib/src/index.dart @@ -164,46 +164,12 @@ class IndexEntry { /// Returns id of the index entry as sha-1 hex. String get sha => _oidToHex(_indexEntryPointer.ref.id); - GitFilemode get mode { - switch (_indexEntryPointer.ref.mode) { - case 0: - return GitFilemode.undreadable; - case 16384: - return GitFilemode.tree; - case 33188: - return GitFilemode.blob; - case 33261: - return GitFilemode.blobExecutable; - case 40960: - return GitFilemode.link; - case 57344: - return GitFilemode.commit; - default: - return GitFilemode.undreadable; - } - } + /// Returns the UNIX file attributes of a index entry. + GitFilemode get mode => intToGitFilemode(_indexEntryPointer.ref.mode); + /// Sets the UNIX file attributes of a index entry. set mode(GitFilemode mode) { - switch (mode) { - case GitFilemode.undreadable: - _indexEntryPointer.ref.mode = 0; - break; - case GitFilemode.tree: - _indexEntryPointer.ref.mode = 16384; - break; - case GitFilemode.blob: - _indexEntryPointer.ref.mode = 33188; - break; - case GitFilemode.blobExecutable: - _indexEntryPointer.ref.mode = 33261; - break; - case GitFilemode.link: - _indexEntryPointer.ref.mode = 40960; - break; - case GitFilemode.commit: - _indexEntryPointer.ref.mode = 57344; - break; - } + _indexEntryPointer.ref.mode = gitFilemodeToInt(mode); } String _oidToHex(git_oid oid) { diff --git a/lib/src/tree.dart b/lib/src/tree.dart index 211f3ae..a71df7f 100644 --- a/lib/src/tree.dart +++ b/lib/src/tree.dart @@ -1,9 +1,9 @@ import 'dart:ffi'; -import 'package:libgit2dart/src/repository.dart'; - import 'bindings/libgit2_bindings.dart'; import 'bindings/tree.dart' as bindings; +import 'repository.dart'; import 'oid.dart'; +import 'enums.dart'; import 'util.dart'; class Tree { @@ -17,8 +17,8 @@ class Tree { /// [Repository] and [Oid] objects. /// /// Should be freed with `free()` to release allocated memory. - Tree.lookup(Repository repo, Oid id) { - _treePointer = bindings.lookup(repo.pointer, id.pointer); + Tree.lookup(Repository repo, Oid oid) { + _treePointer = bindings.lookup(repo.pointer, oid.pointer); } late final Pointer _treePointer; @@ -26,8 +26,89 @@ class Tree { /// Pointer to memory address for allocated tree object. Pointer get pointer => _treePointer; - /// Releases memory allocated for tree object. - void free() { - bindings.free(_treePointer); + /// Returns a list with tree entries of a tree. + List get entries { + final entryCount = bindings.entryCount(_treePointer); + var result = []; + for (var i = 0; i < entryCount; i++) { + result.add(TreeEntry(bindings.getByIndex(_treePointer, i))); + } + + return result; } + + /// Looksup a tree entry in the tree. + /// + /// If integer [value] is provided, lookup is done by entry position in the tree. + /// + /// If string [value] is provided, lookup is done by entry filename. + /// + /// If provided string [value] is a path to file, lookup is done by path. In that case + /// returned object should be freed explicitly. + TreeEntry operator [](Object value) { + if (value is int) { + return TreeEntry(bindings.getByIndex(_treePointer, value)); + } else if (value is String && value.contains('/')) { + return TreeEntry(bindings.getByPath(_treePointer, value)); + } else if (value is String) { + return TreeEntry(bindings.getByName(_treePointer, value)); + } else { + throw ArgumentError.value( + '$value should be either index position, filename or path'); + } + } + + /// Releases memory allocated for tree object. + void free() => bindings.free(_treePointer); +} + +class TreeEntry { + /// Initializes a new instance of [TreeEntry] class. + TreeEntry(this._treeEntryPointer); + + /// Pointer to memory address for allocated tree entry object. + final Pointer _treeEntryPointer; + + /// Returns the Oid of the object pointed by the entry. + Oid get id => Oid(bindings.entryId(_treeEntryPointer)); + + /// Returns the filename of a tree entry. + String get name => bindings.entryName(_treeEntryPointer); + + /// Returns the UNIX file attributes of a tree entry. + GitFilemode get filemode { + return intToGitFilemode(bindings.entryFilemode(_treeEntryPointer)); + } + + @override + bool operator ==(other) { + return (other is TreeEntry) && + (bindings.compare(_treeEntryPointer, other._treeEntryPointer) == 0); + } + + bool operator <(other) { + return (other is TreeEntry) && + (bindings.compare(_treeEntryPointer, other._treeEntryPointer) == -1); + } + + bool operator <=(other) { + return (other is TreeEntry) && + (bindings.compare(_treeEntryPointer, other._treeEntryPointer) == -1); + } + + bool operator >(other) { + return (other is TreeEntry) && + (bindings.compare(_treeEntryPointer, other._treeEntryPointer) == 1); + } + + bool operator >=(other) { + return (other is TreeEntry) && + (bindings.compare(_treeEntryPointer, other._treeEntryPointer) == 1); + } + + @override + int get hashCode => _treeEntryPointer.address.hashCode; + + /// Releases memory allocated for tree entry object. + void free() => bindings.entryFree(_treeEntryPointer); } diff --git a/lib/src/util.dart b/lib/src/util.dart index 8183020..520af43 100644 --- a/lib/src/util.dart +++ b/lib/src/util.dart @@ -1,6 +1,7 @@ -import 'dart:ffi'; import 'dart:io'; +import 'dart:ffi'; import 'bindings/libgit2_bindings.dart'; +import 'enums.dart'; DynamicLibrary loadLibrary() { if (Platform.isLinux || Platform.isAndroid || Platform.isFuchsia) { @@ -25,3 +26,39 @@ bool isValidShaHex(String str) { return hexRegExp.hasMatch(str) && (GIT_OID_MINPREFIXLEN <= str.length && GIT_OID_HEXSZ >= str.length); } + +GitFilemode intToGitFilemode(int i) { + switch (i) { + case 0: + return GitFilemode.undreadable; + case 16384: + return GitFilemode.tree; + case 33188: + return GitFilemode.blob; + case 33261: + return GitFilemode.blobExecutable; + case 40960: + return GitFilemode.link; + case 57344: + return GitFilemode.commit; + default: + return GitFilemode.undreadable; + } +} + +int gitFilemodeToInt(GitFilemode filemode) { + switch (filemode) { + case GitFilemode.undreadable: + return 0; + case GitFilemode.tree: + return 16384; + case GitFilemode.blob: + return 33188; + case GitFilemode.blobExecutable: + return 33261; + case GitFilemode.link: + return 40960; + case GitFilemode.commit: + return 57344; + } +} diff --git a/test/assets/testrepo/.gitdir/COMMIT_EDITMSG b/test/assets/testrepo/.gitdir/COMMIT_EDITMSG index 2cdbe7d..5fc5f18 100644 --- a/test/assets/testrepo/.gitdir/COMMIT_EDITMSG +++ b/test/assets/testrepo/.gitdir/COMMIT_EDITMSG @@ -1 +1 @@ -add another feature file +add subdirectory file diff --git a/test/assets/testrepo/.gitdir/index b/test/assets/testrepo/.gitdir/index index 2c38a75..42726c1 100644 Binary files a/test/assets/testrepo/.gitdir/index and b/test/assets/testrepo/.gitdir/index differ diff --git a/test/assets/testrepo/.gitdir/logs/HEAD b/test/assets/testrepo/.gitdir/logs/HEAD index 2087c0c..fee0ae9 100644 --- a/test/assets/testrepo/.gitdir/logs/HEAD +++ b/test/assets/testrepo/.gitdir/logs/HEAD @@ -8,3 +8,4 @@ c68ff54aabf660fcdd9a2838d401583fe31249e3 78b8bf123e3952c970ae5c1ce0a3ea1d1336f6e 78b8bf123e3952c970ae5c1ce0a3ea1d1336f6e8 fc38877b2552ab554752d9a77e1f48f738cca79b Aleksey Kulikov 1626091245 +0300 checkout: moving from master to feature fc38877b2552ab554752d9a77e1f48f738cca79b 5aecfa0fb97eadaac050ccb99f03c3fb65460ad4 Aleksey Kulikov 1626091274 +0300 commit: add another feature file 5aecfa0fb97eadaac050ccb99f03c3fb65460ad4 78b8bf123e3952c970ae5c1ce0a3ea1d1336f6e8 Aleksey Kulikov 1626091285 +0300 checkout: moving from feature to master +78b8bf123e3952c970ae5c1ce0a3ea1d1336f6e8 821ed6e80627b8769d170a293862f9fc60825226 Aleksey Kulikov 1630568461 +0300 commit: add subdirectory file diff --git a/test/assets/testrepo/.gitdir/logs/refs/heads/master b/test/assets/testrepo/.gitdir/logs/refs/heads/master index d2dba8f..2debc2c 100644 --- a/test/assets/testrepo/.gitdir/logs/refs/heads/master +++ b/test/assets/testrepo/.gitdir/logs/refs/heads/master @@ -1,3 +1,4 @@ 0000000000000000000000000000000000000000 f17d0d48eae3aa08cecf29128a35e310c97b3521 Aleksey Kulikov 1626090830 +0300 commit (initial): init f17d0d48eae3aa08cecf29128a35e310c97b3521 c68ff54aabf660fcdd9a2838d401583fe31249e3 Aleksey Kulikov 1626091171 +0300 commit: add .gitignore c68ff54aabf660fcdd9a2838d401583fe31249e3 78b8bf123e3952c970ae5c1ce0a3ea1d1336f6e8 Aleksey Kulikov 1626091184 +0300 merge feature: Merge made by the 'recursive' strategy. +78b8bf123e3952c970ae5c1ce0a3ea1d1336f6e8 821ed6e80627b8769d170a293862f9fc60825226 Aleksey Kulikov 1630568461 +0300 commit: add subdirectory file diff --git a/test/assets/testrepo/.gitdir/objects/66/bd6147704f78165d764ce8348ccaec81415db4 b/test/assets/testrepo/.gitdir/objects/66/bd6147704f78165d764ce8348ccaec81415db4 new file mode 100644 index 0000000..2da87bd Binary files /dev/null and b/test/assets/testrepo/.gitdir/objects/66/bd6147704f78165d764ce8348ccaec81415db4 differ diff --git a/test/assets/testrepo/.gitdir/objects/82/1ed6e80627b8769d170a293862f9fc60825226 b/test/assets/testrepo/.gitdir/objects/82/1ed6e80627b8769d170a293862f9fc60825226 new file mode 100644 index 0000000..091c359 --- /dev/null +++ b/test/assets/testrepo/.gitdir/objects/82/1ed6e80627b8769d170a293862f9fc60825226 @@ -0,0 +1 @@ +xAn F9#E`,EU)0TG;t^Nz;̰Ў`zILaN"cRa4-I=L^G-bS?E_DZ}%C-來nn,U_ s!.JyyP \ No newline at end of file diff --git a/test/assets/testrepo/.gitdir/objects/a8/ae3dd59e6e1802c6f78e05e301bfd57c9f334f b/test/assets/testrepo/.gitdir/objects/a8/ae3dd59e6e1802c6f78e05e301bfd57c9f334f new file mode 100644 index 0000000..540dbbc Binary files /dev/null and b/test/assets/testrepo/.gitdir/objects/a8/ae3dd59e6e1802c6f78e05e301bfd57c9f334f differ diff --git a/test/assets/testrepo/.gitdir/refs/heads/master b/test/assets/testrepo/.gitdir/refs/heads/master index e4cb9a5..fb9321c 100644 --- a/test/assets/testrepo/.gitdir/refs/heads/master +++ b/test/assets/testrepo/.gitdir/refs/heads/master @@ -1 +1 @@ -78b8bf123e3952c970ae5c1ce0a3ea1d1336f6e8 +821ed6e80627b8769d170a293862f9fc60825226 diff --git a/test/assets/testrepo/.gitdir/refs/tags/empty_marker b/test/assets/testrepo/.gitdir/refs/tags/empty_marker new file mode 100644 index 0000000..e69de29 diff --git a/test/assets/testrepo/dir/dir_file.txt b/test/assets/testrepo/dir/dir_file.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/index_test.dart b/test/index_test.dart index a25882a..f8a6e67 100644 --- a/test/index_test.dart +++ b/test/index_test.dart @@ -32,7 +32,7 @@ void main() { const featureFileSha = '9c78c21d6680a7ffebc76f7ac68cacc11d8f48bc'; test('returns number of entries', () { - expect(index.count, 3); + expect(index.count, 4); }); test('returns mode of index entry', () { @@ -40,7 +40,7 @@ void main() { }); test('returns index entry at provided position', () { - expect(index[2].path, 'file'); + expect(index[2].path, 'feature_file'); expect(index['file'].sha, fileSha); }); @@ -74,7 +74,7 @@ void main() { }); test('clears the contents', () { - expect(index.count, 3); + expect(index.count, 4); index.clear(); expect(index.count, 0); }); @@ -85,13 +85,13 @@ void main() { index.add(entry); expect(index['file'].sha, fileSha); - expect(index.count, 3); + expect(index.count, 4); }); test('successfully adds with provided path string', () { index.add('file'); expect(index['file'].sha, fileSha); - expect(index.count, 3); + expect(index.count, 4); }); test('throws if file not found at provided path', () { @@ -134,7 +134,7 @@ void main() { }); test('writes to disk', () { - expect(index.count, 3); + expect(index.count, 4); File('$tmpDir/new_file').createSync(); @@ -144,7 +144,7 @@ void main() { index.clear(); index.read(); expect(index['new_file'].path, 'new_file'); - expect(index.count, 4); + expect(index.count, 5); }); test('removes an entry', () { @@ -166,18 +166,18 @@ void main() { group('read tree', () { const treeSha = 'df2b8fc99e1c1d4dbc0a854d9f72157f1d6ea078'; test('successfully reads with provided SHA hex', () { - expect(index.count, 3); + expect(index.count, 4); index.readTree(treeSha); expect(index.count, 1); // make sure the index is only modified in memory index.read(); - expect(index.count, 3); + expect(index.count, 4); }); test('successfully reads with provided short SHA hex', () { - expect(index.count, 3); + expect(index.count, 4); index.readTree(treeSha.substring(0, 5)); expect(index.count, 1); @@ -186,7 +186,7 @@ void main() { test('successfully writes tree', () { final oid = index.writeTree(); - expect(oid.sha, '7796359a96eb722939c24bafdb1afe9f07f2f628'); + expect(oid.sha, 'a8ae3dd59e6e1802c6f78e05e301bfd57c9f334f'); }); }); } diff --git a/test/reference_test.dart b/test/reference_test.dart index 815745a..600a127 100644 --- a/test/reference_test.dart +++ b/test/reference_test.dart @@ -5,7 +5,7 @@ import 'package:libgit2dart/libgit2dart.dart'; import 'helpers/util.dart'; void main() { - const lastCommit = '78b8bf123e3952c970ae5c1ce0a3ea1d1336f6e8'; + const lastCommit = '821ed6e80627b8769d170a293862f9fc60825226'; const newCommit = 'c68ff54aabf660fcdd9a2838d401583fe31249e3'; group('Reference', () { diff --git a/test/reflog_test.dart b/test/reflog_test.dart index 9d564ad..55aaf4e 100644 --- a/test/reflog_test.dart +++ b/test/reflog_test.dart @@ -34,22 +34,19 @@ void main() { }); test('returns correct number of log entries', () { - expect(reflog.count, 3); + expect(reflog.count, 4); }); test('returns the log message', () { final entry = reflog.entryAt(0); - expect( - entry.message, - "merge feature: Merge made by the 'recursive' strategy.", - ); + expect(entry.message, "commit: add subdirectory file"); }); test('returns the committer of the entry', () { final entry = reflog.entryAt(0); expect(entry.committer.name, 'Aleksey Kulikov'); expect(entry.committer.email, 'skinny.mind@gmail.com'); - expect(entry.committer.time, 1626091184); + expect(entry.committer.time, 1630568461); }); }); } diff --git a/test/repository_test.dart b/test/repository_test.dart index c4ba14a..31f7bec 100644 --- a/test/repository_test.dart +++ b/test/repository_test.dart @@ -150,7 +150,7 @@ void main() { }); group('testrepo', () { - const lastCommit = '78b8bf123e3952c970ae5c1ce0a3ea1d1336f6e8'; + const lastCommit = '821ed6e80627b8769d170a293862f9fc60825226'; const featureCommit = '5aecfa0fb97eadaac050ccb99f03c3fb65460ad4'; final tmpDir = '${Directory.systemTemp.path}/testrepo/'; @@ -181,6 +181,7 @@ void main() { test('returns list of commits by walking from provided starting oid', () { const log = [ + '821ed6e80627b8769d170a293862f9fc60825226', '78b8bf123e3952c970ae5c1ce0a3ea1d1336f6e8', 'c68ff54aabf660fcdd9a2838d401583fe31249e3', 'fc38877b2552ab554752d9a77e1f48f738cca79b', diff --git a/test/revparse_test.dart b/test/revparse_test.dart index c07e1b4..1b8cad8 100644 --- a/test/revparse_test.dart +++ b/test/revparse_test.dart @@ -7,8 +7,8 @@ import 'helpers/util.dart'; void main() { late Repository repo; final tmpDir = '${Directory.systemTemp.path}/revparse_testrepo/'; - const headSHA = '78b8bf123e3952c970ae5c1ce0a3ea1d1336f6e8'; - const parentSHA = 'c68ff54aabf660fcdd9a2838d401583fe31249e3'; + const headSHA = '821ed6e80627b8769d170a293862f9fc60825226'; + const parentSHA = '78b8bf123e3952c970ae5c1ce0a3ea1d1336f6e8'; setUp(() async { if (await Directory(tmpDir).exists()) { diff --git a/test/tree_test.dart b/test/tree_test.dart new file mode 100644 index 0000000..e1f5598 --- /dev/null +++ b/test/tree_test.dart @@ -0,0 +1,80 @@ +import 'dart:io'; + +import 'package:test/test.dart'; +import 'package:libgit2dart/libgit2dart.dart'; +import 'helpers/util.dart'; + +void main() { + late Repository repo; + late Tree tree; + final tmpDir = '${Directory.systemTemp.path}/tree_testrepo/'; + const treeSHA = 'a8ae3dd59e6e1802c6f78e05e301bfd57c9f334f'; + const fileSHA = '1377554ebea6f98a2c748183bc5a96852af12ac2'; + + 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); + tree = Tree.lookup(repo, Oid.fromSHA(repo, treeSHA)); + }); + + tearDown(() async { + tree.free(); + repo.free(); + await Directory(tmpDir).delete(recursive: true); + }); + + group('Tree', () { + test('successfully initializes tree from provided Oid', () { + expect(tree, isA()); + }); + + test('returns number of entries', () { + expect(tree.entries.length, 4); + }); + + test('returns sha of tree entry', () { + expect(tree.entries.first.id.sha, fileSHA); + }); + + test('returns name of tree entry', () { + expect(tree.entries[0].name, '.gitignore'); + }); + + test('returns filemode of tree entry', () { + expect(tree.entries[0].filemode, GitFilemode.blob); + }); + + test('returns tree entry with provided index position', () { + expect(tree[0].id.sha, fileSHA); + }); + + test('throws when provided index position is outside of valid range', () { + expect(() => tree[10], throwsA(isA())); + expect(() => tree[-10], throwsA(isA())); + }); + + test('returns tree entry with provided filename', () { + expect(tree['.gitignore'].id.sha, fileSHA); + }); + + test('throws when nothing found for provided filename', () { + expect(() => tree['invalid'], throwsA(isA())); + }); + + test('returns tree entry with provided path to file', () { + final entry = tree['dir/dir_file.txt']; + expect(entry.id.sha, 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'); + entry.free(); + }); + + test('throws when nothing found for provided path', () { + expect(() => tree['invalid/path'], throwsA(isA())); + }); + }); +}