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/treebuilder.dart';
|
||||||
export 'src/branch.dart';
|
export 'src/branch.dart';
|
||||||
export 'src/worktree.dart';
|
export 'src/worktree.dart';
|
||||||
|
export 'src/diff.dart';
|
||||||
export 'src/error.dart';
|
export 'src/error.dart';
|
||||||
export 'src/git_types.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
|
@override
|
||||||
String toString() => 'GitReset.$_name';
|
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 'dart:ffi';
|
||||||
import 'package:ffi/ffi.dart';
|
import 'package:ffi/ffi.dart';
|
||||||
|
import 'package:libgit2dart/libgit2dart.dart';
|
||||||
import 'package:libgit2dart/src/tree.dart';
|
import 'package:libgit2dart/src/tree.dart';
|
||||||
import 'bindings/libgit2_bindings.dart';
|
import 'bindings/libgit2_bindings.dart';
|
||||||
import 'bindings/index.dart' as bindings;
|
import 'bindings/index.dart' as bindings;
|
||||||
|
import 'bindings/diff.dart' as diff_bindings;
|
||||||
import 'oid.dart';
|
import 'oid.dart';
|
||||||
import 'git_types.dart';
|
import 'git_types.dart';
|
||||||
import 'repository.dart';
|
import 'repository.dart';
|
||||||
|
@ -17,9 +19,11 @@ class Index {
|
||||||
libgit2.git_libgit2_init();
|
libgit2.git_libgit2_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pointer to memory address for allocated index object.
|
|
||||||
late final Pointer<git_index> _indexPointer;
|
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.
|
/// 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.
|
/// 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.
|
/// Throws a [LibGit2Error] if error occured.
|
||||||
void write() => bindings.write(_indexPointer);
|
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;
|
/// 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
|
/// 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]) =>
|
void remove(String path, [int stage = 0]) =>
|
||||||
bindings.remove(_indexPointer, path, stage);
|
bindings.remove(_indexPointer, path, stage);
|
||||||
|
|
||||||
/// Remove all matching index entries.
|
/// Removes all matching index entries.
|
||||||
///
|
///
|
||||||
/// Throws a [LibGit2Error] if error occured.
|
/// Throws a [LibGit2Error] if error occured.
|
||||||
void removeAll(List<String> path) => bindings.removeAll(_indexPointer, path);
|
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.
|
/// Releases memory allocated for index object.
|
||||||
void free() => bindings.free(_indexPointer);
|
void free() => bindings.free(_indexPointer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
import 'bindings/libgit2_bindings.dart';
|
import 'bindings/libgit2_bindings.dart';
|
||||||
import 'bindings/tree.dart' as bindings;
|
import 'bindings/tree.dart' as bindings;
|
||||||
|
import 'bindings/diff.dart' as diff_bindings;
|
||||||
|
import 'diff.dart';
|
||||||
|
import 'index.dart';
|
||||||
import 'repository.dart';
|
import 'repository.dart';
|
||||||
import 'oid.dart';
|
import 'oid.dart';
|
||||||
import 'git_types.dart';
|
import 'git_types.dart';
|
||||||
|
@ -67,6 +70,73 @@ class Tree {
|
||||||
/// Get the number of entries listed in a tree.
|
/// Get the number of entries listed in a tree.
|
||||||
int get length => bindings.entryCount(_treePointer);
|
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.
|
/// Releases memory allocated for tree object.
|
||||||
void free() => bindings.free(_treePointer);
|
void free() => bindings.free(_treePointer);
|
||||||
}
|
}
|
||||||
|
|
1
test/assets/dirtyrepo/.gitdir/COMMIT_EDITMSG
Normal file
1
test/assets/dirtyrepo/.gitdir/COMMIT_EDITMSG
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Initial commit of test files
|
1
test/assets/dirtyrepo/.gitdir/HEAD
Normal file
1
test/assets/dirtyrepo/.gitdir/HEAD
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ref: refs/heads/master
|
0
test/assets/dirtyrepo/.gitdir/branches/empty_marker
Normal file
0
test/assets/dirtyrepo/.gitdir/branches/empty_marker
Normal file
5
test/assets/dirtyrepo/.gitdir/config
Normal file
5
test/assets/dirtyrepo/.gitdir/config
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
[core]
|
||||||
|
repositoryformatversion = 0
|
||||||
|
filemode = true
|
||||||
|
bare = false
|
||||||
|
logallrefupdates = true
|
1
test/assets/dirtyrepo/.gitdir/description
Normal file
1
test/assets/dirtyrepo/.gitdir/description
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Unnamed repository; edit this file 'description' to name the repository.
|
15
test/assets/dirtyrepo/.gitdir/hooks/applypatch-msg.sample
Executable file
15
test/assets/dirtyrepo/.gitdir/hooks/applypatch-msg.sample
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to check the commit log message taken by
|
||||||
|
# applypatch from an e-mail message.
|
||||||
|
#
|
||||||
|
# The hook should exit with non-zero status after issuing an
|
||||||
|
# appropriate message if it wants to stop the commit. The hook is
|
||||||
|
# allowed to edit the commit message file.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "applypatch-msg".
|
||||||
|
|
||||||
|
. git-sh-setup
|
||||||
|
test -x "$GIT_DIR/hooks/commit-msg" &&
|
||||||
|
exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
|
||||||
|
:
|
24
test/assets/dirtyrepo/.gitdir/hooks/commit-msg.sample
Executable file
24
test/assets/dirtyrepo/.gitdir/hooks/commit-msg.sample
Executable file
|
@ -0,0 +1,24 @@
|
||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to check the commit log message.
|
||||||
|
# Called by "git commit" with one argument, the name of the file
|
||||||
|
# that has the commit message. The hook should exit with non-zero
|
||||||
|
# status after issuing an appropriate message if it wants to stop the
|
||||||
|
# commit. The hook is allowed to edit the commit message file.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "commit-msg".
|
||||||
|
|
||||||
|
# Uncomment the below to add a Signed-off-by line to the message.
|
||||||
|
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
|
||||||
|
# hook is more suited to it.
|
||||||
|
#
|
||||||
|
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
|
||||||
|
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
|
||||||
|
|
||||||
|
# This example catches duplicate Signed-off-by lines.
|
||||||
|
|
||||||
|
test "" = "$(grep '^Signed-off-by: ' "$1" |
|
||||||
|
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
|
||||||
|
echo >&2 Duplicate Signed-off-by lines.
|
||||||
|
exit 1
|
||||||
|
}
|
8
test/assets/dirtyrepo/.gitdir/hooks/post-commit.sample
Executable file
8
test/assets/dirtyrepo/.gitdir/hooks/post-commit.sample
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script that is called after a successful
|
||||||
|
# commit is made.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "post-commit".
|
||||||
|
|
||||||
|
: Nothing
|
15
test/assets/dirtyrepo/.gitdir/hooks/post-receive.sample
Executable file
15
test/assets/dirtyrepo/.gitdir/hooks/post-receive.sample
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script for the "post-receive" event.
|
||||||
|
#
|
||||||
|
# The "post-receive" script is run after receive-pack has accepted a pack
|
||||||
|
# and the repository has been updated. It is passed arguments in through
|
||||||
|
# stdin in the form
|
||||||
|
# <oldrev> <newrev> <refname>
|
||||||
|
# For example:
|
||||||
|
# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
|
||||||
|
#
|
||||||
|
# see contrib/hooks/ for a sample, or uncomment the next line and
|
||||||
|
# rename the file to "post-receive".
|
||||||
|
|
||||||
|
#. /usr/share/doc/git-core/contrib/hooks/post-receive-email
|
8
test/assets/dirtyrepo/.gitdir/hooks/post-update.sample
Executable file
8
test/assets/dirtyrepo/.gitdir/hooks/post-update.sample
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to prepare a packed repository for use over
|
||||||
|
# dumb transports.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "post-update".
|
||||||
|
|
||||||
|
exec git update-server-info
|
14
test/assets/dirtyrepo/.gitdir/hooks/pre-applypatch.sample
Executable file
14
test/assets/dirtyrepo/.gitdir/hooks/pre-applypatch.sample
Executable file
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to verify what is about to be committed
|
||||||
|
# by applypatch from an e-mail message.
|
||||||
|
#
|
||||||
|
# The hook should exit with non-zero status after issuing an
|
||||||
|
# appropriate message if it wants to stop the commit.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "pre-applypatch".
|
||||||
|
|
||||||
|
. git-sh-setup
|
||||||
|
test -x "$GIT_DIR/hooks/pre-commit" &&
|
||||||
|
exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
|
||||||
|
:
|
46
test/assets/dirtyrepo/.gitdir/hooks/pre-commit.sample
Executable file
46
test/assets/dirtyrepo/.gitdir/hooks/pre-commit.sample
Executable file
|
@ -0,0 +1,46 @@
|
||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to verify what is about to be committed.
|
||||||
|
# Called by "git commit" with no arguments. The hook should
|
||||||
|
# exit with non-zero status after issuing an appropriate message if
|
||||||
|
# it wants to stop the commit.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "pre-commit".
|
||||||
|
|
||||||
|
if git rev-parse --verify HEAD >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
against=HEAD
|
||||||
|
else
|
||||||
|
# Initial commit: diff against an empty tree object
|
||||||
|
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If you want to allow non-ascii filenames set this variable to true.
|
||||||
|
allownonascii=$(git config hooks.allownonascii)
|
||||||
|
|
||||||
|
# Cross platform projects tend to avoid non-ascii filenames; prevent
|
||||||
|
# them from being added to the repository. We exploit the fact that the
|
||||||
|
# printable range starts at the space character and ends with tilde.
|
||||||
|
if [ "$allownonascii" != "true" ] &&
|
||||||
|
# Note that the use of brackets around a tr range is ok here, (it's
|
||||||
|
# even required, for portability to Solaris 10's /usr/bin/tr), since
|
||||||
|
# the square bracket bytes happen to fall in the designated range.
|
||||||
|
test "$(git diff --cached --name-only --diff-filter=A -z $against |
|
||||||
|
LC_ALL=C tr -d '[ -~]\0')"
|
||||||
|
then
|
||||||
|
echo "Error: Attempt to add a non-ascii file name."
|
||||||
|
echo
|
||||||
|
echo "This can cause problems if you want to work"
|
||||||
|
echo "with people on other platforms."
|
||||||
|
echo
|
||||||
|
echo "To be portable it is advisable to rename the file ..."
|
||||||
|
echo
|
||||||
|
echo "If you know what you are doing you can disable this"
|
||||||
|
echo "check using:"
|
||||||
|
echo
|
||||||
|
echo " git config hooks.allownonascii true"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec git diff-index --check --cached $against --
|
172
test/assets/dirtyrepo/.gitdir/hooks/pre-rebase.sample
Executable file
172
test/assets/dirtyrepo/.gitdir/hooks/pre-rebase.sample
Executable file
|
@ -0,0 +1,172 @@
|
||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Copyright (c) 2006, 2008 Junio C Hamano
|
||||||
|
#
|
||||||
|
# The "pre-rebase" hook is run just before "git rebase" starts doing
|
||||||
|
# its job, and can prevent the command from running by exiting with
|
||||||
|
# non-zero status.
|
||||||
|
#
|
||||||
|
# The hook is called with the following parameters:
|
||||||
|
#
|
||||||
|
# $1 -- the upstream the series was forked from.
|
||||||
|
# $2 -- the branch being rebased (or empty when rebasing the current branch).
|
||||||
|
#
|
||||||
|
# This sample shows how to prevent topic branches that are already
|
||||||
|
# merged to 'next' branch from getting rebased, because allowing it
|
||||||
|
# would result in rebasing already published history.
|
||||||
|
|
||||||
|
publish=next
|
||||||
|
basebranch="$1"
|
||||||
|
if test "$#" = 2
|
||||||
|
then
|
||||||
|
topic="refs/heads/$2"
|
||||||
|
else
|
||||||
|
topic=`git symbolic-ref HEAD` ||
|
||||||
|
exit 0 ;# we do not interrupt rebasing detached HEAD
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$topic" in
|
||||||
|
refs/heads/??/*)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
exit 0 ;# we do not interrupt others.
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Now we are dealing with a topic branch being rebased
|
||||||
|
# on top of master. Is it OK to rebase it?
|
||||||
|
|
||||||
|
# Does the topic really exist?
|
||||||
|
git show-ref -q "$topic" || {
|
||||||
|
echo >&2 "No such branch $topic"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Is topic fully merged to master?
|
||||||
|
not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
|
||||||
|
if test -z "$not_in_master"
|
||||||
|
then
|
||||||
|
echo >&2 "$topic is fully merged to master; better remove it."
|
||||||
|
exit 1 ;# we could allow it, but there is no point.
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Is topic ever merged to next? If so you should not be rebasing it.
|
||||||
|
only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
|
||||||
|
only_next_2=`git rev-list ^master ${publish} | sort`
|
||||||
|
if test "$only_next_1" = "$only_next_2"
|
||||||
|
then
|
||||||
|
not_in_topic=`git rev-list "^$topic" master`
|
||||||
|
if test -z "$not_in_topic"
|
||||||
|
then
|
||||||
|
echo >&2 "$topic is already up-to-date with master"
|
||||||
|
exit 1 ;# we could allow it, but there is no point.
|
||||||
|
else
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
|
||||||
|
/usr/bin/perl -e '
|
||||||
|
my $topic = $ARGV[0];
|
||||||
|
my $msg = "* $topic has commits already merged to public branch:\n";
|
||||||
|
my (%not_in_next) = map {
|
||||||
|
/^([0-9a-f]+) /;
|
||||||
|
($1 => 1);
|
||||||
|
} split(/\n/, $ARGV[1]);
|
||||||
|
for my $elem (map {
|
||||||
|
/^([0-9a-f]+) (.*)$/;
|
||||||
|
[$1 => $2];
|
||||||
|
} split(/\n/, $ARGV[2])) {
|
||||||
|
if (!exists $not_in_next{$elem->[0]}) {
|
||||||
|
if ($msg) {
|
||||||
|
print STDERR $msg;
|
||||||
|
undef $msg;
|
||||||
|
}
|
||||||
|
print STDERR " $elem->[1]\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "$topic" "$not_in_next" "$not_in_master"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|
||||||
|
<<\DOC_END
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
This sample hook safeguards topic branches that have been
|
||||||
|
published from being rewound.
|
||||||
|
|
||||||
|
The workflow assumed here is:
|
||||||
|
|
||||||
|
* Once a topic branch forks from "master", "master" is never
|
||||||
|
merged into it again (either directly or indirectly).
|
||||||
|
|
||||||
|
* Once a topic branch is fully cooked and merged into "master",
|
||||||
|
it is deleted. If you need to build on top of it to correct
|
||||||
|
earlier mistakes, a new topic branch is created by forking at
|
||||||
|
the tip of the "master". This is not strictly necessary, but
|
||||||
|
it makes it easier to keep your history simple.
|
||||||
|
|
||||||
|
* Whenever you need to test or publish your changes to topic
|
||||||
|
branches, merge them into "next" branch.
|
||||||
|
|
||||||
|
The script, being an example, hardcodes the publish branch name
|
||||||
|
to be "next", but it is trivial to make it configurable via
|
||||||
|
$GIT_DIR/config mechanism.
|
||||||
|
|
||||||
|
With this workflow, you would want to know:
|
||||||
|
|
||||||
|
(1) ... if a topic branch has ever been merged to "next". Young
|
||||||
|
topic branches can have stupid mistakes you would rather
|
||||||
|
clean up before publishing, and things that have not been
|
||||||
|
merged into other branches can be easily rebased without
|
||||||
|
affecting other people. But once it is published, you would
|
||||||
|
not want to rewind it.
|
||||||
|
|
||||||
|
(2) ... if a topic branch has been fully merged to "master".
|
||||||
|
Then you can delete it. More importantly, you should not
|
||||||
|
build on top of it -- other people may already want to
|
||||||
|
change things related to the topic as patches against your
|
||||||
|
"master", so if you need further changes, it is better to
|
||||||
|
fork the topic (perhaps with the same name) afresh from the
|
||||||
|
tip of "master".
|
||||||
|
|
||||||
|
Let's look at this example:
|
||||||
|
|
||||||
|
o---o---o---o---o---o---o---o---o---o "next"
|
||||||
|
/ / / /
|
||||||
|
/ a---a---b A / /
|
||||||
|
/ / / /
|
||||||
|
/ / c---c---c---c B /
|
||||||
|
/ / / \ /
|
||||||
|
/ / / b---b C \ /
|
||||||
|
/ / / / \ /
|
||||||
|
---o---o---o---o---o---o---o---o---o---o---o "master"
|
||||||
|
|
||||||
|
|
||||||
|
A, B and C are topic branches.
|
||||||
|
|
||||||
|
* A has one fix since it was merged up to "next".
|
||||||
|
|
||||||
|
* B has finished. It has been fully merged up to "master" and "next",
|
||||||
|
and is ready to be deleted.
|
||||||
|
|
||||||
|
* C has not merged to "next" at all.
|
||||||
|
|
||||||
|
We would want to allow C to be rebased, refuse A, and encourage
|
||||||
|
B to be deleted.
|
||||||
|
|
||||||
|
To compute (1):
|
||||||
|
|
||||||
|
git rev-list ^master ^topic next
|
||||||
|
git rev-list ^master next
|
||||||
|
|
||||||
|
if these match, topic has not merged in next at all.
|
||||||
|
|
||||||
|
To compute (2):
|
||||||
|
|
||||||
|
git rev-list master..topic
|
||||||
|
|
||||||
|
if this is empty, it is fully merged to "master".
|
||||||
|
|
||||||
|
DOC_END
|
36
test/assets/dirtyrepo/.gitdir/hooks/prepare-commit-msg.sample
Executable file
36
test/assets/dirtyrepo/.gitdir/hooks/prepare-commit-msg.sample
Executable file
|
@ -0,0 +1,36 @@
|
||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to prepare the commit log message.
|
||||||
|
# Called by "git commit" with the name of the file that has the
|
||||||
|
# commit message, followed by the description of the commit
|
||||||
|
# message's source. The hook's purpose is to edit the commit
|
||||||
|
# message file. If the hook fails with a non-zero status,
|
||||||
|
# the commit is aborted.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "prepare-commit-msg".
|
||||||
|
|
||||||
|
# This hook includes three examples. The first comments out the
|
||||||
|
# "Conflicts:" part of a merge commit.
|
||||||
|
#
|
||||||
|
# The second includes the output of "git diff --name-status -r"
|
||||||
|
# into the message, just before the "git status" output. It is
|
||||||
|
# commented because it doesn't cope with --amend or with squashed
|
||||||
|
# commits.
|
||||||
|
#
|
||||||
|
# The third example adds a Signed-off-by line to the message, that can
|
||||||
|
# still be edited. This is rarely a good idea.
|
||||||
|
|
||||||
|
case "$2,$3" in
|
||||||
|
merge,)
|
||||||
|
/usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;;
|
||||||
|
|
||||||
|
# ,|template,)
|
||||||
|
# /usr/bin/perl -i.bak -pe '
|
||||||
|
# print "\n" . `git diff --cached --name-status -r`
|
||||||
|
# if /^#/ && $first++ == 0' "$1" ;;
|
||||||
|
|
||||||
|
*) ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
|
||||||
|
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
|
128
test/assets/dirtyrepo/.gitdir/hooks/update.sample
Executable file
128
test/assets/dirtyrepo/.gitdir/hooks/update.sample
Executable file
|
@ -0,0 +1,128 @@
|
||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to blocks unannotated tags from entering.
|
||||||
|
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "update".
|
||||||
|
#
|
||||||
|
# Config
|
||||||
|
# ------
|
||||||
|
# hooks.allowunannotated
|
||||||
|
# This boolean sets whether unannotated tags will be allowed into the
|
||||||
|
# repository. By default they won't be.
|
||||||
|
# hooks.allowdeletetag
|
||||||
|
# This boolean sets whether deleting tags will be allowed in the
|
||||||
|
# repository. By default they won't be.
|
||||||
|
# hooks.allowmodifytag
|
||||||
|
# This boolean sets whether a tag may be modified after creation. By default
|
||||||
|
# it won't be.
|
||||||
|
# hooks.allowdeletebranch
|
||||||
|
# This boolean sets whether deleting branches will be allowed in the
|
||||||
|
# repository. By default they won't be.
|
||||||
|
# hooks.denycreatebranch
|
||||||
|
# This boolean sets whether remotely creating branches will be denied
|
||||||
|
# in the repository. By default this is allowed.
|
||||||
|
#
|
||||||
|
|
||||||
|
# --- Command line
|
||||||
|
refname="$1"
|
||||||
|
oldrev="$2"
|
||||||
|
newrev="$3"
|
||||||
|
|
||||||
|
# --- Safety check
|
||||||
|
if [ -z "$GIT_DIR" ]; then
|
||||||
|
echo "Don't run this script from the command line." >&2
|
||||||
|
echo " (if you want, you could supply GIT_DIR then run" >&2
|
||||||
|
echo " $0 <ref> <oldrev> <newrev>)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
|
||||||
|
echo "Usage: $0 <ref> <oldrev> <newrev>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Config
|
||||||
|
allowunannotated=$(git config --bool hooks.allowunannotated)
|
||||||
|
allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
|
||||||
|
denycreatebranch=$(git config --bool hooks.denycreatebranch)
|
||||||
|
allowdeletetag=$(git config --bool hooks.allowdeletetag)
|
||||||
|
allowmodifytag=$(git config --bool hooks.allowmodifytag)
|
||||||
|
|
||||||
|
# check for no description
|
||||||
|
projectdesc=$(sed -e '1q' "$GIT_DIR/description")
|
||||||
|
case "$projectdesc" in
|
||||||
|
"Unnamed repository"* | "")
|
||||||
|
echo "*** Project description file hasn't been set" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# --- Check types
|
||||||
|
# if $newrev is 0000...0000, it's a commit to delete a ref.
|
||||||
|
zero="0000000000000000000000000000000000000000"
|
||||||
|
if [ "$newrev" = "$zero" ]; then
|
||||||
|
newrev_type=delete
|
||||||
|
else
|
||||||
|
newrev_type=$(git cat-file -t $newrev)
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$refname","$newrev_type" in
|
||||||
|
refs/tags/*,commit)
|
||||||
|
# un-annotated tag
|
||||||
|
short_refname=${refname##refs/tags/}
|
||||||
|
if [ "$allowunannotated" != "true" ]; then
|
||||||
|
echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
|
||||||
|
echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
refs/tags/*,delete)
|
||||||
|
# delete tag
|
||||||
|
if [ "$allowdeletetag" != "true" ]; then
|
||||||
|
echo "*** Deleting a tag is not allowed in this repository" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
refs/tags/*,tag)
|
||||||
|
# annotated tag
|
||||||
|
if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
|
||||||
|
then
|
||||||
|
echo "*** Tag '$refname' already exists." >&2
|
||||||
|
echo "*** Modifying a tag is not allowed in this repository." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
refs/heads/*,commit)
|
||||||
|
# branch
|
||||||
|
if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
|
||||||
|
echo "*** Creating a branch is not allowed in this repository" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
refs/heads/*,delete)
|
||||||
|
# delete branch
|
||||||
|
if [ "$allowdeletebranch" != "true" ]; then
|
||||||
|
echo "*** Deleting a branch is not allowed in this repository" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
refs/remotes/*,commit)
|
||||||
|
# tracking branch
|
||||||
|
;;
|
||||||
|
refs/remotes/*,delete)
|
||||||
|
# delete tracking branch
|
||||||
|
if [ "$allowdeletebranch" != "true" ]; then
|
||||||
|
echo "*** Deleting a tracking branch is not allowed in this repository" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Anything else (is there anything else?)
|
||||||
|
echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# --- Finished
|
||||||
|
exit 0
|
BIN
test/assets/dirtyrepo/.gitdir/index
Normal file
BIN
test/assets/dirtyrepo/.gitdir/index
Normal file
Binary file not shown.
6
test/assets/dirtyrepo/.gitdir/info/exclude
Normal file
6
test/assets/dirtyrepo/.gitdir/info/exclude
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# git ls-files --others --exclude-from=.git/info/exclude
|
||||||
|
# Lines that start with '#' are comments.
|
||||||
|
# For a project mostly in C, the following would be a good set of
|
||||||
|
# exclude patterns (uncomment them if you want to use them):
|
||||||
|
# *.[oa]
|
||||||
|
# *~
|
1
test/assets/dirtyrepo/.gitdir/logs/HEAD
Normal file
1
test/assets/dirtyrepo/.gitdir/logs/HEAD
Normal file
|
@ -0,0 +1 @@
|
||||||
|
0000000000000000000000000000000000000000 a763aa560953e7cfb87ccbc2f536d665aa4dff22 Julien Miotte <mike.perdide@gmail.com> 1311240164 +0200 commit (initial): Initial commit of test files
|
1
test/assets/dirtyrepo/.gitdir/logs/refs/heads/master
Normal file
1
test/assets/dirtyrepo/.gitdir/logs/refs/heads/master
Normal file
|
@ -0,0 +1 @@
|
||||||
|
0000000000000000000000000000000000000000 a763aa560953e7cfb87ccbc2f536d665aa4dff22 Julien Miotte <mike.perdide@gmail.com> 1311240164 +0200 commit (initial): Initial commit of test files
|
|
@ -0,0 +1,2 @@
|
||||||
|
x<01>ÍK
|
||||||
|
Â0€a×9Åì…’GóЏUð1™è`ÒH;½¿oàö_|ꃖöÀ" ñ:Ä”0›¥3Ñ¡
F&«<>=_òÃGlqãW_à¶UÂîÔ™¦Fo>¸dÊxy¶HuH½<48>A¥ô(•á(µ”b¯û—ñA\gbŠ~ôŒ+C¡Š«ørÍC¨
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
0
test/assets/dirtyrepo/.gitdir/objects/info/empty_marker
Normal file
0
test/assets/dirtyrepo/.gitdir/objects/info/empty_marker
Normal file
0
test/assets/dirtyrepo/.gitdir/objects/pack/empty_marker
Normal file
0
test/assets/dirtyrepo/.gitdir/objects/pack/empty_marker
Normal file
1
test/assets/dirtyrepo/.gitdir/refs/heads/master
Normal file
1
test/assets/dirtyrepo/.gitdir/refs/heads/master
Normal file
|
@ -0,0 +1 @@
|
||||||
|
a763aa560953e7cfb87ccbc2f536d665aa4dff22
|
0
test/assets/dirtyrepo/.gitdir/refs/tags/empty_marker
Normal file
0
test/assets/dirtyrepo/.gitdir/refs/tags/empty_marker
Normal file
0
test/assets/dirtyrepo/current_file
Normal file
0
test/assets/dirtyrepo/current_file
Normal file
1
test/assets/dirtyrepo/modified_file
Normal file
1
test/assets/dirtyrepo/modified_file
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Modified content
|
0
test/assets/dirtyrepo/new_file
Normal file
0
test/assets/dirtyrepo/new_file
Normal file
1
test/assets/dirtyrepo/staged_changes
Normal file
1
test/assets/dirtyrepo/staged_changes
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Modified content
|
1
test/assets/dirtyrepo/staged_changes_file_modified
Normal file
1
test/assets/dirtyrepo/staged_changes_file_modified
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Other modified content
|
1
test/assets/dirtyrepo/staged_delete_file_modified
Normal file
1
test/assets/dirtyrepo/staged_delete_file_modified
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Modified content
|
0
test/assets/dirtyrepo/staged_new
Normal file
0
test/assets/dirtyrepo/staged_new
Normal file
1
test/assets/dirtyrepo/staged_new_file_modified
Normal file
1
test/assets/dirtyrepo/staged_new_file_modified
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Other modified content
|
0
test/assets/dirtyrepo/subdir/current_file
Normal file
0
test/assets/dirtyrepo/subdir/current_file
Normal file
1
test/assets/dirtyrepo/subdir/modified_file
Normal file
1
test/assets/dirtyrepo/subdir/modified_file
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Modified content
|
0
test/assets/dirtyrepo/subdir/new_file
Normal file
0
test/assets/dirtyrepo/subdir/new_file
Normal file
271
test/diff_test.dart
Normal file
271
test/diff_test.dart
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:libgit2dart/src/git_types.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:libgit2dart/libgit2dart.dart';
|
||||||
|
import 'helpers/util.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late Repository repo;
|
||||||
|
final tmpDir = '${Directory.systemTemp.path}/diff_testrepo/';
|
||||||
|
const indexToWorkdir = [
|
||||||
|
'file_deleted',
|
||||||
|
'modified_file',
|
||||||
|
'staged_changes_file_deleted',
|
||||||
|
'staged_changes_file_modified',
|
||||||
|
'staged_new_file_deleted',
|
||||||
|
'staged_new_file_modified',
|
||||||
|
'subdir/deleted_file',
|
||||||
|
'subdir/modified_file',
|
||||||
|
];
|
||||||
|
|
||||||
|
const indexToTree = [
|
||||||
|
'staged_changes',
|
||||||
|
'staged_changes_file_deleted',
|
||||||
|
'staged_changes_file_modified',
|
||||||
|
'staged_delete',
|
||||||
|
'staged_delete_file_modified',
|
||||||
|
'staged_new',
|
||||||
|
'staged_new_file_deleted',
|
||||||
|
'staged_new_file_modified',
|
||||||
|
];
|
||||||
|
|
||||||
|
const treeToWorkdir = [
|
||||||
|
'file_deleted',
|
||||||
|
'modified_file',
|
||||||
|
'staged_changes',
|
||||||
|
'staged_changes_file_deleted',
|
||||||
|
'staged_changes_file_modified',
|
||||||
|
'staged_delete',
|
||||||
|
'staged_delete_file_modified',
|
||||||
|
'subdir/deleted_file',
|
||||||
|
'subdir/modified_file',
|
||||||
|
];
|
||||||
|
|
||||||
|
const treeToTree = [
|
||||||
|
'deleted_file',
|
||||||
|
'file_deleted',
|
||||||
|
'staged_changes',
|
||||||
|
'staged_changes_file_deleted',
|
||||||
|
'staged_changes_file_modified',
|
||||||
|
'staged_delete',
|
||||||
|
'staged_delete_file_modified',
|
||||||
|
'subdir/current_file',
|
||||||
|
'subdir/deleted_file',
|
||||||
|
'subdir/modified_file',
|
||||||
|
];
|
||||||
|
|
||||||
|
const patch = """
|
||||||
|
diff --git a/subdir/modified_file b/subdir/modified_file
|
||||||
|
index e69de29..c217c63 100644
|
||||||
|
--- a/subdir/modified_file
|
||||||
|
+++ b/subdir/modified_file
|
||||||
|
@@ -0,0 +1 @@
|
||||||
|
+Modified content
|
||||||
|
""";
|
||||||
|
|
||||||
|
const statsPrint = """
|
||||||
|
file_deleted | 0
|
||||||
|
modified_file | 1 +
|
||||||
|
staged_changes_file_deleted | 1 -
|
||||||
|
staged_changes_file_modified | 2 +-
|
||||||
|
staged_new_file_deleted | 0
|
||||||
|
staged_new_file_modified | 1 +
|
||||||
|
subdir/deleted_file | 0
|
||||||
|
subdir/modified_file | 1 +
|
||||||
|
8 files changed, 4 insertions(+), 2 deletions(-)
|
||||||
|
""";
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
if (await Directory(tmpDir).exists()) {
|
||||||
|
await Directory(tmpDir).delete(recursive: true);
|
||||||
|
}
|
||||||
|
await copyRepo(
|
||||||
|
from: Directory('test/assets/dirtyrepo/'),
|
||||||
|
to: await Directory(tmpDir).create(),
|
||||||
|
);
|
||||||
|
repo = Repository.open(tmpDir);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
repo.free();
|
||||||
|
await Directory(tmpDir).delete(recursive: true);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Diff', () {
|
||||||
|
test('successfully returns diff between index and workdir', () {
|
||||||
|
final index = repo.index;
|
||||||
|
final diff = index.diffToWorkdir();
|
||||||
|
|
||||||
|
expect(diff.length, 8);
|
||||||
|
for (var i = 0; i < diff.deltas.length; i++) {
|
||||||
|
expect(diff.deltas[i].newFile.path, indexToWorkdir[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
diff.free();
|
||||||
|
index.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully returns diff between index and tree', () {
|
||||||
|
final index = repo.index;
|
||||||
|
final tree = (repo[repo.head.target.sha] as Commit).tree;
|
||||||
|
final diff = index.diffToTree(tree: tree);
|
||||||
|
|
||||||
|
expect(diff.length, 8);
|
||||||
|
for (var i = 0; i < diff.deltas.length; i++) {
|
||||||
|
expect(diff.deltas[i].newFile.path, indexToTree[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.free();
|
||||||
|
diff.free();
|
||||||
|
index.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully returns diff between tree and workdir', () {
|
||||||
|
final tree = (repo[repo.head.target.sha] as Commit).tree;
|
||||||
|
final diff = tree.diffToWorkdir();
|
||||||
|
|
||||||
|
expect(diff.length, 9);
|
||||||
|
for (var i = 0; i < diff.deltas.length; i++) {
|
||||||
|
expect(diff.deltas[i].newFile.path, treeToWorkdir[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.free();
|
||||||
|
diff.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully returns diff between tree and index', () {
|
||||||
|
final index = repo.index;
|
||||||
|
final tree = (repo[repo.head.target.sha] as Commit).tree;
|
||||||
|
final diff = tree.diffToIndex(index: index);
|
||||||
|
|
||||||
|
expect(diff.length, 8);
|
||||||
|
for (var i = 0; i < diff.deltas.length; i++) {
|
||||||
|
expect(diff.deltas[i].newFile.path, indexToTree[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.free();
|
||||||
|
diff.free();
|
||||||
|
index.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully returns diff between tree and tree', () {
|
||||||
|
final tree1 = (repo[repo.head.target.sha] as Commit).tree;
|
||||||
|
final tree2 = repo['b85d53c9236e89aff2b62558adaa885fd1d6ff1c'] as Tree;
|
||||||
|
final diff = tree1.diffToTree(tree: tree2);
|
||||||
|
|
||||||
|
expect(diff.length, 10);
|
||||||
|
for (var i = 0; i < diff.deltas.length; i++) {
|
||||||
|
expect(diff.deltas[i].newFile.path, treeToTree[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
tree1.free();
|
||||||
|
tree2.free();
|
||||||
|
diff.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully merges diffs', () {
|
||||||
|
final tree1 = (repo[repo.head.target.sha] as Commit).tree;
|
||||||
|
final tree2 = repo['b85d53c9236e89aff2b62558adaa885fd1d6ff1c'] as Tree;
|
||||||
|
final diff1 = tree1.diffToTree(tree: tree2);
|
||||||
|
final diff2 = tree1.diffToWorkdir();
|
||||||
|
|
||||||
|
expect(diff1.length, 10);
|
||||||
|
expect(diff2.length, 9);
|
||||||
|
|
||||||
|
diff1.merge(diff2);
|
||||||
|
expect(diff1.length, 11);
|
||||||
|
|
||||||
|
tree1.free();
|
||||||
|
tree2.free();
|
||||||
|
diff1.free();
|
||||||
|
diff2.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully parses provided diff', () {
|
||||||
|
final diff = Diff.parse(patch);
|
||||||
|
final stats = diff.stats;
|
||||||
|
|
||||||
|
expect(diff.length, 1);
|
||||||
|
expect(stats.filesChanged, 1);
|
||||||
|
expect(stats.insertions, 1);
|
||||||
|
|
||||||
|
stats.free();
|
||||||
|
diff.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully finds similar entries', () {
|
||||||
|
final index = repo.index;
|
||||||
|
final oldTree = (repo[repo.head.target.sha] as Commit).tree;
|
||||||
|
final newTree = repo[index.writeTree().sha] as Tree;
|
||||||
|
|
||||||
|
final diff = oldTree.diffToTree(tree: newTree);
|
||||||
|
expect(
|
||||||
|
diff.deltas.singleWhere((e) => e.newFile.path == 'staged_new').status,
|
||||||
|
GitDelta.added,
|
||||||
|
);
|
||||||
|
|
||||||
|
diff.findSimilar();
|
||||||
|
expect(
|
||||||
|
diff.deltas.singleWhere((e) => e.newFile.path == 'staged_new').status,
|
||||||
|
GitDelta.renamed,
|
||||||
|
);
|
||||||
|
|
||||||
|
diff.free();
|
||||||
|
index.free();
|
||||||
|
oldTree.free();
|
||||||
|
newTree.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns deltas and patches', () {
|
||||||
|
final index = repo.index;
|
||||||
|
final diff = index.diffToWorkdir();
|
||||||
|
|
||||||
|
expect(diff.deltas[0].numberOfFiles, 1);
|
||||||
|
expect(diff.deltas[0].status, GitDelta.deleted);
|
||||||
|
expect(diff.deltas[0].statusChar, 'D');
|
||||||
|
expect(diff.deltas[0].flags, isEmpty);
|
||||||
|
expect(diff.deltas[0].similarity, 0);
|
||||||
|
|
||||||
|
expect(diff.deltas[0].oldFile.path, indexToWorkdir[0]);
|
||||||
|
expect(
|
||||||
|
diff.deltas[0].oldFile.id.sha,
|
||||||
|
'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
diff.deltas[0].newFile.id.sha,
|
||||||
|
'0000000000000000000000000000000000000000',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(diff.deltas[2].oldFile.size, 17);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
diff.deltas[0].oldFile.flags,
|
||||||
|
{GitDiffFlag.validId, GitDiffFlag.exists},
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
diff.deltas[0].newFile.flags,
|
||||||
|
{GitDiffFlag.validId},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(diff.deltas[0].oldFile.mode, GitFilemode.blob);
|
||||||
|
|
||||||
|
diff.free();
|
||||||
|
index.free();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns stats', () {
|
||||||
|
final index = repo.index;
|
||||||
|
final diff = index.diffToWorkdir();
|
||||||
|
final stats = diff.stats;
|
||||||
|
|
||||||
|
expect(stats.insertions, 4);
|
||||||
|
expect(stats.deletions, 2);
|
||||||
|
expect(stats.filesChanged, 8);
|
||||||
|
expect(stats.print({GitDiffStats.full}, 80), statsPrint);
|
||||||
|
|
||||||
|
stats.free();
|
||||||
|
diff.free();
|
||||||
|
index.free();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -43,15 +43,27 @@ void main() {
|
||||||
repo.reset(sha, GitReset.soft);
|
repo.reset(sha, GitReset.soft);
|
||||||
contents = file.readAsStringSync();
|
contents = file.readAsStringSync();
|
||||||
expect(contents, 'Feature edit\n');
|
expect(contents, 'Feature edit\n');
|
||||||
|
|
||||||
|
final index = repo.index;
|
||||||
|
final diff = index.diffToWorkdir();
|
||||||
|
expect(diff.deltas, isEmpty);
|
||||||
|
|
||||||
|
index.free();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('successfully resets with soft', () {
|
test('successfully resets with mixed', () {
|
||||||
var contents = file.readAsStringSync();
|
var contents = file.readAsStringSync();
|
||||||
expect(contents, 'Feature edit\n');
|
expect(contents, 'Feature edit\n');
|
||||||
|
|
||||||
repo.reset(sha, GitReset.mixed);
|
repo.reset(sha, GitReset.mixed);
|
||||||
contents = file.readAsStringSync();
|
contents = file.readAsStringSync();
|
||||||
expect(contents, 'Feature edit\n');
|
expect(contents, 'Feature edit\n');
|
||||||
|
|
||||||
|
final index = repo.index;
|
||||||
|
final diff = index.diffToWorkdir();
|
||||||
|
expect(diff.deltas.length, 1);
|
||||||
|
|
||||||
|
index.free();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue