diff --git a/CHANGELOG.md b/CHANGELOG.md index 7156b40..8479242 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,43 @@ +## 1.2.2 + +- fix: lookup package in correct location of Dart/Flutter cached packages + +## 1.2.1 + +- fix: use default location of Flutter's '.pub_cache' folder + +- docs: update README with troubleshooting instructions for Windows + +- chore: bump minimum dart and flutter versions + +- chore: Lookup library in system path (thanks @dnys1) + +## 1.2.0 + +- feat: upgrade libgit2 to 1.5.0 + +- feat: add ability to pass checkout options to `reset(...)` API method + +- feat: add ability to pass options to `prune(...)` Worktree API method + +- feat: add ability to pass options to `Merge.fileFromIndex(...)` API method + +- feat: add ability to pass options to `addAll(...)` Index API method + +- feat: add ability to pass options to `revert(...)` and `revertTo(...)` Commit API methods: + + - select parent to revert to for merge commits + - merge options + - checkout options + +- chore: upgrade dependencies + +## 1.1.2 + +- fix: lookup library in Flutter's .pub_cache folder + +- feat: add ability to limit number of commits to walk in revision walk + ## 1.1.1 - fix: lookup library in correct locations diff --git a/README.md b/README.md index 8c17eb4..e1c6679 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # libgit2dart -![Coverage](coverage_badge.svg) - **Dart bindings to libgit2** libgit2dart package provides ability to use [libgit2](https://github.com/libgit2/libgit2) in Dart/Flutter. @@ -665,6 +663,8 @@ Fork libgit2dart, improve libgit2dart, send a pull request. ### Troubleshooting +#### Linux: + If you are developing on Linux using non-Debian based distrib you might encounter these errors: - Failed to load dynamic library: libpcre.so.3: cannot open shared object file: No such file or directory @@ -679,6 +679,16 @@ sudo ln -s /usr/lib64/libpcre.so /usr/lib64/libpcre.so.3 sudo ln -s /usr/lib64/libpcreposix.so /usr/lib64/libpcreposix.so.3 ``` +#### Windows: + +If you are developing on Windows you might encounter: + +- Failed to load dynamic library: error code 126 + +That happens because libgit2 dynamic library bundled with libgit2dart package is precompiled with ssh support, and it fails to find the `libssh2.dll`. + +To fix that error you should [build](https://github.com/libssh2/libssh2/blob/master/docs/INSTALL_CMAKE.md) libssh2, and place resulting `libssh2.dll` somewhere in system path (e.g. "Windows\System32"). + ### Ffigen To generate bindings with ffigen use (adjust paths to yours): @@ -693,7 +703,6 @@ To run all tests and generate coverage report make sure to have activated packag ```sh $ dart pub global activate coverage -$ dart pub global activate flutter_coverage_badge ``` And run: diff --git a/analysis_options.yaml b/analysis_options.yaml index 9b00425..fe612a1 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -4,6 +4,7 @@ analyzer: language: strict-casts: true strict-raw-types: true + strict-inference: true exclude: - lib/src/bindings/libgit2_bindings.dart diff --git a/bin/setup.dart b/bin/setup.dart index a71be0d..74bce74 100644 --- a/bin/setup.dart +++ b/bin/setup.dart @@ -5,7 +5,6 @@ import 'package:libgit2dart/libgit2dart.dart'; import 'package:libgit2dart/src/libgit2.dart'; import 'package:libgit2dart/src/util.dart'; import 'package:path/path.dart' as path; -import 'package:pub_cache/pub_cache.dart'; /// Copies prebuilt libgit2 library from package in '.pub_cache' into correct /// directory for [platform]. @@ -23,27 +22,24 @@ Future copyLibrary(String platform) async { ); } } else { - String? checkCache(PubCache pubCache) => - pubCache.getLatestVersion('libgit2dart')?.resolve()?.location.path; - - final libPath = checkCache(PubCache()) ?? - checkCache( - PubCache( - Directory( - path.join(Platform.environment['FLUTTER_ROOT']!, '.pub-cache'), - ), - ), - ); + final libPath = checkCache(); final libName = getLibName(); stdout.writeln('Copying libgit2 for $platform'); - final destination = path.join(libDir, platform); - Directory(destination).createSync(recursive: true); - File(path.join(libPath!, platform, libName)).copySync( - path.join(destination, libName), - ); + if (libPath == null) { + stdout.writeln( + "Couldn't find libgit2dart package.\n" + "Make sure to run 'dart pub get' to resolve dependencies.", + ); + } else { + final destination = path.join(libDir, platform); + Directory(destination).createSync(recursive: true); + File(path.join(libPath, platform, libName)).copySync( + path.join(destination, libName), + ); - stdout.writeln('Done! libgit2 for $platform is now available!'); + stdout.writeln('Done! libgit2 for $platform is now available!'); + } } } @@ -57,7 +53,9 @@ class CleanCommand extends Command { @override void run() { stdout.writeln('Cleaning...'); - Directory(libDir).deleteSync(recursive: true); + if (Directory(libDir).existsSync()) { + Directory(libDir).deleteSync(recursive: true); + } } } diff --git a/coverage.sh b/coverage.sh index 79f52cb..1ea3ef2 100755 --- a/coverage.sh +++ b/coverage.sh @@ -1,2 +1,2 @@ #!/bin/bash -dart test --coverage=coverage --test-randomize-ordering-seed random && dart pub global run coverage:format_coverage --lcov --check-ignore --in=coverage --out=coverage/lcov.info --report-on=lib && genhtml coverage/lcov.info -o coverage/ && dart pub global run flutter_coverage_badge \ No newline at end of file +dart test --coverage=coverage --test-randomize-ordering-seed random && dart pub global run coverage:format_coverage --lcov --check-ignore --in=coverage --out=coverage/lcov.info --report-on=lib && genhtml coverage/lcov.info -o coverage/ \ No newline at end of file diff --git a/coverage_badge.svg b/coverage_badge.svg deleted file mode 100644 index 0c4efc0..0000000 --- a/coverage_badge.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - coverage - coverage - 100% - 100% - - diff --git a/lib/src/annotated.dart b/lib/src/annotated.dart index c500dd9..0c4669e 100644 --- a/lib/src/annotated.dart +++ b/lib/src/annotated.dart @@ -84,6 +84,7 @@ class AnnotatedCommit extends Equatable { /// Pointer to pointer to memory address for allocated commit object. /// /// Note: For internal use. + @internal Pointer get pointer => _annotatedCommitPointer; /// Commit oid that the given annotated commit refers to. diff --git a/lib/src/bindings/commit.dart b/lib/src/bindings/commit.dart index f4a97c6..70a66ac 100644 --- a/lib/src/bindings/commit.dart +++ b/lib/src/bindings/commit.dart @@ -384,8 +384,48 @@ Pointer tree(Pointer commit) { void revert({ required Pointer repoPointer, required Pointer commitPointer, + required int mainline, + int? mergeFavor, + int? mergeFlags, + int? mergeFileFlags, + int? checkoutStrategy, + String? checkoutDirectory, + List? checkoutPaths, }) { - final error = libgit2.git_revert(repoPointer, commitPointer, nullptr); + final opts = calloc(); + libgit2.git_revert_options_init(opts, GIT_REVERT_OPTIONS_VERSION); + + opts.ref.mainline = mainline; + + if (mergeFavor != null) opts.ref.merge_opts.file_favor = mergeFavor; + if (mergeFlags != null) opts.ref.merge_opts.flags = mergeFlags; + if (mergeFileFlags != null) opts.ref.merge_opts.file_flags = mergeFileFlags; + + if (checkoutStrategy != null) { + opts.ref.checkout_opts.checkout_strategy = checkoutStrategy; + } + if (checkoutDirectory != null) { + opts.ref.checkout_opts.target_directory = checkoutDirectory.toChar(); + } + var pathPointers = >[]; + Pointer> strArray = nullptr; + if (checkoutPaths != null) { + pathPointers = checkoutPaths.map((e) => e.toChar()).toList(); + strArray = calloc(checkoutPaths.length); + for (var i = 0; i < checkoutPaths.length; i++) { + strArray[i] = pathPointers[i]; + } + opts.ref.checkout_opts.paths.strings = strArray; + opts.ref.checkout_opts.paths.count = checkoutPaths.length; + } + + final error = libgit2.git_revert(repoPointer, commitPointer, opts); + + for (final p in pathPointers) { + calloc.free(p); + } + calloc.free(strArray); + calloc.free(opts); if (error < 0) { throw LibGit2Error(libgit2.git_error_last()); @@ -403,11 +443,18 @@ Pointer revertCommit({ required Pointer revertCommitPointer, required Pointer ourCommitPointer, required int mainline, + int? mergeFavor, + int? mergeFlags, + int? mergeFileFlags, }) { final out = calloc>(); final opts = calloc(); libgit2.git_merge_options_init(opts, GIT_MERGE_OPTIONS_VERSION); + if (mergeFavor != null) opts.ref.file_favor = mergeFavor; + if (mergeFlags != null) opts.ref.flags = mergeFlags; + if (mergeFileFlags != null) opts.ref.file_flags = mergeFileFlags; + final error = libgit2.git_revert_commit( out, repoPointer, diff --git a/lib/src/bindings/index.dart b/lib/src/bindings/index.dart index c7e1d06..5ae8db9 100644 --- a/lib/src/bindings/index.dart +++ b/lib/src/bindings/index.dart @@ -292,6 +292,7 @@ void addFromBuffer({ void addAll({ required Pointer indexPointer, required List pathspec, + required int flags, }) { final pathspecC = calloc(); final pathPointers = pathspec.map((e) => e.toChar()).toList(); @@ -307,7 +308,7 @@ void addAll({ final error = libgit2.git_index_add_all( indexPointer, pathspecC, - 0, + flags, nullptr, nullptr, ); diff --git a/lib/src/bindings/libgit2_bindings.dart b/lib/src/bindings/libgit2_bindings.dart index f220431..e7c322b 100644 --- a/lib/src/bindings/libgit2_bindings.dart +++ b/lib/src/bindings/libgit2_bindings.dart @@ -2217,6 +2217,23 @@ class Libgit2 { int Function( ffi.Pointer, ffi.Pointer, ffi.Pointer)>(); + /// Return the prerelease state of the libgit2 library currently being + /// used. For nightly builds during active development, this will be + /// "alpha". Releases may have a "beta" or release candidate ("rc1", + /// "rc2", etc) prerelease. For a final release, this function returns + /// NULL. + /// + /// @return the name of the prerelease state or NULL + ffi.Pointer git_libgit2_prerelease() { + return _git_libgit2_prerelease(); + } + + late final _git_libgit2_prereleasePtr = + _lookup Function()>>( + 'git_libgit2_prerelease'); + late final _git_libgit2_prerelease = + _git_libgit2_prereleasePtr.asFunction Function()>(); + /// Query compile time options for libgit2. /// /// @return A combination of GIT_FEATURE_* values. @@ -9459,8 +9476,8 @@ class Libgit2 { /// See `git_tag_create()` for rules about valid names. /// /// Note that if the move succeeds, the old reference object will not - /// + be valid anymore, and should be freed immediately by the user using - /// + `git_reference_free()`. + /// be valid anymore, and should be freed immediately by the user using + /// `git_reference_free()`. /// /// @param out New reference object for the updated name. /// @@ -11868,7 +11885,7 @@ class Libgit2 { /// completes, resolve any conflicts and prepare a commit. /// /// For compatibility with git, the repository is put into a merging - /// state. Once the commit is done (or if the uses wishes to abort), + /// state. Once the commit is done (or if the user wishes to abort), /// you should clear this state by calling /// `git_repository_state_cleanup()`. /// @@ -15441,7 +15458,7 @@ class Libgit2 { /// global configuration file. /// /// This method will not guess the path to the xdg compatible - /// config file (.config/git/config). + /// config file (`.config/git/config`). /// /// @param out Pointer to a user-allocated git_buf in which to store the path /// @return 0 if a global configuration file has been found. Its path will be stored in `out`. @@ -15488,8 +15505,8 @@ class Libgit2 { /// Locate the path to the system configuration file /// - /// If /etc/gitconfig doesn't exist, it will look for - /// %PROGRAMFILES%\Git\etc\gitconfig. + /// If `/etc/gitconfig` doesn't exist, it will look for + /// `%PROGRAMFILES%\Git\etc\gitconfig`. /// /// @param out Pointer to a user-allocated git_buf in which to store the path /// @return 0 if a system configuration file has been @@ -15510,7 +15527,7 @@ class Libgit2 { /// Locate the path to the configuration file in ProgramData /// - /// Look for the file in %PROGRAMDATA%\Git\config used by portable git. + /// Look for the file in `%PROGRAMDATA%\Git\config` used by portable git. /// /// @param out Pointer to a user-allocated git_buf in which to store the path /// @return 0 if a ProgramData configuration file has been @@ -16083,8 +16100,8 @@ class Libgit2 { /// Return the current entry and advance the iterator /// - /// The pointers returned by this function are valid until the iterator - /// is freed. + /// The pointers returned by this function are valid until the next call + /// to `git_config_next` or until the iterator is freed. /// /// @param entry pointer to store the entry /// @param iter the iterator @@ -16922,7 +16939,8 @@ class Libgit2 { late final _git_error_clear = _git_error_clearPtr.asFunction(); - /// Set the error message string for this thread. + /// Set the error message string for this thread, using `printf`-style + /// formatting. /// /// This function is public so that custom ODB backends and the like can /// relay an error message through libgit2. Most regular users of libgit2 @@ -16935,7 +16953,31 @@ class Libgit2 { /// /// @param error_class One of the `git_error_t` enum above describing the /// general subsystem that is responsible for the error. - /// @param string The formatted error message to keep + /// @param fmt The `printf`-style format string; subsequent arguments must + /// be the arguments for the format string. + void git_error_set( + int error_class, + ffi.Pointer fmt, + ) { + return _git_error_set( + error_class, + fmt, + ); + } + + late final _git_error_setPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function(ffi.Int, ffi.Pointer)>>('git_error_set'); + late final _git_error_set = + _git_error_setPtr.asFunction)>(); + + /// Set the error message string for this thread. This function is like + /// `git_error_set` but takes a static string instead of a `printf`-style + /// format. + /// + /// @param error_class One of the `git_error_t` enum above describing the + /// general subsystem that is responsible for the error. + /// @param string The error message to keep /// @return 0 on success or -1 on failure int git_error_set_str( int error_class, @@ -30687,7 +30729,7 @@ abstract class git_error_t { static const int GIT_ERROR_FILESYSTEM = 30; static const int GIT_ERROR_PATCH = 31; static const int GIT_ERROR_WORKTREE = 32; - static const int GIT_ERROR_SHA1 = 33; + static const int GIT_ERROR_SHA = 33; static const int GIT_ERROR_HTTP = 34; static const int GIT_ERROR_INTERNAL = 35; } @@ -31200,12 +31242,15 @@ class git_status_options extends ffi.Struct { external int version; /// The `show` value is one of the `git_status_show_t` constants that - /// control which files to scan and in what order. + /// control which files to scan and in what order. The default is + /// `GIT_STATUS_SHOW_INDEX_AND_WORKDIR`. @ffi.Int32() external int show1; /// The `flags` value is an OR'ed combination of the - /// `git_status_opt_t` values above. + /// `git_status_opt_t` values above. The default is + /// `GIT_STATUS_OPT_DEFAULTS`, which matches git's default + /// behavior. @ffi.UnsignedInt() external int flags; @@ -32606,6 +32651,8 @@ const int GITERR_WORKTREE = 32; const int GITERR_SHA1 = 33; +const int GIT_ERROR_SHA1 = 33; + const int GIT_IDXENTRY_NAMEMASK = 4095; const int GIT_IDXENTRY_STAGEMASK = 12288; @@ -32710,14 +32757,16 @@ const int GIT_CREDTYPE_SSH_MEMORY = 64; const int GIT_EMAIL_CREATE_OPTIONS_VERSION = 1; -const String LIBGIT2_VERSION = '1.4.3'; +const String LIBGIT2_VERSION = '1.5.0'; const int LIBGIT2_VER_MAJOR = 1; -const int LIBGIT2_VER_MINOR = 4; +const int LIBGIT2_VER_MINOR = 5; -const int LIBGIT2_VER_REVISION = 3; +const int LIBGIT2_VER_REVISION = 0; const int LIBGIT2_VER_PATCH = 0; -const String LIBGIT2_SOVERSION = '1.4'; +const int LIBGIT2_VER_PRERELEASE = 0; + +const String LIBGIT2_SOVERSION = '1.5'; diff --git a/lib/src/bindings/merge.dart b/lib/src/bindings/merge.dart index 88229bf..2e16d11 100644 --- a/lib/src/bindings/merge.dart +++ b/lib/src/bindings/merge.dart @@ -186,30 +186,39 @@ String mergeFile({ libgit2.git_merge_file_input_init(theirsC, GIT_MERGE_FILE_INPUT_VERSION); ancestorC.ref.ptr = ancestor.toChar(); ancestorC.ref.size = ancestor.length; + Pointer ancestorLabelC = nullptr; oursC.ref.ptr = ours.toChar(); oursC.ref.size = ours.length; + Pointer oursLabelC = nullptr; theirsC.ref.ptr = theirs.toChar(); theirsC.ref.size = theirs.length; + Pointer theirsLabelC = nullptr; final opts = calloc(); libgit2.git_merge_file_options_init(opts, GIT_MERGE_FILE_OPTIONS_VERSION); opts.ref.favor = favor; opts.ref.flags = flags; if (ancestorLabel.isNotEmpty) { - opts.ref.ancestor_label = ancestorLabel.toChar(); + ancestorLabelC = ancestorLabel.toChar(); + opts.ref.ancestor_label = ancestorLabelC; } if (oursLabel.isNotEmpty) { - opts.ref.our_label = oursLabel.toChar(); + oursLabelC = oursLabel.toChar(); + opts.ref.our_label = oursLabelC; } if (theirsLabel.isNotEmpty) { - opts.ref.their_label = theirsLabel.toChar(); + theirsLabelC = theirsLabel.toChar(); + opts.ref.their_label = theirsLabelC; } libgit2.git_merge_file(out, ancestorC, oursC, theirsC, opts); calloc.free(ancestorC); + calloc.free(ancestorLabelC); calloc.free(oursC); + calloc.free(oursLabelC); calloc.free(theirsC); + calloc.free(theirsLabelC); calloc.free(opts); final result = out.ref.ptr.toDartString(length: out.ref.len); @@ -226,17 +235,43 @@ String mergeFile({ String mergeFileFromIndex({ required Pointer repoPointer, required Pointer? ancestorPointer, + required String ancestorLabel, required Pointer oursPointer, + required String oursLabel, required Pointer theirsPointer, + required String theirsLabel, + required int favor, + required int flags, }) { final out = calloc(); + final opts = calloc(); + Pointer ancestorLabelC = nullptr; + Pointer oursLabelC = nullptr; + Pointer theirsLabelC = nullptr; + + libgit2.git_merge_file_options_init(opts, GIT_MERGE_FILE_OPTIONS_VERSION); + opts.ref.favor = favor; + opts.ref.flags = flags; + if (ancestorLabel.isNotEmpty) { + ancestorLabelC = ancestorLabel.toChar(); + opts.ref.ancestor_label = ancestorLabelC; + } + if (oursLabel.isNotEmpty) { + oursLabelC = oursLabel.toChar(); + opts.ref.our_label = oursLabelC; + } + if (theirsLabel.isNotEmpty) { + theirsLabelC = theirsLabel.toChar(); + opts.ref.their_label = theirsLabelC; + } + final error = libgit2.git_merge_file_from_index( out, repoPointer, ancestorPointer ?? nullptr, oursPointer, theirsPointer, - nullptr, + opts, ); late final String result; @@ -244,6 +279,10 @@ String mergeFileFromIndex({ result = out.ref.ptr.toDartString(length: out.ref.len); } + calloc.free(ancestorLabelC); + calloc.free(oursLabelC); + calloc.free(theirsLabelC); + calloc.free(opts); calloc.free(out); if (error < 0) { diff --git a/lib/src/bindings/reset.dart b/lib/src/bindings/reset.dart index cca5dba..7b45c3f 100644 --- a/lib/src/bindings/reset.dart +++ b/lib/src/bindings/reset.dart @@ -17,13 +17,48 @@ import 'package:libgit2dart/src/util.dart'; /// HARD reset will trigger a MIXED reset and the working directory will be /// replaced with the content of the index. (Untracked and ignored files will /// be left alone, however.) +/// +/// Throws a [LibGit2Error] if error occured. void reset({ required Pointer repoPointer, required Pointer targetPointer, required int resetType, - required Pointer checkoutOptsPointer, + int? strategy, + String? checkoutDirectory, + List? pathspec, }) { - libgit2.git_reset(repoPointer, targetPointer, resetType, checkoutOptsPointer); + final opts = calloc(); + libgit2.git_checkout_options_init(opts, GIT_CHECKOUT_OPTIONS_VERSION); + + if (strategy != null) { + opts.ref.checkout_strategy = strategy; + } + if (checkoutDirectory != null) { + opts.ref.target_directory = checkoutDirectory.toChar(); + } + var pathPointers = >[]; + Pointer> strArray = nullptr; + if (pathspec != null) { + pathPointers = pathspec.map((e) => e.toChar()).toList(); + strArray = calloc(pathspec.length); + for (var i = 0; i < pathspec.length; i++) { + strArray[i] = pathPointers[i]; + } + opts.ref.paths.strings = strArray; + opts.ref.paths.count = pathspec.length; + } + + final error = libgit2.git_reset(repoPointer, targetPointer, resetType, opts); + + for (final p in pathPointers) { + calloc.free(p); + } + calloc.free(strArray); + calloc.free(opts); + + if (error < 0) { + throw LibGit2Error(libgit2.git_error_last()); + } } /// Updates some entries in the index from the target commit tree. diff --git a/lib/src/bindings/revwalk.dart b/lib/src/bindings/revwalk.dart index 93d9bf6..1869067 100644 --- a/lib/src/bindings/revwalk.dart +++ b/lib/src/bindings/revwalk.dart @@ -145,11 +145,12 @@ void pushRange({ List> walk({ required Pointer repoPointer, required Pointer walkerPointer, + required int limit, }) { final result = >[]; var error = 0; - while (error == 0) { + void next() { final oid = calloc(); error = libgit2.git_revwalk_next(oid, walkerPointer); if (error == 0) { @@ -158,10 +159,21 @@ List> walk({ oidPointer: oid, ); result.add(commit); + calloc.free(oid); } else { - break; + calloc.free(oid); + return; + } + } + + if (limit == 0) { + while (error == 0) { + next(); + } + } else { + for (var i = 0; i < limit; i++) { + next(); } - calloc.free(oid); } return result; diff --git a/lib/src/bindings/worktree.dart b/lib/src/bindings/worktree.dart index 663c1f2..61923da 100644 --- a/lib/src/bindings/worktree.dart +++ b/lib/src/bindings/worktree.dart @@ -96,8 +96,16 @@ bool isPrunable(Pointer wt) { /// Prune working tree. /// /// Prune the working tree, that is remove the git data structures on disk. -void prune(Pointer wt) { - libgit2.git_worktree_prune(wt, nullptr); +void prune({required Pointer worktreePointer, int? flags}) { + final opts = calloc(); + libgit2.git_worktree_prune_options_init( + opts, + GIT_WORKTREE_PRUNE_OPTIONS_VERSION, + ); + + if (flags != null) opts.ref.flags = flags; + + libgit2.git_worktree_prune(worktreePointer, opts); } /// List names of linked working trees. diff --git a/lib/src/blob.dart b/lib/src/blob.dart index 89ea241..5b27d78 100644 --- a/lib/src/blob.dart +++ b/lib/src/blob.dart @@ -12,6 +12,7 @@ class Blob extends Equatable { /// blob object in memory. /// /// Note: For internal use. Use [Blob.lookup] instead. + @internal Blob(this._blobPointer) { _finalizer.attach(this, _blobPointer, detach: this); } @@ -30,6 +31,7 @@ class Blob extends Equatable { /// Pointer to memory address for allocated blob object. /// /// Note: For internal use. + @internal Pointer get pointer => _blobPointer; /// Creates a new blob from a [content] string and writes it to ODB. diff --git a/lib/src/branch.dart b/lib/src/branch.dart index 976598c..c221105 100644 --- a/lib/src/branch.dart +++ b/lib/src/branch.dart @@ -15,6 +15,7 @@ class Branch extends Equatable { /// Note: For internal use. Instead, use one of: /// - [Branch.create] /// - [Branch.lookup] + @internal Branch(this._branchPointer) { _finalizer.attach(this, _branchPointer, detach: this); } @@ -74,6 +75,7 @@ class Branch extends Equatable { /// Pointer to memory address for allocated branch object. /// /// Note: For internal use. + @internal Pointer get pointer => _branchPointer; /// Returns a list of branches that can be found in a [repo]sitory for diff --git a/lib/src/commit.dart b/lib/src/commit.dart index 9fbb59e..3cd7ef1 100644 --- a/lib/src/commit.dart +++ b/lib/src/commit.dart @@ -13,6 +13,7 @@ class Commit extends Equatable { /// commit object in memory. /// /// Note: For internal use. Use [Commit.lookup] instead. + @internal Commit(this._commitPointer) { _finalizer.attach(this, _commitPointer, detach: this); } @@ -31,6 +32,7 @@ class Commit extends Equatable { /// Pointer to memory address for allocated commit object. /// /// Note: For internal use. + @internal Pointer get pointer => _commitPointer; /// Creates new commit in the [repo]sitory. @@ -189,23 +191,64 @@ class Commit extends Equatable { /// Reverts commit, producing changes in the index and working directory. /// + /// [mainline] is parent of the commit if it is a merge (i.e. 1, 2, etc.). + /// + /// [mergeFavor] is one of the optional [GitMergeFileFavor] flags for + /// handling conflicting content. + /// + /// [mergeFlags] is optional combination of [GitMergeFlag] flags. + /// + /// [mergeFileFlags] is optional combination of [GitMergeFileFlag] flags. + /// + /// [checkoutStrategy] is optional combination of [GitCheckout] flags. + /// + /// [checkoutDirectory] is optional alternative checkout path to workdir. + /// + /// [checkoutPaths] is optional list of files to checkout (by default all + /// paths are processed). + /// /// Throws a [LibGit2Error] if error occured. - void revert() { + void revert({ + int mainline = 0, + GitMergeFileFavor? mergeFavor, + Set? mergeFlags, + Set? mergeFileFlags, + Set? checkoutStrategy, + String? checkoutDirectory, + List? checkoutPaths, + }) { bindings.revert( repoPointer: bindings.owner(_commitPointer), commitPointer: _commitPointer, + mainline: mainline, + mergeFavor: mergeFavor?.value, + mergeFlags: mergeFlags?.fold(0, (acc, e) => acc! | e.value), + mergeFileFlags: mergeFileFlags?.fold(0, (acc, e) => acc! | e.value), + checkoutStrategy: checkoutStrategy?.fold(0, (acc, e) => acc! | e.value), + checkoutDirectory: checkoutDirectory, + checkoutPaths: checkoutPaths, ); } /// Reverts commit against provided [commit], producing an index that /// reflects the result of the revert. /// - /// [mainline] is parent of the commit if it is a merge (i.e. 1, 2). + /// [mainline] is parent of the commit if it is a merge (i.e. 1, 2, etc.). + /// + /// [mergeFavor] is one of the optional [GitMergeFileFavor] flags for + /// handling conflicting content. + /// + /// [mergeFlags] is optional combination of [GitMergeFlag] flags. + /// + /// [mergeFileFlags] is optional combination of [GitMergeFileFlag] flags. /// /// Throws a [LibGit2Error] if error occured. Index revertTo({ required Commit commit, int mainline = 0, + GitMergeFileFavor? mergeFavor, + Set? mergeFlags, + Set? mergeFileFlags, }) { return Index( bindings.revertCommit( @@ -213,6 +256,9 @@ class Commit extends Equatable { revertCommitPointer: _commitPointer, ourCommitPointer: commit.pointer, mainline: mainline, + mergeFavor: mergeFavor?.value, + mergeFlags: mergeFlags?.fold(0, (acc, e) => acc! | e.value), + mergeFileFlags: mergeFileFlags?.fold(0, (acc, e) => acc! | e.value), ), ); } diff --git a/lib/src/config.dart b/lib/src/config.dart index 7ccce28..5d236de 100644 --- a/lib/src/config.dart +++ b/lib/src/config.dart @@ -20,6 +20,7 @@ class Config with IterableMixin { /// - [Config.system] /// - [Config.global] /// - [Config.xdg] + @internal Config(this._configPointer) { _finalizer.attach(this, _configPointer, detach: this); } diff --git a/lib/src/diff.dart b/lib/src/diff.dart index 9ca2605..3425ce2 100644 --- a/lib/src/diff.dart +++ b/lib/src/diff.dart @@ -21,6 +21,7 @@ class Diff extends Equatable { /// - [Diff.treeToWorkdirWithIndex] /// - [Diff.treeToTree] /// - [Diff.parse] + @internal Diff(this._diffPointer) { _finalizer.attach(this, _diffPointer, detach: this); } @@ -276,6 +277,7 @@ class Diff extends Equatable { /// Pointer to memory address for allocated diff object. /// /// Note: For internal use. + @internal Pointer get pointer => _diffPointer; /// How many diff records are there in a diff. @@ -471,6 +473,9 @@ final _finalizer = Finalizer>( class DiffDelta extends Equatable { /// Initializes a new instance of [DiffDelta] class from provided /// pointer to diff delta object in memory. + /// + /// Note: For internal use. + @internal const DiffDelta(this._diffDeltaPointer); /// Pointer to memory address for allocated diff delta object. @@ -578,6 +583,7 @@ class DiffStats { /// pointer to diff stats object in memory. /// /// Note: For internal use. + @internal DiffStats(this._diffStatsPointer) { _statsFinalizer.attach(this, _diffStatsPointer, detach: this); } diff --git a/lib/src/error.dart b/lib/src/error.dart index 97eec9b..5a52968 100644 --- a/lib/src/error.dart +++ b/lib/src/error.dart @@ -3,9 +3,12 @@ import 'dart:ffi'; import 'package:libgit2dart/src/bindings/libgit2_bindings.dart'; import 'package:libgit2dart/src/extensions.dart'; +import 'package:meta/meta.dart'; /// Details of the last error that occurred. class LibGit2Error { + /// Note: For internal use. + @internal LibGit2Error(this._errorPointer); final Pointer _errorPointer; diff --git a/lib/src/git_types.dart b/lib/src/git_types.dart index 564e960..3659324 100644 --- a/lib/src/git_types.dart +++ b/lib/src/git_types.dart @@ -1173,3 +1173,37 @@ enum GitBlobFilter { const GitBlobFilter(this.value); final int value; } + +/// Flags for APIs that add files matching pathspec. +enum GitIndexAddOption { + defaults(0), + + /// Skip the checking of ignore rules. + force(1), + + /// Disable glob expansion and force exact matching of files in working + /// directory. + disablePathspecMatch(2), + + /// Check that each entry in the pathspec is an exact match to a filename on + /// disk is either not ignored or already in the index. + checkPathspec(4); + + const GitIndexAddOption(this.value); + final int value; +} + +/// Flags to alter working tree pruning behavior. +enum GitWorktree { + /// Prune working tree even if working tree is valid. + pruneValid(1), + + /// Prune working tree even if it is locked. + pruneLocked(2), + + /// Prune checked out working tree. + pruneWorkingTree(4); + + const GitWorktree(this.value); + final int value; +} diff --git a/lib/src/index.dart b/lib/src/index.dart index 63edaad..63ab0be 100644 --- a/lib/src/index.dart +++ b/lib/src/index.dart @@ -13,6 +13,7 @@ class Index with IterableMixin { /// pointer to index object in memory. /// /// Note: For internal use. + @internal Index(this._indexPointer) { _finalizer.attach(this, _indexPointer, detach: this); } @@ -31,6 +32,7 @@ class Index with IterableMixin { /// Pointer to memory address for allocated index object. /// /// Note: For internal use. + @internal Pointer get pointer => _indexPointer; /// Full path to the index file on disk. @@ -215,9 +217,18 @@ class Index with IterableMixin { /// that matches will be added to the index (either updating an existing /// entry or adding a new entry). /// + /// [flags] is optional combination of [GitIndexAddOption] flags. + /// /// Throws a [LibGit2Error] if error occured. - void addAll(List pathspec) { - bindings.addAll(indexPointer: _indexPointer, pathspec: pathspec); + void addAll( + List pathspec, { + Set flags = const {GitIndexAddOption.defaults}, + }) { + bindings.addAll( + indexPointer: _indexPointer, + pathspec: pathspec, + flags: flags.fold(0, (acc, e) => acc | e.value), + ); } /// Updates all index entries to match the working directory. @@ -330,6 +341,7 @@ class IndexEntry extends Equatable { /// Initializes a new instance of [IndexEntry] class. /// /// Note: For internal use. + @internal const IndexEntry(this._indexEntryPointer); final Pointer _indexEntryPointer; @@ -337,6 +349,7 @@ class IndexEntry extends Equatable { /// Pointer to memory address for allocated index entry object. /// /// Note: For internal use. + @internal Pointer get pointer => _indexEntryPointer; /// [Oid] of the index entry. @@ -378,6 +391,7 @@ class ConflictEntry { /// Initializes a new instance of [ConflictEntry] class. /// /// Note: For internal use. + @internal const ConflictEntry( this._indexPointer, this._path, diff --git a/lib/src/merge.dart b/lib/src/merge.dart index 015724b..da080e0 100644 --- a/lib/src/merge.dart +++ b/lib/src/merge.dart @@ -265,18 +265,43 @@ class Merge { /// given common [ancestor] as the baseline, producing a string that reflects /// the merge result containing possible conflicts. /// + /// [ancestorLabel] is optional label for the ancestor file side of the + /// conflict which will be prepended to labels in diff3-format merge files. + /// + /// [oursLabel] is optional label for our file side of the conflict which + /// will be prepended to labels in merge files. + /// + /// [theirsLabel] is optional label for their file side of the conflict which + /// will be prepended to labels in merge files. + /// + /// [favor] is one of the [GitMergeFileFavor] flags for handling conflicting + /// content. Defaults to [GitMergeFileFavor.normal]. + /// + /// [flags] is a combination of [GitMergeFileFlag] flags. Defaults to + /// [GitMergeFileFlag.defaults]. + /// /// Throws a [LibGit2Error] if error occured. static String fileFromIndex({ required Repository repo, required IndexEntry? ancestor, + String ancestorLabel = '', required IndexEntry ours, + String oursLabel = '', required IndexEntry theirs, + String theirsLabel = '', + GitMergeFileFavor favor = GitMergeFileFavor.normal, + Set flags = const {GitMergeFileFlag.defaults}, }) { return bindings.mergeFileFromIndex( repoPointer: repo.pointer, ancestorPointer: ancestor?.pointer, + ancestorLabel: ancestorLabel, oursPointer: ours.pointer, + oursLabel: oursLabel, theirsPointer: theirs.pointer, + theirsLabel: theirsLabel, + favor: favor.value, + flags: flags.fold(0, (acc, e) => acc | e.value), ); } diff --git a/lib/src/note.dart b/lib/src/note.dart index 1dd874b..f29aac0 100644 --- a/lib/src/note.dart +++ b/lib/src/note.dart @@ -11,6 +11,7 @@ class Note extends Equatable { /// pointer to note and annotatedOid objects in memory. /// /// Note: For internal use. Use [Note.lookup] instead. + @internal Note(this._notePointer, this._annotatedOidPointer) { _finalizer.attach(this, _notePointer, detach: this); } diff --git a/lib/src/odb.dart b/lib/src/odb.dart index 0969f2f..a3c486f 100644 --- a/lib/src/odb.dart +++ b/lib/src/odb.dart @@ -12,6 +12,7 @@ class Odb extends Equatable { /// pointer to Odb object in memory. /// /// Note: For internal use. + @internal Odb(this._odbPointer) { _finalizer.attach(this, _odbPointer, detach: this); } @@ -32,6 +33,7 @@ class Odb extends Equatable { /// Pointer to memory address for allocated oid object. /// /// Note: For internal use. + @internal Pointer get pointer => _odbPointer; /// Adds an on-disk alternate to an existing Object DB. diff --git a/lib/src/oid.dart b/lib/src/oid.dart index ceca4b1..0e50b1b 100644 --- a/lib/src/oid.dart +++ b/lib/src/oid.dart @@ -14,6 +14,7 @@ class Oid extends Equatable { /// pointer to Oid object in memory. /// /// Note: For internal use. Use [Oid.fromSHA] instead. + @internal Oid(this._oidPointer); /// Initializes a new instance of [Oid] class by determining if an object can @@ -41,6 +42,9 @@ class Oid extends Equatable { /// Initializes a new instance of [Oid] class from provided raw git_oid /// structure. + /// + /// Note: For internal use. + @internal Oid.fromRaw(git_oid raw) { _oidPointer = bindings.fromRaw(raw.id); } @@ -50,6 +54,7 @@ class Oid extends Equatable { /// Pointer to memory address for allocated oid object. /// /// Note: For internal use. + @internal Pointer get pointer => _oidPointer; /// Hexadecimal SHA string. diff --git a/lib/src/packbuilder.dart b/lib/src/packbuilder.dart index f151b07..c9a9ea6 100644 --- a/lib/src/packbuilder.dart +++ b/lib/src/packbuilder.dart @@ -2,6 +2,7 @@ import 'dart:ffi'; import 'package:libgit2dart/libgit2dart.dart'; import 'package:libgit2dart/src/bindings/libgit2_bindings.dart'; import 'package:libgit2dart/src/bindings/packbuilder.dart' as bindings; +import 'package:meta/meta.dart'; class PackBuilder { /// Initializes a new instance of [PackBuilder] class. @@ -9,6 +10,7 @@ class PackBuilder { /// Throws a [LibGit2Error] if error occured. /// /// Note: For internal use. + @internal PackBuilder(Repository repo) { _packbuilderPointer = bindings.init(repo.pointer); _finalizer.attach(this, _packbuilderPointer, detach: this); diff --git a/lib/src/patch.dart b/lib/src/patch.dart index 16e5b25..c837afa 100644 --- a/lib/src/patch.dart +++ b/lib/src/patch.dart @@ -18,6 +18,7 @@ class Patch extends Equatable { /// - [Patch.fromBlobAndBuffer] /// - [Patch.fromBuffers] /// - [Patch.fromDiff] + @internal Patch(this._patchPointer) { _finalizer.attach(this, _patchPointer, detach: this); } diff --git a/lib/src/reference.dart b/lib/src/reference.dart index 1f45270..c5d4471 100644 --- a/lib/src/reference.dart +++ b/lib/src/reference.dart @@ -17,6 +17,7 @@ class Reference extends Equatable { /// Note: For internal use. Instead, use one of: /// - [Reference.create] /// - [Reference.lookup] + @internal Reference(this._refPointer) { _finalizer.attach(this, _refPointer, detach: this); } @@ -87,6 +88,7 @@ class Reference extends Equatable { /// Pointer to memory address for allocated reference object. /// /// Note: For internal use. + @internal Pointer get pointer => _refPointer; /// Deletes an existing reference with provided [name]. diff --git a/lib/src/refspec.dart b/lib/src/refspec.dart index 679abca..66c9405 100644 --- a/lib/src/refspec.dart +++ b/lib/src/refspec.dart @@ -11,6 +11,7 @@ class Refspec extends Equatable { /// from provided pointer to refspec object in memory. /// /// Note: For internal use. + @internal const Refspec(this._refspecPointer); /// Pointer to memory address for allocated refspec object. diff --git a/lib/src/remote.dart b/lib/src/remote.dart index 9144861..d74dbf3 100644 --- a/lib/src/remote.dart +++ b/lib/src/remote.dart @@ -327,6 +327,7 @@ class TransferProgress { /// pointer to transfer progress object in memory. /// /// Note: For internal use. + @internal const TransferProgress(this._transferProgressPointer); /// Pointer to memory address for allocated transfer progress object. diff --git a/lib/src/repository.dart b/lib/src/repository.dart index 2cc9f04..03c7190 100644 --- a/lib/src/repository.dart +++ b/lib/src/repository.dart @@ -23,6 +23,7 @@ class Repository extends Equatable { /// - [Repository.init] /// - [Repository.open] /// - [Repository.clone] + @internal Repository(Pointer pointer) { _repoPointer = pointer; _finalizer.attach(this, _repoPointer, detach: this); @@ -161,6 +162,7 @@ class Repository extends Equatable { /// Pointer to memory address for allocated repository object. /// /// Note: For internal use. + @internal Pointer get pointer => _repoPointer; /// Looks for a git repository and return its path. The lookup start from @@ -555,8 +557,23 @@ class Repository extends Equatable { /// /// [resetType] is one of the [GitReset] flags. /// + /// [strategy], [checkoutDirectory] and [pathspec] are optional checkout + /// options to be used for a HARD reset. + /// + /// [strategy] is optional combination of [GitCheckout] flags. + /// + /// [checkoutDirectory] is optional alternative checkout path to workdir. + /// + /// [pathspec] is optional list of files to checkout. + /// /// Throws a [LibGit2Error] if error occured. - void reset({required Oid oid, required GitReset resetType}) { + void reset({ + required Oid oid, + required GitReset resetType, + Set? strategy, + String? checkoutDirectory, + List? pathspec, + }) { final object = object_bindings.lookup( repoPointer: _repoPointer, oidPointer: oid.pointer, @@ -567,7 +584,9 @@ class Repository extends Equatable { repoPointer: _repoPointer, targetPointer: object, resetType: resetType.value, - checkoutOptsPointer: nullptr, + strategy: strategy?.fold(0, (acc, e) => acc! | e.value), + checkoutDirectory: checkoutDirectory, + pathspec: pathspec, ); object_bindings.free(object); diff --git a/lib/src/revwalk.dart b/lib/src/revwalk.dart index c3a08c7..78b8491 100644 --- a/lib/src/revwalk.dart +++ b/lib/src/revwalk.dart @@ -2,6 +2,7 @@ import 'dart:ffi'; import 'package:libgit2dart/libgit2dart.dart'; import 'package:libgit2dart/src/bindings/libgit2_bindings.dart'; import 'package:libgit2dart/src/bindings/revwalk.dart' as bindings; +import 'package:meta/meta.dart'; class RevWalk { /// Initializes a new instance of the [RevWalk] class. @@ -15,15 +16,20 @@ class RevWalk { /// Pointer to memory address for allocated [RevWalk] object. /// /// Note: For internal use. + @internal Pointer get pointer => _revWalkPointer; /// Returns the list of commits from the revision walk. /// + /// [limit] is optional number of commits to walk (by default walks through + /// all of the commits pushed onto the walker). + /// /// Default sorting is reverse chronological order (default in git). - List walk() { + List walk({int limit = 0}) { final pointers = bindings.walk( repoPointer: bindings.repository(_revWalkPointer), walkerPointer: _revWalkPointer, + limit: limit, ); return pointers.map((e) => Commit(e)).toList(); diff --git a/lib/src/signature.dart b/lib/src/signature.dart index c533191..620b33c 100644 --- a/lib/src/signature.dart +++ b/lib/src/signature.dart @@ -16,6 +16,7 @@ class Signature extends Equatable { /// Note: For internal use. Instead, use one of: /// - [Signature.create] /// - [Signature.defaultSignature] + @internal Signature(Pointer pointer) { _signaturePointer = bindings.duplicate(pointer); _finalizer.attach(this, _signaturePointer, detach: this); @@ -62,6 +63,7 @@ class Signature extends Equatable { /// Pointer to memory address for allocated signature object. /// /// Note: For internal use. + @internal Pointer get pointer => _signaturePointer; /// Full name of the author. diff --git a/lib/src/stash.dart b/lib/src/stash.dart index 85f5ff2..bbd1267 100644 --- a/lib/src/stash.dart +++ b/lib/src/stash.dart @@ -7,6 +7,9 @@ import 'package:meta/meta.dart'; class Stash extends Equatable { /// Initializes a new instance of [Stash] class from provided stash [index], /// [message] and [oid]. + /// + /// Note: For internal use. Use [Stash.create] instead to create stash. + @internal const Stash({ required this.index, required this.message, diff --git a/lib/src/tag.dart b/lib/src/tag.dart index 8a9e26a..14965e8 100644 --- a/lib/src/tag.dart +++ b/lib/src/tag.dart @@ -13,6 +13,7 @@ class Tag extends Equatable { /// tag object in memory. /// /// Note: For internal use. Use [Tag.lookup] instead. + @internal Tag(this._tagPointer) { _finalizer.attach(this, _tagPointer, detach: this); } diff --git a/lib/src/tree.dart b/lib/src/tree.dart index 5ab1a73..d3a479a 100644 --- a/lib/src/tree.dart +++ b/lib/src/tree.dart @@ -12,6 +12,7 @@ class Tree extends Equatable { /// tree object in memory. /// /// Note: For internal use. Use [Tree.lookup] instead. + @internal Tree(this._treePointer) { _finalizer.attach(this, _treePointer, detach: this); } @@ -30,6 +31,7 @@ class Tree extends Equatable { /// Pointer to memory address for allocated tree object. /// /// Note: For internal use. + @internal Pointer get pointer => _treePointer; /// List with tree entries of a tree. @@ -113,6 +115,7 @@ class TreeEntry extends Equatable { /// tree entry object in memory. /// /// Note: For internal use. + @internal const TreeEntry(this._treeEntryPointer); /// Initializes a new instance of [TreeEntry] class from provided pointer to diff --git a/lib/src/util.dart b/lib/src/util.dart index 001e488..3ba855c 100644 --- a/lib/src/util.dart +++ b/lib/src/util.dart @@ -1,13 +1,15 @@ // coverage:ignore-file +import 'dart:convert'; import 'dart:ffi'; import 'dart:io'; import 'package:libgit2dart/src/bindings/libgit2_bindings.dart'; import 'package:libgit2dart/src/bindings/libgit2_opts_bindings.dart'; import 'package:path/path.dart' as path; +import 'package:pub_semver/pub_semver.dart'; -const libgit2Version = '1.4.3'; +const libgit2Version = '1.5.0'; final libDir = path.join('.dart_tool', 'libgit2'); String getLibName() { @@ -24,6 +26,20 @@ String getLibName() { return 'libgit2-$libgit2Version.$ext'; } +/// Returns location of the most recent verison of the libgit2dart package +/// contained in the cache. +String? checkCache() { + final cache = json.decode( + Process.runSync('dart', ['pub', 'cache', 'list']).stdout as String, + ) as Map; + final packages = cache['packages'] as Map; + final libPackages = packages['libgit2dart'] as Map?; + final versions = libPackages?.keys.map((e) => Version.parse(e)).toList(); + final latestVersion = libPackages?[Version.primary(versions!).toString()] + as Map?; + return latestVersion?['location'] as String?; +} + /// Checks if [File]/[Link] exists for [path]. bool _doesFileExist(String path) { return File(path).existsSync() || Link(path).existsSync(); @@ -52,6 +68,25 @@ String? _resolveLibPath(String name) { libPath = path.join(path.dirname(Platform.resolvedExecutable), 'lib', name); if (_doesFileExist(libPath)) return libPath; + // If lib is installed in system dir. + if (Platform.isMacOS || Platform.isLinux) { + final paths = [ + '/usr/local/lib/libgit2.$libgit2Version.dylib', + '/usr/local/lib/libgit2.so.$libgit2Version', + '/usr/lib64/libgit2.so.$libgit2Version' + ]; + for (final path in paths) { + if (_doesFileExist(path)) return path; + } + } + + // If lib is in '.pub_cache' folder. + final cachedLocation = checkCache(); + if (cachedLocation != null) { + libPath = path.join(cachedLocation, Platform.operatingSystem, name); + if (_doesFileExist(libPath)) return libPath; + } + return null; } diff --git a/lib/src/worktree.dart b/lib/src/worktree.dart index 32939c6..d78e7cc 100644 --- a/lib/src/worktree.dart +++ b/lib/src/worktree.dart @@ -77,10 +77,15 @@ class Worktree extends Equatable { /// Throws a [LibGit2Error] if error occured. bool get isPrunable => bindings.isPrunable(_worktreePointer); - /// Prunes working tree. + /// Prunes working tree, that is removes the git data structures on disk. /// - /// Prune the working tree, that is remove the git data structures on disk. - void prune() => bindings.prune(_worktreePointer); + /// [flags] is optional combination of [GitWorktree] flags. + void prune([Set? flags]) { + bindings.prune( + worktreePointer: _worktreePointer, + flags: flags?.fold(0, (acc, e) => acc! | e.value), + ); + } /// Whether worktree is valid. /// diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 325acb8..64d842a 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -20,6 +20,6 @@ target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) # List of absolute paths to libraries that should be bundled with the plugin set(libgit2dart_bundled_libraries - "${CMAKE_CURRENT_SOURCE_DIR}/libgit2-1.4.3.so" + "${CMAKE_CURRENT_SOURCE_DIR}/libgit2-1.5.0.so" PARENT_SCOPE ) diff --git a/linux/libgit2-1.4.3.so b/linux/libgit2-1.4.3.so deleted file mode 100644 index d5f1411..0000000 Binary files a/linux/libgit2-1.4.3.so and /dev/null differ diff --git a/linux/libgit2-1.5.0.so b/linux/libgit2-1.5.0.so new file mode 100644 index 0000000..77a2487 Binary files /dev/null and b/linux/libgit2-1.5.0.so differ diff --git a/macos/libgit2-1.4.3.dylib b/macos/libgit2-1.4.3.dylib deleted file mode 100644 index 28a0046..0000000 Binary files a/macos/libgit2-1.4.3.dylib and /dev/null differ diff --git a/macos/libgit2-1.5.0.dylib b/macos/libgit2-1.5.0.dylib new file mode 100644 index 0000000..105751d Binary files /dev/null and b/macos/libgit2-1.5.0.dylib differ diff --git a/macos/libgit2dart.podspec b/macos/libgit2dart.podspec index 756018f..5ccfc67 100644 --- a/macos/libgit2dart.podspec +++ b/macos/libgit2dart.podspec @@ -4,7 +4,7 @@ # Pod::Spec.new do |s| s.name = 'libgit2dart' - s.version = '1.1.1' + s.version = '1.2.2' s.summary = 'Dart bindings to libgit2.' s.description = <<-DESC Dart bindings to libgit2. @@ -15,7 +15,7 @@ Dart bindings to libgit2. s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'FlutterMacOS' - s.vendored_libraries = 'libgit2-1.4.3.dylib' + s.vendored_libraries = 'libgit2-1.5.0.dylib' s.platform = :osx, '10.11' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } diff --git a/pubspec.yaml b/pubspec.yaml index 696d650..d0c47a5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,24 +2,24 @@ name: libgit2dart description: Dart bindings to libgit2, provides ability to use libgit2 library in Dart and Flutter. -version: 1.1.1 +version: 1.2.2 homepage: https://github.com/SkinnyMind/libgit2dart environment: - sdk: ">=2.17.0 <3.0.0" - flutter: ">=3.0.0" + sdk: ">=2.18.0 <3.0.0" + flutter: ">=3.3.0" dependencies: args: ^2.3.0 equatable: ^2.0.3 - ffi: ^1.1.2 + ffi: ^2.0.0 meta: ^1.7.0 path: ^1.8.1 - pub_cache: ^0.3.1 + pub_semver: ^2.1.3 dev_dependencies: - ffigen: ^5.0.0 + ffigen: ^6.0.1 lints: ^2.0.0 test: ^1.20.0 diff --git a/test/commit_test.dart b/test/commit_test.dart index 7e95471..a68f9c0 100644 --- a/test/commit_test.dart +++ b/test/commit_test.dart @@ -67,6 +67,109 @@ void main() { expect(file.existsSync(), false); }); + test('reverts merge commit to provided parent', () { + const masterContents = 'master contents'; + final file = File(p.join(repo.workdir, 'another_feature_file')) + ..createSync() + ..writeAsStringSync(masterContents); + + repo.index.add('another_feature_file'); + repo.index.write(); + + // Creating commit on 'master' branch with file contents conflicting to + // 'feature' branch. + final masterTip = Commit.create( + repo: repo, + updateRef: 'HEAD', + message: 'master commit\n', + author: author, + committer: committer, + tree: Tree.lookup(repo: repo, oid: repo.index.writeTree()), + parents: [Commit.lookup(repo: repo, oid: repo.head.target)], + ); + + // Switching to 'feature' branch. + Checkout.reference(repo: repo, name: 'refs/heads/feature'); + repo.setHead('refs/heads/feature'); + + file.writeAsStringSync('feature contents'); + + repo.index.add('another_feature_file'); + repo.index.write(); + + // Creating commit on 'feature' branch with file contents conflicting to + // 'master' branch. + final featureTip = Commit.create( + repo: repo, + updateRef: 'HEAD', + message: 'feature commit\n', + author: author, + committer: committer, + tree: Tree.lookup(repo: repo, oid: repo.index.writeTree()), + parents: [Commit.lookup(repo: repo, oid: repo.head.target)], + ); + + // Merging master branch. + Merge.commit( + repo: repo, + commit: AnnotatedCommit.lookup( + repo: repo, + oid: Oid.fromSHA(repo: repo, sha: masterTip.sha), + ), + ); + + expect(repo.index.hasConflicts, true); + + // "Resolving" conflict. + repo.index.updateAll(['another_feature_file']); + repo.index.write(); + repo.stateCleanup(); + + // Creating merge commit. + final mergeOid = Commit.create( + repo: repo, + updateRef: 'HEAD', + message: 'merge commit\n', + author: author, + committer: committer, + tree: Tree.lookup(repo: repo, oid: repo.index.writeTree()), + parents: [ + Commit.lookup(repo: repo, oid: featureTip), + Commit.lookup(repo: repo, oid: masterTip), + ], + ); + + final mergeCommit = Commit.lookup(repo: repo, oid: mergeOid); + mergeCommit.revert(mainline: 2); + + expect(file.readAsStringSync(), masterContents); + }); + + test('reverts commit with provided merge options and checkout options', () { + final commit = Commit.lookup(repo: repo, oid: repo['821ed6e']); + final file = File(p.join(repo.workdir, 'dir', 'dir_file.txt')); + expect(repo.index.find('dir/dir_file.txt'), true); + expect(file.existsSync(), true); + + commit.revert( + mergeFavor: GitMergeFileFavor.ours, + mergeFlags: {GitMergeFlag.noRecursive, GitMergeFlag.skipREUC}, + mergeFileFlags: { + GitMergeFileFlag.ignoreWhitespace, + GitMergeFileFlag.styleZdiff3 + }, + checkoutStrategy: { + GitCheckout.force, + GitCheckout.conflictStyleMerge, + }, + checkoutDirectory: repo.workdir, + checkoutPaths: ['dir/dir_file.txt'], + ); + + expect(repo.index.find('dir/dir_file.txt'), false); + expect(file.existsSync(), false); + }); + test('throws when trying to revert and error occurs', () { expect(() => Commit(nullptr).revert(), throwsA(isA())); }); @@ -84,6 +187,25 @@ void main() { expect(file.existsSync(), true); }); + test('reverts commit to provided commit with provided merge options', () { + final file = File(p.join(repo.workdir, 'dir', 'dir_file.txt')); + expect(repo.index.find('dir/dir_file.txt'), true); + expect(file.existsSync(), true); + + final from = Commit.lookup(repo: repo, oid: repo['821ed6e']); + final revertIndex = from.revertTo( + commit: Commit.lookup(repo: repo, oid: repo['78b8bf1']), + mergeFavor: GitMergeFileFavor.ours, + mergeFlags: {GitMergeFlag.noRecursive, GitMergeFlag.skipREUC}, + mergeFileFlags: { + GitMergeFileFlag.ignoreWhitespace, + GitMergeFileFlag.styleZdiff3 + }, + ); + expect(revertIndex.find('dir/dir_file.txt'), false); + expect(file.existsSync(), true); + }); + test('throws when trying to revert commit and error occurs', () { final nullCommit = Commit(nullptr); expect( diff --git a/test/credentials_test.dart b/test/credentials_test.dart index cdfaa1c..f198093 100644 --- a/test/credentials_test.dart +++ b/test/credentials_test.dart @@ -83,32 +83,28 @@ void main() { cloneDir.deleteSync(recursive: true); }); - test( - testOn: '!linux', - 'clones repository with provided keypair', - () { - final cloneDir = Directory.systemTemp.createTempSync('clone'); - final keypair = Keypair( - username: 'git', - pubKey: p.join('test', 'assets', 'keys', 'id_rsa.pub'), - privateKey: p.join('test', 'assets', 'keys', 'id_rsa'), - passPhrase: 'empty', - ); - final callbacks = Callbacks(credentials: keypair); + test('clones repository with provided keypair', () { + final cloneDir = Directory.systemTemp.createTempSync('clone'); + final keypair = Keypair( + username: 'git', + pubKey: p.join('test', 'assets', 'keys', 'id_rsa.pub'), + privateKey: p.join('test', 'assets', 'keys', 'id_rsa'), + passPhrase: 'empty', + ); + final callbacks = Callbacks(credentials: keypair); - final repo = Repository.clone( - url: 'ssh://git@github.com/libgit2/TestGitRepository', - localPath: cloneDir.path, - callbacks: callbacks, - ); + final repo = Repository.clone( + url: 'ssh://git@github.com/libgit2/TestGitRepository', + localPath: cloneDir.path, + callbacks: callbacks, + ); - expect(repo.isEmpty, false); + expect(repo.isEmpty, false); - if (Platform.isLinux || Platform.isMacOS) { - cloneDir.deleteSync(recursive: true); - } - }, - ); + if (Platform.isLinux || Platform.isMacOS) { + cloneDir.deleteSync(recursive: true); + } + }); test('throws when no credentials is provided', () { final cloneDir = Directory.systemTemp.createTempSync('clone'); @@ -189,36 +185,32 @@ void main() { cloneDir.deleteSync(recursive: true); }); - test( - testOn: '!linux', - 'clones repository with provided keypair from memory', - () { - final cloneDir = Directory.systemTemp.createTempSync('clone'); - final pubKey = File(p.join('test', 'assets', 'keys', 'id_rsa.pub')) - .readAsStringSync(); - final privateKey = - File(p.join('test', 'assets', 'keys', 'id_rsa')).readAsStringSync(); - final keypair = KeypairFromMemory( - username: 'git', - pubKey: pubKey, - privateKey: privateKey, - passPhrase: 'empty', - ); - final callbacks = Callbacks(credentials: keypair); + test('clones repository with provided keypair from memory', () { + final cloneDir = Directory.systemTemp.createTempSync('clone'); + final pubKey = File(p.join('test', 'assets', 'keys', 'id_rsa.pub')) + .readAsStringSync(); + final privateKey = + File(p.join('test', 'assets', 'keys', 'id_rsa')).readAsStringSync(); + final keypair = KeypairFromMemory( + username: 'git', + pubKey: pubKey, + privateKey: privateKey, + passPhrase: 'empty', + ); + final callbacks = Callbacks(credentials: keypair); - final repo = Repository.clone( - url: 'ssh://git@github.com/libgit2/TestGitRepository', - localPath: cloneDir.path, - callbacks: callbacks, - ); + final repo = Repository.clone( + url: 'ssh://git@github.com/libgit2/TestGitRepository', + localPath: cloneDir.path, + callbacks: callbacks, + ); - expect(repo.isEmpty, false); + expect(repo.isEmpty, false); - if (Platform.isLinux || Platform.isMacOS) { - cloneDir.deleteSync(recursive: true); - } - }, - ); + if (Platform.isLinux || Platform.isMacOS) { + cloneDir.deleteSync(recursive: true); + } + }); test('throws when provided keypair from memory is incorrect', () { final cloneDir = Directory.systemTemp.createTempSync('clone'); diff --git a/test/git_types_test.dart b/test/git_types_test.dart index c0d81e9..68ef591 100644 --- a/test/git_types_test.dart +++ b/test/git_types_test.dart @@ -551,4 +551,25 @@ void main() { expect(actual, expected); }); }); + + test('GitIndexAddOption returns correct values', () { + const expected = { + GitIndexAddOption.defaults: 0, + GitIndexAddOption.force: 1, + GitIndexAddOption.disablePathspecMatch: 2, + GitIndexAddOption.checkPathspec: 4, + }; + final actual = {for (final e in GitIndexAddOption.values) e: e.value}; + expect(actual, expected); + }); + + test('GitWorktree returns correct values', () { + const expected = { + GitWorktree.pruneValid: 1, + GitWorktree.pruneLocked: 2, + GitWorktree.pruneWorkingTree: 4, + }; + final actual = {for (final e in GitWorktree.values) e: e.value}; + expect(actual, expected); + }); } diff --git a/test/index_test.dart b/test/index_test.dart index 9a496a1..a681ad4 100644 --- a/test/index_test.dart +++ b/test/index_test.dart @@ -175,7 +175,10 @@ void main() { group('addAll()', () { test('adds with provided pathspec', () { index.clear(); - index.addAll(['file', 'feature_file']); + index.addAll( + ['file', 'feature_file'], + flags: {GitIndexAddOption.checkPathspec, GitIndexAddOption.force}, + ); expect(index.length, 2); expect(index['file'].oid.sha, fileSha); diff --git a/test/merge_test.dart b/test/merge_test.dart index 6f3a276..4b35d29 100644 --- a/test/merge_test.dart +++ b/test/merge_test.dart @@ -168,31 +168,38 @@ Another feature edit expect(diff, diffExpected); }); - test('merges with provided merge flags and file flags', () { + test('merges with provided options', () { const diffExpected = """ -\<<<<<<< conflict_file -master conflict edit +\<<<<<<< ours +Feature edit on feature branch +||||||| ancestor +Feature edit ======= -conflict branch edit ->>>>>>> conflict_file +Another feature edit +>>>>>>> theirs """; + Checkout.reference(repo: repo, name: 'refs/heads/feature'); + repo.setHead('refs/heads/feature'); + Merge.commit( repo: repo, commit: AnnotatedCommit.lookup( repo: repo, - oid: Branch.lookup(repo: repo, name: 'conflict-branch').target, + oid: Branch.lookup(repo: repo, name: 'ancestor-conflict').target, ), - mergeFlags: {GitMergeFlag.noRecursive}, - fileFlags: {GitMergeFileFlag.ignoreWhitespaceEOL}, ); - final conflictedFile = repo.index.conflicts['conflict_file']!; + final conflictedFile = repo.index.conflicts['feature_file']!; final diff = Merge.fileFromIndex( repo: repo, - ancestor: null, + ancestor: conflictedFile.ancestor, + ancestorLabel: 'ancestor', ours: conflictedFile.our!, + oursLabel: 'ours', theirs: conflictedFile.their!, + theirsLabel: 'theirs', + flags: {GitMergeFileFlag.styleDiff3}, ); expect(diff, diffExpected); diff --git a/test/remote_test.dart b/test/remote_test.dart index 126f317..caa3a63 100644 --- a/test/remote_test.dart +++ b/test/remote_test.dart @@ -278,7 +278,7 @@ void main() { expect(refs.first.localId, null); expect(refs.first.name, 'HEAD'); expect(refs.first.symRef, 'refs/heads/master'); - expect((refs.first.oid).sha, '49322bb17d3acc9146f98c97d078513228bbf3c0'); + expect(refs.first.oid.sha, '49322bb17d3acc9146f98c97d078513228bbf3c0'); expect(refs.first.toString(), contains('RemoteReference{')); expect(refs.first, remote.ls().first); }); diff --git a/test/reset_test.dart b/test/reset_test.dart index 96366d6..f5dd62e 100644 --- a/test/reset_test.dart +++ b/test/reset_test.dart @@ -50,6 +50,34 @@ void main() { expect(diff.deltas.length, 1); }); + test('resets with provided checkout options', () { + expect(file.readAsStringSync(), 'Feature edit\n'); + + repo.reset( + oid: repo[sha], + resetType: GitReset.hard, + strategy: {GitCheckout.conflictStyleZdiff3}, + pathspec: ['feature_file'], + ); + + expect(file.readAsStringSync(), isEmpty); + }); + + test( + 'throws when trying to reset and error occurs', + testOn: '!windows', + () { + expect( + () => repo.reset( + oid: repo[sha], + resetType: GitReset.hard, + checkoutDirectory: '', + ), + throwsA(isA()), + ); + }, + ); + group('resetDefault', () { test('updates entry in the index', () { file.writeAsStringSync('new edit'); diff --git a/test/revwalk_test.dart b/test/revwalk_test.dart index e491ab0..5b3c9a0 100644 --- a/test/revwalk_test.dart +++ b/test/revwalk_test.dart @@ -50,6 +50,16 @@ void main() { } }); + test('walks only number of commits provided with limit', () { + final walker = RevWalk(repo); + + walker.push(repo[log.first]); + final commits = walker.walk(limit: 1); + + expect(commits.length, 1); + expect(commits[0].oid.sha, log[0]); + }); + test('returns list of commits with reverse sorting', () { final walker = RevWalk(repo); diff --git a/test/worktree_test.dart b/test/worktree_test.dart index 0da572a..550cf33 100644 --- a/test/worktree_test.dart +++ b/test/worktree_test.dart @@ -151,6 +151,22 @@ void main() { expect(repo.worktrees, []); }); + test('prunes worktree with provided flags', () { + expect(repo.worktrees, []); + + final worktree = Worktree.create( + repo: repo, + name: worktreeName, + path: worktreeDir.path, + ); + expect(repo.worktrees, [worktreeName]); + expect(worktree.isPrunable, false); + expect(worktree.isValid, true); + + worktree.prune({GitWorktree.pruneValid}); + expect(repo.worktrees, []); + }); + test('throws when trying get list of worktrees and error occurs', () { expect( () => Worktree.list(Repository(nullptr)), diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index a653c8a..128c67f 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -19,6 +19,6 @@ target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) # List of absolute paths to libraries that should be bundled with the plugin set(libgit2dart_bundled_libraries - "${CMAKE_CURRENT_SOURCE_DIR}/libgit2-1.4.3.dll" + "${CMAKE_CURRENT_SOURCE_DIR}/libgit2-1.5.0.dll" PARENT_SCOPE ) diff --git a/windows/libgit2-1.4.3.dll b/windows/libgit2-1.5.0.dll similarity index 57% rename from windows/libgit2-1.4.3.dll rename to windows/libgit2-1.5.0.dll index 19ad281..3c7347b 100644 Binary files a/windows/libgit2-1.4.3.dll and b/windows/libgit2-1.5.0.dll differ