diff --git a/lib/src/repository.dart b/lib/src/repository.dart index 29a78ab..0cd9b62 100644 --- a/lib/src/repository.dart +++ b/lib/src/repository.dart @@ -1233,20 +1233,34 @@ class Repository { ); } - /// Checkouts the provided reference [refName] using the given strategy, and - /// updates the HEAD. + /// Checkouts the provided [target] using the given strategy, and updates the + /// 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 /// [GitCheckout.recreateMissing]. /// /// [directory] is alternative checkout path to workdir. /// - /// [paths] is list of files to checkout from provided reference [refName]. - /// If paths are provided HEAD will not be set to the reference [refName]. + /// [paths] is list of files to checkout from provided [target]. + /// If paths are provided, HEAD will not be set to the [target]. + /// + /// Throws [ArgumentError] if provided [target] is not String or [Oid]. void checkout({ - String? refName, + Object? target, Set strategy = const { GitCheckout.safe, GitCheckout.recreateMissing @@ -1256,22 +1270,22 @@ class Repository { }) { final strat = strategy.fold(0, (int acc, e) => acc | e.value); - if (refName == null) { + if (target == null) { checkout_bindings.index( repoPointer: _repoPointer, strategy: strat, directory: directory, paths: paths, ); - } else if (refName == 'HEAD') { + } else if (target == 'HEAD') { checkout_bindings.head( repoPointer: _repoPointer, strategy: strat, directory: directory, paths: paths, ); - } else { - final ref = lookupReference(refName); + } else if (target is String) { + final ref = lookupReference(target); final treeish = object_bindings.lookup( repoPointer: _repoPointer, oidPointer: ref.target.pointer, @@ -1285,11 +1299,27 @@ class Repository { paths: paths, ); if (paths == null) { - setHead(refName); + setHead(target); } - - object_bindings.free(treeish); 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'); } } diff --git a/test/blob_test.dart b/test/blob_test.dart index 3817dbd..cb6b420 100644 --- a/test/blob_test.dart +++ b/test/blob_test.dart @@ -128,7 +128,7 @@ void main() { }); 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 blob = repo.lookupBlob(blobOid); diff --git a/test/checkout_test.dart b/test/checkout_test.dart index ae1bf80..1dee711 100644 --- a/test/checkout_test.dart +++ b/test/checkout_test.dart @@ -28,7 +28,7 @@ void main() { expect(repo.status, contains('feature_file')); repo.checkout( - refName: 'HEAD', + target: 'HEAD', strategy: {GitCheckout.force}, paths: ['feature_file'], ); @@ -40,7 +40,7 @@ void main() { 'directory', () { expect( () => repo.checkout( - refName: 'HEAD', + target: 'HEAD', directory: 'not/there', ), throwsA(isA()), @@ -70,7 +70,7 @@ void main() { ); }); - test('checkouts tree', () { + test('checkouts reference', () { final masterHead = repo.lookupCommit( repo['821ed6e80627b8769d170a293862f9fc60825226'], ); @@ -80,7 +80,7 @@ void main() { false, ); - repo.checkout(refName: 'refs/heads/feature'); + repo.checkout(target: 'refs/heads/feature'); final featureHead = repo.lookupCommit( repo['5aecfa0fb97eadaac050ccb99f03c3fb65460ad4'], ); @@ -101,17 +101,56 @@ void main() { }); test( - 'throws when trying to checkout tree with invalid alternative ' + 'throws when trying to checkout reference with invalid alternative ' 'directory', () { expect( () => repo.checkout( - refName: 'refs/heads/feature', + target: 'refs/heads/feature', directory: 'not/there', ), throwsA(isA()), ); }); + 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', () { final altDir = Directory(p.join(Directory.systemTemp.path, 'alt_dir')); // making sure there is no directory @@ -121,16 +160,18 @@ void main() { altDir.createSync(); 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)); altDir.deleteSync(recursive: true); }); test('checkouts file with provided path', () { + final featureTip = repo.lookupReference('refs/heads/feature').target; + expect(repo.status, isEmpty); repo.checkout( - refName: 'refs/heads/feature', + target: 'refs/heads/feature', paths: ['another_feature_file'], ); expect( @@ -139,6 +180,8 @@ void main() { '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', () { @@ -148,7 +191,7 @@ void main() { expect(file.existsSync(), false); repo.checkout( - refName: 'refs/heads/feature', + target: 'refs/heads/feature', strategy: {GitCheckout.dryRun}, ); expect(index.length, 4); @@ -156,5 +199,9 @@ void main() { index.free(); }); + + test('throws when provided target is not String or Oid', () { + expect(() => repo.checkout(target: 1), throwsA(isA())); + }); }); } diff --git a/test/diff_test.dart b/test/diff_test.dart index 78632a9..230b583 100644 --- a/test/diff_test.dart +++ b/test/diff_test.dart @@ -337,7 +337,7 @@ index e69de29..c217c63 100644 ); final diff2 = Diff.parse(patchText); - repo.checkout(refName: 'HEAD', strategy: {GitCheckout.force}); + repo.checkout(target: 'HEAD', strategy: {GitCheckout.force}); expect( repo.applies(diff: diff2, location: GitApplyLocation.both), true, @@ -357,7 +357,7 @@ index e69de29..c217c63 100644 final diff2 = Diff.parse(patchText); final hunk = diff2.patches.first.hunks.first; - repo.checkout(refName: 'HEAD', strategy: {GitCheckout.force}); + repo.checkout(target: 'HEAD', strategy: {GitCheckout.force}); expect( repo.applies( diff: diff2, @@ -375,7 +375,7 @@ index e69de29..c217c63 100644 final diff = Diff.parse(patchText); 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(), ''); repo.apply(diff: diff); @@ -405,7 +405,7 @@ index e69de29..c217c63 100644 final hunk = diff.patches.first.hunks.first; 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(), ''); repo.apply(diff: diff, hunkIndex: hunk.index); @@ -417,7 +417,7 @@ index e69de29..c217c63 100644 test('applies diff to tree', () { final diff = Diff.parse(patchText); - repo.checkout(refName: 'HEAD', strategy: {GitCheckout.force}); + repo.checkout(target: 'HEAD', strategy: {GitCheckout.force}); final head = repo.head; final commit = repo.lookupCommit(head.target); final tree = commit.tree; @@ -444,7 +444,7 @@ index e69de29..c217c63 100644 final diff = Diff.parse(patchText); 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 commit = repo.lookupCommit(head.target); final tree = commit.tree; diff --git a/test/index_test.dart b/test/index_test.dart index 1b47d27..00c76b4 100644 --- a/test/index_test.dart +++ b/test/index_test.dart @@ -365,7 +365,7 @@ void main() { oid: conflictBranch.target, ); - conflictRepo.checkout(refName: 'refs/heads/feature'); + conflictRepo.checkout(target: 'refs/heads/feature'); conflictRepo.merge(commit: commit); @@ -421,7 +421,7 @@ void main() { oid: conflictBranch.target, ); - conflictRepo.checkout(refName: 'refs/heads/our-conflict'); + conflictRepo.checkout(target: 'refs/heads/our-conflict'); conflictRepo.merge(commit: commit); @@ -449,7 +449,7 @@ void main() { oid: conflictBranch.target, ); - conflictRepo.checkout(refName: 'refs/heads/feature'); + conflictRepo.checkout(target: 'refs/heads/feature'); conflictRepo.merge(commit: commit); diff --git a/test/merge_test.dart b/test/merge_test.dart index ca32649..0870ba8 100644 --- a/test/merge_test.dart +++ b/test/merge_test.dart @@ -159,7 +159,7 @@ Another feature edit repo: repo, oid: conflictBranch.target, ); - repo.checkout(refName: 'refs/heads/feature'); + repo.checkout(target: 'refs/heads/feature'); final index = repo.index; repo.merge(commit: commit); diff --git a/test/rebase_test.dart b/test/rebase_test.dart index ee52b9c..9a774ed 100644 --- a/test/rebase_test.dart +++ b/test/rebase_test.dart @@ -43,7 +43,7 @@ void main() { reference: feature, ); - repo.checkout(refName: feature.name); + repo.checkout(target: feature.name); expect(() => repo.index['.gitignore'], throwsA(isA())); final rebase = Rebase.init( @@ -141,7 +141,7 @@ void main() { final feature = repo.lookupReference('refs/heads/feature'); 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())); final rebase = Rebase.init( @@ -194,7 +194,7 @@ void main() { final conflict = repo.lookupReference('refs/heads/conflict-branch'); final ontoHead = AnnotatedCommit.lookup(repo: repo, oid: conflict.target); - repo.checkout(refName: conflict.name); + repo.checkout(target: conflict.name); final rebase = Rebase.init( repo: repo, @@ -231,7 +231,7 @@ void main() { final conflict = repo.lookupReference('refs/heads/conflict-branch'); final ontoHead = AnnotatedCommit.lookup(repo: repo, oid: conflict.target); - repo.checkout(refName: conflict.name); + repo.checkout(target: conflict.name); final rebase = Rebase.init( repo: repo, @@ -257,7 +257,7 @@ void main() { final conflict = repo.lookupReference('refs/heads/conflict-branch'); final ontoHead = AnnotatedCommit.lookup(repo: repo, oid: conflict.target); - repo.checkout(refName: conflict.name); + repo.checkout(target: conflict.name); final rebase = Rebase.init( repo: repo,