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);
|
||||
}
|
||||
|
|
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);
|
||||
contents = file.readAsStringSync();
|
||||
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();
|
||||
expect(contents, 'Feature edit\n');
|
||||
|
||||
repo.reset(sha, GitReset.mixed);
|
||||
contents = file.readAsStringSync();
|
||||
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