Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added merge mode and rebase mode #25

Merged
merged 2 commits into from
Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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