import * as TT from 'typings/tutorial' import { exec, exists } from '../node' import logger from '../logger' const gitOrigin = 'coderoad' const stashAllFiles = async (): Promise => { // stash files including untracked (eg. newly created file) const { stdout, stderr } = await exec(`git stash --include-untracked`) if (stderr) { console.error(stderr) throw new Error('Error stashing files') } } const cherryPickCommit = async (commit: string, count = 0): Promise => { if (count > 1) { console.warn('cherry-pick failed') return } try { // cherry-pick pulls commits from another branch // -X theirs merges and accepts incoming changes over existing changes const { stdout } = await exec(`git cherry-pick -X theirs ${commit}`) if (!stdout) { throw new Error('No cherry-pick output') } } catch (error) { console.log('cherry-pick-commit failed') // stash all files if cherry-pick fails await stashAllFiles() return cherryPickCommit(commit, ++count) } } /* SINGLE git cherry-pick %COMMIT% if fails, will stash all and retry */ export function loadCommit(commit: string): Promise { return cherryPickCommit(commit) } /* save commit git commit -am '${level}/${step} complete' */ export async function saveCommit(message: string): Promise { const { stdout, stderr } = await exec(`git commit -am '${message}'`) if (stderr) { console.error(stderr) throw new Error('Error saving progress to Git') } logger(['save with commit & continue stdout', stdout]) } export async function clear(): Promise { try { // commit progress to git const { stderr } = await exec('git reset HEAD --hard && git clean -fd') if (!stderr) { return } console.error(stderr) } catch (error) { console.error(error) } throw new Error('Error cleaning up current unsaved work') } async function init(): Promise { const { stderr } = await exec('git init') if (stderr) { throw new Error('Error initializing Git') } } export async function initIfNotExists(): Promise { const hasGitInit = exists('.git') if (!hasGitInit) { await init() } } export async function checkRemoteConnects(repo: TT.TutorialRepo): Promise { // check for git repo const externalRepoExists = await exec(`git ls-remote --exit-code --heads ${repo.uri}`) if (externalRepoExists.stderr) { // no repo found or no internet connection throw new Error(externalRepoExists.stderr) } // check for git repo branch const { stderr, stdout } = await exec(`git ls-remote --exit-code --heads ${repo.uri} ${repo.branch}`) if (stderr) { throw new Error(stderr) } if (!stdout || !stdout.length) { throw new Error('Tutorial branch does not exist') } } export async function addRemote(repo: string): Promise { const { stderr } = await exec(`git remote add ${gitOrigin} ${repo} && git fetch ${gitOrigin}`) if (stderr) { const alreadyExists = stderr.match(`${gitOrigin} already exists.`) const successfulNewBranch = stderr.match('new branch') // validate the response is acceptable if (!alreadyExists && !successfulNewBranch) { console.error(stderr) throw new Error('Error adding git remote') } } } export async function checkRemoteExists(): Promise { try { const { stdout, stderr } = await exec('git remote -v') if (stderr) { return false } // string match on remote output // TODO improve the specificity of this regex return !!stdout.match(gitOrigin) } catch (error) { return false } } export async function setupCodeRoadRemote(repo: string): Promise { // check coderoad remote not taken const hasRemote = await checkRemoteExists() // git remote add coderoad tutorial // git fetch coderoad if (hasRemote) { throw new Error('A CodeRoad remote is already configured') } await addRemote(repo) }