feat(checkout)!: add ability to checkout commit

This commit is contained in:
Aleksey Kulikov 2022-01-21 17:00:46 +03:00
parent 6fe24dcb65
commit eeffa6a008
7 changed files with 115 additions and 38 deletions

View file

@ -1233,20 +1233,34 @@ class Repository {
); );
} }
/// Checkouts the provided reference [refName] using the given strategy, and /// Checkouts the provided [target] using the given strategy, and updates the
/// updates the HEAD. /// HEAD.
/// ///
/// If no reference [refName] is given, checkouts from the index. /// [target] can be null, 'HEAD', reference name or commit [Oid].
///
/// If no [target] is given, updates files in the working tree to match the
/// content of the index.
///
/// If [target] is 'HEAD' string, updates files in the index and the working
/// tree to match the content of the commit pointed at by HEAD.
///
/// If [target] is reference name, updates files in the index and working
/// tree to match the content of the tree pointed at by the reference.
///
/// If [target] is commit oid, updates files in the index and working
/// tree to match the content of the tree pointed at by the commit.
/// ///
/// Default checkout strategy is combination of [GitCheckout.safe] and /// Default checkout strategy is combination of [GitCheckout.safe] and
/// [GitCheckout.recreateMissing]. /// [GitCheckout.recreateMissing].
/// ///
/// [directory] is alternative checkout path to workdir. /// [directory] is alternative checkout path to workdir.
/// ///
/// [paths] is list of files to checkout from provided reference [refName]. /// [paths] is list of files to checkout from provided [target].
/// If paths are provided HEAD will not be set to the reference [refName]. /// If paths are provided, HEAD will not be set to the [target].
///
/// Throws [ArgumentError] if provided [target] is not String or [Oid].
void checkout({ void checkout({
String? refName, Object? target,
Set<GitCheckout> strategy = const { Set<GitCheckout> strategy = const {
GitCheckout.safe, GitCheckout.safe,
GitCheckout.recreateMissing GitCheckout.recreateMissing
@ -1256,22 +1270,22 @@ class Repository {
}) { }) {
final strat = strategy.fold(0, (int acc, e) => acc | e.value); final strat = strategy.fold(0, (int acc, e) => acc | e.value);
if (refName == null) { if (target == null) {
checkout_bindings.index( checkout_bindings.index(
repoPointer: _repoPointer, repoPointer: _repoPointer,
strategy: strat, strategy: strat,
directory: directory, directory: directory,
paths: paths, paths: paths,
); );
} else if (refName == 'HEAD') { } else if (target == 'HEAD') {
checkout_bindings.head( checkout_bindings.head(
repoPointer: _repoPointer, repoPointer: _repoPointer,
strategy: strat, strategy: strat,
directory: directory, directory: directory,
paths: paths, paths: paths,
); );
} else { } else if (target is String) {
final ref = lookupReference(refName); final ref = lookupReference(target);
final treeish = object_bindings.lookup( final treeish = object_bindings.lookup(
repoPointer: _repoPointer, repoPointer: _repoPointer,
oidPointer: ref.target.pointer, oidPointer: ref.target.pointer,
@ -1285,11 +1299,27 @@ class Repository {
paths: paths, paths: paths,
); );
if (paths == null) { if (paths == null) {
setHead(refName); setHead(target);
} }
object_bindings.free(treeish);
ref.free(); ref.free();
} else if (target is Oid) {
final treeish = object_bindings.lookup(
repoPointer: _repoPointer,
oidPointer: target.pointer,
type: GitObject.any.value,
);
checkout_bindings.tree(
repoPointer: _repoPointer,
treeishPointer: treeish,
strategy: strat,
directory: directory,
paths: paths,
);
if (paths == null) {
setHead(target);
}
} else {
throw ArgumentError.value('$target must be either String or Oid');
} }
} }

View file

@ -128,7 +128,7 @@ void main() {
}); });
test('filters content of a blob with provided commit for attributes', () { test('filters content of a blob with provided commit for attributes', () {
repo.checkout(refName: 'refs/tags/v0.2'); repo.checkout(target: 'refs/tags/v0.2');
final blobOid = repo.createBlob('clrf\nclrf\n'); final blobOid = repo.createBlob('clrf\nclrf\n');
final blob = repo.lookupBlob(blobOid); final blob = repo.lookupBlob(blobOid);

View file

@ -28,7 +28,7 @@ void main() {
expect(repo.status, contains('feature_file')); expect(repo.status, contains('feature_file'));
repo.checkout( repo.checkout(
refName: 'HEAD', target: 'HEAD',
strategy: {GitCheckout.force}, strategy: {GitCheckout.force},
paths: ['feature_file'], paths: ['feature_file'],
); );
@ -40,7 +40,7 @@ void main() {
'directory', () { 'directory', () {
expect( expect(
() => repo.checkout( () => repo.checkout(
refName: 'HEAD', target: 'HEAD',
directory: 'not/there', directory: 'not/there',
), ),
throwsA(isA<LibGit2Error>()), throwsA(isA<LibGit2Error>()),
@ -70,7 +70,7 @@ void main() {
); );
}); });
test('checkouts tree', () { test('checkouts reference', () {
final masterHead = repo.lookupCommit( final masterHead = repo.lookupCommit(
repo['821ed6e80627b8769d170a293862f9fc60825226'], repo['821ed6e80627b8769d170a293862f9fc60825226'],
); );
@ -80,7 +80,7 @@ void main() {
false, false,
); );
repo.checkout(refName: 'refs/heads/feature'); repo.checkout(target: 'refs/heads/feature');
final featureHead = repo.lookupCommit( final featureHead = repo.lookupCommit(
repo['5aecfa0fb97eadaac050ccb99f03c3fb65460ad4'], repo['5aecfa0fb97eadaac050ccb99f03c3fb65460ad4'],
); );
@ -101,17 +101,56 @@ void main() {
}); });
test( test(
'throws when trying to checkout tree with invalid alternative ' 'throws when trying to checkout reference with invalid alternative '
'directory', () { 'directory', () {
expect( expect(
() => repo.checkout( () => repo.checkout(
refName: 'refs/heads/feature', target: 'refs/heads/feature',
directory: 'not/there', directory: 'not/there',
), ),
throwsA(isA<LibGit2Error>()), throwsA(isA<LibGit2Error>()),
); );
}); });
test('checkouts commit', () {
final index = repo.index;
expect(index.find('another_feature_file'), equals(false));
final featureHead = repo.lookupCommit(
repo['5aecfa0fb97eadaac050ccb99f03c3fb65460ad4'],
);
repo.checkout(target: featureHead.oid);
final repoHead = repo.head;
expect(repoHead.target, featureHead.oid);
expect(repo.status, isEmpty);
expect(index.find('another_feature_file'), equals(true));
repoHead.free();
featureHead.free();
index.free();
});
test('checkouts commit with provided path', () {
final featureHead = repo.lookupCommit(
repo['5aecfa0fb97eadaac050ccb99f03c3fb65460ad4'],
);
repo.checkout(target: featureHead.oid, paths: ['another_feature_file']);
final repoHead = repo.head;
// When path is provided HEAD will not be set to target;
expect(repoHead.target, isNot(featureHead.oid));
expect(
repo.status,
{
'another_feature_file': {GitStatus.indexNew}
},
);
repoHead.free();
featureHead.free();
});
test('checkouts with alrenative directory', () { test('checkouts with alrenative directory', () {
final altDir = Directory(p.join(Directory.systemTemp.path, 'alt_dir')); final altDir = Directory(p.join(Directory.systemTemp.path, 'alt_dir'));
// making sure there is no directory // making sure there is no directory
@ -121,16 +160,18 @@ void main() {
altDir.createSync(); altDir.createSync();
expect(altDir.listSync().length, 0); expect(altDir.listSync().length, 0);
repo.checkout(refName: 'refs/heads/feature', directory: altDir.path); repo.checkout(target: 'refs/heads/feature', directory: altDir.path);
expect(altDir.listSync().length, isNot(0)); expect(altDir.listSync().length, isNot(0));
altDir.deleteSync(recursive: true); altDir.deleteSync(recursive: true);
}); });
test('checkouts file with provided path', () { test('checkouts file with provided path', () {
final featureTip = repo.lookupReference('refs/heads/feature').target;
expect(repo.status, isEmpty); expect(repo.status, isEmpty);
repo.checkout( repo.checkout(
refName: 'refs/heads/feature', target: 'refs/heads/feature',
paths: ['another_feature_file'], paths: ['another_feature_file'],
); );
expect( expect(
@ -139,6 +180,8 @@ void main() {
'another_feature_file': {GitStatus.indexNew} 'another_feature_file': {GitStatus.indexNew}
}, },
); );
// When path is provided HEAD will not be set to target;
expect(repo.head.target, isNot(featureTip));
}); });
test('performs dry run checkout', () { test('performs dry run checkout', () {
@ -148,7 +191,7 @@ void main() {
expect(file.existsSync(), false); expect(file.existsSync(), false);
repo.checkout( repo.checkout(
refName: 'refs/heads/feature', target: 'refs/heads/feature',
strategy: {GitCheckout.dryRun}, strategy: {GitCheckout.dryRun},
); );
expect(index.length, 4); expect(index.length, 4);
@ -156,5 +199,9 @@ void main() {
index.free(); index.free();
}); });
test('throws when provided target is not String or Oid', () {
expect(() => repo.checkout(target: 1), throwsA(isA<ArgumentError>()));
});
}); });
} }

View file

@ -337,7 +337,7 @@ index e69de29..c217c63 100644
); );
final diff2 = Diff.parse(patchText); final diff2 = Diff.parse(patchText);
repo.checkout(refName: 'HEAD', strategy: {GitCheckout.force}); repo.checkout(target: 'HEAD', strategy: {GitCheckout.force});
expect( expect(
repo.applies(diff: diff2, location: GitApplyLocation.both), repo.applies(diff: diff2, location: GitApplyLocation.both),
true, true,
@ -357,7 +357,7 @@ index e69de29..c217c63 100644
final diff2 = Diff.parse(patchText); final diff2 = Diff.parse(patchText);
final hunk = diff2.patches.first.hunks.first; final hunk = diff2.patches.first.hunks.first;
repo.checkout(refName: 'HEAD', strategy: {GitCheckout.force}); repo.checkout(target: 'HEAD', strategy: {GitCheckout.force});
expect( expect(
repo.applies( repo.applies(
diff: diff2, diff: diff2,
@ -375,7 +375,7 @@ index e69de29..c217c63 100644
final diff = Diff.parse(patchText); final diff = Diff.parse(patchText);
final file = File(p.join(tmpDir.path, 'subdir', 'modified_file')); final file = File(p.join(tmpDir.path, 'subdir', 'modified_file'));
repo.checkout(refName: 'HEAD', strategy: {GitCheckout.force}); repo.checkout(target: 'HEAD', strategy: {GitCheckout.force});
expect(file.readAsStringSync(), ''); expect(file.readAsStringSync(), '');
repo.apply(diff: diff); repo.apply(diff: diff);
@ -405,7 +405,7 @@ index e69de29..c217c63 100644
final hunk = diff.patches.first.hunks.first; final hunk = diff.patches.first.hunks.first;
final file = File(p.join(tmpDir.path, 'subdir', 'modified_file')); final file = File(p.join(tmpDir.path, 'subdir', 'modified_file'));
repo.checkout(refName: 'HEAD', strategy: {GitCheckout.force}); repo.checkout(target: 'HEAD', strategy: {GitCheckout.force});
expect(file.readAsStringSync(), ''); expect(file.readAsStringSync(), '');
repo.apply(diff: diff, hunkIndex: hunk.index); repo.apply(diff: diff, hunkIndex: hunk.index);
@ -417,7 +417,7 @@ index e69de29..c217c63 100644
test('applies diff to tree', () { test('applies diff to tree', () {
final diff = Diff.parse(patchText); final diff = Diff.parse(patchText);
repo.checkout(refName: 'HEAD', strategy: {GitCheckout.force}); repo.checkout(target: 'HEAD', strategy: {GitCheckout.force});
final head = repo.head; final head = repo.head;
final commit = repo.lookupCommit(head.target); final commit = repo.lookupCommit(head.target);
final tree = commit.tree; final tree = commit.tree;
@ -444,7 +444,7 @@ index e69de29..c217c63 100644
final diff = Diff.parse(patchText); final diff = Diff.parse(patchText);
final hunk = diff.patches.first.hunks.first; final hunk = diff.patches.first.hunks.first;
repo.checkout(refName: 'HEAD', strategy: {GitCheckout.force}); repo.checkout(target: 'HEAD', strategy: {GitCheckout.force});
final head = repo.head; final head = repo.head;
final commit = repo.lookupCommit(head.target); final commit = repo.lookupCommit(head.target);
final tree = commit.tree; final tree = commit.tree;

View file

@ -365,7 +365,7 @@ void main() {
oid: conflictBranch.target, oid: conflictBranch.target,
); );
conflictRepo.checkout(refName: 'refs/heads/feature'); conflictRepo.checkout(target: 'refs/heads/feature');
conflictRepo.merge(commit: commit); conflictRepo.merge(commit: commit);
@ -421,7 +421,7 @@ void main() {
oid: conflictBranch.target, oid: conflictBranch.target,
); );
conflictRepo.checkout(refName: 'refs/heads/our-conflict'); conflictRepo.checkout(target: 'refs/heads/our-conflict');
conflictRepo.merge(commit: commit); conflictRepo.merge(commit: commit);
@ -449,7 +449,7 @@ void main() {
oid: conflictBranch.target, oid: conflictBranch.target,
); );
conflictRepo.checkout(refName: 'refs/heads/feature'); conflictRepo.checkout(target: 'refs/heads/feature');
conflictRepo.merge(commit: commit); conflictRepo.merge(commit: commit);

View file

@ -159,7 +159,7 @@ Another feature edit
repo: repo, repo: repo,
oid: conflictBranch.target, oid: conflictBranch.target,
); );
repo.checkout(refName: 'refs/heads/feature'); repo.checkout(target: 'refs/heads/feature');
final index = repo.index; final index = repo.index;
repo.merge(commit: commit); repo.merge(commit: commit);

View file

@ -43,7 +43,7 @@ void main() {
reference: feature, reference: feature,
); );
repo.checkout(refName: feature.name); repo.checkout(target: feature.name);
expect(() => repo.index['.gitignore'], throwsA(isA<ArgumentError>())); expect(() => repo.index['.gitignore'], throwsA(isA<ArgumentError>()));
final rebase = Rebase.init( final rebase = Rebase.init(
@ -141,7 +141,7 @@ void main() {
final feature = repo.lookupReference('refs/heads/feature'); final feature = repo.lookupReference('refs/heads/feature');
final upstream = AnnotatedCommit.lookup(repo: repo, oid: repo[shas[1]]); final upstream = AnnotatedCommit.lookup(repo: repo, oid: repo[shas[1]]);
repo.checkout(refName: feature.name); repo.checkout(target: feature.name);
expect(() => repo.index['conflict_file'], throwsA(isA<ArgumentError>())); expect(() => repo.index['conflict_file'], throwsA(isA<ArgumentError>()));
final rebase = Rebase.init( final rebase = Rebase.init(
@ -194,7 +194,7 @@ void main() {
final conflict = repo.lookupReference('refs/heads/conflict-branch'); final conflict = repo.lookupReference('refs/heads/conflict-branch');
final ontoHead = AnnotatedCommit.lookup(repo: repo, oid: conflict.target); final ontoHead = AnnotatedCommit.lookup(repo: repo, oid: conflict.target);
repo.checkout(refName: conflict.name); repo.checkout(target: conflict.name);
final rebase = Rebase.init( final rebase = Rebase.init(
repo: repo, repo: repo,
@ -231,7 +231,7 @@ void main() {
final conflict = repo.lookupReference('refs/heads/conflict-branch'); final conflict = repo.lookupReference('refs/heads/conflict-branch');
final ontoHead = AnnotatedCommit.lookup(repo: repo, oid: conflict.target); final ontoHead = AnnotatedCommit.lookup(repo: repo, oid: conflict.target);
repo.checkout(refName: conflict.name); repo.checkout(target: conflict.name);
final rebase = Rebase.init( final rebase = Rebase.init(
repo: repo, repo: repo,
@ -257,7 +257,7 @@ void main() {
final conflict = repo.lookupReference('refs/heads/conflict-branch'); final conflict = repo.lookupReference('refs/heads/conflict-branch');
final ontoHead = AnnotatedCommit.lookup(repo: repo, oid: conflict.target); final ontoHead = AnnotatedCommit.lookup(repo: repo, oid: conflict.target);
repo.checkout(refName: conflict.name); repo.checkout(target: conflict.name);
final rebase = Rebase.init( final rebase = Rebase.init(
repo: repo, repo: repo,