refactor!: return sets of git type flags instead of integers

This commit is contained in:
Aleksey Kulikov 2021-09-10 20:22:02 +03:00
parent 050c0eb57a
commit 7618f944c0
12 changed files with 459 additions and 191 deletions

View file

@ -1,121 +1,173 @@
/// Basic type of any Git reference.
class ReferenceType {
const ReferenceType._(this._value);
const ReferenceType._(this._value, this._name);
final int _value;
final String _name;
/// Invalid reference.
static const invalid = ReferenceType._(0);
static const invalid = ReferenceType._(0, 'invalid');
/// A reference that points at an object id.
static const direct = ReferenceType._(1);
static const direct = ReferenceType._(1, 'direct');
/// A reference that points at another reference.
static const symbolic = ReferenceType._(2);
static const symbolic = ReferenceType._(2, 'symbolic');
static const all = ReferenceType._(3);
static const all = ReferenceType._(3, 'all');
static const List<ReferenceType> values = [invalid, direct, symbolic, all];
int get value => _value;
@override
String toString() => 'ReferenceType.$_name';
}
/// Valid modes for index and tree entries.
class GitFilemode {
const GitFilemode._(this._value);
const GitFilemode._(this._value, this._name);
final int _value;
final String _name;
static const unreadable = GitFilemode._(0);
static const tree = GitFilemode._(16384);
static const blob = GitFilemode._(33188);
static const blobExecutable = GitFilemode._(33261);
static const link = GitFilemode._(40960);
static const commit = GitFilemode._(57344);
static const unreadable = GitFilemode._(0, 'unreadable');
static const tree = GitFilemode._(16384, 'tree');
static const blob = GitFilemode._(33188, 'blob');
static const blobExecutable = GitFilemode._(33261, 'blobExecutable');
static const link = GitFilemode._(40960, 'link');
static const commit = GitFilemode._(57344, 'commit');
static const List<GitFilemode> values = [
unreadable,
tree,
blob,
blobExecutable,
link,
commit,
];
int get value => _value;
@override
String toString() => 'GitFilemode.$_name';
}
/// Flags to specify the sorting which a revwalk should perform.
class GitSort {
const GitSort._(this._value);
const GitSort._(this._value, this._name);
final int _value;
final String _name;
/// Sort the output with the same default method from `git`: reverse
/// chronological order. This is the default sorting for new walkers.
static const none = GitSort._(0);
static const none = GitSort._(0, 'none');
/// Sort the repository contents in topological order (no parents before
/// all of its children are shown); this sorting mode can be combined
/// with time sorting to produce `git`'s `--date-order``.
static const topological = GitSort._(1);
static const topological = GitSort._(1, 'topological');
/// Sort the repository contents by commit time;
/// this sorting mode can be combined with topological sorting.
static const time = GitSort._(2);
static const time = GitSort._(2, 'time');
/// Iterate through the repository contents in reverse order; this sorting mode
/// can be combined with any of the above.
static const reverse = GitSort._(4);
static const reverse = GitSort._(4, 'reverse');
static const List<GitSort> values = [none, topological, time, reverse];
int get value => _value;
@override
String toString() => 'GitSort.$_name';
}
/// Basic type (loose or packed) of any Git object.
class GitObject {
const GitObject._(this._value);
const GitObject._(this._value, this._name);
final int _value;
final String _name;
/// Object can be any of the following.
static const any = GitObject._(-2);
static const any = GitObject._(-2, 'any');
/// Object is invalid.
static const invalid = GitObject._(-1);
static const invalid = GitObject._(-1, 'invalid');
/// A commit object.
static const commit = GitObject._(1);
static const commit = GitObject._(1, 'commit');
/// A tree (directory listing) object.
static const tree = GitObject._(2);
static const tree = GitObject._(2, 'tree');
/// A file revision object.
static const blob = GitObject._(3);
static const blob = GitObject._(3, 'blob');
/// An annotated tag object.
static const tag = GitObject._(4);
static const tag = GitObject._(4, 'tag');
/// A delta, base is given by an offset.
static const offsetDelta = GitObject._(6);
static const offsetDelta = GitObject._(6, 'offsetDelta');
/// A delta, base is given by object id.
static const refDelta = GitObject._(7);
static const refDelta = GitObject._(7, 'refDelta');
static const List<GitObject> values = [
any,
invalid,
commit,
tree,
blob,
tag,
offsetDelta,
refDelta,
];
int get value => _value;
@override
String toString() => 'GitObject.$_name';
}
/// Revparse flags, indicate the intended behavior of the spec.
class GitRevParse {
const GitRevParse._(this._value);
const GitRevParse._(this._value, this._name);
final int _value;
final String _name;
/// The spec targeted a single object.
static const single = GitRevParse._(1);
static const single = GitRevParse._(1, 'single');
/// The spec targeted a range of commits.
static const range = GitRevParse._(2);
static const range = GitRevParse._(2, 'range');
/// The spec used the '...' operator, which invokes special semantics.
static const mergeBase = GitRevParse._(4);
static const mergeBase = GitRevParse._(4, 'mergeBase');
static const List<GitRevParse> values = [single, range, mergeBase];
int get value => _value;
@override
String toString() => 'GitRevParse.$_name';
}
/// Basic type of any Git branch.
class GitBranch {
const GitBranch._(this._value);
const GitBranch._(this._value, this._name);
final int _value;
final String _name;
static const local = GitBranch._(1);
static const remote = GitBranch._(2);
static const all = GitBranch._(3);
static const local = GitBranch._(1, 'local');
static const remote = GitBranch._(2, 'remote');
static const all = GitBranch._(3, 'all');
static const List<GitBranch> values = [local, remote, all];
int get value => _value;
@override
String toString() => 'GitBranch.$_name';
}
/// Status flags for a single file.
@ -127,73 +179,115 @@ class GitBranch {
/// `GitStatus.wt` set of flags represent the status of the file in the
/// working directory relative to the index.
class GitStatus {
const GitStatus._(this._value);
const GitStatus._(this._value, this._name);
final int _value;
final String _name;
static const current = GitStatus._(0);
static const indexNew = GitStatus._(1);
static const indexModified = GitStatus._(2);
static const indexDeleted = GitStatus._(4);
static const indexRenamed = GitStatus._(8);
static const indexTypeChange = GitStatus._(16);
static const wtNew = GitStatus._(128);
static const wtModified = GitStatus._(256);
static const wtDeleted = GitStatus._(512);
static const wtTypeChange = GitStatus._(1024);
static const wtRenamed = GitStatus._(2048);
static const wtUnreadable = GitStatus._(4096);
static const ignored = GitStatus._(16384);
static const conflicted = GitStatus._(32768);
static const current = GitStatus._(0, 'current');
static const indexNew = GitStatus._(1, 'indexNew');
static const indexModified = GitStatus._(2, 'indexModified');
static const indexDeleted = GitStatus._(4, 'indexDeleted');
static const indexRenamed = GitStatus._(8, 'indexRenamed');
static const indexTypeChange = GitStatus._(16, 'indexTypeChange');
static const wtNew = GitStatus._(128, 'wtNew');
static const wtModified = GitStatus._(256, 'wtModified');
static const wtDeleted = GitStatus._(512, 'wtDeleted');
static const wtTypeChange = GitStatus._(1024, 'wtTypeChange');
static const wtRenamed = GitStatus._(2048, 'wtRenamed');
static const wtUnreadable = GitStatus._(4096, 'wtUnreadable');
static const ignored = GitStatus._(16384, 'ignored');
static const conflicted = GitStatus._(32768, 'conflicted');
static const List<GitStatus> values = [
current,
indexNew,
indexModified,
indexDeleted,
indexRenamed,
indexTypeChange,
wtNew,
wtModified,
wtDeleted,
wtTypeChange,
wtRenamed,
wtUnreadable,
ignored,
conflicted,
];
int get value => _value;
@override
String toString() => 'GitStatus.$_name';
}
/// The results of `mergeAnalysis` indicate the merge opportunities.
class GitMergeAnalysis {
const GitMergeAnalysis._(this._value);
const GitMergeAnalysis._(this._value, this._name);
final int _value;
final String _name;
/// No merge is possible (unused).
static const none = GitMergeAnalysis._(0);
static const none = GitMergeAnalysis._(0, 'none');
/// A "normal" merge; both HEAD and the given merge input have diverged
/// from their common ancestor. The divergent commits must be merged.
static const normal = GitMergeAnalysis._(1);
static const normal = GitMergeAnalysis._(1, 'normal');
/// All given merge inputs are reachable from HEAD, meaning the
/// repository is up-to-date and no merge needs to be performed.
static const upToDate = GitMergeAnalysis._(2);
static const upToDate = GitMergeAnalysis._(2, 'upToDate');
/// The given merge input is a fast-forward from HEAD and no merge
/// needs to be performed. Instead, the client can check out the
/// given merge input.
static const fastForward = GitMergeAnalysis._(4);
static const fastForward = GitMergeAnalysis._(4, 'fastForward');
/// The HEAD of the current repository is "unborn" and does not point to
/// a valid commit. No merge can be performed, but the caller may wish
/// to simply set HEAD to the target commit(s).
static const unborn = GitMergeAnalysis._(8);
static const unborn = GitMergeAnalysis._(8, 'unborn');
static const List<GitMergeAnalysis> values = [
normal,
upToDate,
fastForward,
unborn,
];
int get value => _value;
@override
String toString() => 'GitMergeAnalysis.$_name';
}
/// The user's stated preference for merges.
class GitMergePreference {
const GitMergePreference._(this._value);
const GitMergePreference._(this._value, this._name);
final int _value;
final String _name;
/// No configuration was found that suggests a preferred behavior for merge.
static const none = GitMergePreference._(0);
static const none = GitMergePreference._(0, 'none');
/// There is a `merge.ff=false` configuration setting, suggesting that
/// the user does not want to allow a fast-forward merge.
static const noFastForward = GitMergePreference._(1);
static const noFastForward = GitMergePreference._(1, 'noFastForward');
/// There is a `merge.ff=only` configuration setting, suggesting that
/// the user only wants fast-forward merges.
static const fastForwardOnly = GitMergePreference._(2);
static const fastForwardOnly = GitMergePreference._(2, 'fastForwardOnly');
static const List<GitMergePreference> values = [
none,
noFastForward,
fastForwardOnly,
];
int get value => _value;
@override
String toString() => 'GitMergePreference.$_name';
}
/// Repository state.
@ -201,113 +295,174 @@ class GitMergePreference {
/// These values represent possible states for the repository to be in,
/// based on the current operation which is ongoing.
class GitRepositoryState {
const GitRepositoryState._(this._value);
const GitRepositoryState._(this._value, this._name);
final int _value;
final String _name;
static const none = GitRepositoryState._(0);
static const merge = GitRepositoryState._(1);
static const revert = GitRepositoryState._(2);
static const reverSequence = GitRepositoryState._(3);
static const cherrypick = GitRepositoryState._(4);
static const cherrypickSequence = GitRepositoryState._(5);
static const bisect = GitRepositoryState._(6);
static const rebase = GitRepositoryState._(7);
static const rebaseInteractive = GitRepositoryState._(8);
static const rebaseMerge = GitRepositoryState._(9);
static const applyMailbox = GitRepositoryState._(10);
static const applyMailboxOrRebase = GitRepositoryState._(11);
static const none = GitRepositoryState._(0, 'none');
static const merge = GitRepositoryState._(1, 'merge');
static const revert = GitRepositoryState._(2, 'revert');
static const revertSequence = GitRepositoryState._(3, 'revertSequence');
static const cherrypick = GitRepositoryState._(4, 'cherrypick');
static const cherrypickSequence =
GitRepositoryState._(5, 'cherrypickSequence');
static const bisect = GitRepositoryState._(6, 'bisect');
static const rebase = GitRepositoryState._(7, 'rebase');
static const rebaseInteractive = GitRepositoryState._(8, 'rebaseInteractive');
static const rebaseMerge = GitRepositoryState._(9, 'rebaseMerge');
static const applyMailbox = GitRepositoryState._(10, 'applyMailbox');
static const applyMailboxOrRebase =
GitRepositoryState._(11, 'applyMailboxOrRebase');
static const List<GitRepositoryState> values = [
none,
merge,
revert,
revertSequence,
cherrypick,
cherrypickSequence,
bisect,
rebase,
rebaseInteractive,
rebaseMerge,
applyMailbox,
applyMailboxOrRebase,
];
int get value => _value;
@override
String toString() => 'GitRepositoryState.$_name';
}
/// Flags for merge options.
class GitMergeFlag {
const GitMergeFlag._(this._value);
const GitMergeFlag._(this._value, this._name);
final int _value;
final String _name;
/// Detect renames that occur between the common ancestor and the "ours"
/// side or the common ancestor and the "theirs" side. This will enable
/// the ability to merge between a modified and renamed file.
static const findRenames = GitMergeFlag._(1);
static const findRenames = GitMergeFlag._(1, 'findRenames');
/// If a conflict occurs, exit immediately instead of attempting to
/// continue resolving conflicts. The merge operation will fail with
/// and no index will be returned.
static const failOnConflict = GitMergeFlag._(2);
static const failOnConflict = GitMergeFlag._(2, 'failOnConflict');
/// Do not write the REUC extension on the generated index.
static const skipREUC = GitMergeFlag._(4);
static const skipREUC = GitMergeFlag._(4, 'skipREUC');
/// If the commits being merged have multiple merge bases, do not build
/// a recursive merge base (by merging the multiple merge bases),
/// instead simply use the first base.
static const noRecursive = GitMergeFlag._(8);
static const noRecursive = GitMergeFlag._(8, 'noRecursive');
static const List<GitMergeFlag> values = [
findRenames,
failOnConflict,
skipREUC,
noRecursive,
];
int get value => _value;
@override
String toString() => 'GitMergeFlag.$_name';
}
/// Merge file favor options to instruct the file-level merging functionality
/// on how to deal with conflicting regions of the files.
class GitMergeFileFavor {
const GitMergeFileFavor._(this._value);
const GitMergeFileFavor._(this._value, this._name);
final int _value;
final String _name;
/// When a region of a file is changed in both branches, a conflict
/// will be recorded in the index. This is the default.
static const normal = GitMergeFileFavor._(0);
static const normal = GitMergeFileFavor._(0, 'normal');
/// When a region of a file is changed in both branches, the file
/// created in the index will contain the "ours" side of any conflicting
/// region. The index will not record a conflict.
static const ours = GitMergeFileFavor._(1);
static const ours = GitMergeFileFavor._(1, 'ours');
/// When a region of a file is changed in both branches, the file
/// created in the index will contain the "theirs" side of any conflicting
/// region. The index will not record a conflict.
static const theirs = GitMergeFileFavor._(2);
static const theirs = GitMergeFileFavor._(2, 'theirs');
/// When a region of a file is changed in both branches, the file
/// created in the index will contain each unique line from each side,
/// which has the result of combining both files. The index will not
/// record a conflict.
static const union = GitMergeFileFavor._(3);
static const union = GitMergeFileFavor._(3, 'union');
static const List<GitMergeFileFavor> values = [
normal,
ours,
theirs,
union,
];
int get value => _value;
@override
String toString() => 'GitMergeFileFavor.$_name';
}
/// File merging flags.
class GitMergeFileFlag {
const GitMergeFileFlag._(this._value);
const GitMergeFileFlag._(this._value, this._name);
final int _value;
final String _name;
/// Defaults.
static const defaults = GitMergeFileFlag._(0);
static const defaults = GitMergeFileFlag._(0, 'defaults');
/// Create standard conflicted merge files.
static const styleMerge = GitMergeFileFlag._(1);
static const styleMerge = GitMergeFileFlag._(1, 'styleMerge');
/// Create diff3-style files.
static const styleDiff3 = GitMergeFileFlag._(2);
static const styleDiff3 = GitMergeFileFlag._(2, 'styleDiff3');
/// Condense non-alphanumeric regions for simplified diff file.
static const simplifyAlnum = GitMergeFileFlag._(4);
static const simplifyAlnum = GitMergeFileFlag._(4, 'simplifyAlnum');
/// Ignore all whitespace.
static const ignoreWhitespace = GitMergeFileFlag._(8);
static const ignoreWhitespace = GitMergeFileFlag._(8, 'ignoreWhitespace');
/// Ignore changes in amount of whitespace.
static const ignoreWhitespaceChange = GitMergeFileFlag._(16);
static const ignoreWhitespaceChange =
GitMergeFileFlag._(16, 'ignoreWhitespaceChange');
/// Ignore whitespace at end of line.
static const ignoreWhitespaceEOL = GitMergeFileFlag._(32);
static const ignoreWhitespaceEOL =
GitMergeFileFlag._(32, 'ignoreWhitespaceEOL');
/// Use the "patience diff" algorithm.
static const diffPatience = GitMergeFileFlag._(64);
static const diffPatience = GitMergeFileFlag._(64, 'diffPatience');
/// Take extra time to find minimal diff.
static const diffMinimal = GitMergeFileFlag._(128);
static const diffMinimal = GitMergeFileFlag._(128, 'diffMinimal');
static const List<GitMergeFileFlag> values = [
defaults,
styleMerge,
styleDiff3,
simplifyAlnum,
ignoreWhitespace,
ignoreWhitespaceChange,
ignoreWhitespaceEOL,
diffPatience,
diffMinimal,
];
int get value => _value;
@override
String toString() => 'GitMergeFileFlag.$_name';
}
/// Checkout behavior flags.
@ -316,11 +471,12 @@ class GitMergeFileFlag {
/// to match a target tree. Unlike git checkout, it does not move the HEAD
/// commit for you - use `setHead` or the like to do that.
class GitCheckout {
const GitCheckout._(this._value);
const GitCheckout._(this._value, this._name);
final int _value;
final String _name;
/// Default is a dry run, no actual updates.
static const none = GitCheckout._(0);
static const none = GitCheckout._(0, 'none');
/// Allow safe updates that cannot overwrite uncommitted data.
/// If the uncommitted changes don't conflict with the checked out files,
@ -328,65 +484,97 @@ class GitCheckout {
///
/// Mutually exclusive with [GitCheckout.force].
/// [GitCheckout.force] takes precedence over [GitCheckout.safe].
static const safe = GitCheckout._(1);
static const safe = GitCheckout._(1, 'safe');
/// Allow all updates to force working directory to look like index.
///
/// Mutually exclusive with [GitCheckout.safe].
/// [GitCheckout.force] takes precedence over [GitCheckout.safe].
static const force = GitCheckout._(2);
static const force = GitCheckout._(2, 'force');
/// Allow checkout to recreate missing files.
static const recreateMissing = GitCheckout._(4);
static const recreateMissing = GitCheckout._(4, 'recreateMissing');
/// Allow checkout to make safe updates even if conflicts are found.
static const allowConflicts = GitCheckout._(16);
static const allowConflicts = GitCheckout._(16, 'allowConflicts');
/// Remove untracked files not in index (that are not ignored).
static const removeUntracked = GitCheckout._(32);
static const removeUntracked = GitCheckout._(32, 'removeUntracked');
/// Remove ignored files not in index.
static const removeIgnored = GitCheckout._(64);
static const removeIgnored = GitCheckout._(64, 'removeIgnored');
/// Only update existing files, don't create new ones.
static const updateOnly = GitCheckout._(128);
static const updateOnly = GitCheckout._(128, 'updateOnly');
/// Normally checkout updates index entries as it goes; this stops that.
/// Implies [GitCheckout.dontWriteIndex].
static const dontUpdateIndex = GitCheckout._(256);
static const dontUpdateIndex = GitCheckout._(256, 'dontUpdateIndex');
/// Don't refresh index/config/etc before doing checkout.
static const noRefresh = GitCheckout._(512);
static const noRefresh = GitCheckout._(512, 'noRefresh');
/// Allow checkout to skip unmerged files.
static const skipUnmerged = GitCheckout._(1024);
static const skipUnmerged = GitCheckout._(1024, 'skipUnmerged');
/// For unmerged files, checkout stage 2 from index.
static const useOurs = GitCheckout._(2048);
static const useOurs = GitCheckout._(2048, 'useOurs');
/// For unmerged files, checkout stage 3 from index.
static const useTheirs = GitCheckout._(4096);
static const useTheirs = GitCheckout._(4096, 'useTheirs');
/// Treat pathspec as simple list of exact match file paths.
static const disablePathspecMatch = GitCheckout._(8192);
static const disablePathspecMatch =
GitCheckout._(8192, 'disablePathspecMatch');
/// Ignore directories in use, they will be left empty.
static const skipLockedDirectories = GitCheckout._(262144);
static const skipLockedDirectories =
GitCheckout._(262144, 'skipLockedDirectories');
/// Don't overwrite ignored files that exist in the checkout target.
static const dontOverwriteIgnored = GitCheckout._(524288);
static const dontOverwriteIgnored =
GitCheckout._(524288, 'dontOverwriteIgnored');
/// Write normal merge files for conflicts.
static const conflictStyleMerge = GitCheckout._(1048576);
static const conflictStyleMerge =
GitCheckout._(1048576, 'conflictStyleMerge');
/// Include common ancestor data in diff3 format files for conflicts.
static const conflictStyleDiff3 = GitCheckout._(2097152);
static const conflictStyleDiff3 =
GitCheckout._(2097152, 'conflictStyleDiff3');
/// Don't overwrite existing files or folders.
static const dontRemoveExisting = GitCheckout._(4194304);
static const dontRemoveExisting =
GitCheckout._(4194304, 'dontRemoveExisting');
/// Normally checkout writes the index upon completion; this prevents that.
static const dontWriteIndex = GitCheckout._(8388608);
static const dontWriteIndex = GitCheckout._(8388608, 'dontWriteIndex');
static const List<GitCheckout> values = [
none,
safe,
force,
recreateMissing,
allowConflicts,
removeUntracked,
removeIgnored,
updateOnly,
dontUpdateIndex,
noRefresh,
skipUnmerged,
useOurs,
useTheirs,
disablePathspecMatch,
skipLockedDirectories,
dontOverwriteIgnored,
conflictStyleMerge,
conflictStyleDiff3,
dontRemoveExisting,
dontWriteIndex,
];
int get value => _value;
@override
String toString() => 'GitCheckout.$_name';
}

View file

@ -208,7 +208,10 @@ class IndexEntry {
String get sha => _oidToHex(_indexEntryPointer.ref.id);
/// Returns the UNIX file attributes of a index entry.
GitFilemode get mode => intToGitFilemode(_indexEntryPointer.ref.mode);
GitFilemode get mode {
final modeInt = _indexEntryPointer.ref.mode;
return GitFilemode.values.singleWhere((mode) => modeInt == mode.value);
}
/// Sets the UNIX file attributes of a index entry.
set mode(GitFilemode mode) => _indexEntryPointer.ref.mode = mode.value;

View file

@ -194,7 +194,11 @@ class Repository {
/// Returns the status of a git repository - ie, whether an operation
/// (merge, cherry-pick, etc) is in progress.
int get state => bindings.state(_repoPointer);
GitRepositoryState get state {
final stateInt = bindings.state(_repoPointer);
return GitRepositoryState.values
.singleWhere((flag) => stateInt == flag.value);
}
/// Removes all the metadata associated with an ongoing command like
/// merge, revert, cherry-pick, etc. For example: MERGE_HEAD, MERGE_MSG, etc.
@ -357,7 +361,7 @@ class Repository {
/// Returns the list of commits starting from provided [sha] hex string.
///
/// If [sorting] isn't provided default will be used (reverse chronological order, like in git).
List<Commit> log(String sha, [List<GitSort> sorting = const [GitSort.none]]) {
List<Commit> log(String sha, [Set<GitSort> sorting = const {GitSort.none}]) {
final oid = Oid.fromSHA(this, sha);
final walker = RevWalk(this);
@ -454,8 +458,8 @@ class Repository {
/// Checks status of the repository and returns map of file paths and their statuses.
///
/// Returns empty map if there are no changes in statuses.
Map<String, int> get status {
var result = <String, int>{};
Map<String, Set<GitStatus>> get status {
var result = <String, Set<GitStatus>>{};
var list = status_bindings.listNew(_repoPointer);
var count = status_bindings.listEntryCount(list);
@ -471,7 +475,15 @@ class Repository {
.cast<Utf8>()
.toDartString();
}
result[path] = entry.ref.status;
var statuses = <GitStatus>{};
// Skipping GitStatus.current because entry that is in the list can't be without changes
// but `&` on `0` value falsly adds it to the set of flags
for (var flag in GitStatus.values.skip(1)) {
if (entry.ref.status & flag.value == flag.value) {
statuses.add(flag);
}
}
result[path] = statuses;
}
status_bindings.listFree(list);
@ -487,7 +499,23 @@ class Repository {
/// through looking for the path that you are interested in.
///
/// Throws a [LibGit2Error] if error occured.
int statusFile(String path) => status_bindings.file(_repoPointer, path);
Set<GitStatus> statusFile(String path) {
final statusInt = status_bindings.file(_repoPointer, path);
var statuses = <GitStatus>{};
if (statusInt == GitStatus.current.value) {
statuses.add(GitStatus.current);
} else {
// Skipping GitStatus.current because `&` on `0` value falsly adds it to the set of flags
for (var flag in GitStatus.values.skip(1)) {
if (statusInt & flag.value == flag.value) {
statuses.add(flag);
}
}
}
return statuses;
}
/// Finds a merge base between two commits.
///
@ -509,14 +537,30 @@ class Repository {
/// respectively.
///
/// Throws a [LibGit2Error] if error occured.
List<int> mergeAnalysis(Oid theirHead, [String ourRef = 'HEAD']) {
List<Set<dynamic>> mergeAnalysis(Oid theirHead, [String ourRef = 'HEAD']) {
final ref = references[ourRef];
final head = commit_bindings.annotatedLookup(
_repoPointer,
theirHead.pointer,
);
final result = merge_bindings.analysis(_repoPointer, ref.pointer, head, 1);
var result = <Set<dynamic>>[];
var analysisSet = <GitMergeAnalysis>{};
final analysisInt = merge_bindings.analysis(
_repoPointer,
ref.pointer,
head,
1,
);
for (var analysis in GitMergeAnalysis.values) {
if (analysisInt[0] & analysis.value == analysis.value) {
analysisSet.add(analysis);
}
}
result.add(analysisSet);
result.add(
{GitMergePreference.values.singleWhere((e) => analysisInt[1] == e.value)},
);
commit_bindings.annotatedFree(head.value);
ref.free();
@ -553,19 +597,15 @@ class Repository {
required Commit ourCommit,
required Commit theirCommit,
GitMergeFileFavor favor = GitMergeFileFavor.normal,
List<GitMergeFlag> mergeFlags = const [GitMergeFlag.findRenames],
List<GitMergeFileFlag> fileFlags = const [GitMergeFileFlag.defaults],
Set<GitMergeFlag> mergeFlags = const {GitMergeFlag.findRenames},
Set<GitMergeFileFlag> fileFlags = const {GitMergeFileFlag.defaults},
}) {
var opts = <String, int>{};
opts['favor'] = favor.value;
opts['mergeFlags'] = mergeFlags.fold(
0,
(previousValue, element) => previousValue + element.value,
);
opts['fileFlags'] = fileFlags.fold(
0,
(previousValue, element) => previousValue + element.value,
);
opts['mergeFlags'] =
mergeFlags.fold(0, (previousValue, e) => previousValue | e.value);
opts['fileFlags'] =
fileFlags.fold(0, (previousValue, e) => previousValue | e.value);
final result = merge_bindings.mergeCommits(
_repoPointer,
@ -638,17 +678,15 @@ class Repository {
/// HEAD will not be set to the reference [refName].
void checkout({
String refName = '',
List<GitCheckout> strategy = const [
Set<GitCheckout> strategy = const {
GitCheckout.safe,
GitCheckout.recreateMissing
],
},
String? directory,
List<String>? paths,
}) {
final int strat = strategy.fold(
0,
(previousValue, element) => previousValue + element.value,
);
final int strat =
strategy.fold(0, (previousValue, e) => previousValue | e.value);
if (refName.isEmpty) {
checkout_bindings.index(_repoPointer, strat, directory, paths);

View file

@ -78,14 +78,14 @@ class RevSpec {
}
/// The intent of the revspec.
GitRevParse get flags {
final flag = _revSpecPointer.ref.flags;
if (flag == 1) {
return GitRevParse.single;
} else if (flag == 2) {
return GitRevParse.range;
} else {
return GitRevParse.mergeBase;
Set<GitRevParse> get flags {
final flagInt = _revSpecPointer.ref.flags;
var flags = <GitRevParse>{};
for (var flag in GitRevParse.values) {
if (flagInt & flag.value == flag.value) {
flags.add(flag);
}
}
return flags;
}
}

View file

@ -36,8 +36,11 @@ class RevWalk {
/// Changing the sorting mode resets the walker.
///
/// Throws a [LibGit2Error] if error occured.
void sorting(List<GitSort> sorting) {
final sort = sorting.fold(0, (previousValue, e) => e.value);
void sorting(Set<GitSort> sorting) {
final int sort = sorting.fold(
0,
(previousValue, e) => previousValue | e.value,
);
bindings.sorting(_revWalkPointer, sort);
}

View file

@ -86,7 +86,8 @@ class TreeEntry {
/// Returns the UNIX file attributes of a tree entry.
GitFilemode get filemode {
return intToGitFilemode(bindings.entryFilemode(_treeEntryPointer));
final modeInt = bindings.entryFilemode(_treeEntryPointer);
return GitFilemode.values.singleWhere((mode) => modeInt == mode.value);
}
@override

View file

@ -1,7 +1,6 @@
import 'dart:io';
import 'dart:ffi';
import 'bindings/libgit2_bindings.dart';
import 'git_types.dart';
DynamicLibrary loadLibrary() {
if (Platform.isLinux || Platform.isAndroid || Platform.isFuchsia) {
@ -26,22 +25,3 @@ bool isValidShaHex(String str) {
return hexRegExp.hasMatch(str) &&
(GIT_OID_MINPREFIXLEN <= str.length && GIT_OID_HEXSZ >= str.length);
}
GitFilemode intToGitFilemode(int i) {
switch (i) {
case 0:
return GitFilemode.unreadable;
case 16384:
return GitFilemode.tree;
case 33188:
return GitFilemode.blob;
case 33261:
return GitFilemode.blobExecutable;
case 40960:
return GitFilemode.link;
case 57344:
return GitFilemode.commit;
default:
return GitFilemode.unreadable;
}
}

View file

@ -29,7 +29,7 @@ void main() {
File('${tmpDir}feature_file').writeAsStringSync('edit');
expect(repo.status, contains('feature_file'));
repo.checkout(refName: 'HEAD', strategy: [GitCheckout.force]);
repo.checkout(refName: 'HEAD', strategy: {GitCheckout.force});
expect(repo.status, isEmpty);
});
@ -37,7 +37,10 @@ void main() {
File('${repo.workdir}feature_file').writeAsStringSync('edit');
expect(repo.status, contains('feature_file'));
repo.checkout(strategy: [GitCheckout.force]);
repo.checkout(strategy: {
GitCheckout.force,
GitCheckout.conflictStyleMerge,
});
expect(repo.status, isEmpty);
});
@ -90,7 +93,12 @@ void main() {
refName: 'refs/heads/feature',
paths: ['another_feature_file'],
);
expect(repo.status, {'another_feature_file': GitStatus.indexNew.value});
expect(
repo.status,
{
'another_feature_file': {GitStatus.indexNew}
},
);
});
});
}

View file

@ -31,7 +31,8 @@ void main() {
repo['c68ff54aabf660fcdd9a2838d401583fe31249e3'] as Commit;
final result = repo.mergeAnalysis(commit.id);
expect(result[0], GitMergeAnalysis.upToDate.value);
expect(result[0], {GitMergeAnalysis.upToDate});
expect(result[1], {GitMergePreference.none});
expect(repo.status, isEmpty);
commit.free();
@ -42,7 +43,7 @@ void main() {
repo['c68ff54aabf660fcdd9a2838d401583fe31249e3'] as Commit;
final result = repo.mergeAnalysis(commit.id, 'refs/tags/v0.1');
expect(result[0], GitMergeAnalysis.upToDate.value);
expect(result[0], {GitMergeAnalysis.upToDate});
expect(repo.status, isEmpty);
commit.free();
@ -61,7 +62,7 @@ void main() {
final result = repo.mergeAnalysis(theirHead.id, ffBranch.name);
expect(
result[0],
GitMergeAnalysis.fastForward.value + GitMergeAnalysis.normal.value,
{GitMergeAnalysis.fastForward, GitMergeAnalysis.normal},
);
expect(repo.status, isEmpty);
@ -75,7 +76,7 @@ void main() {
repo['5aecfa0fb97eadaac050ccb99f03c3fb65460ad4'] as Commit;
final result = repo.mergeAnalysis(commit.id);
expect(result[0], GitMergeAnalysis.normal.value);
expect(result[0], {GitMergeAnalysis.normal});
expect(repo.status, isEmpty);
commit.free();
@ -87,13 +88,18 @@ void main() {
final index = repo.index;
final result = repo.mergeAnalysis(conflictBranch.target);
expect(result[0], GitMergeAnalysis.normal.value);
expect(result[0], {GitMergeAnalysis.normal});
repo.merge(conflictBranch.target);
expect(index.hasConflicts, true);
expect(index.conflicts.length, 1);
expect(repo.state, GitRepositoryState.merge.value);
expect(repo.status, {'conflict_file': GitStatus.conflicted.value});
expect(repo.state, GitRepositoryState.merge);
expect(
repo.status,
{
'conflict_file': {GitStatus.conflicted}
},
);
final conflictedFile = index.conflicts['conflict_file']!;
expect(conflictedFile.ancestor, null);
@ -104,7 +110,12 @@ void main() {
index.write();
expect(index.hasConflicts, false);
expect(index.conflicts, isEmpty);
expect(repo.status, {'conflict_file': GitStatus.indexModified.value});
expect(
repo.status,
{
'conflict_file': {GitStatus.indexModified}
},
);
index.free();
conflictBranch.free();
@ -172,6 +183,32 @@ void main() {
ourCommit.free();
theirCommit.free();
});
test('successfully merges with provided merge and file flags', () {
final theirCommit =
repo['5aecfa0fb97eadaac050ccb99f03c3fb65460ad4'] as Commit;
final ourCommit =
repo['14905459d775f3f56a39ebc2ff081163f7da3529'] as Commit;
final mergeIndex = repo.mergeCommits(
ourCommit: ourCommit,
theirCommit: theirCommit,
mergeFlags: {
GitMergeFlag.findRenames,
GitMergeFlag.noRecursive,
},
fileFlags: {
GitMergeFileFlag.ignoreWhitespace,
GitMergeFileFlag.ignoreWhitespaceEOL,
GitMergeFileFlag.styleMerge,
},
);
expect(mergeIndex.conflicts, isEmpty);
mergeIndex.free();
ourCommit.free();
theirCommit.free();
});
});
group('merge trees', () {
@ -246,7 +283,7 @@ void main() {
test('successfully cherry-picks commit', () {
final cherry = repo['5aecfa0fb97eadaac050ccb99f03c3fb65460ad4'] as Commit;
repo.cherryPick(cherry);
expect(repo.state, GitRepositoryState.cherrypick.value);
expect(repo.state, GitRepositoryState.cherrypick);
expect(repo.message, 'add another feature file\n');
final index = repo.index;
expect(index.conflicts, isEmpty);

View file

@ -343,7 +343,13 @@ void main() {
final index = repo.index;
index.remove('file');
index.add('new_file.txt');
expect(repo.status, {'file': 132, 'new_file.txt': 1});
expect(
repo.status,
{
'file': {GitStatus.indexDeleted, GitStatus.wtNew},
'new_file.txt': {GitStatus.indexNew}
},
);
index.free();
});
@ -351,7 +357,11 @@ void main() {
test('returns status of a single file for provided path', () {
final index = repo.index;
index.remove('file');
expect(repo.statusFile('file'), 132);
expect(
repo.statusFile('file'),
{GitStatus.indexDeleted, GitStatus.wtNew},
);
expect(repo.statusFile('.gitignore'), {GitStatus.current});
index.free();
});

View file

@ -81,7 +81,7 @@ void main() {
expect(revspec.from.id.sha, headSHA);
expect(revspec.to, isNull);
expect(revspec.flags, GitRevParse.single);
expect(revspec.flags, {GitRevParse.single});
revspec.from.free();
@ -89,7 +89,7 @@ void main() {
expect(revspec.from.id.sha, parentSHA);
expect(revspec.to?.id.sha, '5aecfa0fb97eadaac050ccb99f03c3fb65460ad4');
expect(revspec.flags, GitRevParse.range);
expect(revspec.flags, {GitRevParse.range});
revspec.from.free();
revspec.to?.free();
@ -98,7 +98,7 @@ void main() {
expect(revspec.from.id.sha, headSHA);
expect(revspec.to?.id.sha, '5aecfa0fb97eadaac050ccb99f03c3fb65460ad4');
expect(revspec.flags, GitRevParse.mergeBase);
expect(revspec.flags, {GitRevParse.range, GitRevParse.mergeBase});
expect(
repo.mergeBase(revspec.from.id.sha, revspec.to!.id.sha),
isA<Oid>(),

View file

@ -53,7 +53,7 @@ void main() {
final start = Oid.fromSHA(repo, log.first);
walker.push(start);
walker.sorting([GitSort.reverse]);
walker.sorting({GitSort.reverse});
final commits = walker.walk();
for (var i = 0; i < commits.length; i++) {
@ -77,7 +77,7 @@ void main() {
expect(timeSortedCommits[i].id.sha, log[i]);
}
walker.sorting([GitSort.time, GitSort.reverse]);
walker.sorting({GitSort.time, GitSort.reverse});
final reverseSortedCommits = walker.walk();
for (var i = 0; i < reverseSortedCommits.length; i++) {
expect(reverseSortedCommits[i].id.sha, log.reversed.toList()[i]);