import * as O from 'fp-ts/Option';
import { gitP, PushResult, SimpleGit } from 'simple-git';
import { mainBranchName } from '../core/BranchLogic';
import * as Files from './Files';
import * as ObjUtils from './ObjUtils';
import * as PromiseUtils from './PromiseUtils';
import * as StringUtils from './StringUtils';
type Option = O.Option;
const ASSUMED_REMOTE = 'origin'; // This module assumes a single remote called 'origin'
export interface TempGit {
readonly git: SimpleGit;
readonly dir: string;
}
export const initInTempFolder = async (bare: boolean = false): Promise => {
const dir = await Files.tempFolder();
const git = gitP(dir);
await git.init(bare);
return { dir, git };
};
export const cloneIn = async (gitUrl: string, dir: string): Promise => {
console.log(`Cloning ${gitUrl} to ${dir}`);
const git = gitP(dir);
await git.clone(gitUrl, dir);
return { dir, git };
};
export const cloneInTempFolder = async (gitUrl: string, temp: Option = O.none): Promise => {
const dir = O.isSome(temp) ? temp.value : (await Files.tempFolder());
return await cloneIn(gitUrl, dir);
};
export const checkoutNewBranch = (git: SimpleGit, branchName: string): Promise =>
git.checkout([ '-b', branchName ]);
export const currentBranch = (git: SimpleGit): Promise =>
git.branch().then((b) => b.current);
export const push = async (git: SimpleGit): Promise => {
const cur = await currentBranch(git);
return git.push(ASSUMED_REMOTE, cur, { '--set-upstream': null });
};
export const pushOneTag = async (git: SimpleGit, tagName: string): Promise =>
git.push(ASSUMED_REMOTE, tagName);
export const currentRevisionShortSha = (git: SimpleGit): Promise =>
git.revparse([ '--short', 'HEAD' ]);
export const remoteBranchNames = async (git: SimpleGit): Promise => {
const rbs = await git.branch();
return rbs.all
.filter((r) => r.startsWith('remotes/origin/'))
.map((r) => StringUtils.removeLeading(r, 'remotes/origin/'));
};
export const doesRemoteBranchExist = async (git: SimpleGit, branchName: string): Promise => {
const b = await git.branch();
return ObjUtils.hasKey(b.branches, 'remotes/origin/' + branchName);
};
const dryRunMessage = async (dir: string, git: SimpleGit): Promise => {
const curBranch = await currentBranch(git);
return `dry-run - not pushing. To complete, push "${curBranch}" branch from ${dir}`;
};
export const pushUnlessDryRun = async (dir: string, git: SimpleGit, dryRun: boolean): Promise => {
if (dryRun) {
console.log(await dryRunMessage(dir, git));
} else {
console.log('git push');
await push(git);
}
};
export const branchShouldNotExist = async (git: SimpleGit, branchName: string): Promise => {
if (await doesRemoteBranchExist(git, branchName)) {
return PromiseUtils.fail(`Remote branch already exists: ${branchName}`);
}
};
export const checkout = async (git: SimpleGit, branchName: string): Promise => {
console.log(`Checking out branch: ${branchName}`);
await git.checkout(branchName);
return branchName;
};
export const checkoutMainBranch = (git: SimpleGit): Promise =>
checkout(git, mainBranchName);
export const detectGitUrl = async (g: SimpleGit): Promise => {
const remotes = await g.getRemotes(true);
if (remotes.length === 1) {
return remotes[0].refs.fetch;
} else if (remotes.length === 0) {
return PromiseUtils.fail('Could not detect git url - the repo has no remotes.');
} else {
const res = remotes.find((r) => r.name === 'origin');
if (res !== undefined) {
return res.refs.fetch;
} else {
return PromiseUtils.fail('Could not detect git url - there were multiple remotes and none were called "origin"');
}
}
};
const detectGitUrlFromDir = async (dir: string): Promise => {
const g = gitP(dir);
return await detectGitUrl(g);
};
export const resolveGitUrl = async (gitUrlArg: Option, workingDirArg: string): Promise =>
O.isSome(gitUrlArg) ? gitUrlArg.value : await detectGitUrlFromDir(workingDirArg);
const isWorkingDirDirty = async (git: SimpleGit) => {
const diff = await git.diffSummary([ 'HEAD' ]);
return diff.changed > 0;
};
const isAheadOfRemote = async (git: SimpleGit, branchName: string) => {
await git.fetch();
const log = await git.log({ from: `origin/${branchName}`, to: branchName, symmetric: false });
return log.total > 0;
};
export const hasLocalChanges = async (workingDir: string, branchName: string) => {
const git = gitP(workingDir);
const isAhead = await isAheadOfRemote(git, branchName);
const isDirty = await isWorkingDirDirty(git);
return isDirty || isAhead;
};
export const getTags = async (g: SimpleGit): Promise => {
const tags = await g.tags();
return tags.all;
};