@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.
- package/LICENSE +7 -0
- package/README.md +158 -0
- package/bin/get-metadata.js +11 -0
- package/bin/git-node.js +30 -0
- package/bin/ncu-ci.js +600 -0
- package/bin/ncu-config.js +101 -0
- package/bin/ncu-team.js +76 -0
- package/components/git/backport.js +70 -0
- package/components/git/epilogue.js +18 -0
- package/components/git/land.js +223 -0
- package/components/git/metadata.js +94 -0
- package/components/git/release.js +99 -0
- package/components/git/security.js +35 -0
- package/components/git/status.js +32 -0
- package/components/git/sync.js +24 -0
- package/components/git/v8.js +121 -0
- package/components/git/vote.js +84 -0
- package/components/git/wpt.js +87 -0
- package/components/metadata.js +49 -0
- package/lib/auth.js +133 -0
- package/lib/backport_session.js +302 -0
- package/lib/cache.js +107 -0
- package/lib/cherry_pick.js +304 -0
- package/lib/ci/build-types/benchmark_run.js +72 -0
- package/lib/ci/build-types/citgm_build.js +194 -0
- package/lib/ci/build-types/citgm_comparison_build.js +174 -0
- package/lib/ci/build-types/commit_build.js +112 -0
- package/lib/ci/build-types/daily_build.js +24 -0
- package/lib/ci/build-types/fanned_build.js +87 -0
- package/lib/ci/build-types/health_build.js +63 -0
- package/lib/ci/build-types/job.js +114 -0
- package/lib/ci/build-types/linter_build.js +35 -0
- package/lib/ci/build-types/normal_build.js +89 -0
- package/lib/ci/build-types/pr_build.js +101 -0
- package/lib/ci/build-types/test_build.js +186 -0
- package/lib/ci/build-types/test_run.js +41 -0
- package/lib/ci/ci_failure_parser.js +325 -0
- package/lib/ci/ci_type_parser.js +203 -0
- package/lib/ci/ci_utils.js +106 -0
- package/lib/ci/failure_aggregator.js +152 -0
- package/lib/ci/jenkins_constants.js +28 -0
- package/lib/ci/run_ci.js +120 -0
- package/lib/cli.js +192 -0
- package/lib/collaborators.js +140 -0
- package/lib/config.js +72 -0
- package/lib/figures.js +7 -0
- package/lib/file.js +43 -0
- package/lib/github/templates/next-security-release.md +97 -0
- package/lib/github/tree.js +162 -0
- package/lib/landing_session.js +506 -0
- package/lib/links.js +123 -0
- package/lib/mergeable_state.js +3 -0
- package/lib/metadata_gen.js +61 -0
- package/lib/pr_checker.js +605 -0
- package/lib/pr_data.js +115 -0
- package/lib/pr_summary.js +62 -0
- package/lib/prepare_release.js +772 -0
- package/lib/prepare_security.js +117 -0
- package/lib/proxy.js +21 -0
- package/lib/queries/DefaultBranchRef.gql +8 -0
- package/lib/queries/LastCommit.gql +16 -0
- package/lib/queries/PR.gql +37 -0
- package/lib/queries/PRComments.gql +27 -0
- package/lib/queries/PRCommits.gql +45 -0
- package/lib/queries/PRs.gql +25 -0
- package/lib/queries/Reviews.gql +23 -0
- package/lib/queries/SearchIssue.gql +51 -0
- package/lib/queries/Team.gql +22 -0
- package/lib/queries/TreeEntries.gql +12 -0
- package/lib/queries/VotePRInfo.gql +28 -0
- package/lib/release/utils.js +53 -0
- package/lib/request.js +185 -0
- package/lib/review_state.js +5 -0
- package/lib/reviews.js +178 -0
- package/lib/run.js +106 -0
- package/lib/session.js +415 -0
- package/lib/sync_session.js +15 -0
- package/lib/team_info.js +95 -0
- package/lib/update-v8/applyNodeChanges.js +49 -0
- package/lib/update-v8/backport.js +258 -0
- package/lib/update-v8/commitUpdate.js +26 -0
- package/lib/update-v8/common.js +35 -0
- package/lib/update-v8/constants.js +86 -0
- package/lib/update-v8/index.js +56 -0
- package/lib/update-v8/majorUpdate.js +171 -0
- package/lib/update-v8/minorUpdate.js +105 -0
- package/lib/update-v8/updateMaintainingDependencies.js +34 -0
- package/lib/update-v8/updateV8Clone.js +53 -0
- package/lib/update-v8/updateVersionNumbers.js +122 -0
- package/lib/update-v8/util.js +62 -0
- package/lib/user.js +4 -0
- package/lib/user_status.js +5 -0
- package/lib/utils.js +66 -0
- package/lib/verbosity.js +26 -0
- package/lib/voting_session.js +136 -0
- package/lib/wpt/index.js +243 -0
- package/lib/wpt/templates/README.md +16 -0
- 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
|
+
}
|