From 7739b9ba2efcda9dde65ad1e3c2dbe65b41dfba7 Mon Sep 17 00:00:00 2001
From: Robert Wieczoreck <1175008+RobertWieczoreck@users.noreply.github.com>
Date: Wed, 16 Aug 2023 22:34:54 +0200
Subject: [PATCH] Add option to fetch tags even if fetch-depth > 0 (#579)

* Add option to fetch tags even if fetch-depth > 0

* Add jest tests for fetchDepth and fetchTags options
---
 README.md                            |   4 +
 __test__/git-auth-helper.test.ts     |   1 +
 __test__/git-command-manager.test.ts | 176 +++++++++++++++++++++++++++
 __test__/input-helper.test.ts        |   1 +
 action.yml                           |   3 +
 dist/index.js                        |  11 +-
 src/git-command-manager.ts           |   9 +-
 src/git-source-provider.ts           |   7 +-
 src/git-source-settings.ts           |   5 +
 src/input-helper.ts                  |   5 +
 10 files changed, 214 insertions(+), 8 deletions(-)

diff --git a/README.md b/README.md
index 5427a50..9df4ae0 100644
--- a/README.md
+++ b/README.md
@@ -87,6 +87,10 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
     # Default: 1
     fetch-depth: ''
 
+    # Whether to fetch tags, even if fetch-depth > 0.
+    # Default: false
+    fetch-tags: ''
+
     # Whether to download Git-LFS files
     # Default: false
     lfs: ''
diff --git a/__test__/git-auth-helper.test.ts b/__test__/git-auth-helper.test.ts
index fec6573..8e580c1 100644
--- a/__test__/git-auth-helper.test.ts
+++ b/__test__/git-auth-helper.test.ts
@@ -805,6 +805,7 @@ async function setup(testName: string): Promise<void> {
     sparseCheckout: [],
     sparseCheckoutConeMode: true,
     fetchDepth: 1,
+    fetchTags: false,
     lfs: false,
     submodules: false,
     nestedSubmodules: false,
diff --git a/__test__/git-command-manager.test.ts b/__test__/git-command-manager.test.ts
index 1c31ef9..16c348f 100644
--- a/__test__/git-command-manager.test.ts
+++ b/__test__/git-command-manager.test.ts
@@ -88,3 +88,179 @@ describe('git-auth-helper tests', () => {
     expect(branches.sort()).toEqual(['foo'].sort())
   })
 })
+
+describe('Test fetchDepth and fetchTags options', () => {
+  beforeEach(async () => {
+    jest.spyOn(fshelper, 'fileExistsSync').mockImplementation(jest.fn())
+    jest.spyOn(fshelper, 'directoryExistsSync').mockImplementation(jest.fn())
+    mockExec.mockImplementation((path, args, options) => {
+      console.log(args, options.listeners.stdout)
+
+      if (args.includes('version')) {
+        options.listeners.stdout(Buffer.from('2.18'))
+      }
+
+      return 0
+    })
+  })
+
+  afterEach(() => {
+    jest.restoreAllMocks()
+  })
+
+  it('should call execGit with the correct arguments when fetchDepth is 0 and fetchTags is true', async () => {
+    jest.spyOn(exec, 'exec').mockImplementation(mockExec)
+    const workingDirectory = 'test'
+    const lfs = false
+    const doSparseCheckout = false
+    git = await commandManager.createCommandManager(
+      workingDirectory,
+      lfs,
+      doSparseCheckout
+    )
+
+    const refSpec = ['refspec1', 'refspec2']
+    const options = {
+      filter: 'filterValue',
+      fetchDepth: 0,
+      fetchTags: true
+    }
+
+    await git.fetch(refSpec, options)
+
+    expect(mockExec).toHaveBeenCalledWith(
+      expect.any(String),
+      [
+        '-c',
+        'protocol.version=2',
+        'fetch',
+        '--prune',
+        '--progress',
+        '--no-recurse-submodules',
+        '--filter=filterValue',
+        'origin',
+        'refspec1',
+        'refspec2'
+      ],
+      expect.any(Object)
+    )
+  })
+
+  it('should call execGit with the correct arguments when fetchDepth is 0 and fetchTags is false', async () => {
+    jest.spyOn(exec, 'exec').mockImplementation(mockExec)
+
+    const workingDirectory = 'test'
+    const lfs = false
+    const doSparseCheckout = false
+    git = await commandManager.createCommandManager(
+      workingDirectory,
+      lfs,
+      doSparseCheckout
+    )
+    const refSpec = ['refspec1', 'refspec2']
+    const options = {
+      filter: 'filterValue',
+      fetchDepth: 0,
+      fetchTags: false
+    }
+
+    await git.fetch(refSpec, options)
+
+    expect(mockExec).toHaveBeenCalledWith(
+      expect.any(String),
+      [
+        '-c',
+        'protocol.version=2',
+        'fetch',
+        '--no-tags',
+        '--prune',
+        '--progress',
+        '--no-recurse-submodules',
+        '--filter=filterValue',
+        'origin',
+        'refspec1',
+        'refspec2'
+      ],
+      expect.any(Object)
+    )
+  })
+
+  it('should call execGit with the correct arguments when fetchDepth is 1 and fetchTags is false', async () => {
+    jest.spyOn(exec, 'exec').mockImplementation(mockExec)
+
+    const workingDirectory = 'test'
+    const lfs = false
+    const doSparseCheckout = false
+    git = await commandManager.createCommandManager(
+      workingDirectory,
+      lfs,
+      doSparseCheckout
+    )
+    const refSpec = ['refspec1', 'refspec2']
+    const options = {
+      filter: 'filterValue',
+      fetchDepth: 1,
+      fetchTags: false
+    }
+
+    await git.fetch(refSpec, options)
+
+    expect(mockExec).toHaveBeenCalledWith(
+      expect.any(String),
+      [
+        '-c',
+        'protocol.version=2',
+        'fetch',
+        '--no-tags',
+        '--prune',
+        '--progress',
+        '--no-recurse-submodules',
+        '--filter=filterValue',
+        '--depth=1',
+        'origin',
+        'refspec1',
+        'refspec2'
+      ],
+      expect.any(Object)
+    )
+  })
+
+  it('should call execGit with the correct arguments when fetchDepth is 1 and fetchTags is true', async () => {
+    jest.spyOn(exec, 'exec').mockImplementation(mockExec)
+
+    const workingDirectory = 'test'
+    const lfs = false
+    const doSparseCheckout = false
+    git = await commandManager.createCommandManager(
+      workingDirectory,
+      lfs,
+      doSparseCheckout
+    )
+    const refSpec = ['refspec1', 'refspec2']
+    const options = {
+      filter: 'filterValue',
+      fetchDepth: 1,
+      fetchTags: true
+    }
+
+    await git.fetch(refSpec, options)
+
+    expect(mockExec).toHaveBeenCalledWith(
+      expect.any(String),
+      [
+        '-c',
+        'protocol.version=2',
+        'fetch',
+        '--prune',
+        '--progress',
+        '--no-recurse-submodules',
+        '--filter=filterValue',
+        '--depth=1',
+        'origin',
+        'refspec1',
+        'refspec2'
+      ],
+      expect.any(Object)
+    )
+  })
+})
diff --git a/__test__/input-helper.test.ts b/__test__/input-helper.test.ts
index 069fda4..aa58415 100644
--- a/__test__/input-helper.test.ts
+++ b/__test__/input-helper.test.ts
@@ -82,6 +82,7 @@ describe('input-helper tests', () => {
     expect(settings.sparseCheckout).toBe(undefined)
     expect(settings.sparseCheckoutConeMode).toBe(true)
     expect(settings.fetchDepth).toBe(1)
+    expect(settings.fetchTags).toBe(false)
     expect(settings.lfs).toBe(false)
     expect(settings.ref).toBe('refs/heads/some-ref')
     expect(settings.repositoryName).toBe('some-repo')
diff --git a/action.yml b/action.yml
index e562b56..58f07d7 100644
--- a/action.yml
+++ b/action.yml
@@ -65,6 +65,9 @@ inputs:
   fetch-depth:
     description: 'Number of commits to fetch. 0 indicates all history for all branches and tags.'
     default: 1
+  fetch-tags:
+    description: 'Whether to fetch tags, even if fetch-depth > 0.'
+    default: false
   lfs:
     description: 'Whether to download Git-LFS files'
     default: false
diff --git a/dist/index.js b/dist/index.js
index 4556295..9e38490 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -637,7 +637,7 @@ class GitCommandManager {
     fetch(refSpec, options) {
         return __awaiter(this, void 0, void 0, function* () {
             const args = ['-c', 'protocol.version=2', 'fetch'];
-            if (!refSpec.some(x => x === refHelper.tagsRefSpec)) {
+            if (!refSpec.some(x => x === refHelper.tagsRefSpec) && !options.fetchTags) {
                 args.push('--no-tags');
             }
             args.push('--prune', '--progress', '--no-recurse-submodules');
@@ -718,8 +718,8 @@ class GitCommandManager {
     }
     log1(format) {
         return __awaiter(this, void 0, void 0, function* () {
-            var args = format ? ['log', '-1', format] : ['log', '-1'];
-            var silent = format ? false : true;
+            const args = format ? ['log', '-1', format] : ['log', '-1'];
+            const silent = format ? false : true;
             const output = yield this.execGit(args, false, silent);
             return output.stdout;
         });
@@ -1256,6 +1256,7 @@ function getSource(settings) {
             }
             else {
                 fetchOptions.fetchDepth = settings.fetchDepth;
+                fetchOptions.fetchTags = settings.fetchTags;
                 const refSpec = refHelper.getRefSpec(settings.ref, settings.commit);
                 yield git.fetch(refSpec, fetchOptions);
             }
@@ -1734,6 +1735,10 @@ function getInputs() {
             result.fetchDepth = 0;
         }
         core.debug(`fetch depth = ${result.fetchDepth}`);
+        // Fetch tags
+        result.fetchTags =
+            (core.getInput('fetch-tags') || 'false').toUpperCase() === 'TRUE';
+        core.debug(`fetch tags = ${result.fetchTags}`);
         // LFS
         result.lfs = (core.getInput('lfs') || 'false').toUpperCase() === 'TRUE';
         core.debug(`lfs = ${result.lfs}`);
diff --git a/src/git-command-manager.ts b/src/git-command-manager.ts
index e684dba..6ab807b 100644
--- a/src/git-command-manager.ts
+++ b/src/git-command-manager.ts
@@ -33,6 +33,7 @@ export interface IGitCommandManager {
     options: {
       filter?: string
       fetchDepth?: number
+      fetchTags?: boolean
     }
   ): Promise<void>
   getDefaultBranch(repositoryUrl: string): Promise<string>
@@ -240,10 +241,10 @@ class GitCommandManager {
 
   async fetch(
     refSpec: string[],
-    options: {filter?: string; fetchDepth?: number}
+    options: {filter?: string; fetchDepth?: number; fetchTags?: boolean}
   ): Promise<void> {
     const args = ['-c', 'protocol.version=2', 'fetch']
-    if (!refSpec.some(x => x === refHelper.tagsRefSpec)) {
+    if (!refSpec.some(x => x === refHelper.tagsRefSpec) && !options.fetchTags) {
       args.push('--no-tags')
     }
 
@@ -333,8 +334,8 @@ class GitCommandManager {
   }
 
   async log1(format?: string): Promise<string> {
-    var args = format ? ['log', '-1', format] : ['log', '-1']
-    var silent = format ? false : true
+    const args = format ? ['log', '-1', format] : ['log', '-1']
+    const silent = format ? false : true
     const output = await this.execGit(args, false, silent)
     return output.stdout
   }
diff --git a/src/git-source-provider.ts b/src/git-source-provider.ts
index 8f9d63f..042563e 100644
--- a/src/git-source-provider.ts
+++ b/src/git-source-provider.ts
@@ -153,7 +153,11 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
 
     // Fetch
     core.startGroup('Fetching the repository')
-    const fetchOptions: {filter?: string; fetchDepth?: number} = {}
+    const fetchOptions: {
+      filter?: string
+      fetchDepth?: number
+      fetchTags?: boolean
+    } = {}
     if (settings.sparseCheckout) fetchOptions.filter = 'blob:none'
     if (settings.fetchDepth <= 0) {
       // Fetch all branches and tags
@@ -171,6 +175,7 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
       }
     } else {
       fetchOptions.fetchDepth = settings.fetchDepth
+      fetchOptions.fetchTags = settings.fetchTags
       const refSpec = refHelper.getRefSpec(settings.ref, settings.commit)
       await git.fetch(refSpec, fetchOptions)
     }
diff --git a/src/git-source-settings.ts b/src/git-source-settings.ts
index 3272e63..9875d8d 100644
--- a/src/git-source-settings.ts
+++ b/src/git-source-settings.ts
@@ -44,6 +44,11 @@ export interface IGitSourceSettings {
    */
   fetchDepth: number
 
+  /**
+   * Fetch tags, even if fetchDepth > 0 (default: false)
+   */
+  fetchTags: boolean
+
   /**
    * Indicates whether to fetch LFS objects
    */
diff --git a/src/input-helper.ts b/src/input-helper.ts
index 410e480..631fbdb 100644
--- a/src/input-helper.ts
+++ b/src/input-helper.ts
@@ -100,6 +100,11 @@ export async function getInputs(): Promise<IGitSourceSettings> {
   }
   core.debug(`fetch depth = ${result.fetchDepth}`)
 
+  // Fetch tags
+  result.fetchTags =
+    (core.getInput('fetch-tags') || 'false').toUpperCase() === 'TRUE'
+  core.debug(`fetch tags = ${result.fetchTags}`)
+
   // LFS
   result.lfs = (core.getInput('lfs') || 'false').toUpperCase() === 'TRUE'
   core.debug(`lfs = ${result.lfs}`)