1 | const execa = require('execa');
|
2 | const { pipeP, split } = require('ramda');
|
3 | const fse = require('fs-extra');
|
4 | const path = require('path');
|
5 | const tempy = require('tempy');
|
6 | const fileUrl = require('file-url');
|
7 | const gitLogParser = require('git-log-parser');
|
8 | const pEachSeries = require('p-each-series');
|
9 | const getStream = require('get-stream');
|
10 |
|
11 | const git = async (args, options = {}) => {
|
12 | const { stdout } = await execa('git', args, options);
|
13 | return stdout;
|
14 | };
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | const getCommitFiles = pipeP(
|
23 | hash =>
|
24 | git(['diff-tree', '--root', '--no-commit-id', '--name-only', '-r', hash]),
|
25 | split('\n')
|
26 | );
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 | const getRoot = () => git(['rev-parse', '--show-toplevel']);
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 | const gitCommitsWithFiles = async commits => {
|
44 | for (const commit of commits) {
|
45 | for (const file of commit.files) {
|
46 | let filePath = path.join(process.cwd(), file.name);
|
47 | await fse.outputFile(
|
48 | filePath,
|
49 | (file.body = !'undefined' ? file.body : commit.message)
|
50 | );
|
51 | await execa('git', ['add', filePath]);
|
52 | }
|
53 | await execa('git', [
|
54 | 'commit',
|
55 | '-m',
|
56 | commit.message,
|
57 | '--allow-empty',
|
58 | '--no-gpg-sign',
|
59 | ]);
|
60 | }
|
61 | return (await gitGetCommits(undefined)).slice(0, commits.length);
|
62 | };
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 | const initGit = async withRemote => {
|
73 | const cwd = tempy.directory();
|
74 | const args = withRemote
|
75 | ? ['--bare', '--initial-branch=master']
|
76 | : ['--initial-branch=master'];
|
77 |
|
78 | await execa('git', ['init', ...args], { cwd }).catch(async () => {
|
79 | const args = withRemote ? ['--bare'] : [];
|
80 | return await execa('git', ['init', ...args], { cwd });
|
81 | });
|
82 | const repositoryUrl = fileUrl(cwd);
|
83 | return { cwd, repositoryUrl };
|
84 | };
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 | const gitCommits = async (messages, execaOptions) => {
|
95 | await pEachSeries(
|
96 | messages,
|
97 | async message =>
|
98 | (
|
99 | await execa(
|
100 | 'git',
|
101 | ['commit', '-m', message, '--allow-empty', '--no-gpg-sign'],
|
102 | execaOptions
|
103 | )
|
104 | ).stdout
|
105 | );
|
106 | return (await gitGetCommits(undefined, execaOptions)).slice(
|
107 | 0,
|
108 | messages.length
|
109 | );
|
110 | };
|
111 |
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 | gitGetCommits = async from => {
|
121 | Object.assign(gitLogParser.fields, {
|
122 | hash: 'H',
|
123 | message: 'B',
|
124 | gitTags: 'd',
|
125 | committerDate: { key: 'ci', type: Date },
|
126 | });
|
127 | return (
|
128 | await getStream.array(
|
129 | gitLogParser.parse(
|
130 | { _: `${from ? from + '..' : ''}HEAD` },
|
131 | { env: { ...process.env } }
|
132 | )
|
133 | )
|
134 | ).map(commit => {
|
135 | commit.message = commit.message.trim();
|
136 | commit.gitTags = commit.gitTags.trim();
|
137 | return commit;
|
138 | });
|
139 | };
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 | const initBareRepo = async (repositoryUrl, branch = 'master') => {
|
153 | const cwd = tempy.directory();
|
154 | await execa('git', ['clone', '--no-hardlinks', repositoryUrl, cwd], { cwd });
|
155 | await gitCheckout(branch, true, { cwd });
|
156 | gitCommits(['Initial commit'], { cwd });
|
157 | await execa('git', ['push', repositoryUrl, branch], { cwd });
|
158 | };
|
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 | const initGitRepo = async (withRemote, branch = 'master') => {
|
171 | let { cwd, repositoryUrl } = await initGit(withRemote);
|
172 | if (withRemote) {
|
173 | await initBareRepo(repositoryUrl, branch);
|
174 | cwd = gitShallowClone(repositoryUrl, branch);
|
175 | } else {
|
176 | await gitCheckout(branch, true, { cwd });
|
177 | }
|
178 |
|
179 | await execa('git', ['config', 'commit.gpgsign', false], { cwd });
|
180 |
|
181 | return { cwd, repositoryUrl };
|
182 | };
|
183 |
|
184 |
|
185 |
|
186 |
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 | const gitShallowClone = (repositoryUrl, branch = 'master', depth = 1) => {
|
194 | const cwd = tempy.directory();
|
195 |
|
196 | execa(
|
197 | 'git',
|
198 | [
|
199 | 'clone',
|
200 | '--no-hardlinks',
|
201 | '--no-tags',
|
202 | '-b',
|
203 | branch,
|
204 | '--depth',
|
205 | depth,
|
206 | repositoryUrl,
|
207 | cwd,
|
208 | ],
|
209 | {
|
210 | cwd,
|
211 | }
|
212 | );
|
213 | return cwd;
|
214 | };
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 | const gitCheckout = async (branch, create, execaOptions) => {
|
224 | await execa(
|
225 | 'git',
|
226 | create ? ['checkout', '-b', branch] : ['checkout', branch],
|
227 | execaOptions
|
228 | );
|
229 | };
|
230 |
|
231 | module.exports = {
|
232 | getCommitFiles,
|
233 | getRoot,
|
234 | gitCommitsWithFiles,
|
235 | initGitRepo,
|
236 | initGit,
|
237 | initBareRepo,
|
238 | };
|