Skip to content

Commit 0cac7fc

Browse files
authored
Merge pull request #958 from karamosky/iss-801
feat: add flag to enforce atomic push without a retry
2 parents 0db48db + d26811a commit 0cac7fc

File tree

5 files changed

+57
-18
lines changed

5 files changed

+57
-18
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ nx run workspace:version [...options]
7171
| ---------------------------- | ------------------ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7272
| **`--dryRun`** | `boolean` | `false` | run with dry mode |
7373
| **`--noVerify`** | `boolean` | `false` | skip git hooks |
74+
| **`--enforceAtomicPush`** | `boolean` | `false` | enforce an atomic push without retrying a non atomic one in case of failure |
7475
| **`--push`** | `boolean` | `false` | push the release to the remote repository |
7576
| **`--syncVersions`** | `boolean` | `false` | lock/sync versions between projects |
7677
| **`--skipRootChangelog`** | `boolean` | `false` | skip generating root changelog |

packages/semver/src/executors/version/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export default async function version(
3030
): Promise<{ success: boolean }> {
3131
const {
3232
push,
33+
enforceAtomicPush,
3334
remote,
3435
dryRun,
3536
trackDeps,
@@ -158,6 +159,7 @@ export default async function version(
158159
tag,
159160
branch: baseBranch,
160161
noVerify,
162+
enforceAtomicPush,
161163
remote,
162164
projectName,
163165
}),
@@ -227,6 +229,7 @@ function _normalizeOptions(options: VersionBuilderSchema) {
227229
return {
228230
...options,
229231
push: options.push as boolean,
232+
enforceAtomicPush: options.enforceAtomicPush as boolean,
230233
remote: options.remote as string,
231234
dryRun: options.dryRun as boolean,
232235
trackDeps: options.trackDeps as boolean,

packages/semver/src/executors/version/schema.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface VersionBuilderSchema {
1919
dryRun?: boolean;
2020
noVerify?: boolean;
2121
push?: boolean;
22+
enforceAtomicPush?: boolean;
2223
remote?: string;
2324
baseBranch?: string;
2425
syncVersions?: boolean;

packages/semver/src/executors/version/utils/git.spec.ts

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ describe('git', () => {
3939
stream.emit('data', 'feat B');
4040
stream.emit('close');
4141

42-
expect(observer.next).toBeCalledTimes(1);
43-
expect(observer.next).toBeCalledWith(['feat A', 'feat B']);
44-
expect(observer.complete).toBeCalledTimes(1);
42+
expect(observer.next).toHaveBeenCalledTimes(1);
43+
expect(observer.next).toHaveBeenCalledWith(['feat A', 'feat B']);
44+
expect(observer.complete).toHaveBeenCalledTimes(1);
4545
});
4646
});
4747

@@ -55,11 +55,12 @@ describe('git', () => {
5555
remote: 'upstream',
5656
branch: 'master',
5757
noVerify: false,
58+
enforceAtomicPush: false,
5859
projectName: 'p',
5960
}),
6061
);
6162

62-
expect(cp.exec).toBeCalledWith(
63+
expect(cp.exec).toHaveBeenCalledWith(
6364
'git',
6465
expect.arrayContaining([
6566
'push',
@@ -80,11 +81,12 @@ describe('git', () => {
8081
remote: 'origin',
8182
branch: 'main',
8283
noVerify: true,
84+
enforceAtomicPush: false,
8385
projectName: 'p',
8486
}),
8587
);
8688

87-
expect(cp.exec).toBeCalledWith(
89+
expect(cp.exec).toHaveBeenCalledWith(
8890
'git',
8991
expect.arrayContaining([
9092
'push',
@@ -97,7 +99,7 @@ describe('git', () => {
9799
);
98100
});
99101

100-
it(`should retry Git push if '--atomic' option not supported`, async () => {
102+
it(`should retry Git push if '--atomic' option is not supported`, async () => {
101103
jest
102104
.spyOn(cp, 'exec')
103105
.mockReturnValueOnce(throwError(() => new Error('atomic failed')))
@@ -111,6 +113,7 @@ describe('git', () => {
111113
remote: 'origin',
112114
branch: 'master',
113115
noVerify: false,
116+
enforceAtomicPush: false,
114117
projectName: 'p',
115118
}),
116119
);
@@ -125,7 +128,34 @@ describe('git', () => {
125128
'git',
126129
expect.not.arrayContaining(['--atomic']),
127130
);
128-
expect(console.warn).toBeCalled();
131+
expect(console.warn).toHaveBeenCalled();
132+
});
133+
134+
it('should not retry with a non atomic Git push if asked for', async () => {
135+
jest
136+
.spyOn(cp, 'exec')
137+
.mockReturnValueOnce(throwError(() => new Error('atomic failed')));
138+
139+
await expect(
140+
lastValueFrom(
141+
tryPush({
142+
tag: 'v1.0.0',
143+
remote: 'origin',
144+
branch: 'master',
145+
noVerify: false,
146+
enforceAtomicPush: true,
147+
projectName: 'p',
148+
}),
149+
),
150+
).rejects.toEqual(new Error('atomic failed'));
151+
152+
expect(cp.exec).toHaveBeenCalledTimes(1);
153+
154+
expect(cp.exec).toHaveBeenNthCalledWith(
155+
1,
156+
'git',
157+
expect.arrayContaining(['push', '--atomic', 'v1.0.0']),
158+
);
129159
});
130160

131161
it(`should throw if Git push failed`, async () => {
@@ -140,11 +170,12 @@ describe('git', () => {
140170
remote: 'origin',
141171
branch: 'master',
142172
noVerify: false,
173+
enforceAtomicPush: false,
143174
projectName: 'p',
144175
}),
145176
),
146177
).rejects.toEqual(new Error('Something went wrong'));
147-
expect(cp.exec).toBeCalledTimes(1);
178+
expect(cp.exec).toHaveBeenCalledTimes(1);
148179
});
149180

150181
it('should fail if options are undefined', async () => {
@@ -157,6 +188,7 @@ describe('git', () => {
157188
branch: undefined as any,
158189
/* eslint-enable @typescript-eslint/no-explicit-any */
159190
noVerify: false,
191+
enforceAtomicPush: false,
160192
projectName: 'p',
161193
}),
162194
),
@@ -176,7 +208,7 @@ describe('git', () => {
176208
}),
177209
);
178210

179-
expect(cp.exec).toBeCalledWith(
211+
expect(cp.exec).toHaveBeenCalledWith(
180212
'git',
181213
expect.arrayContaining([
182214
'add',
@@ -198,7 +230,7 @@ describe('git', () => {
198230
{ defaultValue: undefined },
199231
);
200232

201-
expect(cp.exec).not.toBeCalled();
233+
expect(cp.exec).not.toHaveBeenCalled();
202234
});
203235

204236
it('should skip add to git stage if skipStage is true but should continue the chain', async () => {
@@ -212,7 +244,7 @@ describe('git', () => {
212244
}),
213245
);
214246

215-
expect(cp.exec).not.toBeCalled();
247+
expect(cp.exec).not.toHaveBeenCalled();
216248
expect(value).toEqual(undefined);
217249
});
218250

@@ -228,7 +260,7 @@ describe('git', () => {
228260
{ defaultValue: undefined },
229261
);
230262

231-
expect(cp.exec).not.toBeCalled();
263+
expect(cp.exec).not.toHaveBeenCalled();
232264
});
233265
});
234266

@@ -239,7 +271,7 @@ describe('git', () => {
239271
const tag = await lastValueFrom(getFirstCommitRef());
240272

241273
expect(tag).toBe('sha1');
242-
expect(cp.exec).toBeCalledWith(
274+
expect(cp.exec).toHaveBeenCalledWith(
243275
'git',
244276
expect.arrayContaining(['rev-list', '--max-parents=0', 'HEAD']),
245277
);
@@ -251,7 +283,7 @@ describe('git', () => {
251283
const tag = await lastValueFrom(getFirstCommitRef());
252284

253285
expect(tag).toBe('sha3');
254-
expect(cp.exec).toBeCalledWith(
286+
expect(cp.exec).toHaveBeenCalledWith(
255287
'git',
256288
expect.arrayContaining(['rev-list', '--max-parents=0', 'HEAD']),
257289
);
@@ -273,7 +305,7 @@ describe('git', () => {
273305
);
274306

275307
expect(tag).toBe('project-a-1.0.0');
276-
expect(cp.exec).toBeCalledWith(
308+
expect(cp.exec).toHaveBeenCalledWith(
277309
'git',
278310
expect.arrayContaining([
279311
'tag',
@@ -295,7 +327,7 @@ describe('git', () => {
295327
projectName: 'p',
296328
}).subscribe({
297329
complete: () => {
298-
expect(cp.exec).not.toBeCalled();
330+
expect(cp.exec).not.toHaveBeenCalled();
299331
done();
300332
},
301333
});
@@ -320,7 +352,7 @@ describe('git', () => {
320352
next: expect.fail,
321353
complete: () => expect.fail('should not complete'),
322354
error: (error) => {
323-
expect(cp.exec).toBeCalled();
355+
expect(cp.exec).toHaveBeenCalled();
324356
expect(error.message).toMatch(
325357
'Failed to tag "project-a-1.0.0", this tag already exists.',
326358
);

packages/semver/src/executors/version/utils/git.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,15 @@ export function tryPush({
7373
remote,
7474
branch,
7575
noVerify,
76+
enforceAtomicPush,
7677
projectName,
7778
tag,
7879
}: {
7980
tag: string;
8081
remote: string;
8182
branch: string;
8283
noVerify: boolean;
84+
enforceAtomicPush: boolean;
8385
projectName: string;
8486
}): Observable<string> {
8587
if (remote == null || branch == null) {
@@ -103,7 +105,7 @@ export function tryPush({
103105
])
104106
.pipe(
105107
catchError((error) => {
106-
if (/atomic/.test(error)) {
108+
if (!enforceAtomicPush && /atomic/.test(error)) {
107109
_logStep({
108110
step: 'warning',
109111
level: 'warn',

0 commit comments

Comments
 (0)