/ src / git-utils.ts
git-utils.ts
  1  import * as core from '@actions/core';
  2  import * as exec from '@actions/exec';
  3  import * as glob from '@actions/glob';
  4  import path from 'path';
  5  import fs from 'fs';
  6  import {URL} from 'url';
  7  import {Inputs, CmdResult} from './interfaces';
  8  import {createDir} from './utils';
  9  import {cp, rm} from 'shelljs';
 10  
 11  export async function createBranchForce(branch: string): Promise<void> {
 12    await exec.exec('git', ['init']);
 13    await exec.exec('git', ['checkout', '--orphan', branch]);
 14    return;
 15  }
 16  
 17  export function getServerUrl(): URL {
 18    return new URL(process.env['GITHUB_SERVER_URL'] || 'https://github.com');
 19  }
 20  
 21  export async function deleteExcludedAssets(destDir: string, excludeAssets: string): Promise<void> {
 22    if (excludeAssets === '') return;
 23    core.info(`[INFO] delete excluded assets`);
 24    const excludedAssetNames: Array<string> = excludeAssets.split(',');
 25    const excludedAssetPaths = ((): Array<string> => {
 26      const paths: Array<string> = [];
 27      for (const pattern of excludedAssetNames) {
 28        paths.push(path.join(destDir, pattern));
 29      }
 30      return paths;
 31    })();
 32    const globber = await glob.create(excludedAssetPaths.join('\n'));
 33    const files = await globber.glob();
 34    for await (const file of globber.globGenerator()) {
 35      core.info(`[INFO] delete ${file}`);
 36    }
 37    rm('-rf', files);
 38    return;
 39  }
 40  
 41  export async function copyAssets(
 42    publishDir: string,
 43    destDir: string,
 44    excludeAssets: string
 45  ): Promise<void> {
 46    core.info(`[INFO] prepare publishing assets`);
 47  
 48    if (!fs.existsSync(destDir)) {
 49      core.info(`[INFO] create ${destDir}`);
 50      await createDir(destDir);
 51    }
 52  
 53    const dotGitPath = path.join(publishDir, '.git');
 54    if (fs.existsSync(dotGitPath)) {
 55      core.info(`[INFO] delete ${dotGitPath}`);
 56      rm('-rf', dotGitPath);
 57    }
 58  
 59    core.info(`[INFO] copy ${publishDir} to ${destDir}`);
 60    cp('-RfL', [`${publishDir}/*`, `${publishDir}/.*`], destDir);
 61  
 62    await deleteExcludedAssets(destDir, excludeAssets);
 63  
 64    return;
 65  }
 66  
 67  export async function setRepo(inps: Inputs, remoteURL: string, workDir: string): Promise<void> {
 68    const publishDir = path.isAbsolute(inps.PublishDir)
 69      ? inps.PublishDir
 70      : path.join(`${process.env.GITHUB_WORKSPACE}`, inps.PublishDir);
 71  
 72    if (path.isAbsolute(inps.DestinationDir)) {
 73      throw new Error('destination_dir should be a relative path');
 74    }
 75    const destDir = ((): string => {
 76      if (inps.DestinationDir === '') {
 77        return workDir;
 78      } else {
 79        return path.join(workDir, inps.DestinationDir);
 80      }
 81    })();
 82  
 83    core.info(`[INFO] ForceOrphan: ${inps.ForceOrphan}`);
 84    if (inps.ForceOrphan) {
 85      await createDir(destDir);
 86      core.info(`[INFO] chdir ${workDir}`);
 87      process.chdir(workDir);
 88      await createBranchForce(inps.PublishBranch);
 89      await copyAssets(publishDir, destDir, inps.ExcludeAssets);
 90      return;
 91    }
 92  
 93    const result: CmdResult = {
 94      exitcode: 0,
 95      output: ''
 96    };
 97    const options = {
 98      listeners: {
 99        stdout: (data: Buffer): void => {
100          result.output += data.toString();
101        }
102      }
103    };
104  
105    try {
106      result.exitcode = await exec.exec(
107        'git',
108        ['clone', '--depth=1', '--single-branch', '--branch', inps.PublishBranch, remoteURL, workDir],
109        options
110      );
111      if (result.exitcode === 0) {
112        await createDir(destDir);
113  
114        if (inps.KeepFiles) {
115          core.info('[INFO] Keep existing files');
116        } else {
117          core.info(`[INFO] clean up ${destDir}`);
118          core.info(`[INFO] chdir ${destDir}`);
119          process.chdir(destDir);
120          await exec.exec('git', ['rm', '-r', '--ignore-unmatch', '*']);
121        }
122  
123        core.info(`[INFO] chdir ${workDir}`);
124        process.chdir(workDir);
125        await copyAssets(publishDir, destDir, inps.ExcludeAssets);
126        return;
127      } else {
128        throw new Error(`Failed to clone remote branch ${inps.PublishBranch}`);
129      }
130    } catch (e) {
131      core.info(`[INFO] first deployment, create new branch ${inps.PublishBranch}`);
132      core.info(`[INFO] ${e.message}`);
133      await createDir(destDir);
134      core.info(`[INFO] chdir ${workDir}`);
135      process.chdir(workDir);
136      await createBranchForce(inps.PublishBranch);
137      await copyAssets(publishDir, destDir, inps.ExcludeAssets);
138      return;
139    }
140  }
141  
142  export function getUserName(userName: string): string {
143    if (userName) {
144      return userName;
145    } else {
146      return `${process.env.GITHUB_ACTOR}`;
147    }
148  }
149  
150  export function getUserEmail(userEmail: string): string {
151    if (userEmail) {
152      return userEmail;
153    } else {
154      return `${process.env.GITHUB_ACTOR}@users.noreply.github.com`;
155    }
156  }
157  
158  export async function setCommitAuthor(userName: string, userEmail: string): Promise<void> {
159    if (userName && !userEmail) {
160      throw new Error('user_email is undefined');
161    }
162    if (!userName && userEmail) {
163      throw new Error('user_name is undefined');
164    }
165    await exec.exec('git', ['config', 'user.name', getUserName(userName)]);
166    await exec.exec('git', ['config', 'user.email', getUserEmail(userEmail)]);
167  }
168  
169  export function getCommitMessage(
170    msg: string,
171    fullMsg: string,
172    extRepo: string,
173    baseRepo: string,
174    hash: string
175  ): string {
176    const msgHash = ((): string => {
177      if (extRepo) {
178        return `${baseRepo}@${hash}`;
179      } else {
180        return hash;
181      }
182    })();
183  
184    const subject = ((): string => {
185      if (fullMsg) {
186        return fullMsg;
187      } else if (msg) {
188        return `${msg} ${msgHash}`;
189      } else {
190        return `deploy: ${msgHash}`;
191      }
192    })();
193  
194    return subject;
195  }
196  
197  export async function commit(allowEmptyCommit: boolean, msg: string): Promise<void> {
198    try {
199      if (allowEmptyCommit) {
200        await exec.exec('git', ['commit', '--allow-empty', '-m', `${msg}`]);
201      } else {
202        await exec.exec('git', ['commit', '-m', `${msg}`]);
203      }
204    } catch (e) {
205      core.info('[INFO] skip commit');
206      core.debug(`[INFO] skip commit ${e.message}`);
207    }
208  }
209  
210  export async function push(branch: string, forceOrphan: boolean): Promise<void> {
211    if (forceOrphan) {
212      await exec.exec('git', ['push', 'origin', '--force', branch]);
213    } else {
214      await exec.exec('git', ['push', 'origin', branch]);
215    }
216  }
217  
218  export async function pushTag(tagName: string, tagMessage: string): Promise<void> {
219    if (tagName === '') {
220      return;
221    }
222  
223    let msg = '';
224    if (tagMessage) {
225      msg = tagMessage;
226    } else {
227      msg = `Deployment ${tagName}`;
228    }
229  
230    await exec.exec('git', ['tag', '-a', `${tagName}`, '-m', `${msg}`]);
231    await exec.exec('git', ['push', 'origin', `${tagName}`]);
232  }