diff --git a/lib/src/bindings/index.dart b/lib/src/bindings/index.dart index ef4a4ff..9a9eb34 100644 --- a/lib/src/bindings/index.dart +++ b/lib/src/bindings/index.dart @@ -25,6 +25,41 @@ void read(Pointer index, bool force) { } } +/// Read a tree into the index file with stats. +/// +/// The current index contents will be replaced by the specified tree. +/// +/// Throws a [LibGit2Error] if error occured. +void readTree(Pointer index, Pointer tree) { + final error = libgit2.git_index_read_tree(index, tree); + + if (error < 0) { + throw LibGit2Error(libgit2.git_error_last()); + } +} + +/// Write the index as a tree. +/// +/// This method will scan the index and write a representation of its current state back to disk; +/// it recursively creates tree objects for each of the subtrees stored in the index, but only +/// returns the OID of the root tree. This is the OID that can be used e.g. to create a commit. +/// +/// The index instance cannot be bare, and needs to be associated to an existing repository. +/// +/// The index must not contain any file in conflict. +/// +/// Throws a [LibGit2Error] if error occured. +Pointer writeTree(Pointer index) { + final out = calloc(); + final error = libgit2.git_index_write_tree(out, index); + + if (error < 0) { + throw LibGit2Error(libgit2.git_error_last()); + } else { + return out; + } +} + /// Find the first position of any entries which point to given path in the Git index. bool find(Pointer index, String path) { final pathC = path.toNativeUtf8().cast(); @@ -223,5 +258,9 @@ void removeAll(Pointer index, List pathspec) { } } +/// Get the repository this index relates to. +Pointer owner(Pointer index) => + libgit2.git_index_owner(index); + /// Free an existing index object. void free(Pointer index) => libgit2.git_index_free(index); diff --git a/lib/src/bindings/tree.dart b/lib/src/bindings/tree.dart new file mode 100644 index 0000000..0935e45 --- /dev/null +++ b/lib/src/bindings/tree.dart @@ -0,0 +1,48 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import '../error.dart'; +import 'libgit2_bindings.dart'; +import '../util.dart'; + +/// Get the id of a tree. +Pointer id(Pointer tree) => libgit2.git_tree_id(tree); + +/// Lookup a tree object from the repository. +/// +/// Throws a [LibGit2Error] if error occured. +Pointer lookup(Pointer repo, Pointer id) { + final out = calloc>(); + final error = libgit2.git_tree_lookup(out, repo, id); + + if (error < 0) { + throw LibGit2Error(libgit2.git_error_last()); + } else { + return out.value; + } +} + +/// Lookup a tree object from the repository, given a prefix of its identifier (short id). +/// +/// 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); + + if (error < 0) { + throw LibGit2Error(libgit2.git_error_last()); + } else { + return out.value; + } +} + +/// Close an open tree. +/// +/// You can no longer use the git_tree pointer after this call. +/// +/// IMPORTANT: You MUST call this method when you stop using a tree to release memory. +/// Failure to do so will cause a memory leak. +void free(Pointer tree) => libgit2.git_tree_free(tree); diff --git a/lib/src/index.dart b/lib/src/index.dart index 01efe16..44d52b2 100644 --- a/lib/src/index.dart +++ b/lib/src/index.dart @@ -1,7 +1,10 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; +import 'package:libgit2dart/src/tree.dart'; import 'bindings/libgit2_bindings.dart'; import 'bindings/index.dart' as bindings; +import 'bindings/repository.dart' as repo_bindings; +import 'odb.dart'; import 'oid.dart'; import 'types.dart'; import 'util.dart'; @@ -88,11 +91,48 @@ class Index { /// Throws a [LibGit2Error] if error occured. void read({bool force = true}) => bindings.read(_indexPointer, force); + /// Updates the contents of an existing index object in memory by reading from the + /// specified tree. + void readTree(Object target) { + late final Oid oid; + late final Tree tree; + if (target is Oid) { + tree = Tree(bindings.owner(_indexPointer), target.pointer); + } else if (target is Tree) { + tree = target; + } else if (isValidShaHex(target as String)) { + if (target.length == 40) { + oid = Oid.fromSHA(target); + tree = Tree(bindings.owner(_indexPointer), oid.pointer); + } else { + final shortOid = Oid.fromSHAn(target); + final odb = Odb(repo_bindings.odb(bindings.owner(_indexPointer))); + oid = Oid(odb.existsPrefix(shortOid.pointer, target.length)); + odb.free(); + tree = Tree(bindings.owner(_indexPointer), oid.pointer); + } + } else { + throw ArgumentError.value( + '$target should be either Oid object, SHA hex string or Tree object'); + } + bindings.readTree(_indexPointer, tree.pointer); + tree.free(); + } + /// Writes an existing index object from memory back to disk using an atomic file lock. /// /// Throws a [LibGit2Error] if error occured. void write() => bindings.write(_indexPointer); + /// Write the index as a tree. + /// + /// This method will scan the index and write a representation of its current state back to disk; + /// it recursively creates tree objects for each of the subtrees stored in the index, but only + /// returns the OID of the root tree. This is the OID that can be used e.g. to create a commit. + /// + /// The index must not contain any file in conflict. + Oid writeTree() => Oid(bindings.writeTree(_indexPointer)); + /// Removes an entry from the index. /// /// Throws a [LibGit2Error] if error occured. diff --git a/lib/src/tree.dart b/lib/src/tree.dart new file mode 100644 index 0000000..b21c13e --- /dev/null +++ b/lib/src/tree.dart @@ -0,0 +1,26 @@ +import 'dart:ffi'; +import 'bindings/libgit2_bindings.dart'; +import 'bindings/tree.dart' as bindings; +import 'util.dart'; + +class Tree { + /// Initializes a new instance of [Tree] class from provided + /// pointers to repository object and oid object in memory. + /// + /// Should be freed with `free()` to release allocated memory. + Tree(Pointer repo, Pointer id) { + libgit2.git_libgit2_init(); + _treePointer = bindings.lookup(repo, id); + } + + late final Pointer _treePointer; + + /// Pointer to memory address for allocated tree object. + Pointer get pointer => _treePointer; + + /// Releases memory allocated for tree object. + void free() { + bindings.free(_treePointer); + libgit2.git_libgit2_shutdown(); + } +} diff --git a/test/index_test.dart b/test/index_test.dart index dec2262..684e042 100644 --- a/test/index_test.dart +++ b/test/index_test.dart @@ -164,5 +164,31 @@ void main() { expect(index.contains('file'), false); expect(index.contains('feature_file'), false); }); + + group('read tree', () { + const treeSha = 'df2b8fc99e1c1d4dbc0a854d9f72157f1d6ea078'; + test('successfully reads with provided SHA hex', () { + expect(index.count, 3); + index.readTree(treeSha); + + expect(index.count, 1); + + // make sure the index is only modified in memory + index.read(); + expect(index.count, 3); + }); + + test('successfully reads with provided short SHA hex', () { + expect(index.count, 3); + index.readTree(treeSha.substring(0, 5)); + + expect(index.count, 1); + }); + }); + + test('successfully writes tree', () { + final oid = index.writeTree(); + expect(oid.sha, '7796359a96eb722939c24bafdb1afe9f07f2f628'); + }); }); }