diff --git a/lib/datasource/api.ts b/lib/datasource/api.ts
index 9cf8796dc8be5e304a1c057f0da060cbcbcc0a98..6469279f8c6cdfc6089b7075a427d97620650ce2 100644
--- a/lib/datasource/api.ts
+++ b/lib/datasource/api.ts
@@ -8,8 +8,8 @@ import { DartDatasource } from './dart';
 import * as docker from './docker';
 import { GalaxyDatasource } from './galaxy';
 import { GalaxyCollectionDatasource } from './galaxy-collection';
-import * as gitRefs from './git-refs';
-import * as gitTags from './git-tags';
+import { GitRefsDatasource } from './git-refs';
+import { GitTagsDatasource } from './git-tags';
 import * as githubReleases from './github-releases';
 import * as githubTags from './github-tags';
 import { GitlabPackagesDatasource } from './gitlab-packages';
@@ -50,8 +50,8 @@ api.set('dart', new DartDatasource());
 api.set('docker', docker);
 api.set('galaxy', new GalaxyDatasource());
 api.set('galaxy-collection', new GalaxyCollectionDatasource());
-api.set('git-refs', gitRefs);
-api.set('git-tags', gitTags);
+api.set('git-refs', new GitRefsDatasource());
+api.set('git-tags', new GitTagsDatasource());
 api.set('github-releases', githubReleases);
 api.set('github-tags', githubTags);
 api.set('gitlab-packages', new GitlabPackagesDatasource());
diff --git a/lib/datasource/git-refs/base.ts b/lib/datasource/git-refs/base.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5cc5548a173fc453c2f25430160544a792cc7776
--- /dev/null
+++ b/lib/datasource/git-refs/base.ts
@@ -0,0 +1,64 @@
+import simpleGit from 'simple-git';
+import { logger } from '../../logger';
+import { cache } from '../../util/cache/package/decorator';
+import { simpleGitConfig } from '../../util/git/config';
+import { getRemoteUrlWithToken } from '../../util/git/url';
+import { regEx } from '../../util/regex';
+import type { GetReleasesConfig } from '../types';
+import type { RawRefs } from './types';
+
+const refMatch = regEx(/(?<hash>.*?)\s+refs\/(?<type>.*?)\/(?<value>.*)/);
+const headMatch = regEx(/(?<hash>.*?)\s+HEAD/);
+
+// TODO: extract to a separate directory structure (#10532)
+export class GitDatasource {
+  static id = 'git';
+
+  @cache({
+    namespace: `datasource-${GitDatasource.id}`,
+    key: ({ lookupName }: GetReleasesConfig) => lookupName,
+  })
+  static async getRawRefs(
+    { lookupName }: GetReleasesConfig,
+    hostType: string
+  ): Promise<RawRefs[] | null> {
+    const git = simpleGit(simpleGitConfig());
+
+    // fetch remote tags
+    const lsRemote = await git.listRemote([
+      getRemoteUrlWithToken(lookupName, hostType),
+    ]);
+    if (!lsRemote) {
+      return null;
+    }
+
+    const refs = lsRemote
+      .trim()
+      .split('\n')
+      .map((line) => line.trim())
+      .map((line) => {
+        let match = refMatch.exec(line);
+        if (match) {
+          return {
+            type: match.groups.type,
+            value: match.groups.value,
+            hash: match.groups.hash,
+          };
+        }
+        match = headMatch.exec(line);
+        if (match) {
+          return {
+            type: '',
+            value: 'HEAD',
+            hash: match.groups.hash,
+          };
+        }
+        logger.trace(`malformed ref: ${line}`);
+        return null;
+      })
+      .filter(Boolean)
+      .filter((ref) => ref.type !== 'pull' && !ref.value.endsWith('^{}'));
+
+    return refs;
+  }
+}
diff --git a/lib/datasource/git-refs/index.spec.ts b/lib/datasource/git-refs/index.spec.ts
index ae0d31258d9cbda1aaacf5b4c6c2ac45535685d4..56adb9d9a1c7e52fd632664d5f55a8387e87293a 100644
--- a/lib/datasource/git-refs/index.spec.ts
+++ b/lib/datasource/git-refs/index.spec.ts
@@ -1,7 +1,7 @@
 import _simpleGit from 'simple-git';
 import { getPkgReleases } from '..';
 import { loadFixture } from '../../../test/util';
-import { id as datasource, getDigest } from '.';
+import { GitRefsDatasource } from '.';
 
 jest.mock('simple-git');
 const simpleGit: any = _simpleGit;
@@ -10,6 +10,8 @@ const depName = 'https://github.com/example/example.git';
 
 const lsRemote1 = loadFixture('ls-remote-1.txt');
 
+const datasource = GitRefsDatasource.id;
+
 describe('datasource/git-refs/index', () => {
   describe('getReleases', () => {
     it('returns nil if response is wrong', async () => {
@@ -24,6 +26,18 @@ describe('datasource/git-refs/index', () => {
       });
       expect(versions).toBeNull();
     });
+    it('returns nil if response is malformed', async () => {
+      simpleGit.mockReturnValue({
+        listRemote() {
+          return Promise.resolve('aabbccddeeff');
+        },
+      });
+      const { releases } = await getPkgReleases({
+        datasource,
+        depName,
+      });
+      expect(releases).toBeEmpty();
+    });
     it('returns nil if remote call throws exception', async () => {
       simpleGit.mockReturnValue({
         listRemote() {
@@ -59,7 +73,7 @@ describe('datasource/git-refs/index', () => {
           return Promise.resolve(lsRemote1);
         },
       });
-      const digest = await getDigest(
+      const digest = await new GitRefsDatasource().getDigest(
         { lookupName: 'a tag to look up' },
         'v2.0.0'
       );
@@ -71,7 +85,7 @@ describe('datasource/git-refs/index', () => {
           return Promise.resolve(lsRemote1);
         },
       });
-      const digest = await getDigest(
+      const digest = await new GitRefsDatasource().getDigest(
         { lookupName: 'a tag to look up' },
         'v1.0.4'
       );
@@ -83,7 +97,7 @@ describe('datasource/git-refs/index', () => {
           return Promise.resolve(lsRemote1);
         },
       });
-      const digest = await getDigest(
+      const digest = await new GitRefsDatasource().getDigest(
         { lookupName: 'another tag to look up' },
         undefined
       );
diff --git a/lib/datasource/git-refs/index.ts b/lib/datasource/git-refs/index.ts
index 653fc08940b6c92ea991b0a9fd38135bc242c956..dcd2e603506bfc4e3fcea39b9d171384b7c8420c 100644
--- a/lib/datasource/git-refs/index.ts
+++ b/lib/datasource/git-refs/index.ts
@@ -1,114 +1,73 @@
-import simpleGit from 'simple-git';
-import * as packageCache from '../../util/cache/package';
-import { simpleGitConfig } from '../../util/git/config';
-import { getRemoteUrlWithToken } from '../../util/git/url';
+import { cache } from '../../util/cache/package/decorator';
 import { regEx } from '../../util/regex';
 import * as semver from '../../versioning/semver';
+import { Datasource } from '../datasource';
 import type { DigestConfig, GetReleasesConfig, ReleaseResult } from '../types';
+import { GitDatasource } from './base';
 import type { RawRefs } from './types';
 
-export const id = 'git-refs';
-export const customRegistrySupport = false;
-
-const cacheMinutes = 10;
-
 // git will prompt for known hosts or passwords, unless we activate BatchMode
 process.env.GIT_SSH_COMMAND = 'ssh -o BatchMode=yes';
 
-export async function getRawRefs(
-  { lookupName }: GetReleasesConfig,
-  hostType: string
-): Promise<RawRefs[] | null> {
-  const git = simpleGit(simpleGitConfig());
-  const cacheNamespace = 'git-raw-refs';
+export class GitRefsDatasource extends Datasource {
+  static readonly id = 'git-refs';
 
-  const cachedResult = await packageCache.get<RawRefs[]>(
-    cacheNamespace,
-    lookupName
-  );
-  /* istanbul ignore next line */
-  if (cachedResult) {
-    return cachedResult;
-  }
-
-  // fetch remote tags
-  const lsRemote = await git.listRemote([
-    getRemoteUrlWithToken(lookupName, hostType),
-  ]);
-  if (!lsRemote) {
-    return null;
+  constructor() {
+    super(GitRefsDatasource.id);
   }
 
-  const refMatch = regEx(/(?<hash>.*?)\s+refs\/(?<type>.*?)\/(?<value>.*)/);
-  const headMatch = regEx(/(?<hash>.*?)\s+HEAD/);
+  override readonly customRegistrySupport = false;
 
-  const refs = lsRemote
-    .trim()
-    .split('\n')
-    .map((line) => line.trim())
-    .map((line) => {
-      let match = refMatch.exec(line);
-      if (match) {
-        return {
-          type: match.groups.type,
-          value: match.groups.value,
-          hash: match.groups.hash,
-        };
-      }
-      match = headMatch.exec(line);
-      if (match) {
-        return {
-          type: '',
-          value: 'HEAD',
-          hash: match.groups.hash,
-        };
-      }
-      // istanbul ignore next
-      return null;
-    })
-    .filter(Boolean)
-    .filter((ref) => ref.type !== 'pull' && !ref.value.endsWith('^{}'));
-  await packageCache.set(cacheNamespace, lookupName, refs, cacheMinutes);
-  return refs;
-}
-
-export async function getReleases({
-  lookupName,
-}: GetReleasesConfig): Promise<ReleaseResult | null> {
-  const rawRefs: RawRefs[] = await getRawRefs({ lookupName }, id);
+  @cache({
+    namespace: `datasource-${GitRefsDatasource.id}`,
+    key: ({ lookupName }: GetReleasesConfig) => lookupName,
+  })
+  // eslint-disable-next-line class-methods-use-this
+  override async getReleases({
+    lookupName,
+  }: GetReleasesConfig): Promise<ReleaseResult | null> {
+    const rawRefs: RawRefs[] = await GitDatasource.getRawRefs(
+      { lookupName },
+      this.id
+    );
 
-  const refs = rawRefs
-    .filter((ref) => ref.type === 'tags' || ref.type === 'heads')
-    .map((ref) => ref.value)
-    .filter((ref) => semver.isVersion(ref));
+    const refs = rawRefs
+      .filter((ref) => ref.type === 'tags' || ref.type === 'heads')
+      .map((ref) => ref.value)
+      .filter((ref) => semver.isVersion(ref));
 
-  const uniqueRefs = [...new Set(refs)];
+    const uniqueRefs = [...new Set(refs)];
 
-  const sourceUrl = lookupName
-    .replace(regEx(/\.git$/), '')
-    .replace(regEx(/\/$/), '');
+    const sourceUrl = lookupName
+      .replace(regEx(/\.git$/), '')
+      .replace(regEx(/\/$/), '');
 
-  const result: ReleaseResult = {
-    sourceUrl,
-    releases: uniqueRefs.map((ref) => ({
-      version: ref,
-      gitRef: ref,
-      newDigest: rawRefs.find((rawRef) => rawRef.value === ref).hash,
-    })),
-  };
+    const result: ReleaseResult = {
+      sourceUrl,
+      releases: uniqueRefs.map((ref) => ({
+        version: ref,
+        gitRef: ref,
+        newDigest: rawRefs.find((rawRef) => rawRef.value === ref).hash,
+      })),
+    };
 
-  return result;
-}
+    return result;
+  }
 
-export async function getDigest(
-  { lookupName }: Partial<DigestConfig>,
-  newValue?: string
-): Promise<string | null> {
-  const rawRefs: RawRefs[] = await getRawRefs({ lookupName }, id);
-  const findValue = newValue || 'HEAD';
-  const ref = rawRefs.find((rawRef) => rawRef.value === findValue);
-  if (ref) {
-    return ref.hash;
+  // eslint-disable-next-line class-methods-use-this
+  override async getDigest(
+    { lookupName }: DigestConfig,
+    newValue?: string
+  ): Promise<string | null> {
+    const rawRefs: RawRefs[] = await GitDatasource.getRawRefs(
+      { lookupName },
+      this.id
+    );
+    const findValue = newValue || 'HEAD';
+    const ref = rawRefs.find((rawRef) => rawRef.value === findValue);
+    if (ref) {
+      return ref.hash;
+    }
+    return null;
   }
-  return null;
 }
diff --git a/lib/datasource/git-tags/index.spec.ts b/lib/datasource/git-tags/index.spec.ts
index 6b05503946f35b968e3b41138ea9589e6d615e0b..e9384bdd301c99edb7703177c920c529fe823832 100644
--- a/lib/datasource/git-tags/index.spec.ts
+++ b/lib/datasource/git-tags/index.spec.ts
@@ -1,7 +1,7 @@
 import _simpleGit from 'simple-git';
 import { getPkgReleases } from '..';
 import { loadFixture } from '../../../test/util';
-import { id as datasource, getDigest } from '.';
+import { GitTagsDatasource } from '.';
 
 jest.mock('simple-git');
 const simpleGit: any = _simpleGit;
@@ -10,6 +10,9 @@ const depName = 'https://github.com/example/example.git';
 
 const lsRemote1 = loadFixture('ls-remote-1.txt', '../git-refs');
 
+const datasource = GitTagsDatasource.id;
+const datasourceInstance = new GitTagsDatasource();
+
 describe('datasource/git-tags/index', () => {
   describe('getReleases', () => {
     it('returns nil if response is wrong', async () => {
@@ -51,7 +54,7 @@ describe('datasource/git-tags/index', () => {
           return Promise.resolve(lsRemote1);
         },
       });
-      const digest = await getDigest(
+      const digest = await datasourceInstance.getDigest(
         { datasource, depName: 'a tag to look up' },
         'notfound'
       );
@@ -63,7 +66,7 @@ describe('datasource/git-tags/index', () => {
           return Promise.resolve(lsRemote1);
         },
       });
-      const digest = await getDigest(
+      const digest = await datasourceInstance.getDigest(
         { datasource, depName: 'a tag to look up' },
         'v1.0.2'
       );
@@ -75,7 +78,7 @@ describe('datasource/git-tags/index', () => {
           return Promise.resolve(lsRemote1);
         },
       });
-      const digest = await getDigest(
+      const digest = await datasourceInstance.getDigest(
         { datasource, depName: 'another tag to look up' },
         undefined
       );
diff --git a/lib/datasource/git-tags/index.ts b/lib/datasource/git-tags/index.ts
index a742ff7b8ed5c8008b3655c4fd603fb1d18a5a9f..f48a66fc10b9a4c5871dad8b95def1aa4da88476 100644
--- a/lib/datasource/git-tags/index.ts
+++ b/lib/datasource/git-tags/index.ts
@@ -1,49 +1,64 @@
+import { cache } from '../../util/cache/package/decorator';
 import { regEx } from '../../util/regex';
 import * as semver from '../../versioning/semver';
-import * as gitRefs from '../git-refs';
+import { Datasource } from '../datasource';
+import { GitDatasource } from '../git-refs/base';
 import type { DigestConfig, GetReleasesConfig, ReleaseResult } from '../types';
 
-export const id = 'git-tags';
-export const customRegistrySupport = false;
+export class GitTagsDatasource extends Datasource {
+  static readonly id = 'git-tags';
 
-export async function getReleases({
-  lookupName,
-}: GetReleasesConfig): Promise<ReleaseResult | null> {
-  const rawRefs = await gitRefs.getRawRefs({ lookupName }, id);
+  constructor() {
+    super(GitTagsDatasource.id);
+  }
 
-  if (rawRefs === null) {
-    return null;
+  override readonly customRegistrySupport = false;
+
+  @cache({
+    namespace: `datasource-${GitTagsDatasource.id}`,
+    key: ({ lookupName }: GetReleasesConfig) => lookupName,
+  })
+  // eslint-disable-next-line class-methods-use-this
+  async getReleases({
+    lookupName,
+  }: GetReleasesConfig): Promise<ReleaseResult | null> {
+    const rawRefs = await GitDatasource.getRawRefs({ lookupName }, this.id);
+
+    if (rawRefs === null) {
+      return null;
+    }
+    const releases = rawRefs
+      .filter((ref) => ref.type === 'tags')
+      .filter((ref) => semver.isVersion(ref.value))
+      .map((ref) => ({
+        version: ref.value,
+        gitRef: ref.value,
+        newDigest: ref.hash,
+      }));
+
+    const sourceUrl = lookupName
+      .replace(regEx(/\.git$/), '')
+      .replace(regEx(/\/$/), '');
+
+    const result: ReleaseResult = {
+      sourceUrl,
+      releases,
+    };
+
+    return result;
   }
-  const releases = rawRefs
-    .filter((ref) => ref.type === 'tags')
-    .filter((ref) => semver.isVersion(ref.value))
-    .map((ref) => ({
-      version: ref.value,
-      gitRef: ref.value,
-      newDigest: ref.hash,
-    }));
-
-  const sourceUrl = lookupName
-    .replace(regEx(/\.git$/), '')
-    .replace(regEx(/\/$/), '');
-
-  const result: ReleaseResult = {
-    sourceUrl,
-    releases,
-  };
-
-  return result;
-}
 
-export async function getDigest(
-  { lookupName }: Partial<DigestConfig>,
-  newValue?: string
-): Promise<string | null> {
-  const rawRefs = await gitRefs.getRawRefs({ lookupName }, id);
-  const findValue = newValue || 'HEAD';
-  const ref = rawRefs.find((rawRef) => rawRef.value === findValue);
-  if (ref) {
-    return ref.hash;
+  // eslint-disable-next-line class-methods-use-this
+  override async getDigest(
+    { lookupName }: DigestConfig,
+    newValue?: string
+  ): Promise<string | null> {
+    const rawRefs = await GitDatasource.getRawRefs({ lookupName }, this.id);
+    const findValue = newValue || 'HEAD';
+    const ref = rawRefs.find((rawRef) => rawRef.value === findValue);
+    if (ref) {
+      return ref.hash;
+    }
+    return null;
   }
-  return null;
 }
diff --git a/lib/manager/ansible-galaxy/collections.ts b/lib/manager/ansible-galaxy/collections.ts
index a15ee60d9fdefbd9c11d8031a51ed63b9d480a32..adf6f4cd65697ce1905cb79a3baa006e0a7c28b3 100644
--- a/lib/manager/ansible-galaxy/collections.ts
+++ b/lib/manager/ansible-galaxy/collections.ts
@@ -1,5 +1,5 @@
 import { GalaxyCollectionDatasource } from '../../datasource/galaxy-collection';
-import * as datasourceGitTags from '../../datasource/git-tags';
+import { GitTagsDatasource } from '../../datasource/git-tags';
 import * as datasourceGithubTags from '../../datasource/github-tags';
 import { SkipReason } from '../../types';
 import { regEx } from '../../util/regex';
@@ -50,14 +50,14 @@ function handleGitDep(
   nameMatch: RegExpExecArray
 ): void {
   /* eslint-disable no-param-reassign */
-  dep.datasource = datasourceGitTags.id;
+  dep.datasource = GitTagsDatasource.id;
 
   if (nameMatch) {
     // if a github.com repository is referenced use github-tags instead of git-tags
     if (nameMatch.groups.hostname === 'github.com') {
       dep.datasource = datasourceGithubTags.id;
     } else {
-      dep.datasource = datasourceGitTags.id;
+      dep.datasource = GitTagsDatasource.id;
     }
     // source definition without version appendix
     const source = nameMatch.groups.source;
diff --git a/lib/manager/ansible-galaxy/roles.ts b/lib/manager/ansible-galaxy/roles.ts
index 21b3f9646e2ec0e24d6bcf6d7826502ee55b5acc..f47da5a9059998cfcd1495716bbf3320f4df3881 100644
--- a/lib/manager/ansible-galaxy/roles.ts
+++ b/lib/manager/ansible-galaxy/roles.ts
@@ -1,5 +1,5 @@
 import { GalaxyDatasource } from '../../datasource/galaxy';
-import * as datasourceGitTags from '../../datasource/git-tags';
+import { GitTagsDatasource } from '../../datasource/git-tags';
 import { SkipReason } from '../../types';
 import { regEx } from '../../util/regex';
 import type { PackageDependency } from '../types';
@@ -54,7 +54,7 @@ function finalize(dependency: PackageDependency): boolean {
   const source: string = dep.managerData.src;
   const sourceMatch = nameMatchRegex.exec(source);
   if (sourceMatch) {
-    dep.datasource = datasourceGitTags.id;
+    dep.datasource = GitTagsDatasource.id;
     dep.depName = sourceMatch.groups.depName.replace(regEx(/.git$/), '');
     // remove leading `git+` from URLs like `git+https://...`
     dep.lookupName = source.replace(regEx(/git\+/), '');
diff --git a/lib/manager/argocd/extract.ts b/lib/manager/argocd/extract.ts
index 78a0b555554d4d3475adcfd58b1c7959a80756cc..ae5d1dd730d36b3c129a0b4ffc6d944f044aaad0 100644
--- a/lib/manager/argocd/extract.ts
+++ b/lib/manager/argocd/extract.ts
@@ -1,6 +1,6 @@
 import is from '@sindresorhus/is';
 import { loadAll } from 'js-yaml';
-import * as gitTags from '../../datasource/git-tags';
+import { GitTagsDatasource } from '../../datasource/git-tags';
 import { HelmDatasource } from '../../datasource/helm';
 import type { ExtractConfig, PackageDependency, PackageFile } from '../types';
 import type { ApplicationDefinition } from './types';
@@ -31,7 +31,7 @@ function createDependency(
   return {
     depName: source.repoURL,
     currentValue: source.targetRevision,
-    datasource: gitTags.id,
+    datasource: GitTagsDatasource.id,
   };
 }
 
diff --git a/lib/manager/azure-pipelines/extract.ts b/lib/manager/azure-pipelines/extract.ts
index c7ab5686adf9446b8568bdda8736df35e62fb246..d89bfd09ec067a24c7d72aa06299effe2c23e680 100644
--- a/lib/manager/azure-pipelines/extract.ts
+++ b/lib/manager/azure-pipelines/extract.ts
@@ -1,5 +1,5 @@
 import { load } from 'js-yaml';
-import * as datasourceGitTags from '../../datasource/git-tags';
+import { GitTagsDatasource } from '../../datasource/git-tags';
 import { logger } from '../../logger';
 import { getDep } from '../dockerfile/extract';
 import type { PackageDependency, PackageFile } from '../types';
@@ -19,7 +19,7 @@ export function extractRepository(
   return {
     autoReplaceStringTemplate: 'refs/tags/{{newValue}}',
     currentValue: repository.ref.replace('refs/tags/', ''),
-    datasource: datasourceGitTags.id,
+    datasource: GitTagsDatasource.id,
     depName: repository.name,
     depType: 'gitTags',
     lookupName: `https://github.com/${repository.name}.git`,
diff --git a/lib/manager/batect/extract.spec.ts b/lib/manager/batect/extract.spec.ts
index 09da6465c5612a9d164ec24e61a9f55bd277807e..8b76e27da3ae99a56afc5553fe90e57bf8ab2d44 100644
--- a/lib/manager/batect/extract.spec.ts
+++ b/lib/manager/batect/extract.spec.ts
@@ -1,6 +1,6 @@
 import { setGlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
-import { id as gitTagDatasource } from '../../datasource/git-tags';
+import { GitTagsDatasource } from '../../datasource/git-tags';
 import { id as dockerVersioning } from '../../versioning/docker';
 import { id as semverVersioning } from '../../versioning/semver';
 import { getDep } from '../dockerfile/extract';
@@ -21,7 +21,7 @@ function createGitDependency(repo: string, version: string): PackageDependency {
     depName: repo,
     currentValue: version,
     versioning: semverVersioning,
-    datasource: gitTagDatasource,
+    datasource: GitTagsDatasource.id,
     commitMessageTopic: 'bundle {{depName}}',
   };
 }
diff --git a/lib/manager/batect/extract.ts b/lib/manager/batect/extract.ts
index dd3982ae5c49af211afa6fd83453b03df4987371..d1e3a9a810caaf64b80db172dfe65651ca5b0404 100644
--- a/lib/manager/batect/extract.ts
+++ b/lib/manager/batect/extract.ts
@@ -1,6 +1,6 @@
 import { load } from 'js-yaml';
 import * as upath from 'upath';
-import { id as gitTagDatasource } from '../../datasource/git-tags';
+import { GitTagsDatasource } from '../../datasource/git-tags';
 import { logger } from '../../logger';
 import { readLocalFile } from '../../util/fs';
 import { id as dockerVersioning } from '../../versioning/docker';
@@ -72,7 +72,7 @@ function createBundleDependency(bundle: BatectGitInclude): PackageDependency {
     depName: bundle.repo,
     currentValue: bundle.ref,
     versioning: semverVersioning,
-    datasource: gitTagDatasource,
+    datasource: GitTagsDatasource.id,
     commitMessageTopic: 'bundle {{depName}}',
   };
 }
diff --git a/lib/manager/cocoapods/extract.ts b/lib/manager/cocoapods/extract.ts
index b170cd24f1582d4e93876112e6cff652a212418b..09ce166170d74928b7a140a601fa485e79e91a2b 100644
--- a/lib/manager/cocoapods/extract.ts
+++ b/lib/manager/cocoapods/extract.ts
@@ -1,4 +1,4 @@
-import * as datasourceGitTags from '../../datasource/git-tags';
+import { GitTagsDatasource } from '../../datasource/git-tags';
 import * as datasourceGithubTags from '../../datasource/github-tags';
 import * as datasourceGitlabTags from '../../datasource/gitlab-tags';
 import * as datasourcePod from '../../datasource/pod';
@@ -72,7 +72,7 @@ export function gitDep(parsedLine: ParsedLine): PackageDependency | null {
   }
 
   return {
-    datasource: datasourceGitTags.id,
+    datasource: GitTagsDatasource.id,
     depName,
     lookupName: git,
     currentValue: tag,
diff --git a/lib/manager/composer/extract.ts b/lib/manager/composer/extract.ts
index f12b8b4ff6c7634c6f59c02c3bc2691d41f578e8..754109701c918f13cbb2a182d68002e24779b2ad 100644
--- a/lib/manager/composer/extract.ts
+++ b/lib/manager/composer/extract.ts
@@ -1,5 +1,5 @@
 import is from '@sindresorhus/is';
-import * as datasourceGitTags from '../../datasource/git-tags';
+import { GitTagsDatasource } from '../../datasource/git-tags';
 import * as datasourcePackagist from '../../datasource/packagist';
 import { logger } from '../../logger';
 import { SkipReason } from '../../types';
@@ -135,7 +135,7 @@ export async function extractPackageFile(
             switch (repositories[depName].type) {
               case 'vcs':
               case 'git':
-                datasource = datasourceGitTags.id;
+                datasource = GitTagsDatasource.id;
                 lookupName = repositories[depName].url;
                 break;
             }
diff --git a/lib/manager/git-submodules/extract.ts b/lib/manager/git-submodules/extract.ts
index ae09c10ecf3a8c3dc4d93373f26184e2eb16dde3..0017b70e8d6fc38ab893c1763b78a201951c9c7a 100644
--- a/lib/manager/git-submodules/extract.ts
+++ b/lib/manager/git-submodules/extract.ts
@@ -2,7 +2,7 @@ import URL from 'url';
 import Git, { SimpleGit } from 'simple-git';
 import upath from 'upath';
 import { getGlobalConfig } from '../../config/global';
-import * as datasourceGitRefs from '../../datasource/git-refs';
+import { GitRefsDatasource } from '../../datasource/git-refs';
 import { logger } from '../../logger';
 import { simpleGitConfig } from '../../util/git/config';
 import { getHttpUrl, getRemoteUrlWithToken } from '../../util/git/url';
@@ -128,5 +128,5 @@ export default async function extractPackageFile(
     }
   }
 
-  return { deps, datasource: datasourceGitRefs.id };
+  return { deps, datasource: GitRefsDatasource.id };
 }
diff --git a/lib/manager/kustomize/extract.spec.ts b/lib/manager/kustomize/extract.spec.ts
index 8121a8920ebf1e92918e161c2a0f05bd5e915197..b795bbd97b4cf2e42b5ab7be7952f6b6f5d25f34 100644
--- a/lib/manager/kustomize/extract.spec.ts
+++ b/lib/manager/kustomize/extract.spec.ts
@@ -1,6 +1,6 @@
 import { loadFixture } from '../../../test/util';
 import * as datasourceDocker from '../../datasource/docker';
-import * as datasourceGitTags from '../../datasource/git-tags';
+import { GitTagsDatasource } from '../../datasource/git-tags';
 import * as datasourceGitHubTags from '../../datasource/github-tags';
 import { SkipReason } from '../../types';
 import {
@@ -54,7 +54,7 @@ describe('manager/kustomize/extract', () => {
       );
       expect(pkg).toEqual({
         currentValue: 'v1.2.3',
-        datasource: datasourceGitTags.id,
+        datasource: GitTagsDatasource.id,
         depName: 'bitbucket.com/user/test-repo',
         lookupName: 'ssh://git@bitbucket.com/user/test-repo',
       });
@@ -65,7 +65,7 @@ describe('manager/kustomize/extract', () => {
       );
       expect(pkg).toEqual({
         currentValue: 'v1.2.3',
-        datasource: datasourceGitTags.id,
+        datasource: GitTagsDatasource.id,
         depName: 'bitbucket.com:7999/user/test-repo',
         lookupName: 'ssh://git@bitbucket.com:7999/user/test-repo',
       });
@@ -76,7 +76,7 @@ describe('manager/kustomize/extract', () => {
       );
       expect(pkg).toEqual({
         currentValue: 'v1.2.3',
-        datasource: datasourceGitTags.id,
+        datasource: GitTagsDatasource.id,
         depName: 'bitbucket.com/user/test-repo',
         lookupName: 'ssh://git@bitbucket.com/user/test-repo',
       });
diff --git a/lib/manager/kustomize/extract.ts b/lib/manager/kustomize/extract.ts
index 8c3f3ade8568a9c44adf614ccd0aedf2761f8ffa..24fe43f570edadd786f18d3f038feb838736b408 100644
--- a/lib/manager/kustomize/extract.ts
+++ b/lib/manager/kustomize/extract.ts
@@ -1,7 +1,7 @@
 import is from '@sindresorhus/is';
 import { load } from 'js-yaml';
 import * as datasourceDocker from '../../datasource/docker';
-import * as datasourceGitTags from '../../datasource/git-tags';
+import { GitTagsDatasource } from '../../datasource/git-tags';
 import * as datasourceGitHubTags from '../../datasource/github-tags';
 import { logger } from '../../logger';
 import { SkipReason } from '../../types';
@@ -33,7 +33,7 @@ export function extractBase(base: string): PackageDependency | null {
   }
 
   return {
-    datasource: datasourceGitTags.id,
+    datasource: GitTagsDatasource.id,
     depName: path.replace('.git', ''),
     lookupName: match.groups.url,
     currentValue: match.groups.currentValue,
diff --git a/lib/manager/swift/extract.ts b/lib/manager/swift/extract.ts
index 2000dd184c51087a16a5177e76438973dd0b5956..cff2eceb9cf2b9ca525504f64d59f98cc85e19af 100644
--- a/lib/manager/swift/extract.ts
+++ b/lib/manager/swift/extract.ts
@@ -1,4 +1,4 @@
-import * as datasourceGitTags from '../../datasource/git-tags';
+import { GitTagsDatasource } from '../../datasource/git-tags';
 import { regEx } from '../../util/regex';
 import type { PackageDependency, PackageFile } from '../types';
 import type { MatchResult } from './types';
@@ -152,7 +152,7 @@ export function extractPackageFile(
     const depName = getDepName(lookupName);
     if (depName && currentValue) {
       const dep: PackageDependency = {
-        datasource: datasourceGitTags.id,
+        datasource: GitTagsDatasource.id,
         depName,
         lookupName,
         currentValue,
diff --git a/lib/manager/terraform/modules.ts b/lib/manager/terraform/modules.ts
index 34f0d6c8f4bd12e841e55a1ac70cb6b64b9150ab..ae24e68fda38aefe5de6f28ee66ddc10d3550085 100644
--- a/lib/manager/terraform/modules.ts
+++ b/lib/manager/terraform/modules.ts
@@ -1,4 +1,4 @@
-import * as datasourceGitTags from '../../datasource/git-tags';
+import { GitTagsDatasource } from '../../datasource/git-tags';
 import * as datasourceGithubTags from '../../datasource/github-tags';
 import { TerraformModuleDatasource } from '../../datasource/terraform-module';
 import { logger } from '../../logger';
@@ -52,7 +52,7 @@ export function analyseTerraformModule(dep: PackageDependency): void {
       dep.lookupName = gitTagsRefMatch.groups.url;
     }
     dep.currentValue = gitTagsRefMatch.groups.tag;
-    dep.datasource = datasourceGitTags.id;
+    dep.datasource = GitTagsDatasource.id;
   } else if (dep.managerData.source) {
     const moduleParts = dep.managerData.source.split('//')[0].split('/');
     if (moduleParts[0] === '..') {
diff --git a/lib/manager/terragrunt/modules.ts b/lib/manager/terragrunt/modules.ts
index be070cbd402b329d4d25085b9bd07bbbfe92b8e0..f10ba1486ed60ba961910d614557b50a883fa653 100644
--- a/lib/manager/terragrunt/modules.ts
+++ b/lib/manager/terragrunt/modules.ts
@@ -1,4 +1,4 @@
-import * as datasourceGitTags from '../../datasource/git-tags';
+import { GitTagsDatasource } from '../../datasource/git-tags';
 import * as datasourceGithubTags from '../../datasource/github-tags';
 import { TerraformModuleDatasource } from '../../datasource/terraform-module';
 import { logger } from '../../logger';
@@ -53,7 +53,7 @@ export function analyseTerragruntModule(dep: PackageDependency): void {
       dep.lookupName = gitTagsRefMatch.groups.url;
     }
     dep.currentValue = gitTagsRefMatch.groups.tag;
-    dep.datasource = datasourceGitTags.id;
+    dep.datasource = GitTagsDatasource.id;
   } else if (dep.managerData.source) {
     const moduleParts = dep.managerData.source.split('//')[0].split('/');
     if (moduleParts[0] === '..') {
diff --git a/lib/workers/branch/get-updated.spec.ts b/lib/workers/branch/get-updated.spec.ts
index fd084c5456f0539d6200877ddb21f3d5c711226d..c5b2ea2e0a49d24f38a4aeb8a7cb2b663d9f695d 100644
--- a/lib/workers/branch/get-updated.spec.ts
+++ b/lib/workers/branch/get-updated.spec.ts
@@ -1,5 +1,5 @@
 import { defaultConfig, git, mocked } from '../../../test/util';
-import * as datasourceGitRefs from '../../datasource/git-refs';
+import { GitRefsDatasource } from '../../datasource/git-refs';
 import * as _composer from '../../manager/composer';
 import * as _gitSubmodules from '../../manager/git-submodules';
 import * as _helmv3 from '../../manager/helmv3';
@@ -183,7 +183,7 @@ describe('workers/branch/get-updated', () => {
     it('handles git submodules', async () => {
       config.upgrades.push({
         manager: 'git-submodules',
-        datasource: datasourceGitRefs.id,
+        datasource: GitRefsDatasource.id,
       } as never);
       gitSubmodules.updateDependency.mockResolvedValueOnce('existing content');
       const res = await getUpdatedPackageFiles(config);
diff --git a/lib/workers/repository/process/lookup/index.spec.ts b/lib/workers/repository/process/lookup/index.spec.ts
index e47571fe33a61ced68b54005a568be20b9bb4399..52547b1c3252c63685bc01a1d768a0eef7a03b7c 100644
--- a/lib/workers/repository/process/lookup/index.spec.ts
+++ b/lib/workers/repository/process/lookup/index.spec.ts
@@ -8,8 +8,8 @@ import {
 import { CONFIG_VALIDATION } from '../../../../constants/error-messages';
 import * as datasourceDocker from '../../../../datasource/docker';
 import { id as datasourceDockerId } from '../../../../datasource/docker';
-import * as datasourceGitRefs from '../../../../datasource/git-refs';
-import { id as datasourceGitRefsId } from '../../../../datasource/git-refs';
+import { GitRefsDatasource } from '../../../../datasource/git-refs';
+import { GitDatasource } from '../../../../datasource/git-refs/base';
 import * as datasourceGithubReleases from '../../../../datasource/github-releases';
 import { id as datasourceGithubTagsId } from '../../../../datasource/github-tags';
 import { id as datasourceNpmId } from '../../../../datasource/npm';
@@ -24,7 +24,6 @@ import type { LookupUpdateConfig } from './types';
 import * as lookup from '.';
 
 jest.mock('../../../../datasource/docker');
-jest.mock('../../../../datasource/git-refs');
 jest.mock('../../../../datasource/github-releases');
 
 const fixtureRoot = '../../../../config/npm';
@@ -41,7 +40,6 @@ const webpackJson = loadJsonFixture('webpack.json', fixtureRoot);
 
 const docker = mocked(datasourceDocker) as any;
 docker.defaultRegistryUrls = ['https://index.docker.io'];
-const gitRefs = mocked(datasourceGitRefs);
 const githubReleases = mocked(datasourceGithubReleases);
 
 Object.assign(githubReleases, { defaultRegistryUrls: ['https://github.com'] });
@@ -1354,20 +1352,34 @@ describe('workers/repository/process/lookup/index', () => {
       expect(res).toMatchSnapshot();
     });
     it('handles git submodule update', async () => {
+      jest.mock('../../../../datasource/git-refs', () => ({
+        GitRefsDatasource: jest.fn(() => ({
+          getReleases: jest.fn().mockResolvedValue({
+            releases: [
+              {
+                version: 'master',
+              },
+            ],
+          }),
+          getDigest: jest
+            .fn()
+            .mockResolvedValue('4b825dc642cb6eb9a060e54bf8d69288fbee4904'),
+        })),
+      }));
+
+      jest.spyOn(GitDatasource, 'getRawRefs').mockResolvedValueOnce([
+        {
+          value: 'HEAD',
+          hash: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
+          type: '',
+        },
+      ]);
+
       config.depName = 'some-path';
       config.versioning = gitVersioningId;
-      config.datasource = datasourceGitRefsId;
+      config.datasource = GitRefsDatasource.id;
       config.currentDigest = 'some-digest';
-      gitRefs.getReleases.mockResolvedValueOnce({
-        releases: [
-          {
-            version: 'master',
-          },
-        ],
-      });
-      gitRefs.getDigest.mockResolvedValueOnce(
-        '4b825dc642cb6eb9a060e54bf8d69288fbee4904'
-      );
+
       const res = await lookup.lookupUpdates(config);
       // FIXME: explicit assert condition
       expect(res).toMatchSnapshot();