Skip to content

Commit

Permalink
feat: added merge mode and rebase mode (#25)
Browse files Browse the repository at this point in the history
* feat: added merge mode and rebase mode

* feat: update readme
  • Loading branch information
baiwusanyu-c authored Apr 5, 2023
1 parent d2d66d8 commit e5f8d20
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 34 deletions.
14 changes: 14 additions & 0 deletions README.ZH-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,20 @@ pr-checker -v
pr-checker -h
````
#### -m | --mode
使用 rebase 模式 或者 merge 模式 , 默认值是 rebase 模式
> 在 `rebase` 模式中, 你可以选择仓库或这直接对所有你所提交的 `pr` 进行 `rebase` 操作
它将调用 `/repos/${repoName}/pulls/${prNumber}/update-branch`.
>在 `merge` 模式中,你可以对你所拥有的仓库(`fork` 的仓库除外)进行 `merge` 操作
它将调用 `/repos/${repoName}/pulls/${prNumber}/merge`.
一个典型的引用场景就是批量处理`dependabot`的 `pr` (加入到 `merge queue` 的功能还未完成)
```` shell
pr-checker run -m merge | rebase
````
## 快照
<img src="./public/img1.png" alt="Detect and update your Pull Requests in batches"/>
<img src="./public/img2.png" alt="Detect and update your Pull Requests in batches"/>
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,20 @@ pr-checker -v
#### -h | --help
Display help message
#### -m | --mode
Use `rebase` mode or `merge` mode, the default value is `rebase` mode
> In `rebase` mode, you can choose a repository or directly `rebase` all your submitted `pr`
It will call `/repos/${repoName}/pulls/${prNumber}/update-branch`.
>In `merge` mode, you can `merge` on repositories you own (except `fork` repositories)
It will call `/repos/${repoName}/pulls/${prNumber}/merge`.
A typical usage scenario is batch processing `pr` of `dependabot` (the function of adding to `merge queue` has not yet been completed)
```` shell
pr-checker run -m merge | rebase
````
```` shell
pr-checker -h
````
Expand Down
32 changes: 19 additions & 13 deletions packages/cli/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,27 @@ async function initCli() {
// get git config
cli.option('-g, --get', 'get git config')

cli.command('run', 'check your pr').action(async() => {
if (!storage.token) {
log('error', 'use `pr-checker -t <TOKEN>` to set your token')
process.exit(1)
}
cli.command('run', 'check your pr')
// use rebase or merge
.option('-m <value>, --mode <value>', 'use rebase or merge')
.action(async(options) => {
if (!storage.token) {
log('error', 'use `pr-checker -t <TOKEN>` to set your token')
process.exit(1)
}

if (!storage.username) {
log('info', 'You have not set a username, '
if (!storage.username) {
log('info', 'You have not set a username, '
+ 'it has been automatically set for you according to the token')
const { login } = await getUserName(storage.token)
storage.username = login
await saveStorage()
}
await handleSelect(storage as Storage)
})
const { login } = await getUserName(storage.token)
storage.username = login
await saveStorage()
}

const { m, mode } = options
const lastMode = m || mode || 'rebase'
await handleSelect(storage as Storage, lastMode)
})
cli.help()
cli.version(version)
return cli
Expand Down
29 changes: 18 additions & 11 deletions packages/cli/core/select/handle-select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import type { IPRCheckRes, IPRInfo, IPRListItem } from '@pr-checker/utils/git-ap
import type { Storage } from '../store/storage'
declare type IPRSelect = Record<string, IPRCheckRes[]>

export async function handleSelect(store: Storage) {
export async function handleSelect(store: Storage, mode: 'merge' | 'rebase') {
// select type ( all Repo ?)
const isAllRepo = await promptsRun(typeOption)

let updateRes = []
const spinner = ora({ text: 'Loading Repo......', color: 'blue' }).start()
const githubApi = new GitApi(store.token, store.username!)
const prList = await githubApi.getPRList()
const prList = mode === 'rebase' ? (await githubApi.getSubmitPRList()) : (await githubApi.getAllRepoPRList())
spinner.stop()
const repoList = Object.keys(prList)

Expand All @@ -26,12 +26,12 @@ export async function handleSelect(store: Storage) {
const prl = prList[selectRepo.RepoSelect as keyof typeof prList] as IPRListItem[]
// check pr
log('info', `Checking PR by ${selectRepo.RepoSelect}......`)
const prListByRepo = await checkPR(prl, githubApi)
const prListByRepo = await checkPR(prl, githubApi, mode)
// select pr
const prSelectRes = await promptsRun(createPrOption(prListByRepo as IPRCheckRes[]))
// update pr
log('info', `Update PR by ${selectRepo.RepoSelect}......`)
updateRes = await updatePR(prl, prSelectRes as IPRSelect, githubApi)
updateRes = await updatePR(prl, prSelectRes as IPRSelect, githubApi, mode)
} else {
const prl = [] as IPRListItem[]
repoList.forEach((val: string) => {
Expand All @@ -41,20 +41,20 @@ export async function handleSelect(store: Storage) {
})
// check pr
log('info', 'Checking PR......')
const prListByRepo = await checkPR(prl, githubApi)
const prListByRepo = await checkPR(prl, githubApi, mode)
// select pr
const prSelectRes = await promptsRun(createPrOption(prListByRepo as IPRCheckRes[]))
// update pr
log('info', 'Update PR......')
updateRes = await updatePR(prl, prSelectRes as IPRSelect, githubApi)
updateRes = await updatePR(prl, prSelectRes as IPRSelect, githubApi, mode)
}

spinner.succeed()
log('success', '✔ All PR updates completed')
await printUpdateRes(updateRes as IPRCheckRes[])
}

async function checkPR(prl: IPRListItem[], githubApi: GitApi) {
async function checkPR(prl: IPRListItem[], githubApi: GitApi, mode: 'merge' | 'rebase') {
if (prl.length === 0) {
log('error', 'Please select a pr to check')
process.exit()
Expand All @@ -63,7 +63,7 @@ async function checkPR(prl: IPRListItem[], githubApi: GitApi) {
// get pr detail data
const prInfo = await githubApi.getPRByRepo(prl[i].number, prl[i].repo, prl[i].title)
// need update pr ?
const res = await githubApi.needUpdate(prl[i].repo, prInfo as IPRInfo)
const res = await githubApi.needUpdate(prl[i].repo, prInfo as IPRInfo, mode)
log('success', `✔ Check PR #${prl[i].number} completed`)
return {
...prInfo,
Expand All @@ -73,12 +73,19 @@ async function checkPR(prl: IPRListItem[], githubApi: GitApi) {
return prListByRepo
}

async function updatePR(prl: IPRListItem[],
async function updatePR(
prl: IPRListItem[],
prSelectRes: IPRSelect,
githubApi: GitApi) {
githubApi: GitApi,
mode: 'merge' | 'rebase') {
const updateRes = await Promise.all(createRunList(prl.length, async(i: number) => {
if (prSelectRes.prSelect[i] && prSelectRes.prSelect[i].isNeedUpdate) {
await githubApi.updatePR(prSelectRes.prSelect[i].number, prSelectRes.prSelect[i].repo)
if (mode === 'rebase')
await githubApi.rebasePR(prSelectRes.prSelect[i].number, prSelectRes.prSelect[i].repo)

if (mode === 'merge')
await githubApi.mergePR(prSelectRes.prSelect[i].number, prSelectRes.prSelect[i].repo)

log('success', `✔ Update PR #${prl[i].number} completed`)
}
return {
Expand Down
77 changes: 68 additions & 9 deletions utils/git-api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Octokit } from '@octokit/core'
import { log } from '@pr-checker/utils'
import { createRunList, isEmptyObj, log } from '@pr-checker/utils'
export declare interface IPRListItem {
title: string
number: number
Expand Down Expand Up @@ -36,15 +36,18 @@ export class GitApi {
}

/**
* 获取账户下所有 pr
* 获取账户下所有提交 pr
* @param username
*/
async getPRList(username: string = this.owner) {
async getSubmitPRList(username: string = this.owner) {
try {
const { data } = await this.octokit.request('GET /search/issues', {
q: `is:pr is:open author:${username}`,
per_page: 1000,
})
if (!data.items || (data.items && data.items.length === 0))
log('error', 'You don\'t have any pull requests that are open')

const res = {} as Record<string, IPRListItem[]>
data.items.forEach((val: any) => {
const repo = val.repository_url.split('repos/')[1]
Expand All @@ -56,16 +59,55 @@ export class GitApi {
id: val.id,
})
})
if (!data.items || (data.items && data.items.length === 0))
log('error', 'You don\'t have any pull requests that are open')
return res
} catch (error: any) {
if (error.status === 401)
log('error', 'Your token is invalid or does not match your username')

log('error', error)
return {}
}
}

async getAllRepoPRList(username: string = this.owner) {
try {
const { data } = await this.octokit.request('GET /user/repos', {
type: 'owner',
per_page: 1000,
})
const notForkRepoList = data.filter(v => !v.fork)
const res = {} as Record<string, IPRListItem[]>
await Promise.all(createRunList(notForkRepoList.length, async(i: number) => {
const { data: prData } = await this.octokit.request(
'GET /repos/{owner}/{repo}/pulls',
{
owner: username,
repo: notForkRepoList[i].name,
per_page: 1000,
})

prData.forEach((val: any) => {
const repo = notForkRepoList[i].full_name
if (!res[repo]) res[repo] = []
res[repo].push({
title: val.title,
number: val.number,
repo,
id: val.id,
})
})
}))

if (isEmptyObj(res))
log('error', 'You don\'t have any pull requests')

return res
} catch (error: any) {
if (error.status === 401)
log('error', 'Your token is invalid or does not match your username')

log('error', error)
return []
return {}
}
}

Expand Down Expand Up @@ -108,8 +150,9 @@ export class GitApi {
* Determine whether pr wants to synchronize the upstream Repo
* @param repo_name upstream Repo name
* @param pr_info The getPRByRepo function returns the result
* @param mode
*/
async needUpdate(repo_name: string, pr_info: IPRInfo) {
async needUpdate(repo_name: string, pr_info: IPRInfo, mode: 'rebase' | 'merge') {
try {
if (pr_info.mergeable_state === 'dirty')
return { isNeedUpdate: false, reason: 'code conflict' }
Expand All @@ -122,7 +165,8 @@ export class GitApi {
// Comparison of the default branch hash of
// the fork warehouse and the upstream warehouse hash
const cur_sha = data[0].sha
if (pr_info.sha !== cur_sha
const matchedSha = (mode === 'rebase' && pr_info.sha !== cur_sha) || mode === 'merge'
if (matchedSha
&& pr_info.mergeable
&& !pr_info.merged)
return { isNeedUpdate: true, reason: '--' }
Expand All @@ -142,7 +186,7 @@ export class GitApi {
* @param pull_number
* @param repo_name
*/
async updatePR(pull_number: number, repo_name: string) {
async rebasePR(pull_number: number, repo_name: string) {
try {
const res = await this.octokit.request(
`PUT /repos/${repo_name}/pulls/{pull_number}/update-branch`,
Expand All @@ -156,6 +200,21 @@ export class GitApi {
return {}
}
}

async mergePR(pull_number: number, repo_name: string) {
try {
const res = await this.octokit.request(
`PUT /repos/${repo_name}/pulls/{pull_number}/merge`,
{
pull_number,
},
)
return res.data.message
} catch (error: any) {
log('error', error)
return {}
}
}
}

export async function getUserName(token: string) {
Expand Down
2 changes: 1 addition & 1 deletion utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export * from './git-api'

export function createRunList(
taskNum: number,
taskFunc: (index: number) => Promise<Record<any, any>>) {
taskFunc: (index: number) => Promise<Record<any, any> | void>) {
const taskList = [] as Array<Promise<any>>
for (let i = 0; i < taskNum; i++) {
taskList.push(new Promise((resolve) => {
Expand Down

0 comments on commit e5f8d20

Please sign in to comment.