Skip to content

Commit 6abf6e4

Browse files
committed
feat: bot creates branch, updates files, and creates pull request
1 parent 90370c8 commit 6abf6e4

File tree

15 files changed

+919
-71
lines changed

15 files changed

+919
-71
lines changed

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"dependencies": {
2626
"@probot/serverless-lambda": "^0.2.0",
2727
"all-contributors-cli": "^5.7.0",
28-
"probot": "^7.2.0"
28+
"probot": "^7.4.0"
2929
},
3030
"devDependencies": {
3131
"@tophat/eslint-config": "^0.1.4",
@@ -57,7 +57,9 @@
5757
"src/*.js",
5858
"!**/node_modules/**"
5959
],
60-
"setupFiles": ["<rootDir>/test/setup.js"],
60+
"setupFiles": [
61+
"<rootDir>/test/setup.js"
62+
],
6163
"setupTestFrameworkScriptFile": "<rootDir>/test/setupPerTest.js"
6264
},
6365
"jest-junit": {

src/ContentFiles/index.js

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,30 +21,34 @@ class ContentFiles {
2121
)
2222
}
2323

24-
this.contentFilesByPath = await this.repository.getMultipleFileContents(
24+
this.contentFilesByPath = await this.repository.getMultipleFiles(
2525
options.files,
2626
)
2727
} else {
28-
this.contentFilesByPath = await this.repository.getMultipleFileContents(
29-
['README.md'],
30-
)
28+
this.contentFilesByPath = await this.repository.getMultipleFiles([
29+
'README.md',
30+
])
3131
}
3232
}
3333

3434
async generate(optionsConfig) {
3535
const options = optionsConfig.get()
36-
const newReadmeFileContentsByPath = {}
36+
const newFilesByPath = {}
37+
debugger
3738
Object.entries(this.contentFilesByPath).forEach(
38-
([filePath, fileContents]) => {
39+
([filePath, { content, sha }]) => {
3940
const newFileContents = generateContentFile(
4041
options,
4142
options.contributors,
42-
fileContents,
43+
content,
4344
)
44-
newReadmeFileContentsByPath[filePath] = newFileContents
45+
newFilesByPath[filePath] = {
46+
content: newFileContents,
47+
originalSha: sha,
48+
}
4549
},
4650
)
47-
this.contentFilesByPath = newReadmeFileContentsByPath
51+
this.contentFilesByPath = newFilesByPath
4852
}
4953

5054
get() {

src/OptionsConfig/index.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,20 @@ const { addContributorWithDetails } = require('all-contributors-cli')
55
const { ResourceNotFoundError } = require('../utils/errors')
66

77
class OptionsConfig {
8-
constructor({ context, repository, commentReply }) {
9-
this.context = context
8+
constructor({ repository, commentReply }) {
109
this.repository = repository
1110
this.commentReply = commentReply
12-
this.options = null
11+
this.options
12+
this.originalOptionsSha
1313
}
1414

1515
async fetch() {
1616
try {
17-
const rawOptionsFileContent = await this.repository.getFileContents(
18-
ALL_CONTRIBUTORS_RC,
19-
)
17+
const {
18+
content: rawOptionsFileContent,
19+
sha,
20+
} = await this.repository.getFile(ALL_CONTRIBUTORS_RC)
21+
this.originalOptionsSha = sha
2022
try {
2123
const optionsConfig = JSON.parse(rawOptionsFileContent)
2224
this.options = optionsConfig
@@ -57,6 +59,10 @@ class OptionsConfig {
5759
return ALL_CONTRIBUTORS_RC
5860
}
5961

62+
getOriginalSha() {
63+
return this.originalOptionsSha
64+
}
65+
6066
async addContributor({ login, contributions, name, avatar_url, profile }) {
6167
// TODO: this method should also handle updating a contributor to avoid there previous contributions being blown away
6268
const newContributorsList = await addContributorWithDetails({

src/Repository/index.js

Lines changed: 101 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
11
const { ResourceNotFoundError } = require('../utils/errors')
22

33
class Repository {
4-
constructor(context) {
5-
this.context = context
6-
const { repo, owner } = context.repo()
4+
constructor({ repo, owner, github }) {
5+
this.github = github
76
this.repo = repo
87
this.owner = owner
9-
this.full_name = context.payload.repository.full_name
108
}
119

1210
getFullname() {
13-
return this.full_name
11+
return `${this.owner}/${this.repo}`
1412
}
1513

16-
async getFileContents(filePath) {
14+
async getFile(filePath) {
1715
// https://octokit.github.io/rest.js/#api-Repos-getContents
1816
let file
1917
try {
20-
file = await this.context.github.repos.getContents({
18+
file = await this.github.repos.getContents({
2119
owner: this.owner,
2220
repo: this.repo,
2321
path: filePath,
@@ -33,38 +31,120 @@ class Repository {
3331
// Contents can be an array if its a directory, should be an edge case, and we can just crash
3432
const contentBinary = file.data.content
3533
const content = Buffer.from(contentBinary, 'base64').toString()
36-
return content
34+
return {
35+
content,
36+
sha: file.data.sha,
37+
}
3738
}
3839

39-
async getMultipleFileContents(filePathsArray) {
40+
async getMultipleFiles(filePathsArray) {
4041
// TODO: can probably optimise this instead of sending a request per file
4142
const repository = this
4243

4344
const getFilesMultiple = filePathsArray.map(filePath => {
44-
return repository.getFileContents(filePath).then(content => ({
45+
return repository.getFile(filePath).then(({ content, sha }) => ({
4546
filePath,
4647
content,
48+
sha,
4749
}))
4850
})
4951

5052
const getFilesMultipleList = await Promise.all(getFilesMultiple)
51-
const multipleFileContentsByPath = {}
52-
getFilesMultipleList.forEach(({ filePath, content }) => {
53-
multipleFileContentsByPath[filePath] = content
53+
const multipleFilesByPath = {}
54+
getFilesMultipleList.forEach(({ filePath, content, sha }) => {
55+
multipleFilesByPath[filePath] = {
56+
content,
57+
sha,
58+
}
5459
})
5560

56-
return multipleFileContentsByPath
61+
return multipleFilesByPath
5762
}
5863

59-
async createPullRequest({ title, body, fileContentsByPath }) {
60-
// TODO: Create branch, update files
61-
// GET master state when we read files
64+
async getHeadRef() {
65+
const result = await this.github.git.getRef({
66+
owner: this.owner,
67+
repo: this.repo,
68+
ref: `heads/master`,
69+
})
70+
return result.data.object.sha
71+
}
72+
73+
async createBranch(branchName) {
74+
const fromSha = await this.getHeadRef()
75+
6276
// https://octokit.github.io/rest.js/#api-Git-createRef
63-
// https://octokit.github.io/rest.js/#api-Repos-updateFile
77+
await this.github.git.createRef({
78+
owner: this.owner,
79+
repo: this.repo,
80+
ref: `refs/heads/${branchName}`,
81+
sha: fromSha,
82+
})
83+
}
84+
85+
async updateFile({ filePath, content, branchName, originalSha }) {
86+
const contentBinary = Buffer.from(content).toString('base64')
87+
88+
//octokit.github.io/rest.js/#api-Repos-updateFile
89+
await this.github.repos.updateFile({
90+
owner: this.owner,
91+
repo: this.repo,
92+
path: filePath,
93+
message: `docs: update ${filePath}`,
94+
content: contentBinary,
95+
sha: originalSha,
96+
branch: branchName,
97+
})
98+
}
6499

65-
// TODO: post pull request
66-
// https://octokit.github.io/rest.js/#api-Pulls-createFromIssue
67-
const pullRequestNumber = 1
100+
async updateFiles({ filesByPath, branchName }) {
101+
// TODO: can probably optimise this instead of sending a request per file
102+
const repository = this
103+
const updateFilesMultiple = Object.entries(filesByPath).map(
104+
([filePath, { content, originalSha }]) => {
105+
return repository.updateFile({
106+
filePath,
107+
content,
108+
branchName,
109+
originalSha,
110+
})
111+
},
112+
)
113+
114+
await Promise.all(updateFilesMultiple)
115+
}
116+
117+
async createPullRequest({ title, body, branchName }) {
118+
const result = await this.github.pulls.create({
119+
owner: this.owner,
120+
repo: this.repo,
121+
title,
122+
body,
123+
head: branchName,
124+
base: 'master',
125+
maintainer_can_modify: true,
126+
})
127+
return result.data.number
128+
}
129+
130+
async createPullRequestFromFiles({
131+
title,
132+
body,
133+
filesByPath,
134+
branchName,
135+
}) {
136+
await this.createBranch(branchName)
137+
138+
await this.updateFiles({
139+
filesByPath,
140+
branchName,
141+
})
142+
143+
const pullRequestNumber = await this.createPullRequest({
144+
title,
145+
body,
146+
branchName,
147+
})
68148

69149
return pullRequestNumber
70150
}

src/processIssueComment.js

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ const { GIHUB_BOT_NAME } = require('./utils/settings')
1010
const { AllContributorBotError } = require('./utils/errors')
1111

1212
async function processIssueComment({ context, commentReply }) {
13-
const repository = new Repository(context)
13+
const repository = new Repository({
14+
...context.repo(),
15+
github: context.github,
16+
})
1417
const optionsConfig = new OptionsConfig({
15-
context,
1618
repository,
1719
commentReply,
1820
})
@@ -43,17 +45,23 @@ async function processIssueComment({ context, commentReply }) {
4345
})
4446
await contentFiles.fetch(optionsConfig)
4547
await contentFiles.generate(optionsConfig)
46-
const fileContentsByPathToUpdate = contentFiles.get()
47-
fileContentsByPathToUpdate[optionsConfig.getPath()] = optionsConfig.getRaw()
48+
const filesByPathToUpdate = contentFiles.get()
49+
filesByPathToUpdate[optionsConfig.getPath()] = {
50+
content: optionsConfig.getRaw(),
51+
originalSha: optionsConfig.getOriginalSha(),
52+
}
53+
54+
debugger
4855

49-
const pullRequestNumber = await repository.createPullRequest({
50-
title: `docs: add ${who} as contributor`,
56+
const pullRequestNumber = await repository.createPullRequestFromFiles({
57+
title: `docs: add ${who} as a contributor`,
5158
body: `Adds ${who} as a contributor for ${contributions.join(
52-
',',
53-
)}.\n\n This was requested by ${commentReply.replyingToWho()} on ${
59+
', ',
60+
)}.\n\nThis was requested by ${commentReply.replyingToWho()} [in this comment](${
5461
commentReply.replyingToWhere
55-
}`,
56-
fileContentsByPath: fileContentsByPathToUpdate,
62+
})`,
63+
filesByPath: filesByPathToUpdate,
64+
branchName: `all-contributors/add-${who}`,
5765
})
5866

5967
commentReply.reply(
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Repository createPullRequest with files 1`] = `
4+
Object {
5+
"ref": "refs/heads/all-contributors/add-jakebolam",
6+
"sha": "aa218f56b14c9653891f9e74264a383fa43fefbd",
7+
}
8+
`;
9+
10+
exports[`Repository createPullRequest with files 2`] = `
11+
Object {
12+
"branch": "all-contributors/add-jakebolam",
13+
"content": "eyJ0ZXN0IjoidGVzdCBjb250ZW50In0=",
14+
"message": "docs: update .all-contributorsrc",
15+
"sha": "aa218f56b14c9653891f9e74264a383fa43fefbe",
16+
}
17+
`;
18+
19+
exports[`Repository createPullRequest with files 3`] = `
20+
Object {
21+
"branch": "all-contributors/add-jakebolam",
22+
"content": "VXBkYXRlZCBsaXN0",
23+
"message": "docs: update README.md",
24+
"sha": "aa218f56b14c9653891f9e74264a383fa43fefbl",
25+
}
26+
`;
27+
28+
exports[`Repository createPullRequest with files 4`] = `
29+
Object {
30+
"branch": "all-contributors/add-jakebolam",
31+
"content": "VXBkYXRlZCBsaXN0IGluIG5lc3RlZCBmb2xkZXI=",
32+
"message": "docs: update /nested-folder/SOME-DOC.md",
33+
"sha": "aa218f56b14de653891f9e74264a383fa43fefbd",
34+
}
35+
`;
36+
37+
exports[`Repository createPullRequest with files 5`] = `
38+
Object {
39+
"base": "master",
40+
"body": "Pull request body",
41+
"head": "all-contributors/add-jakebolam",
42+
"maintainer_can_modify": true,
43+
"title": "Pull request title",
44+
}
45+
`;

0 commit comments

Comments
 (0)