@node-core/utils 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +158 -0
  3. package/bin/get-metadata.js +11 -0
  4. package/bin/git-node.js +30 -0
  5. package/bin/ncu-ci.js +600 -0
  6. package/bin/ncu-config.js +101 -0
  7. package/bin/ncu-team.js +76 -0
  8. package/components/git/backport.js +70 -0
  9. package/components/git/epilogue.js +18 -0
  10. package/components/git/land.js +223 -0
  11. package/components/git/metadata.js +94 -0
  12. package/components/git/release.js +99 -0
  13. package/components/git/security.js +35 -0
  14. package/components/git/status.js +32 -0
  15. package/components/git/sync.js +24 -0
  16. package/components/git/v8.js +121 -0
  17. package/components/git/vote.js +84 -0
  18. package/components/git/wpt.js +87 -0
  19. package/components/metadata.js +49 -0
  20. package/lib/auth.js +133 -0
  21. package/lib/backport_session.js +302 -0
  22. package/lib/cache.js +107 -0
  23. package/lib/cherry_pick.js +304 -0
  24. package/lib/ci/build-types/benchmark_run.js +72 -0
  25. package/lib/ci/build-types/citgm_build.js +194 -0
  26. package/lib/ci/build-types/citgm_comparison_build.js +174 -0
  27. package/lib/ci/build-types/commit_build.js +112 -0
  28. package/lib/ci/build-types/daily_build.js +24 -0
  29. package/lib/ci/build-types/fanned_build.js +87 -0
  30. package/lib/ci/build-types/health_build.js +63 -0
  31. package/lib/ci/build-types/job.js +114 -0
  32. package/lib/ci/build-types/linter_build.js +35 -0
  33. package/lib/ci/build-types/normal_build.js +89 -0
  34. package/lib/ci/build-types/pr_build.js +101 -0
  35. package/lib/ci/build-types/test_build.js +186 -0
  36. package/lib/ci/build-types/test_run.js +41 -0
  37. package/lib/ci/ci_failure_parser.js +325 -0
  38. package/lib/ci/ci_type_parser.js +203 -0
  39. package/lib/ci/ci_utils.js +106 -0
  40. package/lib/ci/failure_aggregator.js +152 -0
  41. package/lib/ci/jenkins_constants.js +28 -0
  42. package/lib/ci/run_ci.js +120 -0
  43. package/lib/cli.js +192 -0
  44. package/lib/collaborators.js +140 -0
  45. package/lib/config.js +72 -0
  46. package/lib/figures.js +7 -0
  47. package/lib/file.js +43 -0
  48. package/lib/github/templates/next-security-release.md +97 -0
  49. package/lib/github/tree.js +162 -0
  50. package/lib/landing_session.js +506 -0
  51. package/lib/links.js +123 -0
  52. package/lib/mergeable_state.js +3 -0
  53. package/lib/metadata_gen.js +61 -0
  54. package/lib/pr_checker.js +605 -0
  55. package/lib/pr_data.js +115 -0
  56. package/lib/pr_summary.js +62 -0
  57. package/lib/prepare_release.js +772 -0
  58. package/lib/prepare_security.js +117 -0
  59. package/lib/proxy.js +21 -0
  60. package/lib/queries/DefaultBranchRef.gql +8 -0
  61. package/lib/queries/LastCommit.gql +16 -0
  62. package/lib/queries/PR.gql +37 -0
  63. package/lib/queries/PRComments.gql +27 -0
  64. package/lib/queries/PRCommits.gql +45 -0
  65. package/lib/queries/PRs.gql +25 -0
  66. package/lib/queries/Reviews.gql +23 -0
  67. package/lib/queries/SearchIssue.gql +51 -0
  68. package/lib/queries/Team.gql +22 -0
  69. package/lib/queries/TreeEntries.gql +12 -0
  70. package/lib/queries/VotePRInfo.gql +28 -0
  71. package/lib/release/utils.js +53 -0
  72. package/lib/request.js +185 -0
  73. package/lib/review_state.js +5 -0
  74. package/lib/reviews.js +178 -0
  75. package/lib/run.js +106 -0
  76. package/lib/session.js +415 -0
  77. package/lib/sync_session.js +15 -0
  78. package/lib/team_info.js +95 -0
  79. package/lib/update-v8/applyNodeChanges.js +49 -0
  80. package/lib/update-v8/backport.js +258 -0
  81. package/lib/update-v8/commitUpdate.js +26 -0
  82. package/lib/update-v8/common.js +35 -0
  83. package/lib/update-v8/constants.js +86 -0
  84. package/lib/update-v8/index.js +56 -0
  85. package/lib/update-v8/majorUpdate.js +171 -0
  86. package/lib/update-v8/minorUpdate.js +105 -0
  87. package/lib/update-v8/updateMaintainingDependencies.js +34 -0
  88. package/lib/update-v8/updateV8Clone.js +53 -0
  89. package/lib/update-v8/updateVersionNumbers.js +122 -0
  90. package/lib/update-v8/util.js +62 -0
  91. package/lib/user.js +4 -0
  92. package/lib/user_status.js +5 -0
  93. package/lib/utils.js +66 -0
  94. package/lib/verbosity.js +26 -0
  95. package/lib/voting_session.js +136 -0
  96. package/lib/wpt/index.js +243 -0
  97. package/lib/wpt/templates/README.md +16 -0
  98. package/package.json +69 -0
package/lib/reviews.js ADDED
@@ -0,0 +1,178 @@
1
+ import {
2
+ PENDING, COMMENTED, APPROVED, CHANGES_REQUESTED, DISMISSED
3
+ } from './review_state.js';
4
+ import { isCollaborator } from './collaborators.js';
5
+ import { ascending } from './utils.js';
6
+
7
+ const LGTM_RE = /^lgtm\W?$/i;
8
+ const FROM_REVIEW = 'review';
9
+ const FROM_COMMENT = 'comment';
10
+ const FROM_REVIEW_COMMENT = 'review_comment';
11
+
12
+ export class Review {
13
+ /**
14
+ * @param {string} state
15
+ * @param {string} date // ISO date string
16
+ * @param {string} ref
17
+ * @param {string} source
18
+ */
19
+ constructor(state, date, ref, source) {
20
+ this.state = state;
21
+ this.date = date;
22
+ this.ref = ref;
23
+ this.source = source;
24
+ }
25
+ }
26
+
27
+ /**
28
+ * @typedef {Object} GHReview
29
+ * @property {string} bodyText
30
+ * @property {string} state
31
+ * @property {{login: string}} author
32
+ * @property {string} url
33
+ * @property {string} publishedAt
34
+ *
35
+ * @typedef {Object} GHComment
36
+ * @property {string} bodyText
37
+ * @property {{login: string}} author
38
+ * @property {string} publishedAt
39
+ *
40
+ */
41
+ export class ReviewAnalyzer {
42
+ /**
43
+ * @param {PRData} data
44
+ */
45
+ constructor(data) {
46
+ const { reviews, comments, collaborators } = data;
47
+ this.reviews = reviews;
48
+ this.comments = comments;
49
+ this.collaborators = collaborators;
50
+ }
51
+
52
+ /**
53
+ * @returns {Map<string, Review>}
54
+ */
55
+ mapByGithubReviews() {
56
+ const map = new Map();
57
+ const collaborators = this.collaborators;
58
+ const list = this.reviews
59
+ .filter((r) => r.state !== PENDING)
60
+ .filter((r) => {
61
+ if (r.state === COMMENTED) {
62
+ return this.isApprovedInComment(r);
63
+ } else {
64
+ return true;
65
+ }
66
+ })
67
+ .filter((r) => {
68
+ return (isCollaborator(collaborators, r.author));
69
+ }).sort((a, b) => {
70
+ return ascending(a.publishedAt, b.publishedAt);
71
+ });
72
+
73
+ for (const r of list) {
74
+ const login = r.author.login.toLowerCase();
75
+ const entry = map.get(login);
76
+ if (!entry) { // initialize
77
+ map.set(
78
+ login,
79
+ new Review(r.state, r.publishedAt, r.url, FROM_REVIEW)
80
+ );
81
+ }
82
+ switch (r.state) {
83
+ case APPROVED:
84
+ case CHANGES_REQUESTED:
85
+ // Overwrite previous reviews, whether initalized or not
86
+ map.set(
87
+ login,
88
+ new Review(r.state, r.publishedAt, r.url, FROM_REVIEW)
89
+ );
90
+ break;
91
+ case COMMENTED:
92
+ map.set(
93
+ login,
94
+ new Review(APPROVED, r.publishedAt, r.bodyText, FROM_REVIEW_COMMENT)
95
+ );
96
+ break;
97
+ case DISMISSED:
98
+ // TODO: check the state of the dismissed review?
99
+ map.delete(login);
100
+ break;
101
+ }
102
+ }
103
+ return map;
104
+ }
105
+
106
+ // TODO: count -1 ...? But they should just make it explicit
107
+ /**
108
+ * @param {Map<string, Review>} oldMap
109
+ * @returns {Map<string, Review>}
110
+ */
111
+ updateMapByRawReviews(oldMap) {
112
+ const comments = this.comments;
113
+ const collaborators = this.collaborators;
114
+ const withLgtm = comments.filter((c) => this.hasLGTM(c))
115
+ .filter((c) => {
116
+ return (isCollaborator(collaborators, c.author));
117
+ }).sort((a, b) => {
118
+ return ascending(a.publishedAt, b.publishedAt);
119
+ });
120
+
121
+ for (const c of withLgtm) {
122
+ const login = c.author.login.toLowerCase();
123
+ const entry = oldMap.get(login);
124
+ if (!entry || entry.date < c.publishedAt) {
125
+ oldMap.set(
126
+ login,
127
+ // no url, have to use bodyText for refs
128
+ new Review(APPROVED, c.publishedAt, c.bodyText, FROM_COMMENT)
129
+ );
130
+ }
131
+ }
132
+ return oldMap;
133
+ }
134
+
135
+ /**
136
+ * @typedef {{reviwewer: Collaborator, review: Review}[]} ReviewerList
137
+ * @returns {{approved: ReviewerList, requestedChanges: ReviewerList}}
138
+ */
139
+ getReviewers() {
140
+ const ghReviews = this.mapByGithubReviews();
141
+ const reviewers = this.updateMapByRawReviews(ghReviews);
142
+ const result = {
143
+ approved: [],
144
+ requestedChanges: []
145
+ };
146
+ const collaborators = this.collaborators;
147
+ for (const [login, review] of reviewers) {
148
+ const reviewer = collaborators.get(login.toLowerCase());
149
+ if (review.state === APPROVED) {
150
+ result.approved.push({ reviewer, review });
151
+ } else if (review.state === CHANGES_REQUESTED) {
152
+ result.requestedChanges.push({ reviewer, review });
153
+ }
154
+ }
155
+ return result;
156
+ }
157
+
158
+ /**
159
+ * @param review
160
+ * @returns {boolean}
161
+ */
162
+ isApprovedInComment(review) {
163
+ return review.state === COMMENTED && this.hasLGTM(review);
164
+ }
165
+
166
+ /**
167
+ * @param object
168
+ * @param prop: string
169
+ * @returns {boolean}
170
+ */
171
+ hasLGTM(object) {
172
+ return LGTM_RE.test(object.bodyText.trim());
173
+ }
174
+ }
175
+
176
+ export const REVIEW_SOURCES = {
177
+ FROM_COMMENT, FROM_REVIEW, FROM_REVIEW_COMMENT
178
+ };
package/lib/run.js ADDED
@@ -0,0 +1,106 @@
1
+ import { spawn, spawnSync } from 'node:child_process';
2
+ import { fileURLToPath } from 'node:url';
3
+
4
+ import {
5
+ isDebugVerbosity,
6
+ debuglog
7
+ } from './verbosity.js';
8
+
9
+ export const IGNORE = '__ignore__';
10
+
11
+ function runAsyncBase(cmd, args, {
12
+ ignoreFailure = true,
13
+ spawnArgs,
14
+ input,
15
+ captureStdout = false
16
+ } = {}) {
17
+ if (cmd instanceof URL) {
18
+ cmd = fileURLToPath(cmd);
19
+ }
20
+ let stdio = 'inherit';
21
+ if (captureStdout || input != null) {
22
+ stdio = [input == null ? 'inherit' : 'pipe', captureStdout ? 'pipe' : 'inherit', 'inherit'];
23
+ }
24
+ return new Promise((resolve, reject) => {
25
+ const opt = Object.assign({
26
+ cwd: process.cwd(),
27
+ stdio
28
+ }, spawnArgs);
29
+ if (isDebugVerbosity()) {
30
+ debuglog('[Spawn]', `${cmd} ${(args || []).join(' ')}`, opt);
31
+ }
32
+ const child = spawn(cmd, args, opt);
33
+ let stdout;
34
+ if (captureStdout) {
35
+ stdout = '';
36
+ child.stdout.setEncoding('utf8');
37
+ child.stdout.on('data', (chunk) => { stdout += chunk; });
38
+ }
39
+ child.on('close', (code) => {
40
+ if (code !== 0) {
41
+ if (ignoreFailure) {
42
+ return reject(new Error(IGNORE));
43
+ }
44
+ const err = new Error(`${cmd} ${args} failed: ${code}`);
45
+ err.code = code;
46
+ err.messageOnly = true;
47
+ return reject(err);
48
+ }
49
+ if (captureStdout === 'lines') {
50
+ stdout = stdout.split(/\r?\n/g);
51
+ if (stdout[stdout.length - 1] === '') stdout.pop();
52
+ }
53
+ return resolve(stdout);
54
+ });
55
+ if (input != null) child.stdin.end(input);
56
+ });
57
+ }
58
+
59
+ export function forceRunAsync(cmd, args, options) {
60
+ return runAsyncBase(cmd, args, options).catch((error) => {
61
+ if (error.message !== IGNORE) {
62
+ if (!error.messageOnly) {
63
+ console.error(error);
64
+ }
65
+ throw error;
66
+ }
67
+ });
68
+ };
69
+
70
+ export function runPromise(promise) {
71
+ return promise.catch((error) => {
72
+ if (error.message !== IGNORE) {
73
+ console.error(error);
74
+ }
75
+ exit();
76
+ });
77
+ };
78
+
79
+ export function runAsync(cmd, args, options) {
80
+ return runPromise(runAsyncBase(cmd, args, options));
81
+ };
82
+
83
+ export function runSync(cmd, args, options) {
84
+ if (cmd instanceof URL) {
85
+ cmd = fileURLToPath(cmd);
86
+ }
87
+ const opt = Object.assign({
88
+ cwd: process.cwd()
89
+ }, options);
90
+ if (isDebugVerbosity()) {
91
+ debuglog('[SpawnSync]', `${cmd} ${(args || []).join(' ')}`, opt);
92
+ }
93
+ const child = spawnSync(cmd, args, opt);
94
+ if (child.error) {
95
+ throw child.error;
96
+ } else if (child.status !== 0) {
97
+ throw new Error(`${cmd} ${args} failed with stderr: ` +
98
+ child.stderr.toString());
99
+ } else {
100
+ return child.stdout.toString();
101
+ }
102
+ };
103
+
104
+ export function exit() {
105
+ process.exit(1);
106
+ };
package/lib/session.js ADDED
@@ -0,0 +1,415 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+
4
+ import { getMergedConfig, getNcuDir } from './config.js';
5
+ import { readJson, writeJson, readFile, writeFile } from './file.js';
6
+ import {
7
+ runAsync, runSync, forceRunAsync
8
+ } from './run.js';
9
+ import {
10
+ shortSha
11
+ } from './utils.js';
12
+
13
+ const APPLYING = 'APPLYING';
14
+ const STARTED = 'STARTED';
15
+ const AMENDING = 'AMENDING';
16
+
17
+ export default class Session {
18
+ constructor(cli, dir, prid, argv, warnForMissing = true) {
19
+ this.cli = cli;
20
+ this.dir = dir;
21
+ this.prid = prid;
22
+ this.config = { ...getMergedConfig(this.dir), ...argv };
23
+
24
+ if (warnForMissing) {
25
+ const { upstream, owner, repo } = this;
26
+ if (this.warnForMissing()) {
27
+ throw new Error('Failed to create new session');
28
+ }
29
+
30
+ const upstreamHref = runSync('git', [
31
+ 'config', '--get',
32
+ `remote.${upstream}.url`]).trim();
33
+ if (!new RegExp(`${owner}/${repo}(?:.git)?$`).test(upstreamHref)) {
34
+ cli.warn('Remote repository URL does not point to the expected ' +
35
+ `repository ${owner}/${repo}`);
36
+ cli.setExitCode(1);
37
+ }
38
+ }
39
+ }
40
+
41
+ get session() {
42
+ return readJson(this.sessionPath);
43
+ }
44
+
45
+ get gitDir() {
46
+ return path.join(this.dir, '.git');
47
+ }
48
+
49
+ get ncuDir() {
50
+ return getNcuDir(this.dir);
51
+ }
52
+
53
+ get argv() {
54
+ // TODO(joyeecheung): remove this and make argv an object
55
+ return {
56
+ owner: this.owner,
57
+ repo: this.repo,
58
+ upstream: this.upstream,
59
+ branch: this.branch,
60
+ readme: this.readme,
61
+ waitTimeSingleApproval: this.waitTimeSingleApproval,
62
+ waitTimeMultiApproval: this.waitTimeMultiApproval,
63
+ updateDeprecations: this.updateDeprecations,
64
+ ciType: this.ciType,
65
+ prid: this.prid,
66
+ checkCI: this.checkCI
67
+ };
68
+ }
69
+
70
+ get sessionPath() {
71
+ return path.join(this.ncuDir, 'land');
72
+ }
73
+
74
+ get owner() {
75
+ return this.config.owner || 'nodejs';
76
+ }
77
+
78
+ get repo() {
79
+ return this.config.repo || 'node';
80
+ }
81
+
82
+ get upstream() {
83
+ return this.config.upstream;
84
+ }
85
+
86
+ get branch() {
87
+ return this.config.branch;
88
+ }
89
+
90
+ get readme() {
91
+ return this.config.readme;
92
+ }
93
+
94
+ get waitTimeSingleApproval() {
95
+ return this.config.waitTimeSingleApproval;
96
+ }
97
+
98
+ get waitTimeMultiApproval() {
99
+ return this.config.waitTimeMultiApproval;
100
+ }
101
+
102
+ get ciType() {
103
+ return this.config.ciType || 'nodejs';
104
+ }
105
+
106
+ get pullName() {
107
+ return `${this.owner}/${this.repo}/pulls/${this.prid}`;
108
+ }
109
+
110
+ get pullDir() {
111
+ return path.join(this.ncuDir, `${this.prid}`);
112
+ }
113
+
114
+ get updateDeprecations() {
115
+ return this.config.updateDeprecations || 'yes';
116
+ }
117
+
118
+ startLanding() {
119
+ writeJson(this.sessionPath, {
120
+ state: STARTED,
121
+ prid: this.prid,
122
+ config: this.config
123
+ });
124
+ }
125
+
126
+ // TODO(joyeecheung): more states
127
+ // - STARTED (fetching metadata)
128
+ // - DOWNLOADING (downloading the patch)
129
+ // - PATCHING (git am)
130
+ // - AMENDING (git rebase or just amending messages)
131
+ // - DONE
132
+
133
+ startApplying() {
134
+ this.updateSession({
135
+ state: APPLYING
136
+ });
137
+ }
138
+
139
+ startAmending() {
140
+ this.updateSession({
141
+ state: AMENDING
142
+ });
143
+ }
144
+
145
+ cleanFiles() {
146
+ let sess;
147
+ try {
148
+ sess = this.session;
149
+ } catch (err) {
150
+ return fs.rmSync(this.sessionPath, { recursive: true, force: true });
151
+ }
152
+
153
+ if (sess.prid && sess.prid === this.prid) {
154
+ fs.rmSync(this.pullDir, { recursive: true, force: true });
155
+ }
156
+ fs.rmSync(this.sessionPath, { recursive: true, force: true });
157
+ }
158
+
159
+ get statusPath() {
160
+ return path.join(this.pullDir, 'status');
161
+ }
162
+
163
+ get status() {
164
+ return readJson(this.statusPath);
165
+ }
166
+
167
+ get metadataPath() {
168
+ return path.join(this.pullDir, 'metadata');
169
+ }
170
+
171
+ get metadata() {
172
+ return readFile(this.metadataPath);
173
+ }
174
+
175
+ get commitInfoPath() {
176
+ return path.join(this.pullDir, 'commit-info');
177
+ }
178
+
179
+ get commitInfo() {
180
+ return readJson(this.commitInfoPath);
181
+ }
182
+
183
+ getMessagePath(rev) {
184
+ return path.join(this.pullDir, `${shortSha(rev)}.COMMIT_EDITMSG`);
185
+ }
186
+
187
+ updateSession(update) {
188
+ const old = this.session;
189
+ writeJson(this.sessionPath, Object.assign(old, update));
190
+ }
191
+
192
+ saveStatus(status) {
193
+ writeJson(this.statusPath, status);
194
+ }
195
+
196
+ saveMetadata(status) {
197
+ writeFile(this.metadataPath, status.metadata);
198
+ }
199
+
200
+ saveCommitInfo(commitInfo) {
201
+ writeJson(this.commitInfoPath, commitInfo);
202
+ }
203
+
204
+ saveMessage(rev, message) {
205
+ const file = this.getMessagePath(rev);
206
+ writeFile(file, message);
207
+ return file;
208
+ }
209
+
210
+ hasStarted() {
211
+ return !!this.session.prid && this.session.prid === this.prid;
212
+ }
213
+
214
+ isApplying() {
215
+ return this.session.state === APPLYING;
216
+ }
217
+
218
+ readyToAmend() {
219
+ if (this.session.state === AMENDING) {
220
+ return true;
221
+ } else if (this.isApplying()) {
222
+ return !this.cherryPickInProgress();
223
+ } else {
224
+ return false;
225
+ }
226
+ }
227
+
228
+ readyToFinal() {
229
+ if (this.amInProgress() || this.cherryPickInProgress()) {
230
+ return false; // git am/rebase in progress
231
+ }
232
+ return this.session.state === AMENDING;
233
+ }
234
+
235
+ // Refs: https://github.com/git/git/blob/99de064/git-rebase.sh#L208-L228
236
+ // XXX: This may be unused at this point?
237
+ amInProgress() {
238
+ const amPath = path.join(this.gitDir, 'rebase-apply', 'applying');
239
+ return fs.existsSync(amPath);
240
+ }
241
+
242
+ rebaseInProgress() {
243
+ if (this.amInProgress()) {
244
+ return false;
245
+ }
246
+
247
+ const normalRebasePath = path.join(this.gitDir, 'rebase-apply');
248
+ const mergeRebasePath = path.join(this.gitDir, 'rebase-merge');
249
+ return fs.existsSync(normalRebasePath) || fs.existsSync(mergeRebasePath);
250
+ }
251
+
252
+ cherryPickInProgress() {
253
+ const cpPath = path.join(this.gitDir, 'CHERRY_PICK_HEAD');
254
+ return fs.existsSync(cpPath);
255
+ }
256
+
257
+ restore() {
258
+ const sess = this.session;
259
+ if (sess.prid) {
260
+ this.prid = sess.prid;
261
+ this.config = sess.config;
262
+ }
263
+ return this;
264
+ }
265
+
266
+ async tryAbortAm() {
267
+ const { cli } = this;
268
+ if (!this.amInProgress()) {
269
+ return cli.ok('No git am in progress');
270
+ }
271
+ const shouldAbortAm = await cli.prompt(
272
+ 'Abort previous git am sessions?');
273
+ if (shouldAbortAm) {
274
+ await forceRunAsync('git', ['am', '--abort']);
275
+ cli.ok('Aborted previous git am sessions');
276
+ }
277
+ }
278
+
279
+ async tryAbortCherryPick() {
280
+ const { cli } = this;
281
+ if (!this.cherryPickInProgress()) {
282
+ return cli.ok('No git cherry-pick in progress');
283
+ }
284
+ const shouldAbortCherryPick = await cli.prompt(
285
+ 'Abort previous git cherry-pick sessions?');
286
+ if (shouldAbortCherryPick) {
287
+ await forceRunAsync('git', ['cherry-pick', '--abort']);
288
+ cli.ok('Aborted previous git cherry-pick sessions');
289
+ }
290
+ }
291
+
292
+ async tryAbortRebase() {
293
+ const { cli } = this;
294
+ if (!this.rebaseInProgress()) {
295
+ return cli.ok('No git rebase in progress');
296
+ }
297
+ const shouldAbortRebase = await cli.prompt(
298
+ 'Abort previous git rebase sessions?');
299
+ if (shouldAbortRebase) {
300
+ await forceRunAsync('git', ['rebase', '--abort']);
301
+ cli.ok('Aborted previous git rebase sessions');
302
+ }
303
+ }
304
+
305
+ async tryResetBranch() {
306
+ const { cli, upstream, branch } = this;
307
+ await this.tryAbortCherryPick();
308
+ await this.tryAbortAm();
309
+ await this.tryAbortRebase();
310
+
311
+ const branchName = `${upstream}/${branch}`;
312
+ const shouldResetHead = await cli.prompt(
313
+ `Do you want to try reset the local ${branch} branch to ${branchName}?`);
314
+ if (shouldResetHead) {
315
+ await this.tryResetHead();
316
+ }
317
+ }
318
+
319
+ getCurrentRev() {
320
+ return runSync('git', ['rev-parse', 'HEAD']).trim();
321
+ }
322
+
323
+ getCurrentBranch() {
324
+ return runSync('git', ['rev-parse', '--abbrev-ref', 'HEAD']).trim();
325
+ }
326
+
327
+ getUpstreamHead() {
328
+ const { upstream, branch } = this;
329
+ return runSync('git', ['rev-parse', `${upstream}/${branch}`]).trim();
330
+ }
331
+
332
+ getStrayCommits(verbose) {
333
+ const { upstream, branch } = this;
334
+ const ref = `${upstream}/${branch}...HEAD`;
335
+ const gitCmd = verbose
336
+ ? ['log', '--oneline', '--reverse', ref]
337
+ : ['rev-list', '--reverse', ref];
338
+ const revs = runSync('git', gitCmd).trim();
339
+ return revs ? revs.split('\n') : [];
340
+ }
341
+
342
+ async tryResetHead() {
343
+ const { cli, upstream, branch } = this;
344
+ const branchName = `${upstream}/${branch}`;
345
+ cli.startSpinner(`Bringing ${branchName} up to date...`);
346
+ await runAsync('git', ['fetch', upstream, branch]);
347
+ cli.stopSpinner(`${branchName} is now up-to-date`);
348
+ const stray = this.getStrayCommits(true);
349
+ if (!stray.length) {
350
+ return;
351
+ }
352
+ cli.log(`${branch} is out of sync with ${branchName}. ` +
353
+ 'Mismatched commits:\n' +
354
+ ` - ${stray.join('\n - ')}`);
355
+ const shouldReset = await cli.prompt(`Reset to ${branchName}?`);
356
+ if (shouldReset) {
357
+ await runAsync('git', ['reset', '--hard', branchName]);
358
+ cli.ok(`Reset to ${branchName}`);
359
+ }
360
+ }
361
+
362
+ warnForMissing() {
363
+ const { upstream, branch, cli } = this;
364
+ const missing = !upstream || !branch;
365
+ if (!branch) {
366
+ cli.warn('You have not told git-node what branch you are trying' +
367
+ ' to land commits on.');
368
+ cli.separator();
369
+ cli.info(
370
+ 'For example, if your want to land commits on the ' +
371
+ '`main` branch, you can run:\n\n' +
372
+ ' $ ncu-config set branch main');
373
+ cli.separator();
374
+ cli.setExitCode(1);
375
+ }
376
+ if (!upstream) {
377
+ cli.warn('You have not told git-node the remote you want to sync with.');
378
+ cli.separator();
379
+ cli.info(
380
+ 'For example, if your remote pointing to nodejs/node is' +
381
+ ' `remote-upstream`, you can run:\n\n' +
382
+ ' $ ncu-config set upstream remote-upstream');
383
+ cli.separator();
384
+ cli.setExitCode(1);
385
+ }
386
+ return missing;
387
+ }
388
+
389
+ warnForWrongBranch() {
390
+ const { branch, cli } = this;
391
+ const rev = this.getCurrentBranch();
392
+ if (rev === 'HEAD') {
393
+ cli.warn(
394
+ 'You are in detached HEAD state. Please run git-node on a valid ' +
395
+ 'branch');
396
+ cli.setExitCode(1);
397
+ return true;
398
+ }
399
+ if (rev === branch) {
400
+ return false;
401
+ }
402
+ cli.warn(
403
+ `You are running git-node-land on \`${rev}\`,\n but you have` +
404
+ ` configured \`${branch}\` to be the branch to land commits.`);
405
+ cli.separator();
406
+ cli.info(
407
+ `You can switch to \`${branch}\` with \`git checkout ${branch}\`, or\n` +
408
+ ' reconfigure the target branch with:\n\n' +
409
+ ` $ ncu-config set branch ${rev}`);
410
+ cli.separator();
411
+ cli.setExitCode(1);
412
+ return true;
413
+ // TODO warn if backporting onto master branch
414
+ }
415
+ }
@@ -0,0 +1,15 @@
1
+ import Session from './session.js';
2
+
3
+ export default class SyncSession extends Session {
4
+ // eslint-disable-next-line no-useless-constructor
5
+ constructor(cli, dir) {
6
+ super(cli, dir);
7
+ }
8
+
9
+ async sync() {
10
+ if (this.warnForWrongBranch()) {
11
+ return;
12
+ }
13
+ return this.tryResetHead();
14
+ }
15
+ }