UNPKG

@atomist/automation-client

Version:
317 lines • 13.1 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const _ = require("lodash"); const promiseRetry = require("promise-retry"); const ActionResult_1 = require("../../action/ActionResult"); const commandLine_1 = require("../../action/cli/commandLine"); const logger_1 = require("../../internal/util/logger"); const RepoId_1 = require("../../operations/common/RepoId"); const DirectoryManager_1 = require("../../spi/clone/DirectoryManager"); const tmpDirectoryManager_1 = require("../../spi/clone/tmpDirectoryManager"); const LocalProject_1 = require("../local/LocalProject"); const NodeFsLocalProject_1 = require("../local/NodeFsLocalProject"); const gitStatus_1 = require("./gitStatus"); exports.DefaultDirectoryManager = tmpDirectoryManager_1.TmpDirectoryManager; /** * Implements GitProject interface using the Git binary from the command line. * Works only if git is installed. */ class GitCommandGitProject extends NodeFsLocalProject_1.NodeFsLocalProject { constructor(id, baseDir, credentials, release, provenance) { super(id, baseDir, release); this.baseDir = baseDir; this.credentials = credentials; this.provenance = provenance; this.newRepo = false; this.branch = id.sha; logger_1.logger.debug(`Created GitProject`); } static fromProject(p, credentials) { if (LocalProject_1.isLocalProject(p)) { return GitCommandGitProject.fromBaseDir(p.id, p.baseDir, credentials, () => Promise.resolve()); } throw new Error(`Project ${p.name} doesn't have a local directory`); } /** * Create a project from an existing git directory * @param {RepoRef} id * @param {string} baseDir * @param {ProjectOperationCredentials} credentials * @param release call this when you're done with the project. make its filesystem resources available to others. * @param provenance optional; for debugging, describe how this was constructed * @return {GitCommandGitProject} */ static fromBaseDir(id, baseDir, credentials, release, provenance) { return new GitCommandGitProject(id, baseDir, credentials, release, provenance); } /** * Create a new GitCommandGitProject by cloning the given remote project * @param {ProjectOperationCredentials} credentials * @param {RemoteRepoRef} id * @param {CloneOptions} opts * @param {DirectoryManager} directoryManager * @return {Promise<GitCommandGitProject>} */ static cloned(credentials, id, opts = DirectoryManager_1.DefaultCloneOptions, directoryManager = exports.DefaultDirectoryManager) { return clone(credentials, id, opts, directoryManager) .then(p => { if (!!id.path) { const pathInsideRepo = id.path.startsWith("/") ? id.path : "/" + id.path; // not sure this will work with cached const gp = GitCommandGitProject.fromBaseDir(id, p.baseDir + pathInsideRepo, credentials, () => p.release(), p.provenance + "\ncopied into one with extra path " + id.path); return gp; } else { return p; } }); } init() { this.newRepo = true; this.branch = "master"; return this.runCommandInCurrentWorkingDirectory("git init"); } isClean() { return this.runCommandInCurrentWorkingDirectory("git status --porcelain") .then(commandResult => { return Object.assign({}, commandResult, { success: commandResult.stdout !== undefined && commandResult.stdout === "" }); }); } gitStatus() { return gitStatus_1.runStatusIn(this.baseDir); } /** * Remote is of form https://github.com/USERNAME/REPOSITORY.git * @param remote */ setRemote(remote) { this.remote = remote; return this.runCommandInCurrentWorkingDirectory(`git remote add origin ${remote}`); } setUserConfig(user, email) { return this.runCommandInCurrentWorkingDirectory(`git config user.name "${user}"`) .then(() => this.runCommandInCurrentWorkingDirectory(`git config user.email "${email}"`)); } createAndSetRemote(gid, description = gid.repo, visibility) { this.id = gid; return gid.createRemote(this.credentials, description, visibility) .then(res => { if (res.success) { logger_1.logger.debug(`Repo created ok`); return this.setRemote(gid.cloneUrl(this.credentials)); } else { return Promise.reject(res.error); } }); } configureFromRemote() { if (RepoId_1.isRemoteRepoRef(this.id)) { return this.id.setUserConfig(this.credentials, this); } return Promise.resolve(ActionResult_1.successOn(this)); } /** * Raise a PR after a push to this branch * @param title * @param body */ raisePullRequest(title, body = name, targetBranch = "master") { if (!this.branch) { throw new Error("Cannot create a PR: no branch has been created"); } if (!RepoId_1.isRemoteRepoRef(this.id)) { throw new Error("No remote in " + JSON.stringify(this.id)); } return this.id.raisePullRequest(this.credentials, title, body, this.branch, targetBranch) .then(() => ActionResult_1.successOn(this)); } /** * `git add .` and `git commit` * @param {string} message * @returns {Promise<CommandResult<this>>} */ commit(message) { return this.runCommandInCurrentWorkingDirectory(`git add .`) .then(() => { const escapedMessage = message.replace(/"/g, `\\"`); const command = `git commit -a -m "${escapedMessage}"`; return this.runCommandInCurrentWorkingDirectory(command); }); } /** * Check out a particular commit. We'll end in detached head state * @param sha * @return {any} */ checkout(sha) { return this.runCommandInCurrentWorkingDirectory(`git checkout ${sha} --`) .then(res => { this.branch = sha; return res; }); } /** * Revert all changes since last commit * @return {any} */ revert() { return __awaiter(this, void 0, void 0, function* () { return clean(this.baseDir); }); } push(options) { let gitPushCmd = "git push"; _.forOwn(options, (v, k) => { const opt = k.replace(/_/g, "-"); if (typeof v === "boolean") { if (v === false) { gitPushCmd += ` --no-${opt}`; } else { gitPushCmd += ` --${opt}`; } } else if (typeof v === "string") { gitPushCmd += ` --${opt}=${v}`; } else { return Promise.reject(new Error(`Unknown option key type for ${k}: ${typeof v}`)); } }); if (!!this.branch && !!this.remote) { // We need to set the remote gitPushCmd += ` ${this.remote} ${this.branch}`; } else { gitPushCmd += ` --set-upstream origin ${this.branch}`; } return this.runCommandInCurrentWorkingDirectory(gitPushCmd) .catch(err => { err.message = `Unable to push '${gitPushCmd}': ${err.message}`; logger_1.logger.error(err.message); return Promise.reject(err); }); } createBranch(name) { return this.runCommandInCurrentWorkingDirectory(`git branch ${name}`) .then(() => this.runCommandInCurrentWorkingDirectory(`git checkout ${name} --`)) .then(res => { this.branch = name; return res; }); } hasBranch(name) { return this.runCommandInCurrentWorkingDirectory(`git branch --list ${name}`) .then(commandResult => { if (commandResult.success && commandResult.stdout.includes(name)) { return Promise.resolve(true); } else if (commandResult.success) { return Promise.resolve(false); } else { return Promise.reject(new Error(`command <git branch --list ${name}> failed: ${commandResult.stderr}`)); } }); } runCommandInCurrentWorkingDirectory(cmd) { return commandLine_1.runCommand(cmd, { cwd: this.baseDir }) .then(result => { return Object.assign({ target: this }, result); }); } } exports.GitCommandGitProject = GitCommandGitProject; /** * Clone the given repo from GitHub * @param credentials git provider credentials * @param id remote repo ref * @param opts options for clone * @param directoryManager strategy for cloning */ function clone(credentials, id, opts, directoryManager, secondTry = false) { return directoryManager.directoryFor(id.owner, id.repo, id.sha, opts) .then(cloneDirectoryInfo => { switch (cloneDirectoryInfo.type) { case "empty-directory": return cloneInto(credentials, cloneDirectoryInfo, opts, id); case "existing-directory": const repoDir = cloneDirectoryInfo.path; return resetOrigin(repoDir, credentials, id) .then(() => checkout(repoDir, id.sha)) .then(() => clean(repoDir)) .then(() => { return GitCommandGitProject.fromBaseDir(id, repoDir, credentials, cloneDirectoryInfo.release, cloneDirectoryInfo.provenance + "\nRe-using existing clone"); }, error => { return cloneDirectoryInfo.invalidate().then(() => { if (secondTry) { throw error; } else { return clone(credentials, id, opts, directoryManager, true); } }); }); default: throw new Error("What is this type: " + cloneDirectoryInfo.type); } }); } function cloneInto(credentials, targetDirectoryInfo, opts, id) { const repoDir = targetDirectoryInfo.path; const url = id.cloneUrl(credentials); const command = (!opts.alwaysDeep && id.sha === "master" && targetDirectoryInfo.transient) ? runIn(".", `git clone --depth 1 ${url} ${repoDir}`) : runIn(".", `git clone ${url} ${repoDir}`) .then(() => runIn(repoDir, `git checkout ${id.sha} --`)); const cleanUrl = url.replace(/\/\/.*:x-oauth-basic/, "//TOKEN:x-oauth-basic"); logger_1.logger.debug(`Cloning repo '${cleanUrl}' in '${repoDir}'`); const retryOptions = { retries: 4, factor: 2, minTimeout: 100, maxTimeout: 500, randomize: false, }; return promiseRetry(retryOptions, (retry, count) => { return command .catch(err => { logger_1.logger.debug(`clone of ${id.owner}/${id.repo} attempt ${count} failed`); retry(err); }); }) .then(() => { logger_1.logger.debug(`Clone succeeded with URL '${cleanUrl}'`); return GitCommandGitProject.fromBaseDir(id, repoDir, credentials, targetDirectoryInfo.release, targetDirectoryInfo.provenance + "\nfreshly cloned"); }); } function resetOrigin(repoDir, credentials, id) { return runIn(repoDir, `git remote set origin ${id.cloneUrl(credentials)}`); } function checkout(repoDir, branch) { return pwd(repoDir) .then(() => runIn(repoDir, `git fetch origin ${branch}`)) .then(() => runIn(repoDir, `git checkout ${branch} --`)) .then(() => runIn(repoDir, `git reset --hard origin/${branch}`)); } function clean(repoDir) { return pwd(repoDir) .then(() => runIn(repoDir, "git clean -dfx")) // also removes ignored files .then(result => runIn(repoDir, "git checkout -- .")); } function runIn(baseDir, command) { return commandLine_1.runCommand(command, { cwd: baseDir }); } function pwd(baseDir) { return commandLine_1.runCommand("pwd", { cwd: baseDir }).then(result => console.log(result.stdout)); } //# sourceMappingURL=GitCommandGitProject.js.map