-
-
Notifications
You must be signed in to change notification settings - Fork 302
/
Copy pathgit-util.js
133 lines (105 loc) · 3.54 KB
/
git-util.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
'use strict';
const execa = require('execa');
const version = require('./version');
exports.latestTag = () => execa.stdout('git', ['describe', '--abbrev=0', '--tags']);
const firstCommit = () => execa.stdout('git', ['rev-list', '--max-parents=0', 'HEAD']);
exports.latestTagOrFirstCommit = async () => {
let latest;
try {
// In case a previous tag exists, we use it to compare the current repo status to.
latest = await exports.latestTag();
} catch (_) {
// Otherwise, we fallback to using the first commit for comparison.
latest = await firstCommit();
}
return latest;
};
exports.hasUpstream = async () => {
const {stdout} = await execa('git', ['status', '--short', '--branch', '--porcelain=2']);
return /^# branch\.upstream [\w\-/]+$/m.test(stdout);
};
exports.currentBranch = () => execa.stdout('git', ['symbolic-ref', '--short', 'HEAD']);
exports.verifyCurrentBranchIsMaster = async () => {
if (await exports.currentBranch() !== 'master') {
throw new Error('Not on `master` branch. Use --any-branch to publish anyway.');
}
};
exports.isWorkingTreeClean = async () => {
try {
const {stdout: status} = await execa('git', ['status', '--porcelain']);
if (status !== '') {
return false;
}
return true;
} catch (_) {
return false;
}
};
exports.verifyWorkingTreeIsClean = async () => {
if (!(await exports.isWorkingTreeClean())) {
throw new Error('Unclean working tree. Commit or stash changes first.');
}
};
exports.isRemoteHistoryClean = async () => {
let history;
try { // Gracefully handle no remote set up.
history = await execa.stdout('git', ['rev-list', '--count', '--left-only', '@{u}...HEAD']);
} catch (_) {}
if (history && history !== '0') {
return false;
}
return true;
};
exports.verifyRemoteHistoryIsClean = async () => {
if (!(await exports.isRemoteHistoryClean())) {
throw new Error('Remote history differs. Please pull changes.');
}
};
exports.verifyRemoteIsValid = async () => {
try {
await execa('git', ['ls-remote', 'origin', 'HEAD']);
} catch (error) {
throw new Error(error.stderr.replace('fatal:', 'Git fatal error:'));
}
};
exports.fetch = () => execa('git', ['fetch']);
exports.tagExistsOnRemote = async tagName => {
try {
const {stdout: revInfo} = await execa('git', ['rev-parse', '--quiet', '--verify', `refs/tags/${tagName}`]);
if (revInfo) {
return true;
}
return false;
} catch (error) {
// Command fails with code 1 and no output if the tag does not exist, even though `--quiet` is provided
// https://github.com/sindresorhus/np/pull/73#discussion_r72385685
if (error.stdout === '' && error.stderr === '') {
return false;
}
throw error;
}
};
exports.verifyTagDoesNotExistOnRemote = async tagName => {
if (await exports.tagExistsOnRemote(tagName)) {
throw new Error(`Git tag \`${tagName}\` already exists.`);
}
};
exports.commitLogFromRevision = revision => execa.stdout('git', ['log', '--format=%s %h', `${revision}..HEAD`]);
exports.push = () => execa('git', ['push', '--follow-tags']);
exports.deleteTag = async tagName => {
await execa('git', ['tag', '--delete', tagName]);
};
exports.removeLastCommit = async () => {
await execa('git', ['reset', '--hard', 'HEAD~1']);
};
const gitVersion = async () => {
const {stdout} = await execa('git', ['version']);
return stdout.match(/git version (\d+\.\d+\.\d+).*/)[1];
};
exports.verifyRecentGitVersion = async () => {
const installedVersion = await gitVersion();
const minVersion = '2.11.0';
if (!version(minVersion).isGreaterThanOrEqualTo(installedVersion)) {
throw new Error('Please upgrade to [email protected] or newer.');
}
};