regit-cli
Version:
git helper cli
363 lines (291 loc) • 9.72 kB
text/typescript
import { branchFeature, branchRelease, branchStable } from '../const.js'
import { git } from './git.js'
import { uniq as removeDuplicate, uniqBy } from './utils.js'
import semver from 'semver'
export const getProjectRootDirectory = async () => {
const result = await git.revparse(['--show-toplevel'])
return result
}
export const warmupGitRepo = async () => {
await git.fetch()
}
export const getCurrentBranch = async () => {
const result = await git.branch()
return result.current
}
export const pushBranch = async (branchName: string) => {
await git.push(['-u', 'origin', branchName])
}
export const getOpenReleaseBranch = async () => {
const result = await listBranchStartingWith(branchRelease)
return result[0]
}
export const assertOnSameBranch = async (branch: string) => {
const currentBranch = await getCurrentBranch()
if (branch !== currentBranch) {
throw new Error(`You are not on the branch ${branch}`)
}
}
export const assertNotOnSameBranch = async (branch: string) => {
const currentBranch = await getCurrentBranch()
if (branch === currentBranch) {
throw new Error(`You are already on the branch ${branch}`)
}
}
export const assertCurrentBranchIsClean = async () => {
const statusResult = await git.status()
const isClean = statusResult.isClean()
if (!isClean) {
throw new Error('Current branch is dirty, cannot continue')
}
}
export const getLatestTag = async () => {
const tagResult = await git.tags()
const lastTag = tagResult.latest
return lastTag
}
export const getLatestTags = async (tagCount: number) => {
const tagResult = await git.tags()
return tagResult.all.slice(-tagCount)
}
export interface ReleaseHeaderResult {
commit: string
author: string
date: string
}
export const getBranchDetails = async (branch: string) => {
const result = await git.show([branch])
const lines = result.split('\n')
return {
commit: lines[0].split(' ')[1].trim(),
author: lines[1].split(': ')[1].trim(),
date: lines[2].split(': ')[1].trim(),
} as ReleaseHeaderResult
}
export interface TagHeaderResult {
tag: string
author: string
date: string
}
export const getTagDetails = async (tag: string) => {
const result = await git.show([tag])
const lines = result.split('\n')
return {
tag,
author: lines[1].split(':')[1].trim(),
date: lines[2].split(': ')[1].trim(),
} as TagHeaderResult
}
export const createTag = async (version: string, included: string[] = []) => {
await git.addAnnotatedTag(
version,
`[regit] ${version}. (${included.join(',')})`
)
await git.push(['origin', version])
}
export const branchExists = async (branchName: string) => {
const isLocal = await localBranchExists(branchName)
const isRemote = await remoteBranchExists(branchName)
return isLocal || isRemote
}
export const localBranchExists = async (branchName: string) => {
const result = await git.branch()
const isLocal = result.all.find((branch) => branch === branchName)
return Boolean(isLocal)
}
export const remoteBranchExists = async (branchName: string) => {
const result = await git.branch()
const isRemote = result.all.find(
(branch) => branch === `remotes/origin/${branchName}`
)
return Boolean(isRemote)
}
export const initStableBranch = async () => {
await git.checkoutLocalBranch(branchStable)
}
export const pushStableBranch = async (version: string) => {
await git.raw(['add', './.regit'])
await git.raw(['commit', '-m', `[regit] Init ${version}`])
await git.push(['-u', 'origin', branchStable])
await createTag(version)
}
export interface ListBranchResult {
name: string
remoteName?: string
from: string
show: string[]
isPresentLocally: boolean
}
export const getBranchInfo = async (branchName: string) => {
const allVersionsMerged = await git.raw([
'tag',
'--merged',
`origin/${branchName}`,
])
const allVersions = allVersionsMerged.trim().split('\n')
const lastestVersionMerged = allVersions.sort(semver.rcompare)[0]
const from = lastestVersionMerged
const show = await git.show([`origin/${branchName}`])
const showDetails = show.trim().split('\n').slice(0, 3)
const isPresentLocally = await localBranchExists(branchName)
return {
name: branchName.replace('remotes/origin/', ''),
from: from.trim(),
show: showDetails,
remoteName: '',
isPresentLocally,
}
}
export const listBranchStartingWith = async (branchName: string) => {
const data: ListBranchResult[] = []
const result = await git.branch()
const allBranches = result.all
.filter(
(branch) =>
branch.startsWith(branchName) ||
branch.startsWith(`remotes/origin/${branchName}`)
)
.map((branch) => {
return branch.replace('remotes/origin/', '')
})
const branches = removeDuplicate(allBranches)
for (let i = 0; i < branches.length; i++) {
const name = branches[i]
const branchData = await getBranchInfo(name)
data.push(branchData)
}
const uniq = uniqBy(data, 'name')
return uniq
}
export const startOrCheckoutBranch = async (branchName: string) => {
const lastTag = await getLatestTag()
const doesBranchExists = await branchExists(branchName)
if (doesBranchExists) {
console.log(`Branch ${branchName} already exists`)
await git.checkout(branchName)
} else {
console.log(`Creating Branch ${branchName} from tag ${lastTag}`)
await git.checkoutBranch(branchName, `tags/${lastTag}`)
await git.commit(`[regit] Init '${branchName}'`, {
'--allow-empty': null,
})
await git.push(['-u', 'origin', branchName])
}
}
export const mergeBranch = async (branchName: string) => {
const result = await git.merge(['--no-ff', `origin/${branchName}`])
}
export interface ListBranchesInBranchResult extends ListBranchResult {
upToDate: boolean
}
export const listBranchesInBranch = async (
targetBranch: string
): Promise<ListBranchesInBranchResult[]> => {
const allCommits = await git.raw([
'reflog',
'show',
'--no-abbrev',
targetBranch,
])
const allCommitsHashes = allCommits
.trim()
.split('\n')
.map((line) => {
return line.split(' ')[0]
})
const firstCommit = allCommitsHashes[allCommitsHashes.length - 1]
const lastCommit = allCommitsHashes[0]
if (!firstCommit || !lastCommit) {
return []
}
const allCommitsOnBranch = await git.log([
targetBranch,
`${firstCommit}..${lastCommit}`,
])
const allMergedFeatureNames = allCommitsOnBranch.all
.filter((branch) => {
return branch.message.includes(branchFeature)
})
.map((branch) => {
const match = branch.message.match(/'(.*)'/)
if (match) {
return match[1]
}
})
.filter(Boolean)
.map((name) => {
return name!.replace('origin/', '')
})
// get merged and up to date branches
const allCompletelyMergedFeatures = await git.branch([
'--no-color',
'-r',
'--merged',
targetBranch,
])
const allCompletelyMergedFeatureNames = allCompletelyMergedFeatures.all
.filter((name) => {
return name.includes(branchFeature)
})
.map((name) => {
return name.replace('origin/', '')
})
const allPartiallyMergedFeatureNames = allMergedFeatureNames.filter(
(name) => {
return !allCompletelyMergedFeatureNames.includes(name!)
}
)
const allFeatures = await listBranchStartingWith(branchFeature)
const mergedBranches = allFeatures.filter((feature) => {
return allCompletelyMergedFeatureNames.includes(feature.name)
})
const partiallyMergedBranches = allFeatures.filter((feature) => {
return allPartiallyMergedFeatureNames.includes(feature.name)
})
const result: ListBranchesInBranchResult[] = [
...mergedBranches.map((feature) => {
return {
...feature,
upToDate: true,
}
}),
...partiallyMergedBranches.map((feature) => {
return {
...feature,
upToDate: false,
}
}),
]
return result
}
export const listBranchesBetweenTags = async (tag1: string, tag2: string) => {
const result = await git.raw([
'log',
'--no-merges',
'--pretty=format:"%s"',
'--abbrev-commit',
`${tag1}..${tag2}`,
])
const branches = [...result.matchAll(/\[regit\] Init '(.*)'/g)].reduce<
string[]
>((acc, current) => {
const branchName = current[1] as string
acc.push(branchName)
return acc
}, [])
const twgitBranchesCompat = [
...result.matchAll(/\[twgit\] Init feature '(.*)'/g),
].reduce<string[]>((acc, current) => {
const branchName = current[1] as string
acc.push(branchName)
return acc
}, [])
return [...branches, ...twgitBranchesCompat]
}
export const deleteBranch = async (branchName: string) => {
const isPresentLocally = await localBranchExists(branchName)
if (isPresentLocally) {
await git.deleteLocalBranch(branchName, true)
}
await git.push(['origin', '--delete', branchName])
}