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 }