mirror of
https://github.com/SkinnyMind/libgit2dart.git
synced 2025-05-04 20:29:08 -04:00
feat(diff): add bindings and api
This commit is contained in:
parent
2ae5751efa
commit
a7b714c2f3
47 changed files with 1789 additions and 4 deletions
|
@ -14,5 +14,6 @@ export 'src/tag.dart';
|
|||
export 'src/treebuilder.dart';
|
||||
export 'src/branch.dart';
|
||||
export 'src/worktree.dart';
|
||||
export 'src/diff.dart';
|
||||
export 'src/error.dart';
|
||||
export 'src/git_types.dart';
|
||||
|
|
277
lib/src/bindings/diff.dart
Normal file
277
lib/src/bindings/diff.dart
Normal file
|
@ -0,0 +1,277 @@
|
|||
import 'dart:ffi';
|
||||
import 'package:ffi/ffi.dart';
|
||||
import '../error.dart';
|
||||
import 'libgit2_bindings.dart';
|
||||
import '../util.dart';
|
||||
|
||||
/// Create a diff between the repository index and the workdir directory.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Pointer<git_diff> indexToWorkdir(
|
||||
Pointer<git_repository> repo,
|
||||
Pointer<git_index> index,
|
||||
int flags,
|
||||
int contextLines,
|
||||
int interhunkLines,
|
||||
) {
|
||||
final out = calloc<Pointer<git_diff>>();
|
||||
final opts = calloc<git_diff_options>();
|
||||
final optsError =
|
||||
libgit2.git_diff_options_init(opts, GIT_DIFF_OPTIONS_VERSION);
|
||||
opts.ref.flags = flags;
|
||||
opts.ref.context_lines = contextLines;
|
||||
opts.ref.interhunk_lines = interhunkLines;
|
||||
|
||||
if (optsError < 0) {
|
||||
throw LibGit2Error(libgit2.git_error_last());
|
||||
}
|
||||
|
||||
libgit2.git_diff_index_to_workdir(out, repo, index, opts);
|
||||
|
||||
calloc.free(opts);
|
||||
|
||||
return out.value;
|
||||
}
|
||||
|
||||
/// Create a diff between a tree and repository index.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Pointer<git_diff> treeToIndex(
|
||||
Pointer<git_repository> repo,
|
||||
Pointer<git_tree> oldTree,
|
||||
Pointer<git_index> index,
|
||||
int flags,
|
||||
int contextLines,
|
||||
int interhunkLines,
|
||||
) {
|
||||
final out = calloc<Pointer<git_diff>>();
|
||||
final opts = calloc<git_diff_options>();
|
||||
final optsError =
|
||||
libgit2.git_diff_options_init(opts, GIT_DIFF_OPTIONS_VERSION);
|
||||
opts.ref.flags = flags;
|
||||
opts.ref.context_lines = contextLines;
|
||||
opts.ref.interhunk_lines = interhunkLines;
|
||||
|
||||
if (optsError < 0) {
|
||||
throw LibGit2Error(libgit2.git_error_last());
|
||||
}
|
||||
|
||||
libgit2.git_diff_tree_to_index(out, repo, oldTree, index, opts);
|
||||
|
||||
calloc.free(opts);
|
||||
|
||||
return out.value;
|
||||
}
|
||||
|
||||
/// Create a diff between a tree and the working directory.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Pointer<git_diff> treeToWorkdir(
|
||||
Pointer<git_repository> repo,
|
||||
Pointer<git_tree> oldTree,
|
||||
int flags,
|
||||
int contextLines,
|
||||
int interhunkLines,
|
||||
) {
|
||||
final out = calloc<Pointer<git_diff>>();
|
||||
final opts = calloc<git_diff_options>();
|
||||
final optsError =
|
||||
libgit2.git_diff_options_init(opts, GIT_DIFF_OPTIONS_VERSION);
|
||||
opts.ref.flags = flags;
|
||||
opts.ref.context_lines = contextLines;
|
||||
opts.ref.interhunk_lines = interhunkLines;
|
||||
|
||||
if (optsError < 0) {
|
||||
throw LibGit2Error(libgit2.git_error_last());
|
||||
}
|
||||
|
||||
libgit2.git_diff_tree_to_workdir(out, repo, oldTree, opts);
|
||||
|
||||
calloc.free(opts);
|
||||
|
||||
return out.value;
|
||||
}
|
||||
|
||||
/// Create a diff with the difference between two tree objects.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Pointer<git_diff> treeToTree(
|
||||
Pointer<git_repository> repo,
|
||||
Pointer<git_tree> oldTree,
|
||||
Pointer<git_tree> newTree,
|
||||
int flags,
|
||||
int contextLines,
|
||||
int interhunkLines,
|
||||
) {
|
||||
final out = calloc<Pointer<git_diff>>();
|
||||
final opts = calloc<git_diff_options>();
|
||||
final optsError =
|
||||
libgit2.git_diff_options_init(opts, GIT_DIFF_OPTIONS_VERSION);
|
||||
opts.ref.flags = flags;
|
||||
opts.ref.context_lines = contextLines;
|
||||
opts.ref.interhunk_lines = interhunkLines;
|
||||
|
||||
if (optsError < 0) {
|
||||
throw LibGit2Error(libgit2.git_error_last());
|
||||
}
|
||||
|
||||
libgit2.git_diff_tree_to_tree(out, repo, oldTree, newTree, opts);
|
||||
|
||||
calloc.free(opts);
|
||||
|
||||
return out.value;
|
||||
}
|
||||
|
||||
/// Query how many diff records are there in a diff.
|
||||
int length(Pointer<git_diff> diff) => libgit2.git_diff_num_deltas(diff);
|
||||
|
||||
/// Merge one diff into another.
|
||||
///
|
||||
/// This merges items from the "from" list into the "onto" list. The resulting diff
|
||||
/// will have all items that appear in either list. If an item appears in both lists,
|
||||
/// then it will be "merged" to appear as if the old version was from the "onto" list
|
||||
/// and the new version is from the "from" list (with the exception that if the item
|
||||
/// has a pending DELETE in the middle, then it will show as deleted).
|
||||
void merge(Pointer<git_diff> onto, Pointer<git_diff> from) {
|
||||
libgit2.git_diff_merge(onto, from);
|
||||
}
|
||||
|
||||
/// Read the contents of a git patch file into a git diff object.
|
||||
///
|
||||
/// The diff object produced is similar to the one that would be produced if you actually
|
||||
/// produced it computationally by comparing two trees, however there may be subtle differences.
|
||||
/// For example, a patch file likely contains abbreviated object IDs, so the object IDs in a
|
||||
/// diff delta produced by this function will also be abbreviated.
|
||||
///
|
||||
/// This function will only read patch files created by a git implementation, it will not
|
||||
/// read unified diffs produced by the `diff` program, nor any other types of patch files.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Pointer<git_diff> parse(String content) {
|
||||
final out = calloc<Pointer<git_diff>>();
|
||||
final contentC = content.toNativeUtf8().cast<Int8>();
|
||||
final error = libgit2.git_diff_from_buffer(out, contentC, content.length);
|
||||
|
||||
calloc.free(contentC);
|
||||
|
||||
if (error < 0) {
|
||||
throw LibGit2Error(libgit2.git_error_last());
|
||||
} else {
|
||||
return out.value;
|
||||
}
|
||||
}
|
||||
|
||||
/// Transform a diff marking file renames, copies, etc.
|
||||
///
|
||||
/// This modifies a diff in place, replacing old entries that look like renames or copies
|
||||
/// with new entries reflecting those changes. This also will, if requested, break modified
|
||||
/// files into add/remove pairs if the amount of change is above a threshold.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
void findSimilar(
|
||||
Pointer<git_diff> diff,
|
||||
int flags,
|
||||
int renameThreshold,
|
||||
int copyThreshold,
|
||||
int renameFromRewriteThreshold,
|
||||
int breakRewriteThreshold,
|
||||
int renameLimit) {
|
||||
final opts = calloc<git_diff_find_options>();
|
||||
final optsError =
|
||||
libgit2.git_diff_find_options_init(opts, GIT_DIFF_FIND_OPTIONS_VERSION);
|
||||
opts.ref.flags = flags;
|
||||
opts.ref.rename_threshold = renameThreshold;
|
||||
opts.ref.copy_threshold = copyThreshold;
|
||||
opts.ref.rename_from_rewrite_threshold = renameFromRewriteThreshold;
|
||||
opts.ref.break_rewrite_threshold = breakRewriteThreshold;
|
||||
opts.ref.rename_limit = renameLimit;
|
||||
|
||||
if (optsError < 0) {
|
||||
throw LibGit2Error(libgit2.git_error_last());
|
||||
}
|
||||
|
||||
final error = libgit2.git_diff_find_similar(diff, opts);
|
||||
|
||||
if (error < 0) {
|
||||
throw LibGit2Error(libgit2.git_error_last());
|
||||
}
|
||||
|
||||
calloc.free(opts);
|
||||
}
|
||||
|
||||
/// Return the diff delta for an entry in the diff list.
|
||||
///
|
||||
/// Throws [RangeError] if index out of range.
|
||||
Pointer<git_diff_delta> getDeltaByIndex(Pointer<git_diff> diff, int idx) {
|
||||
final result = libgit2.git_diff_get_delta(diff, idx);
|
||||
|
||||
if (result == nullptr) {
|
||||
throw RangeError('$idx is out of bounds');
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// Look up the single character abbreviation for a delta status code.
|
||||
///
|
||||
/// When you run `git diff --name-status` it uses single letter codes in the output such as
|
||||
/// 'A' for added, 'D' for deleted, 'M' for modified, etc. This function converts a [GitDelta]
|
||||
/// value into these letters for your own purposes. [GitDelta.untracked] will return
|
||||
/// a space (i.e. ' ').
|
||||
String statusChar(int status) {
|
||||
final result = libgit2.git_diff_status_char(status);
|
||||
return String.fromCharCode(result);
|
||||
}
|
||||
|
||||
/// Accumulate diff statistics for all patches.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Pointer<git_diff_stats> stats(Pointer<git_diff> diff) {
|
||||
final out = calloc<Pointer<git_diff_stats>>();
|
||||
final error = libgit2.git_diff_get_stats(out, diff);
|
||||
|
||||
if (error < 0) {
|
||||
throw LibGit2Error(libgit2.git_error_last());
|
||||
} else {
|
||||
return out.value;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the total number of insertions in a diff.
|
||||
int statsInsertions(Pointer<git_diff_stats> stats) =>
|
||||
libgit2.git_diff_stats_insertions(stats);
|
||||
|
||||
/// Get the total number of deletions in a diff.
|
||||
int statsDeletions(Pointer<git_diff_stats> stats) =>
|
||||
libgit2.git_diff_stats_deletions(stats);
|
||||
|
||||
/// Get the total number of files changed in a diff.
|
||||
int statsFilesChanged(Pointer<git_diff_stats> stats) =>
|
||||
libgit2.git_diff_stats_files_changed(stats);
|
||||
|
||||
/// Print diff statistics.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
String statsPrint(
|
||||
Pointer<git_diff_stats> stats,
|
||||
int format,
|
||||
int width,
|
||||
) {
|
||||
final out = calloc<git_buf>(sizeOf<git_buf>());
|
||||
final error = libgit2.git_diff_stats_to_buf(out, stats, format, width);
|
||||
|
||||
if (error < 0) {
|
||||
throw LibGit2Error(libgit2.git_error_last());
|
||||
} else {
|
||||
final result = out.ref.ptr.cast<Utf8>().toDartString();
|
||||
calloc.free(out);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// Free a previously allocated diff stats.
|
||||
void statsFree(Pointer<git_diff_stats> stats) =>
|
||||
libgit2.git_diff_stats_free(stats);
|
||||
|
||||
/// Free a previously allocated diff.
|
||||
void free(Pointer<git_diff> diff) => libgit2.git_diff_free(diff);
|
213
lib/src/diff.dart
Normal file
213
lib/src/diff.dart
Normal file
|
@ -0,0 +1,213 @@
|
|||
import 'dart:ffi';
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'bindings/libgit2_bindings.dart';
|
||||
import 'bindings/diff.dart' as bindings;
|
||||
import 'git_types.dart';
|
||||
import 'oid.dart';
|
||||
import 'util.dart';
|
||||
|
||||
class Diff {
|
||||
/// Initializes a new instance of [Diff] class from provided
|
||||
/// pointer to diff object in memory.
|
||||
///
|
||||
/// Should be freed with `free()` to release allocated memory.
|
||||
Diff(this._diffPointer) {
|
||||
libgit2.git_libgit2_init();
|
||||
}
|
||||
|
||||
Diff.parse(String content) {
|
||||
libgit2.git_libgit2_init();
|
||||
_diffPointer = bindings.parse(content);
|
||||
}
|
||||
|
||||
late final Pointer<git_diff> _diffPointer;
|
||||
|
||||
/// Pointer to memory address for allocated diff object.
|
||||
Pointer<git_diff> get pointer => _diffPointer;
|
||||
|
||||
/// Queries how many diff records are there in a diff.
|
||||
int get length => bindings.length(_diffPointer);
|
||||
|
||||
/// Returns a list of [DiffDelta]s containing file pairs with and old and new revisions.
|
||||
List<DiffDelta> get deltas {
|
||||
final length = bindings.length(_diffPointer);
|
||||
var deltas = <DiffDelta>[];
|
||||
for (var i = 0; i < length; i++) {
|
||||
deltas.add(DiffDelta(bindings.getDeltaByIndex(_diffPointer, i)));
|
||||
}
|
||||
return deltas;
|
||||
}
|
||||
|
||||
/// Accumulates diff statistics for all patches.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
DiffStats get stats => DiffStats(bindings.stats(_diffPointer));
|
||||
|
||||
/// Merges one diff into another.
|
||||
void merge(Diff diff) => bindings.merge(_diffPointer, diff.pointer);
|
||||
|
||||
/// Transforms a diff marking file renames, copies, etc.
|
||||
///
|
||||
/// This modifies a diff in place, replacing old entries that look like renames or copies
|
||||
/// with new entries reflecting those changes. This also will, if requested, break modified
|
||||
/// files into add/remove pairs if the amount of change is above a threshold.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
void findSimilar({
|
||||
Set<GitDiffFind> flags = const {GitDiffFind.byConfig},
|
||||
int renameThreshold = 50,
|
||||
int copyThreshold = 50,
|
||||
int renameFromRewriteThreshold = 50,
|
||||
int breakRewriteThreshold = 60,
|
||||
int renameLimit = 200,
|
||||
}) {
|
||||
final int flagsInt =
|
||||
flags.fold(0, (previousValue, e) => previousValue | e.value);
|
||||
|
||||
bindings.findSimilar(
|
||||
_diffPointer,
|
||||
flagsInt,
|
||||
renameThreshold,
|
||||
copyThreshold,
|
||||
renameFromRewriteThreshold,
|
||||
breakRewriteThreshold,
|
||||
renameLimit,
|
||||
);
|
||||
}
|
||||
|
||||
/// Releases memory allocated for diff object.
|
||||
void free() => bindings.free(_diffPointer);
|
||||
}
|
||||
|
||||
class DiffDelta {
|
||||
/// Initializes a new instance of [DiffDelta] class from provided
|
||||
/// pointer to diff delta object in memory.
|
||||
DiffDelta(this._diffDeltaPointer);
|
||||
|
||||
/// Pointer to memory address for allocated diff delta object.
|
||||
final Pointer<git_diff_delta> _diffDeltaPointer;
|
||||
|
||||
/// Returns type of change.
|
||||
GitDelta get status {
|
||||
late final GitDelta status;
|
||||
for (var type in GitDelta.values) {
|
||||
if (_diffDeltaPointer.ref.status == type.value) {
|
||||
status = type;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/// Looks up the single character abbreviation for a delta status code.
|
||||
///
|
||||
/// When you run `git diff --name-status` it uses single letter codes in the output such as
|
||||
/// 'A' for added, 'D' for deleted, 'M' for modified, etc. This function converts a [GitDelta]
|
||||
/// value into these letters for your own purposes. [GitDelta.untracked] will return
|
||||
/// a space (i.e. ' ').
|
||||
String get statusChar => bindings.statusChar(_diffDeltaPointer.ref.status);
|
||||
|
||||
/// Returns flags for the delta object.
|
||||
Set<GitDiffFlag> get flags {
|
||||
var flags = <GitDiffFlag>{};
|
||||
for (var flag in GitDiffFlag.values) {
|
||||
if (_diffDeltaPointer.ref.flags & flag.value == flag.value) {
|
||||
flags.add(flag);
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
/// Returns a similarity score for renamed or copied files between 0 and 100
|
||||
/// indicating how similar the old and new sides are.
|
||||
///
|
||||
/// The similarity score is zero unless you call `find_similar()` which does
|
||||
/// a similarity analysis of files in the diff.
|
||||
int get similarity => _diffDeltaPointer.ref.similarity;
|
||||
|
||||
/// Returns number of files in this delta.
|
||||
int get numberOfFiles => _diffDeltaPointer.ref.nfiles;
|
||||
|
||||
/// Represents the "from" side of the diff.
|
||||
DiffFile get oldFile => DiffFile(_diffDeltaPointer.ref.old_file);
|
||||
|
||||
/// Represents the "to" side of the diff.
|
||||
DiffFile get newFile => DiffFile(_diffDeltaPointer.ref.new_file);
|
||||
}
|
||||
|
||||
/// Description of one side of a delta.
|
||||
///
|
||||
/// Although this is called a "file", it could represent a file, a symbolic
|
||||
/// link, a submodule commit id, or even a tree (although that only if you
|
||||
/// are tracking type changes or ignored/untracked directories).
|
||||
class DiffFile {
|
||||
/// Initializes a new instance of [DiffFile] class from provided diff file object.
|
||||
DiffFile(this._diffFile);
|
||||
|
||||
final git_diff_file _diffFile;
|
||||
|
||||
/// Returns oid of the item. If the entry represents an absent side of a diff
|
||||
/// then the oid will be zeroes.
|
||||
Oid get id => Oid.fromRaw(_diffFile.id);
|
||||
|
||||
/// Returns path to the entry relative to the working directory of the repository.
|
||||
String get path => _diffFile.path.cast<Utf8>().toDartString();
|
||||
|
||||
/// Returns the size of the entry in bytes.
|
||||
int get size => _diffFile.size;
|
||||
|
||||
/// Returns flags for the diff file object.
|
||||
Set<GitDiffFlag> get flags {
|
||||
var flags = <GitDiffFlag>{};
|
||||
for (var flag in GitDiffFlag.values) {
|
||||
if (_diffFile.flags & flag.value == flag.value) {
|
||||
flags.add(flag);
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
/// Returns one of the [GitFilemode] values.
|
||||
GitFilemode get mode {
|
||||
late final GitFilemode result;
|
||||
for (var mode in GitFilemode.values) {
|
||||
if (_diffFile.mode == mode.value) {
|
||||
result = mode;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class DiffStats {
|
||||
/// Initializes a new instance of [DiffStats] class from provided
|
||||
/// pointer to diff stats object in memory.
|
||||
DiffStats(this._diffStatsPointer);
|
||||
|
||||
/// Pointer to memory address for allocated diff delta object.
|
||||
final Pointer<git_diff_stats> _diffStatsPointer;
|
||||
|
||||
/// Returns the total number of insertions.
|
||||
int get insertions => bindings.statsInsertions(_diffStatsPointer);
|
||||
|
||||
/// Returns the total number of deletions.
|
||||
int get deletions => bindings.statsDeletions(_diffStatsPointer);
|
||||
|
||||
/// Returns the total number of files changed.
|
||||
int get filesChanged => bindings.statsFilesChanged(_diffStatsPointer);
|
||||
|
||||
/// Print diff statistics.
|
||||
///
|
||||
/// Width for output only affects formatting of [GitDiffStats.full].
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
String print(Set<GitDiffStats> format, int width) {
|
||||
final int formatInt =
|
||||
format.fold(0, (previousValue, e) => previousValue | e.value);
|
||||
return bindings.statsPrint(_diffStatsPointer, formatInt, width);
|
||||
}
|
||||
|
||||
/// Releases memory allocated for diff stats object.
|
||||
void free() => bindings.statsFree(_diffStatsPointer);
|
||||
}
|
|
@ -601,3 +601,405 @@ class GitReset {
|
|||
@override
|
||||
String toString() => 'GitReset.$_name';
|
||||
}
|
||||
|
||||
/// Flags for diff options. A combination of these flags can be passed.
|
||||
class GitDiff {
|
||||
const GitDiff._(this._value, this._name);
|
||||
final int _value;
|
||||
final String _name;
|
||||
|
||||
/// Normal diff, the default.
|
||||
static const normal = GitDiff._(0, 'normal');
|
||||
|
||||
/// Reverse the sides of the diff.
|
||||
static const reverse = GitDiff._(1, 'reverse');
|
||||
|
||||
/// Include ignored files in the diff.
|
||||
static const includeIgnored = GitDiff._(2, 'includeIgnored');
|
||||
|
||||
/// Even with [GitDiff.includeUntracked], an entire ignored directory
|
||||
/// will be marked with only a single entry in the diff; this flag
|
||||
/// adds all files under the directory as IGNORED entries, too.
|
||||
static const recurseIgnoredDirs = GitDiff._(4, 'recurseIgnoredDirs');
|
||||
|
||||
/// Include untracked files in the diff.
|
||||
static const includeUntracked = GitDiff._(8, 'includeUntracked');
|
||||
|
||||
/// Even with [GitDiff.includeUntracked], an entire untracked
|
||||
/// directory will be marked with only a single entry in the diff
|
||||
/// (a la what core Git does in `git status`); this flag adds *all*
|
||||
/// files under untracked directories as UNTRACKED entries, too.
|
||||
static const recurseUntrackedDirs = GitDiff._(16, 'recurseUntrackedDirs');
|
||||
|
||||
/// Include unmodified files in the diff.
|
||||
static const includeUnmodified = GitDiff._(32, 'includeUnmodified');
|
||||
|
||||
/// Normally, a type change between files will be converted into a
|
||||
/// DELETED record for the old and an ADDED record for the new; this
|
||||
/// options enabled the generation of TYPECHANGE delta records.
|
||||
static const includeTypechange = GitDiff._(64, 'includeTypechange');
|
||||
|
||||
/// Even with [GitDiff.includeTypechange], blob->tree changes still
|
||||
/// generally show as a DELETED blob. This flag tries to correctly
|
||||
/// label blob->tree transitions as TYPECHANGE records with new_file's
|
||||
/// mode set to tree. Note: the tree SHA will not be available.
|
||||
static const includeTypechangeTrees =
|
||||
GitDiff._(128, 'includeTypechangeTrees');
|
||||
|
||||
/// Ignore file mode changes.
|
||||
static const ignoreFilemode = GitDiff._(256, 'ignoreFilemode');
|
||||
|
||||
/// Treat all submodules as unmodified.
|
||||
static const ignoreSubmodules = GitDiff._(512, 'ignoreSubmodules');
|
||||
|
||||
/// Use case insensitive filename comparisons.
|
||||
static const ignoreCase = GitDiff._(1024, 'ignoreCase');
|
||||
|
||||
/// May be combined with [GitDiff.ignoreCase] to specify that a file
|
||||
/// that has changed case will be returned as an add/delete pair.
|
||||
static const includeCaseChange = GitDiff._(2048, 'includeCaseChange');
|
||||
|
||||
/// If the pathspec is set in the diff options, this flags indicates
|
||||
/// that the paths will be treated as literal paths instead of
|
||||
/// fnmatch patterns. Each path in the list must either be a full
|
||||
/// path to a file or a directory. (A trailing slash indicates that
|
||||
/// the path will _only_ match a directory). If a directory is
|
||||
/// specified, all children will be included.
|
||||
static const disablePathspecMatch = GitDiff._(4096, 'disablePathspecMatch');
|
||||
|
||||
/// Disable updating of the `binary` flag in delta records. This is
|
||||
/// useful when iterating over a diff if you don't need hunk and data
|
||||
/// callbacks and want to avoid having to load file completely.
|
||||
static const skipBinaryCheck = GitDiff._(8192, 'skipBinaryCheck');
|
||||
|
||||
/// When diff finds an untracked directory, to match the behavior of
|
||||
/// core Git, it scans the contents for IGNORED and UNTRACKED files.
|
||||
/// If *all* contents are IGNORED, then the directory is IGNORED; if
|
||||
/// any contents are not IGNORED, then the directory is UNTRACKED.
|
||||
/// This is extra work that may not matter in many cases. This flag
|
||||
/// turns off that scan and immediately labels an untracked directory
|
||||
/// as UNTRACKED (changing the behavior to not match core Git).
|
||||
static const enableFastUntrackedDirs =
|
||||
GitDiff._(16384, 'enableFastUntrackedDirs');
|
||||
|
||||
/// When diff finds a file in the working directory with stat
|
||||
/// information different from the index, but the OID ends up being the
|
||||
/// same, write the correct stat information into the index. Note:
|
||||
/// without this flag, diff will always leave the index untouched.
|
||||
static const updateIndex = GitDiff._(32768, 'updateIndex');
|
||||
|
||||
/// Include unreadable files in the diff.
|
||||
static const includeUnreadable = GitDiff._(65536, 'includeUnreadable');
|
||||
|
||||
/// Include unreadable files in the diff.
|
||||
static const includeUnreadableAsUntracked =
|
||||
GitDiff._(131072, 'includeUnreadableAsUntracked');
|
||||
|
||||
/// Use a heuristic that takes indentation and whitespace into account
|
||||
/// which generally can produce better diffs when dealing with ambiguous
|
||||
/// diff hunks.
|
||||
static const indentHeuristic = GitDiff._(262144, 'indentHeuristic');
|
||||
|
||||
/// Treat all files as text, disabling binary attributes & detection.
|
||||
static const forceText = GitDiff._(1048576, 'forceText');
|
||||
|
||||
/// Treat all files as binary, disabling text diffs.
|
||||
static const forceBinary = GitDiff._(2097152, 'forceBinary');
|
||||
|
||||
/// Ignore all whitespace.
|
||||
static const ignoreWhitespace = GitDiff._(4194304, 'ignoreWhitespace');
|
||||
|
||||
/// Ignore changes in amount of whitespace.
|
||||
static const ignoreWhitespaceChange =
|
||||
GitDiff._(8388608, 'ignoreWhitespaceChange');
|
||||
|
||||
/// Ignore whitespace at end of line.
|
||||
static const ignoreWhitespaceEOL = GitDiff._(16777216, 'ignoreWhitespaceEOL');
|
||||
|
||||
/// When generating patch text, include the content of untracked
|
||||
/// files. This automatically turns on [GitDiff.includeUntracked] but
|
||||
/// it does not turn on [GitDiff.recurseUntrackedDirs]. Add that
|
||||
/// flag if you want the content of every single UNTRACKED file.
|
||||
static const showUntrackedContent =
|
||||
GitDiff._(33554432, 'showUntrackedContent');
|
||||
|
||||
/// When generating output, include the names of unmodified files if
|
||||
/// they are included in the git diff. Normally these are skipped in
|
||||
/// the formats that list files (e.g. name-only, name-status, raw).
|
||||
/// Even with this, these will not be included in patch format.
|
||||
static const showUnmodified = GitDiff._(67108864, 'showUnmodified');
|
||||
|
||||
/// Use the "patience diff" algorithm.
|
||||
static const patience = GitDiff._(268435456, 'patience');
|
||||
|
||||
/// Take extra time to find minimal diff.
|
||||
static const minimal = GitDiff._(536870912, 'minimal');
|
||||
|
||||
/// Include the necessary deflate / delta information so that `git-apply`
|
||||
/// can apply given diff information to binary files.
|
||||
static const showBinary = GitDiff._(1073741824, 'showBinary');
|
||||
|
||||
static const List<GitDiff> values = [
|
||||
normal,
|
||||
reverse,
|
||||
includeIgnored,
|
||||
recurseIgnoredDirs,
|
||||
includeUntracked,
|
||||
recurseUntrackedDirs,
|
||||
includeUnmodified,
|
||||
includeTypechange,
|
||||
includeTypechangeTrees,
|
||||
ignoreFilemode,
|
||||
ignoreSubmodules,
|
||||
ignoreCase,
|
||||
includeCaseChange,
|
||||
disablePathspecMatch,
|
||||
skipBinaryCheck,
|
||||
enableFastUntrackedDirs,
|
||||
updateIndex,
|
||||
includeUnreadable,
|
||||
includeUnreadableAsUntracked,
|
||||
indentHeuristic,
|
||||
forceText,
|
||||
forceBinary,
|
||||
ignoreWhitespace,
|
||||
ignoreWhitespaceChange,
|
||||
ignoreWhitespaceEOL,
|
||||
showUntrackedContent,
|
||||
showUnmodified,
|
||||
patience,
|
||||
minimal,
|
||||
showBinary,
|
||||
];
|
||||
|
||||
int get value => _value;
|
||||
|
||||
@override
|
||||
String toString() => 'GitDiff.$_name';
|
||||
}
|
||||
|
||||
/// What type of change is described by a git_diff_delta?
|
||||
///
|
||||
/// [GitDelta.renamed] and [GitDelta.copied] will only show up if you run
|
||||
/// `findSimilar()` on the diff object.
|
||||
///
|
||||
/// [GitDelta.typechange] only shows up given [GitDiff.includeTypechange]
|
||||
/// in the option flags (otherwise type changes will be split into ADDED /
|
||||
/// DELETED pairs).
|
||||
class GitDelta {
|
||||
const GitDelta._(this._value, this._name);
|
||||
final int _value;
|
||||
final String _name;
|
||||
|
||||
/// No changes.
|
||||
static const unmodified = GitDelta._(0, 'unmodified');
|
||||
|
||||
/// Entry does not exist in old version.
|
||||
static const added = GitDelta._(1, 'added');
|
||||
|
||||
/// Entry does not exist in new version.
|
||||
static const deleted = GitDelta._(2, 'deleted');
|
||||
|
||||
/// Entry content changed between old and new.
|
||||
static const modified = GitDelta._(3, 'modified');
|
||||
|
||||
/// Entry was renamed between old and new.
|
||||
static const renamed = GitDelta._(4, 'renamed');
|
||||
|
||||
/// Entry was copied from another old entry.
|
||||
static const copied = GitDelta._(5, 'copied');
|
||||
|
||||
/// Entry is ignored item in workdir.
|
||||
static const ignored = GitDelta._(6, 'ignored');
|
||||
|
||||
/// Entry is is untracked item in workdir.
|
||||
static const untracked = GitDelta._(7, 'untracked');
|
||||
|
||||
/// Type of entry changed between old and new.
|
||||
static const typechange = GitDelta._(8, 'typechange');
|
||||
|
||||
/// Entry is unreadable.
|
||||
static const unreadable = GitDelta._(9, 'unreadable');
|
||||
|
||||
/// Entry in the index is conflicted.
|
||||
static const conflicted = GitDelta._(10, 'conflicted');
|
||||
|
||||
static const List<GitDelta> values = [
|
||||
unmodified,
|
||||
added,
|
||||
deleted,
|
||||
modified,
|
||||
renamed,
|
||||
copied,
|
||||
ignored,
|
||||
untracked,
|
||||
typechange,
|
||||
unreadable,
|
||||
conflicted,
|
||||
];
|
||||
|
||||
int get value => _value;
|
||||
|
||||
@override
|
||||
String toString() => 'GitDelta.$_name';
|
||||
}
|
||||
|
||||
/// Flags for the delta object and the file objects on each side.
|
||||
class GitDiffFlag {
|
||||
const GitDiffFlag._(this._value, this._name);
|
||||
final int _value;
|
||||
final String _name;
|
||||
|
||||
/// File(s) treated as binary data.
|
||||
static const binary = GitDiffFlag._(1, 'binary');
|
||||
|
||||
/// File(s) treated as text data.
|
||||
static const notBinary = GitDiffFlag._(2, 'notBinary');
|
||||
|
||||
/// `id` value is known correct.
|
||||
static const validId = GitDiffFlag._(4, 'validId');
|
||||
|
||||
/// File exists at this side of the delta.
|
||||
static const exists = GitDiffFlag._(8, 'exists');
|
||||
|
||||
static const List<GitDiffFlag> values = [binary, notBinary, validId, exists];
|
||||
|
||||
int get value => _value;
|
||||
|
||||
@override
|
||||
String toString() => 'GitDiffFlag.$_name';
|
||||
}
|
||||
|
||||
/// Formatting options for diff stats.
|
||||
class GitDiffStats {
|
||||
const GitDiffStats._(this._value, this._name);
|
||||
final int _value;
|
||||
final String _name;
|
||||
|
||||
/// No stats.
|
||||
static const none = GitDiffStats._(0, 'none');
|
||||
|
||||
/// Full statistics, equivalent of `--stat`.
|
||||
static const full = GitDiffStats._(1, 'full');
|
||||
|
||||
/// Short statistics, equivalent of `--shortstat`.
|
||||
static const short = GitDiffStats._(2, 'short');
|
||||
|
||||
/// Number statistics, equivalent of `--numstat`.
|
||||
static const number = GitDiffStats._(4, 'number');
|
||||
|
||||
/// Extended header information such as creations, renames and mode changes,
|
||||
/// equivalent of `--summary`.
|
||||
static const includeSummary = GitDiffStats._(8, 'includeSummary');
|
||||
|
||||
static const List<GitDiffStats> values = [
|
||||
none,
|
||||
full,
|
||||
short,
|
||||
number,
|
||||
includeSummary,
|
||||
];
|
||||
|
||||
int get value => _value;
|
||||
|
||||
@override
|
||||
String toString() => 'GitDiffStats.$_name';
|
||||
}
|
||||
|
||||
/// Formatting options for diff stats.
|
||||
class GitDiffFind {
|
||||
const GitDiffFind._(this._value, this._name);
|
||||
final int _value;
|
||||
final String _name;
|
||||
|
||||
/// Obey `diff.renames`. Overridden by any other [GitDiffFind] flag.
|
||||
static const byConfig = GitDiffFind._(0, 'byConfig');
|
||||
|
||||
/// Look for renames? (`--find-renames`)
|
||||
static const renames = GitDiffFind._(1, 'renames');
|
||||
|
||||
/// Consider old side of MODIFIED for renames? (`--break-rewrites=N`)
|
||||
static const renamesFromRewrites = GitDiffFind._(2, 'renamesFromRewrites');
|
||||
|
||||
/// Look for copies? (a la `--find-copies`)
|
||||
static const copies = GitDiffFind._(4, 'copies');
|
||||
|
||||
/// Consider UNMODIFIED as copy sources? (`--find-copies-harder`)
|
||||
///
|
||||
/// For this to work correctly, use [GitDiff.includeUnmodified] when
|
||||
/// the initial git diff is being generated.
|
||||
static const copiesFromUnmodified = GitDiffFind._(8, 'copiesFromUnmodified');
|
||||
|
||||
/// Mark significant rewrites for split (`--break-rewrites=/M`)
|
||||
static const rewrites = GitDiffFind._(16, 'rewrites');
|
||||
|
||||
/// Actually split large rewrites into delete/add pairs.
|
||||
static const breakRewrites = GitDiffFind._(32, 'breakRewrites');
|
||||
|
||||
/// Mark rewrites for split and break into delete/add pairs.
|
||||
static const andBreakRewrites = GitDiffFind._(48, 'andBreakRewrites');
|
||||
|
||||
/// Find renames/copies for UNTRACKED items in working directory.
|
||||
///
|
||||
/// For this to work correctly, use [GitDiff.includeUntracked] when the
|
||||
/// initial git diff is being generated (and obviously the diff must
|
||||
/// be against the working directory for this to make sense).
|
||||
static const forUntracked = GitDiffFind._(64, 'forUntracked');
|
||||
|
||||
/// Turn on all finding features.
|
||||
static const all = GitDiffFind._(255, 'all');
|
||||
|
||||
/// Measure similarity ignoring all whitespace.
|
||||
static const ignoreWhitespace = GitDiffFind._(4096, 'ignoreWhitespace');
|
||||
|
||||
/// Measure similarity including all data.
|
||||
static const dontIgnoreWhitespace =
|
||||
GitDiffFind._(8192, 'dontIgnoreWhitespace');
|
||||
|
||||
/// Measure similarity only by comparing SHAs (fast and cheap).
|
||||
static const exactMatchOnly = GitDiffFind._(16384, 'exactMatchOnly');
|
||||
|
||||
/// Do not break rewrites unless they contribute to a rename.
|
||||
///
|
||||
/// Normally, [GitDiffFind.andBreakRewrites] will measure the self-
|
||||
/// similarity of modified files and split the ones that have changed a
|
||||
/// lot into a DELETE / ADD pair. Then the sides of that pair will be
|
||||
/// considered candidates for rename and copy detection.
|
||||
///
|
||||
/// If you add this flag in and the split pair is *not* used for an
|
||||
/// actual rename or copy, then the modified record will be restored to
|
||||
/// a regular MODIFIED record instead of being split.
|
||||
static const breakRewritesForRenamesOnly =
|
||||
GitDiffFind._(32768, 'breakRewritesForRenamesOnly');
|
||||
|
||||
/// Remove any UNMODIFIED deltas after find_similar is done.
|
||||
///
|
||||
/// Using [GitDiffFind.copiesFromUnmodified] to emulate the
|
||||
/// --find-copies-harder behavior requires building a diff with the
|
||||
/// [GitDiff.includeUnmodified] flag. If you do not want UNMODIFIED
|
||||
/// records in the final result, pass this flag to have them removed.
|
||||
static const removeUnmodified = GitDiffFind._(65536, 'removeUnmodified');
|
||||
|
||||
static const List<GitDiffFind> values = [
|
||||
byConfig,
|
||||
renames,
|
||||
renamesFromRewrites,
|
||||
copies,
|
||||
copiesFromUnmodified,
|
||||
rewrites,
|
||||
breakRewrites,
|
||||
andBreakRewrites,
|
||||
forUntracked,
|
||||
all,
|
||||
ignoreWhitespace,
|
||||
dontIgnoreWhitespace,
|
||||
exactMatchOnly,
|
||||
breakRewritesForRenamesOnly,
|
||||
removeUnmodified,
|
||||
];
|
||||
|
||||
int get value => _value;
|
||||
|
||||
@override
|
||||
String toString() => 'GitDiffFind.$_name';
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import 'dart:ffi';
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:libgit2dart/libgit2dart.dart';
|
||||
import 'package:libgit2dart/src/tree.dart';
|
||||
import 'bindings/libgit2_bindings.dart';
|
||||
import 'bindings/index.dart' as bindings;
|
||||
import 'bindings/diff.dart' as diff_bindings;
|
||||
import 'oid.dart';
|
||||
import 'git_types.dart';
|
||||
import 'repository.dart';
|
||||
|
@ -17,9 +19,11 @@ class Index {
|
|||
libgit2.git_libgit2_init();
|
||||
}
|
||||
|
||||
/// Pointer to memory address for allocated index object.
|
||||
late final Pointer<git_index> _indexPointer;
|
||||
|
||||
/// Pointer to memory address for allocated index object.
|
||||
Pointer<git_index> get pointer => _indexPointer;
|
||||
|
||||
/// Returns index entry located at provided 0-based position or string path.
|
||||
///
|
||||
/// Throws error if position is out of bounds or entry isn't found at path.
|
||||
|
@ -154,7 +158,7 @@ class Index {
|
|||
/// Throws a [LibGit2Error] if error occured.
|
||||
void write() => bindings.write(_indexPointer);
|
||||
|
||||
/// Write the index as a tree.
|
||||
/// Writes 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
|
||||
|
@ -177,11 +181,55 @@ class Index {
|
|||
void remove(String path, [int stage = 0]) =>
|
||||
bindings.remove(_indexPointer, path, stage);
|
||||
|
||||
/// Remove all matching index entries.
|
||||
/// Removes all matching index entries.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
void removeAll(List<String> path) => bindings.removeAll(_indexPointer, path);
|
||||
|
||||
/// Creates a diff between the repository index and the workdir directory.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Diff diffToWorkdir({
|
||||
Set<GitDiff> flags = const {GitDiff.normal},
|
||||
int contextLines = 3,
|
||||
int interhunkLines = 0,
|
||||
}) {
|
||||
final repo = bindings.owner(_indexPointer);
|
||||
final int flagsInt =
|
||||
flags.fold(0, (previousValue, e) => previousValue | e.value);
|
||||
|
||||
return Diff(diff_bindings.indexToWorkdir(
|
||||
repo,
|
||||
_indexPointer,
|
||||
flagsInt,
|
||||
contextLines,
|
||||
interhunkLines,
|
||||
));
|
||||
}
|
||||
|
||||
/// Creates a diff between a tree and repository index.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Diff diffToTree({
|
||||
required Tree tree,
|
||||
Set<GitDiff> flags = const {GitDiff.normal},
|
||||
int contextLines = 3,
|
||||
int interhunkLines = 0,
|
||||
}) {
|
||||
final repo = bindings.owner(_indexPointer);
|
||||
final int flagsInt =
|
||||
flags.fold(0, (previousValue, e) => previousValue | e.value);
|
||||
|
||||
return Diff(diff_bindings.treeToIndex(
|
||||
repo,
|
||||
tree.pointer,
|
||||
_indexPointer,
|
||||
flagsInt,
|
||||
contextLines,
|
||||
interhunkLines,
|
||||
));
|
||||
}
|
||||
|
||||
/// Releases memory allocated for index object.
|
||||
void free() => bindings.free(_indexPointer);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import 'dart:ffi';
|
||||
import 'bindings/libgit2_bindings.dart';
|
||||
import 'bindings/tree.dart' as bindings;
|
||||
import 'bindings/diff.dart' as diff_bindings;
|
||||
import 'diff.dart';
|
||||
import 'index.dart';
|
||||
import 'repository.dart';
|
||||
import 'oid.dart';
|
||||
import 'git_types.dart';
|
||||
|
@ -67,6 +70,73 @@ class Tree {
|
|||
/// Get the number of entries listed in a tree.
|
||||
int get length => bindings.entryCount(_treePointer);
|
||||
|
||||
/// Creates a diff between a tree and the working directory.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Diff diffToWorkdir({
|
||||
Set<GitDiff> flags = const {GitDiff.normal},
|
||||
int contextLines = 3,
|
||||
int interhunkLines = 0,
|
||||
}) {
|
||||
final repo = bindings.owner(_treePointer);
|
||||
final int flagsInt =
|
||||
flags.fold(0, (previousValue, e) => previousValue | e.value);
|
||||
|
||||
return Diff(diff_bindings.treeToWorkdir(
|
||||
repo,
|
||||
_treePointer,
|
||||
flagsInt,
|
||||
contextLines,
|
||||
interhunkLines,
|
||||
));
|
||||
}
|
||||
|
||||
/// Creates a diff between a tree and repository index.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Diff diffToIndex({
|
||||
required Index index,
|
||||
Set<GitDiff> flags = const {GitDiff.normal},
|
||||
int contextLines = 3,
|
||||
int interhunkLines = 0,
|
||||
}) {
|
||||
final repo = bindings.owner(_treePointer);
|
||||
final int flagsInt =
|
||||
flags.fold(0, (previousValue, e) => previousValue | e.value);
|
||||
|
||||
return Diff(diff_bindings.treeToIndex(
|
||||
repo,
|
||||
_treePointer,
|
||||
index.pointer,
|
||||
flagsInt,
|
||||
contextLines,
|
||||
interhunkLines,
|
||||
));
|
||||
}
|
||||
|
||||
/// Creates a diff with the difference between two tree objects.
|
||||
///
|
||||
/// Throws a [LibGit2Error] if error occured.
|
||||
Diff diffToTree({
|
||||
required Tree tree,
|
||||
Set<GitDiff> flags = const {GitDiff.normal},
|
||||
int contextLines = 3,
|
||||
int interhunkLines = 0,
|
||||
}) {
|
||||
final repo = bindings.owner(_treePointer);
|
||||
final int flagsInt =
|
||||
flags.fold(0, (previousValue, e) => previousValue | e.value);
|
||||
|
||||
return Diff(diff_bindings.treeToTree(
|
||||
repo,
|
||||
_treePointer,
|
||||
tree.pointer,
|
||||
flagsInt,
|
||||
contextLines,
|
||||
interhunkLines,
|
||||
));
|
||||
}
|
||||
|
||||
/// Releases memory allocated for tree object.
|
||||
void free() => bindings.free(_treePointer);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue