mirror of
https://github.com/SkinnyMind/libgit2dart.git
synced 2025-05-04 20:29:08 -04:00
feat(blame): add bindings and api
This commit is contained in:
parent
9686d93935
commit
5ee0662376
62 changed files with 1390 additions and 4 deletions
|
@ -21,6 +21,7 @@ export 'src/remote.dart';
|
|||
export 'src/refspec.dart';
|
||||
export 'src/callbacks.dart';
|
||||
export 'src/credentials.dart';
|
||||
export 'src/blame.dart';
|
||||
export 'src/features.dart';
|
||||
export 'src/error.dart';
|
||||
export 'src/git_types.dart';
|
||||
|
|
104
lib/src/bindings/blame.dart
Normal file
104
lib/src/bindings/blame.dart
Normal file
|
@ -0,0 +1,104 @@
|
|||
import 'dart:ffi';
|
||||
import 'package:ffi/ffi.dart';
|
||||
import '../error.dart';
|
||||
import '../oid.dart';
|
||||
import '../util.dart';
|
||||
import 'libgit2_bindings.dart';
|
||||
|
||||
/// Get the blame for a single file.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Pointer<git_blame> file({
|
||||
required Pointer<git_repository> repoPointer,
|
||||
required String path,
|
||||
int? flags,
|
||||
int? minMatchCharacters,
|
||||
Oid? newestCommit,
|
||||
Oid? oldestCommit,
|
||||
int? minLine,
|
||||
int? maxLine,
|
||||
}) {
|
||||
final out = calloc<Pointer<git_blame>>();
|
||||
final pathC = path.toNativeUtf8().cast<Int8>();
|
||||
final options = calloc<git_blame_options>();
|
||||
final optionsError = libgit2.git_blame_options_init(
|
||||
options,
|
||||
GIT_BLAME_OPTIONS_VERSION,
|
||||
);
|
||||
|
||||
if (optionsError < 0) {
|
||||
throw LibGit2Error(libgit2.git_error_last());
|
||||
}
|
||||
|
||||
if (flags != null) {
|
||||
options.ref.flags = flags;
|
||||
}
|
||||
|
||||
if (minMatchCharacters != null) {
|
||||
options.ref.min_match_characters = minMatchCharacters;
|
||||
}
|
||||
|
||||
if (newestCommit != null) {
|
||||
options.ref.newest_commit = newestCommit.pointer.ref;
|
||||
}
|
||||
|
||||
if (oldestCommit != null) {
|
||||
options.ref.oldest_commit = oldestCommit.pointer.ref;
|
||||
}
|
||||
|
||||
if (minLine != null) {
|
||||
options.ref.min_line = minLine;
|
||||
}
|
||||
|
||||
if (maxLine != null) {
|
||||
options.ref.max_line = maxLine;
|
||||
}
|
||||
|
||||
final error = libgit2.git_blame_file(out, repoPointer, pathC, options);
|
||||
|
||||
if (error < 0) {
|
||||
throw LibGit2Error(libgit2.git_error_last());
|
||||
} else {
|
||||
return out.value;
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the number of hunks that exist in the blame structure.
|
||||
int hunkCount(Pointer<git_blame> blame) {
|
||||
return libgit2.git_blame_get_hunk_count(blame);
|
||||
}
|
||||
|
||||
/// Gets the blame hunk at the given index.
|
||||
///
|
||||
/// Throws [RangeError] if index out of range.
|
||||
Pointer<git_blame_hunk> getHunkByIndex({
|
||||
required Pointer<git_blame> blamePointer,
|
||||
required int index,
|
||||
}) {
|
||||
final result = libgit2.git_blame_get_hunk_byindex(blamePointer, index);
|
||||
|
||||
if (result == nullptr) {
|
||||
throw RangeError('$index is out of bounds');
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the hunk that relates to the given line number (1-based) in the newest commit.
|
||||
///
|
||||
/// Throws [RangeError] if [lineNumber] is out of range.
|
||||
Pointer<git_blame_hunk> getHunkByLine({
|
||||
required Pointer<git_blame> blamePointer,
|
||||
required int lineNumber,
|
||||
}) {
|
||||
final result = libgit2.git_blame_get_hunk_byline(blamePointer, lineNumber);
|
||||
|
||||
if (result == nullptr) {
|
||||
throw RangeError('$lineNumber is out of bounds');
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// Free memory allocated for blame object.
|
||||
void free(Pointer<git_blame> blame) => libgit2.git_blame_free(blame);
|
172
lib/src/blame.dart
Normal file
172
lib/src/blame.dart
Normal file
|
@ -0,0 +1,172 @@
|
|||
import 'dart:collection';
|
||||
import 'dart:ffi';
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'bindings/libgit2_bindings.dart';
|
||||
import 'bindings/blame.dart' as bindings;
|
||||
import 'util.dart';
|
||||
import 'git_types.dart';
|
||||
import 'oid.dart';
|
||||
import 'repository.dart';
|
||||
import 'signature.dart';
|
||||
|
||||
class Blame with IterableMixin<BlameHunk> {
|
||||
/// Initializes a new instance of the [Blame] class from
|
||||
/// provided pointer to blame object in memory.
|
||||
Blame(this._blamePointer);
|
||||
|
||||
/// Initializes a new instance of the [Blame] class by getting
|
||||
/// the blame for a single file.
|
||||
///
|
||||
/// [flags] is a combination of [GitBlameFlag]s.
|
||||
///
|
||||
/// [minMatchCharacters] is the lower bound on the number of alphanumeric
|
||||
/// characters that must be detected as moving/copying within a file for
|
||||
/// it to associate those lines with the parent commit. The default value is 20.
|
||||
/// This value only takes effect if any of the [GitBlameFlag.trackCopies*]
|
||||
/// flags are specified.
|
||||
///
|
||||
/// [newestCommit] is the id of the newest commit to consider. The default is HEAD.
|
||||
///
|
||||
/// [oldestCommit] is the id of the oldest commit to consider. The default is the
|
||||
/// first commit encountered with no parent.
|
||||
///
|
||||
/// [minLine] is the first line in the file to blame. The default is 1
|
||||
/// (line numbers start with 1).
|
||||
///
|
||||
/// [maxLine] is the last line in the file to blame. The default is the last
|
||||
/// line of the file.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Blame.file({
|
||||
required Repository repo,
|
||||
required String path,
|
||||
Set<GitBlameFlag> flags = const {GitBlameFlag.normal},
|
||||
int? minMatchCharacters,
|
||||
Oid? newestCommit,
|
||||
Oid? oldestCommit,
|
||||
int? minLine,
|
||||
int? maxLine,
|
||||
}) {
|
||||
libgit2.git_libgit2_init();
|
||||
|
||||
final int flagsInt =
|
||||
flags.fold(0, (previousValue, e) => previousValue | e.value);
|
||||
|
||||
_blamePointer = bindings.file(
|
||||
repoPointer: repo.pointer,
|
||||
path: path,
|
||||
flags: flagsInt,
|
||||
minMatchCharacters: minMatchCharacters,
|
||||
newestCommit: newestCommit,
|
||||
oldestCommit: oldestCommit,
|
||||
minLine: minLine,
|
||||
maxLine: maxLine,
|
||||
);
|
||||
}
|
||||
|
||||
/// Pointer to memory address for allocated blame object.
|
||||
late final Pointer<git_blame> _blamePointer;
|
||||
|
||||
/// Returns the blame hunk at the given index.
|
||||
///
|
||||
/// Throws [RangeError] if index out of range.
|
||||
BlameHunk operator [](int index) {
|
||||
return BlameHunk(bindings.getHunkByIndex(
|
||||
blamePointer: _blamePointer,
|
||||
index: index,
|
||||
));
|
||||
}
|
||||
|
||||
/// Gets the hunk that relates to the given line number (1-based) in the newest commit.
|
||||
///
|
||||
/// Throws [RangeError] if [lineNumber] is out of range.
|
||||
BlameHunk forLine(int lineNumber) {
|
||||
return BlameHunk(bindings.getHunkByLine(
|
||||
blamePointer: _blamePointer,
|
||||
lineNumber: lineNumber,
|
||||
));
|
||||
}
|
||||
|
||||
/// Releases memory allocated for blame object.
|
||||
void free() => bindings.free(_blamePointer);
|
||||
|
||||
@override
|
||||
Iterator<BlameHunk> get iterator => _BlameIterator(_blamePointer);
|
||||
}
|
||||
|
||||
class BlameHunk {
|
||||
/// Initializes a new instance of the [BlameHunk] class from
|
||||
/// provided pointer to blame hunk object in memory.
|
||||
const BlameHunk(this._blameHunkPointer);
|
||||
|
||||
/// Pointer to memory address for allocated blame hunk object.
|
||||
final Pointer<git_blame_hunk> _blameHunkPointer;
|
||||
|
||||
/// Returns the number of lines in this hunk.
|
||||
int get linesCount => _blameHunkPointer.ref.lines_in_hunk;
|
||||
|
||||
/// Checks if the hunk has been tracked to a boundary commit
|
||||
/// (the root, or the commit specified in [oldestCommit] argument)
|
||||
bool get isBoundary {
|
||||
return _blameHunkPointer.ref.boundary == 1 ? true : false;
|
||||
}
|
||||
|
||||
/// Returns the 1-based line number where this hunk begins, in the final
|
||||
/// version of the file.
|
||||
int get finalStartLineNumber => _blameHunkPointer.ref.final_start_line_number;
|
||||
|
||||
/// Returns the author of [finalCommitId]. If [GitBlameFlag.useMailmap] has been
|
||||
/// specified, it will contain the canonical real name and email address.
|
||||
Signature get finalCommitter =>
|
||||
Signature(_blameHunkPointer.ref.final_signature);
|
||||
|
||||
/// Returns the [Oid] of the commit where this line was last changed.
|
||||
Oid get finalCommitId => Oid.fromRaw(_blameHunkPointer.ref.final_commit_id);
|
||||
|
||||
/// Returns the 1-based line number where this hunk begins, in the file
|
||||
/// named by [originPath] in the commit specified by [originCommitId].
|
||||
int get originStartLineNumber => _blameHunkPointer.ref.orig_start_line_number;
|
||||
|
||||
/// Returns the author of [originCommitId]. If [GitBlameFlag.useMailmap] has been
|
||||
/// specified, it will contain the canonical real name and email address.
|
||||
Signature get originCommitter =>
|
||||
Signature(_blameHunkPointer.ref.orig_signature);
|
||||
|
||||
/// Returns the [Oid] of the commit where this hunk was found. This will usually be
|
||||
/// the same as [finalCommitId], except when [GitBlameFlag.trackCopiesAnyCommitCopies]
|
||||
/// been specified.
|
||||
Oid get originCommitId => Oid.fromRaw(_blameHunkPointer.ref.orig_commit_id);
|
||||
|
||||
/// Returns the path to the file where this hunk originated, as of the commit
|
||||
/// specified by [originCommitId].
|
||||
String get originPath =>
|
||||
_blameHunkPointer.ref.orig_path.cast<Utf8>().toDartString();
|
||||
}
|
||||
|
||||
class _BlameIterator implements Iterator<BlameHunk> {
|
||||
_BlameIterator(this._blamePointer) {
|
||||
count = bindings.hunkCount(_blamePointer);
|
||||
}
|
||||
|
||||
final Pointer<git_blame> _blamePointer;
|
||||
BlameHunk? currentHunk;
|
||||
late final int count;
|
||||
int index = 0;
|
||||
|
||||
@override
|
||||
BlameHunk get current => currentHunk!;
|
||||
|
||||
@override
|
||||
bool moveNext() {
|
||||
if (index == count) {
|
||||
return false;
|
||||
} else {
|
||||
currentHunk = BlameHunk(bindings.getHunkByIndex(
|
||||
blamePointer: _blamePointer,
|
||||
index: index,
|
||||
));
|
||||
index++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1384,3 +1384,72 @@ class GitAttributeCheck {
|
|||
@override
|
||||
String toString() => 'GitAttributeCheck.$_name';
|
||||
}
|
||||
|
||||
/// Flags for indicating option behavior for git blame APIs.
|
||||
class GitBlameFlag {
|
||||
const GitBlameFlag._(this._value, this._name);
|
||||
final int _value;
|
||||
final String _name;
|
||||
|
||||
/// Normal blame, the default.
|
||||
static const normal = GitBlameFlag._(0, 'normal');
|
||||
|
||||
/// Track lines that have moved within a file (like `git blame -M`).
|
||||
///
|
||||
/// This is not yet implemented and reserved for future use.
|
||||
static const trackCopiesSameFile = GitBlameFlag._(1, 'trackCopiesSameFile');
|
||||
|
||||
/// Track lines that have moved across files in the same commit
|
||||
/// (like `git blame -C`).
|
||||
///
|
||||
/// This is not yet implemented and reserved for future use.
|
||||
static const trackCopiesSameCommitMoves =
|
||||
GitBlameFlag._(2, 'trackCopiesSameCommitMoves');
|
||||
|
||||
/// Track lines that have been copied from another file that exists
|
||||
/// in the same commit (like `git blame -CC`). Implies SAME_FILE.
|
||||
///
|
||||
/// This is not yet implemented and reserved for future use.
|
||||
static const trackCopiesSameCommitCopies = GitBlameFlag._(
|
||||
4,
|
||||
'trackCopiesSameCommitCopies',
|
||||
);
|
||||
|
||||
/// Track lines that have been copied from another file that exists in
|
||||
/// *any* commit (like `git blame -CCC`). Implies SAME_COMMIT_COPIES.
|
||||
///
|
||||
/// This is not yet implemented and reserved for future use.
|
||||
static const trackCopiesAnyCommitCopies = GitBlameFlag._(
|
||||
8,
|
||||
'trackCopiesAnyCommitCopies',
|
||||
);
|
||||
|
||||
/// Restrict the search of commits to those reachable following only
|
||||
/// the first parents.
|
||||
static const firstParent = GitBlameFlag._(16, 'firstParent');
|
||||
|
||||
/// Use mailmap file to map author and committer names and email
|
||||
/// addresses to canonical real names and email addresses. The
|
||||
/// mailmap will be read from the working directory, or HEAD in a
|
||||
/// bare repository.
|
||||
static const useMailmap = GitBlameFlag._(32, 'useMailmap');
|
||||
|
||||
/// Ignore whitespace differences.
|
||||
static const ignoreWhitespace = GitBlameFlag._(64, 'ignoreWhitespace');
|
||||
|
||||
static const List<GitBlameFlag> values = [
|
||||
normal,
|
||||
trackCopiesSameFile,
|
||||
trackCopiesSameCommitMoves,
|
||||
trackCopiesSameCommitCopies,
|
||||
trackCopiesAnyCommitCopies,
|
||||
firstParent,
|
||||
useMailmap,
|
||||
ignoreWhitespace,
|
||||
];
|
||||
|
||||
int get value => _value;
|
||||
|
||||
@override
|
||||
String toString() => 'GitBlameFlag.$_name';
|
||||
}
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import 'dart:ffi';
|
||||
import 'package:libgit2dart/libgit2dart.dart';
|
||||
|
||||
import 'bindings/libgit2_bindings.dart';
|
||||
import 'bindings/remote.dart' as bindings;
|
||||
import 'callbacks.dart';
|
||||
import 'git_types.dart';
|
||||
import 'refspec.dart';
|
||||
import 'repository.dart';
|
||||
|
||||
class Remotes {
|
||||
/// Initializes a new instance of the [References] class
|
||||
/// from provided [Repository] object.
|
||||
/// Initializes a new instance of the [Remotes] class from
|
||||
/// provided [Repository] object.
|
||||
Remotes(Repository repo) {
|
||||
_repoPointer = repo.pointer;
|
||||
}
|
||||
|
|
|
@ -1075,4 +1075,47 @@ class Repository {
|
|||
name: name,
|
||||
);
|
||||
}
|
||||
|
||||
/// Gets the blame for a single file.
|
||||
///
|
||||
/// [flags] is a combination of [GitBlameFlag]s.
|
||||
///
|
||||
/// [minMatchCharacters] is the lower bound on the number of alphanumeric
|
||||
/// characters that must be detected as moving/copying within a file for
|
||||
/// it to associate those lines with the parent commit. The default value is 20.
|
||||
/// This value only takes effect if any of the [GitBlameFlag.trackCopies*]
|
||||
/// flags are specified.
|
||||
///
|
||||
/// [newestCommit] is the id of the newest commit to consider. The default is HEAD.
|
||||
///
|
||||
/// [oldestCommit] is the id of the oldest commit to consider. The default is the
|
||||
/// first commit encountered with no parent.
|
||||
///
|
||||
/// [minLine] is the first line in the file to blame. The default is 1
|
||||
/// (line numbers start with 1).
|
||||
///
|
||||
/// [maxLine] is the last line in the file to blame. The default is the last
|
||||
/// line of the file.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Blame blame({
|
||||
required String path,
|
||||
Set<GitBlameFlag> flags = const {GitBlameFlag.normal},
|
||||
int? minMatchCharacters,
|
||||
Oid? newestCommit,
|
||||
Oid? oldestCommit,
|
||||
int? minLine,
|
||||
int? maxLine,
|
||||
}) {
|
||||
return Blame.file(
|
||||
repo: this,
|
||||
path: path,
|
||||
flags: flags,
|
||||
minMatchCharacters: minMatchCharacters,
|
||||
newestCommit: newestCommit,
|
||||
oldestCommit: oldestCommit,
|
||||
minLine: minLine,
|
||||
maxLine: maxLine,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,4 +80,9 @@ class Signature {
|
|||
|
||||
/// Releases memory allocated for signature object.
|
||||
void free() => bindings.free(_signaturePointer);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Signature{name: $name, email: $email, time: $time, offset: $offset}';
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue