mirror of
https://github.com/SkinnyMind/libgit2dart.git
synced 2025-05-05 04:39:07 -04:00
feat(stash): add bindings and api
This commit is contained in:
parent
7b8dfcc1af
commit
3cb55817ad
7 changed files with 386 additions and 13 deletions
|
@ -16,5 +16,6 @@ export 'src/branch.dart';
|
||||||
export 'src/worktree.dart';
|
export 'src/worktree.dart';
|
||||||
export 'src/diff.dart';
|
export 'src/diff.dart';
|
||||||
export 'src/patch.dart';
|
export 'src/patch.dart';
|
||||||
|
export 'src/stash.dart';
|
||||||
export 'src/error.dart';
|
export 'src/error.dart';
|
||||||
export 'src/git_types.dart';
|
export 'src/git_types.dart';
|
||||||
|
|
|
@ -19,10 +19,10 @@ void head(
|
||||||
String? directory,
|
String? directory,
|
||||||
List<String>? paths,
|
List<String>? paths,
|
||||||
) {
|
) {
|
||||||
final initOptions = _initOptions(strategy, directory, paths);
|
final initOpts = initOptions(strategy, directory, paths);
|
||||||
final optsC = initOptions[0];
|
final optsC = initOpts[0];
|
||||||
final pathPointers = initOptions[1];
|
final pathPointers = initOpts[1];
|
||||||
final strArray = initOptions[2];
|
final strArray = initOpts[2];
|
||||||
|
|
||||||
final error = libgit2.git_checkout_head(repo, optsC);
|
final error = libgit2.git_checkout_head(repo, optsC);
|
||||||
|
|
||||||
|
@ -47,10 +47,10 @@ void index(
|
||||||
String? directory,
|
String? directory,
|
||||||
List<String>? paths,
|
List<String>? paths,
|
||||||
) {
|
) {
|
||||||
final initOptions = _initOptions(strategy, directory, paths);
|
final initOpts = initOptions(strategy, directory, paths);
|
||||||
final optsC = initOptions[0];
|
final optsC = initOpts[0];
|
||||||
final pathPointers = initOptions[1];
|
final pathPointers = initOpts[1];
|
||||||
final strArray = initOptions[2];
|
final strArray = initOpts[2];
|
||||||
|
|
||||||
final error = libgit2.git_checkout_index(repo, nullptr, optsC);
|
final error = libgit2.git_checkout_index(repo, nullptr, optsC);
|
||||||
|
|
||||||
|
@ -77,10 +77,10 @@ void tree(
|
||||||
String? directory,
|
String? directory,
|
||||||
List<String>? paths,
|
List<String>? paths,
|
||||||
) {
|
) {
|
||||||
final initOptions = _initOptions(strategy, directory, paths);
|
final initOpts = initOptions(strategy, directory, paths);
|
||||||
final optsC = initOptions[0];
|
final optsC = initOpts[0];
|
||||||
final pathPointers = initOptions[1];
|
final pathPointers = initOpts[1];
|
||||||
final strArray = initOptions[2];
|
final strArray = initOpts[2];
|
||||||
|
|
||||||
final error = libgit2.git_checkout_tree(repo, treeish, optsC);
|
final error = libgit2.git_checkout_tree(repo, treeish, optsC);
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ void tree(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<dynamic> _initOptions(
|
List<dynamic> initOptions(
|
||||||
int strategy,
|
int strategy,
|
||||||
String? directory,
|
String? directory,
|
||||||
List<String>? paths,
|
List<String>? paths,
|
||||||
|
|
148
lib/src/bindings/stash.dart
Normal file
148
lib/src/bindings/stash.dart
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
import 'dart:ffi';
|
||||||
|
import 'package:ffi/ffi.dart';
|
||||||
|
import '../error.dart';
|
||||||
|
import '../stash.dart';
|
||||||
|
import 'checkout.dart' as checkout_bindings;
|
||||||
|
import 'libgit2_bindings.dart';
|
||||||
|
import '../util.dart';
|
||||||
|
|
||||||
|
/// Save the local modifications to a new stash.
|
||||||
|
///
|
||||||
|
/// Throws a [LibGit2Error] if error occured.
|
||||||
|
Pointer<git_oid> stash(
|
||||||
|
Pointer<git_repository> repo,
|
||||||
|
Pointer<git_signature> stasher,
|
||||||
|
String message,
|
||||||
|
int flags,
|
||||||
|
) {
|
||||||
|
final out = calloc<git_oid>();
|
||||||
|
final messageC =
|
||||||
|
message.isNotEmpty ? message.toNativeUtf8().cast<Int8>() : nullptr;
|
||||||
|
final error = libgit2.git_stash_save(out, repo, stasher, messageC, flags);
|
||||||
|
|
||||||
|
if (error < 0) {
|
||||||
|
throw LibGit2Error(libgit2.git_error_last());
|
||||||
|
} else {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply a single stashed state from the stash list.
|
||||||
|
///
|
||||||
|
/// Throws a [LibGit2Error] if error occured.
|
||||||
|
void apply(
|
||||||
|
Pointer<git_repository> repo,
|
||||||
|
int index,
|
||||||
|
int flags,
|
||||||
|
int strategy,
|
||||||
|
String? directory,
|
||||||
|
List<String>? paths,
|
||||||
|
) {
|
||||||
|
final options =
|
||||||
|
calloc<git_stash_apply_options>(sizeOf<git_stash_apply_options>());
|
||||||
|
final optionsError = libgit2.git_stash_apply_options_init(
|
||||||
|
options, GIT_STASH_APPLY_OPTIONS_VERSION);
|
||||||
|
|
||||||
|
if (optionsError < 0) {
|
||||||
|
throw LibGit2Error(libgit2.git_error_last());
|
||||||
|
}
|
||||||
|
|
||||||
|
final checkoutOptions =
|
||||||
|
checkout_bindings.initOptions(strategy, directory, paths);
|
||||||
|
final optsC = checkoutOptions[0];
|
||||||
|
final pathPointers = checkoutOptions[1];
|
||||||
|
final strArray = checkoutOptions[2];
|
||||||
|
|
||||||
|
options.ref.flags = flags;
|
||||||
|
options.ref.checkout_options = (optsC as Pointer<git_checkout_options>).ref;
|
||||||
|
|
||||||
|
final error = libgit2.git_stash_apply(repo, index, options);
|
||||||
|
|
||||||
|
if (error < 0) {
|
||||||
|
throw LibGit2Error(libgit2.git_error_last());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var p in pathPointers) {
|
||||||
|
calloc.free(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
calloc.free(strArray);
|
||||||
|
calloc.free(optsC);
|
||||||
|
calloc.free(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a single stashed state from the stash list.
|
||||||
|
///
|
||||||
|
/// Throws a [LibGit2Error] if error occured.
|
||||||
|
void drop(Pointer<git_repository> repo, int index) {
|
||||||
|
libgit2.git_stash_drop(repo, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply a single stashed state from the stash list and remove it from the list if successful.
|
||||||
|
///
|
||||||
|
/// Throws a [LibGit2Error] if error occured.
|
||||||
|
void pop(
|
||||||
|
Pointer<git_repository> repo,
|
||||||
|
int index,
|
||||||
|
int flags,
|
||||||
|
int strategy,
|
||||||
|
String? directory,
|
||||||
|
List<String>? paths,
|
||||||
|
) {
|
||||||
|
final options =
|
||||||
|
calloc<git_stash_apply_options>(sizeOf<git_stash_apply_options>());
|
||||||
|
final optionsError = libgit2.git_stash_apply_options_init(
|
||||||
|
options, GIT_STASH_APPLY_OPTIONS_VERSION);
|
||||||
|
|
||||||
|
if (optionsError < 0) {
|
||||||
|
throw LibGit2Error(libgit2.git_error_last());
|
||||||
|
}
|
||||||
|
|
||||||
|
final checkoutOptions =
|
||||||
|
checkout_bindings.initOptions(strategy, directory, paths);
|
||||||
|
final optsC = checkoutOptions[0];
|
||||||
|
final pathPointers = checkoutOptions[1];
|
||||||
|
final strArray = checkoutOptions[2];
|
||||||
|
|
||||||
|
options.ref.flags = flags;
|
||||||
|
options.ref.checkout_options = (optsC as Pointer<git_checkout_options>).ref;
|
||||||
|
|
||||||
|
final error = libgit2.git_stash_pop(repo, index, options);
|
||||||
|
|
||||||
|
if (error < 0) {
|
||||||
|
throw LibGit2Error(libgit2.git_error_last());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var p in pathPointers) {
|
||||||
|
calloc.free(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
calloc.free(strArray);
|
||||||
|
calloc.free(optsC);
|
||||||
|
calloc.free(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
var _stashList = <Stash>[];
|
||||||
|
|
||||||
|
/// Loop over all the stashed states.
|
||||||
|
List<Stash> list(Pointer<git_repository> repo) {
|
||||||
|
const except = -1;
|
||||||
|
_stashList.clear();
|
||||||
|
git_stash_cb callBack = Pointer.fromFunction(_stashCb, except);
|
||||||
|
libgit2.git_stash_foreach(repo, callBack, nullptr);
|
||||||
|
return _stashList;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _stashCb(
|
||||||
|
int index,
|
||||||
|
Pointer<Int8> message,
|
||||||
|
Pointer<git_oid> oid,
|
||||||
|
Pointer<Void> payload,
|
||||||
|
) {
|
||||||
|
_stashList.add(Stash(
|
||||||
|
index: index,
|
||||||
|
message: message.cast<Utf8>().toDartString(),
|
||||||
|
oid: oid,
|
||||||
|
));
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -1120,3 +1120,57 @@ class GitConfigLevel {
|
||||||
@override
|
@override
|
||||||
String toString() => 'GitConfigLevel.$_name';
|
String toString() => 'GitConfigLevel.$_name';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stash flags.
|
||||||
|
class GitStash {
|
||||||
|
const GitStash._(this._value, this._name);
|
||||||
|
final int _value;
|
||||||
|
final String _name;
|
||||||
|
|
||||||
|
/// No option, default.
|
||||||
|
static const defaults = GitStash._(0, 'defaults');
|
||||||
|
|
||||||
|
/// All changes already added to the index are left intact in
|
||||||
|
/// the working directory.
|
||||||
|
static const keepIndex = GitStash._(1, 'keepIndex');
|
||||||
|
|
||||||
|
/// All untracked files are also stashed and then cleaned up
|
||||||
|
/// from the working directory.
|
||||||
|
static const includeUntracked = GitStash._(2, 'includeUntracked');
|
||||||
|
|
||||||
|
/// All ignored files are also stashed and then cleaned up from
|
||||||
|
/// the working directory.
|
||||||
|
static const includeIgnored = GitStash._(4, 'includeIgnored');
|
||||||
|
|
||||||
|
static const List<GitStash> values = [
|
||||||
|
defaults,
|
||||||
|
keepIndex,
|
||||||
|
includeUntracked,
|
||||||
|
includeIgnored,
|
||||||
|
];
|
||||||
|
|
||||||
|
int get value => _value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'GitStash.$_name';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stash application flags.
|
||||||
|
class GitStashApply {
|
||||||
|
const GitStashApply._(this._value, this._name);
|
||||||
|
final int _value;
|
||||||
|
final String _name;
|
||||||
|
|
||||||
|
static const defaults = GitStashApply._(0, 'defaults');
|
||||||
|
|
||||||
|
/// Try to reinstate not only the working tree's changes,
|
||||||
|
/// but also the index's changes.
|
||||||
|
static const reinstateIndex = GitStashApply._(1, 'reinstateIndex');
|
||||||
|
|
||||||
|
static const List<GitStashApply> values = [defaults, reinstateIndex];
|
||||||
|
|
||||||
|
int get value => _value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'GitStashApply.$_name';
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import 'bindings/commit.dart' as commit_bindings;
|
||||||
import 'bindings/checkout.dart' as checkout_bindings;
|
import 'bindings/checkout.dart' as checkout_bindings;
|
||||||
import 'bindings/reset.dart' as reset_bindings;
|
import 'bindings/reset.dart' as reset_bindings;
|
||||||
import 'bindings/diff.dart' as diff_bindings;
|
import 'bindings/diff.dart' as diff_bindings;
|
||||||
|
import 'bindings/stash.dart' as stash_bindings;
|
||||||
import 'branch.dart';
|
import 'branch.dart';
|
||||||
import 'commit.dart';
|
import 'commit.dart';
|
||||||
import 'config.dart';
|
import 'config.dart';
|
||||||
|
@ -837,4 +838,77 @@ class Repository {
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Saves the local modifications to a new stash.
|
||||||
|
///
|
||||||
|
/// Throws a [LibGit2Error] if error occured.
|
||||||
|
Oid stash({
|
||||||
|
required Signature stasher,
|
||||||
|
String message = '',
|
||||||
|
bool keepIndex = false,
|
||||||
|
bool includeUntracked = false,
|
||||||
|
bool includeIgnored = false,
|
||||||
|
}) {
|
||||||
|
int flags = 0;
|
||||||
|
if (keepIndex) flags |= GitStash.keepIndex.value;
|
||||||
|
if (includeUntracked) flags |= GitStash.includeUntracked.value;
|
||||||
|
if (includeIgnored) flags |= GitStash.includeIgnored.value;
|
||||||
|
|
||||||
|
return Oid(stash_bindings.stash(
|
||||||
|
_repoPointer,
|
||||||
|
stasher.pointer,
|
||||||
|
message,
|
||||||
|
flags,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies a single stashed state from the stash list.
|
||||||
|
///
|
||||||
|
/// Throws a [LibGit2Error] if error occured.
|
||||||
|
void stashApply({
|
||||||
|
int index = 0,
|
||||||
|
bool reinstateIndex = false,
|
||||||
|
Set<GitCheckout> strategy = const {
|
||||||
|
GitCheckout.safe,
|
||||||
|
GitCheckout.recreateMissing
|
||||||
|
},
|
||||||
|
String? directory,
|
||||||
|
List<String>? paths,
|
||||||
|
}) {
|
||||||
|
int flags = reinstateIndex ? GitStashApply.reinstateIndex.value : 0;
|
||||||
|
final int strat =
|
||||||
|
strategy.fold(0, (previousValue, e) => previousValue | e.value);
|
||||||
|
|
||||||
|
stash_bindings.apply(_repoPointer, index, flags, strat, directory, paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes a single stashed state from the stash list.
|
||||||
|
///
|
||||||
|
/// Throws a [LibGit2Error] if error occured.
|
||||||
|
void stashDrop([int index = 0]) => stash_bindings.drop(_repoPointer, index);
|
||||||
|
|
||||||
|
/// Applies a single stashed state from the stash list and remove it from the list if successful.
|
||||||
|
///
|
||||||
|
/// Throws a [LibGit2Error] if error occured.
|
||||||
|
void stashPop({
|
||||||
|
int index = 0,
|
||||||
|
bool reinstateIndex = false,
|
||||||
|
Set<GitCheckout> strategy = const {
|
||||||
|
GitCheckout.safe,
|
||||||
|
GitCheckout.recreateMissing
|
||||||
|
},
|
||||||
|
String? directory,
|
||||||
|
List<String>? paths,
|
||||||
|
}) {
|
||||||
|
int flags = reinstateIndex ? GitStashApply.reinstateIndex.value : 0;
|
||||||
|
final int strat =
|
||||||
|
strategy.fold(0, (previousValue, e) => previousValue | e.value);
|
||||||
|
|
||||||
|
stash_bindings.pop(_repoPointer, index, flags, strat, directory, paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns list of all the stashed states, first being the most recent.
|
||||||
|
List<Stash> get stashList {
|
||||||
|
return stash_bindings.list(_repoPointer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
28
lib/src/stash.dart
Normal file
28
lib/src/stash.dart
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import 'dart:ffi';
|
||||||
|
import 'bindings/libgit2_bindings.dart';
|
||||||
|
import 'oid.dart';
|
||||||
|
|
||||||
|
class Stash {
|
||||||
|
/// Initializes a new instance of [Stash] class.
|
||||||
|
Stash({
|
||||||
|
required this.index,
|
||||||
|
required this.message,
|
||||||
|
required Pointer<git_oid> oid,
|
||||||
|
}) {
|
||||||
|
this.oid = Oid(oid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The position within the stash list.
|
||||||
|
final int index;
|
||||||
|
|
||||||
|
/// The stash message.
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
/// The commit oid of the stashed state.
|
||||||
|
late final Oid oid;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'Stash{index: $index, message: $message, sha: ${oid.sha}}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -374,6 +374,74 @@ void main() {
|
||||||
throwsA(isA<LibGit2Error>()),
|
throwsA(isA<LibGit2Error>()),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('stash', () {
|
||||||
|
late Signature stasher;
|
||||||
|
setUp(() {
|
||||||
|
stasher = Signature.create(
|
||||||
|
name: 'Stasher',
|
||||||
|
email: 'stasher@email.com',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() => stasher.free());
|
||||||
|
|
||||||
|
test('successfully saves changes to stash', () {
|
||||||
|
File('${tmpDir}file').writeAsStringSync(
|
||||||
|
'edit',
|
||||||
|
mode: FileMode.append,
|
||||||
|
);
|
||||||
|
|
||||||
|
repo.stash(stasher: stasher, includeUntracked: true);
|
||||||
|
expect(repo.status.isEmpty, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully applies changes from stash', () {
|
||||||
|
File('${tmpDir}file').writeAsStringSync(
|
||||||
|
'edit',
|
||||||
|
mode: FileMode.append,
|
||||||
|
);
|
||||||
|
|
||||||
|
repo.stash(stasher: stasher);
|
||||||
|
expect(repo.status.isEmpty, true);
|
||||||
|
|
||||||
|
repo.stashApply();
|
||||||
|
expect(repo.status, contains('file'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully drops stash', () {
|
||||||
|
File('${tmpDir}file').writeAsStringSync(
|
||||||
|
'edit',
|
||||||
|
mode: FileMode.append,
|
||||||
|
);
|
||||||
|
|
||||||
|
repo.stash(stasher: stasher);
|
||||||
|
repo.stashDrop();
|
||||||
|
expect(() => repo.stashApply(), throwsA(isA<LibGit2Error>()));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('successfully pops from stash', () {
|
||||||
|
File('${tmpDir}file').writeAsStringSync(
|
||||||
|
'edit',
|
||||||
|
mode: FileMode.append,
|
||||||
|
);
|
||||||
|
|
||||||
|
repo.stash(stasher: stasher);
|
||||||
|
repo.stashPop();
|
||||||
|
expect(repo.status, contains('file'));
|
||||||
|
expect(() => repo.stashApply(), throwsA(isA<LibGit2Error>()));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns list of stashes', () {
|
||||||
|
File('${tmpDir}file').writeAsStringSync(
|
||||||
|
'edit',
|
||||||
|
mode: FileMode.append,
|
||||||
|
);
|
||||||
|
|
||||||
|
repo.stash(stasher: stasher);
|
||||||
|
expect(repo.stashList.length, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue