import { EOL } from 'node:os'; import { format, parseGitUrl } from '../util.js'; import Plugin from './Plugin.js'; const options = { write: false }; const changelogFallback = 'git log --pretty=format:"* %s (%h)"'; class GitBase extends Plugin { async init() { const remoteUrl = await this.getRemoteUrl(); await this.fetch(remoteUrl); const branchName = await this.getBranchName(); const repo = parseGitUrl(remoteUrl); this.setContext({ remoteUrl, branchName, repo }); this.config.setContext({ remoteUrl, branchName, repo }); const latestTag = await this.getLatestTagName(); const secondLatestTag = !this.config.isIncrement ? await this.getSecondLatestTagName(latestTag) : null; const tagTemplate = this.options.tagName || ((latestTag || '').match(/^v/) ? 'v${version}' : '${version}'); this.config.setContext({ latestTag, secondLatestTag, tagTemplate }); } getName() { return this.getContext('repo.project'); } getLatestVersion() { const { tagTemplate, latestTag } = this.config.getContext(); const prefix = tagTemplate.replace(/\$\{version\}/, ''); return latestTag ? latestTag.replace(prefix, '').replace(/^v/, '') : null; } async getChangelog() { const { latestTag, secondLatestTag } = this.config.getContext(); const context = { latestTag, from: latestTag, to: 'HEAD' }; const { changelog } = this.options; if (!changelog) return null; if (latestTag && !this.config.isIncrement) { context.from = secondLatestTag; context.to = `${latestTag}^1`; } if (!context.from && changelog.includes('${from}')) { return this.exec(changelogFallback); } return this.exec(changelog, { context, options }); } bump(version) { const { tagTemplate } = this.config.getContext(); const context = Object.assign(this.config.getContext(), { version }); const tagName = format(tagTemplate, context) || version; this.setContext({ version }); this.config.setContext({ tagName }); } isRemoteName(remoteUrlOrName) { return remoteUrlOrName && !remoteUrlOrName.includes('/'); } async getRemoteUrl() { const remoteNameOrUrl = this.options.pushRepo || (await this.getRemote()) || 'origin'; return this.isRemoteName(remoteNameOrUrl) ? this.exec(`git remote get-url ${remoteNameOrUrl}`, { options }).catch(() => this.exec(`git config --get remote.${remoteNameOrUrl}.url`, { options }).catch(() => null) ) : remoteNameOrUrl; } async getRemote() { const branchName = await this.getBranchName(); return branchName ? await this.getRemoteForBranch(branchName) : null; } getBranchName() { return this.exec('git rev-parse --abbrev-ref HEAD', { options }).catch(() => null); } getRemoteForBranch(branch) { return this.exec(`git config --get branch.${branch}.remote`, { options }).catch(() => null); } fetch(remoteUrl) { return this.exec('git fetch').catch(err => { this.debug(err); throw new Error(`Unable to fetch from ${remoteUrl}${EOL}${err.message}`); }); } getLatestTagName() { const context = Object.assign({}, this.config.getContext(), { version: '*' }); const match = format(this.options.tagMatch || this.options.tagName || '${version}', context); const exclude = this.options.tagExclude ? ` --exclude=${format(this.options.tagExclude, context)}` : ''; return this.exec(`git describe --tags --match=${match} --abbrev=0${exclude}`, { options }).then( stdout => stdout || null, () => null ); } async getSecondLatestTagName(latestTag) { const sha = await this.exec(`git rev-list ${latestTag || '--skip=1'} --tags --max-count=1`, { options }); return this.exec(`git describe --tags --abbrev=0 "${sha}^"`, { options }).catch(() => null); } } export default GitBase;