Purge invalid git repositories from cache before cloning (#2159)

* Purge invalid git repository from cache before cloning.

* Test cases for invalid git repositories
diff --git a/lib/src/source/git.dart b/lib/src/source/git.dart
index 6a7234c..60225b3 100644
--- a/lib/src/source/git.dart
+++ b/lib/src/source/git.dart
@@ -380,6 +380,8 @@
     var path = _repoCachePath(ref);
     if (_updatedRepos.contains(path)) return;
 
+    _cleanInvalidGitRepoCache(path);
+
     if (!entryExists(path)) await _createRepoCache(ref);
 
     // Try to list the revision. If it doesn't exist, git will fail and we'll
@@ -397,6 +399,8 @@
     var path = _repoCachePath(ref);
     if (_updatedRepos.contains(path)) return;
 
+    _cleanInvalidGitRepoCache(path);
+
     if (!entryExists(path)) {
       await _createRepoCache(ref);
     } else {
@@ -411,7 +415,12 @@
     var path = _repoCachePath(ref);
     assert(!_updatedRepos.contains(path));
 
-    await _clone(ref.description['url'], path, mirror: true);
+    try {
+      await _clone(ref.description['url'], path, mirror: true);
+    } catch (_) {
+      _cleanInvalidGitRepoCache(path);
+      rethrow;
+    }
     _updatedRepos.add(path);
   }
 
@@ -426,6 +435,19 @@
     _updatedRepos.add(path);
   }
 
+  ///  Clean-up [dirPath] if it's an invalid git repository
+  void _cleanInvalidGitRepoCache(String dirPath) {
+    if (dirExists(dirPath)) {
+      var processResult = runProcessSync(
+          git.command, ['rev-parse', '--is-inside-git-dir'],
+          workingDir: dirPath);
+      var result = processResult.stdout?.join('\n');
+      if (processResult.exitCode != 0 || result != 'true') {
+        deleteEntry(dirPath);
+      }
+    }
+  }
+
   /// Updates the package list file in [revisionCachePath] to include [path], if
   /// necessary.
   void _updatePackageList(String revisionCachePath, String path) {
diff --git a/test/get/git/clean_invalid_git_repo_cache_test.dart b/test/get/git/clean_invalid_git_repo_cache_test.dart
new file mode 100644
index 0000000..e7ff16b
--- /dev/null
+++ b/test/get/git/clean_invalid_git_repo_cache_test.dart
@@ -0,0 +1,108 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+import 'dart:io';
+
+import 'package:path/path.dart' as path;
+import 'package:test/test.dart';
+
+import '../../descriptor.dart' as d;
+import '../../test_pub.dart';
+
+/// Invalidates a git clone in the pub-cache, by recreating it as empty-directory.
+void _invalidateGitCache(String repo) {
+  final cacheDir =
+      path.join(d.sandbox, path.joinAll([cachePath, 'git', 'cache']));
+  final Directory fooCacheDir =
+      Directory(cacheDir).listSync().firstWhere((entity) {
+    if (entity is Directory &&
+        entity.path.split(Platform.pathSeparator).last.startsWith(repo))
+      return true;
+    return false;
+  });
+
+  fooCacheDir.deleteSync(recursive: true);
+  fooCacheDir.createSync();
+}
+
+main() {
+  test('Clean-up invalid git repo cache', () async {
+    ensureGit();
+
+    await d.git(
+        'foo.git', [d.libDir('foo'), d.libPubspec('foo', '1.0.0')]).create();
+
+    await d.appDir({
+      "foo": {"git": "../foo.git"}
+    }).create();
+
+    await pubGet();
+
+    await d.dir(cachePath, [
+      d.dir('git', [
+        d.dir('cache', [d.gitPackageRepoCacheDir('foo')]),
+        d.gitPackageRevisionCacheDir('foo')
+      ])
+    ]).validate();
+
+    _invalidateGitCache('foo');
+
+    await pubGet();
+  });
+
+  test('Clean-up invalid git repo cache at a specific branch', () async {
+    ensureGit();
+
+    var repo =
+        d.git('foo.git', [d.libDir('foo'), d.libPubspec('foo', '1.0.0')]);
+    await repo.create();
+    await repo.runGit(["branch", "old"]);
+
+    await d.appDir({
+      "foo": {
+        "git": {"url": "../foo.git", "ref": "old"}
+      }
+    }).create();
+
+    await pubGet();
+
+    await d.dir(cachePath, [
+      d.dir('git', [
+        d.dir('cache', [d.gitPackageRepoCacheDir('foo')]),
+        d.gitPackageRevisionCacheDir('foo')
+      ])
+    ]).validate();
+
+    _invalidateGitCache('foo');
+
+    await pubGet();
+  });
+
+  test('Clean-up invalid git repo cache at a specific commit', () async {
+    ensureGit();
+
+    var repo =
+        d.git('foo.git', [d.libDir('foo'), d.libPubspec('foo', '1.0.0')]);
+    await repo.create();
+    var commit = await repo.revParse('HEAD');
+
+    await d.appDir({
+      "foo": {
+        "git": {"url": "../foo.git", "ref": commit}
+      }
+    }).create();
+
+    await pubGet();
+
+    await d.dir(cachePath, [
+      d.dir('git', [
+        d.dir('cache', [d.gitPackageRepoCacheDir('foo')]),
+        d.gitPackageRevisionCacheDir('foo')
+      ])
+    ]).validate();
+
+    _invalidateGitCache('foo');
+
+    await pubGet();
+  });
+}