1 | 'use strict';
|
2 | const execa = require('execa');
|
3 | const escapeStringRegexp = require('escape-string-regexp');
|
4 | const {verifyRequirementSatisfied} = require('./version');
|
5 |
|
6 | exports.latestTag = async () => {
|
7 | const {stdout} = await execa('git', ['describe', '--abbrev=0', '--tags']);
|
8 | return stdout;
|
9 | };
|
10 |
|
11 | const firstCommit = async () => {
|
12 | const {stdout} = await execa('git', ['rev-list', '--max-parents=0', 'HEAD']);
|
13 | return stdout;
|
14 | };
|
15 |
|
16 | exports.latestTagOrFirstCommit = async () => {
|
17 | let latest;
|
18 | try {
|
19 |
|
20 | latest = await exports.latestTag();
|
21 | } catch (_) {
|
22 |
|
23 | latest = await firstCommit();
|
24 | }
|
25 |
|
26 | return latest;
|
27 | };
|
28 |
|
29 | exports.hasUpstream = async () => {
|
30 | const escapedCurrentBranch = escapeStringRegexp(await exports.currentBranch());
|
31 | const {stdout} = await execa('git', ['status', '--short', '--branch', '--porcelain']);
|
32 |
|
33 | return new RegExp(String.raw`^## ${escapedCurrentBranch}\.\.\..+\/${escapedCurrentBranch}`).test(stdout);
|
34 | };
|
35 |
|
36 | exports.currentBranch = async () => {
|
37 | const {stdout} = await execa('git', ['symbolic-ref', '--short', 'HEAD']);
|
38 | return stdout;
|
39 | };
|
40 |
|
41 | exports.verifyCurrentBranchIsMaster = async () => {
|
42 | if (await exports.currentBranch() !== 'master') {
|
43 | throw new Error('Not on `master` branch. Use --any-branch to publish anyway.');
|
44 | }
|
45 | };
|
46 |
|
47 | exports.isWorkingTreeClean = async () => {
|
48 | try {
|
49 | const {stdout: status} = await execa('git', ['status', '--porcelain']);
|
50 | if (status !== '') {
|
51 | return false;
|
52 | }
|
53 |
|
54 | return true;
|
55 | } catch (_) {
|
56 | return false;
|
57 | }
|
58 | };
|
59 |
|
60 | exports.verifyWorkingTreeIsClean = async () => {
|
61 | if (!(await exports.isWorkingTreeClean())) {
|
62 | throw new Error('Unclean working tree. Commit or stash changes first.');
|
63 | }
|
64 | };
|
65 |
|
66 | exports.isRemoteHistoryClean = async () => {
|
67 | let history;
|
68 | try {
|
69 | const {stdout} = await execa('git', ['rev-list', '--count', '--left-only', '@{u}...HEAD']);
|
70 | history = stdout;
|
71 | } catch (_) {}
|
72 |
|
73 | if (history && history !== '0') {
|
74 | return false;
|
75 | }
|
76 |
|
77 | return true;
|
78 | };
|
79 |
|
80 | exports.verifyRemoteHistoryIsClean = async () => {
|
81 | if (!(await exports.isRemoteHistoryClean())) {
|
82 | throw new Error('Remote history differs. Please pull changes.');
|
83 | }
|
84 | };
|
85 |
|
86 | exports.verifyRemoteIsValid = async () => {
|
87 | try {
|
88 | await execa('git', ['ls-remote', 'origin', 'HEAD']);
|
89 | } catch (error) {
|
90 | throw new Error(error.stderr.replace('fatal:', 'Git fatal error:'));
|
91 | }
|
92 | };
|
93 |
|
94 | exports.fetch = async () => {
|
95 | await execa('git', ['fetch']);
|
96 | };
|
97 |
|
98 | exports.tagExistsOnRemote = async tagName => {
|
99 | try {
|
100 | const {stdout: revInfo} = await execa('git', ['rev-parse', '--quiet', '--verify', `refs/tags/${tagName}`]);
|
101 |
|
102 | if (revInfo) {
|
103 | return true;
|
104 | }
|
105 |
|
106 | return false;
|
107 | } catch (error) {
|
108 |
|
109 |
|
110 | if (error.stdout === '' && error.stderr === '') {
|
111 | return false;
|
112 | }
|
113 |
|
114 | throw error;
|
115 | }
|
116 | };
|
117 |
|
118 | exports.verifyTagDoesNotExistOnRemote = async tagName => {
|
119 | if (await exports.tagExistsOnRemote(tagName)) {
|
120 | throw new Error(`Git tag \`${tagName}\` already exists.`);
|
121 | }
|
122 | };
|
123 |
|
124 | exports.commitLogFromRevision = async revision => {
|
125 | const {stdout} = await execa('git', ['log', '--format=%s %h', `${revision}..HEAD`]);
|
126 | return stdout;
|
127 | };
|
128 |
|
129 | exports.push = async () => {
|
130 | await execa('git', ['push', '--follow-tags']);
|
131 | };
|
132 |
|
133 | exports.deleteTag = async tagName => {
|
134 | await execa('git', ['tag', '--delete', tagName]);
|
135 | };
|
136 |
|
137 | exports.removeLastCommit = async () => {
|
138 | await execa('git', ['reset', '--hard', 'HEAD~1']);
|
139 | };
|
140 |
|
141 | const gitVersion = async () => {
|
142 | const {stdout} = await execa('git', ['version']);
|
143 | return stdout.match(/git version (\d+\.\d+\.\d+).*/)[1];
|
144 | };
|
145 |
|
146 | exports.verifyRecentGitVersion = async () => {
|
147 | const installedVersion = await gitVersion();
|
148 |
|
149 | verifyRequirementSatisfied('git', installedVersion);
|
150 | };
|