From e60e2497774ee58cb8a4afaecb7d42934f3dd034 Mon Sep 17 00:00:00 2001
From: Diego Dompe <ddompe@gmail.com>
Date: Fri, 30 Jun 2023 15:14:41 -0600
Subject: [PATCH] Add support for reference repository parameter

---
 README.md                             |  5 +++++
 __test__/git-auth-helper.test.ts      |  4 +++-
 __test__/git-directory-helper.test.ts |  1 +
 __test__/input-helper.test.ts         |  1 +
 action.yml                            |  6 ++++++
 dist/index.js                         | 20 ++++++++++++++++++++
 src/git-command-manager.ts            | 10 ++++++++++
 src/git-source-provider.ts            | 12 ++++++++++++
 src/git-source-settings.ts            |  5 +++++
 src/input-helper.ts                   |  3 +++
 10 files changed, 66 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 5427a50..de28269 100644
--- a/README.md
+++ b/README.md
@@ -87,6 +87,11 @@ When Git 2.18 or higher is not in your PATH, falls back to the REST API to downl
     # Default: 1
     fetch-depth: ''
 
+    # Path to a local bare git [reference repository to minimize network
+    # usage](https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---reference-if-ableltrepositorygt).
+    # If the directory doesn't exists this option will ignore it and log a message.
+    reference: ''
+
     # 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..bba2a87 100644
--- a/__test__/git-auth-helper.test.ts
+++ b/__test__/git-auth-helper.test.ts
@@ -762,6 +762,7 @@ async function setup(testName: string): Promise<void> {
     lfsInstall: jest.fn(),
     log1: jest.fn(),
     remoteAdd: jest.fn(),
+    referenceAdd: jest.fn(),
     removeEnvironmentVariable: jest.fn((name: string) => delete git.env[name]),
     revParse: jest.fn(),
     setEnvironmentVariable: jest.fn((name: string, value: string) => {
@@ -818,7 +819,8 @@ async function setup(testName: string): Promise<void> {
     sshStrict: true,
     workflowOrganizationId: 123456,
     setSafeDirectory: true,
-    githubServerUrl: githubServerUrl
+    githubServerUrl: githubServerUrl,
+    reference: '/some/path'
   }
 }
 
diff --git a/__test__/git-directory-helper.test.ts b/__test__/git-directory-helper.test.ts
index 362133f..8e25354 100644
--- a/__test__/git-directory-helper.test.ts
+++ b/__test__/git-directory-helper.test.ts
@@ -477,6 +477,7 @@ async function setup(testName: string): Promise<void> {
     lfsInstall: jest.fn(),
     log1: jest.fn(),
     remoteAdd: jest.fn(),
+    referenceAdd: jest.fn(),
     removeEnvironmentVariable: jest.fn(),
     revParse: jest.fn(),
     setEnvironmentVariable: jest.fn(),
diff --git a/__test__/input-helper.test.ts b/__test__/input-helper.test.ts
index 069fda4..a8aeb81 100644
--- a/__test__/input-helper.test.ts
+++ b/__test__/input-helper.test.ts
@@ -88,6 +88,7 @@ describe('input-helper tests', () => {
     expect(settings.repositoryOwner).toBe('some-owner')
     expect(settings.repositoryPath).toBe(gitHubWorkspace)
     expect(settings.setSafeDirectory).toBe(true)
+    expect(settings.reference).toBe(undefined)
   })
 
   it('qualifies ref', async () => {
diff --git a/action.yml b/action.yml
index e562b56..d8b2674 100644
--- a/action.yml
+++ b/action.yml
@@ -65,6 +65,12 @@ inputs:
   fetch-depth:
     description: 'Number of commits to fetch. 0 indicates all history for all branches and tags.'
     default: 1
+  reference:
+    required: false
+    description: >
+      Path to a local bare git [reference repository to minimize network usage](https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---reference-if-ableltrepositorygt).
+      
+      If the directory doesn't exists this option will ignore it and log a message.
   lfs:
     description: 'Whether to download Git-LFS files'
     default: false
diff --git a/dist/index.js b/dist/index.js
index 4556295..6c063e0 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -729,6 +729,13 @@ class GitCommandManager {
             yield this.execGit(['remote', 'add', remoteName, remoteUrl]);
         });
     }
+    referenceAdd(alternateObjects) {
+        return __awaiter(this, void 0, void 0, function* () {
+            const alternatePath = path.join(this.workingDirectory, '.git/objects/info/alternates');
+            core.info(`Adding a git alternate to reference repo at ${alternateObjects}`);
+            yield fs.promises.writeFile(alternatePath, `${alternateObjects}\n`);
+        });
+    }
     removeEnvironmentVariable(name) {
         delete this.gitEnv[name];
     }
@@ -1208,6 +1215,17 @@ function getSource(settings) {
                 yield git.init();
                 yield git.remoteAdd('origin', repositoryUrl);
                 core.endGroup();
+                if (settings.reference !== undefined) {
+                    const alternateObjects = path.join(settings.reference, '/objects');
+                    if (fsHelper.directoryExistsSync(alternateObjects, false)) {
+                        core.startGroup('Adding a reference repository');
+                        yield git.referenceAdd(alternateObjects);
+                        core.endGroup();
+                    }
+                    else {
+                        core.warning(`Reference repository was specified, but directory ${alternateObjects} does not exists`);
+                    }
+                }
             }
             // Disable automatic garbage collection
             core.startGroup('Disabling automatic garbage collection');
@@ -1768,6 +1786,8 @@ function getInputs() {
         // Determine the GitHub URL that the repository is being hosted from
         result.githubServerUrl = core.getInput('github-server-url');
         core.debug(`GitHub Host URL = ${result.githubServerUrl}`);
+        result.reference = core.getInput('reference');
+        core.debug(`Reference repository = ${result.reference}`);
         return result;
     });
 }
diff --git a/src/git-command-manager.ts b/src/git-command-manager.ts
index e684dba..eb3eda6 100644
--- a/src/git-command-manager.ts
+++ b/src/git-command-manager.ts
@@ -43,6 +43,7 @@ export interface IGitCommandManager {
   lfsInstall(): Promise<void>
   log1(format?: string): Promise<string>
   remoteAdd(remoteName: string, remoteUrl: string): Promise<void>
+  referenceAdd(reference: string): Promise<void>
   removeEnvironmentVariable(name: string): void
   revParse(ref: string): Promise<string>
   setEnvironmentVariable(name: string, value: string): void
@@ -343,6 +344,15 @@ class GitCommandManager {
     await this.execGit(['remote', 'add', remoteName, remoteUrl])
   }
 
+  async referenceAdd(alternateObjects: string): Promise<void> {
+    const alternatePath = path.join(
+      this.workingDirectory,
+      '.git/objects/info/alternates'
+    )
+    core.info(`Adding a git alternate to reference repo at ${alternateObjects}`)
+    await fs.promises.writeFile(alternatePath, `${alternateObjects}\n`)
+  }
+
   removeEnvironmentVariable(name: string): void {
     delete this.gitEnv[name]
   }
diff --git a/src/git-source-provider.ts b/src/git-source-provider.ts
index 8f9d63f..be10659 100644
--- a/src/git-source-provider.ts
+++ b/src/git-source-provider.ts
@@ -110,6 +110,18 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
       await git.init()
       await git.remoteAdd('origin', repositoryUrl)
       core.endGroup()
+
+      if (settings.reference !== undefined) {
+        const alternateObjects = path.join(settings.reference, '/objects')
+
+        if (fsHelper.directoryExistsSync(alternateObjects, false)) {
+          core.startGroup('Adding a reference repository')
+          await git.referenceAdd(alternateObjects)
+          core.endGroup()
+        } else {
+          core.warning(`Reference repository was specified, but directory ${alternateObjects} does not exists`);
+        }
+      }
     }
 
     // Disable automatic garbage collection
diff --git a/src/git-source-settings.ts b/src/git-source-settings.ts
index 3272e63..30e0cd1 100644
--- a/src/git-source-settings.ts
+++ b/src/git-source-settings.ts
@@ -44,6 +44,11 @@ export interface IGitSourceSettings {
    */
   fetchDepth: number
 
+  /**
+   * The local reference repository
+   */
+  reference: string | undefined
+
   /**
    * Indicates whether to fetch LFS objects
    */
diff --git a/src/input-helper.ts b/src/input-helper.ts
index 410e480..7c458b7 100644
--- a/src/input-helper.ts
+++ b/src/input-helper.ts
@@ -141,5 +141,8 @@ export async function getInputs(): Promise<IGitSourceSettings> {
   result.githubServerUrl = core.getInput('github-server-url')
   core.debug(`GitHub Host URL = ${result.githubServerUrl}`)
 
+  result.reference = core.getInput('reference')
+  core.debug(`Reference repository = ${result.reference}`)
+
   return result
 }