UNPKG

5.43 kBJavaScriptView Raw
1const execa = require('execa');
2const debug = require('debug')('semantic-release:git');
3
4/**
5 * Get the commit sha for a given tag.
6 *
7 * @param {String} tagName Tag name for which to retrieve the commit sha.
8 * @param {Object} [execaOpts] Options to pass to `execa`.
9 *
10 * @return {string} The commit sha of the tag in parameter or `null`.
11 */
12async function getTagHead(tagName, execaOpts) {
13 try {
14 return (await execa('git', ['rev-list', '-1', tagName], execaOpts)).stdout;
15 } catch (error) {
16 debug(error);
17 }
18}
19
20/**
21 * Get all the repository tags.
22 *
23 * @param {Object} [execaOpts] Options to pass to `execa`.
24 *
25 * @return {Array<String>} List of git tags.
26 * @throws {Error} If the `git` command fails.
27 */
28async function getTags(execaOpts) {
29 return (await execa('git', ['tag'], execaOpts)).stdout
30 .split('\n')
31 .map(tag => tag.trim())
32 .filter(Boolean);
33}
34
35/**
36 * Verify if the `ref` is in the direct history of the current branch.
37 *
38 * @param {String} ref The reference to look for.
39 * @param {Object} [execaOpts] Options to pass to `execa`.
40 *
41 * @return {Boolean} `true` if the reference is in the history of the current branch, falsy otherwise.
42 */
43async function isRefInHistory(ref, execaOpts) {
44 try {
45 await execa('git', ['merge-base', '--is-ancestor', ref, 'HEAD'], execaOpts);
46 return true;
47 } catch (error) {
48 if (error.exitCode === 1) {
49 return false;
50 }
51
52 debug(error);
53 throw error;
54 }
55}
56
57/**
58 * Unshallow the git repository if necessary and fetch all the tags.
59 *
60 * @param {String} repositoryUrl The remote repository URL.
61 * @param {Object} [execaOpts] Options to pass to `execa`.
62 */
63async function fetch(repositoryUrl, execaOpts) {
64 try {
65 await execa('git', ['fetch', '--unshallow', '--tags', repositoryUrl], execaOpts);
66 } catch (_) {
67 await execa('git', ['fetch', '--tags', repositoryUrl], execaOpts);
68 }
69}
70
71/**
72 * Get the HEAD sha.
73 *
74 * @param {Object} [execaOpts] Options to pass to `execa`.
75 *
76 * @return {String} the sha of the HEAD commit.
77 */
78async function getGitHead(execaOpts) {
79 return (await execa('git', ['rev-parse', 'HEAD'], execaOpts)).stdout;
80}
81
82/**
83 * Get the repository remote URL.
84 *
85 * @param {Object} [execaOpts] Options to pass to `execa`.
86 *
87 * @return {string} The value of the remote git URL.
88 */
89async function repoUrl(execaOpts) {
90 try {
91 return (await execa('git', ['config', '--get', 'remote.origin.url'], execaOpts)).stdout;
92 } catch (error) {
93 debug(error);
94 }
95}
96
97/**
98 * Test if the current working directory is a Git repository.
99 *
100 * @param {Object} [execaOpts] Options to pass to `execa`.
101 *
102 * @return {Boolean} `true` if the current working directory is in a git repository, falsy otherwise.
103 */
104async function isGitRepo(execaOpts) {
105 try {
106 return (await execa('git', ['rev-parse', '--git-dir'], execaOpts)).exitCode === 0;
107 } catch (error) {
108 debug(error);
109 }
110}
111
112/**
113 * Verify the write access authorization to remote repository with push dry-run.
114 *
115 * @param {String} repositoryUrl The remote repository URL.
116 * @param {String} branch The repositoru branch for which to verify write access.
117 * @param {Object} [execaOpts] Options to pass to `execa`.
118 *
119 * @throws {Error} if not authorized to push.
120 */
121async function verifyAuth(repositoryUrl, branch, execaOpts) {
122 try {
123 await execa('git', ['push', '--dry-run', repositoryUrl, `HEAD:${branch}`], execaOpts);
124 } catch (error) {
125 debug(error);
126 throw error;
127 }
128}
129
130/**
131 * Tag the commit head on the local repository.
132 *
133 * @param {String} tagName The name of the tag.
134 * @param {Object} [execaOpts] Options to pass to `execa`.
135 *
136 * @throws {Error} if the tag creation failed.
137 */
138async function tag(tagName, execaOpts) {
139 await execa('git', ['tag', tagName], execaOpts);
140}
141
142/**
143 * Push to the remote repository.
144 *
145 * @param {String} repositoryUrl The remote repository URL.
146 * @param {Object} [execaOpts] Options to pass to `execa`.
147 *
148 * @throws {Error} if the push failed.
149 */
150async function push(repositoryUrl, execaOpts) {
151 await execa('git', ['push', '--tags', repositoryUrl], execaOpts);
152}
153
154/**
155 * Verify a tag name is a valid Git reference.
156 *
157 * @param {String} tagName the tag name to verify.
158 * @param {Object} [execaOpts] Options to pass to `execa`.
159 *
160 * @return {Boolean} `true` if valid, falsy otherwise.
161 */
162async function verifyTagName(tagName, execaOpts) {
163 try {
164 return (await execa('git', ['check-ref-format', `refs/tags/${tagName}`], execaOpts)).exitCode === 0;
165 } catch (error) {
166 debug(error);
167 }
168}
169
170/**
171 * Verify the local branch is up to date with the remote one.
172 *
173 * @param {String} repositoryUrl The remote repository URL.
174 * @param {String} branch The repository branch for which to verify status.
175 * @param {Object} [execaOpts] Options to pass to `execa`.
176 *
177 * @return {Boolean} `true` is the HEAD of the current local branch is the same as the HEAD of the remote branch, falsy otherwise.
178 */
179async function isBranchUpToDate(repositoryUrl, branch, execaOpts) {
180 const {stdout: remoteHead} = await execa('git', ['ls-remote', '--heads', repositoryUrl, branch], execaOpts);
181 try {
182 return await isRefInHistory(remoteHead.match(/^(\w+)?/)[1], execaOpts);
183 } catch (error) {
184 debug(error);
185 }
186}
187
188module.exports = {
189 getTagHead,
190 getTags,
191 isRefInHistory,
192 fetch,
193 getGitHead,
194 repoUrl,
195 isGitRepo,
196 verifyAuth,
197 tag,
198 push,
199 verifyTagName,
200 isBranchUpToDate,
201};