From 7618f944c04d5a93c28feda92722e5cbe66518b0 Mon Sep 17 00:00:00 2001 From: Aleksey Kulikov Date: Fri, 10 Sep 2021 20:22:02 +0300 Subject: [PATCH] refactor!: return sets of git type flags instead of integers --- lib/src/git_types.dart | 420 +++++++++++++++++++++++++++----------- lib/src/index.dart | 5 +- lib/src/repository.dart | 86 +++++--- lib/src/revparse.dart | 16 +- lib/src/revwalk.dart | 7 +- lib/src/tree.dart | 3 +- lib/src/util.dart | 20 -- test/checkout_test.dart | 14 +- test/merge_test.dart | 55 ++++- test/repository_test.dart | 14 +- test/revparse_test.dart | 6 +- test/revwalk_test.dart | 4 +- 12 files changed, 459 insertions(+), 191 deletions(-) diff --git a/lib/src/git_types.dart b/lib/src/git_types.dart index 028a66f..5b78de3 100644 --- a/lib/src/git_types.dart +++ b/lib/src/git_types.dart @@ -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 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 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 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 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 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 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 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); + /// from their common ancestor. The divergent commits must be merged. + 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 + /// 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 + /// 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 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 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 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 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 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 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 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'; } diff --git a/lib/src/index.dart b/lib/src/index.dart index f834f49..b67d092 100644 --- a/lib/src/index.dart +++ b/lib/src/index.dart @@ -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; diff --git a/lib/src/repository.dart b/lib/src/repository.dart index 5d72b87..38c8ae6 100644 --- a/lib/src/repository.dart +++ b/lib/src/repository.dart @@ -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 log(String sha, [List sorting = const [GitSort.none]]) { + List log(String sha, [Set 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 get status { - var result = {}; + Map> get status { + var result = >{}; var list = status_bindings.listNew(_repoPointer); var count = status_bindings.listEntryCount(list); @@ -471,7 +475,15 @@ class Repository { .cast() .toDartString(); } - result[path] = entry.ref.status; + var statuses = {}; + // 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 statusFile(String path) { + final statusInt = status_bindings.file(_repoPointer, path); + var statuses = {}; + + 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 mergeAnalysis(Oid theirHead, [String ourRef = 'HEAD']) { + List> 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 = >[]; + var analysisSet = {}; + 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 mergeFlags = const [GitMergeFlag.findRenames], - List fileFlags = const [GitMergeFileFlag.defaults], + Set mergeFlags = const {GitMergeFlag.findRenames}, + Set fileFlags = const {GitMergeFileFlag.defaults}, }) { var opts = {}; 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 strategy = const [ + Set strategy = const { GitCheckout.safe, GitCheckout.recreateMissing - ], + }, String? directory, List? 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); diff --git a/lib/src/revparse.dart b/lib/src/revparse.dart index c6c158e..3d4bffe 100644 --- a/lib/src/revparse.dart +++ b/lib/src/revparse.dart @@ -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 get flags { + final flagInt = _revSpecPointer.ref.flags; + var flags = {}; + for (var flag in GitRevParse.values) { + if (flagInt & flag.value == flag.value) { + flags.add(flag); + } } + return flags; } } diff --git a/lib/src/revwalk.dart b/lib/src/revwalk.dart index ecb060f..ed2bd17 100644 --- a/lib/src/revwalk.dart +++ b/lib/src/revwalk.dart @@ -36,8 +36,11 @@ class RevWalk { /// Changing the sorting mode resets the walker. /// /// Throws a [LibGit2Error] if error occured. - void sorting(List sorting) { - final sort = sorting.fold(0, (previousValue, e) => e.value); + void sorting(Set sorting) { + final int sort = sorting.fold( + 0, + (previousValue, e) => previousValue | e.value, + ); bindings.sorting(_revWalkPointer, sort); } diff --git a/lib/src/tree.dart b/lib/src/tree.dart index d678328..3eb4fd1 100644 --- a/lib/src/tree.dart +++ b/lib/src/tree.dart @@ -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 diff --git a/lib/src/util.dart b/lib/src/util.dart index 572190c..4218b3c 100644 --- a/lib/src/util.dart +++ b/lib/src/util.dart @@ -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; - } -} diff --git a/test/checkout_test.dart b/test/checkout_test.dart index 02b6d3f..2e1dd11 100644 --- a/test/checkout_test.dart +++ b/test/checkout_test.dart @@ -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} + }, + ); }); }); } diff --git a/test/merge_test.dart b/test/merge_test.dart index 611f9a5..202033a 100644 --- a/test/merge_test.dart +++ b/test/merge_test.dart @@ -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); diff --git a/test/repository_test.dart b/test/repository_test.dart index 8af3b6a..27d6b84 100644 --- a/test/repository_test.dart +++ b/test/repository_test.dart @@ -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(); }); diff --git a/test/revparse_test.dart b/test/revparse_test.dart index d2c386e..a3d49b8 100644 --- a/test/revparse_test.dart +++ b/test/revparse_test.dart @@ -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(), diff --git a/test/revwalk_test.dart b/test/revwalk_test.dart index b6833df..a4942f5 100644 --- a/test/revwalk_test.dart +++ b/test/revwalk_test.dart @@ -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]);