Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
# Changelog
## [0.5.1] - 2026-06-29
### Fixed
* Require `git2dart_binaries` `>=1.11.2 <1.12.0` to pick up the latest
packaged libgit2 binaries and generated bindings.
* Add a remote `certificateCheck` callback so SSH clients can supply host key
trust decisions without relying on `known_hosts` lookup.

### Documentation
* Document the `git2dart_binaries` `1.11.2` dependency baseline in the README.

## [0.5.0] - 2026-05-30
### Features
* Update `git2dart_binaries` constraint to `>=1.11.0 <1.12.0`.
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ This is a hardfork of [libgit2dart](https://github.com/SkinnyMind/libgit2dart)
Currently supported platforms are 64-bit Windows, Linux, macOS, Android
(arm64-v8a, x86_64), and iOS on Flutter. Desktop platforms also work on the
Dart VM when native libraries are available.

Version `0.5.1` requires `git2dart_binaries >=1.11.2 <1.12.0`, which provides
the bundled libgit2 artifacts and generated FFI bindings used by this package.
## Usage

git2dart provides you ability to manage Git repository. You can read and write objects (commit, tag, tree and blob), walk a tree, access the staging area, manage config and lots more.
Expand Down
12 changes: 12 additions & 0 deletions doc/types/remote.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ remote.push(refspecs: ['refs/heads/main']);
remote.prune();
```

## Certificate Checks

```dart
final callbacks = Callbacks(
certificateCheck: (certificate, host, {required valid}) {
return host == 'github.com';
},
);

remote.fetch(callbacks: callbacks);
```

Network-dependent tests are skipped in CI unless explicitly enabled.

See [test/remote_test.dart](../../test/remote_test.dart).
1 change: 1 addition & 0 deletions lib/git2dart.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export 'src/blame.dart';
export 'src/blob.dart';
export 'src/branch.dart';
export 'src/callbacks.dart';
export 'src/certificate.dart';
export 'src/checkout.dart';
export 'src/commit.dart';
export 'src/commit_graph.dart';
Expand Down
112 changes: 112 additions & 0 deletions lib/src/bindings/certificate.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import 'dart:ffi';
import 'dart:typed_data';

import 'package:git2dart_binaries/git2dart_binaries.dart';

/// Return certificate type as a `GIT_CERT_` value.
int type(Pointer<git_cert> certificatePointer) {
return certificatePointer.ref.cert_typeAsInt;
}

/// Return certificate as SSH host key structure.
Pointer<git_cert_hostkey> hostkey(Pointer<git_cert> certificatePointer) {
return certificatePointer.cast<git_cert_hostkey>();
}

/// Return certificate as X.509 structure.
Pointer<git_cert_x509> x509(Pointer<git_cert> certificatePointer) {
return certificatePointer.cast<git_cert_x509>();
}

/// Return raw bitmask of available SSH host key fields.
int hostkeyTypeFlags(Pointer<git_cert_hostkey> hostkeyPointer) {
return hostkeyPointer.ref.typeAsInt;
}

/// Return whether [flag] is set in SSH host key fields.
bool hostkeyHasFlag({
required Pointer<git_cert_hostkey> hostkeyPointer,
required int flag,
}) {
return hostkeyTypeFlags(hostkeyPointer) & flag != 0;
}

/// Return whether MD5 host key hash is available.
bool hostkeyHasMd5(Pointer<git_cert_hostkey> hostkeyPointer) {
return hostkeyHasFlag(
hostkeyPointer: hostkeyPointer,
flag: git_cert_ssh_t.GIT_CERT_SSH_MD5.value,
);
}

/// Return whether SHA-1 host key hash is available.
bool hostkeyHasSha1(Pointer<git_cert_hostkey> hostkeyPointer) {
return hostkeyHasFlag(
hostkeyPointer: hostkeyPointer,
flag: git_cert_ssh_t.GIT_CERT_SSH_SHA1.value,
);
}

/// Return whether SHA-256 host key hash is available.
bool hostkeyHasSha256(Pointer<git_cert_hostkey> hostkeyPointer) {
return hostkeyHasFlag(
hostkeyPointer: hostkeyPointer,
flag: git_cert_ssh_t.GIT_CERT_SSH_SHA256.value,
);
}

/// Return whether raw SSH host key is available.
bool hostkeyHasRaw(Pointer<git_cert_hostkey> hostkeyPointer) {
return hostkeyHasFlag(
hostkeyPointer: hostkeyPointer,
flag: git_cert_ssh_t.GIT_CERT_SSH_RAW.value,
);
}

/// Return MD5 host key hash.
Uint8List hostkeyMd5(Pointer<git_cert_hostkey> hostkeyPointer) {
return _copyArray(hostkeyPointer.ref.hash_md5, 16);
}

/// Return SHA-1 host key hash.
Uint8List hostkeySha1(Pointer<git_cert_hostkey> hostkeyPointer) {
return _copyArray(hostkeyPointer.ref.hash_sha1, 20);
}

/// Return SHA-256 host key hash.
Uint8List hostkeySha256(Pointer<git_cert_hostkey> hostkeyPointer) {
return _copyArray(hostkeyPointer.ref.hash_sha256, 32);
}

/// Return raw SSH host key type.
int hostkeyRawType(Pointer<git_cert_hostkey> hostkeyPointer) {
return hostkeyPointer.ref.raw_typeAsInt;
}

/// Return raw SSH host key bytes.
Uint8List hostkeyRaw(Pointer<git_cert_hostkey> hostkeyPointer) {
final hostkey = hostkeyPointer.ref.hostkey;
final length = hostkeyPointer.ref.hostkey_len;

if (hostkey == nullptr || length == 0) {
return Uint8List(0);
}

return Uint8List.fromList(hostkey.cast<Uint8>().asTypedList(length));
}

/// Return raw X.509 certificate bytes.
Uint8List x509Data(Pointer<git_cert_x509> certificatePointer) {
final dataPointer = certificatePointer.ref.data;
final length = certificatePointer.ref.len;

if (dataPointer == nullptr || length == 0) {
return Uint8List(0);
}

return Uint8List.fromList(dataPointer.cast<Uint8>().asTypedList(length));
}

Uint8List _copyArray(Array<UnsignedChar> array, int length) {
return Uint8List.fromList(<int>[for (var i = 0; i < length; i++) array[i]]);
}
28 changes: 28 additions & 0 deletions lib/src/bindings/remote_callbacks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,25 @@ class RemoteCallbacks {
return 0;
}

/// Callback function used to validate a remote certificate.
static CertificateCheck? certificateCheck;

/// Native callback that handles remote certificate validation.
static int certificateCheckCb(
Pointer<git_cert> cert,
int valid,
Pointer<Char> host,
Pointer<Void> payload,
) {
final accepted = certificateCheck!(
GitCertificate(cert),
host == nullptr ? '' : host.toDartString(),
valid: valid == 1,
);

return accepted ? 0 : -1;
}

/// Values used to override the remote creation and customization process
/// during a clone operation.
static RemoteCallback? remoteCbData;
Expand Down Expand Up @@ -260,6 +279,14 @@ class RemoteCallbacks {
);
}

if (callbacks.certificateCheck != null) {
certificateCheck = callbacks.certificateCheck;
callbacksOptions.certificate_check = Pointer.fromFunction(
certificateCheckCb,
except,
);
}

if (callbacks.credentials != null) {
credentials = callbacks.credentials;
final withUser = calloc<Int8>()..value = 1;
Expand All @@ -278,6 +305,7 @@ class RemoteCallbacks {
sidebandProgress = null;
updateTips = null;
pushUpdateReference = null;
certificateCheck = null;
remoteCbData = null;
repositoryCbData = null;
credentials = null;
Expand Down
22 changes: 22 additions & 0 deletions lib/src/callbacks.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
import 'package:git2dart/git2dart.dart';

/// Callback for validating a remote certificate.
///
/// Return `true` to accept the certificate, or `false` to reject it.
typedef CertificateCheck =
bool Function(
GitCertificate certificate,
String host, {
required bool valid,
});

/// A class that encapsulates various callback functions used in Git operations.
///
/// This class is primarily used with [Remote] methods and [Repository.clone]
/// operations to handle different aspects of Git operations:
/// - Authentication
/// - Progress tracking
/// - Certificate checks
/// - Reference updates
/// - Push status updates
class Callbacks {
Expand All @@ -15,12 +26,14 @@ class Callbacks {
/// Git operation requirements:
///
/// * [credentials] - Authentication credentials (see [Credentials] implementations)
/// * [certificateCheck] - Remote certificate trust decision
/// * [transferProgress] - Progress tracking for data transfers
/// * [sidebandProgress] - Textual progress updates from remote
/// * [updateTips] - Reference update notifications
/// * [pushUpdateReference] - Push operation status updates
const Callbacks({
this.credentials,
this.certificateCheck,
this.transferProgress,
this.sidebandProgress,
this.updateTips,
Expand All @@ -36,6 +49,15 @@ class Callbacks {
/// * [KeypairFromMemory] - In-memory SSH key pair authentication
final Credentials? credentials;

/// Callback for validating a remote certificate.
///
/// Return `true` to accept the certificate, or `false` to reject it. Leave
/// this unset to use libgit2's default certificate validation behavior.
///
/// This is especially useful on platforms such as Android where SSH
/// `known_hosts` lookup may not be available.
final CertificateCheck? certificateCheck;

/// Callback for tracking data transfer progress.
///
/// Provides real-time updates about the progress of data transfers
Expand Down
Loading
Loading