From 2c10282b7dbf459d170064c029b7cd24eb98701a Mon Sep 17 00:00:00 2001
From: Sergei Zharinov <zharinov@users.noreply.github.com>
Date: Fri, 6 Jan 2023 18:49:52 +0300
Subject: [PATCH] feat(github-tags): Leverage GraphQL for tag commit hashes
 (#19187)

Co-authored-by: Rhys Arkins <rhys@arkins.net>
Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
---
 .../datasource/github-tags/index.spec.ts      | 82 ++++++++-----------
 lib/modules/datasource/github-tags/index.ts   | 22 ++---
 lib/util/github/types.ts                      |  8 --
 3 files changed, 43 insertions(+), 69 deletions(-)

diff --git a/lib/modules/datasource/github-tags/index.spec.ts b/lib/modules/datasource/github-tags/index.spec.ts
index 347be8cf41..5ac69faabf 100644
--- a/lib/modules/datasource/github-tags/index.spec.ts
+++ b/lib/modules/datasource/github-tags/index.spec.ts
@@ -5,7 +5,6 @@ import * as hostRules from '../../../util/host-rules';
 import { GithubTagsDatasource } from '.';
 
 const githubApiHost = 'https://api.github.com';
-const githubEnterpriseApiHost = 'https://git.enterprise.com';
 
 describe('modules/datasource/github-tags/index', () => {
   const github = new GithubTagsDatasource();
@@ -20,7 +19,6 @@ describe('modules/datasource/github-tags/index', () => {
 
   describe('getDigest', () => {
     const packageName = 'some/dep';
-    const tag = 'v1.2.0';
 
     it('returns commit digest', async () => {
       httpMock
@@ -42,7 +40,7 @@ describe('modules/datasource/github-tags/index', () => {
       expect(res).toBeNull();
     });
 
-    it('returns digest', async () => {
+    it('returns untagged commit digest', async () => {
       httpMock
         .scope(githubApiHost)
         .get(`/repos/${packageName}/commits?per_page=1`)
@@ -52,56 +50,48 @@ describe('modules/datasource/github-tags/index', () => {
     });
 
     it('returns tagged commit digest', async () => {
-      httpMock
-        .scope(githubApiHost)
-        .get(`/repos/${packageName}/git/refs/tags/${tag}`)
-        .reply(200, {
-          object: { type: 'tag', url: `${githubApiHost}/some-url` },
-        })
-        .get('/some-url')
-        .reply(200, { object: { type: 'commit', sha: 'ddd111' } });
-      const res = await github.getDigest({ packageName }, tag);
-      expect(res).toBe('ddd111');
+      jest.spyOn(githubGraphql, 'queryTags').mockResolvedValueOnce([
+        {
+          version: 'v1.0.0',
+          gitRef: 'v1.0.0',
+          releaseTimestamp: '2021-01-01',
+          hash: '123',
+        },
+        {
+          version: 'v2.0.0',
+          gitRef: 'v2.0.0',
+          releaseTimestamp: '2022-01-01',
+          hash: 'abc',
+        },
+      ]);
+      const res = await github.getDigest({ packageName }, 'v2.0.0');
+      expect(res).toBe('abc');
     });
 
-    it('warns if unknown ref', async () => {
-      httpMock
-        .scope(githubApiHost)
-        .get(`/repos/${packageName}/git/refs/tags/${tag}`)
-        .reply(200, { object: { sha: 'ddd111' } });
-      const res = await github.getDigest({ packageName }, tag);
+    it('returns null for missing tagged commit digest', async () => {
+      jest.spyOn(githubGraphql, 'queryTags').mockResolvedValueOnce([
+        {
+          version: 'v1.0.0',
+          gitRef: 'v1.0.0',
+          releaseTimestamp: '2021-01-01',
+          hash: '123',
+        },
+        {
+          version: 'v2.0.0',
+          gitRef: 'v2.0.0',
+          releaseTimestamp: '2022-01-01',
+          hash: 'abc',
+        },
+      ]);
+      const res = await github.getDigest({ packageName }, 'v3.0.0');
       expect(res).toBeNull();
     });
 
-    it('returns null for missed tagged digest', async () => {
-      httpMock
-        .scope(githubApiHost)
-        .get(`/repos/${packageName}/git/refs/tags/${tag}`)
-        .reply(200, {});
-      const res = await github.getDigest({ packageName: 'some/dep' }, 'v1.2.0');
+    it('returns null for error', async () => {
+      jest.spyOn(githubGraphql, 'queryTags').mockRejectedValueOnce('error');
+      const res = await github.getDigest({ packageName }, 'v3.0.0');
       expect(res).toBeNull();
     });
-
-    it('supports GHE', async () => {
-      httpMock
-        .scope(githubEnterpriseApiHost)
-        .get(`/api/v3/repos/${packageName}/git/refs/tags/${tag}`)
-        .reply(200, { object: { type: 'commit', sha: 'ddd111' } })
-        .get(`/api/v3/repos/${packageName}/commits?per_page=1`)
-        .reply(200, [{ sha: 'abcdef' }]);
-
-      const sha1 = await github.getDigest(
-        { packageName, registryUrl: githubEnterpriseApiHost },
-        undefined
-      );
-      const sha2 = await github.getDigest(
-        { packageName: 'some/dep', registryUrl: githubEnterpriseApiHost },
-        'v1.2.0'
-      );
-
-      expect(sha1).toBe('abcdef');
-      expect(sha2).toBe('ddd111');
-    });
   });
 
   describe('getReleases', () => {
diff --git a/lib/modules/datasource/github-tags/index.ts b/lib/modules/datasource/github-tags/index.ts
index 680abee424..52c736c3ba 100644
--- a/lib/modules/datasource/github-tags/index.ts
+++ b/lib/modules/datasource/github-tags/index.ts
@@ -1,6 +1,5 @@
 import { logger } from '../../../logger';
 import { queryTags } from '../../../util/github/graphql';
-import type { GithubRestRef } from '../../../util/github/types';
 import { getApiBaseUrl, getSourceUrl } from '../../../util/github/url';
 import { GithubHttp } from '../../../util/http/github';
 import { Datasource } from '../datasource';
@@ -20,29 +19,22 @@ export class GithubTagsDatasource extends Datasource {
 
   async getTagCommit(
     registryUrl: string | undefined,
-    githubRepo: string,
+    packageName: string,
     tag: string
   ): Promise<string | null> {
-    const apiBaseUrl = getApiBaseUrl(registryUrl);
-    let digest: string | null = null;
     try {
-      const url = `${apiBaseUrl}repos/${githubRepo}/git/refs/tags/${tag}`;
-      const res = (await this.http.getJson<GithubRestRef>(url)).body.object;
-      if (res.type === 'commit') {
-        digest = res.sha;
-      } else if (res.type === 'tag') {
-        digest = (await this.http.getJson<GithubRestRef>(res.url)).body.object
-          .sha;
-      } else {
-        logger.warn({ res }, 'Unknown git tag refs type');
+      const tags = await queryTags({ packageName, registryUrl }, this.http);
+      const tagItem = tags.find(({ version }) => version === tag);
+      if (tagItem) {
+        return tagItem.hash;
       }
     } catch (err) {
       logger.debug(
-        { githubRepo, err },
+        { githubRepo: packageName, err },
         'Error getting tag commit from GitHub repo'
       );
     }
-    return digest;
+    return null;
   }
 
   async getCommit(
diff --git a/lib/util/github/types.ts b/lib/util/github/types.ts
index 40c65b805a..a2221d756f 100644
--- a/lib/util/github/types.ts
+++ b/lib/util/github/types.ts
@@ -21,14 +21,6 @@ export interface GithubRestAsset {
   size: number;
 }
 
-export interface GithubRestRef {
-  object: {
-    type: string;
-    url: string;
-    sha: string;
-  };
-}
-
 export interface GithubRestTag {
   name: string;
 }
-- 
GitLab