@atomist/automation-client
Version:
Atomist API for software low-level client
317 lines • 13.1 kB
JavaScript
;
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